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
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></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 excludeLists'></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>>
<<importTiddlers>>
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
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]]
<!--{{{-->
<div class='header' role='banner' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' role='navigation' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' role='navigation' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' role='complementary' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea' role='main'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

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

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

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

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

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

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

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

.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}

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

.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]];}
.readOnly {background:[[ColorPalette::TertiaryPale]];}

#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 0 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0 1em 1em; left:0; top:0;}

.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:0 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 0.3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

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

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

.tiddlerPopupButton {padding:0.2em;}
.popupTiddler {position: absolute; z-index:300; padding: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 0;}
.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 0 0 0.5em;}
.tab {margin:0 0 0 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 0 14em;}

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

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

.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:0 0.25em; padding:0 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:0 3px 0 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 0; font-size:.9em;}
.editorFooter .button {padding-top:0; padding-bottom:0;}

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

.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;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0; right:0;}
#backstageButton a {padding:0.1em 0.4em; margin:0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin-left:3em; padding:1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding: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;}
noscript {display:none;} /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
}
/*}}}*/
<!--{{{-->
<div class='toolbar' role='navigation' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></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>
<!--}}}-->
{{{
# a2enmod headers

<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin *
</IfModule>

<FilesMatch "\.(html|htm|js|css|vfb|ics)$">
FileETag None
<IfModule mod_headers.c>
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
</IfModule>
</FilesMatch>
}}}

/% <<reminder year:2013 month:5 day:28 tag:"timezone:UTC-0400" paramsfunction:replaceTiddlerFormatting() messagefunction:replaceTiddlerName() title:"@@background-color:#83b8c2;color:#000000;priority:20;10:00 - 11:30 : TIDDLERNAME@@ <<goToTiddler label:> title:[[TIDDLERFULLNAME]] focus:text>>">> %/

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

/% <<reminder year:2013 month:5 day:31 tag:"timezone:UTC-0400" paramsfunction:replaceTiddlerFormatting() messagefunction:replaceTiddlerName() title:"@@background-color:#c0ffee;color:#000000;priority:1;TIDDLERNAME@@ <<goToTiddler label:> title:[[TIDDLERFULLNAME]] focus:text>>">> %/

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

/% <<reminder year:2013 month:6 day:1 tag:"timezone:UTC-0400" paramsfunction:replaceTiddlerFormatting() messagefunction:replaceTiddlerName() title:"@@background-color:#c0ffee;color:#000000;priority:1;TIDDLERNAME@@ <<goToTiddler label:> title:[[TIDDLERFULLNAME]] focus:text>>">> %/

[_] cereal
[_] bread
@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@

//only for generate an <<tag Menu>> item
// // http://www.movable-type.co.uk/scripts/aes.html
//{{{
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES implementation in JavaScript (c) Chris Veness 2005-2011                                   */
/*   - see http://csrc.nist.gov/publications/PubsFIPS.html#197                                    */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Aes = {};  // Aes namespace

/**
 * AES Cipher function: encrypt 'input' state with Rijndael algorithm
 *   applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
 *
 * @param {Number[]} input 16-byte (128-bit) input state array
 * @param {Number[][]} w   Key schedule as 2D byte-array (Nr+1 x Nb bytes)
 * @returns {Number[]}     Encrypted output state array
 */
Aes.cipher = function(input, w) {    // main Cipher function [§5.1]
  var Nb = 4;               // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

  state = Aes.addRoundKey(state, w, 0, Nb);

  for (var round=1; round<Nr; round++) {
    state = Aes.subBytes(state, Nb);
    state = Aes.shiftRows(state, Nb);
    state = Aes.mixColumns(state, Nb);
    state = Aes.addRoundKey(state, w, round, Nb);
  }

  state = Aes.subBytes(state, Nb);
  state = Aes.shiftRows(state, Nb);
  state = Aes.addRoundKey(state, w, Nr, Nb);

  var output = new Array(4*Nb);  // convert state to 1-d array before returning [§3.4]
  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
  return output;
}

/**
 * Perform Key Expansion to generate a Key Schedule
 *
 * @param {Number[]} key Key as 16/24/32-byte array
 * @returns {Number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes)
 */
Aes.keyExpansion = function(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
  var Nb = 4;            // block size (in words): no of columns in state (fixed at 4 for AES)
  var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
  var Nr = Nk + 6;       // no of rounds: 10/12/14 for 128/192/256-bit keys

  var w = new Array(Nb*(Nr+1));
  var temp = new Array(4);

  for (var i=0; i<Nk; i++) {
    var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
    w[i] = r;
  }

  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
    w[i] = new Array(4);
    for (var t=0; t<4; t++) temp[t] = w[i-1][t];
    if (i % Nk == 0) {
      temp = Aes.subWord(Aes.rotWord(temp));
      for (var t=0; t<4; t++) temp[t] ^= Aes.rCon[i/Nk][t];
    } else if (Nk > 6 && i%Nk == 4) {
      temp = Aes.subWord(temp);
    }
    for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
  }

  return w;
}

/*
 * ---- remaining routines are private, not called externally ----
 */
 
Aes.subBytes = function(s, Nb) {    // apply SBox to state S [§5.1.1]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) s[r][c] = Aes.sBox[s[r][c]];
  }
  return s;
}

Aes.shiftRows = function(s, Nb) {    // shift row r of state S left by r bytes [§5.1.2]
  var t = new Array(4);
  for (var r=1; r<4; r++) {
    for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
    for (var c=0; c<4; c++) s[r][c] = t[c];         // and copy back
  }          // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
  return s;  // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}

Aes.mixColumns = function(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
  for (var c=0; c<4; c++) {
    var a = new Array(4);  // 'a' is a copy of the current column from 's'
    var b = new Array(4);  // 'b' is a·{02} in GF(2^8)
    for (var i=0; i<4; i++) {
      a[i] = s[i][c];
      b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;

    }
    // a[n] ^ b[n] is a·{03} in GF(2^8)
    s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
    s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
    s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
    s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
  }
  return s;
}

Aes.addRoundKey = function(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
  for (var r=0; r<4; r++) {
    for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
  }
  return state;
}

Aes.subWord = function(w) {    // apply SBox to 4-byte word w
  for (var i=0; i<4; i++) w[i] = Aes.sBox[w[i]];
  return w;
}

Aes.rotWord = function(w) {    // rotate 4-byte word w left by one byte
  var tmp = w[0];
  for (var i=0; i<3; i++) w[i] = w[i+1];
  w[3] = tmp;
  return w;
}

// sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]
Aes.sBox =  [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
             0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
             0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
             0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
             0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
             0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
             0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
             0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
             0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
             0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
             0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
             0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
             0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
             0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
             0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
             0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];

// rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
Aes.rCon = [ [0x00, 0x00, 0x00, 0x00],
             [0x01, 0x00, 0x00, 0x00],
             [0x02, 0x00, 0x00, 0x00],
             [0x04, 0x00, 0x00, 0x00],
             [0x08, 0x00, 0x00, 0x00],
             [0x10, 0x00, 0x00, 0x00],
             [0x20, 0x00, 0x00, 0x00],
             [0x40, 0x00, 0x00, 0x00],
             [0x80, 0x00, 0x00, 0x00],
             [0x1b, 0x00, 0x00, 0x00],
             [0x36, 0x00, 0x00, 0x00] ]; 


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2011                      */
/*   - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf                       */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

Aes.Ctr = {};  // Aes.Ctr namespace: a subclass or extension of Aes

/** 
 * Encrypt a text using AES encryption in Counter mode of operation
 *
 * Unicode multi-byte character safe
 *
 * @param {String} plaintext Source text to be encrypted
 * @param {String} password  The password to use to generate a key
 * @param {Number} nBits     Number of bits to be used in the key (128, 192, or 256)
 * @returns {string}         Encrypted text
 */
Aes.Ctr.encrypt = function(plaintext, password, nBits) {
  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  if (!(nBits==128 || nBits==192 || nBits==256)) return '';  // standard allows 128/192/256 bit keys
  plaintext = Utf8.encode(plaintext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer
	
  // use AES itself to encrypt password to get cipher key (using plain password as source for key 
  // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)
  var nBytes = nBits/8;  // no bytes in key (16/24/32)
  var pwBytes = new Array(nBytes);
  for (var i=0; i<nBytes; i++) {  // use 1st 16/24/32 chars of password for key
    pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
  }
  var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));  // gives us 16-byte key
  key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long

  // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec, 
  // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
  var counterBlock = new Array(blockSize);
  
  var nonce = (new Date()).getTime();  // timestamp: milliseconds since 1-Jan-1970
  var nonceMs = nonce%1000;
  var nonceSec = Math.floor(nonce/1000);
  var nonceRnd = Math.floor(Math.random()*0xffff);
  
  for (var i=0; i<2; i++) counterBlock[i]   = (nonceMs  >>> i*8) & 0xff;
  for (var i=0; i<2; i++) counterBlock[i+2] = (nonceRnd >>> i*8) & 0xff;
  for (var i=0; i<4; i++) counterBlock[i+4] = (nonceSec >>> i*8) & 0xff;
  
  // and convert it to a string to go on the front of the ciphertext
  var ctrTxt = '';
  for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);

  // generate key schedule - an expansion of the key into distinct Key Rounds for each round
  var keySchedule = Aes.keyExpansion(key);
  
  var blockCount = Math.ceil(plaintext.length/blockSize);
  var ciphertxt = new Array(blockCount);  // ciphertext as array of strings
  
  for (var b=0; b<blockCount; b++) {
    // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
    // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)
    for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
    for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)

    var cipherCntr = Aes.cipher(counterBlock, keySchedule);  // -- encrypt counter block --
    
    // block size is reduced on final block
    var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
    var cipherChar = new Array(blockLength);
    
    for (var i=0; i<blockLength; i++) {  // -- xor plaintext with ciphered counter char-by-char --
      cipherChar[i] = cipherCntr[i] ^ plaintext.charCodeAt(b*blockSize+i);
      cipherChar[i] = String.fromCharCode(cipherChar[i]);
    }
    ciphertxt[b] = cipherChar.join(''); 
  }

  // Array.join is more efficient than repeated string concatenation in IE
  var ciphertext = ctrTxt + ciphertxt.join('');
  ciphertext = Base64.encode(ciphertext);  // encode in base64
  
  //alert((new Date()) - t);
  return ciphertext;
}

/** 
 * Decrypt a text encrypted by AES in counter mode of operation
 *
 * @param {String} ciphertext Source text to be encrypted
 * @param {String} password   The password to use to generate a key
 * @param {Number} nBits      Number of bits to be used in the key (128, 192, or 256)
 * @returns {String}          Decrypted text
 */
Aes.Ctr.decrypt = function(ciphertext, password, nBits) {
  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
  if (!(nBits==128 || nBits==192 || nBits==256)) return '';  // standard allows 128/192/256 bit keys
  ciphertext = Base64.decode(ciphertext);
  password = Utf8.encode(password);
  //var t = new Date();  // timer
  
  // use AES to encrypt password (mirroring encrypt routine)
  var nBytes = nBits/8;  // no bytes in key
  var pwBytes = new Array(nBytes);
  for (var i=0; i<nBytes; i++) {
    pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
  }
  var key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
  key = key.concat(key.slice(0, nBytes-16));  // expand key to 16/24/32 bytes long

  // recover nonce from 1st 8 bytes of ciphertext
  var counterBlock = new Array(8);
  ctrTxt = ciphertext.slice(0, 8);
  for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
  
  // generate key schedule
  var keySchedule = Aes.keyExpansion(key);

  // separate ciphertext into blocks (skipping past initial 8 bytes)
  var nBlocks = Math.ceil((ciphertext.length-8) / blockSize);
  var ct = new Array(nBlocks);
  for (var b=0; b<nBlocks; b++) ct[b] = ciphertext.slice(8+b*blockSize, 8+b*blockSize+blockSize);
  ciphertext = ct;  // ciphertext is now array of block-length strings

  // plaintext will get generated block-by-block into array of block-length strings
  var plaintxt = new Array(ciphertext.length);

  for (var b=0; b<nBlocks; b++) {
    // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
    for (var c=0; c<4; c++) counterBlock[15-c] = ((b) >>> c*8) & 0xff;
    for (var c=0; c<4; c++) counterBlock[15-c-4] = (((b+1)/0x100000000-1) >>> c*8) & 0xff;

    var cipherCntr = Aes.cipher(counterBlock, keySchedule);  // encrypt counter block

    var plaintxtByte = new Array(ciphertext[b].length);
    for (var i=0; i<ciphertext[b].length; i++) {
      // -- xor plaintxt with ciphered counter byte-by-byte --
      plaintxtByte[i] = cipherCntr[i] ^ ciphertext[b].charCodeAt(i);
      plaintxtByte[i] = String.fromCharCode(plaintxtByte[i]);
    }
    plaintxt[b] = plaintxtByte.join('');
  }

  // join array of blocks into single plaintext string
  var plaintext = plaintxt.join('');
  plaintext = Utf8.decode(plaintext);  // decode from UTF8 back to Unicode multi-byte chars
  
  //alert((new Date()) - t);
  return plaintext;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2011                          */
/*    note: depends on Utf8 class                                                                 */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Base64 = {};  // Base64 namespace

Base64.code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 * Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, no newlines are added.
 *
 * @param {String} str The string to be encoded as base-64
 * @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded 
 *   to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters
 * @returns {String} Base64-encoded string
 */ 
Base64.encode = function(str, utf8encode) {  // http://tools.ietf.org/html/rfc4648
  utf8encode =  (typeof utf8encode == 'undefined') ? false : utf8encode;
  var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad = '', c, plain, coded;
  var b64 = Base64.code;
   
  plain = utf8encode ? str.encodeUTF8() : str;
  
  c = plain.length % 3;  // pad string to length of multiple of 3
  if (c > 0) { while (c++ < 3) { pad += '='; plain += '\0'; } }
  // note: doing padding here saves us doing special-case packing for trailing 1 or 2 chars
   
  for (c=0; c<plain.length; c+=3) {  // pack three octets into four hexets
    o1 = plain.charCodeAt(c);
    o2 = plain.charCodeAt(c+1);
    o3 = plain.charCodeAt(c+2);
      
    bits = o1<<16 | o2<<8 | o3;
      
    h1 = bits>>18 & 0x3f;
    h2 = bits>>12 & 0x3f;
    h3 = bits>>6 & 0x3f;
    h4 = bits & 0x3f;

    // use hextets to index into code string
    e[c/3] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
  }
  coded = e.join('');  // join() is far faster than repeated string concatenation in IE
  
  // replace 'A's from padded nulls with '='s
  coded = coded.slice(0, coded.length-pad.length) + pad;
   
  return coded;
}

/**
 * Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648]
 * (instance method extending String object). As per RFC 4648, newlines are not catered for.
 *
 * @param {String} str The string to be decoded from base-64
 * @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded 
 *   from UTF8 after conversion from base64
 * @returns {String} decoded string
 */ 
Base64.decode = function(str, utf8decode) {
  utf8decode =  (typeof utf8decode == 'undefined') ? false : utf8decode;
  var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;
  var b64 = Base64.code;

  coded = utf8decode ? str.decodeUTF8() : str;
  
  
  for (var c=0; c<coded.length; c+=4) {  // unpack four hexets into three octets
    h1 = b64.indexOf(coded.charAt(c));
    h2 = b64.indexOf(coded.charAt(c+1));
    h3 = b64.indexOf(coded.charAt(c+2));
    h4 = b64.indexOf(coded.charAt(c+3));
      
    bits = h1<<18 | h2<<12 | h3<<6 | h4;
      
    o1 = bits>>>16 & 0xff;
    o2 = bits>>>8 & 0xff;
    o3 = bits & 0xff;
    
    d[c/4] = String.fromCharCode(o1, o2, o3);
    // check for padding
    if (h4 == 0x40) d[c/4] = String.fromCharCode(o1, o2);
    if (h3 == 0x40) d[c/4] = String.fromCharCode(o1);
  }
  plain = d.join('');  // join() is far faster than repeated string concatenation in IE
   
  return utf8decode ? plain.decodeUTF8() : plain; 
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple          */
/*              single-byte character encoding (c) Chris Veness 2002-2011                         */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Utf8 = {};  // Utf8 namespace

/**
 * Encode multi-byte Unicode string into utf-8 multiple single-byte characters 
 * (BMP / basic multilingual plane only)
 *
 * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars
 *
 * @param {String} strUni Unicode string to be encoded as UTF-8
 * @returns {String} encoded string
 */
Utf8.encode = function(strUni) {
  // use regular expressions & String.replace callback function for better efficiency 
  // than procedural approaches
  var strUtf = strUni.replace(
      /[\u0080-\u07ff]/g,  // U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0);
        return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); }
    );
  strUtf = strUtf.replace(
      /[\u0800-\uffff]/g,  // U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzz
      function(c) { 
        var cc = c.charCodeAt(0); 
        return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); }
    );
  return strUtf;
}

/**
 * Decode utf-8 encoded string back into multi-byte Unicode characters
 *
 * @param {String} strUtf UTF-8 string to be decoded back to Unicode
 * @returns {String} decoded string
 */
Utf8.decode = function(strUtf) {
  // note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!
  var strUni = strUtf.replace(
      /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,  // 3-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = ((c.charCodeAt(0)&0x0f)<<12) | ((c.charCodeAt(1)&0x3f)<<6) | ( c.charCodeAt(2)&0x3f); 
        return String.fromCharCode(cc); }
    );
  strUni = strUni.replace(
      /[\u00c0-\u00df][\u0080-\u00bf]/g,                 // 2-byte chars
      function(c) {  // (note parentheses for precence)
        var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f;
        return String.fromCharCode(cc); }
    );
  return strUni;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
//}}}
<<tiddler ContactsFormTemplate>><data>{"last.name":"Adams","first.name":"Douglas","birthday":"1952-03-11"}</data>
/***
|''Name''|ActivitiesMacro|
|''Description''|Adds a reminder macro to the current tiddler |
|''Author''|Michael Borck|
|''Version''|0.2.5|
|''Date''|4 Dec 2008|
|''Status''|@@beta@@|
|''Source''|http://schedule.tiddlyspot.com/|
|''Copyright''|2008|
|''License:''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]] |
|''Feedback''|borck.m@gmail.com|
|''CoreVersion''|2.4|
|''Type''|Macro|
|''Keywords''|timetable, schedule, appointments, hard landscape, macro, reminders|


!Description
This macro adds a reminder to the current tiddler and is an extension/port of the newReminder macro.  It provides an alternative tool to construct a reminder.  When used in conjunction with the schedule macro the start and end promts are used to schedule the event.   If DatePicker is installed it will use that otherwise it will use a custom date picker (the same one provided in newReminder).

!Usage
!Usage
{{{<<addActivity [buttonName]>>>}}}

Where {{{[buttonName]}}} is a name you give to the slider button created.
!!!Code
***/
//{{{
if (!version.extensions.activities) {
	version.extensions.activites = {
		major: 0,
		minor: 2,
		revision: 5,
		date: new Date(2008, 4, 12),
		source: "http://schedule.tiddlyspot.com/"
	};

	config.macros.addActivity = {
		handler: function(place, macroName, params, wikifier, paramString, tiddler) {
			if (DatePicker) // Nice calendar popup
			{
				var format = "DD MMM YYYY";
				var dateBox = 'When:<input type="text" size="15" name="dateBox" value="' + new Date().formatString(format) + '">';
			}
			else // build our own date picker
			{
				var today = new Date();
				dateBox = 'Year: <select name="year"><option value="">Every year' + '</option>';
				for (var i = 0; i < 5; i++) {
					dateBox += '<option' + ((i == 0) ? ' selected': '') + ' value="' + (today.getFullYear() + i) + '">' + (today.getFullYear() + i) + '</option>';
				}
				dateBox += '</select>&nbsp;&nbsp;Month:<select name="month">' + '<option value="">Every month</option>';
				for (i = 0; i < 12; i++) {
					dateBox += '<option' + ((i == today.getMonth()) ? ' selected': '') + ' value="' + (i + 1) + '">' + config.messages.dates.months[i] + '</option>';
				}
				dateBox += '</select>&nbsp;&nbsp;Day:<select name="day">' + '<option value="">Every day</option>';
				for (i = 1; i < 32; i++) {
					dateBox += '<option' + ((i == (today.getDate())) ? ' selected': '') + ' value="' + i + '">' + i + '</option>';
				}
			}

			// Create form elements
			var start = 'From:<input type="text" size="5" name="start" value="09:00"' + 'onfocus="this.select();">';
			var end = 'to <input type="text" size="5" name="end" value="09:00"' + 'onfocus="this.select();">';
			var desc = 'What:<input type="text" size="41" name="title" ' + 'value="Please enter a description" onfocus="this.select();">';
			var btn = '<input type="button" value="add" ' + 'onclick="config.macros.addActivity.addEventToTiddler(this.form)">';
			var once = '<input type="radio" name="repeat" value="once" checked> Once';
			var daily = '<input type="radio" name="repeat" value="daily"> Daily';
			var weekly = '<input type="radio" name="repeat" value="weekly"> Weekly';
			var twoWeeks = '<input type="radio" name="repeat" value="twoWeeks"> Every 2 weeks';
			var fourWeeks = '<input type="radio" name="repeat" value="fourWeeks"> Every 4 weeks';
			var monthly = '<input type="radio" name="repeat" value="monthly"> Monthly';
			var yearly = '<input type="radio" name="repeat" value="yearly"> Yearly';
			var hidden = 'Hide: <input type="checkbox" name="hidden" value="hidden" checked>';

			var frequency = '<div>How often:' + once + daily + weekly + twoWeeks + fourWeeks + monthly + yearly + '</div>';

			// Construct form
			var formstring = '<br><html><form>' + desc + '<br><br>' + dateBox + '&nbsp;&nbsp;' + start + '&nbsp;&nbsp;' + end + '<br><br>' + frequency + '<br>' + hidden + '<br>' + btn + '</form></html>';
			var btnName = params[0] || "Add Activity";
			var panel = config.macros.slider.createSlider(place, null, btnName, "Open a form to add a new hidden reminder to this tiddler");
			wikify(formstring, panel, null, store.getTiddler(params[1]));

			// Form built, now attach calendar popup
			if (DatePicker) {
				var cb = function(el, objDate) {
					el.value = objDate.formatString(format);
				};
				var box = document.getElementsByName("dateBox")[0];
				DatePicker.create(box, new Date(), cb);
			}
		}
	};

	config.macros.addActivity.addEventToTiddler = function(form) {
		// Determine where to write the macro
		var title = window.story.findContainingTiddler(form).id.substr(7);
		var tiddler = store.getTiddler(title);

		// Determine options for reminder
		var eventDate = new Date(form.dateBox.value);
		var year = eventDate.getFullYear() || "";
		var month = eventDate.getMonth() ? (eventDate.getMonth() + 1) : "";
		var day = eventDate.getDate() || "";
		var frequency = "once";
		for (i = 0; i < form.repeat.length; i++) {
			if (form.repeat[i].checked) {
				frequency = form.repeat[i].value;
			}
		}
		var recurring = "";
		switch (frequency) {
		case "once":
			break;
		case "daily":
			recurring = 1;
			break;
		case "weekly":
			recurring = 7;
			break;
		case "twoWeeks":
			recurring = 14;
			break;
		case "fourWeeks":
			recurring = 28;
			break;
		case "monthly":
			month = "";
			break
		case "yearly":
			recurring = 365;
			break
		}

		// Build reminder macro
		var txt = '\n<<reminder ';
		txt += year ? 'year:' + year + ' ': "";
		txt += month ? 'month:' + month + ' ': "";
		txt += day ? 'day:' + day + ' ': "";
		txt += recurring ? 'recurdays:' + recurring + ' ': "";
		txt += form.start.value ? 'title:"' + form.start.value: ""
		txt += form.start.value && form.end.value ? '-' + form.end.value: "";
		txt += form.title.value ? ' ' + form.title.value + '" ': "";
		txt += form.hidden.checked ? ' hidden ': "";
		txt += ' >>';

		// Write the macro and refresh tiddler
		tiddler.set(null, tiddler.text + txt);
		window.story.refreshTiddler(title, 1, true);
		store.setDirty(true);
	};

}
//}}}
<<showtoc>>
<<EncryptionDecryptAll "Decrypt all" "Decrypt all tiddlers">> 

!Meetings

<<calendar thismonth name:meetings tag:"meeting AND NOT cancelled AND NOT attachment AND NOT Trash" tag-task:"task" tag-meeting:"meeting">>
<<matchTags popup "label:Meetings" "prompt:Meeting list" sort:+title meeting AND NOT attachment AND NOT Trash>>

<<showReminders format:"DIFF: TITLE" leadtime:21 limit tag:"meeting AND NOT cancelled AND NOT attachment AND NOT Trash">>

!Tasks

<<calendar thismonth name:tasks tag:"task AND NOT done AND NOT attachment AND NOT Trash" tag-task:"task" tag-meeting:"meeting">>
<<showReminders format:"DIFF: TITLE" leadtime:21 limit tag:"task AND NOT done AND NOT attachment AND NOT Trash">>

<<newTiddler
	label:"New task"
	prompt:"Create a new task"
	title:{{new Date().formatString(getTaskDateFomat()) + ' : '}}
	text:{{"@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@\n\n\<\<reminder " + ((function () {var date = new Date(); var timezoneHours = (date.getTimezoneOffset() / 60) | 0; var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60)); var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes; return "year:" + date.getFullYear() + " month:" + (date.getMonth() + 1) + " day:" + date.getDate() + " tag:\"timezone:UTC" + (date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone + "\"";})()) + " function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() title:\"@@background-color:" + config.macros.date.remindersbg + ";color:" + config.macros.date.reminderstxt + ";priority:" + config.macros.date.reminderspriority + ";\\\"\\\"\\\"TIDDLERNAME\\\"\\\"\\\"@@ \\<\\<goToTiddler label:> title:{" + "{'REMINDERDATE : TIDDLERNAMEESC'}" + "} text:{" + "{'@@background-color:#ffff00;[x(done)] \\'\\'Done&nbsp;\\'\\'@@\\n\\n'}" + "} tag:task tag:[[TIDDLERFULLNAME]] focus:text\\>\\>\"\>\>\n\n"}}
	focus:"title"
	tag:{{new Date().formatString(getTaskDateFomat())}}
	tag:"task"
>>

<<list filter "[tag[task AND NOT done AND NOT attachment AND NOT Trash]]">>
<<matchTags popup "label:Completed tasks" "prompt:Completed task list" sort:+title task AND done AND NOT attachment AND NOT Trash>>
<script>config.macros.fileDrop.icalendartag = "agenda";</script>/%
<<reminder year:1 month:1 day:1 "function:createICalendarReminders('agenda')" leadtime:7 tag:"meeting ics%UID%" "title:@@background-color:#83b8c2;color:#000000;priority:20; %START% - %END% : \"\"\"%SUMMARY%\"\"\"@@ \<\<goToTiddler label:> prompt:{{'%DESCRIPTION%'}} title:{{'%DATE% %START% - %END% : ' + '%SUMMARY%'}} text:{{'[x(cancelled)] Cancelled [x(doNotPublish)] Do not publish\n\n%DESCRIPTION%'}} tag:meeting tag:[[%ICS_TIDDLER_TITLE%]] tag:{{'ics' + '%UID%'}} focus:text\>\>">>
%/
<<calendar
	thismonth
	taggedLabel:"étiqueté"
	remindersLabel:"rappels"
	noRemindersLabel:"aucun"
	remindersFormat:"{noReminderFound:'&laquo; TITLE &raquo; n\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF - TITLE"
	newTaskLabel:"nouvelle tâche..."
	newTaskPrompt:"ajouter une tâche à '%0'"
	taskPrefix:"@@background-color:#ffff00;[x(done)] ''Terminé&nbsp;''@@"
	newMeetingLabel:"nouvelle rencontre..."
	newMeetingPrompt:"ajouter une rencontre à '%0'"
	meetingPrefix:"[x(cancelled)] Annulé"
	newEventPromptTitle:"Titre"
	weekNumberTitle:"S"
	allDayText:"Tâches"
	axisFormat:"HH:mm"
	slotMinutes:15
	firstDay:1
	timeFormat:"HH:mm{ - HH:mm}"
	columnFormat:"{
		month: 'ddd',
		week: 'ddd M/d',
		day: 'dddd M/d'
	}"
	titleFormat:"{
		month: 'MMMM yyyy',
		week: 'd MMM [ yyyy]{ \'&#8212;\' d [ MMM] yyyy}',
		day: 'dddd d MMM yyyy'
	}"
	buttonText:"{
		prev: '<span class=\'fc-text-arrow\'>&lsaquo;</span>',
		next: '<span class=\'fc-text-arrow\'>&rsaquo;</span>',
		prevYear: '<span class=\'fc-text-arrow\'>&laquo;</span>',
		nextYear: '<span class=\'fc-text-arrow\'>&raquo;</span>',
		today: 'aujourd\'hui',
		month: 'mois',
		week: 'semaine',
		day: 'jour'
	}"
	monthNames:"['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']"
	monthNamesShort:"['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jul', 'aoû', 'sep', 'oct', 'nov', 'déc']"
	dayNames:"['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']"
	dayNamesShort:"['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam']"
	tag:"(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT attachment AND NOT Trash"
	tag-task:"task"
	tag-meeting:"meeting">>
<<matchTags popup "label:Rencontres" "prompt:Liste de rencontres" sort:+title meeting AND NOT attachment AND NOT Trash>>

<<showReminders format:"{noReminderFound:'&laquo; TITLE &raquo; n\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF : TITLE" leadtime:21 limit tag:"(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT attachment AND NOT Trash">>

<<newTiddler
	label:"Nouvelle tâche"
	prompt:"Créer une nouvelle tâche"
	title:{{new Date().formatString(getTaskDateFomat()) + ' : '}}
	text:{{"@@background-color:#ffff00;[x(done)] ''Terminé&nbsp;''@@\n\n\<\<reminder " + ((function () {var date = new Date(); var timezoneHours = (date.getTimezoneOffset() / 60) | 0; var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60)); var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes; return "year:" + date.getFullYear() + " month:" + (date.getMonth() + 1) + " day:" + date.getDate() + " tag:\"timezone:UTC" + (date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone + "\"";})()) + " function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() format:\"{noReminderFound:'&laquo; TITLE &raquo; n\\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF - TITLE\" title:\"@@background-color:" + config.macros.date.remindersbg + ";color:" + config.macros.date.reminderstxt + ";priority:" + config.macros.date.reminderspriority + ";\\\"\\\"\\\"TIDDLERNAME\\\"\\\"\\\"@@ \\<\\<goToTiddler label:> title:{" + "{'REMINDERDATE : TIDDLERNAMEESC'}" + "} text:{" + "{'@@background-color:#ffff00;[x(done)] \\'\\'Terminé&nbsp;\\'\\'@@\\n\\n'}" + "} tag:task tag:[[TIDDLERFULLNAME]] focus:text\\>\\>\"\>\>\n\n"}}
	focus:"title"
	tag:{{new Date().formatString(getTaskDateFomat())}}
	tag:"task"
>>

<<list filter "[tag[task AND NOT done AND NOT attachment AND NOT Trash]]">>
<<matchTags popup "label:Tâches complétées" "prompt:Liste de tâches complétées" sort:+title task AND done AND NOT attachment AND NOT Trash>>


<<relatedTiddlers "Vue d'ensemble" tiddler:"all" tag:"task OR meeting">>
<<calendar
	nofullcalendar
	thismonth
	taggedLabel:"étiqueté"
	remindersLabel:"rappels"
	noRemindersLabel:"aucun"
	remindersFormat:"{noReminderFound:'&laquo; TITLE &raquo; n\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF - TITLE"
	newTaskLabel:"nouvelle tâche..."
	newTaskPrompt:"ajouter une tâche à '%0'"
	taskPrefix:"@@background-color:#ffff00;[x(done)] ''Terminé&nbsp;''@@"
	newMeetingLabel:"nouvelle rencontre..."
	newMeetingPrompt:"ajouter une rencontre à '%0'"
	meetingPrefix:"[x(cancelled)] Annulé"
	newEventPromptTitle:"Titre"
	weekNumberTitle:"S"
	allDayText:"Tâches"
	axisFormat:"HH:mm"
	slotMinutes:15
	firstDay:1
	timeFormat:"HH:mm{ - HH:mm}"
	columnFormat:"{
		month: 'ddd',
		week: 'ddd M/d',
		day: 'dddd M/d'
	}"
	titleFormat:"{
		month: 'MMMM yyyy',
		week: 'd MMM [ yyyy]{ \'&#8212;\' d [ MMM] yyyy}',
		day: 'dddd d MMM yyyy'
	}"
	buttonText:"{
		prev: '<span class=\'fc-text-arrow\'>&lsaquo;</span>',
		next: '<span class=\'fc-text-arrow\'>&rsaquo;</span>',
		prevYear: '<span class=\'fc-text-arrow\'>&laquo;</span>',
		nextYear: '<span class=\'fc-text-arrow\'>&raquo;</span>',
		today: 'aujourd\'hui',
		month: 'mois',
		week: 'semaine',
		day: 'jour'
	}"
	monthNames:"['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']"
	monthNamesShort:"['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jul', 'aoû', 'sep', 'oct', 'nov', 'déc']"
	dayNames:"['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']"
	dayNamesShort:"['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam']"
	tag:"(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT attachment AND NOT Trash"
	tag-task:"task"
	tag-meeting:"meeting">>
<<matchTags popup "label:Rencontres" "prompt:Liste de rencontres" sort:+title meeting AND NOT attachment AND NOT Trash>>

<<showReminders format:"{noReminderFound:'&laquo; TITLE &raquo; n\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF : TITLE" leadtime:21 limit tag:"(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT attachment AND NOT Trash">>

<<newTiddler
	label:"Nouvelle tâche"
	prompt:"Créer une nouvelle tâche"
	title:{{new Date().formatString(getTaskDateFomat()) + ' : '}}
	text:{{"@@background-color:#ffff00;[x(done)] ''Terminé&nbsp;''@@\n\n\<\<reminder " + ((function () {var date = new Date(); var timezoneHours = (date.getTimezoneOffset() / 60) | 0; var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60)); var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes; return "year:" + date.getFullYear() + " month:" + (date.getMonth() + 1) + " day:" + date.getDate() + " tag:\"timezone:UTC" + (date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone + "\"";})()) + " function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() format:\"{noReminderFound:'&laquo; TITLE &raquo; n\\'a pas été trouvé dans les prochains LEADTIMEUPPER jours.', todayString:'Aujourd\\'hui', tomorrowString:'Demain', ndaysString:'DIFF jours'}DIFF - TITLE\" title:\"@@background-color:" + config.macros.date.remindersbg + ";color:" + config.macros.date.reminderstxt + ";priority:" + config.macros.date.reminderspriority + ";\\\"\\\"\\\"TIDDLERNAME\\\"\\\"\\\"@@ \\<\\<goToTiddler label:> title:{" + "{'REMINDERDATE : TIDDLERNAMEESC'}" + "} text:{" + "{'@@background-color:#ffff00;[x(done)] \\'\\'Terminé&nbsp;\\'\\'@@\\n\\n'}" + "} tag:task tag:[[TIDDLERFULLNAME]] focus:text\\>\\>\"\>\>\n\n"}}
	focus:"title"
	tag:{{new Date().formatString(getTaskDateFomat())}}
	tag:"task"
>>

<<list filter "[tag[task AND NOT done AND NOT attachment AND NOT Trash]]">>
<<matchTags popup "label:Tâches complétées" "prompt:Liste de tâches complétées" sort:+title task AND done AND NOT attachment AND NOT Trash>>


<<relatedTiddlers "Vue d'ensemble" tiddler:"all" tag:"task OR meeting">>
//{{{
// config.options.chkAlwaysConfirmExit_AndTidWiki = true;
//}}}
//{{{
TiddlyWiki.prototype.isDirtyOverride_base = TiddlyWiki.prototype.isDirty;
TiddlyWiki.prototype.isDirty = function() {
	if (config.options.chkAlwaysConfirmExit_AndTidWiki) {
		return config.browser.isAndTidWiki() || this.isDirtyOverride_base();
	}
	return this.isDirtyOverride_base();
};
//}}}
//{{{
config.browser.isAndTidWiki = function () {
	var stackTrace = config.browser.isAndTidWiki_printStackTrace();
	return config.browser.isSafari && (config.userAgent.indexOf("android") != -1) && (stackTrace.indexOf("unknown source", stackTrace.length - "unknown source".length) != -1);
};
//}}}

/***
{{title{stacktrace-0.3.js}}}

https://github.com/emwendelin/javascript-stacktrace
https://github.com/emwendelin/javascript-stacktrace/downloads
***/
//{{{
// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
//                  Luke Smith http://lucassmith.name/ (2008)
//                  Loic Dachary <loic@dachary.org> (2008)
//                  Johan Euphrosine <proppy@aminche.com> (2008)
//                  Øyvind Sean Kinsey http://kinsey.no/blog (2010)
//
// Information and discussions
// http://jspoker.pokersource.info/skin/test-printstacktrace.html
// http://eriwen.com/javascript/js-stack-trace/
// http://eriwen.com/javascript/stacktrace-update/
// http://pastie.org/253058
//
// guessFunctionNameFromLines comes from firebug
//
// Software License Agreement (BSD License)
//
// Copyright (c) 2007, Parakey Inc.
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above
//   copyright notice, this list of conditions and the
//   following disclaimer.
//
// * Redistributions in binary form must reproduce the above
//   copyright notice, this list of conditions and the
//   following disclaimer in the documentation and/or other
//   materials provided with the distribution.
//
// * Neither the name of Parakey Inc. nor the names of its
//   contributors may be used to endorse or promote products
//   derived from this software without specific prior
//   written permission of Parakey Inc.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/**
 * Main function giving a function stack trace with a forced or passed in Error 
 *
 * @cfg {Error} e The error to create a stacktrace from (optional)
 * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
 * @return {Array} of Strings with functions, lines, files, and arguments where possible 
 */
config.browser.isAndTidWiki_printStackTrace = function printStackTrace(options) {
    var ex = (options && options.e) ? options.e : null;
    var guess = options ? !!options.guess : true;
    
    var p = new config.browser.isAndTidWiki_printStackTrace.implementation();
    var result = p.run(ex);
    return (guess) ? p.guessFunctions(result) : result;
}

config.browser.isAndTidWiki_printStackTrace.implementation = function() {};

config.browser.isAndTidWiki_printStackTrace.implementation.prototype = {
    run: function(ex) {
        ex = ex ||
            (function() {
                try {
                    var _err = __undef__ << 1;
                } catch (e) {
                    return e;
                }
            })();
        // Use either the stored mode, or resolve it
        var mode = this._mode || this.mode(ex);
        if (mode === 'other') {
            return this.other(arguments.callee);
        } else {
            return this[mode](ex);
        }
    },
    
    /**
     * @return {String} mode of operation for the environment in question.
     */
    mode: function(e) {
        if (e['arguments']) {
            return (this._mode = 'chrome');
        } else if (window.opera && e.stacktrace) {
            return (this._mode = 'opera10');
        } else if (e.stack) {
            return (this._mode = 'firefox');
        } else if (window.opera && !('stacktrace' in e)) { //Opera 9-
            return (this._mode = 'opera');
        }
        return (this._mode = 'other');
    },

    /**
     * Given a context, function name, and callback function, overwrite it so that it calls
     * printStackTrace() first with a callback and then runs the rest of the body.
     * 
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to instrument
     * @param {Function} function to call with a stack trace on invocation
     */
    instrumentFunction: function(context, functionName, callback) {
        context = context || window;
        context['_old' + functionName] = context[functionName];
        context[functionName] = function() { 
            callback.call(this, config.browser.isAndTidWiki_printStackTrace());
            return context['_old' + functionName].apply(this, arguments);
        };
        context[functionName]._instrumented = true;
    },
    
    /**
     * Given a context and function name of a function that has been
     * instrumented, revert the function to it's original (non-instrumented)
     * state.
     *
     * @param {Object} context of execution (e.g. window)
     * @param {String} functionName to de-instrument
     */
    deinstrumentFunction: function(context, functionName) {
        if (context[functionName].constructor === Function &&
                context[functionName]._instrumented &&
                context['_old' + functionName].constructor === Function) {
            context[functionName] = context['_old' + functionName];
        }
    },
    
    /**
     * Given an Error object, return a formatted Array based on Chrome's stack string.
     * 
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    chrome: function(e) {
        return e.stack.replace(/^[^\(]+?[\n$]/gm, '').replace(/^\s+at\s+/gm, '').replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@').split('\n');
    },

    /**
     * Given an Error object, return a formatted Array based on Firefox's stack string.
     * 
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    firefox: function(e) {
        return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
    },

    /**
     * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
     * 
     * @param e - Error object to inspect
     * @return Array<String> of function calls, files and line numbers
     */
    opera10: function(e) {
        var stack = e.stacktrace;
        var lines = stack.split('\n'), ANON = '{anonymous}',
            lineRE = /.*line (\d+), column (\d+) in ((<anonymous function\:?\s*(\S+))|([^\(]+)\([^\)]*\))(?: in )?(.*)\s*$/i, i, j, len;
        for (i = 2, j = 0, len = lines.length; i < len - 2; i++) {
            if (lineRE.test(lines[i])) {
                var location = RegExp.$6 + ':' + RegExp.$1 + ':' + RegExp.$2;
                var fnName = RegExp.$3;
                fnName = fnName.replace(/<anonymous function\:?\s?(\S+)?>/g, ANON);
                lines[j++] = fnName + '@' + location;
            }
        }
        
        lines.splice(j, lines.length - j);
        return lines;
    },
    
    // Opera 7.x-9.x only!
    opera: function(e) {
        var lines = e.message.split('\n'), ANON = '{anonymous}', 
            lineRE = /Line\s+(\d+).*script\s+(http\S+)(?:.*in\s+function\s+(\S+))?/i, 
            i, j, len;
        
        for (i = 4, j = 0, len = lines.length; i < len; i += 2) {
            //TODO: RegExp.exec() would probably be cleaner here
            if (lineRE.test(lines[i])) {
                lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + '()@' + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i + 1].replace(/^\s+/, '');
            }
        }
        
        lines.splice(j, lines.length - j);
        return lines;
    },
    
    // Safari, IE, and others
    other: function(curr) {
        var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i,
            stack = [], j = 0, fn, args;
        
        var maxStackSize = 10;
        while (curr && stack.length < maxStackSize) {
            fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
            args = Array.prototype.slice.call(curr['arguments']);
            stack[j++] = fn + '(' + this.stringifyArguments(args) + ')';
            curr = curr.caller;
        }
        return stack;
    },
    
    /**
     * Given arguments array as a String, subsituting type names for non-string types.
     *
     * @param {Arguments} object
     * @return {Array} of Strings with stringified arguments
     */
    stringifyArguments: function(args) {
        for (var i = 0; i < args.length; ++i) {
            var arg = args[i];
            if (arg === undefined) {
                args[i] = 'undefined';
            } else if (arg === null) {
                args[i] = 'null';
            } else if (arg.constructor) {
                if (arg.constructor === Array) {
                    if (arg.length < 3) {
                        args[i] = '[' + this.stringifyArguments(arg) + ']';
                    } else {
                        args[i] = '[' + this.stringifyArguments(Array.prototype.slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(Array.prototype.slice.call(arg, -1)) + ']';
                    }
                } else if (arg.constructor === Object) {
                    args[i] = '#object';
                } else if (arg.constructor === Function) {
                    args[i] = '#function';
                } else if (arg.constructor === String) {
                    args[i] = '"' + arg + '"';
                }
            }
        }
        return args.join(',');
    },
    
    sourceCache: {},
    
    /**
     * @return the text from a given URL.
     */
    ajax: function(url) {
        var req = this.createXMLHTTPObject();
        if (!req) {
            return;
        }
        req.open('GET', url, false);
        req.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
        req.send('');
        return req.responseText;
    },
    
    /**
     * Try XHR methods in order and store XHR factory.
     *
     * @return <Function> XHR function or equivalent
     */
    createXMLHTTPObject: function() {
        var xmlhttp, XMLHttpFactories = [
            function() {
                return new XMLHttpRequest();
            }, function() {
                return new ActiveXObject('Msxml2.XMLHTTP');
            }, function() {
                return new ActiveXObject('Msxml3.XMLHTTP');
            }, function() {
                return new ActiveXObject('Microsoft.XMLHTTP');
            }
        ];
        for (var i = 0; i < XMLHttpFactories.length; i++) {
            try {
                xmlhttp = XMLHttpFactories[i]();
                // Use memoization to cache the factory
                this.createXMLHTTPObject = XMLHttpFactories[i];
                return xmlhttp;
            } catch (e) {}
        }
    },

    /**
     * Given a URL, check if it is in the same domain (so we can get the source
     * via Ajax).
     *
     * @param url <String> source url
     * @return False if we need a cross-domain request
     */
    isSameDomain: function(url) {
        return url.indexOf(location.hostname) !== -1;
    },
    
    /**
     * Get source code from given URL if in the same domain.
     *
     * @param url <String> JS source URL
     * @return <String> Source code
     */
    getSource: function(url) {
        if (!(url in this.sourceCache)) {
            this.sourceCache[url] = this.ajax(url).split('\n');
        }
        return this.sourceCache[url];
    },
    
    guessFunctions: function(stack) {
        for (var i = 0; i < stack.length; ++i) {
            var reStack = /\{anonymous\}\(.*\)@(\w+:\/\/([\-\w\.]+)+(:\d+)?[^:]+):(\d+):?(\d+)?/;
            var frame = stack[i], m = reStack.exec(frame);
            if (m) {
                var file = m[1], lineno = m[4]; //m[7] is character position in Chrome
                if (file && this.isSameDomain(file) && lineno) {
                    var functionName = this.guessFunctionName(file, lineno);
                    stack[i] = frame.replace('{anonymous}', functionName);
                }
            }
        }
        return stack;
    },
    
    guessFunctionName: function(url, lineNo) {
        try {
            return this.guessFunctionNameFromLines(lineNo, this.getSource(url));
        } catch (e) {
            return 'getSource failed with url: ' + url + ', exception: ' + e.toString();
        }
    },
    
    guessFunctionNameFromLines: function(lineNo, source) {
        var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
        var reGuessFunction = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
        // Walk backwards from the first line in the function until we find the line which
        // matches the pattern above, which is the function definition
        var line = "", maxLines = 10;
        for (var i = 0; i < maxLines; ++i) {
            line = source[lineNo - i] + line;
            if (line !== undefined) {
                var m = reGuessFunction.exec(line);
                if (m && m[1]) {
                    return m[1];
                } else {
                    m = reFunctionArgNames.exec(line);
                    if (m && m[1]) {
                        return m[1];
                    }
                }
            }
        }
        return '(?)';
    }
};
//}}}
text/plain
.txt .text .js .vbs .asp .cgi .pl
----
text/html
.htm .html .hta .htx .mht
----
text/comma-separated-values
.csv
----
text/javascript
.js
----
text/css
.css
----
text/xml
.xml .xsl .xslt
----
image/gif
.gif
----
image/jpeg
.jpg .jpe .jpeg
----
image/png
.png
----
image/bmp
.bmp
----
image/tiff
.tif .tiff
----
audio/basic
.au .snd
----
audio/wav
.wav
----
audio/x-pn-realaudio
.ra .rm .ram
----
audio/x-midi
.mid .midi
----
audio/mp3
.mp3
----
audio/m3u
.m3u
----
video/x-ms-asf
.asf
----
video/avi
.avi
----
video/mpeg
.mpg .mpeg
----
video/quicktime
.qt .mov .qtvr
----
application/pdf
.pdf
----
application/rtf
.rtf
----
application/postscript
.ai .eps .ps
----
application/wordperfect
.wpd
----
application/mswrite
.wri
----
application/msexcel
.xls .xls3 .xls4 .xls5 .xlw
----
application/msword
.doc
----
application/mspowerpoint
.ppt .pps
----
application/x-director
.swa
----
application/x-shockwave-flash
.swf
----
application/x-zip-compressed
.zip
----
application/x-gzip
.gz
----
application/x-rar-compressed
.rar
----
application/octet-stream
.com .exe .dll .ocx
----
application/java-archive
.jar
text/plain
.txt .text .js .vbs .asp .cgi .pl
----
text/html
.htm .html .hta .htx .mht
----
text/comma-separated-values
.csv
----
text/javascript
.js
----
text/css
.css
----
text/xml
.xml .xsl .xslt
----
image/gif
.gif
----
image/jpeg
.jpg .jpe .jpeg
----
image/png
.png
----
image/bmp
.bmp
----
image/tiff
.tif .tiff
----
audio/basic
.au .snd
----
audio/wav
.wav
----
audio/x-pn-realaudio
.ra .rm .ram
----
audio/x-midi
.mid .midi
----
audio/mp3
.mp3
----
audio/m3u
.m3u
----
video/x-ms-asf
.asf
----
video/avi
.avi
----
video/mpeg
.mpg .mpeg
----
video/quicktime
.qt .mov .qtvr
----
application/pdf
.pdf
----
application/rtf
.rtf
----
application/postscript
.ai .eps .ps
----
application/wordperfect
.wpd
----
application/mswrite
.wri
----
application/msexcel
.xls .xls3 .xls4 .xls5 .xlw
----
application/msword
.doc
----
application/mspowerpoint
.ppt .pps
----
application/x-director
.swa
----
application/x-shockwave-flash
.swf
----
application/x-zip-compressed
.zip
----
application/x-gzip
.gz
----
application/x-rar-compressed
.rar
----
application/octet-stream
.com .exe .dll .ocx
----
application/java-archive
.jar
----
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xlsx
----
application/vnd.oasis.opendocument.spreadsheet
.ods
----
application/x-7z-compressed
.7z
/***
|Name|AttachFilePlugin|
|Source|http://www.TiddlyTools.com/#AttachFilePlugin|
|Documentation|http://www.TiddlyTools.com/#AttachFilePluginInfo|
|Version|4.0.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Requires|AttachFilePluginFormatters, AttachFileMIMETypes|
|Description|Store binary files as base64-encoded tiddlers with fallback links for separate local and/or remote file storage|
Store or link binary files (such as jpg, gif, pdf or even mp3) within your TiddlyWiki document and then use them as images or links from within your tiddler content.
> Important note: As of version 3.6.0, in order to //render// images and other binary attachments created with this plugin, you must also install [[AttachFilePluginFormatters]], which extends the behavior of the TiddlyWiki core formatters for embedded images ({{{[img[tooltip|image]]}}}), linked embedded images ({{{[img[tooltip|image][link]]}}}), and external/"pretty" links ({{{[[label|link]]}}}), so that these formatter will process references to attachment tiddlers as if a normal file reference had been provided. |
!!!!!Documentation
>see [[AttachFilePluginInfo]]
!!!!!Inline interface (live)
>see [[AttachFile]] (shadow tiddler)
><<tiddler AttachFile>>
!!!!!Revisions
<<<
2011.02.14 4.0.1 fix OSX error: use picker.file.path
2009.06.04 4.0.0 changed attachment storage format to use //sections// instead of embedded substring markers.
|please see [[AttachFilePluginInfo]] for additional revision details|
2005.07.20 1.0.0 Initial Release
<<<
!!!!!Code
***/
// // version
//{{{
version.extensions.AttachFilePlugin= {major: 4, minor: 0, revision: 1, date: new Date(2011,2,14)};

// shadow tiddler
config.shadowTiddlers.AttachFile="<<attach inline>>";

// add 'attach' backstage task (insert before built-in 'importTask')
if (config.tasks) { // for TW2.2b or above
	config.tasks.attachTask = {
		text: "attach",
		tooltip: "Attach a binary file as a tiddler",
		content: "<<attach inline>>"
	}
	config.backstageTasks.splice(config.backstageTasks.indexOf("importTask"),0,"attachTask");
}

config.macros.attach = {
// // lingo
//{{{
	label: "attach file",
	tooltip: "Attach a file to this document",
	linkTooltip: "Attachment: ",

	typeList: "AttachFileMIMETypes",

	titlePrompt: " enter tiddler title...",
	MIMEPrompt: "<option value=''>select MIME type...</option><option value='editlist'>[edit list...]</option>",
	localPrompt: " enter local path/filename...",
	URLPrompt: " enter remote URL...",

	tiddlerErr: "Please enter a tiddler title",
	sourceErr: "Please enter a source path/filename",
	storageErr: "Please select a storage method: embedded, local or remote",
	MIMEErr: "Unrecognized file format.  Please select a MIME type",
	localErr: "Please enter a local path/filename",
	URLErr: "Please enter a remote URL",
	fileErr: "Invalid path/file or file not found",

	tiddlerFormat: '!usage\n{{{%0}}}\n%0\n!notes\n%1\n!type\n%2\n!file\n%3\n!url\n%4\n!data\n%5\n',

//}}}
// // macro definition
//{{{
	handler:
	function(place,macroName,params) {
		if (params && !params[0])
			{ createTiddlyButton(place,this.label,this.tooltip,this.toggleAttachPanel); return; }
		var id=params.shift();
		this.createAttachPanel(place,id+"_attachPanel",params);
		document.getElementById(id+"_attachPanel").style.position="static";
		document.getElementById(id+"_attachPanel").style.display="block";
	},
//}}}
//{{{
	createAttachPanel:
	function(place,panel_id,params) {
		if (!panel_id || !panel_id.length) var panel_id="_attachPanel";
		// remove existing panel (if any)
		var panel=document.getElementById(panel_id); if (panel) panel.parentNode.removeChild(panel);
		// set styles for this panel
		setStylesheet(this.css,"attachPanel");
		// create new panel
		var title=""; if (params && params[0]) title=params.shift();
		var types=this.MIMEPrompt+this.formatListOptions(store.getTiddlerText(this.typeList)); // get MIME types
		panel=createTiddlyElement(place,"span",panel_id,"attachPanel",null);
		var html=this.html.replace(/%id%/g,panel_id);
		html=html.replace(/%title%/g,title);
		html=html.replace(/%disabled%/g,title.length?"disabled":"");
		html=html.replace(/%IEdisabled%/g,config.browser.isIE?"disabled":"");
		html=html.replace(/%types%/g,types);
		panel.innerHTML=html;
		if (config.browser.isGecko) { // FF3 FIXUP
			document.getElementById("attachSource").style.display="none";
			document.getElementById("attachFixPanel").style.display="block";
		}
		return panel;
	},
//}}}
//{{{
	toggleAttachPanel:
	function (e) {
		if (!e) var e = window.event;
		var parent=resolveTarget(e).parentNode;
		var panel = document.getElementById("_attachPanel");
		if (panel==undefined || panel.parentNode!=parent)
			panel=config.macros.attach.createAttachPanel(parent,"_attachPanel");
		var isOpen = panel.style.display=="block";
		if(config.options.chkAnimate)
			anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,"none"));
		else
			panel.style.display = isOpen ? "none" : "block" ;
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
		return(false);
	},
//}}}
//{{{
	formatListOptions:
	function(text) {
		if (!text || !text.trim().length) return "";
		// get MIME list content from text
		var parts=text.split("\n----\n");
		var out="";
		for (var p=0; p<parts.length; p++) {
			var lines=parts[p].split("\n");
			var label=lines.shift(); // 1st line=display text
			var value=lines.shift(); // 2nd line=item value
			out +='<option value="%1">%0</option>'.format([label,value]);
		}
		return out;
	},
//}}}
// // interface definition
//{{{
	css:
	".attachPanel { display: none; position:absolute; z-index:10; width:35em; right:105%; top:0em;\
		background-color: #eee; color:#000; font-size: 8pt; line-height:110%;\
		border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;\
		padding: 0.5em; margin:0em; -moz-border-radius:1em;-webkit-border-radius:1em; text-align:left }\
	.attachPanel form { display:inline;border:0;padding:0;margin:0; }\
	.attachPanel select { width:99%;margin:0px;font-size:8pt;line-height:110%;}\
	.attachPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%}\
	.attachPanel textarea { width:98%;margin:0px;height:2em;font-size:8pt;line-height:110%}\
	.attachPanel table { width:100%;border:0;margin:0;padding:0;color:inherit; }\
	.attachPanel tbody, .attachPanel tr, .attachPanel td { border:0;margin:0;padding:0;color:#000; }\
	.attachPanel .box { border:1px solid black; padding:.3em; margin:.3em 0px; background:#f8f8f8; \
		-moz-border-radius:5px;-webkit-border-radius:5px; }\
	.attachPanel .chk { width:auto;border:0; }\
	.attachPanel .btn { width:auto; }\
	.attachPanel .btn2 { width:49%; }\
	",
//}}}
//{{{
	html:
	'<form>\
		attach from source file\
		<input type="file" id="attachSource" name="source" size="56"\
			onChange="config.macros.attach.onChangeSource(this)">\
		<div id="attachFixPanel" style="display:none"><!-- FF3 FIXUP -->\
			<input type="text" id="attachFixSource" style="width:90%"\
				title="Enter a path/file to attach"\
				onChange="config.macros.attach.onChangeSource(this);">\
			<input type="button" style="width:7%" value="..."\
				title="Enter a path/file to attach"\
				onClick="config.macros.attach.askForFilename(document.getElementById(\'attachFixSource\'));">\
		</div><!--end FF3 FIXUP-->\
		<div class="box">\
		<table style="border:0"><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\
			embed data <input type=checkbox class=chk name="useData" %IEdisabled% \
				onclick="if (!this.form.MIMEType.value.length)\
					this.form.MIMEType.selectedIndex=this.checked?1:0; ">&nbsp;\
		</td><td style="border:0">\
			<select size=1 name="MIMEType" \
				onchange="this.title=this.value; if (this.value==\'editlist\')\
					{ this.selectedIndex=this.form.useData.checked?1:0; story.displayTiddler(null,config.macros.attach.typeList,2); return; }">\
				<option value=""></option>\
				%types%\
			</select>\
		</td></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\
			local link <input type=checkbox class=chk name="useLocal"\
				onclick="this.form.local.value=this.form.local.defaultValue=this.checked?config.macros.attach.localPrompt:\'\';">&nbsp;\
		</td><td style="border:0">\
			<input type=text name="local" size=15 autocomplete=off value=""\
				onchange="this.form.useLocal.checked=this.value.length" \
				onkeyup="this.form.useLocal.checked=this.value.length" \
				onfocus="if (!this.value.length) this.value=config.macros.attach.localPrompt; this.select()">\
		</td></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\
			remote link <input type=checkbox class=chk name="useURL"\
				onclick="this.form.URL.value=this.form.URL.defaultValue=this.checked?config.macros.attach.URLPrompt:\'\';\">&nbsp;\
		</td><td style="border:0">\
			<input type=text name="URL" size=15 autocomplete=off value=""\
				onfocus="if (!this.value.length) this.value=config.macros.attach.URLPrompt; this.select()"\
				onchange="this.form.useURL.checked=this.value.length;"\
				onkeyup="this.form.useURL.checked=this.value.length;">\
		</td></tr></table>\
		</div>\
		<table style="border:0"><tr style="border:0"><td style="border:0;text-align:right;vertical-align:top;width:1%;white-space:nowrap">\
			notes&nbsp;\
		</td><td style="border:0" colspan=2>\
			<textarea name="notes" style="width:98%;height:3.5em;margin-bottom:2px"></textarea>\
		</td><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\
			attach as&nbsp;\
		</td><td style="border:0" colspan=2>\
			<input type=text name="tiddlertitle" size=15 autocomplete=off value="%title%"\
				onkeyup="if (!this.value.length) { this.value=config.macros.attach.titlePrompt; this.select(); }"\
				onfocus="if (!this.value.length) this.value=config.macros.attach.titlePrompt; this.select()" %disabled%>\
		</td></tr></tr><tr style="border:0"><td style="border:0;text-align:right;width:1%;white-space:nowrap">\
			add tags&nbsp;\
		</td><td style="border:0">\
			<input type=text name="tags" size=15 autocomplete=off value="" onfocus="this.select()">\
		</td><td style="width:40%;text-align:right;border:0">\
			<input type=button class=btn2 value="attach"\
				onclick="config.macros.attach.onClickAttach(this)"><!--\
			--><input type=button class=btn2 value="close"\
				onclick="var panel=document.getElementById(\'%id%\'); if (panel) panel.parentNode.removeChild(panel);">\
		</td></tr></table>\
	</form>',
//}}}
// // control processing
//{{{
	onChangeSource:
	function(here) {
		var form=here.form;
		var list=form.MIMEType;
		var theFilename  = here.value;
		var theExtension = theFilename.substr(theFilename.lastIndexOf('.')).toLowerCase();
		// if theFilename is in current document folder, remove path prefix and use relative reference
		var h=document.location.href; folder=getLocalPath(decodeURIComponent(h.substr(0,h.lastIndexOf("/")+1)));
		if (theFilename.substr(0,folder.length)==folder) theFilename='./'+theFilename.substr(folder.length);
		else theFilename='file:///'+theFilename; // otherwise, use absolute reference
		theFilename=theFilename.replace(/\\/g,"/"); // fixup: change \ to /
		form.useLocal.checked = true;
		form.local.value = theFilename;
		form.useData.checked = !form.useData.disabled;
		list.selectedIndex=1;
		for (var i=0; i<list.options.length; i++) // find matching MIME type
			if (list.options[i].value.indexOf(theExtension)!=-1) { list.selectedIndex = i; break; }
		if (!form.tiddlertitle.disabled)
			form.tiddlertitle.value=theFilename.substr(theFilename.lastIndexOf('/')+1); // get tiddlername from filename
	},
//}}}
//{{{
	onClickAttach:
	function (here) {
		clearMessage();
		// get input values
		var form=here.form;
		var src=form.source; if (config.browser.isGecko) src=document.getElementById("attachFixSource");
		src=src.value!=src.defaultValue?src.value:"";
		var when=(new Date()).formatString(config.macros.timeline.dateFormat);
		var title=form.tiddlertitle.value;
		var local = form.local.value!=form.local.defaultValue?form.local.value:"";
		var url = form.URL.value!=form.URL.defaultValue?form.URL.value:"";
		var notes = form.notes.value;
		var tags = "attachment excludeMissing "+form.tags.value;
		var useData=form.useData.checked;
		var useLocal=form.useLocal.checked;
		var useURL=form.useURL.checked;
		var mimetype = form.MIMEType.value.length?form.MIMEType.options[form.MIMEType.selectedIndex].text:"";
		// validate checkboxes and get filename
		if (useData) {
			if (src.length) { if (!theLocation) var theLocation=src; }
			else { alert(this.sourceErr); src.focus(); return false; }
		}
		if (useLocal) {
			if (local.length) { if (!theLocation) var theLocation = local; }
			else { alert(this.localErr); form.local.focus(); return false; }
		}
		if (useURL) {
			if (url.length) { if (!theLocation) var theLocation = url; }
			else { alert(this.URLErr); form.URL.focus(); return false; }
		}
		if (!(useData||useLocal||useURL))
			{ form.useData.focus(); alert(this.storageErr); return false; }
		if (!theLocation)
			{ src.focus(); alert(this.sourceErr); return false; }
		if (!title || !title.trim().length || title==this.titlePrompt)
			{ form.tiddlertitle.focus(); alert(this.tiddlerErr); return false; }
		// if not already selected, determine MIME type based on filename extension (if any)
		if (useData && !mimetype.length && theLocation.lastIndexOf('.')!=-1) {
			var theExt = theLocation.substr(theLocation.lastIndexOf('.')).toLowerCase();
			var theList=form.MIMEType;
			for (var i=0; i<theList.options.length; i++)
				if (theList.options[i].value.indexOf(theExt)!=-1)
					{ var mimetype=theList.options[i].text; theList.selectedIndex=i; break; }
		}
		// attach the file
		return this.createAttachmentTiddler(src, when, notes, tags, title,
			useData, useLocal, useURL, local, url, mimetype);
	},
	getMIMEType:
	function(src,def) {
		var ext = src.substr(src.lastIndexOf('.')).toLowerCase();
		var list=store.getTiddlerText(this.typeList);
		if (!list || !list.trim().length) return def;
		// get MIME list content from tiddler
		var parts=list.split("\n----\n");
		for (var p=0; p<parts.length; p++) {
			var lines=parts[p].split("\n");
			var mime=lines.shift(); // 1st line=MIME type
			var match=lines.shift(); // 2nd line=matching extensions
			if (match.indexOf(ext)!=-1) return mime;
		}
		return def;
	},
	createAttachmentTiddler:
	function (src, when, notes, tags, title, useData, useLocal, useURL, local, url, mimetype, noshow) {
		if (useData) { // encode the data
			if (!mimetype.length) {
				alert(this.MIMEErr);
				form.MIMEType.selectedIndex=1; form.MIMEType.focus();
				return false;
			}
			var d = this.readFile(src); if (!d) { return false; }
			displayMessage('encoding '+src);
			var encoded = this.encodeBase64(d);
			displayMessage('file size='+d.length+' bytes, encoded size='+encoded.length+' bytes');
		}
		var usage=(mimetype.substr(0,5)=="image"?'[img[%0]]':'[[%0|%0]]').format([title]);
		var theText=this.tiddlerFormat.format([
			usage, notes.length?notes:'//none//', mimetype,
			useLocal?local.replace(/\\/g,'/'):'', useURL?url:'',
			useData?('data:'+mimetype+';base64,'+encoded):'' ]);
		store.saveTiddler(title,title,theText,config.options.txtUserName,new Date(),tags);
		var panel=document.getElementById("attachPanel"); if (panel) panel.style.display="none";
		if (!noshow) { story.displayTiddler(null,title); story.refreshTiddler(title,null,true); }
		displayMessage('attached "'+title+'"');
		return true;
	},
//}}}
// // base64 conversion
//{{{
	encodeBase64:
	function (d) {
		if (!d) return null;
		// encode as base64
		var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
		var out="";
		var chr1,chr2,chr3="";
		var enc1,enc2,enc3,enc4="";
		for (var count=0,i=0; i<d.length; ) {
			chr1=d.charCodeAt(i++);
			chr2=d.charCodeAt(i++);
			chr3=d.charCodeAt(i++);
			enc1=chr1 >> 2;
			enc2=((chr1 & 3) << 4) | (chr2 >> 4);
			enc3=((chr2 & 15) << 2) | (chr3 >> 6);
			enc4=chr3 & 63;
			if (isNaN(chr2)) enc3=enc4=64;
			else if (isNaN(chr3)) enc4=64;
			out+=keyStr.charAt(enc1)+keyStr.charAt(enc2)+keyStr.charAt(enc3)+keyStr.charAt(enc4);
			chr1=chr2=chr3=enc1=enc2=enc3=enc4="";
		}
		return out;
	},
	decodeBase64: function(input) {
		var out="";
		var chr1,chr2,chr3;
		var enc1,enc2,enc3,enc4;
		var i = 0;
		// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
		input=input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
		do {
			enc1=keyStr.indexOf(input.charAt(i++));
			enc2=keyStr.indexOf(input.charAt(i++));
			enc3=keyStr.indexOf(input.charAt(i++));
			enc4=keyStr.indexOf(input.charAt(i++));
			chr1=(enc1 << 2) | (enc2 >> 4);
			chr2=((enc2 & 15) << 4) | (enc3 >> 2);
			chr3=((enc3 & 3) << 6) | enc4;
			out=out+String.fromCharCode(chr1);
			if (enc3!=64) out=out+String.fromCharCode(chr2);
			if (enc4!=64) out=out+String.fromCharCode(chr3);
		} while (i<input.length);
		return out;
	},
//}}}
// // I/O functions
//{{{
	readFile: // read local BINARY file data
	function(filePath) {
		if(!window.Components) { return null; }
		try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); }
		catch(e) { alert("access denied: "+filePath); return null; }
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		try { file.initWithPath(filePath); } catch(e) { alert("cannot read file - invalid path: "+filePath); return null; }
		if (!file.exists()) { alert("cannot read file - not found: "+filePath); return null; }
		var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
		inputStream.init(file, 0x01, 00004, null);
		var bInputStream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
		bInputStream.setInputStream(inputStream);
		return(bInputStream.readBytes(inputStream.available()));
	},
//}}}
//{{{
	writeFile:
	function(filepath,data) {
		// TBD: decode base64 and write BINARY data to specified local path/filename
		return(false);
	},
//}}}
//{{{
	askForFilename: // for FF3 fixup
	function(target) {
		var msg=config.messages.selectFile;
		if (target && target.title) msg=target.title; // use target field tooltip (if any) as dialog prompt text
		// get local path for current document
		var path=getLocalPath(document.location.href);
		var p=path.lastIndexOf("/"); if (p==-1) p=path.lastIndexOf("\\"); // Unix or Windows
		if (p!=-1) path=path.substr(0,p+1); // remove filename, leave trailing slash
		var file=""
		var result=window.mozAskForFilename(msg,path,file,true); // FF3 FIXUP ONLY
		if (target && result.length) // set target field and trigger handling
			{ target.value=result; target.onchange(); }
		return result; 
	}
};
//}}}
//{{{
if (window.mozAskForFilename===undefined) { // also defined by CoreTweaks (for ticket #604)
	window.mozAskForFilename=function(msg,path,file,mustExist) {
		if(!window.Components) return false;
		try {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
			var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
			var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
			picker.init(window, msg, mustExist?nsIFilePicker.modeOpen:nsIFilePicker.modeSave);
			var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
			thispath.initWithPath(path);
			picker.displayDirectory=thispath;
			picker.defaultExtension='';
			picker.defaultString=file;
			picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
			if (picker.show()!=nsIFilePicker.returnCancel)
				var result=picker.file.path;
		}
		catch(ex) { displayMessage(ex.toString()); }
		return result;
	}
}
//}}}
/***
|Name|AttachFilePluginFormatters|
|Source|http://www.TiddlyTools.com/#AttachFilePluginFormatters|
|Version|4.0.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1.3|
|Type|plugin|
|Description|run-time library for displaying attachment tiddlers|
Runtime processing for //rendering// attachment tiddlers created by [[AttachFilePlugin]].   Attachment tiddlers are tagged with<<tag attachment>>and contain binary file content (e.g., jpg, gif, pdf, mp3, etc.) that has been stored directly as base64 text-encoded data or can be loaded from external files stored on a local filesystem or remote web server.  Note: after creating new attachment tiddlers, you can remove [[AttachFilePlugin]], as long as you retain //this// tiddler (so that images can be rendered later on).
!!!!!Formatters
<<<
This plugin extends the behavior of the following TiddlyWiki core "wikify()" formatters:
* embedded images: {{{[img[tooltip|image]]}}}
* linked embedded images: {{{[img[tooltip|image][link]]}}}
* external/"pretty" links: {{{[[label|link]]}}}
''Please refer to AttachFilePlugin (source: http://www.TiddlyTools.com/#AttachFilePlugin) for additional information.''
<<<
!!!!!Revisions
<<<
2009.10.10 [4.0.1] in fileExists(), check for IE to avoid hanging Chrome during startup
2009.06.04 [4.0.0] changed attachment storage format to use //sections// instead of embedded substring markers.
2008.01.08 [*.*.*] plugin size reduction: documentation moved to ...Info
2007.12.04 [*.*.*] update for TW2.3.0: replaced deprecated core functions, regexps, and macros
2007.10.29 [3.7.0] more code reduction: removed upload handling from AttachFilePlugin (saves ~7K!)
2007.10.28 [3.6.0] removed duplicate formatter code from AttachFilePlugin (saves ~10K!) and updated documentation accordingly.  This plugin ([[AttachFilePluginFormatters]]) is now //''required''// in order to display attached images/binary files within tiddler content.
2006.05.20 [3.4.0] through 2007.03.01 [3.5.3] sync with AttachFilePlugin
2006.05.13 [3.2.0] created from AttachFilePlugin v3.2.0
<<<
!!!!!Code
***/
// // version
//{{{
version.extensions.AttachFilePluginFormatters= {major: 4, minor: 0, revision: 1, date: new Date(2009,10,10)};
//}}}

//{{{
if (config.macros.attach==undefined) config.macros.attach= { };
//}}}
//{{{
if (config.macros.attach.isAttachment==undefined) config.macros.attach.isAttachment=function (title) {
	var tiddler = store.getTiddler(title);
	if (tiddler==undefined || tiddler.tags==undefined) return false;
	return (tiddler.tags.indexOf("attachment")!=-1);
}
//}}}

//{{{
// test for local file existence - returns true/false without visible error display
if (config.macros.attach.fileExists==undefined) config.macros.attach.fileExists=function(f) {
	if(window.Components) { // MOZ
		try { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); }
		catch(e) { return false; } // security access denied
		var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
		try { file.initWithPath(f); }
		catch(e) { return false; } // invalid directory
		return file.exists();
	}
	else if (config.browser.isIE) { // IE
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		return fso.FileExists(f);
	}
	else return true; // other browsers: assume file exists
}
//}}}

//{{{
if (config.macros.attach.getAttachment==undefined) config.macros.attach.getAttachment=function(title) {

	// extract embedded data, local and remote links (if any)
	var text=store.getTiddlerText(title,'');
	var embedded=store.getTiddlerText(title+'##data','').trim();
	var locallink=store.getTiddlerText(title+'##file','').trim();
	var remotelink=store.getTiddlerText(title+'##url','').trim();

	// backward-compatibility for older attachments (pre 4.0.0)
	var startmarker="---BEGIN_DATA---\n";
	var endmarker="\n---END_DATA---";
	var pos=0; var endpos=0;
	if ((pos=text.indexOf(startmarker))!=-1 && (endpos=text.indexOf(endmarker))!=-1)
		embedded="data:"+(text.substring(pos+startmarker.length,endpos)).replace(/\n/g,'');
	if ((pos=text.indexOf("/%LOCAL_LINK%/"))!=-1)
		locallink=text.substring(text.indexOf("|",pos)+1,text.indexOf("]]",pos));
	if ((pos=text.indexOf("/%REMOTE_LINK%/"))!=-1)
		remotelink=text.substring(text.indexOf("|",pos)+1,text.indexOf("]]",pos));

	// if there is a data: URI defined (not supported by IE)
	if (embedded.length && !config.browser.isIE) return embedded;

	// document is being served remotely... use remote URL (if any)  (avoids security alert)
	if (remotelink.length && document.location.protocol!="file:")
		return remotelink;  

	// local link only... return link without checking file existence (avoids security alert)
	if (locallink.length && !remotelink.length) 
		return locallink; 

	// local link, check for file exist... use local link if found
	if (locallink.length) { 
		locallink=locallink.replace(/^\.[\/\\]/,''); // strip leading './' or '.\' (if any)
		if (this.fileExists(getLocalPath(locallink))) return locallink;
		// maybe local link is relative... add path from current document and try again
		var pathPrefix=document.location.href;  // get current document path and trim off filename
		var slashpos=pathPrefix.lastIndexOf("/"); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf("\\"); 
		if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
		if (this.fileExists(getLocalPath(pathPrefix+locallink))) return locallink;
	}

	// no embedded data, no local (or not found), fallback to remote URL (if any)
	if (remotelink.length) return remotelink;

	// attachment URL doesn't resolve, just return input as is
	return title;
}
//}}}
//{{{
if (config.macros.attach.init_formatters==undefined) config.macros.attach.init_formatters=function() {
	if (this.initialized) return;

	// find the formatter for "image" and replace the handler
	for (var i=0; i<config.formatters.length && config.formatters[i].name!="image"; i++);
	if (i<config.formatters.length)	config.formatters[i].handler=function(w) {
		this.lookaheadRegExp.lastIndex = w.matchStart;
		var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) // Simple bracketted link
			{
			var e = w.output;
			if(lookaheadMatch[5])
				{
				var link = lookaheadMatch[5];
				// ELS -------------
				var external=config.formatterHelpers.isExternalLink(link);
				if (external)
					{
					if (config.macros.attach.isAttachment(link))
						{
						e = createExternalLink(w.output,link);
						e.href=config.macros.attach.getAttachment(link);
						e.title = config.macros.attach.linkTooltip + link;
						}
					else
						e = createExternalLink(w.output,link);
					}
				else 
					e = createTiddlyLink(w.output,link,false,null,w.isStatic);
				// ELS -------------
				addClass(e,"imageLink");
				}
			var img = createTiddlyElement(e,"img");
			if(lookaheadMatch[1])
				img.align = "left";
			else if(lookaheadMatch[2])
				img.align = "right";
			if(lookaheadMatch[3])
				img.title = lookaheadMatch[3];
			img.src = lookaheadMatch[4];
			// ELS -------------
			if (config.macros.attach.isAttachment(lookaheadMatch[4]))
				img.src=config.macros.attach.getAttachment(lookaheadMatch[4]);
			// ELS -------------
			w.nextMatch = this.lookaheadRegExp.lastIndex;
		}
	}
//}}}
//{{{
	// find the formatter for "prettyLink" and replace the handler
	for (var i=0; i<config.formatters.length && config.formatters[i].name!="prettyLink"; i++);
	if (i<config.formatters.length)	{
		config.formatters[i].handler=function(w) {
			this.lookaheadRegExp.lastIndex = w.matchStart;
			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
				var e;
				var text = lookaheadMatch[1];
				if(lookaheadMatch[3]) {
					// Pretty bracketted link
					var link = lookaheadMatch[3];
					if (config.macros.attach.isAttachment(link)) {
						e = createExternalLink(w.output,link);
						e.href=config.macros.attach.getAttachment(link);
						e.title=config.macros.attach.linkTooltip+link;
					}
					else e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link))
						? createExternalLink(w.output,link)
						: createTiddlyLink(w.output,link,false,null,w.isStatic);
				} else {
					e = createTiddlyLink(w.output,text,false,null,w.isStatic);
				}
				createTiddlyText(e,text);
				w.nextMatch = this.lookaheadRegExp.lastIndex;
			}
		}
	} // if "prettyLink" formatter found
	this.initialized=true;
}
//}}}
//{{{
config.macros.attach.init_formatters(); // load time init
//}}}
//{{{
if (TiddlyWiki.prototype.coreGetRecursiveTiddlerText==undefined) {
	TiddlyWiki.prototype.coreGetRecursiveTiddlerText = TiddlyWiki.prototype.getRecursiveTiddlerText;
	TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth) {
		return config.macros.attach.isAttachment(title)?
			config.macros.attach.getAttachment(title):this.coreGetRecursiveTiddlerText.apply(this,arguments);
	}
}
//}}}
//{{{
if (config.macros.attach && config.macros.attach.init_formatters) {
	var code = eval("config.macros.attach.init_formatters").toString();
	var newCode = null;
	
	var startPosition = code.indexOf("!lookaheadMatch[2]");
	if (startPosition > -1) {
		var startPosition2 = code.indexOf(';', startPosition);
		if (startPosition2 > -1) {
			newCode = code.substring(0, startPosition2 + 1)
					+ " if (link && (link.indexOf('data:') == 0)) { e.download = text; e.title = text; } "
					+ code.substring(startPosition2 + 1);
			code = newCode;
		}
		
		startPosition2 = code.lastIndexOf('e.title', startPosition);
		if (startPosition2 > -1) {
			startPosition2 = code.indexOf(';', startPosition2);
			if (startPosition2 > -1) {
				newCode = code.substring(0, startPosition2 + 1)
						+ " if (e.href && (e.href.indexOf('data:') == 0)) { e.download = text; e.title = text; } "
						+ code.substring(startPosition2 + 1);
				code = newCode;
			}
		}
	}
	if (newCode != null) {
		eval("config.macros.attach.init_formatters = function init_formatters" + newCode.substring(newCode.indexOf("(")));
		config.macros.attach.initialized = false;
		config.macros.attach.init_formatters();
	}
}
//}}}
//{{{
var code = config.macros.attach.decodeBase64.toString();
var newCode = null;
var startPosition = code.indexOf("{");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition + 1) + ' var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; ' + code.substring(startPosition + 1);
	code = newCode;
}
if (newCode != null) {
	eval("config.macros.attach.decodeBase64 = function decodeBase64" + newCode.substring(newCode.indexOf("(")));
}
//}}}
//{{{
var code = config.macros.attach.readFile.toString();
var newCode = null;
var startPosition = code.indexOf("{");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition + 1) + " if (filePath.fileData !== undefined) return filePath.fileData; " + code.substring(startPosition + 1);
	code = newCode;
}
if (newCode != null) {
	eval("config.macros.attach.readFile = function readFile" + newCode.substring(newCode.indexOf("(")));
}
//}}}
//{{{
if (typeof netscape != "undefined") {
	if (!netscape.security || !netscape.security.PrivilegeManager || !netscape.security.PrivilegeManager.enablePrivilege) {
		config.macros.attach.onClickAttach = function (here) {
			clearMessage();
			// get input values
			var form=here.form;
			var src=form.source.files[0].name;
			var when=(new Date()).formatString(config.macros.timeline.dateFormat);
			var title=form.tiddlertitle.value;
			var local = form.local.value!=form.local.defaultValue?form.local.value:"";
			var url = form.URL.value!=form.URL.defaultValue?form.URL.value:"";
			var notes = form.notes.value;
			var tags = "attachment excludeMissing "+form.tags.value;
			var useData=form.useData.checked;
			var useLocal=form.useLocal.checked;
			var useURL=form.useURL.checked;
			var mimetype = form.MIMEType.value.length?form.MIMEType.options[form.MIMEType.selectedIndex].text:"";
			// validate checkboxes and get filename
			if (useData) {
				if (src.length) { if (!theLocation) var theLocation=src; }
				else { alert(this.sourceErr); src.focus(); return false; }
			}
			if (useLocal) {
				if (local.length) { if (!theLocation) var theLocation = local; }
				else { alert(this.localErr); form.local.focus(); return false; }
			}
			if (useURL) {
				if (url.length) { if (!theLocation) var theLocation = url; }
				else { alert(this.URLErr); form.URL.focus(); return false; }
			}
			if (!(useData||useLocal||useURL))
				{ form.useData.focus(); alert(this.storageErr); return false; }
			if (!theLocation)
				{ src.focus(); alert(this.sourceErr); return false; }
			if (!title || !title.trim().length || title==this.titlePrompt)
				{ form.tiddlertitle.focus(); alert(this.tiddlerErr); return false; }
			// if not already selected, determine MIME type based on filename extension (if any)
			if (useData && !mimetype.length && theLocation.lastIndexOf('.')!=-1) {
				var theExt = theLocation.substr(theLocation.lastIndexOf('.')).toLowerCase();
				var theList=form.MIMEType;
				for (var i=0; i<theList.options.length; i++)
					if (theList.options[i].value.indexOf(theExt)!=-1)
						{ var mimetype=theList.options[i].text; theList.selectedIndex=i; break; }
			}
			// attach the file
			var reader = new FileReader;
			reader.onload = function (e) {
				var loadedfilesrc = {};
				loadedfilesrc.fileData = e.target.result;
				config.macros.attach.createAttachmentTiddler(loadedfilesrc, when, notes, tags, title, useData, useLocal, useURL, local, url, mimetype);
			};
			reader.readAsBinaryString(form.source.files[0]);
			
			return true;
		}
		
		var code = config.macros.attach.createAttachPanel.toString();
		var newCode = null;
		var startPosition = code.indexOf("config.browser.isGecko");
		if (startPosition > -1) {
			newCode = code.substring(0, startPosition) + "false" + code.substring(startPosition + "config.browser.isGecko".length);
			code = newCode;
		}
		if (newCode != null) {
			eval("config.macros.attach.createAttachPanel = function createAttachPanel" + newCode.substring(newCode.indexOf("(")));
		}
	}
}
//}}}
//{{{
config.macros.attach.typeList = "AttachFileMIMETypesOverride";
//}}}
Copyright (c) 2005-2008, BidiX (http://BidiX.info)

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
*Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
*Neither the name of BidiX nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Contact : [[BidiX @ bidix . info|mailto:BidiX@BidiX.info]]
URL : [[BidiX.info|http://BidiX.info/]]
Donation : [[Donation via Paypal|https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=BidiX%40bidix%2einfo&item_name=TiddlyWikiHacking%20Donations&item_number=Donations&no_shipping=2&no_note=1&tax=0&currency_code=EUR&bn=PP%2dDonationsBF&charset=UTF%2d8]]
// // http://www.antiyes.com/jquery-blink-plugin

//{{{
(function ($)
{
	$.fn.blink = function(options)
	{
		var defaults = { delay:500 };
		var options = $.extend(defaults, options);
		
		return this.each(function()
		{
			var obj = $(this);
			setInterval(function()
			{
				if($(obj).css("visibility") == "visible")
				{
					$(obj).css('visibility','hidden');
				}
				else
				{
					$(obj).css('visibility','visible');
				}
			}, options.delay);
		});
	}
}(jQuery));
//}}}

//{{{
config.formatters[config.formatters.length] = {
	name: "blink",
	match: "<[Bb][Ll][Ii][Nn][Kk]>",
	termRegExp: /(<\/[Bb][Ll][Ii][Nn][Kk]>)/mg,
	handler: function(w) {
		var blinkElement = createTiddlyElement(w.output, "span");
		w.subWikifyTerm(blinkElement, this.termRegExp);
		jQuery(blinkElement).blink();
	}
};
//}}}
( 6 - 2 * 2 ) + ( 5 * 2 x^y 3 ) = 42
<<calc "sample calculator" layout:"quebec-advanced">>

* Click on the //Use// button to insert the calculation into the tiddler.
* Copy-pasting expressions into the calculator's input field also works.
//{{{
if (config.options.txtCalculatorLayout === undefined) {
	config.options.txtCalculatorLayout = "advanced";
}
if (config.options.chkCalculatorShowFormula === undefined) {
	config.options.chkCalculatorShowFormula = true;
}
merge(config.optionsDesc, {
	txtCalculatorLayout: "The default layout of the calculator",
	chkCalculatorShowFormula: "Display the formula as it's entered within the calculator"
});
//}}}
//{{{
config.macros.calc = {
	layouts: {
		advanced: {
			style: "width:355px; max-scale:1.5",
			layout: [
				"@U  CECABS  BBBOBDBH",
				"DGRDPI_ MC_ _7_8_9_/opcp",
				"SNCSTN_ MR_ _4_5_6_*_%md",
				"ASACAT_ MS_ _1_2_3_-1Xfl",
				"SQXYEX_ M+_ _0+-_._+_=RN",
				"SRLGLN_ M-_ _A_B_C_D_E_F"
			]
		}
	},
	
	keyDefs: {},
	
	// http://stackoverflow.com/questions/15454183/how-to-make-a-function-that-computes-the-factorial-for-numbers-with-decimals
	gamma_g: 7,
	gamma_C: [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716 * Math.pow(10, -6), 1.5056327351493116 * Math.pow(10, -7)],
	gamma: function (z) {
		var g = config.macros.calc.gamma_g;
		var C = config.macros.calc.gamma_C;
		var gamma = config.macros.calc.gamma;
		
		if (z < 0.5) {
			return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
		} else {
			z -= 1;
			
			var x = C[0];
			for (var i = 1; i < g + 2; i++) {
				x += C[i] / (z + i);
			}
			
			var t = z + g + 0.5;
			return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
		}
	},
	
	handler: function (place, macroName, params, wikifier, paramString, tiddler) {
		var cmc = config.macros.calc;
		
		var btnName = null;
		var layout = null;
		showFormula = config.options.chkCalculatorShowFormula;
		
		var parsedParams = paramString.parseParams("anon", null, true, false, false);
		for (var i = 0; i < parsedParams.length; i++) {
			if (parsedParams[i]) {
				if ((btnName === null) && (parsedParams[i].name == "anon")) {
					btnName = parsedParams[i].value;
				} else if ((layout === null) && (parsedParams[i].name == "layout")) {
					layout = cmc.layouts[parsedParams[i].value];
				} else if (parsedParams[i].name == "showFormula") {
					showFormula = ("" + parsedParams[i].value).toLowerCase() != "false";
				}
			} 
		}
		
		btnName = (btnName === null) ? "Calculator" : btnName;
		layout = layout ? layout : ((cmc.layouts && config.options.txtCalculatorLayout) ? cmc.layouts[config.options.txtCalculatorLayout] : null);
		
		if (!layout && cmc.layouts) {
			for (var l in cmc.layouts) {
				layout = cmc.layouts[l];
				if (layout) {
					break;
				}
			}
		}
		
		if (layout) {
			eval(store.getTiddlerText('jQueryCalculator##init'));
			
			var panel = config.macros.slider.createSlider(place, "config.macros.calc:" + (tiddler ? escape(tiddler.title) : "") + (paramString ? ":" + escape(paramString) : ""), btnName);
			
			var calculator = createTiddlyElement(panel, "div", null, null, null, (layout && layout.style) ? {style: layout.style} : null);
			
			var squareRoot = jQuery.calculator._keyDefs["SR"];
			jQuery.calculator.addKeyDef("SR", squareRoot[0], squareRoot[1], squareRoot[2], squareRoot[3], squareRoot[4], "q");
			
			jQuery.calculator._keyCodes[13] = "_=";
			
			var factorial = function (inst) {
				inst.curValue = cmc.gamma(inst.curValue + 1);
			}
			jQuery.calculator.addKeyDef("fl", "n!", jQuery.calculator.unary, factorial, "fn factorial", "FACTORIAL", "!");
			
			setStylesheet("\
					.calculator-modulus {\
						font-size: 70%;\
					}", "CalculatorPlugin");
			var modulus = function (inst) {
				inst.curValue = inst.prevValue % inst.curValue;
			}
			jQuery.calculator.addKeyDef("md", "mod", jQuery.calculator.binary, modulus, "arith modulus", "MODULUS", "|");
			jQuery.calculator._keyDefs["md"][7] = 20;
			
			var openParenthesis = function (inst) {
				var stacks = inst._stacks;
				if ((stacks.sequence.length > 1) && (stacks.sequence[stacks.sequence.length - 2][1] != jQuery.calculator.binary) && (stacks.sequence[stacks.sequence.length - 2][0] != "(")) {
					var calculation = stacks.calculation[stacks.calculation.length - 1];
					calculation.number[calculation.number.length] = inst.curValue;
					calculation.operator[calculation.operator.length] = jQuery.calculator._keyDefs["_*"];
				}
				stacks.calculation[inst._stacks.calculation.length] = {number: [], operator: []};
				inst._newValue = true;
			}
			var closeParenthesis = function (inst) {
				var stacks = inst._stacks;
				var calculation = stacks.calculation[stacks.calculation.length - 1];
				if ((stacks.sequence.length > 1) && (stacks.sequence[stacks.sequence.length - 2][1] == jQuery.calculator.binary)) {
					calculation.operator.pop();
				}
				calculation.number[calculation.number.length] = inst.curValue;
				while (calculation.operator.length) {
					inst.curValue = calculation.number.pop();
					inst.prevValue = calculation.number.pop();
					inst._pendingOp = calculation.operator.pop()[2];
					inst._pendingOp(inst);
					inst.curValue = (inst.options.base == 10 ? inst.curValue : Math.floor(inst.curValue));
					calculation.number[calculation.number.length] = inst.curValue;
				}
				if (stacks.calculation.length > 1) {
					stacks.calculation.pop();
				} else {
					calculation.number.pop();
				}
			}
			jQuery.calculator.addKeyDef("op", "(", jQuery.calculator.unary, openParenthesis, "arith parenthesis", "OPENPARENTHESIS", "("); 
			jQuery.calculator.addKeyDef("cp", ")", jQuery.calculator.unary, closeParenthesis, "arith parenthesis", "CLOSEPARENTHESIS", ")");
			
			var use = function (inst) {
				if (inst.options.tiddler) {
					var tiddler = inst.options.tiddler;
					var input = "";
					var sequence = inst._stacks.sequence;
					for (var i = 0; i < sequence.length; i++) {
						if (sequence[i][0] != "#use") {
							if (input.length && ((sequence[i][1] != jQuery.calculator.digit) || (sequence[i - 1][1] != jQuery.calculator.digit))) {
								input = input + " ";
							}
							input = input + (sequence[i][0].charAt(0) == "#" ? inst.options[sequence[i][0].substr(1) + "Text"] : sequence[i][0]);
						}
					}
					if ((sequence.length > 1) && (sequence[sequence.length - 2][0] == "=")) {
						input = input + " " + jQuery.calculator._setDisplay(inst);
					}
					if (input) {
						var tiddlerPosition = inst.options.tiddlerPosition;
						var tiddlerText = store.getTiddlerText(tiddler.title);
						if (tiddlerPosition > -1) {
							tiddler.text = tiddlerText.substring(0, tiddlerPosition) + input + "\n" + tiddler.text.substring(tiddlerPosition);
						} else {
							tiddler.text = tiddlerText + "\n" + input;
						}
						store.setDirty(true);
						story.refreshTiddler(tiddler.title, null, true);
					}
				}
			}
			jQuery.calculator.addKeyDef("@U", "#use", jQuery.calculator.control, use, "use", "USE", "U");
			
			if (cmc.keyDefs) {
				for (var code in cmc.keyDefs) {
					var keyDef = cmc.keyDefs[code];
					jQuery.calculator.addKeyDef(code, keyDef[0], jQuery.calculator[keyDef[1]], keyDef[2], keyDef[3], keyDef[4], keyDef[5], keyDef[6]);
					jQuery.calculator._keyDefs[code][7] = keyDef[7];
				}
			}
			
			jQuery(calculator).calculator({
				layout: layout ? layout.layout : null,
				showFormula: showFormula,
				tiddler: tiddler,
				tiddlerPosition: wikifier ? wikifier.matchStart : -1
			});
			
			var calculatorWidth = jQuery(calculator).width();
			calculatorWidth = calculatorWidth ? calculatorWidth : jQuery(panel).width();
			var calculatorHeight = jQuery(panel).height();
			calculatorHeight = calculatorHeight ? calculatorHeight : jQuery(panel).height();
			
			var scaleCalculator = function() {
				var maxScaleMatch = (layout && layout.style) ? layout.style.match(/max\-scale\s*:\s*(\d*\.?\d*)/i) : null;
				
				if (maxScaleMatch && (maxScaleMatch.length == 2)) {
					var scale = maxScaleMatch[1];
					
					var width = jQuery(jQuery(place).parent()).width();
					var height = jQuery(jQuery(place).parent()).height();
					
					scale = (calculatorWidth * scale > width) ? width / calculatorWidth : scale;
					scale = (scale < 1) ? 1 : scale;
					
					jQuery(calculator).css({
						"min-height": (calculatorHeight * scale) + "px",
						"-moz-transform-origin": "0 0",
						"-moz-transform": "scale(" + scale + ")",
						"-o-transform": "scale(" + scale + ")",
						"zoom": scale
					});
				}
			};
			
			scaleCalculator();
			jQuery(window).resize(scaleCalculator);
		}
	}
}
//}}}
//{{{
config.macros.calc.layouts["conversion"] = {
	style: config.macros.calc.layouts["advanced"].style,
	layout: [
		"@U  CECABS  BBBOBDBH",
		"iMImfc_ MC_ _7_8_9_/opcp",
		"mkfmic_ MR_ _4_5_6_*_%md",
		"tkpkog_ MS_ _1_2_3_-1Xfl",
		"glcmmf_ M+_ _0+-_._+_=RN",
		"SQXYSR_ M-_ _A_B_C_D_E_F"
	]
};

setStylesheet("\
		.calculator-conversion {\
			font-size: 60%;\
			line-height: 80%;\
		}\
		.calculator-conversion-control {\
			width: 28px !important;\
		}\
		.calculator-conversion-active {\
			border: 2px inset #fff;\
		}", "CalculatorPluginConversion");

config.macros.calc.conversion = {
	styleMetric: function (inst) {
		return config.macros.calc.conversion.style(inst, true);
	},
	styleImperial: function (inst) {
		return config.macros.calc.conversion.style(inst, false);
	},
	style: function (inst, metricButton) {
		inst.options.convertToMetric = inst.options.convertToMetric === undefined ? true : inst.options.convertToMetric;
		if (metricButton) {
			return inst.options.convertToMetric ? "conversion-control conversion-active" : "conversion-control";
		} else {
			return inst.options.convertToMetric ? "conversion-control" : "conversion-control conversion-active";
		}
	}
};

config.macros.calc.keyDefs["iM"] = ["&rarr;", "control", function (inst) { inst._stacks.sequence.pop(); inst.options.convertToMetric = true; jQuery.calculator._updateCalculator(inst); }, config.macros.calc.conversion.styleMetric, "CONVERTMETRIC"];
config.macros.calc.keyDefs["Im"] = ["&larr;", "control", function (inst) { inst._stacks.sequence.pop(); inst.options.convertToMetric = false; jQuery.calculator._updateCalculator(inst); }, config.macros.calc.conversion.styleImperial, "CONVERTIMPERIAL"];

// Area

config.macros.calc.conversion.convertSquareFeetSquareMeters = function (value, toMetric) {
	return Math.pow(config.macros.calc.conversion.convertFeetMeters(Math.sqrt(value), toMetric), 2);
}
config.macros.calc.keyDefs["mf"] = ["ft&sup2;<br>&harr;<br>m&sup2;", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "ft\u00B2\u2192m\u00B2" : "m\u00B2\u2192ft\u00B2"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["Mf"] = ["ft\u00B2\u2192m\u00B2", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertSquareFeetSquareMeters(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["mF"] = ["m\u00B2\u2192ft\u00B2", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertSquareFeetSquareMeters(inst.curValue, false); }, "conversion"];

// Length

config.macros.calc.conversion.convertFeetMeters = function (value, toMetric) {
	if (toMetric) {
		return value / 3.280839895;
	} else {
		return value * 3.280839895;
	}
}
config.macros.calc.keyDefs["fm"] = ["ft<br>&harr;<br>m", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "ft\u2192m" : "m\u2192ft"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["fM"] = ["ft\u2192m", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertFeetMeters(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Fm"] = ["m\u2192ft", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertFeetMeters(inst.curValue, false); }, "conversion"];

config.macros.calc.conversion.convertInchesCentimeters = function (value, toMetric) {
	if (toMetric) {
		return value * 2.54;
	} else {
		return value / 2.54;
	}
}
config.macros.calc.keyDefs["ic"] = ["in<br>&harr;<br>cm", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "in\u2192cm" : "cm\u2192in"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["iC"] = ["in\u2192cm", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertInchesCentimeters(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Ic"] = ["cm\u2192in", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertInchesCentimeters(inst.curValue, false); }, "conversion"];

config.macros.calc.conversion.convertMilesKilometers = function (value, toMetric) {
	if (toMetric) {
		return value * 1.609344;
	} else {
		return value / 1.609344;
	}
}
config.macros.calc.keyDefs["mk"] = ["mile<br>&harr;<br>km", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "mile\u2192km" : "km\u2192mile"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["mK"] = ["mile\u2192km", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertMilesKilometers(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Mk"] = ["km\u2192mile", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertMilesKilometers(inst.curValue, false); }, "conversion"];

// Quantity

config.macros.calc.conversion.convertCupsMilliliters = function (value, toMetric) {
	if (toMetric) {
		return value * 250;
	} else {
		return value / 250;
	}
}
config.macros.calc.keyDefs["cm"] = ["cup<br>&harr;<br>ml", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "cup\u2192ml" : "ml\u2192cup"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["cM"] = ["cup\u2192ml", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertCupsMilliliters(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Cm"] = ["ml\u2192cup", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertCupsMilliliters(inst.curValue, false); }, "conversion"];


config.macros.calc.conversion.convertImperialGallonsLiters = function (value, toMetric) {
	if (toMetric) {
		return value * 4.54609188;
	} else {
		return value / 4.54609188;
	}
}

config.macros.calc.conversion.convertUSGallonsLiters = function (value, toMetric) {
	if (toMetric) {
		return value * 3.78541178;
	} else {
		return value / 3.78541178;
	}
}
config.macros.calc.keyDefs["gl"] = ["gal<br>&harr;<br>l", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "us gal\u2192l" : "l\u2192us gal"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["gL"] = ["us gal\u2192l", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertUSGallonsLiters(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Gl"] = ["l\u2192us gal", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertUSGallonsLiters(inst.curValue, false); }, "conversion"];

// Temperature

config.macros.calc.conversion.convertFahrenheitCelsius = function (value, toMetric) {
	if (toMetric) {
		return (value - 32) * 5/9;
	} else {
		return value * 9/5 + 32;
	}
}
config.macros.calc.keyDefs["fc"] = ["&deg;F<br>&harr;<br>&deg;C", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "\u00B0F\u2192\u00B0C" : "\u00B0C\u2192\u00B0F"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["fC"] = ["\u00B0F\u2192\u00B0C", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertFahrenheitCelsius(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Fc"] = ["\u00B0C\u2192\u00B0F", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertFahrenheitCelsius(inst.curValue, false); }, "conversion"];

// Weight

config.macros.calc.conversion.convertOuncesGrams = function (value, toMetric) {
	if (toMetric) {
		return value * 28.349523;
	} else {
		return value / 28.349523;
	}
}
config.macros.calc.keyDefs["og"] = ["oz<br>&harr;<br>g", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "oz\u2192g" : "g\u2192oz"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["oG"] = ["oz\u2192g", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertOuncesGrams(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Og"] = ["g\u2192oz", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertOuncesGrams(inst.curValue, false); }, "conversion"];

config.macros.calc.conversion.convertPoundsKilograms = function (value, toMetric) {
	if (toMetric) {
		return value / 2.2;
	} else {
		return value * 2.2;
	}
}
config.macros.calc.keyDefs["pk"] = ["lb<br>&harr;<br>kg", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "lb\u2192kg" : "kg\u2192lb"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["pK"] = ["lb\u2192kg", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertPoundsKilograms(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Pk"] = ["kg\u2192lb", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertPoundsKilograms(inst.curValue, false); }, "conversion"];

config.macros.calc.conversion.convertUKTonsKilograms = function (value, toMetric) {
	if (toMetric) {
		return value * 1016.0469088;
	} else {
		return value / 1016.0469088;
	}
}

config.macros.calc.conversion.convertUSTonsKilograms = function (value, toMetric) {
	if (toMetric) {
		return value * 907.18474;
	} else {
		return value / 907.18474;
	}
}
config.macros.calc.keyDefs["tk"] = ["ton<br>&harr;<br>kg", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, inst.options.convertToMetric ? "us ton\u2192kg" : "kg\u2192us ton"); inst._disableFormulaUpdate = true; }, "conversion"];
config.macros.calc.keyDefs["tK"] = ["us ton\u2192kg", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertUSTonsKilograms(inst.curValue, true); }, "conversion"];
config.macros.calc.keyDefs["Tk"] = ["kg\u2192us ton", "unary", function (inst) { inst.curValue = config.macros.calc.conversion.convertUSTonsKilograms(inst.curValue, false); }, "conversion"];
//}}}
//{{{
config.macros.calc.layouts["quebec-advanced"] = {style: config.macros.calc.layouts["advanced"].style, layout: config.macros.calc.layouts["advanced"].layout.slice()};
config.macros.calc.layouts["quebec-advanced"].layout[4] = config.macros.calc.layouts["quebec-advanced"].layout[4].replace("RN", "tx");

config.macros.calc.layouts["quebec-conversion"] = {style: config.macros.calc.layouts["conversion"].style, layout: config.macros.calc.layouts["conversion"].layout.slice()};
config.macros.calc.layouts["quebec-conversion"].layout[4] = config.macros.calc.layouts["quebec-conversion"].layout[4].replace("RN", "tx");

config.macros.calc.keyDefs["tx"] = ["tax", "unary", function (inst) { inst._stacks.sequence.pop(); jQuery.calculator._calculate(inst, "* (1 + 0.05 + 0.09975)"); }, "arith tax", "TAX", "x"];
//}}}
/***
|Name|CalendarPlugin|
|Source|http://www.TiddlyTools.com/#CalendarPlugin|
|Version|1.5.1|
|Author|Eric Shulman|
|Original Author|SteveRumsby|
|License|unknown|
|~CoreVersion|2.1|
|Type|plugin|
|Description|display monthly and yearly calendars|
NOTE: For //enhanced// date popup display, optionally install:
*[[DatePlugin]]
*[[ReminderMacros|http://remindermacros.tiddlyspot.com/]]
!!!Usage:
<<<
|{{{<<calendar>>}}}|full-year calendar for the current year|
|{{{<<calendar year>>}}}|full-year calendar for the specified year|
|{{{<<calendar year month>>}}}|one month calendar for the specified month and year|
|{{{<<calendar thismonth>>}}}|one month calendar for the current month|
|{{{<<calendar lastmonth>>}}}|one month calendar for last month|
|{{{<<calendar nextmonth>>}}}|one month calendar for next month|
|{{{<<calendar +n>>}}}<br>{{{<<calendar -n>>}}}|one month calendar for a month +/- 'n' months from now|
<<<
!!!Configuration:
<<<
|''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 >>|
<<<
!!!Revisions
<<<
2011.01.04 1.5.1 corrected parameter handling for {{{<<calendar year>>}}} to show entire year instead of just first month.  In createCalendarMonthHeader(), fixed next/previous month year calculation (use parseInt() to convert to numeric value).  Code reduction (setting options).
2009.04.31 1.5.0 rewrote onClickCalendarDate() (popup handler) and added config.options.txtCalendarReminderTags.  Partial code reduction/cleanup.  Assigned true version number (1.5.0)
2008.09.10 added '+n' (and '-n') param to permit display of relative months (e.g., '+6' means 'six months from now', '-3' means 'three months ago'.  Based on suggestion from Jean.
2008.06.17 added support for config.macros.calendar.todaybg
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
***/
//{{{
version.extensions.CalendarPlugin= { major: 1, minor: 5, revision: 1, date: new Date(2011,1,4)};

// COOKIE OPTIONS
var opts={
	txtCalFirstDay:				0,
	txtCalStartOfWeekend:		5,
	chkDisplayWeekNumbers:		false,
	txtCalFirstDay:				0,
	txtWeekNumberDisplayFormat:	'w0WW',
	txtWeekNumberLinkFormat:	'YYYY-w0WW',
	txtCalendarReminderTags:		'reminder'
};
for (var id in opts) if (config.options[id]===undefined) config.options[id]=opts[id];

// INTERNAL CONFIGURATION
config.macros.calendar = {
	monthnames:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
	daynames:['M','T','W','T','F','S','S'],
	todaybg:'#ccccff',
	weekendbg:'#c0c0c0',
	monthbg:'#e0e0e0',
	holidaybg:'#ffc0c0',
	journalDateFmt:'DD MMM YYYY',
	monthdays:[31,28,31,30,31,30,31,31,30,31,30,31],
	holidays:[ ] // for customization see [[CalendarPluginConfig]]
};
//}}}
//{{{
function calendarIsHoliday(date)
{
	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');
	var today = new Date();
	var year = today.getYear();
	if (year<1900) year+=1900;

 	// get journal format from SideBarOptions (ELS 5/29/06 - suggested by MartinBudden)
	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]; }

	var month=-1;
	if (params[0] == 'thismonth') {
		var month=today.getMonth();
	} else if (params[0] == 'lastmonth') {
		var month = today.getMonth()-1; if (month==-1) { month=11; year--; }
	} else if (params[0] == 'nextmonth') {
		var month = today.getMonth()+1; if (month>11) { month=0; year++; }
	} else if (params[0]&&'+-'.indexOf(params[0].substr(0,1))!=-1) {
		var month = today.getMonth()+parseInt(params[0]);
		if (month>11) { year+=Math.floor(month/12); month%=12; };
		if (month<0)  { year+=Math.floor(month/12); month=12+month%12; }
	} else if (params[0]) {
		year = params[0];
		if(params[1]) {
			month=parseInt(params[1])-1;
			if (month>11) month=11; if (month<0) month=0;
		}
	}

	if (month!=-1) {
		cacheReminders(new Date(year, month, 1, 0, 0), 31);
		createCalendarOneMonth(tbody, year, month);
	} else {
		cacheReminders(new Date(year, 0, 1, 0, 0), 366);
		createCalendarYear(tbody, year);
	}
	window.reminderCacheForCalendar = null;
}
//}}}
//{{{
// cache used to store reminders 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');
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+year, true, year, mon);
	row = createTiddlyElement(calendar, 'tr');
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarMonth(calendar, year, mon)
{
	var row = createTiddlyElement(calendar, 'tr');
	createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+ year, false, year, mon);
	row = createTiddlyElement(calendar, 'tr');
	createCalendarDayHeader(row, 1);
	createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarYear(calendar, year)
{
	var row;
	row = createTiddlyElement(calendar, 'tr');
	var back = createTiddlyElement(row, 'td');
	var backHandler = function() {
		removeChildren(calendar);
		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';
	yearHeader.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?22:19);//wn**
	var fwd = createTiddlyElement(row, 'td');
	var fwdHandler = function() {
		removeChildren(calendar);
		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');
	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');
	createCalendarDayHeader(row, 3);
	createCalendarDayRows(cal, year, mon);
}
//}}}
//{{{
function createCalendarMonthHeader(cal, row, name, nav, year, mon)
{
	var month;
	if (nav) {
		var back = createTiddlyElement(row, 'td');
		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 = parseInt(newyear)-1;}
			removeChildren(cal);
			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')
		createTiddlyLink(month,name,true);
		month.setAttribute('colSpan', config.options.chkDisplayWeekNumbers?6:5);//wn**
		var fwd = createTiddlyElement(row, 'td');
		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 = parseInt(newyear)+1;}
			removeChildren(cal);
			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.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?8:7);//wn**
	}
	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);
			link.appendChild(document.createTextNode(
				ww.formatString(config.options.txtWeekNumberDisplayFormat)));
		}
		else createTiddlyElement(row, 'td');//wn**
	}
	for(i = 0; i < col; i++)
		createTiddlyElement(row, 'td');
	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');
		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 10/30/05 - use <<date>> macro's showDate() function to create popup
			// ELS 05/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);
				if(calendarIsHoliday(celldate))
					daycell.style.background = config.macros.calendar.holidaybg;
				var now=new Date();
				if ((now-celldate>=0) && (now-celldate<86400000)) // is today?
					daycell.style.background = config.macros.calendar.todaybg;
				if(window.findTiddlersWithReminders == null) {
					var link = createTiddlyLink(daycell, title, false);
					link.appendChild(document.createTextNode(day));
				} else
					var button = createTiddlyButton(daycell, day, title, onClickCalendarDate);
			}
		}
		day++;
	}
}
//}}}
//{{{
// Create a pop-up containing:
// * a link to a tiddler for this date
// * a 'new tiddler' link to add a reminder for this date
// * links to current reminders for this date
// NOTE: this code is only used if [[ReminderMacros]] is installed AND [[DatePlugin]] is //not// installed.
function onClickCalendarDate(ev) { ev=ev||window.event;
	var d=new Date(this.getAttribute('title')); var date=d.formatString(config.macros.calendar.journalDateFmt);
	var p=Popup.create(this);  if (!p) return;
	createTiddlyLink(createTiddlyElement(p,'li'),date,true);
	var rem='\\n\\<\\<reminder day:%0 month:%1 year:%2 title: \\>\\>';
	rem=rem.format([d.getDate(),d.getMonth()+1,d.getYear()+1900]);
	var cmd="<<newTiddler label:[[new reminder...]] prompt:[[add a new reminder to '%0']]"
		+" title:[[%0]] text:{{store.getTiddlerText('%0','')+'%1'}} tag:%2>>";
	wikify(cmd.format([date,rem,config.options.txtCalendarReminderTags]),p);
	createTiddlyElement(p,'hr');
	var t=findTiddlersWithReminders(d,[0,31],null,1);
	for(var i=0; i<t.length; i++) {
		var link=createTiddlyLink(createTiddlyElement(p,'li'), t[i].tiddler, false);
		link.appendChild(document.createTextNode(t[i]['params']['title']));
	}
	Popup.show(); ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); return false;
}
//}}}
//{{{
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');
	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');
		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');
	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');
		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');
//}}}
// // override cookie settings for CalendarPlugin:
//{{{
config.options.txtCalFirstDay=6;
config.options.txtCalStartOfWeekend=5;
//}}}

// // override internal default settings for CalendarPlugin:
//{{{
config.macros.calendar.journalDateFmt="DDD MMM 0DD YYYY";
//}}}
//{{{
config.options.chkDisplayWeekNumbers = true;
//}}}
//{{{
merge(config.shadowTiddlers,{
	SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "YYYY-0MM-0DD" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>'
});
//}}}
//{{{
config.macros.calendar.monthnames = ['01','02','03','04','05','06','07','08','09','10','11','12'];
//}}}
//{{{
config.macros.calendar.weekNumberCalculation = function (date, firstDay) {
	if (!(!isNaN(parseInt(firstDay)) && isFinite(firstDay))) {
		firstDay = (!isNaN(parseInt(config.options.txtCalFirstDay)) && isFinite(config.options.txtCalFirstDay)) ? ((parseInt(config.options.txtCalFirstDay) + 1) % 7) : 0;
	}
	
	var firstWeekDate = new Date(date.getTime());
	firstWeekDate.setDate(firstWeekDate.getDate() - (firstWeekDate.getDay() - firstDay >= 0 ? firstWeekDate.getDay() - firstDay : 7 + firstWeekDate.getDay() - firstDay));
	
	var firstYearWeekDate = new Date(firstWeekDate.getTime());
	firstYearWeekDate.setMonth(0);
	firstYearWeekDate.setDate(1);
	firstYearWeekDate.setDate(firstYearWeekDate.getDate() + (4 - firstYearWeekDate.getDay() >= 0 ? 4 - firstYearWeekDate.getDay() : 11 - firstYearWeekDate.getDay()));
	firstYearWeekDate.setDate(firstYearWeekDate.getDate() - (4 - firstDay >= 0 ? 4 - firstDay : 11 - firstDay));
	
	var weekNumber = (((firstWeekDate.getTime() - firstYearWeekDate.getTime()) / 1000 / 60 / 60 / 24 / 7) | 0) + 1;
	return ((weekNumber > 52) && (config.macros.calendar.weekNumberCalculation(new Date(firstWeekDate.getTime() + (1000 * 60 * 60 * 24 * 7)), firstDay) == 2)) ? 1 : weekNumber;
}
//}}}
//{{{
config.macros.calendar.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	var today = new Date();
	var year = today.getFullYear();
	
	var container = {};
	container.params = {};
	params = [];
	
	container.nofullcalendar = config.macros.calendar.nofullcalendar;
	
	var parsedParams = paramString.parseParams("anon", null, true, false, false);
	for (var i = 0; i < parsedParams.length; i++) {
		if (parsedParams[i]) {
			if (parsedParams[i].name == "anon") {
				if (parsedParams[i].value == "nofullcalendar") {
					container.nofullcalendar = true;
				} else {
					params[params.length] = parsedParams[i].value;
				}
			} else {
				if (parsedParams[i].name == "tag") {
					container.filter = parsedParams[i].value;
				} else if (parsedParams[i].name == "tag-task") {
					container.task = parsedParams[i].value;
				} else if (parsedParams[i].name == "tag-meeting") {
					container.meeting = parsedParams[i].value;
				} else if (parsedParams[i].name == "name") {
					container.name = parsedParams[i].value;
				} else if ((parsedParams[i].name !== undefined) && (parsedParams[i].value !== undefined)) {
					container.params[parsedParams[i].name] = parsedParams[i].value;
				}
			}
		} 
	}
	
	container.tiddlerName = tiddler.title;
	container.name = container.name ? container.name : "calendar";
	var calendarDateCache = config.macros.calendar.calendarDateCache;
	if (!calendarDateCache) {
		calendarDateCache = {};
		config.macros.calendar.calendarDateCache = calendarDateCache;
	}
	var calendarDateTiddlerCache = calendarDateCache[container.tiddlerName];
	if (!calendarDateTiddlerCache) {
		calendarDateTiddlerCache = {};
		calendarDateCache[container.tiddlerName] = calendarDateTiddlerCache;
	}
	
 	// get journal format from SideBarOptions (ELS 5/29/06 - suggested by MartinBudden)
	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]; }

	var month=-1;
	var date=1;
	if (params[0] == 'thismonth') {
		month=today.getMonth();
		date = today.getDate();
	} else if (params[0] == 'lastmonth') {
		month = today.getMonth()-1; if (month==-1) { month=11; year--; }
	} else if (params[0] == 'nextmonth') {
		month = today.getMonth()+1; if (month>11) { month=0; year++; }
	} else if (params[0]&&'+-'.indexOf(params[0].substr(0,1))!=-1) {
		month = today.getMonth()+parseInt(params[0]);
		if (month>11) { year+=Math.floor(month/12); month%=12; };
		if (month<0)  { year+=Math.floor(month/12); month=12+month%12; }
	} else if (params[0]) {
		year = params[0];
		if(params[1]) {
			month=parseInt(params[1])-1;
			if (month>11) month=11; if (month<0) month=0;
			date = (month == today.getMonth()) ? today.getDate() : date;
		}
	}

	if (month!=-1) {
		var fullCalendarInit = store.getTiddlerText('FullCalendar##init');
		if (!container.nofullcalendar && fullCalendarInit) {
			eval(fullCalendarInit);
			calendarDateTiddlerCache[container.name] = calendarDateTiddlerCache[container.name] ? calendarDateTiddlerCache[container.name] : new Date(year, month, date, 0, 0);
			createFullCalendar(place, container);
		} else {
			var calendar = createTiddlyElement(place, 'table', null, 'calendar', null);
			var tbody = createTiddlyElement(calendar, 'tbody');
			tbody.tags = container;
			var cal = tbody;
			calendarDateTiddlerCache[container.name] = calendarDateTiddlerCache[container.name] ? calendarDateTiddlerCache[container.name] : new Date(year, month, 1, 0, 0);
			cacheReminders(calendarDateTiddlerCache[container.name], 31, cal.tags);
			createCalendarOneMonth(tbody, calendarDateTiddlerCache[container.name].getFullYear(), calendarDateTiddlerCache[container.name].getMonth());
			window.reminderCacheForCalendar = null;
		}
	} else {
		var calendar = createTiddlyElement(place, 'table', null, 'calendar', null);
		var tbody = createTiddlyElement(calendar, 'tbody');
		tbody.tags = container;
		var cal = tbody;
		calendarDateTiddlerCache[container.name] = calendarDateTiddlerCache[container.name] ? calendarDateTiddlerCache[container.name] : new Date(year, 0, 1, 0, 0);
		cacheReminders(calendarDateTiddlerCache[container.name], 366, cal.tags);
		createCalendarYear(tbody, calendarDateTiddlerCache[container.name].getFullYear());
		window.reminderCacheForCalendar = null;
	}
}
//}}}
//{{{
function createFullCalendar(place, tags) {
	var lumdiff = function (rgb1, rgb2) {
		var colorToIntArray = function (color) {
			if ((color.indexOf('#') == 0) && ((color.length == 4) || (color.length == 7))) {
				var r = (color.length == 4) ? color.substring(1, 2) : color.substring(1, 3);
				r = parseInt((color.length == 4) ? "" + r + "" + r : r, 16);
				var g = (color.length == 4) ? color.substring(2, 3) : color.substring(3, 5);
				g = parseInt((color.length == 4) ? "" + g + "" + g : g, 16);
				var b = (color.length == 4) ? color.substring(3, 4) : color.substring(5, 7);
				b = parseInt((color.length == 4) ? "" + b + "" + b : b, 16);
				if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
					return [r, g, b];
				}
			}
		}
		var RGB1 = colorToIntArray(rgb1);
		var RGB2 = colorToIntArray(rgb2);
		if (RGB1 && RGB2) {
			// The returned value should be bigger than 5 for best readability.
			// http://www.splitbrain.org/blog/2008-09/18-calculating_color_contrast_with_php
			// http://stackoverflow.com/questions/1331591/given-a-background-color-black-or-white-text
			var L1 = 0.2126 * Math.pow(RGB1[0] / 255, 2.2)
					+ 0.7152 * Math.pow(RGB1[1] / 255, 2.2)
					+ 0.0722 * Math.pow(RGB1[2] / 255, 2.2);
			
			var L2 = 0.2126 * Math.pow(RGB2[0] / 255, 2.2)
					+ 0.7152 * Math.pow(RGB2[1] / 255, 2.2)
					+ 0.0722 * Math.pow(RGB2[2] / 255, 2.2);
			
			if (L1 > L2) {
				return (L1 + 0.05) / (L2 + 0.05);
			} else {
				return (L2 + 0.05) / (L1 + 0.05);
			}
		}
		return 0;
	}
	
	var determineEvents = function (start, end, callback) {
		var reminderEvents = new ScheduleReminderEvents();
		reminderEvents.determineEvents(start, tags.filter, [0, 42]);
		var events = [];
		for (var t = 0; t < reminderEvents.list.length; t++) {
			var reminder = reminderEvents.list[t].reminder;
			
			reminder["params"]["format"] = "TITLE";
			var title = getReminderMessageForDisplay(reminder["diff"], reminder["params"], reminder["matchedDate"], reminder["tiddler"]);
			
			var goToTiddlerMacro = null;
			var goToTiddlerLengh =  "<<goToTiddler".length;
			var goToTiddlerMacroIndex = title.indexOf("<<goToTiddler");
			goToTiddlerLengh = (goToTiddlerMacroIndex == -1) ?  "<<newTiddler".length : goToTiddlerLengh;
			goToTiddlerMacroIndex = (goToTiddlerMacroIndex == -1) ? title.indexOf("<<newTiddler") : goToTiddlerMacroIndex;
			if (goToTiddlerMacroIndex > -1) {
				goToTiddlerMacro = title.substring(goToTiddlerMacroIndex, title.indexOf(">>", goToTiddlerMacroIndex) + ">>".length);
				title = title.substring(0, goToTiddlerMacroIndex) + title.substring(title.indexOf(">>", goToTiddlerMacroIndex) + ">>".length);
			}
			
			var backgroundColor = "#DDDDDD";
			var borderColor = "#DDDDDD";
			var textColor = "#000000";
			var titleStyleMatches = (new RegExp("(@@)(.*)", "g")).exec(title);
			if (titleStyleMatches && (titleStyleMatches.length > 1) && (titleStyleMatches[1] == "@@")) {
				titleStyleMatches.source = title;
				titleStyleMatches.nextMatch = titleStyleMatches.index + titleStyleMatches[1].length;
				var styles = config.formatterHelpers.inlineCssHelper(titleStyleMatches);
				for (var i = 0; i < styles.length; i++) {
					if (styles[i].style == "backgroundColor") {
						var color = styles[i].value;
						backgroundColor = color;
						borderColor = color;
						textColor = (lumdiff(color, "#FFFFFF") < lumdiff(color, "#000000")) ? "#000000" : "#FFFFFF";
					}
				}
				for (var i = 0; i < styles.length; i++) {
					if (styles[i].style == "color") {
						textColor = styles[i].value;
					}
				}
			}
			
			var txt = "";
			if (title) {
				var wikifier = new Wikifier(title, formatter, null, null);
				var e = createTiddlyElement(document.body, "div");
				var jqe = jQuery(e);
				jqe.css("display", "none");
				wikifier.subWikify(e);
				txt = jqe.text();
				removeNode(e);
			} else {
				txt = reminder.tiddler;
			}
			
			var date = new Date(reminder['matchedDate'].getFullYear(), reminder['matchedDate'].getMonth(), reminder['matchedDate'].getDate(), 0, 0, 0, 0);
			var start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			var end = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			var allDay = !reminderEvents.list[t].start || !reminderEvents.list[t].end;
			
			if (!allDay) {
				start.setHours(Number(reminderEvents.list[t].start.substring(0, reminderEvents.list[t].start.indexOf(":"))));
				start.setMinutes(Number(reminderEvents.list[t].start.substring(reminderEvents.list[t].start.indexOf(":") + 1)));
				
				end.setHours(Number(reminderEvents.list[t].end.substring(0, reminderEvents.list[t].end.indexOf(":"))));
				end.setMinutes(Number(reminderEvents.list[t].end.substring(reminderEvents.list[t].end.indexOf(":") + 1)));
				
				var timeMatches = (new RegExp("^(\\s*[0-9]?[0-9]:[0-9]?[0-9]\\s*-\\s*[0-9]?[0-9]:[0-9]?[0-9]\\s*:\\s*)")).exec(txt);
				txt = (timeMatches && (timeMatches.length > 0)) ? txt.substring(timeMatches[0].length) : txt;
			}
			
			var editable = !reminder.tags || (reminder.tags.indexOf("readOnlyReminder") == -1);
			editable = editable && (reminder["params"]["year"] !== undefined) && (reminder["params"]["month"] !== undefined) && ((reminder["params"]["day"] !== undefined) || (reminder["params"]["date"] !== undefined));
			editable = editable && (reminder["params"]["rrule"] === undefined);
			editable = editable && (reminder["params"]["recuryears"] === undefined);
			editable = editable && (reminder["params"]["recurmonths"] === undefined);
			editable = editable && (reminder["params"]["recurdays"] === undefined);
			editable = editable && (reminder["macroIndex"] !== undefined) && (reminder["macroText"] !== undefined);
			
			events[events.length] = {
				title: txt,
				start: start,
				end: end,
				allDay: allDay,
				editable: editable,
				backgroundColor: backgroundColor,
				borderColor: borderColor,
				textColor: textColor,
				initialStart: new Date(start.getTime()),
				initialEnd: new Date(end.getTime()),
				initialAllDay: allDay,
				tiddler: reminder.tiddler,
				reminderMacroIndex: reminder["macroIndex"],
				reminderMacroText: reminder["macroText"],
				goToTiddlerMacro: goToTiddlerMacro
			};
		}
		callback(events);
	};
	
	var startYear = null;
	var startMonth = null;
	var startDate = null;
	var viewName = null;
	var cachedDate = config.macros.calendar.calendarDateCache[tags.tiddlerName][tags.name];
	if (cachedDate) {
		startYear = cachedDate.getFullYear();
		startMonth = cachedDate.getMonth();
		startDate = cachedDate.getDate();
		viewName = config.macros.calendar.calendarDateCache[tags.tiddlerName][tags.name].viewName;
	} else {
		var currentDate = new Date();
		startYear = currentDate.getFullYear();
		startMonth = currentDate.getMonth();
		startDate = currentDate.getDate();
	}
	viewName = viewName ? viewName : "month";
	
	var determineTiddlerTitleAndParams = function (event) {
		var tiddlerTitleAndParams = {title: event.tiddler, params: []};
		
		if (event.goToTiddlerMacro) {
			var macroFormatter = null;
			for (var i = 0; i < config.formatters.length; i++) {
				if (config.formatters[i].name == "macro") {
					macroFormatter = config.formatters[i];
					break;
				}
			}
			
			if (macroFormatter) {
				macroFormatter.lookaheadRegExp.lastIndex = 0;
				var matches = macroFormatter.lookaheadRegExp.exec(event.goToTiddlerMacro);
				if (matches.length > 2) {
					var params = matches[2].parseParams("anon", null, true, false, false);
					if (params) {
						tiddlerTitleAndParams.params = params;
						
						var title = params[1] && params[1].name == "anon" ? params[1].value : "";
						title = getParam(params, "title", title);
						
						tiddlerTitleAndParams.title = (title && (title != "")) ? title : tiddlerTitleAndParams.title;
					}
				}
			}
		}
		
		return tiddlerTitleAndParams;
	};
	
	var eventModified = function (event, revertFunc, jsEvent, ui, view) {
		var reminderUpdated = false;
		if ((event.allDay === event.initialAllDay) && (event.tiddler !== undefined) && (event.reminderMacroIndex !== undefined) && (event.reminderMacroText !== undefined)) {
			var tiddler = store.getTiddler(event.tiddler);
			var tiddlerText = tiddler.text;
			if (tiddlerText.substring(event.reminderMacroIndex, event.reminderMacroIndex + event.reminderMacroText.length) == event.reminderMacroText) {
				var formattedOldDate = event.initialStart.formatString(config.macros.calendar.journalDateFmt);
				var formattedNewDate = event.start.formatString(config.macros.calendar.journalDateFmt);
				
				var oldTiddlerTitle = event.tiddler;
				var newTiddlerTitle = oldTiddlerTitle.replace(new RegExp(formattedOldDate, "gi"), formattedNewDate);
				
				var timezoneHours = (event.start.getTimezoneOffset() / 60) | 0;
				var timezoneMinutes = Math.abs(event.start.getTimezoneOffset() - (timezoneHours * 60));
				var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes;
				
				var newReminder = event.reminderMacroText.replace(/year:[0-9]*/, "year:" + event.start.getFullYear()).replace(/month:[0-9]*/, "month:" + (event.start.getMonth() + 1)).replace(/(day|date):[0-9]*/, "date:" + event.start.getDate()).replace(/tag:.timezone:UTC.[0-9]*./, "tag:\"timezone:UTC" + (event.start.getTimezoneOffset() <= 0 ? "+" : "-") + timezone + "\"");
				if (!event.allDay) {
					var time = event.initialStart;
					var oldStartTime = (time.getHours() < 10 ? '0' : '') + time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
					time = event.initialAllDay ? event.initialStart : event.initialEnd;
					var oldEndTime = (time.getHours() < 10 ? '0' : '') + time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
					
					time = event.start;
					var newStartTime = (time.getHours() < 10 ? '0' : '') + time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
					var newEndDate = event.allDay ? event.start : event.end;
					var newEndTime = "23:59";
					if ((time.getFullYear() == newEndDate.getFullYear()) && (time.getMonth() == newEndDate.getMonth()) && (time.getDate() == newEndDate.getDate())) {
						time = newEndDate;
						newEndTime = (time.getHours() < 10 ? '0' : '') + time.getHours() + ':' + (time.getMinutes() < 10 ? '0' : '') + time.getMinutes();
					}
					
					var startTimePosition = newReminder.indexOf(oldStartTime);
					var endTimePosition = newReminder.indexOf(oldEndTime, (startTimePosition == -1) ? 0 : startTimePosition + oldStartTime.length);
					
					if (startTimePosition > -1) {
						newReminder = newReminder.substring(0, startTimePosition) + newStartTime + newReminder.substring(startTimePosition + oldStartTime.length);
					}
					if (endTimePosition > -1) {
						newReminder = newReminder.substring(0, endTimePosition) + newEndTime + newReminder.substring(endTimePosition + oldEndTime.length);
					}
				}
				tiddlerText = tiddlerText.substring(0, event.reminderMacroIndex) + newReminder + tiddlerText.substring(event.reminderMacroIndex + event.reminderMacroText.length);
				
				var newTags = !tiddler.tags ? [] : tiddler.tags.slice();
				if (newTags.indexOf(formattedOldDate) > -1) {
					newTags[newTags.indexOf(formattedOldDate)] = formattedNewDate;
				}
				
				store.saveTiddler(oldTiddlerTitle, newTiddlerTitle, tiddlerText, config.options.txtUserName, new Date(), newTags, tiddler.fields);
				store.setDirty(true);
				story.refreshTiddler(newTiddlerTitle, null, true);
				//story.refreshTiddler(tags.tiddlerName, null, true);
				
				if (config.options.chkUpdateTaggedTiddlersOnSaveChanges && config.commands.saveTiddler.updateTaggedTiddlers) {
					config.commands.saveTiddler.updateTaggedTiddlers(oldTiddlerTitle, newTiddlerTitle);
				}
				
				fullCalendarConfig.calendarInstance.fullCalendar('refetchEvents');
				
				reminderUpdated = true;
			}
		}
		
		if (!reminderUpdated) {
			revertFunc();
		}
	};
	
	var fullCalendarId = "fc" + Math.round((Math.random() * 10000));
	createTiddlyElement(place, 'div', fullCalendarId, 'fullcalendar');
	
	var fullCalendarConfig = {
		year: startYear,
		month: startMonth,
		date: startDate,
		defaultView: viewName,
		header: {
			left: 'prevYear,prev,next,nextYear today',
			center: 'title',
			right: 'month,agendaWeek,agendaDay'
		},
		allDayText: 'Tasks',
		axisFormat: 'HH:mm',
		timeFormat: 'HH:mm{ - HH:mm}',
		slotMinutes: 15,
		weekNumbers: config.options.chkDisplayWeekNumbers,
		weekNumberCalculation: function (date) {
			return config.macros.calendar.weekNumberCalculation(date, this.firstDay);
		},
		firstDay: (!isNaN(parseInt(config.options.txtCalFirstDay)) && isFinite(config.options.txtCalFirstDay)) ? ((parseInt(config.options.txtCalFirstDay) + 1) % 7) : 0,
		selectable: true,
		selectHelper: true,
		select: function (start, end, allDay, jsEvent) {
			Popup.remove();
			var title = prompt(fullCalendarConfig["newEventPromptTitle"] ? fullCalendarConfig["newEventPromptTitle"] : 'Title');
			fullCalendarConfig.calendarInstance.fullCalendar('unselect');
			if (title) {
				var date = start;
				var timezoneHours = (date.getTimezoneOffset() / 60) | 0;
				var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60));
				var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes;
				
				var formattedDate = date.formatString(config.macros.calendar.journalDateFmt);
				
				title = formattedDate + " : " + title;
				
				var newTiddlerTags = [];
				newTiddlerTags[newTiddlerTags.length] = formattedDate;
				var eventTags = allDay ? tags.task : tags.meeting;
				if (!eventTags) {
					newTiddlerTags[newTiddlerTags.length] = allDay ? "task" : "meeting";
				} else {
					var tagList = eventTags.readBracketedList();
					for (var i = 0; i < tagList.length; i++) {
						newTiddlerTags[newTiddlerTags.length] = tagList[i];
					}
				}
				
				var time = "";
				if (!allDay) {
					time = (start.getHours() < 10 ? '0' : '') + start.getHours() + ':' + (start.getMinutes() < 10 ? '0' : '') + start.getMinutes();
					time = time + " - ";
					if ((start.getFullYear() == end.getFullYear()) && (start.getMonth() == end.getMonth()) && (start.getDate() == end.getDate())) {
						time = time + (end.getHours() < 10 ? '0' : '') + end.getHours() + ':' + (end.getMinutes() < 10 ? '0' : '') + end.getMinutes();
					} else {
						time = time + "23:59";
					}
					time = time + " : ";
				}
				
				var remindersFormat = fullCalendarConfig["remindersFormat"];
				
				var goToTaskLabel = ((fullCalendarConfig["goToTaskLabel"] !== undefined) && (fullCalendarConfig["goToTaskLabel"] !== null)) ? fullCalendarConfig["goToTaskLabel"] : ">";
				var goToMeetingLabel = ((fullCalendarConfig["goToMeetingLabel"] !== undefined) && (fullCalendarConfig["goToMeetingLabel"] !== null)) ? fullCalendarConfig["goToMeetingLabel"] : ">";
				
				var taskPrefix = ((fullCalendarConfig["taskPrefix"] !== undefined) && (fullCalendarConfig["taskPrefix"] !== null)) ? fullCalendarConfig["taskPrefix"] : "@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@";
				var meetingPrefix = ((fullCalendarConfig["meetingPrefix"] !== undefined) && (fullCalendarConfig["meetingPrefix"] !== null)) ? fullCalendarConfig["meetingPrefix"] : "[x(cancelled)] Cancelled [x(doNotPublish)] Do not publish";
				
				var rem = '<<reminder year:%2 month:%1 day:%0 tag:"timezone:UTC%3" function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() ' + (remindersFormat  ? 'format:"' + remindersFormat + '" ' : '') + 'title:"@@background-color:' + (allDay ? config.macros.date.remindersbg : config.macros.date.eventbg) + ';color:' + (allDay ? config.macros.date.reminderstxt : config.macros.date.eventtxt) + ';priority:' + (allDay ? config.macros.date.reminderspriority : config.macros.date.eventpriority) + ';' + time + '\\"\\"\\"TIDDLERNAME\\"\\"\\"@@ \\<\\<goToTiddler label:' + (allDay ? goToTaskLabel : goToMeetingLabel) + ' title:{{\'REMINDERDATE : TIDDLERNAMEESC\'}} ' + (allDay ? (taskPrefix ? "text:{{'" + taskPrefix.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") + "\\n\\n'}} " : "") : (meetingPrefix ? "text:{{'" + meetingPrefix.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") + "\\n\\n'}} " : "")) + 'tag:' + (allDay ? 'task' : 'meeting') + ' tag:[[TIDDLERFULLNAME]] focus:text\\>\\>">>';
				rem = rem.format([date.getDate(),date.getMonth()+1,date.getFullYear(),(date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone]);
				
				var text = (allDay ? (taskPrefix ? taskPrefix + "\n\n" : "") : (meetingPrefix ? meetingPrefix + "\n\n" : "")) + rem + "\n\n";
				if (store.getTiddler(title) || readOnly) {
					story.displayTiddler(resolveTarget(jsEvent), title, null, true, null, null, false);
				} else {
					story.displayTiddler(resolveTarget(jsEvent), title, DEFAULT_EDIT_TEMPLATE, false, null, null, false);
					
					if (!store.isShadowTiddler(title)) {
						story.addCustomFields(story.getTiddler(title), String.encodeHashMap(config.defaultCustomFields));
					}
					
					if (text !== undefined) {
						story.getTiddlerField(title, "text").value = text.format([title]);
					}
					
					for (var t = 0; t < newTiddlerTags.length; t++) {
						story.setTiddlerTag(title, newTiddlerTags[t], 1);
					}
					
					story.focusTiddler(title, "text");
				}
			}
		},
		editable: false,
		events: determineEvents,
		viewRender: function (view, element) {
			var cacheName = tags.tiddlerName;
			var calendarName = tags.name;
			var cachedDate = new Date();
			cachedDate = (cachedDate.getTime() >= view.start.getTime()) && (cachedDate.getTime() <= view.end.getTime()) ? cachedDate : view.start;
			config.macros.calendar.calendarDateCache[cacheName][calendarName] = cachedDate;
			config.macros.calendar.calendarDateCache[cacheName][calendarName].viewName = view.name;
		},
		eventClick: function (calEvent, jsEvent, view) {
			if (calEvent.popupTimerId) {
				window.clearTimeout(calEvent.popupTimerId);
				calEvent.popupTimerId = null;
			}
			Popup.remove();
			var tiddlerTitleAndParams = determineTiddlerTitleAndParams(calEvent);
			var title = tiddlerTitleAndParams.title;
			if (title) {
				var tiddler = store.getTiddler(title);
				if (tiddler || readOnly) {
					story.displayTiddler(resolveTarget(jsEvent), title, null, true, null, null, false);
				} else {
					story.displayTiddler(resolveTarget(jsEvent), title, DEFAULT_EDIT_TEMPLATE, false, null, null, false);
					
					if (!store.isShadowTiddler(title)) {
						story.addCustomFields(story.getTiddler(title), String.encodeHashMap(config.defaultCustomFields));
					}
					
					var params = tiddlerTitleAndParams.params;
					
					var text = getParam(params, "text");
					if (text !== undefined) {
						story.getTiddlerField(title, "text").value = text.format([title]);
					}

					for (var t = 1; t < params.length; t++) {
						if ((params[t].name == "anon" && t != 1) || (params[t].name == "tag")) {
							story.setTiddlerTag(title, params[t].value, 1);
						}
					}
					
					story.focusTiddler(title, "text");
				}
			}
			
			return false;
		},
		eventDragStart: function (event, jsEvent, ui, view) {
			event.preventPopup = true;
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
			Popup.remove();
		},
		eventDragStop: function (event, jsEvent, ui, view) {
			event.preventPopup = false;
		},
		eventDrop: function (event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
			event.preventPopup = false;
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
			Popup.remove();
			eventModified(event, revertFunc, jsEvent, ui, view);
		},
		eventResizeStart: function (event, jsEvent, ui, view) {
			event.preventPopup = true;
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
			Popup.remove();
		},
		eventResizeStop: function (event, jsEvent, ui, view) {
			event.preventPopup = false;
		},
		eventResize: function (event, dayDelta, minuteDelta, revertFunc, jsEvent, ui, view) {
			event.preventPopup = false;
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
			Popup.remove();
			eventModified(event, revertFunc, jsEvent, ui, view);
		},
		eventMouseover: function (event, jsEvent, view) {
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
			if (!event.preventPopup) {
				var title = determineTiddlerTitleAndParams(event).title;
				event.popupTimerId = config.macros.tiddlerPreviewPopup.showDelayed(jsEvent, title, store.tiddlerExists(title) ? title : null, 1000);
			}
		},
		eventMouseout: function (event, jsEvent, view) {
			if (event.popupTimerId) {
				window.clearTimeout(event.popupTimerId);
				event.popupTimerId = null;
			}
		},
	};
	
	var isNumber = function (n) {
		return !isNaN(parseFloat(n)) && isFinite(n);
	};
	
	if (tags.params)  {
		for (var optionName in tags.params) {
			if (isNumber(tags.params[optionName])) {
				fullCalendarConfig[optionName] = parseFloat(tags.params[optionName]);
			} else {
				var value = tags.params[optionName];
				try {
					eval("value = " + tags.params[optionName]);
					if (typeof value == "boolean") {
						fullCalendarConfig[optionName] = value;
					} else if (typeof value == "function") {
						fullCalendarConfig[optionName] = value;
					} else if (value instanceof Array) {
						fullCalendarConfig[optionName] = value;
					} else if ((value != null) && (typeof value === 'object')) {
						fullCalendarConfig[optionName] = value;
					} else {
						value = tags.params[optionName];
					}
				} catch (e) {}
				fullCalendarConfig[optionName] = value ;
			}
		}
	}
	
	fullCalendarConfig.calendarInstance = jQuery('#' + fullCalendarId).fullCalendar(fullCalendarConfig);
}
//}}}
//{{{
function cacheReminders(date, leadtime, tags)
{
	if (window.findTiddlersWithReminders == null) return;
	window.reminderCacheForCalendar = {};
	var leadtimeHash = [];
	leadtimeHash [0] = 0;
	leadtimeHash [1] = leadtime;
	var t = findTiddlersWithReminders(date, leadtimeHash, tags ? tags.filter : null, 1);
	var re = null;
	var fm = null;
	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.
		var reminder = window.reminderCacheForCalendar[t[i]['matchedDate']];
		
		var title = null;
		var cacheNewReminder = !reminder || (reminder.indexOf("@@") == -1);
		if (!cacheNewReminder) {
			re = new RegExp('priority\\s*:\\s*(\\d*)', 'mg');
			var newPriority = null;
			t[i]["params"]["format"] = "TITLE";
			title = getReminderMessageForDisplay(t[i]["diff"], t[i]["params"], t[i]["matchedDate"], t[i]["tiddler"]);
			fm = re.exec(title);
			if (fm && (fm[1] != null)) {
				var pa=fm[1].readMacroParams();
				if (!isNaN(pa[0])) {
					newPriority = parseInt(pa[0]);
				}
			}
			if (newPriority != null) {
				re = new RegExp('priority\\s*:\\s*(\\d*)', 'mg');
				var oldPriority = null;
				fm = re.exec(reminder);
				if (fm && (fm[1] != null)) {
					var pa=fm[1].readMacroParams();
					if (!isNaN(pa[0])) {
						oldPriority = parseInt(pa[0]);
					}
				}
				if (oldPriority == null) {
					cacheNewReminder = true;
				} else {
					cacheNewReminder = newPriority > oldPriority;
				}
			}

		}
		if (cacheNewReminder) {
			if (title == null) {
				t[i]["params"]["format"] = "TITLE";
				title = getReminderMessageForDisplay(t[i]["diff"], t[i]["params"], t[i]["matchedDate"], t[i]["tiddler"]);
			}
			window.reminderCacheForCalendar[t[i]['matchedDate']] = 'reminder:' + title; 
		}
	}
}
//}}}
//{{{
function createCalendarMonthHeader(cal, row, name, nav, year, mon)
{
	var month;
	if (nav) {
		var back = createTiddlyElement(row, 'td');
		back.align = 'center';
		back.style.background = config.macros.calendar.monthbg;
		
		var monthNavigationHandler = function(newyear, newmon) {
			removeChildren(cal);
			if (config.macros.calendar.calendarDateCache && config.macros.calendar.calendarDateCache[cal.tags.tiddlerName]) {
				config.macros.calendar.calendarDateCache[cal.tags.tiddlerName][cal.tags.name] = new Date(newyear, newmon , 1, 0, 0);
			}
			cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31, cal.tags);
			createCalendarOneMonth(cal, newyear, newmon);
			return false;
		};
		var backMonHandler = function() {
			var newyear = year;
			var newmon = mon-1;
			if(newmon == -1) { newmon = 11; newyear = parseInt(newyear)-1;}
			return monthNavigationHandler(newyear, newmon); // consume click
		};
		createTiddlyButton(back, '<', 'Previous month', backMonHandler);
		
		month = createTiddlyElement(row, 'td', null, 'calendarMonthname')
		month.setAttribute('colSpan', config.options.chkDisplayWeekNumbers?6:5);//wn**
		
		var yearMonth = "" + year +"-" + (mon < 9 ? "0" : "") + (mon + 1);
		
		var monthTitleLink = createTiddlyLink(month, "" + year, false);
		monthTitleLink.appendChild(document.createTextNode("" + year));
		monthTitleLink.title = yearMonth;
		monthTitleLink.style.fontStyle = "normal";
		monthTitleLink.onclick = function (e) {
			var p = Popup.create(this); if (!p) return false;
			if (!readOnly || store.tiddlerExists(name)) {
				createTiddlyLink(createTiddlyElement(p, 'li'), yearMonth, true);
			} else {
				createTiddlyText(createTiddlyElement(p, 'li'), yearMonth);
			}
			for (var i = year - 2; i < year + 3; i++) {
				var link = createTiddlyLink(createTiddlyElement(p, 'li'), "" + i, true);
				link.style.fontStyle = "normal";
				link.selectedYear = i;
				link.onclick = function (e) {
					monthNavigationHandler(this.selectedYear, mon);
					Popup.remove();
					e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); return false;
				};
			}
			Popup.show(); e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); return false;
		};
		
		month.appendChild(document.createTextNode("-"));
		
		monthTitleLink = createTiddlyLink(month, config.macros.calendar.monthnames[mon], false);
		monthTitleLink.appendChild(document.createTextNode((mon < 9 ? "0" : "") + (mon + 1)));
		monthTitleLink.title = yearMonth;
		monthTitleLink.style.fontStyle = "normal";
		monthTitleLink.onclick = function (e) {
			var p = Popup.create(this); if (!p) return false;
			if (!readOnly || store.tiddlerExists(name)) {
				createTiddlyLink(createTiddlyElement(p, 'li'), yearMonth, true);
			} else {
				createTiddlyText(createTiddlyElement(p, 'li'), yearMonth);
			}
			for (var i = 0; i < config.macros.calendar.monthnames.length; i++) {
				var link = createTiddlyLink(createTiddlyElement(p, 'li'), (i < 9 ? "0" : "") + (i + 1), true);
				link.style.fontStyle = "normal";
				link.selectedMonth = i;
				link.onclick = function (e) {
					monthNavigationHandler(year, this.selectedMonth);
					Popup.remove();
					e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); return false;
				};
			}
			Popup.show(); e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation(); return false;
		};
		
		var fwd = createTiddlyElement(row, 'td');
		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 = parseInt(newyear)+1;}
			return monthNavigationHandler(newyear, newmon); // consume click
		};
		createTiddlyButton(fwd, '>', 'Next month', fwdMonHandler);
	} else {
		month = createTiddlyElement(row, 'td', null, 'calendarMonthname', name)
		month.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?8:7);//wn**
	}
	month.align = 'center';
	month.style.background = config.macros.calendar.monthbg;
}
//}}}
//{{{
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 weekNumber = config.macros.calendar.weekNumberCalculation(ww);
			var td=createTiddlyElement(row, 'td');//wn**
			var txtWeekNumberLinkFormat = config.options.txtWeekNumberLinkFormat;
			txtWeekNumberLinkFormat = txtWeekNumberLinkFormat.replace(/0WW/g,String.zeroPad(weekNumber,2));
			txtWeekNumberLinkFormat = txtWeekNumberLinkFormat.replace(/WW/g,weekNumber);
			var link=createTiddlyLink(td,ww.formatString(txtWeekNumberLinkFormat),false);
			var txtWeekNumberDisplayFormat = row.cal.tags.params["weekNumberTitle"] ? row.cal.tags.params["weekNumberTitle"] + "0WW" : config.options.txtWeekNumberDisplayFormat;
			txtWeekNumberDisplayFormat = txtWeekNumberDisplayFormat.replace(/0WW/g,String.zeroPad(weekNumber,2));
			txtWeekNumberDisplayFormat = txtWeekNumberDisplayFormat.replace(/WW/g,weekNumber);
			link.appendChild(document.createTextNode(
				ww.formatString(txtWeekNumberDisplayFormat)));
		}
		else createTiddlyElement(row, 'td');//wn**
	}
	for(i = 0; i < col; i++)
		createTiddlyElement(row, 'td');
	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');
		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 10/30/05 - use <<date>> macro's showDate() function to create popup
			// ELS 05/29/06 - use journalDateFmt 
			if (window.showDate) showDate(daycell,celldate,'popup','DD',
				config.macros.calendar.journalDateFmt,true, isaWeekend, row.cal ? row.cal.tags : null);
			else {
				if(isaWeekend) daycell.style.background = config.macros.calendar.weekendbg;
				var title = celldate.formatString(config.macros.calendar.journalDateFmt);
				if(calendarIsHoliday(celldate))
					daycell.style.background = config.macros.calendar.holidaybg;
				var now=new Date();
				if ((now-celldate>=0) && (now-celldate<86400000)) // is today?
					daycell.style.background = config.macros.calendar.todaybg;
				if(window.findTiddlersWithReminders == null) {
					var link = createTiddlyLink(daycell, title, false);
					link.appendChild(document.createTextNode(day));
				} else
					var button = createTiddlyButton(daycell, day, title, onClickCalendarDate);
			}
		}
		day++;
	}
}
//}}}
//{{{
var insertedCode = [
		' if (config.macros.calendar.calendarDateCache && config.macros.calendar.calendarDateCache[calendar.tags.tiddlerName]) config.macros.calendar.calendarDateCache[calendar.tags.tiddlerName][calendar.tags.name] = new Date(parseInt(year)-1, 0, 1, 0, 0); cacheReminders(new Date(parseInt(year)-1, 0, 1, 0, 0), 366, calendar.tags); ',
		' if (config.macros.calendar.calendarDateCache && config.macros.calendar.calendarDateCache[calendar.tags.tiddlerName]) config.macros.calendar.calendarDateCache[calendar.tags.tiddlerName][calendar.tags.name] = new Date(parseInt(year)+1, 0, 1, 0, 0); cacheReminders(new Date(parseInt(year)+1, 0, 1, 0, 0), 366, calendar.tags); '
];
var code = eval("createCalendarYear").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; i < 2; i++) {
	startPosition = code.indexOf("removeChildren", startPosition);
	if (startPosition > -1) {
		startPosition = code.indexOf(");", startPosition);
		if (startPosition > -1) {
			newCode = code.substring(0, startPosition + 2) + insertedCode[i] + code.substring(startPosition + 2);
			code = newCode;
		}
	}
}
if (newCode != null) {
	eval("createCalendarYear = function createCalendarYear" + code.substring(newCode.indexOf("(")));
}

var code = eval("createCalendarDayRows").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; i < 1; i++) {
	startPosition = code.indexOf("createCalendarDays", startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition) + ' row.cal = cal; ' + code.substring(startPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval("createCalendarDayRows = function createCalendarDayRows" + code.substring(newCode.indexOf("(")));
}

var code = eval("createCalendarDayRowsSingle").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; i < 1; i++) {
	startPosition = code.indexOf("createCalendarDays", startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition) + ' row.cal = cal; ' + code.substring(startPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval("createCalendarDayRowsSingle = function createCalendarDayRowsSingle" + code.substring(newCode.indexOf("(")));
}
//}}}
/***
|Name|CheckboxPlugin|
|Source|http://www.TiddlyTools.com/#CheckboxPlugin|
|Documentation|http://www.TiddlyTools.com/#CheckboxPluginInfo|
|Version|2.4.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|Add checkboxes to your tiddler content|
This plugin extends the TiddlyWiki syntax to allow definition of checkboxes that can be embedded directly in tiddler content.  Checkbox states are preserved by:
* by setting/removing tags on specified tiddlers,
* or, by setting custom field values on specified tiddlers,
* or, by saving to a locally-stored cookie ID,
* or, automatically modifying the tiddler content (deprecated)
When an ID is assigned to the checkbox, it enables direct programmatic access to the checkbox DOM element, as well as creating an entry in TiddlyWiki's config.options[ID] internal data.  In addition to tracking the checkbox state, you can also specify custom javascript for programmatic initialization and onClick event handling for any checkbox, so you can provide specialized side-effects in response to state changes.
!!!!!Documentation
>see [[CheckboxPluginInfo]]
!!!!!Revisions
<<<
2008.01.08 [*.*.*] plugin size reduction: documentation moved to [[CheckboxPluginInfo]]
2008.01.05 [2.4.0] set global "window.place" to current checkbox element when processing checkbox clicks.  This allows init/beforeClick/afterClick handlers to reference RELATIVE elements, including using "story.findContainingTiddler(place)".  Also, wrap handlers in "function()" so "return" can be used within handler code.
|please see [[CheckboxPluginInfo]] for additional revision details|
2005.12.07 [0.9.0] initial BETA release
<<<
!!!!!Code
***/
//{{{
version.extensions.CheckboxPlugin = {major: 2, minor: 4, revision:0 , date: new Date(2008,1,5)};
//}}}
//{{{
config.checkbox = { refresh: { tagged:true, tagging:true, container:true } };
config.formatters.push( {
	name: "checkbox",
	match: "\\[[xX_ ][\\]\\=\\(\\{]",
	lookahead: "\\[([xX_ ])(=[^\\s\\(\\]{]+)?(\\([^\\)]*\\))?({[^}]*})?({[^}]*})?({[^}]*})?\\]",
	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) {
			// get params
			var checked=(lookaheadMatch[1].toUpperCase()=="X");
			var id=lookaheadMatch[2];
			var target=lookaheadMatch[3];
			if (target) target=target.substr(1,target.length-2).trim(); // trim off parentheses
			var fn_init=lookaheadMatch[4];
			var fn_clickBefore=lookaheadMatch[5];
			var fn_clickAfter=lookaheadMatch[6];
			var tid=story.findContainingTiddler(w.output);  if (tid) tid=tid.getAttribute("tiddler");
			var srctid=w.tiddler?w.tiddler.title:null;
			config.macros.checkbox.create(w.output,tid,srctid,w.matchStart+1,checked,id,target,config.checkbox.refresh,fn_init,fn_clickBefore,fn_clickAfter);
			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
		}
	}
} );
config.macros.checkbox = {
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		if(!(tiddler instanceof Tiddler)) { // if no tiddler passed in try to find one
			var here=story.findContainingTiddler(place);
			if (here) tiddler=store.getTiddler(here.getAttribute("tiddler"))
		}
		var srcpos=0; // "inline X" not applicable to macro syntax
		var target=params.shift(); if (!target) target="";
		var defaultState=params[0]=="checked"; if (defaultState) params.shift();
		var id=params.shift(); if (id && !id.length) id=null;
		var fn_init=params.shift(); if (fn_init && !fn_init.length) fn_init=null;
		var fn_clickBefore=params.shift();
		if (fn_clickBefore && !fn_clickBefore.length) fn_clickBefore=null;
		var fn_clickAfter=params.shift();
		if (fn_clickAfter && !fn_clickAfter.length) fn_clickAfter=null;
		var refresh={ tagged:true, tagging:true, container:false };
		this.create(place,tiddler.title,tiddler.title,0,defaultState,id,target,refresh,fn_init,fn_clickBefore,fn_clickAfter);
	},
	create: function(place,tid,srctid,srcpos,defaultState,id,target,refresh,fn_init,fn_clickBefore,fn_clickAfter) {
		// create checkbox element
		var c = document.createElement("input");
		c.setAttribute("type","checkbox");
		c.onclick=this.onClickCheckbox;
		c.srctid=srctid; // remember source tiddler
		c.srcpos=srcpos; // remember location of "X"
		c.container=tid; // containing tiddler (may be null if not in a tiddler)
		c.tiddler=tid; // default target tiddler 
		c.refresh = {};
		c.refresh.container = refresh.container;
		c.refresh.tagged = refresh.tagged;
		c.refresh.tagging = refresh.tagging;
		place.appendChild(c);
		// set default state
		c.checked=defaultState;
		// track state in config.options.ID
		if (id) {
			c.id=id.substr(1); // trim off leading "="
			if (config.options[c.id]!=undefined)
				c.checked=config.options[c.id];
			else
				config.options[c.id]=c.checked;
		}
		// track state in (tiddlername|tagname) or (fieldname@tiddlername)
		if (target) {
			var pos=target.indexOf("@");
			if (pos!=-1) {
				c.field=pos?target.substr(0,pos):"checked"; // get fieldname (or use default "checked")
				c.tiddler=target.substr(pos+1); // get specified tiddler name (if any)
				if (!c.tiddler || !c.tiddler.length) c.tiddler=tid; // if tiddler not specified, default == container
				if (store.getValue(c.tiddler,c.field)!=undefined)
					c.checked=(store.getValue(c.tiddler,c.field)=="true"); // set checkbox from saved state
			} else {
				var pos=target.indexOf("|"); if (pos==-1) var pos=target.indexOf(":");
				c.tag=target;
				if (pos==0) c.tag=target.substr(1); // trim leading "|" or ":"
				if (pos>0) { c.tiddler=target.substr(0,pos); c.tag=target.substr(pos+1); }
				if (!c.tag.length) c.tag="checked";
				var t=store.getTiddler(c.tiddler);
				if (t && t.tags)
					c.checked=t.isTagged(c.tag); // set checkbox from saved state
			}
		}
		// trim off surrounding { and } delimiters from init/click handlers
		if (fn_init) c.fn_init="(function(){"+fn_init.trim().substr(1,fn_init.length-2)+"})()";
		if (fn_clickBefore) c.fn_clickBefore="(function(){"+fn_clickBefore.trim().substr(1,fn_clickBefore.length-2)+"})()";
		if (fn_clickAfter) c.fn_clickAfter="(function(){"+fn_clickAfter.trim().substr(1,fn_clickAfter.length-2)+"})()";
		c.init=true; c.onclick(); c.init=false; // compute initial state and save in tiddler/config/cookie
	},
	onClickCheckbox: function(event) {
		window.place=this;
		if (this.init && this.fn_init) // custom function hook to set initial state (run only once)
			{ try { eval(this.fn_init); } catch(e) { displayMessage("Checkbox init error: "+e.toString()); } }
		if (!this.init && this.fn_clickBefore) // custom function hook to override changes in checkbox state
			{ try { eval(this.fn_clickBefore) } catch(e) { displayMessage("Checkbox onClickBefore error: "+e.toString()); } }
		if (this.id)
			// save state in config AND cookie (only when ID starts with 'chk')
			{ config.options[this.id]=this.checked; if (this.id.substr(0,3)=="chk") saveOptionCookie(this.id); }
		if (this.srctid && this.srcpos>0 && (!this.id || this.id.substr(0,3)!="chk") && !this.tag && !this.field) {
			// save state in tiddler content only if not using cookie, tag or field tracking
			var t=store.getTiddler(this.srctid); // put X in original source tiddler (if any)
			if (t && this.checked!=(t.text.substr(this.srcpos,1).toUpperCase()=="X")) { // if changed
				t.set(null,t.text.substr(0,this.srcpos)+(this.checked?"X":"_")+t.text.substr(this.srcpos+1),null,null,t.tags);
				if (!story.isDirty(t.title)) story.refreshTiddler(t.title,null,true);
				store.setDirty(true);
			}
		}
		if (this.field) {
			if (this.checked && !store.tiddlerExists(this.tiddler))
				store.saveTiddler(this.tiddler,this.tiddler,"",config.options.txtUserName,new Date());
			// set the field value in the target tiddler
			store.setValue(this.tiddler,this.field,this.checked?"true":"false");
			// DEBUG: displayMessage(this.field+"@"+this.tiddler+" is "+this.checked);
		}
		if (this.tag) {
			if (this.checked && !store.tiddlerExists(this.tiddler))
				store.saveTiddler(this.tiddler,this.tiddler,"",config.options.txtUserName,new Date());
			var t=store.getTiddler(this.tiddler);
			if (t) {
				var tagged=(t.tags && t.tags.indexOf(this.tag)!=-1);
				if (this.checked && !tagged) { t.tags.push(this.tag); store.setDirty(true); }
				if (!this.checked && tagged) { t.tags.splice(t.tags.indexOf(this.tag),1); store.setDirty(true); }
			}
			// if tag state has been changed, update display of corresponding tiddlers (unless they are in edit mode...)
			if (this.checked!=tagged) {
				if (this.refresh.tagged) {
					if (!story.isDirty(this.tiddler)) // the TAGGED tiddler in view mode
						story.refreshTiddler(this.tiddler,null,true); 
					else // the TAGGED tiddler in edit mode (with tags field)
						config.macros.checkbox.refreshEditorTagField(this.tiddler,this.tag,this.checked);
				}
				if (this.refresh.tagging)
					if (!story.isDirty(this.tag)) story.refreshTiddler(this.tag,null,true); // the TAGGING tiddler
			}
		}
		if (!this.init && this.fn_clickAfter) // custom function hook to react to changes in checkbox state
			{ try { eval(this.fn_clickAfter) } catch(e) { displayMessage("Checkbox onClickAfter error: "+e.toString()); } }
		// refresh containing tiddler (but not during initial rendering, or we get an infinite loop!) (and not when editing container)
		if (!this.init && this.refresh.container && this.container!=this.tiddler)
			if (!story.isDirty(this.container)) story.refreshTiddler(this.container,null,true); // the tiddler CONTAINING the checkbox
		return true;
	},
	refreshEditorTagField: function(title,tag,set) {
		var tagfield=story.getTiddlerField(title,"tags");
		if (!tagfield||tagfield.getAttribute("edit")!="tags") return; // if no tags field in editor (i.e., custom template)
		var tags=tagfield.value.readBracketedList();
		if (tags.contains(tag)==set) return; // if no change needed
		if (set) tags.push(tag); // add tag
		else tags.splice(tags.indexOf(tag),1); // remove tag
		for (var t=0;t<tags.length;t++) tags[t]=String.encodeTiddlyLink(tags[t]);
		tagfield.value=tags.join(" "); // reassemble tag string (with brackets as needed)
		return;
	}
}
//}}}
//{{{
// NOTE: This function cannot be elegantly overridden, since a reference to it is assigned  to the checkbox HTML input control during its creation, and the function uses other attributes of that control's instance.
config.macros.checkbox.onClickCheckbox = function(event) {
	var wasDirty = store.isDirty();
	window.place=this;
	if (this.init && this.fn_init) // custom function hook to set initial state (run only once)
		{ try { eval(this.fn_init); } catch(e) { displayMessage("Checkbox init error: "+e.toString()); } }
	if (!this.init && this.fn_clickBefore) // custom function hook to override changes in checkbox state
		{ try { eval(this.fn_clickBefore) } catch(e) { displayMessage("Checkbox onClickBefore error: "+e.toString()); } }
	if (this.id)
		// save state in config AND cookie (only when ID starts with 'chk')
		{ config.options[this.id]=this.checked; if (this.id.substr(0,3)=="chk") saveOptionCookie(this.id); }
	if (!this.init && this.srctid && this.srcpos>0 && (!this.id || this.id.substr(0,3)!="chk") && !this.tag && !this.field) {
		// save state in tiddler content only if not using cookie, tag or field tracking
		var t=store.getTiddler(this.srctid); // put X in original source tiddler (if any)
		if (t && this.checked!=(t.text.substr(this.srcpos,1).toUpperCase()=="X")) { // if changed
			t.set(null,t.text.substr(0,this.srcpos)+(this.checked?"X":"_")+t.text.substr(this.srcpos+1),null,null,t.tags);
			t.modified = new Date();
			if (!story.isDirty(t.title)) story.refreshTiddler(t.title,null,true);
			store.setDirty(true);
		}
	}
	if (this.field && this.tiddler) {
		if (store.tiddlerExists(this.tiddler)) {
			// set the field value in the target tiddler
			store.setValue(this.tiddler,this.field,this.checked?"true":"false");
			// DEBUG: displayMessage(this.field+"@"+this.tiddler+" is "+this.checked);
		}
	}
	if (this.tag && this.tiddler) {
		if (store.tiddlerExists(this.tiddler)) {
			var t=store.getTiddler(this.tiddler);
			if (t) {
				var tagged=(t.tags && t.tags.indexOf(this.tag)!=-1);
				if (this.checked && !tagged) { t.tags.push(this.tag); t.modified = new Date(); store.setDirty(true); }
				if (!this.checked && tagged) { t.tags.splice(t.tags.indexOf(this.tag),1); t.modified = new Date(); store.setDirty(true); }
			}
			// if tag state has been changed, update display of corresponding tiddlers (unless they are in edit mode...)
			if (this.checked!=tagged) {
				if (this.refresh.tagged) {
					if (!story.isDirty(this.tiddler)) // the TAGGED tiddler in view mode
						if (store.getTiddler(this.tiddler)) story.refreshTiddler(this.tiddler,null,true); 
					else // the TAGGED tiddler in edit mode (with tags field)
						config.macros.checkbox.refreshEditorTagField(this.tiddler,this.tag,this.checked);
				}
				if (this.refresh.tagging)
					if (!story.isDirty(this.tag)) story.refreshTiddler(this.tag,null,true); // the TAGGING tiddler
			}
		}
	}
	if (!this.init && this.fn_clickAfter) // custom function hook to react to changes in checkbox state
		{ try { eval(this.fn_clickAfter) } catch(e) { displayMessage("Checkbox onClickAfter error: "+e.toString()); } }
	// refresh containing tiddler (but not during initial rendering, or we get an infinite loop!) (and not when editing container)
	if (!this.init && this.refresh.container && this.container!=this.tiddler)
		if (!story.isDirty(this.container)) story.refreshTiddler(this.container,null,true); // the tiddler CONTAINING the checkbox
	if (this.srctid && !wasDirty && store.isDirty()) {
		story.refreshTiddler(store.getTiddler(this.srctid).title,null,true);
	}
	return true;
};
//}}}
//{{{
var formatter = null;
for (var i = 0; i < config.formatters.length; i++) {
	if (config.formatters[i].name == "checkbox") {
		formatter = config.formatters[i];
		break;
	}
}
if (formatter) {
	var code = formatter.handler.toString();
	var newCode = null;
	var startPosition = code.indexOf("tid.getAttribute");
	if (startPosition > -1) {
		startPosition = code.indexOf(';', startPosition);
		if (startPosition > -1) {
			newCode = code.substring(0, startPosition + 1) + " tid = (w.tiddler && w.tiddler.title) ? w.tiddler.title : tid;" + code.substring(startPosition + 1);
			code = newCode;
		}
	}
	if (newCode != null) {
		eval("formatter.handler = function handler" + newCode.substring(newCode.indexOf("(")));
	}
}
//}}}
<<EncryptionDecryptAll "decrypt all" "Decrypt all Tiddlers.">> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <<saveChanges>>

-------

<html>title (regular expression): <input type="text" id="tiddler_cleanup_title" /></html>
<html>from: <input type="text" id="tiddler_cleanup_date_from" /> until: <input type="text" id="tiddler_cleanup_date_until" /></html>
<html>tags: <input type="text" id="tiddler_cleanup_tags" /></html>
<html>characters - more than: <input type="text" id="tiddler_cleanup_characters_from" /> less than: <input type="text" id="tiddler_cleanup_characters_until" /></html>
<script>

var date = new Date();

$("tiddler_cleanup_title").value = (window.cached_tiddler_cleanup_title !== undefined) ? window.cached_tiddler_cleanup_title : "^(?!" + date.getFullYear() + "-)";

var formatDateString = function(date) {
	return date.getFullYear() + "-"
			+ (date.getMonth() < 9 ? "0" : "") + (date.getMonth() + 1) + "-"
			+ (date.getDate() < 10 ? "0" : "") + date.getDate() + " "
			+ (date.getHours() < 10 ? "0" : "") + date.getHours() + ":"
			+ (date.getMinutes() < 10 ? "0" : "") + date.getMinutes();
}
if ($("tiddler_cleanup_date_until").value == "") {
	$("tiddler_cleanup_date_until").value = (window.cached_tiddler_cleanup_date_until !== undefined) ? window.cached_tiddler_cleanup_date_until : formatDateString(date);
}

date.setFullYear(date.getFullYear() - 1);
if ($("tiddler_cleanup_date_from").value == "") {
	$("tiddler_cleanup_date_from").value = (window.cached_tiddler_cleanup_date_from !== undefined) ? window.cached_tiddler_cleanup_date_from :  formatDateString(date);
}

$("tiddler_cleanup_tags").value = (window.cached_tiddler_cleanup_tags !== undefined) ? window.cached_tiddler_cleanup_tags : "NOT systemConfig AND NOT AddressBook";

$("tiddler_cleanup_characters_from").value = (window.cached_tiddler_cleanup_characters_from !== undefined) ? window.cached_tiddler_cleanup_characters_from : "";
$("tiddler_cleanup_characters_until").value = (window.cached_tiddler_cleanup_characters_until !== undefined) ? window.cached_tiddler_cleanup_characters_until : "";

</script>
<script label="Find">

window.cached_tiddler_cleanup_title = $("tiddler_cleanup_title").value;
window.cached_tiddler_cleanup_date_from = $("tiddler_cleanup_date_from").value;
window.cached_tiddler_cleanup_date_until = $("tiddler_cleanup_date_until").value;
window.cached_tiddler_cleanup_tags = $("tiddler_cleanup_tags").value;
window.cached_tiddler_cleanup_characters_from = $("tiddler_cleanup_characters_from").value;
window.cached_tiddler_cleanup_characters_until = $("tiddler_cleanup_characters_until").value;

if (window.tiddler_cleanup_selection_find_node && jQuery(window.tiddler_cleanup_selection_find_node).next("span")) {
	jQuery(window.tiddler_cleanup_selection_find_node).next("span").remove();
}
if (window.tiddler_cleanup_selection_find_node) {
	delete window.tiddler_cleanup_selection_find_node;
}

var parseDateString = function(date) {
	var dateMatch = date.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/);
	// return (dateMatch) ? Number("" + dateMatch[1] + "" + dateMatch[2] + "" + dateMatch[3] + "" + dateMatch[4] + "" + dateMatch[5] + "00") : null;
	return (dateMatch) ? new Date(dateMatch[1], dateMatch[2] - 1, dateMatch[3], dateMatch[4], dateMatch[5], 0, 0).getTime() : null;
}

var titleRegExp = ($("tiddler_cleanup_title").value == "") ? null : new RegExp($("tiddler_cleanup_title").value);
var dateFrom = parseDateString ($("tiddler_cleanup_date_from").value);
var dateUntil = parseDateString ($("tiddler_cleanup_date_until").value);
var charactersFrom = (isNaN($("tiddler_cleanup_characters_from").value) || ($("tiddler_cleanup_characters_from").value == "")) ? null : Number($("tiddler_cleanup_characters_from").value);
var charactersUntil = (isNaN($("tiddler_cleanup_characters_until").value) || ($("tiddler_cleanup_characters_until").value == "")) ? null : Number($("tiddler_cleanup_characters_until").value);

var tiddlers = [];
window.tiddler_cleanup_selection = [];

var list = ($("tiddler_cleanup_tags").value == "") ? store.getTiddlers() : store.getMatchingTiddlers($("tiddler_cleanup_tags").value);
for (var i = 0; i < list.length; i++) {
	var tiddlerDate = list[i].modified ? Number(list[i].modified) : Number(list[i].created);
	if ((!dateFrom || (tiddlerDate >= dateFrom)) && (!dateUntil || (tiddlerDate < dateUntil)) && (!titleRegExp || titleRegExp.test(list[i].title))) {
		var tiddlerCharacters = list[i].text ? list[i].text.length : 0;
		if (((charactersFrom === null) || (tiddlerCharacters >= charactersFrom)) && ((charactersUntil === null) || (tiddlerCharacters < charactersUntil))) {
			if ((list[i].title != "Trash") && (!list[i].fields['server.host'] || (list[i].fields['server.host'] == "")) && (!list[i].tags || (
					(list[i].tags.indexOf(config.macros.emptyTrash.tag) == -1)
			))) {
				tiddlers.push(list[i]);
				window.tiddler_cleanup_selection.pushUnique(escape(list[i].title));
			}
		}
	}
}

window.tiddler_cleanup_selection_find_node = place;

var out = "\n";
for (var i = 0; i < tiddlers.length; i++) {
	var tags = "";
	var tagList = tiddlers[i].tags;
	if (tagList && tagList.length) {
		tags = " @@''tags:'' ";
		for (var j = 0; j < tagList.length; j++) {
			tags = tags + ((j == 0) ? " " : ", ");
			if (store.getTiddler(tagList[j])) {
				tags = tags + "[[" +  tagList[j] + "]]";
			} else {
				tags = tags + tagList[j];
			}
		}
		tags = tags + "@@";
	}
	
	var referringTiddlers = "";
	var referringTiddlerList = store.getReferringTiddlers(tiddlers[i].title);
	if (referringTiddlerList.length > 0) {
		referringTiddlers = " @@background-color:#c0c0c0;''referred by:''";
		for (var j = 0; j < referringTiddlerList.length; j++) {
			referringTiddlers = referringTiddlers + ((j == 0) ? "" : ",") + " [[" +  referringTiddlerList[j].title + "]]";
		}
		referringTiddlers = referringTiddlers + "@@";
	}
	
	if (!store.tiddlersUpdated) {
		store.updateTiddlers();
	}
	var linkedTiddlers = "";
	var tiddlersLinks = tiddlers[i].links;
	if (tiddlersLinks && tiddlersLinks.length > 0) {
		linkedTiddlers = " @@background-color:#babb1e;''links:''";
		for (var j = 0; j < tiddlersLinks.length; j++) {
			linkedTiddlers = linkedTiddlers + ((j == 0) ? "" : ",") + " [[" +  tiddlersLinks[j] + "]]";
		}
		linkedTiddlers = linkedTiddlers + "@@";
	}
	
	out = out +"\n<html><input type='checkbox' checked='checked' class='tiddler_cleanup_checkbox' onclick='this.checked ? window.tiddler_cleanup_selection.pushUnique(\"" +  escape(tiddlers[i].title) + "\") : window.tiddler_cleanup_selection.remove(\"" + escape(tiddlers[i].title) + "\")' /></html> [[" +  tiddlers[i].title + "]]" + tags + " @@background-color:#c0ffee;''characters:'' " + (tiddlers[i].text ? tiddlers[i].text.length : 0) + "@@" + referringTiddlers + linkedTiddlers;
}

return out;
</script>

-------

<script label="Find plugin info">

window.cached_tiddler_cleanup_date_from = $("tiddler_cleanup_date_from").value;
window.cached_tiddler_cleanup_date_until = $("tiddler_cleanup_date_until").value;
window.cached_tiddler_cleanup_tags = $("tiddler_cleanup_tags").value;
window.cached_tiddler_cleanup_characters_from = $("tiddler_cleanup_characters_from").value;
window.cached_tiddler_cleanup_characters_until = $("tiddler_cleanup_characters_until").value;

if (window.tiddler_cleanup_selection_find_node && jQuery(window.tiddler_cleanup_selection_find_node).next("span")) {
	jQuery(window.tiddler_cleanup_selection_find_node).next("span").remove();
}
if (window.tiddler_cleanup_selection_find_node) {
	delete window.tiddler_cleanup_selection_find_node;
}

var parseDateString = function(date) {
	var dateMatch = date.match(/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})$/);
	// return (dateMatch) ? Number("" + dateMatch[1] + "" + dateMatch[2] + "" + dateMatch[3] + "" + dateMatch[4] + "" + dateMatch[5] + "00") : null;
	return (dateMatch) ? new Date(dateMatch[1], dateMatch[2] - 1, dateMatch[3], dateMatch[4], dateMatch[5], 0, 0).getTime() : null;
}

var dateFrom = parseDateString ($("tiddler_cleanup_date_from").value);
var dateUntil = parseDateString ($("tiddler_cleanup_date_until").value);

var tiddlers = [];
window.tiddler_cleanup_selection = [];

var list = store.getTiddlers();
for (var i = 0; i < list.length; i++) {
	if ((list[i].title != "Trash") && (list[i].fields['server.host'] && (list[i].fields['server.host'].indexOf("-ext.html") == list[i].fields['server.host'].length - "-ext.html".length)) && (!list[i].tags || (
			(list[i].tags.indexOf(config.macros.emptyTrash.tag) == -1)
	))) {
		tiddlers.push(list[i]);
		window.tiddler_cleanup_selection.pushUnique(escape(list[i].title));
	}
}

window.tiddler_cleanup_selection_find_node = place; //.parentNode;

// var out = "\n<html><div id='tiddler_cleanup_results'></html>";
var out = "\n";
for (var i = 0; i < tiddlers.length; i++) {
	out = out +"\n<html><input type='checkbox' checked='checked' class='tiddler_cleanup_checkbox' onclick='this.checked ? window.tiddler_cleanup_selection.pushUnique(\"" +  escape(tiddlers[i].title) + "\") : window.tiddler_cleanup_selection.remove(\"" + escape(tiddlers[i].title) + "\")' /></html> [[" +  tiddlers[i].title + "]]" + (tiddlers[i].getTags() ? " @@''tags:'' " + tiddlers[i].getTags() + "@@" : "") + " @@background-color:#c0ffee;''characters:'' " + (tiddlers[i].text ? tiddlers[i].text.length : 0) + "@@";
}
// out = out + "<html></div></html>";

return out;
</script>

-------

<script label="Select all">
jQuery(".tiddler_cleanup_checkbox").each(function() {
	this.checked = true;
	this.onclick();
});
</script>

<script label="Select none">
jQuery(".tiddler_cleanup_checkbox").each(function() {
	this.checked = false;
	this.onclick();
});
</script>

<script label="Send selected to trash">
if (window.tiddler_cleanup_selection) {
	for (var i = 0; i < window.tiddler_cleanup_selection.length - 1; i++) {
		var title = unescape(window.tiddler_cleanup_selection[i]);
		if (store.tiddlerExists(title)) {
			var tiddler = store.getTiddler(title);
			if (tiddler.tags.indexOf(config.macros.emptyTrash.tag) == -1) tiddler.tags[tiddler.tags.length] = config.macros.emptyTrash.tag;
			if (tiddler.tags.indexOf('excludeLists') == -1) tiddler.tags[tiddler.tags.length] = 'excludeLists';
			if (tiddler.tags.indexOf('excludeMissing') == -1) tiddler.tags[tiddler.tags.length] = 'excludeMissing';
			if (tiddler.tags.indexOf('excludeSearch') == -1) tiddler.tags[tiddler.tags.length] = 'excludeSearch';
			if (tiddler.isTagged('systemConfig')) store.setTiddlerTag(title, 1, 'systemConfigDisable');
			story.closeTiddler(title, true);
		}
	}
	if (window.tiddler_cleanup_selection.length > 0) {
		config.commands.deleteTiddler.sendToTrash(unescape(window.tiddler_cleanup_selection[window.tiddler_cleanup_selection.length - 1]));
	}
	delete window.tiddler_cleanup_selection;
	if (window.tiddler_cleanup_selection_find_node && jQuery(window.tiddler_cleanup_selection_find_node).next("span")) {
		jQuery(window.tiddler_cleanup_selection_find_node).next("span").remove();
	}
	if (window.tiddler_cleanup_selection_find_node) {
		delete window.tiddler_cleanup_selection_find_node;
	}
}
</script>

[[Trash]]
// // http://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
//{{{
if (!config.macros.collator) {
/*
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
 
       http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
var defaultDiacriticsRemovalMap = [
	{'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'},
	{'base':'AA','letters':'\uA732'},
	{'base':'AE','letters':'\u00C6\u01FC\u01E2'},
	{'base':'AO','letters':'\uA734'},
	{'base':'AU','letters':'\uA736'},
	{'base':'AV','letters':'\uA738\uA73A'},
	{'base':'AY','letters':'\uA73C'},
	{'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'},
	{'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'},
	{'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'},
	{'base':'DZ','letters':'\u01F1\u01C4'},
	{'base':'Dz','letters':'\u01F2\u01C5'},
	{'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'},
	{'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'},
	{'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'},
	{'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'},
	{'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'},
	{'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'},
	{'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'},
	{'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'},
	{'base':'LJ','letters':'\u01C7'},
	{'base':'Lj','letters':'\u01C8'},
	{'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'},
	{'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'},
	{'base':'NJ','letters':'\u01CA'},
	{'base':'Nj','letters':'\u01CB'},
	{'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'},
	{'base':'OI','letters':'\u01A2'},
	{'base':'OO','letters':'\uA74E'},
	{'base':'OU','letters':'\u0222'},
	{'base':'OE','letters':'\u008C\u0152'},
	{'base':'oe','letters':'\u009C\u0153'},
	{'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'},
	{'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'},
	{'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'},
	{'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'},
	{'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'},
	{'base':'TZ','letters':'\uA728'},
	{'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'},
	{'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'},
	{'base':'VY','letters':'\uA760'},
	{'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'},
	{'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'},
	{'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'},
	{'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'},
	{'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'},
	{'base':'aa','letters':'\uA733'},
	{'base':'ae','letters':'\u00E6\u01FD\u01E3'},
	{'base':'ao','letters':'\uA735'},
	{'base':'au','letters':'\uA737'},
	{'base':'av','letters':'\uA739\uA73B'},
	{'base':'ay','letters':'\uA73D'},
	{'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'},
	{'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'},
	{'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'},
	{'base':'dz','letters':'\u01F3\u01C6'},
	{'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'},
	{'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'},
	{'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'},
	{'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'},
	{'base':'hv','letters':'\u0195'},
	{'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'},
	{'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'},
	{'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'},
	{'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'},
	{'base':'lj','letters':'\u01C9'},
	{'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'},
	{'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'},
	{'base':'nj','letters':'\u01CC'},
	{'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'},
	{'base':'oi','letters':'\u01A3'},
	{'base':'ou','letters':'\u0223'},
	{'base':'oo','letters':'\uA74F'},
	{'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'},
	{'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'},
	{'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'},
	{'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'},
	{'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'},
	{'base':'tz','letters':'\uA729'},
	{'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'},
	{'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'},
	{'base':'vy','letters':'\uA761'},
	{'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'},
	{'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'},
	{'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'},
	{'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'}
];
 
var diacriticsMap = {};
for (var i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
	var letters = defaultDiacriticsRemovalMap[i].letters;
	for (var j = 0; j < letters.length; j++){
		diacriticsMap[letters[j]] = defaultDiacriticsRemovalMap[i].base;
	}
}
 
// "what?" version ... http://jsperf.com/diacritics/12
function removeDiacritics(str) {
	return !str ? str : str.replace(/[^\u0000-\u007E]/g, function (a) { 
		return diacriticsMap[a] || a; 
	});
}

function toCollationKey(source) {
	return !source ? source : removeDiacritics(source).toLowerCase();
}

function compare(a, b) {
	if (a == b) {
		return 0;
	}
	return toCollationKey(a) < toCollationKey(b) ? -1 : 1;
}

config.macros.collator = {
	removeDiacritics: removeDiacritics,
	toCollationKey: toCollationKey,
	compare: compare
};
}
//}}}
<<search>>

----

<<newTiddler>>

<<saveChanges>>

<<upload>>

----

ImportTiddlers

[[Sync]]

SideBarOptions

[[Indexes]]

/***
|Name|CompareTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#CompareTiddlersPlugin|
|Version|1.1.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|show color-coded differences between two selected tiddlers|
!!!!!Usage
<<<
Display a form that lets you select and compare any two tiddlers:
{{{
<<compareTiddlers>>
}}}
To filter the lists of tiddlers by tags, include an optional tagvalue (or tag expression) parameter:
{{{
<<compareTiddlers "tagValue">>
   OR
<<compareTiddlers "boolean tag expression">> (requires MatchTagsPlugin)
}}}
<<<
!!!!!Example
<<<
{{{<<compareTiddlers>>}}}
{{smallform small{<<compareTiddlers>>}}}
<<<
!!!!!Revisions
<<<
2009.07.25 [1.1.0] added optional tag filter param
2007.10.15 [1.0.0] converted from inline script to true plugin
2006.12.27 [0.0.0] inline script.  {{{diff()}}} and {{{diffString()}}} functions written by Bradley Meck.
<<<
!!!!!Code
***/
//{{{
version.extensions.CompareTiddlersPlugin= {major: 1, minor: 1, revision: 0, date: new Date(2009,7,25)};
//}}}
//{{{
config.shadowTiddlers.CompareTiddlers='<<compareTiddlers>>';
//}}}
/***
//{{{
!html
<form><!--
--><input type=hidden name=filter value=''><!--
--><select name=list1 size=1 style='width:30%'
	onchange='config.macros.compareTiddlers.pick(this,this.form.view1,this.form.edit1,this.form.text1)'></select><!--
--><input type=button name=view1 style='width:10%' value='view' disabled
	onclick='if (this.form.list1.value.length)
		story.displayTiddler(story.findContainingTiddler(this),this.form.list1.value)'><!--
--><input type=button name=edit1 style='width:10%' value='edit' disabled
	onclick='if (this.form.list1.value.length)
		story.displayTiddler(story.findContainingTiddler(this),this.form.list1.value,DEFAULT_EDIT_TEMPLATE)'><!--
--><select name=list2 size=1 style='width:30%'
	onchange='config.macros.compareTiddlers.pick(this,this.form.view2,this.form.edit2,this.form.text2)'></select><!--
--><input type=button name=view2 style='width:10%' value='view' disabled
	onclick='if (this.form.list2.value.length)
		story.displayTiddler(story.findContainingTiddler(this),this.form.list2.value)'><!--
--><input type=button name=edit2 style='width:10%' value='edit' disabled
	onclick='if (this.form.list2.value.length)
		story.displayTiddler(story.findContainingTiddler(this),this.form.list2.value,DEFAULT_EDIT_TEMPLATE)'><br><!--
--><nobr><!--
--><textarea name=text1 style='width:49.5%;display:none' rows='10' readonly></textarea><!--
--><textarea name=text2 style='width:49.5%;display:none' rows='10' readonly></textarea><!--
--></nobr><!--
--><div style='float:left'><!--
-->Additions are shown in <span style='color:green'>GREEN</span>, <!--
-->deletions are shown in <span style='color:red'>RED</span><!--
--></div><!--
--><div style='text-align:right'><!--
--><input type=button name=compare style='width:10%' value='compare' disabled
	onclick='config.macros.compareTiddlers.compare(this.form,this.form.nextSibling)'><!--
--><input type=button name=done style='width:10%' value='done' disabled
	onclick='config.macros.compareTiddlers.reset(this.form,this.form.nextSibling)'><!--
--></div><!--
--></form><div class='compareTiddlersResults'>contents to be replaced by results of comparison</div>
!end
//}}}
***/
//{{{
config.macros.compareTiddlers= {
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		setStylesheet(this.css,'CompareTiddlersStyles');
		var out=createTiddlyElement(place,'span');
		out.innerHTML=store.getTiddlerText('CompareTiddlersPlugin##html');
		var form=out.getElementsByTagName('form')[0];
		var target=form.nextSibling;
		this.reset(form,target,params[0]);
	},
	css: '.compareTiddlersResults \
		{ display:none;clear:both;margin-top:1em;border:1px solid;-moz-border-radius:1em;-webkit-border-radius:1em;padding:1em;white-space:normal; }',
	reset: function(f,target,filter) {
		if (f.filter.value.length) var filter=f.filter.value;
		if (filter) var tids=store.filterTiddlers('[tag['+filter+']]')
		else var tids=store.getTiddlers('title','excludeLists');
		f.text1.style.display='none'; f.text1.value='';
		while (f.list1.options[0]) f.list1.options[0]=null; 
		f.list1.options[0]=new Option('select a tiddler...','',false,false);
		for (i=0; i<tids.length; i++)
			f.list1.options[f.list1.length]=new Option(tids[i].title,tids[i].title,false,false);
		f.text2.style.display='none'; f.text2.value='';
		while (f.list2.options[0]) f.list2.options[0]=null; 
		f.list2.options[0]=new Option('select a tiddler...','',false,false);
		for (i=0; i<tids.length; i++)
			f.list2.options[f.list2.length]=new Option(tids[i].title,tids[i].title,false,false);
		f.view1.disabled=f.view2.disabled=f.edit1.disabled=f.edit2.disabled=f.compare.disabled=f.done.disabled=true;
		f.filter.value=filter;
		target.style.display='none';
		removeChildren(target);
	},
	pick: function(list,view,edit,text) {
		var f=list.form;
		view.disabled=edit.disabled=f.done.disabled=!list.value.length;
		f.compare.disabled=!f.list1.value.length||!f.list2.value.length;
		if (!list.value.length) return;
		f.text1.style.display=f.text2.style.display='inline';
		text.value=store.getTiddlerText(list.value);
	},
	compare: function(f,target) {
		if (!f.list1.value.length) { f.list1.focus(); return alert('select a tiddler'); }
		var t1=store.getTiddlerText(f.list1.value); if (!t1) { displayMessage(f.list1.value+' not found');return false; }
		if (!f.list2.value.length) { f.list2.focus(); return alert('select a tiddler'); }
		var t2=store.getTiddlerText(f.list2.value); if (!t2) { displayMessage(f.list2.value+' not found');return false; }
		var out=this.diffString(t1,t2); if (!out || !out.length) out='no differences';
		removeChildren(target);
		target.innerHTML=out;
		target.style.display='block';
		f.done.disabled=false;
	},
//}}}
//{{{
	diffString: function( o, n ) {
		// This function was written by Bradley Meck
		// returns difference between old and new text, color-formatted additions and deletions
		if (o==n) return ""; // simple check, saves time if true
		var error = 5;
		var reg = new RegExp( "\\n|(?:.{0,"+error+"})", "g" );
		var oarr = o.match( reg ); // dices text into chunks
		var narr = n.match( reg );
		var out = this.diff(oarr,narr); // compare the word arrays
		var str = ""; // construct output
		for (i=0; i<out.length; i++) {
			switch (out[i].change) {
				case "ADDED":
					str+="<span style='color:green'>";
					str+=narr.slice(out[i].index,out[i].index+out[i].length).join("");
					str+="</span> ";
					break;
				case "DELETED":
					str+="<span style='color:red'>";
					str+=oarr.slice(out[i].index,out[i].index+out[i].length).join("");
					str+="</span> ";
					break;
				default:
					str+="<span>";
					str+=oarr.slice(out[i].index,out[i].index+out[i].length).join("");
					str+="</span> ";
					break;
			}	
		}
		return str;
	},
	diff: function( oldArray, newArray ) {
		// This function was written by Bradley Meck
		// finds the differences between one set of objects and another.
		// The objects do not need to be Strings.  It outputs an array of objects with the properties value and change.
		// This function is pretty hefty but appears to be rather light for a diff and tops out at O(N^2) for absolute worst cast scenario.
		var newElementHash = { };
		for( var i = 0; i < newArray.length; i++ ) {
			if( ! newElementHash [ newArray [ i ] ] ) {
				newElementHash [ newArray [ i ] ] = [ ];
			}
			newElementHash [ newArray [ i ] ].push( i );
		}
		var substringTable = [ ];
		for( var i = 0; i < oldArray.length; i++ ) {
			if(newElementHash [ oldArray [ i ] ] ) {
				var locations = newElementHash [ oldArray [ i ] ] ;
				for( var j = 0; j < locations.length; j++){
					var length = 1;
					while( i + length < oldArray.length && locations [ j ] + length < newArray.length
						&& oldArray [ i + length ] == newArray [ locations [ j ] + length ] ){
						length++;
					}
					substringTable.push( {
						oldArrayIndex : i,
						newArrayIndex : locations [ j ],
						matchLength : length
					} );
				}
			}
		}
		substringTable.sort( function( a, b ) {
			if ( a.matchLength > b.matchLength /* a is less than b by some ordering criterion */ ) {
				return -1;
			}
			if ( a.matchLength < b.matchLength /* a is greater than b by the ordering criterion */ ) {
				return 1;
			}
			// a must be equal to b
			return 0
		} );
		//displayMessage( substringTable.toSource( ) );
		for( var i = 0; i < substringTable.length; i++) {
			for( var j = 0; j < i; j++) {
				var oldDelta = substringTable [ i ].oldArrayIndex + substringTable [ i ].matchLength - 1 - substringTable [ j ].oldArrayIndex;
				var newDelta = substringTable [ i ].newArrayIndex + substringTable [ i ].matchLength - 1 - substringTable [ j ].newArrayIndex;
				//displayMessage( "oldDelta ::: " + oldDelta );
				//displayMessage( "newDelta ::: " + newDelta );
				//displayMessage( "matchLength ::: " + substringTable [ j ].matchLength );
				if( ( oldDelta >= 0 && oldDelta <= substringTable [ j ].matchLength )
				|| ( newDelta >= 0 && newDelta <= substringTable [ j ].matchLength )
				|| ( oldDelta < 0 && newDelta > 0 )
				|| ( oldDelta > 0 && newDelta < 0 ) ) {
					substringTable.splice( i, 1 );
					i--;
					break;
				}
			}
		}
		//displayMessage( substringTable.toSource(  ) );
		substringTable.sort( function( a, b ) {
			if ( a.oldArrayIndex < b.oldArrayIndex /* a is less than b by some ordering criterion */ ) {
				return -1;
			}
			if ( a.oldArrayIndex > b.oldArrayIndex /* a is greater than b by the ordering criterion */ ) {
				return 1;
			}
			// a must be equal to b
			return 0
		} );
		//displayMessage( substringTable.toSource( ) );
		var oldArrayIndex = 0;
		var newArrayIndex = 0;
		var results = [ ];
		for( var i = 0; i < substringTable.length; i++ ) {
			if( oldArrayIndex != substringTable [ i ].oldArrayIndex ) {
				results.push( {
					change : "DELETED",
					length : substringTable [ i ].oldArrayIndex - oldArrayIndex,
					index : oldArrayIndex
				} );
			}
			if( newArrayIndex != substringTable [ i ].newArrayIndex ) {
				results.push( {
					change : "ADDED",
					length : substringTable [ i ].newArrayIndex - newArrayIndex,
					index : newArrayIndex
				} );
			}
			results.push( {
				change : "STAYED",
				length : substringTable [ i ].matchLength,
				index : substringTable [ i ].oldArrayIndex
			} );
			oldArrayIndex = substringTable [ i ].oldArrayIndex + substringTable [ i ].matchLength;
			newArrayIndex = substringTable [ i ].newArrayIndex + substringTable [ i ].matchLength;
		}
		if( oldArrayIndex != oldArray.length ) {
			results.push( {
				change : "DELETED",
				length : oldArray.length - oldArrayIndex,
				index : oldArrayIndex
			} );
		}
		if( newArrayIndex != newArray.length ) {
			results.push( {
				change : "ADDED",
				length : newArray.length - newArrayIndex,
				index : newArrayIndex
			} );
		}
		return results;
	}
}
//}}}
<<twab>> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <<twab "tags:AddressBook Encrypt(contacts)" new encrypted contact>>

<<list filter "[tag[AddressBook AND NOT project AND NOT Trash]]">>
<html>
<style>
.rolodex table {
border: 0px solid;
}

.rolodex tr, .rolodex td {
border: 0px solid;
}

</style>
</html>
<<tabs addressBookTabs
Overview "Email Addresses, Homepage URL, etc." TwabTabParts/tab1form
Home "Home Address/Phone, etc." TwabTabParts/tab2form
Business "Business Address/Phone, etc." TwabTabParts/tab3form
Misc "Notes, Birthday, SSN, etc." TwabTabParts/tab4form
>>
//{{{
config.macros.copyline = {
	handler: function (place, macroName, params, wikifier, paramString, tiddler) {
		if (wikifier && tiddler) {
			params = paramString.parseParams("anon", null, true, false, false);
			
			var label = getParam(params, "label", "copy");
			var prompt = getParam(params, "prompt", "Copy tiddler lines");
			var accessKey = getParam(params, "accessKey", null);
			
			var isNumber = function (n) {
				return !isNaN(parseFloat(n)) && isFinite(n);
			};
			
			var offset = getParam(params, "offset", -1);
			offset = isNumber(offset) ? offset | 0 : -1;
			
			var count = getParam(params, "count", 1);
			count = isNumber(count) ? count | 0 : 1;
			
			if ((offset != 0) && (count > 0)) {
				var macroPosition = (offset < 0) ? wikifier.matchStart : wikifier.nextMatch;
				
				var copyText = function () {
					var textStart = -1;
					var textEnd = -1;
					
					if (offset < 0) {
						textEnd = macroPosition + 1;
						for (var i = 0; (i > offset) && (textEnd > -1); i--) {
							textEnd = tiddler.text.lastIndexOf("\n", textEnd - 1);
						}
						if (textEnd > -1) {
							textStart = tiddler.text.lastIndexOf("\n", textEnd - 1);
							textStart = textStart > -1 ? textStart + 1 : 0;
						}
					} else {
						offset--;
						textStart = macroPosition;
						for (var i = 0; (i < offset) && (textStart > -1); i++) {
							textStart = tiddler.text.indexOf("\n", textStart + 1);
						}
						if (textStart > -1) {
							textEnd = tiddler.text.indexOf("\n", textStart + 1);
							textEnd = textEnd > -1 ? textEnd : tiddler.text.length;
						}
					}
					
					if ((textStart > -1) && (textEnd > -1)) {
						if (tiddler.text.charAt(textEnd) == "\n") {
							for (var i = 0; i < count - 1; i++) {
								textEnd = tiddler.text.indexOf("\n", textEnd + 1);
								if (textEnd < 0) {
									textEnd = tiddler.text.length;
									break;
								}
							}
						}
						
						var copiedText = tiddler.text.substring(textStart, textEnd);
						if ((offset < 0) && (copiedText.length > 0) && (copiedText.charAt(copiedText.length - 1) != "\n"))  {
							copiedText = copiedText + "\n";
						}
						
						tiddler.text = tiddler.text.substring(0, macroPosition) + copiedText + tiddler.text.substring(macroPosition);
						store.setDirty(true);
						story.refreshTiddler(tiddler.title, null, true);
					}
				};
				
				createTiddlyButton(place, label, prompt, copyText, null, null, accessKey);
			}
		}
	}	
}
//}}}
/***
|Name|CopyTiddlerPlugin|
|Source|http://www.TiddlyTools.com/#CopyTiddlerPlugin|
|Version|3.2.6|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.3|
|Type|plugin|
|Description|Quickly create a copy of any existing tiddler|
!!!Usage
<<<
The plugin automatically updates the default (shadow) ToolbarCommands definitions to insert the ''copyTiddler'' command, which will appear as ''copy'' when a tiddler is rendered.  If you are already using customized toolbar definitions, you will need to manually add the ''copyTiddler'' toolbar command to your existing ToolbarCommands tiddler, e.g.:
{{{
|EditToolbar|... copyTiddler ... |
}}}
When the ''copy'' command is selected, a new tiddler is created containing an exact copy of the current text/tags/fields, using a title of "{{{TiddlerName (n)}}}", where ''(n)'' is the next available number (starting with 1, of course).  If you copy while //editing// a tiddler, the current values displayed in the editor are used (including any changes you may have already made to those values), and the new tiddler is immediately opened for editing.

The plugin also provides a macro that allows you to embed a ''copy'' command directly in specific tiddler content:
{{{
<<copyTiddler TidderName label:"..." prompt:"...">>
}}}
where
* ''TiddlerName'' (optional)<br>specifies the //source// tiddler to be copied.  If omitted, the current containing tiddler (if any) will be copied.
* ''label:"..."'' (optional)<br>specifies text to use for the embedded link (default="copy TiddlerName")
* ''prompt:"..."'' (optional)<br>specifies mouseover 'tooltip' help text for link
//Note: to use non-default label/prompt values with the current containing tiddler, use "" for the TiddlerName//
<<<
!!!Configuration
<<<
<<option chkCopyTiddlerDate>> use date/time from existing tiddler (otherwise, use current date/time)
{{{<<option chkCopyTiddlerDate>>}}}
<<<
!!!Revisions
<<<
2010.11.30 3.2.6 use story.getTiddler()
2009.06.08 3.2.5 added option to use timestamp from source tiddler
2009.03.09 3.2.4 fixed IE-specific syntax error
2009.03.02 3.2.3 refactored code (again) to restore use of config.commands.copyTiddler.* custom settings
2009.02.13 3.2.2 in click(), fix calls to displayTiddler() to use current tiddlerElem and use getTiddlerText() to permit copying of shadow tiddler content
2009.01.30 3.2.1 fixed handling for copying field values when in edit mode
2009.01.23 3.2.0 refactored code and added {{{<<copyTiddler TiddlerName>>}}} macro
2008.12.18 3.1.4 corrected code for finding next (n) value when 'sparse' handling is in effect
2008.11.14 3.1.3 added optional 'sparse' setting (avoids 'filling in' missing numbers that may have been previously deleted)
2008.11.14 3.1.2 added optional 'zeroPad' setting
2008.11.14 3.1.1 moved hard-coded '(n)' regex into 'suffixPattern' object property so it can be customized
2008.09.26 3.1.0 changed new title generation to use '(n)' suffix instead of 'Copy of' prefix
2008.05.20 3.0.3 in handler, when copying from VIEW mode, create duplicate array from existing tags array before saving new tiddler.
2007.12.19 3.0.2 in handler, when copying from VIEW mode, duplicate custom fields before saving new tiddler.
2007.09.26 3.0.1 in handler, use findContainingTiddler(src) to get tiddlerElem (and title).  Allows 'copy' command to find correct tiddler when transcluded using {{{<<tiddler>>}}} macro or enhanced toolbar inclusion (see [[CoreTweaks]])
2007.06.28 3.0.0 complete re-write to handle custom fields and alternative view/edit templates
2007.05.17 2.1.2 use store.getTiddlerText() to retrieve tiddler content, so that SHADOW tiddlers can be copied correctly when in VIEW mode
2007.04.01 2.1.1 in copyTiddler.handler(), fix check for editor fields by ensuring that found field actually has edit=='text' attribute
2007.02.05 2.1.0 in copyTiddler.handler(), if editor fields (textfield and/or tagsfield) can't be found (i.e., tiddler is in VIEW mode, not EDIT mode), then get text/tags values from stored tiddler instead of active editor fields.  Allows use of COPY toolbar directly from VIEW mode
2006.12.12 2.0.0 completely rewritten so plugin just creates a new tiddler EDITOR with a copy of the current tiddler EDITOR contents, instead of creating the new tiddler in the STORE by copying the current tiddler values from the STORE.
2005.xx.xx 1.0.0 original version by Tim Morgan
<<<
!!!Code
***/
//{{{
version.extensions.CopyTiddlerPlugin= {major: 3, minor: 2, revision: 6, date: new Date(2010,11,30)};

// automatically tweak shadow EditTemplate to add 'copyTiddler' toolbar command (following 'cancelTiddler')
config.shadowTiddlers.ToolbarCommands=config.shadowTiddlers.ToolbarCommands.replace(/cancelTiddler/,'cancelTiddler copyTiddler');

if (config.options.chkCopyTiddlerDate===undefined) config.options.chkCopyTiddlerDate=false;

config.commands.copyTiddler = {
	text: 'copy',
	hideReadOnly: true,
	tooltip: 'Make a copy of this tiddler',
	notitle: 'this tiddler',
	prefix: '',
	suffixText: ' (%0)',
	suffixPattern: / \(([0-9]+)\)$/,
	zeroPad: 0,
	sparse: false,
	handler: function(event,src,title)
		{ return config.commands.copyTiddler.click(src,event); },
	click: function(here,ev) {
		var tiddlerElem=story.findContainingTiddler(here);
		var template=tiddlerElem?tiddlerElem.getAttribute('template'):null;
		var title=here.getAttribute('from');
		if (!title || !title.length) {
			if (!tiddlerElem) return false;
			else title=tiddlerElem.getAttribute('tiddler');
		}
		var root=title.replace(this.suffixPattern,''); // title without suffix
		// find last matching title
		var last=title;
		if (this.sparse) { // don't fill-in holes... really find LAST matching title
			var tids=store.getTiddlers('title','excludeLists');
			for (var t=0; t<tids.length; t++) if (tids[t].title.startsWith(root)) last=tids[t].title;
		}
		// get next number (increment from last matching title)
		var n=1; var match=this.suffixPattern.exec(last); if (match) n=parseInt(match[1])+1;
		var newTitle=this.prefix+root+this.suffixText.format([String.zeroPad(n,this.zeroPad)]);
		// if not sparse mode, find the next hole to fill in...
		while (store.tiddlerExists(newTitle)||story.getTiddler(newTitle))
			{ n++; newTitle=this.prefix+root+this.suffixText.format([String.zeroPad(n,this.zeroPad)]); }
		if (!story.isDirty(title)) { // if tiddler is not being EDITED
			// duplicate stored tiddler (if any)
			var text=store.getTiddlerText(title,'');
			var who=config.options.txtUserName;
			var when=new Date();
			var newtags=[]; var newfields={};
			var tid=store.getTiddler(title); if (tid) {
				if (config.options.chkCopyTiddlerDate) var when=tid.modified;
				for (var t=0; t<tid.tags.length; t++) newtags.push(tid.tags[t]);
				store.forEachField(tid,function(t,f,v){newfields[f]=v;},true);
			}
	                store.saveTiddler(newTitle,newTitle,text,who,when,newtags,newfields,true);
			story.displayTiddler(tiddlerElem,newTitle,template);
		} else {
			story.displayTiddler(tiddlerElem,newTitle,template);
			var fields=config.commands.copyTiddler.gatherFields(tiddlerElem); // get current editor fields
			var newTiddlerElem=story.getTiddler(newTitle);
			for (var f=0; f<fields.length; f++) {  // set fields in new editor
				if (fields[f].name=='title') fields[f].value=newTitle; // rename title in new tiddler
				var fieldElem=config.commands.copyTiddler.findField(newTiddlerElem,fields[f].name);
				if (fieldElem) {
					if (fieldElem.getAttribute('type')=='checkbox')
						fieldElem.checked=fields[f].value;
					else 
						fieldElem.value=fields[f].value;
				}
			}
		}
		story.focusTiddler(newTitle,'title');
		return false;
	},
	findField: function(tiddlerElem,field) {
		var inputs=tiddlerElem.getElementsByTagName('input');
		for (var i=0; i<inputs.length; i++) {
			if (inputs[i].getAttribute('type')=='checkbox' && inputs[i].field == field) return inputs[i];
			if (inputs[i].getAttribute('type')=='text' && inputs[i].getAttribute('edit') == field) return inputs[i];
		}
		var tas=tiddlerElem.getElementsByTagName('textarea');
		for (var i=0; i<tas.length; i++) if (tas[i].getAttribute('edit') == field) return tas[i];
		var sels=tiddlerElem.getElementsByTagName('select');
		for (var i=0; i<sels.length; i++) if (sels[i].getAttribute('edit') == field) return sels[i];
		return null;
	},
	gatherFields: function(tiddlerElem) { // get field names and values from current tiddler editor
		var fields=[];
		// get checkboxes and edit fields
		var inputs=tiddlerElem.getElementsByTagName('input');
		for (var i=0; i<inputs.length; i++) {
			if (inputs[i].getAttribute('type')=='checkbox')
				if (inputs[i].field) fields.push({name:inputs[i].field,value:inputs[i].checked});
			if (inputs[i].getAttribute('type')=='text')
				if (inputs[i].getAttribute('edit')) fields.push({name:inputs[i].getAttribute('edit'),value:inputs[i].value});
		}
		// get textareas (multi-line edit fields)
		var tas=tiddlerElem.getElementsByTagName('textarea');
		for (var i=0; i<tas.length; i++)
			if (tas[i].getAttribute('edit')) fields.push({name:tas[i].getAttribute('edit'),value:tas[i].value});
		// get selection lists (droplist or listbox)
		var sels=tiddlerElem.getElementsByTagName('select');
		for (var i=0; i<sels.length; i++)
			if (sels[i].getAttribute('edit')) fields.push({name:sels[i].getAttribute('edit'),value:sels[i].value});
		return fields;
	}
};
//}}}
// // MACRO DEFINITION
//{{{
config.macros.copyTiddler = {
	label: 'copy',
	prompt: 'Make a copy of %0',
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		var title=params.shift();
		params=paramString.parseParams('anon',null,true,false,false);
		var label	=getParam(params,'label',this.label+(title?' '+title:''));
		var prompt	=getParam(params,'prompt',this.prompt).format([title||this.notitle]);
		var b=createTiddlyButton(place,label,prompt,
			function(ev){return config.commands.copyTiddler.click(this,ev)});
		b.setAttribute('from',title||'');
	}
};
//}}}
<script>

</script>
/***
|''Name:''|DataTiddlerPlugin|
|''Version:''|1.0.7 (2012-04-19)|
|''Summary:''|Enhance your tiddlers with structured data (such as strings, booleans, numbers, or even arrays and compound objects) that can be easily accessed and modified through named fields (in JavaScript code).|
|''Source:''|http://tiddlywiki.abego-software.de/#DataTiddlerPlugin|
|''Twitter:''|[[@abego|https://twitter.com/#!/abego]]|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''License:''|[[BSD open source license|http://www.abego-software.de/legal/apl-v10.html]]|
!Description
Enhance your tiddlers with structured data (such as strings, booleans, numbers, or even arrays and compound objects) that can be easily accessed and modified through named fields (in JavaScript code).

Such tiddler data can be used in various applications. E.g. you may create tables that collect data from various tiddlers. 

''//Example: "Table with all December Expenses"//''
{{{
<<forEachTiddler
    where
        'tiddler.tags.contains("expense") && tiddler.data("month") == "Dec"'
    write
        '"|[["+tiddler.title+"]]|"+tiddler.data("descr")+"| "+tiddler.data("amount")+"|\n"'
>>
}}}
//(This assumes that expenses are stored in tiddlers tagged with "expense".)//
<<forEachTiddler
    where
        'tiddler.tags.contains("expense") && tiddler.data("month") == "Dec"'
    write
        '"|[["+tiddler.title+"]]|"+tiddler.data("descr")+"| "+tiddler.data("amount")+"|\n"'
>>
For other examples see DataTiddlerExamples.




''Access and Modify Tiddler Data''

You can "attach" data to every tiddler by assigning a JavaScript value (such as a string, boolean, number, or even arrays and compound objects) to named fields. 

These values can be accessed and modified through the following Tiddler methods:
|!Method|!Example|!Description|
|{{{data(field)}}}|{{{t.data("age")}}}|Returns the value of the given data field of the tiddler. When no such field is defined or its value is undefined {{{undefined}}} is returned.|
|{{{data(field,defaultValue)}}}|{{{t.data("isVIP",false)}}}|Returns the value of the given data field of the tiddler. When no such field is defined or its value is undefined the defaultValue is returned.|
|{{{data()}}}|{{{t.data()}}}|Returns the data object of the tiddler, with a property for every field. The properties of the returned data object may only be read and not be modified. To modify the data use DataTiddler.setData(...) or the corresponding Tiddler method.|
|{{{setData(field,value)}}}|{{{t.setData("age",42)}}}|Sets the value of the given data field of the tiddler to the value. When the value is {{{undefined}}} the field is removed.|
|{{{setData(field,value,defaultValue)}}}|{{{t.setData("isVIP",flag,false)}}}|Sets the value of the given data field of the tiddler to the value. When the value is equal to the defaultValue no value is set (and the field is removed).|

Alternatively you may use the following functions to access and modify the data. In this case the tiddler argument is either a tiddler or the name of a tiddler.
|!Method|!Description|
|{{{DataTiddler.getData(tiddler,field)}}}|Returns the value of the given data field of the tiddler. When no such field is defined or its value is undefined {{{undefined}}} is returned.|
|{{{DataTiddler.getData(tiddler,field,defaultValue)}}}|Returns the value of the given data field of the tiddler. When no such field is defined or its value is undefined the defaultValue is returned.|
|{{{DataTiddler.getDataObject(tiddler)}}}|Returns the data object of the tiddler, with a property for every field. The properties of the returned data object may only be read and not be modified. To modify the data use DataTiddler.setData(...) or the corresponding Tiddler method.|
|{{{DataTiddler.setData(tiddler,field,value)}}}|Sets the value of the given data field of the tiddler to the value. When the value is {{{undefined}}} the field is removed.|
|{{{DataTiddler.setData(tiddler,field,value,defaultValue)}}}|Sets the value of the given data field of the tiddler to the value. When the value is equal to the defaultValue no value is set (and the field is removed).|
//(For details on the various functions see the detailed comments in the source code.)//


''Data Representation in a Tiddler''

The data of a tiddler is stored as plain text in the tiddler's content/text, inside a "data" section that is framed by a {{{<data>...</data>}}} block. Inside the data section the information is stored in the [[JSON format|http://www.crockford.com/JSON/index.html]]. 

//''Data Section Example:''//
{{{
<data>{"isVIP":true,"user":"John Brown","age":34}</data>
}}}

The data section is not displayed when viewing the tiddler (see also "The showData Macro").

Beside the data section a tiddler may have all kind of other content.

Typically you will not access the data section text directly but use the methods given above. Nevertheless you may retrieve the text of the data section's content through the {{{DataTiddler.getDataText(tiddler)}}} function.


''Saving Changes''

The "setData" methods respect the "ForceMinorUpdate" and "AutoSave" configuration values. I.e. when "ForceMinorUpdate" is true changing a value using setData will not affect the "modifier" and "modified" attributes. With "AutoSave" set to true every setData will directly save the changes after a setData.


''Notifications''

No notifications are sent when a tiddler's data value is changed through the "setData" methods. 

''Escape Data Section''
In case that you want to use the text {{{<data>}}} or {{{</data>}}} in a tiddler text you must prefix the text with a tilde ('~'). Otherwise it may be wrongly considered as the data section. The tiddler text {{{~<data>}}} is displayed as {{{<data>}}}.


''The showData Macro''

By default the data of a tiddler (that is stored in the {{{<data>...</data>}}} section of the tiddler) is not displayed. If you want to display this data you may used the {{{<<showData ...>>}}} macro:

''Syntax:'' 
|>|{{{<<}}}''showData '' [''JSON''] [//tiddlerName//] {{{>>}}}|
|''JSON''|By default the data is rendered as a table with a "Name" and "Value" column. When defining ''JSON'' the data is rendered in JSON format|
|//tiddlerName//|Defines the tiddler holding the data to be displayed. When no tiddler is given the tiddler containing the showData macro is used. When the tiddler name contains spaces you must quote the name (or use the {{{[[...]]}}} syntax.)|
|>|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|
!Revision history
* v1.0.7 (2012-04-19) 
** Bugfix: showData macro fails in TW 2.6.5 when tiddler title contains spaces (Thanks to MattShanks for reporting.)
** remove warnings
* v1.0.6 (2006-08-26) 
** Removed misleading comment
* v1.0.5 (2006-02-27) (Internal Release Only)
** Internal
*** Make "JSLint" conform
* v1.0.4 (2006-02-05)
** Bugfix: showData fails in TiddlyWiki 2.0
* v1.0.3 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.2 (2005-12-22)
** Enhancements:
*** Handle texts "<data>" or "</data>" more robust when used in a tiddler text or as a field value.
*** Improved (JSON) error messages.
** Bugs fixed: 
*** References are not updated when using the DataTiddler.
*** Changes to compound objects are not always saved.
*** "~</data>" is not rendered correctly (expected "</data>")
* v1.0.1 (2005-12-13)
** Features: 
*** The showData macro supports an optional "tiddlername" argument to specify the tiddler containing the data to be displayed
** Bugs fixed: 
*** A script immediately following a data section is deleted when the data is changed. (Thanks to GeoffS for reporting.)
* v1.0.0 (2005-12-12)
** initial version
!Source Code
***/
//{{{
//============================================================================
//============================================================================
//                           DataTiddlerPlugin
//============================================================================
//============================================================================

// Ensure that the DataTiddler Plugin is only installed once.
//
if (!version.extensions.DataTiddlerPlugin) {



version.extensions.DataTiddlerPlugin = {
    major: 1, minor: 0, revision: 7,
    date: new Date(2012, 3, 19), 
    type: 'plugin',
    source: "http://tiddlywiki.abego-software.de/#DataTiddlerPlugin"
};

// For backward compatibility with v1.2.x
//
if (!window.story) window.story=window; 
if (!TiddlyWiki.prototype.getTiddler) {
	TiddlyWiki.prototype.getTiddler = function(title) { 
		var t = this.tiddlers[title]; 
		return (t !== undefined && t instanceof Tiddler) ? t : null; 
	};
}

//============================================================================
// DataTiddler Class
//============================================================================

// ---------------------------------------------------------------------------
// Configurations and constants 
// ---------------------------------------------------------------------------

function DataTiddler() {
}

DataTiddler = {
    // Function to stringify a JavaScript value, producing the text for the data section content.
    // (Must match the implementation of DataTiddler.parse.)
    //
    stringify : null,
    

    // Function to parse the text for the data section content, producing a JavaScript value.
    // (Must match the implementation of DataTiddler.stringify.)
    //
    parse : null
};

// Ensure access for IE
window.DataTiddler = DataTiddler;

// ---------------------------------------------------------------------------
// Data Accessor and Mutator
// ---------------------------------------------------------------------------


// Returns the value of the given data field of the tiddler.
// When no such field is defined or its value is undefined
// the defaultValue is returned.
// 
// @param tiddler either a tiddler name or a tiddler
//
DataTiddler.getData = function(tiddler, field, defaultValue) {
    var t = (typeof tiddler == "string") ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw "Tiddler expected. Got "+tiddler;
    }

    return DataTiddler.getTiddlerDataValue(t, field, defaultValue);
};


// Sets the value of the given data field of the tiddler to
// the value. When the value is equal to the defaultValue
// no value is set (and the field is removed)
//
// Changing data of a tiddler will not trigger notifications.
// 
// @param tiddler either a tiddler name or a tiddler
//
DataTiddler.setData = function(tiddler, field, value, defaultValue) {
    var t = (typeof tiddler == "string") ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw "Tiddler expected. Got "+tiddler+ "("+t+")";
    }

    DataTiddler.setTiddlerDataValue(t, field, value, defaultValue);
};


// Returns the data object of the tiddler, with a property for every field.
//
// The properties of the returned data object may only be read and
// not be modified. To modify the data use DataTiddler.setData(...) 
// or the corresponding Tiddler method.
//
// If no data section is defined a new (empty) object is returned.
//
// @param tiddler either a tiddler name or a Tiddler
//
DataTiddler.getDataObject = function(tiddler) {
    var t = (typeof tiddler == "string") ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw "Tiddler expected. Got "+tiddler;
    }

    return DataTiddler.getTiddlerDataObject(t);
};

// Returns the text of the content of the data section of the tiddler.
//
// When no data section is defined for the tiddler null is returned 
//
// @param tiddler either a tiddler name or a Tiddler
// @return [may be null]
//
DataTiddler.getDataText = function(tiddler) {
    var t = (typeof tiddler == "string") ? store.getTiddler(tiddler) : tiddler;
    if (!(t instanceof Tiddler)) {
        throw "Tiddler expected. Got "+tiddler;
    }

    return DataTiddler.readDataSectionText(t);
};


// ---------------------------------------------------------------------------
// Internal helper methods (must not be used by code from outside this plugin)
// ---------------------------------------------------------------------------

// Internal.
//
// The original JSONError is not very user friendly, 
// especially it does not define a toString() method
// Therefore we extend it here.
//
DataTiddler.extendJSONError = function(ex) {
	if (ex.name == 'JSONError') {
        ex.toString = function() {
			return ex.name + ": "+ex.message+" ("+ex.text+")";
		};
	}
	return ex;
};

// Internal.
//
// @param t a Tiddler
//
DataTiddler.getTiddlerDataObject = function(t) {
    if (t.dataObject === undefined) {
        var data = DataTiddler.readData(t);
        t.dataObject = (data) ? data : {};
    }
    
    return t.dataObject;
};


// Internal.
//
// @param tiddler a Tiddler
//
DataTiddler.getTiddlerDataValue = function(tiddler, field, defaultValue) {
    var value = DataTiddler.getTiddlerDataObject(tiddler)[field];
    return (value === undefined) ? defaultValue : value;
};


// Internal.
//
// @param tiddler a Tiddler
//
DataTiddler.setTiddlerDataValue = function(tiddler, field, value, defaultValue) {
    var data = DataTiddler.getTiddlerDataObject(tiddler);
    var oldValue = data[field];
	
    if (value == defaultValue) {
        if (oldValue !== undefined) {
            delete data[field];
            DataTiddler.save(tiddler);
        }
        return;
    }
    data[field] = value;
    DataTiddler.save(tiddler);
};

// Internal.
//
// Reads the data section from the tiddler's content and returns its text
// (as a String).
//
// Returns null when no data is defined.
//
// @param tiddler a Tiddler
// @return [may be null]
//
DataTiddler.readDataSectionText = function(tiddler) {
    var matches = DataTiddler.getDataTiddlerMatches(tiddler);
    if (matches === null || !matches[2]) {
        return null;
    }
    return matches[2];
};

// Internal.
//
// Reads the data section from the tiddler's content and returns it
// (as an internalized object).
//
// Returns null when no data is defined.
//
// @param tiddler a Tiddler
// @return [may be null]
//
DataTiddler.readData = function(tiddler) {
    var text = DataTiddler.readDataSectionText(tiddler);
	try {
	    return text ? DataTiddler.parse(text) : null;
	} catch(ex) {
		throw DataTiddler.extendJSONError(ex);
	}
};

// Internal.
// 
// Returns the serialized text of the data of the given tiddler, as it
// should be stored in the data section.
//
// @param tiddler a Tiddler
//
DataTiddler.getDataTextOfTiddler = function(tiddler) {
    var data = DataTiddler.getTiddlerDataObject(tiddler);
    return DataTiddler.stringify(data);
};


// Internal.
// 
DataTiddler.indexOfNonEscapedText = function(s, subString, startIndex) {
	var index = s.indexOf(subString, startIndex);
	while ((index > 0) && (s[index-1] == '~')) { 
		index = s.indexOf(subString, index+1);
	}
	return index;
};

// Internal.
//
DataTiddler.getDataSectionInfo = function(text) {
	// Special care must be taken to handle "<data>" and "</data>" texts inside
	// a data section. 
	// Also take care not to use an escaped <data> (i.e. "~<data>") as the start 
	// of a data section. (Same for </data>)

    // NOTE: we are explicitly searching for a data section that contains a JSON
    // string, i.e. framed with braces. This way we are little bit more robust in
    // case the tiddler contains unescaped texts "<data>" or "</data>". This must
    // be changed when using a different stringifier.

	var startTagText = "<data>{";
	var endTagText = "}</data>";

	// Find the first not escaped "<data>".
	var startDataTagIndex = DataTiddler.indexOfNonEscapedText(text, startTagText, 0);
	if (startDataTagIndex < 0) {
		return null;
	}

	// Find the *last* not escaped "</data>".
	var endDataTagIndex = text.indexOf(endTagText, startDataTagIndex);
	if (endDataTagIndex < 0) {
		return null;
	}
	var nextEndDataTagIndex;
	while ((nextEndDataTagIndex = text.indexOf(endTagText, endDataTagIndex+1)) >= 0) {
		endDataTagIndex = nextEndDataTagIndex;
	}

	return {
		prefixEnd: startDataTagIndex, 
		dataStart: startDataTagIndex+(startTagText.length)-1, 
		dataEnd: endDataTagIndex, 
		suffixStart: endDataTagIndex+(endTagText.length)
	};
};

// Internal.
// 
// Returns the "matches" of a content of a DataTiddler on the
// "data" regular expression. Return null when no data is defined
// in the tiddler content.
//
// Group 1: text before data section (prefix)
// Group 2: content of data section
// Group 3: text behind data section (suffix)
//
// @param tiddler a Tiddler
// @return [may be null] null when the tiddler contains no data section, otherwise see above.
//
DataTiddler.getDataTiddlerMatches = function(tiddler) {
	var text = tiddler.text;
	var info = DataTiddler.getDataSectionInfo(text);
	if (!info) {
		return null;
	}

	var prefix = text.substr(0,info.prefixEnd);
	var data = text.substr(info.dataStart, info.dataEnd-info.dataStart+1);
	var suffix = text.substr(info.suffixStart);
	
	return [text, prefix, data, suffix];
};


// Internal.
//
// Saves the data in a <data> block of the given tiddler (as a minor change). 
//
// The "chkAutoSave" and "chkForceMinorUpdate" options are respected. 
// I.e. the TiddlyWiki *file* is only saved when AutoSave is on.
//
// Notifications are not send. 
//
// This method should only be called when the data really has changed. 
//
// @param tiddler
//             the tiddler to be saved.
//
DataTiddler.save = function(tiddler) {

    var matches = DataTiddler.getDataTiddlerMatches(tiddler);

    var prefix;
    var suffix;
    if (matches === null) {
        prefix = tiddler.text;
        suffix = "";
    } else {
        prefix = matches[1];
        suffix = matches[3];
    }

    var dataText = DataTiddler.getDataTextOfTiddler(tiddler);
    var newText = 
            (dataText !== null) 
                ? prefix + "<data>" + dataText + "</data>" + suffix
                : prefix + suffix;
    if (newText != tiddler.text) {
        // make the change in the tiddlers text
        
        // ... see DataTiddler.MyTiddlerChangedFunction
        tiddler.isDataTiddlerChange = true;
        
        // ... do the action change
        tiddler.set(
                tiddler.title,
                newText,
                config.options.txtUserName, 
                config.options.chkForceMinorUpdate? undefined : new Date(),
                tiddler.tags);

        // ... see DataTiddler.MyTiddlerChangedFunction
        delete tiddler.isDataTiddlerChange;

        // Mark the store as dirty.
        store.dirty = true;
 
        // AutoSave if option is selected
        if(config.options.chkAutoSave) {
           saveChanges();
        }
    }
};

// Internal.
//
DataTiddler.MyTiddlerChangedFunction = function() {
    // Remove the data object from the tiddler when the tiddler is changed
    // by code other than DataTiddler code. 
    //
    // This is necessary since the data object is just a "cached version" 
    // of the data defined in the data section of the tiddler and the 
    // "external" change may have changed the content of the data section.
    // Thus we are not sure if the data object reflects the data section 
    // contents. 
    // 
    // By deleting the data object we ensure that the data object is 
    // reconstructed the next time it is needed, with the data defined by
    // the data section in the tiddler's text.
    
    // To indicate that a change is a "DataTiddler change" a temporary
    // property "isDataTiddlerChange" is added to the tiddler.
    if (this.dataObject && !this.isDataTiddlerChange) {
        delete this.dataObject;
    }
    
    // call the original code.
	DataTiddler.originalTiddlerChangedFunction.apply(this, arguments);
};


//============================================================================
// Formatters
//============================================================================

// This formatter ensures that "~<data>" is rendered as "<data>". This is used to 
// escape the "<data>" of a data section, just in case someone really wants to use
// "<data>" as a text in a tiddler and not start a data section.
//
// Same for </data>.
//
config.formatters.push( {
    name: "data-escape",
    match: "~<\\/?data>",

    handler: function(w) {
            w.outputText(w.output,w.matchStart + 1,w.nextMatch);
    }
} );


// This formatter ensures that <data>...</data> sections are not rendered.
//
config.formatters.push( {
    name: "data",
    match: "<data>",

    handler: function(w) {
		var info = DataTiddler.getDataSectionInfo(w.source);
		if (info && info.prefixEnd == w.matchStart) {
            w.nextMatch = info.suffixStart;
		} else {
			w.outputText(w.output,w.matchStart,w.nextMatch);
		}
    }
} );


//============================================================================
// Tiddler Class Extension
//============================================================================

// "Hijack" the changed method ---------------------------------------------------

DataTiddler.originalTiddlerChangedFunction = Tiddler.prototype.changed;
Tiddler.prototype.changed = DataTiddler.MyTiddlerChangedFunction;

// Define accessor methods -------------------------------------------------------

// Returns the value of the given data field of the tiddler. When no such field 
// is defined or its value is undefined the defaultValue is returned.
//
// When field is undefined (or null) the data object is returned. (See 
// DataTiddler.getDataObject.)
//
// @param field [may be null, undefined]
// @param defaultValue [may be null, undefined]
// @return [may be null, undefined]
//
Tiddler.prototype.data = function(field, defaultValue) {
    return (field) 
         ? DataTiddler.getTiddlerDataValue(this, field, defaultValue)
         : DataTiddler.getTiddlerDataObject(this);
};

// Sets the value of the given data field of the tiddler to the value. When the 
// value is equal to the defaultValue no value is set (and the field is removed).
//
// @param value [may be null, undefined]
// @param defaultValue [may be null, undefined]
//
Tiddler.prototype.setData = function(field, value, defaultValue) {
    DataTiddler.setTiddlerDataValue(this, field, value, defaultValue);
};


//============================================================================
// showData Macro
//============================================================================

config.macros.showData = {
     // Standard Properties
     label: "showData",
     prompt: "Display the values stored in the data section of the tiddler"
};

config.macros.showData.handler = function(place,macroName,params) {
    // --- Parsing ------------------------------------------

    var i = 0; // index running over the params
    // Parse the optional "JSON"
    var showInJSONFormat = false;
    if ((i < params.length) && params[i] == "JSON") {
        i++;
        showInJSONFormat = true;
    }
    var tiddlerName = story.findContainingTiddler(place).getAttribute("tiddler");
    if (i < params.length) {
        tiddlerName = params[i];
        i++;
    }

    // --- Processing ------------------------------------------
    try {
        if (showInJSONFormat) {
            this.renderDataInJSONFormat(place, tiddlerName);
        } else {
            this.renderDataAsTable(place, tiddlerName);
        }
    } catch (e) {
        this.createErrorElement(place, e);
    }
};

config.macros.showData.renderDataInJSONFormat = function(place,tiddlerName) {
    var text = DataTiddler.getDataText(tiddlerName);
    if (text) {
        createTiddlyElement(place,"pre",null,null,text);
    }
};

config.macros.showData.renderDataAsTable = function(place,tiddlerName) {
    var text = "|!Name|!Value|\n";
    var data = DataTiddler.getDataObject(tiddlerName);
    if (data) {
        for (var i in data) {
            var value = data[i];
            text += "|"+i+"|"+DataTiddler.stringify(value)+"|\n";
        }
    }
    
    wikify(text, place);
};


// Internal.
//
// Creates an element that holds an error message
// 
config.macros.showData.createErrorElement = function(place, exception) {
    var message = (exception.description) ? exception.description : exception.toString();
    return createTiddlyElement(place,"span",null,"showDataError","<<showData ...>>: "+message);
};

// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
    ".showDataError{color: #ffffff;background-color: #880000;}",
    "showData");


} // of "install only once"
// Used Globals (for JSLint) ==============

// ... TiddlyWiki Core
/*global 	createTiddlyElement, saveChanges, store, story, wikify */
// ... DataTiddler
/*global 	DataTiddler */
// ... JSON
/*global 	JSON */
			

/***
!JSON Code, used to serialize the data
***/
/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
    The global object JSON contains two methods.

    JSON.stringify(value) takes a JavaScript value and produces a JSON text.
    The value must not be cyclical.

    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will
    throw a 'JSONError' exception if there is an error.
*/
var JSON = {
    copyright: '(c)2005 JSON.org',
    license: 'http://www.crockford.com/JSON/license.html',
/*
    Stringify a JavaScript value, producing a JSON text.
*/
    stringify: function (v) {
        var a = [];

/*
    Emit a string.
*/
        function e(s) {
            a[a.length] = s;
        }

/*
    Convert a value.
*/
        function g(x) {
            var c, i = undefined, l, v;

            switch (typeof x) {
            case 'object':
                if (x) {
                    if (x instanceof Array) {
                        e('[');
                        l = a.length;
                        for (i = 0; i < x.length; i += 1) {
                            v = x[i];
                            if (typeof v != 'undefined' &&
                                    typeof v != 'function') {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(v);
                            }
                        }
                        e(']');
                        return;
                    } else if (typeof x.toString != 'undefined') {
                        e('{');
                        l = a.length;
                        for (i in x) {
                            v = x[i];
                            if (x.hasOwnProperty(i) &&
                                    typeof v != 'undefined' &&
                                    typeof v != 'function') {
                                if (l < a.length) {
                                    e(',');
                                }
                                g(i);
                                e(':');
                                g(v);
                            }
                        }
                        return e('}');
                    }
                }
                e('null');
                return;
            case 'number':
                e(isFinite(x) ? +x : 'null');
                return;
            case 'string':
                l = x.length;
                e('"');
                for (i = 0; i < l; i += 1) {
                    c = x.charAt(i);
                    if (c >= ' ') {
                        if (c == '\\' || c == '"') {
                            e('\\');
                        }
                        e(c);
                    } else {
                        switch (c) {
                            case '\b':
                                e('\\b');
                                break;
                            case '\f':
                                e('\\f');
                                break;
                            case '\n':
                                e('\\n');
                                break;
                            case '\r':
                                e('\\r');
                                break;
                            case '\t':
                                e('\\t');
                                break;
                            default:
                                c = c.charCodeAt();
                                e('\\u00' + Math.floor(c / 16).toString(16) +
                                    (c % 16).toString(16));
                        }
                    }
                }
                e('"');
                return;
            case 'boolean':
                e(String(x));
                return;
            default:
                e('null');
                return;
            }
        }
        g(v);
        return a.join('');
    },
/*
    Parse a JSON text, producing a JavaScript value.
*/
    parse: function (text) {
        var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,
            token = undefined,
            operator = undefined;

        function error(m, t) {
            throw {
                name: 'JSONError',
                message: m,
                text: t || operator || token
            };
        }

        function next(b) {
            if (b && b != operator) {
                error("Expected '" + b + "'");
            }
            if (text) {
                var t = p.exec(text);
                if (t) {
                    if (t[2]) {
                        token = null;
                        operator = t[2];
                    } else {
                        operator = null;
                        try {
                            token = eval(t[1]);
                        } catch (e) {
                            error("Bad token", t[1]);
                        }
                    }
                    text = text.substring(t[0].length);
                } else {
                    error("Unrecognized token", text);
                }
            } else {
                token = operator = undefined;
            }
        }


        function val() {
            var k, o;
            switch (operator) {
            case '{':
                next('{');
                o = {};
                if (operator != '}') {
                    for (;;) {
                        if (operator || typeof token != 'string') {
                            error("Missing key");
                        }
                        k = token;
                        next();
                        next(':');
                        o[k] = val();
                        if (operator != ',') {
                            break;
                        }
                        next(',');
                    }
                }
                next('}');
                return o;
            case '[':
                next('[');
                o = [];
                if (operator != ']') {
                    for (;;) {
                        o.push(val());
                        if (operator != ',') {
                            break;
                        }
                        next(',');
                    }
                }
                next(']');
                return o;
            default:
                if (operator !== null) {
                    error("Missing value");
                }
                k = token;
                next();
                return k;
            }
        }
        next();
        return val();
    }
};

/***
!Setup the data serialization
***/

DataTiddler.format = "JSON";
DataTiddler.stringify = JSON.stringify;
DataTiddler.parse = JSON.parse;

//}}}
/***
|''Name:''|DatePickerLibrary|
|''Description:''|DatePicker library for use with macros|
|''Author:''|Saq Imtiaz ( lewcid@gmail.com )|
|''Code Repository:''|http://svn.tiddlywiki.org/Trunk/contributors/SaqImtiaz/libraries/DatePicker.js|
|''Version:''|0.9|
|''Date:''|06/04/2007|
|''License:''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
|''~CoreVersion:''|2.3.0|
***/

// /%
//!BEGIN-PLUGIN-CODE
//{{{
function $id(n) {
	return document.getElementById(n);
}

DatePicker = {
	
	days : ['S','M','T','W','T','F','S'],
		
	cells : new Array(42),
	
	setup : function(){
		var cte = createTiddlyElement;
		var table = this.table = cte(null,"table","datePickerTable");
		table.style.display = 'none';
        document.body.appendChild(table);
		var thead = cte(table,"thead");
		var hRow = cte(thead,"tr");
		hRow.onclick = stopEvent;
		cte(hRow,"td",null,"datePickerNav","<<").onclick = DatePicker.prevm;
		cte(hRow,"td","datePickerMNS",null,null,{colSpan:"5"});
		cte(hRow,"td",null,"datePickerNav",">>",{align:"right"}).onclick=DatePicker.nextm; 
		
		var tbody = cte(table,"tbody","datePickerTableBody");
		var dayRow = cte(tbody,"tr",null,"datePickerDaysHeader");
		
		for (var i=0; i<this.days.length; i++){
			cte(dayRow,"td",null,null,this.days[i]);
		}      
	},
	
	show : function(el,dateObj,cb) {
		var me = DatePicker;
		var now = me.now = new Date();
		if (!dateObj)
			dateObj = now;
		me.root = el;
		if (cb)
			me.root.datePickerCallback = cb;
		me.scc = { m : now.getMonth(), y : now.getFullYear(), d : now.getDate() };
				
		Popup.place(el,me.table,{x:0,y:1});

		
		var cur = [dateObj.getDate(),dateObj.getMonth()+1,dateObj.getFullYear()];

		me.cc = { m : cur[1]-1, y : cur[2] };
		me.sd = { m : cur[1]-1, y : cur[2], d : cur[0] };   
		me.fillCalendar(cur[0],me.scc.d,cur[1]-1,cur[2]);
	},
	
	fillCalendar : function(hd,today,cm,cy) {
		var me = DatePicker;
		
		var sd = me.now.getDate();
		var td = new Date(cy,cm,1)
		var cd = td.getDay();

		$id('datePickerMNS').innerHTML = td.formatString('MMM YYYY')
		
		var tbody = $id('datePickerTableBody');
		removeChildren(tbody);
		
		var days = (new Date(cy, cm+1, 0)).getDate();
		var day = 1;
		for (var j=1;j<=6;j++) { //rows
			var row = createTiddlyElement(tbody,"tr",null,"datePickerDayRow");
			for (var t=1; t<=7; t++) { //cells
				var d = 7 * (j-1) - (-t); //id key      
				if ( (d >= (cd -(-1))) && (d<=cd-(-(days))) ) {
					var dip = ( ((d-cd < sd) && (cm == me.scc.m) && (cy == me.scc.y)) || (cm < me.scc.m && cy == me.scc.y) || (cy < me.scc.y) );
					var htd = ( (hd != '') && (d-cd == hd) );
					var hToday = ( (today != '') && (d-cd == today) && cy == me.scc.y && cm == me.scc.m );
					if (htd)
						_class = 'highlightedDate';                
					else if (dip)
						_class = 'oldDate';
					else if (hToday && ! htd)
						_class = 'todayDate';
					else
						_class = 'defaultDate';
					var cell = createTiddlyElement(row,"td","datePickerDay"+d,_class,d-cd);
					cell.onmouseover = function(e){addClass(this,'tdover');};
					cell.onmouseout = function(e){removeClass(this,'tdover');};
					cell.onclick = me.selectDate;
					me.cells[d] = new Date(cy,cm,d-cd);
				}
				else {
					var cell = createTiddlyElement(row,"td","datePickerDay"+d,"emptyDate");
				}
				day++;
			}
			if(day > days + cd)
				break;
		} 
	},
	
	nextm : function() {
		var me = DatePicker;        
		me.cc.m += 1;
		if (me.cc.m >= 12) {
			me.cc.m = 0;
			me.cc.y++;
		}
		me.fillCalendar(me.getDayStatus(me.cc.m,me.cc.y),me.scc.d,me.cc.m,me.cc.y);
		return false;
	},
	
	prevm : function() {
		var me = DatePicker;
		me.cc.m -= 1;
		if (me.cc.m < 0) {
			me.cc.m = 11;
			me.cc.y--;
		}
		me.fillCalendar(me.getDayStatus(me.cc.m,me.cc.y),me.scc.d,me.cc.m,me.cc.y);
		return false;
	},
	
	getDayStatus : function(ccm,ccy){
		return (ccy == this.sd.y && ccm == this.sd.m)? this.sd.d : '';
	},
	
	selectDate : function(ev){
		var e = ev ? ev : window.event;
		var me = DatePicker;
		var date = me.cells[resolveTarget(e).id.substring(13,resolveTarget(e).id.length)];
		if (me.root.datePickerCallback && typeof me.root.datePickerCallback == 'function')
			me.root.datePickerCallback(me.root,date);
		$id('datePickerTable').style.display = 'none';
		return false;
	},
	
	onclick : function(ev){
		$id("datePickerTable").style.display = 'none';
		return false;
	},
	
	create : function(el,dateObj,cb){
		el.onclick = el.onfocus = function(e){DatePicker.show(el,dateObj,cb);stopEvent(e)};
	},
	
	css: "table#datePickerTable td.datePickerNav {\n"+
		"    cursor:pointer;\n"+
		"}\n"+
		"\n"+
		".datePickerDaysHeader td {\n"+
		"    text-align:center;\n"+
		"    background:#ABABAB;\n"+
		"    font:12px Arial;\n"+
		"}\n"+
		"\n"+
		".datePickerDayRow td {\n"+
		"    width:18px;\n"+
		"    height:18px;\n"+
		"}\n"+
		"\n"+
		"td#datePickerMNS, td.datePickerNav {\n"+
		"    font:bold 13px Arial;\n"+
		"}\n"+
		"\n"+
		"table#datePickerTable {\n"+
		"    position:absolute;\n"+
		"    border-collapse:collapse;\n"+
		"    background:#FFFFFF;\n"+
		"    border:1px solid #ABABAB;\n"+
		"    display:none;   \n"+
		"}\n"+
		"\n"+
		"table#datePickerTable td{\n"+
		"    padding: 3px;\n"+
		"}\n"+
		"\n"+
		"td#datePickerMNS {\n"+
		"    text-align: center;\n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td {\n"+
		"    background-color : #C4D3EA;\n"+
		"    cursor : pointer;\n"+
		"    border : 1px solid #6487AE;\n"+
		"    text-align : center;\n"+
		"	font : 10px Arial;\n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td.defaultDate {\n"+
		"	color : #333333;	\n"+
		"	text-decoration : none;   \n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td.emptyDate {\n"+
		"    cursor:default; \n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td.oldDate {\n"+
		"	color : #ABABAB;\n"+
		"    text-decoration : line-through;\n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td.highlightedDate {\n"+
		"    background : #FFF799;\n"+
		"	font-weight : bold;\n"+
		"	color : #333333;\n"+
		"}\n"+
		"\n"+
		"tr.datePickerDayRow td.todayDate {\n"+
		"	font-weight : bold;\n"+
		"	color : red;\n"+
		"}\n"+
		"\n"+
		"table#datePickerTable tr.datePickerDayRow td.tdover {\n"+
		"    background:#fc6;\n"+
		"}",
	
	init : function(){
		this.setup();
		addEvent(document,'click',DatePicker.onclick);
		config.shadowTiddlers['StyleSheetDatePicker'] = this.css;
		if(store)
			store.addNotification('StyleSheetDatePicker',refreshStyles);
	}
};

DatePicker.init();
//}}}
//!END-PLUGIN-CODE
// %/
/***
|Name|[[DatePlugin]]|
|Source|http://www.TiddlyTools.com/#DatePlugin|
|Documentation|http://www.TiddlyTools.com/#DatePluginInfo|
|Version|2.7.3|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|formatted dates plus popup menu with 'journal' link, changes and (optional) reminders|
This plugin provides a general approach to displaying formatted dates and/or links and popups that permit easy navigation and management of tiddlers based on their creation/modification dates.
!!!!!Documentation
>see [[DatePluginInfo]]
!!!!!Configuration
<<<
<<option chkDatePopupHideCreated>> omit 'created' section from date popups
<<option chkDatePopupHideChanged>> omit 'changed' section from date popups
<<option chkDatePopupHideTagged>> omit 'tagged' section from date popups
<<option chkDatePopupHideReminders>> omit 'reminders' section from date popups
<<option chkShowJulianDate>> display Julian day number (1-365) below current date

see [[DatePluginConfig]] for additional configuration settings, for use in calendar displays, including:
*date formats
*color-coded backgrounds
*annual fixed-date holidays
*weekends
<<<
!!!!!Revisions
<<<
2011.04.23 2.7.3 added config.macros.date.tipformat for custom mouseover tooltip and config.macros.date.leadtime for custom reminder leadtime (default=90 days)
2010.12.15 2.7.2 omit date highlighting when hiding popup items (created/changed/tagged/reminders)
|please see [[DatePluginInfo]] for additional revision details|
2005.10.30 0.9.0 pre-release
<<<
!!!!!Code
***/
//{{{
version.extensions.DatePlugin= {major: 2, minor: 7, revision: 3, date: new Date(2011,4,23)};

config.macros.date = {
	format: 'YYYY.0MM.0DD', // default date display format
	linkformat: 'YYYY.0MM.0DD', // 'dated tiddler' link format
	tipformat: 'YYYY.0MM.0DD', // 'dated tiddler' link tooltip format
	leadtime: 31, // find reminders up to 31 days from now
	linkedbg: '#babb1e',
	todaybg: '#ffab1e',
	weekendbg: '#c0c0c0',
	holidaybg: '#ffaace',
	createdbg: '#bbeeff',
	modifiedsbg: '#bbeeff',
	remindersbg: '#c0ffee',
	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 ],
	holidays: [ '01/01', '07/04', '07/24', '11/24' ]
		// NewYearsDay, IndependenceDay(US), Eric's Birthday (hooray!), Thanksgiving(US)
};

config.macros.date.handler = function(place,macroName,params)
{
	// default: display current date
	var now =new Date();
	var date=now;
	var mode='display';
	if (params[0]&&['display','popup','link'].contains(params[0].toLowerCase()))
		{ mode=params[0]; params.shift(); }

	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();
	showDate(place,date,mode,format,linkformat);
}

window.showDate=showDate;
function showDate(place,date,mode,format,linkformat,autostyle,weekend)
{
	mode	  =mode||'display';
	format	  =format||config.macros.date.format;
	linkformat=linkformat||config.macros.date.linkformat;

	// format the date output
	var title=date.formatString(format);
	var linkto=date.formatString(linkformat);
	var tip=date.formatString(config.macros.date.tipformat);

	// 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.appendChild(document.createTextNode(title));
	link.title = tip;
	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;
		link.style.fontStyle='normal';
	}
	// 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;

	var co=config.options; // abbrev

	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; }
	if (config.options.chkShowJulianDate) { // optional display of Julian date numbers
		var m=[0,31,59,90,120,151,181,212,243,273,304,334];
		var d=date.getDate()+m[date.getMonth()];
		var y=date.getFullYear();
		if (date.getMonth()>1 && (y%4==0 && y%100!=0) || y%400==0)
			d++; // after February in a leap year
		wikify('@@font-size:80%;<br>'+d+'@@',place);
	}

}
//}}}
//{{{
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) { e=e||window.event;
	var p=Popup.create(this); if (!p) return false;
	// always show dated tiddler link (or just date, if readOnly) at the top...
	if (!readOnly || store.tiddlerExists(this.date.formatString(this.linkformat)))
		createTiddlyLink(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat),true);
	else
		createTiddlyText(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat));
	addCreatedsToPopup(p,this.date,this.format);
	addModifiedsToPopup(p,this.date,this.format);
	addTaggedToPopup(p,this.date,this.linkformat);
	addRemindersToPopup(p,this.date,this.linkformat);
	Popup.show(); e.cancelBubble=true; if(e.stopPropagation)e.stopPropagation(); return false;
}
//}}}
//{{{
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();
		createds[date].push(tiddlers[t].title);
	}
	return createds;
}
function hasCreateds(date) // returns true if date has created tiddlers
{
	if (config.options.chkDatePopupHideCreated) return false;
	if (!config.macros.date.createds) config.macros.date.createds=indexCreateds();
	return (config.macros.date.createds[date.formatString('YYYY0MM0DD')]!=undefined);
}

function addCreatedsToPopup(p,when,format)
{
	if (config.options.chkDatePopupHideCreated) return false;
	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) {
		createds.sort();
		var e=createTiddlyElement(p,'div',null,null,'created ('+createds.length+')');
		for(var t=0; t<createds.length; t++) {
			var link=createTiddlyLink(createTiddlyElement(p,'li'),createds[t],false);
			link.appendChild(document.createTextNode(indent+createds[t]));
		}
	}
}
//}}}
//{{{
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();
		modifieds[date].push(tiddlers[t].title);
	}
	return modifieds;
}
function hasModifieds(date) // returns true if date has modified tiddlers
{
	if (config.options.chkDatePopupHideChanged) return false;
	if (!config.macros.date.modifieds) config.macros.date.modifieds = indexModifieds();
	return (config.macros.date.modifieds[date.formatString('YYYY0MM0DD')]!=undefined);
}

function addModifiedsToPopup(p,when,format)
{
	if (config.options.chkDatePopupHideChanged) return false;
	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]))
					temp.push(mods[t]);
			mods=temp;
		}
		mods.sort();
		var e=createTiddlyElement(p,'div',null,null,'changed ('+mods.length+')');
		for(var t=0; t<mods.length; t++) {
			var link=createTiddlyLink(createTiddlyElement(p,'li'),mods[t],false);
			link.appendChild(document.createTextNode(indent+mods[t]));
		}
	}
}
//}}}
//{{{
function hasTagged(date,format) // returns true if date is tagging other tiddlers
{
	if (config.options.chkDatePopupHideTagged) return false;
	return store.getTaggedTiddlers(date.formatString(format)).length>0;
}

function addTaggedToPopup(p,when,format)
{
	if (config.options.chkDatePopupHideTagged) return false;
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	var tagged=store.getTaggedTiddlers(when.formatString(format));
	if (tagged.length) var e=createTiddlyElement(p,'div',null,null,'tagged ('+tagged.length+')');
	for(var t=0; t<tagged.length; t++) {
		var link=createTiddlyLink(createTiddlyElement(p,'li'),tagged[t].title,false);
		link.appendChild(document.createTextNode(indent+tagged[t].title));
	}
}
//}}}
//{{{
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
		var t = findTiddlersWithReminders(date, [0,leadtime], null, null, 1);
		for(var i=0; i<t.length; i++) reminders[t[i].matchedDate]=true;
	}
	return reminders;
}

function hasReminders(date) // returns true if date has reminders
{
	if (config.options.chkDatePopupHideReminders) return false;
	if (window.reminderCacheForCalendar)
		return window.reminderCacheForCalendar[date]; // use calendar cache
	if (!config.macros.date.reminders)
		config.macros.date.reminders = indexReminders(date,config.macros.date.leadtime); // create a reminder cache
	return (config.macros.date.reminders[date]);
}

function addRemindersToPopup(p,when,format)
{
	if (config.options.chkDatePopupHideReminders) return false;
	if(window.findTiddlersWithReminders==undefined) return; // reminder plugin not installed

	var indent = String.fromCharCode(160)+String.fromCharCode(160);
	var reminders=findTiddlersWithReminders(when, [0,config.macros.date.leadtime],null,null,1);
	createTiddlyElement(p,'div',null,null,'reminders ('+(reminders.length||'none')+')');
	for(var t=0; t<reminders.length; t++) {
		link = createTiddlyLink(createTiddlyElement(p,'li'),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;	// readonly... omit 'new reminder...' command
	var rem='\\<\\<reminder day:%0 month:%1 year:%2 title:"Enter a reminder title here"\\>\\>';
	rem=rem.format([when.getDate(),when.getMonth()+1,when.getYear()+1900]);
	var cmd="<<newTiddler label:[["+indent+"new reminder...]] prompt:[[add a reminder to '%0']]"
		+" title:[[%0]] text:{{var t=store.getTiddlerText('%0','');t+(t.length?'\\n':'')+'%1'}} tag:%2>>";
	wikify(cmd.format([when.formatString(format),rem,config.options.txtCalendarReminderTags||'']),
		createTiddlyElement(p,'li'));
}
//}}}
/***
|Name|DatePluginConfig|
|Source|http://www.TiddlyTools.com/#DatePluginConfig|
|Documentation|http://www.TiddlyTools.com/#DatePluginInfo|
|Version|2.7.3|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|formats, background colors and other optional settings for DatePlugin|
***/
// // Default popup content display options (can be overridden by cookies)
//{{{
if (config.options.chkDatePopupHideCreated===undefined)
	config.options.chkDatePopupHideCreated=false;
if (config.options.chkDatePopupHideChanged===undefined)
	config.options.chkDatePopupHideChanged=false;
if (config.options.chkDatePopupHideTagged===undefined)
	config.options.chkDatePopupHideTagged=false;
if (config.options.chkDatePopupHideReminders===undefined)
	config.options.chkDatePopupHideReminders=false;
//}}}

// // show Julian date number below regular date
//{{{
if (config.options.chkShowJulianDate===undefined)
	config.options.chkShowJulianDate=false;
//}}}

// // fixed-date annual holidays
//{{{
config.macros.date.holidays=[
	"01/01", // NewYearsDay, 
	"07/04", // US Independence Day
	"07/24"  // Eric's Birthday (hooray!)
];
//}}}

// // weekend map (1=weekend, 0=weekday)
//{{{
config.macros.date.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
//}}}

// // date display/link formats
//{{{
config.macros.date.format="YYYY.0MM.0DD"; // default date display format
config.macros.date.linkformat="YYYY.0MM.0DD"; // 'dated tiddler' link format
config.macros.date.tipformat="YYYY.0MM.0DD"; // 'dated tiddler' tooltip format
//}}}

// // reminder lead time
//{{{
config.macros.date.leadtime=31; // find reminders up to 31 days from now
//}}}

// // When displaying a calendar (see [[CalendarPlugin]]), you can customize the colors/styles that are applied to the calendar dates by modifying the values and/or functions below:
//{{{
// default calendar colors
config.macros.date.weekendbg="#c0c0c0";
config.macros.date.holidaybg="#ffaace";
config.macros.date.createdbg="#bbeeff";
config.macros.date.modifiedsbg="#bbeeff";
config.macros.date.linkedbg="#babb1e";
config.macros.date.remindersbg="#c0ffee";

// apply calendar styles
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; }
	if (config.options.chkShowJulianDate) {
		var m=[0,31,59,90,120,151,181,212,243,273,304,334];
		var d=date.getDate()+m[date.getMonth()];
		var y=date.getFullYear();
		if (date.getMonth()>1 && (y%4==0 && y%100!=0) || y%400==0) d++; // after February in a leap year
		wikify("@@font-size:80%;<br>"+d+"@@",place);
	}
	var t=store.getTiddlerText(linkto,'')
	if (config.options.chkInlineCalendarJournals && t.length) wikify('<br>'+t,place);
}
//}}}
//{{{
config.options.chkDatePopupHideCreated = true;
config.options.chkDatePopupHideChanged = true;
config.options.chkDatePopupHideTagged = false;
config.macros.date.linkedbg = "";
config.macros.date.reminderstxt = "#000000";
config.macros.date.reminderspriority = "1";
config.macros.date.eventbg = "#83b8c2";
config.macros.date.eventtxt = "#000000";
config.macros.date.eventpriority = "20";
config.macros.date.birthdaylabel = "Birthday : ";
config.macros.date.birthdaybg = "#ffff00";
config.macros.date.birthdaytxt = "#000000";
config.macros.date.birthdaypriority = "10";
//}}}
//{{{
config.macros.date.holidays = [];
//}}}
//{{{
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 (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 (isToday(date)&&(cmd.todaybg!=""))
		{ place.style.background = cmd.todaybg; }
	if (isHoliday(date)&&(cmd.holidaybg!=""))
		{ place.style.background = cmd.holidaybg; }
	var reminder = hasReminders(date);
	if (reminder) {
		var wikifier = new Wikifier(reminder, formatter, null, null);
		var e = createTiddlyElement(document.body, "div");
		var jqe = jQuery(e);
		jqe.css("display", "none");
		wikifier.subWikify(e);
		var style = jqe.children().attr("style");
		removeNode(e);
		if (style && (style != "")) {
			jQuery(place).attr("style", style);
			jQuery(link).attr("style", style);
		} else if (cmd.remindersbg != "") {
			place.style.background = cmd.remindersbg;
		}
	}
	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 (config.options.chkShowJulianDate) {
		var m=[0,31,59,90,120,151,181,212,243,273,304,334];
		var d=date.getDate()+m[date.getMonth()];
		var y=date.getFullYear();
		if (date.getMonth()>1 && (y%4==0 && y%100!=0) || y%400==0) d++; // after February in a leap year
		wikify("@@font-size:80%;<br>"+d+"@@",place);
	}
	var t=store.getTiddlerText(linkto,'')
	if (config.options.chkInlineCalendarJournals && t.length) wikify('<br>'+t,place);
}
//}}}
//{{{
window.getTaskDateFomat = function() {
	var dateFormat = 'YYYY-0MM-0DD';
	
	// From CalendarPlugin config.macros.calendar.handler
	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]) dateFormat = pa[0]; }
	
	return dateFormat;
}
//}}}
//{{{
function onClickDatePopup(e) { e=e||window.event;
	var p=Popup.create(this); if (!p) return false;
	// always show dated tiddler link (or just date, if readOnly) at the top...
	if (!readOnly || store.tiddlerExists(this.date.formatString(this.linkformat)))
		createTiddlyLink(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat),true);
	else
		createTiddlyText(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat));

	var rem;
	var cmd;

	var date = this.date;
	var timezoneHours = (date.getTimezoneOffset() / 60) | 0;
	var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60));
	var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes;

	var newTiddlerTags = 'tag:"task"';
	if (this.tags && this.tags.task) {
		newTiddlerTags = '';
		var tagList = this.tags.task.readBracketedList();
		for (var i = 0; i < tagList.length; i++) {
			newTiddlerTags = newTiddlerTags + ' tag:"' + tagList[i] + '"';
		}
	}

	var remindersFormat = (this.tags && this.tags.params) ? this.tags.params["remindersFormat"] : null;
	remindersFormat = remindersFormat ? remindersFormat.replace(/\\/g, "\\\\") : remindersFormat;

	var newTaskLabel = (this.tags && this.tags.params && (this.tags.params["newTaskLabel"] !== undefined) && (this.tags.params["newTaskLabel"] !== null)) ? this.tags.params["newTaskLabel"] : "new task...";
	var newTaskPrompt = (this.tags && this.tags.params && (this.tags.params["newTaskPrompt"] !== undefined) && (this.tags.params["newTaskPrompt"] !== null)) ? this.tags.params["newTaskPrompt"] : "add a task to '%0'";
	var goToTaskLabel = (this.tags && this.tags.params && (this.tags.params["goToTaskLabel"] !== undefined) && (this.tags.params["goToTaskLabel"] !== null)) ? this.tags.params["goToTaskLabel"] : ">";
	var taskPrefix = (this.tags && this.tags.params && (this.tags.params["taskPrefix"] !== undefined) && (this.tags.params["taskPrefix"] !== null)) ? this.tags.params["taskPrefix"] : "@@background-color:#ffff00;[x(done)] ''Done&nbsp;''@@";
	taskPrefix = taskPrefix ? taskPrefix.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") : taskPrefix;
	rem='\\<\\<reminder year:%2 month:%1 day:%0 tag:\\"timezone:UTC%3\\" function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() ' + (remindersFormat  ? 'format:\\"' + remindersFormat + '\\" ' : '') + 'title:\\"@@background-color:' + config.macros.date.remindersbg + ';color:' + config.macros.date.reminderstxt + ';priority:' + config.macros.date.reminderspriority + ';\\\\\\"\\\\\\"\\\\\\"TIDDLERNAME\\\\\\"\\\\\\"\\\\\\"@@ \\\\<\\\\<goToTiddler label:' + goToTaskLabel + ' title:\\{\\{\\\'REMINDERDATE : TIDDLERNAMEESC\\\'\\}\\} ' + (taskPrefix ? 'text:\\{\\{\\\'' + taskPrefix.replace(/\\/g, "\\\\") + '\\\\n\\\\n\\\'\\}\\} ' : '') + 'tag:task tag:[[TIDDLERFULLNAME]] focus:text\\\\>\\\\>\\"\\>\\>';
	rem=rem.format([this.date.getDate(),this.date.getMonth()+1,this.date.getFullYear(),(date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone]);
	cmd="<<newTiddler label:[[" + newTaskLabel + "]] prompt:[[" + newTaskPrompt + "]]"
		+" title:[[%0 : ]] text:{{\"" + (taskPrefix ? taskPrefix + "\\n\\n" : "") + "%1\\n\\n\"}} tag:%0 " + newTiddlerTags + " >>";
	wikify(cmd.format([this.date.formatString(this.linkformat),rem]),
		createTiddlyElement(p,'li'));

	var newTiddlerTags = 'tag:"meeting"';
	if (this.tags && this.tags.meeting) {
		newTiddlerTags = '';
		var tagList = this.tags.meeting.readBracketedList();
		for (var i = 0; i < tagList.length; i++) {
			newTiddlerTags = newTiddlerTags + ' tag:"' + tagList[i] + '"';
		}
	}

	var newMeetingLabel = (this.tags && this.tags.params && this.tags.params["newMeetingLabel"]) ? this.tags.params["newMeetingLabel"] : "new meeting...";
	var newMeetingPrompt = (this.tags && this.tags.params && this.tags.params["newMeetingPrompt"]) ? this.tags.params["newMeetingPrompt"] : "add a meeting to '%0'";
	var goToMeetingLabel = (this.tags && this.tags.params && this.tags.params["goToMeetingLabel"]) ? this.tags.params["goToMeetingLabel"] : ">";
	var meetingPrefix = (this.tags && this.tags.params && (this.tags.params["meetingPrefix"] !== undefined) && (this.tags.params["meetingPrefix"] !== null)) ? this.tags.params["meetingPrefix"] : "[x(cancelled)] Cancelled [x(doNotPublish)] Do not publish";
	meetingPrefix = meetingPrefix ? meetingPrefix.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") : meetingPrefix;
	rem='\\<\\<reminder year:%2 month:%1 day:%0 tag:\\"timezone:UTC%3\\" function:createICalendarReminders_formatTitle() messagefunction:replaceTiddlerName() messagefunction:replaceReminderDateTime() messagefunction:replaceTiddlerFormatting() ' + (remindersFormat  ? 'format:\\"' + remindersFormat + '\\" ' : '') + 'title:\\"@@background-color:' + config.macros.date.eventbg + ';color:' + config.macros.date.eventtxt + ';priority:' + config.macros.date.eventpriority + ';09:00 - 09:00 : \\\\\\"\\\\\\"\\\\\\"TIDDLERNAME\\\\\\"\\\\\\"\\\\\\"@@ \\\\<\\\\<goToTiddler label:' + goToMeetingLabel + ' title:\\{\\{\\\'REMINDERDATE : TIDDLERNAMEESC\\\'\\}\\} ' + (meetingPrefix ? 'text:\\{\\{\\\'' + meetingPrefix.replace(/\\/g, "\\\\") + '\\\\n\\\\n\\\'\\}\\} ' : '') + 'tag:meeting tag:[[TIDDLERFULLNAME]] focus:text\\\\>\\\\>\\"\\>\\>';
	rem=rem.format([this.date.getDate(),this.date.getMonth()+1,this.date.getFullYear(),(date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone]);
	cmd="<<newTiddler label:[[" + newMeetingLabel + "]] prompt:[[" + newMeetingPrompt + "]]"
		+" title:[[%0 : ]] text:{{\"" + (meetingPrefix ? meetingPrefix + "\\n\\n" : "") + "%1\\n\\n\"}} tag:%0 " + newTiddlerTags + " >>";
	wikify(cmd.format([this.date.formatString(this.linkformat),rem]),
		createTiddlyElement(p,'li'));

	addCreatedsToPopup(p,this.date,this.format,this.linkformat);
	addModifiedsToPopup(p,this.date,this.format,this.linkformat);
	addTaggedToPopup(p,this.date,this.linkformat,this.tags);
	addRemindersToPopup(p,this.date,this.linkformat,this.tags);
	Popup.show(); e.cancelBubble=true; if(e.stopPropagation)e.stopPropagation(); return false;
}
//}}}
//{{{
window.replaceReminderDateTime = function(diff, params, matchedDate, tiddlerTitle) {
	if (params["title"]) {
		if (params["title"].indexOf("REMINDERDATE") > -1) {
			var formatDateString = function(date) {
				return date.getFullYear() + "-"
						+ (date.getMonth() < 9 ? "0" : "") + (date.getMonth() + 1) + "-"
						+ (date.getDate() < 10 ? "0" : "") + date.getDate();
			}
			params["title"] = params["title"].replace(/REMINDERDATE/g, formatDateString(matchedDate));
		}
		
		if ((params["title"].indexOf("REMINDERSTART") > -1) || (params["title"].indexOf("REMINDEREND") > -1)) {
			var m = params["title"].match(/(2[0-3]|[01][0-9]):[0-5][0-9]/g);
			if (m) {
				var start = m[0];
				var end = m[1] ? m[1] : m[0];
				params["title"] = params["title"].replace(/REMINDERSTART/g, start);
				params["title"] = params["title"].replace(/REMINDEREND/g, end);
			}
		}
	}
}
//}}}
//{{{
window.replaceTiddlerName = function(diff, params, matchedDate, tiddlerTitle) {
	if (tiddlerTitle) {
		var reminderTiddlerTitle = (tiddlerTitle.indexOf(" : ") > -1) ? tiddlerTitle.substring(tiddlerTitle.indexOf(" : ") + " : ".length) : tiddlerTitle;
		if (!params["title"]) {
			params["title"] = reminderTiddlerTitle;
		} else {
			params["title"] = params["title"].replace(/TIDDLERNAMEESC/g, reminderTiddlerTitle.replace(/'/g, "\\'").replace(/"/g, '\\"'));
			params["title"] = params["title"].replace(/TIDDLERNAME/g, reminderTiddlerTitle);
			params["title"] = params["title"].replace(/TIDDLERFULLNAMEESC/g, tiddlerTitle.replace(/'/g, "\\'").replace(/"/g, '\\"'));
			params["title"] = params["title"].replace(/TIDDLERFULLNAME/g, tiddlerTitle);
		}
	}
}
//}}}
//{{{
window.replaceTiddlerFormatting = function(diff, params, matchedDate, tiddlerTitle) {
	var tiddler = null;
	if (matchedDate === undefined) {
		// window.replaceTiddlerFormatting = function(dateHash, params) {...}
		tiddler = params["tiddlerreference"];
		params = diff;
	} else {
		tiddler = tiddlerTitle ? store.getTiddler(tiddlerTitle) : null;
	}
	
	var paramsTitle = params["title"];
	var formattedTitle = null
	var formattedTitleStart = -1;
	var formattedTitleEnd = -1;
	if (paramsTitle) {
		formattedTitleStart = paramsTitle.indexOf("@@");
		if (formattedTitleStart > -1) {
			formattedTitleStart += 2;
			formattedTitleEnd = paramsTitle.indexOf("@@", formattedTitleStart);
			if (formattedTitleEnd > -1) {
				formattedTitle = paramsTitle.substring(formattedTitleStart, formattedTitleEnd);
			}
		}
	}
	
	if (formattedTitle) {
		var tagStyles = null;
		var tiddlers = store.getTaggedTiddlers("reminderstyle");
		for (var i = 0; i < tiddlers.length; i++) {
			var matches = tiddlers[i].text.match(/^\s*reminderstyle\s*:\s*.+$/igm);
			if (matches) {
				for (var j = 0; j < matches.length; j++) {
					var styleDefinitionMatches = matches[j].match(/(\s*reminderstyle\s*:\s*)(.+)(\s*)/i);
					if (styleDefinitionMatches && styleDefinitionMatches[2]) {
						var styleDefinitionParams = styleDefinitionMatches[2].parseParams(null, null, false, false);
						if (styleDefinitionParams && styleDefinitionParams[1] && styleDefinitionParams[1].name) {
							var tagName = styleDefinitionParams[1].name;
							var style =  "";
							var styleMatches = styleDefinitionMatches[2].substring(styleDefinitionMatches[2].indexOf(tagName) + tagName.length).match(/(\s*:\s*)(.*)(\s*)/i);
							if (styleMatches && styleMatches[2]) {
								var style =  styleMatches[2];
								style = ((style.length > 0) && (style.lastIndexOf(";") != style.length - 1)) ? style + ";" : style;
							}
							tagStyles = (tagStyles == null) ? {} : tagStyles;
							tagStyles[tagName] = style;
						}
					}
				}
			}
		}
		
		var reminderTags = null;
		if (tagStyles) {
			var titles = [];
			paramsTitle.replace(/.*<<(goToTiddler|newTiddler).*title:\{\{(['|"](?:(?!['|"]\}\}).)*['|"])\}\}.*>>.*/, function () {
				titles.push(Array.prototype.slice.call(arguments, 2, 3));
			});
			if (titles.length > 0) {
				var tagTiddler = store.getTiddler(eval("" + titles[0]));
				if (tagTiddler) {
					if (reminderTags == null) {
						reminderTags = [];
					}
					for (var i = 0; i < tagTiddler.tags.length; i++) {
						reminderTags[reminderTags.length] = tagTiddler.tags[i];
					}
				}
			}
			reminderTags = (params["tag"]) ? (reminderTags ? reminderTags.concat(params["tag"].readBracketedList()) : params["tag"].readBracketedList()) : reminderTags;
			reminderTags = (tiddler && tiddler.tags) ? (reminderTags ? reminderTags.concat(tiddler.tags) : tiddler.tags) : reminderTags;
		}
		
		if (reminderTags) {
			for (var i = 0; i < reminderTags.length; i++) {
				if (tagStyles[reminderTags[i]] || (tagStyles[reminderTags[i]] === "")) {
					var cssRegExp = new RegExp(config.textPrimitives.cssLookahead, "mg");
					var nextMatch = 0;
					var lastIndex = 0;
					var lookaheadMatch = cssRegExp.exec(formattedTitle);
					if (lookaheadMatch) {
						do {
							lastIndex  = cssRegExp.lastIndex;
							nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
							cssRegExp.lastIndex = nextMatch;
							lookaheadMatch = cssRegExp.exec(formattedTitle);
						} while (lookaheadMatch && (lookaheadMatch.index == nextMatch))
					}
					paramsTitle = paramsTitle.substring(0, formattedTitleStart) + tagStyles[reminderTags[i]] + paramsTitle.substring(lastIndex + formattedTitleStart);
					params["title"] = paramsTitle;
					break;
				}
			}
		}
	}
}
//}}}
//{{{
function addRemindersToPopup(p,when,format,tags)
{
	if (config.options.chkDatePopupHideReminders) return false;
	if(window.findTiddlersWithReminders==undefined) return; // reminder plugin not installed

	var indent = String.fromCharCode(160)+String.fromCharCode(160);
	var reminders=findTiddlersWithReminders(when, [0,31],tags ? tags.filter : null,1);
	createTiddlyElement(p,'div',null,null,((tags && tags.params && tags.params["remindersLabel"]) ? tags.params["remindersLabel"] : 'reminders') + ' ('+(reminders.length||((tags && tags.params && tags.params["noRemindersLabel"]) ? tags.params["noRemindersLabel"] : 'none'))+')');
	for(var t=0; t<reminders.length; t++) {
		var link;

		reminders[t]["params"]["format"] = (tags && tags.params && tags.params["remindersFormat"]) ? tags.params["remindersFormat"] : "DIFF - TITLE";
		var title = getReminderMessageForDisplay(reminders[t]["diff"], reminders[t]["params"], reminders[t]["matchedDate"], reminders[t]["tiddler"]);

		var goToTiddlerLengh =  "<<goToTiddler".length;
		var goToTiddlerMacroIndex = title.indexOf("<<goToTiddler");
		goToTiddlerLengh = (goToTiddlerMacroIndex == -1) ?  "<<newTiddler".length : goToTiddlerLengh;
		goToTiddlerMacroIndex = (goToTiddlerMacroIndex == -1) ? title.indexOf("<<newTiddler") : goToTiddlerMacroIndex;
		if (goToTiddlerMacroIndex > -1) {
			link = title.substring(goToTiddlerMacroIndex + goToTiddlerLengh, title.indexOf(">>", goToTiddlerMacroIndex) + ">>".length);
		} else {
			link = createTiddlyLink(createTiddlyElement(p,'li'),reminders[t].tiddler,false);
		}
		var txt="";
		if (title) {
			var wikifier = new Wikifier(title, formatter, null, null);
			var e = createTiddlyElement(document.body, "div");
			var jqe = jQuery(e);
			jqe.css("display", "none");
			wikifier.subWikify(e);
			txt = jqe.text();
			removeNode(e);
		} else {
			txt = reminders[t].tiddler;
		}
		if (goToTiddlerMacroIndex > -1) {
			wikify("<<goToTiddler " + ("label:[[%0]]").format(indent + txt) + link, createTiddlyElement(p,'li'));
		} else {
			link.appendChild(document.createTextNode(indent+txt));
		}
	}
	return;
	if (readOnly) return;	// readonly... omit 'new reminder...' command
	var rem='\\<\\<reminder day:%0 month:%1 year:%2 title:"Enter a reminder title here"\\>\\>';
	rem=rem.format([when.getDate(),when.getMonth()+1,when.getFullYear()]);
	var cmd="<<newTiddler label:[["+indent+"new reminder...]] prompt:[[add a reminder to '%0']]"
		+" title:[[%0]] text:{{var t=store.getTiddlerText('%0','');t+(t.length?'\\n':'')+'%1'}} tag:%2>>";
	wikify(cmd.format([when.formatString(format),rem,config.options.txtCalendarReminderTags||'']),
		createTiddlyElement(p,'li'));
}
//}}}
//{{{
var code = eval("showDate").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; i < 1; i++) {
	startPosition = code.indexOf("link.linkformat", startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition) + ' link.tags = tags; ' + code.substring(startPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval("showDate = function showDate(place,date,mode,format,linkformat,autostyle,weekend,tags" + code.substring(newCode.indexOf(")")));
}

var code = eval("addTaggedToPopup").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; i < 1; i++) {
	startPosition = code.indexOf("getTaggedTiddlers", startPosition);
	if (startPosition > -1) {
		var endPosition = code.indexOf(");", startPosition);
		if (endPosition > -1) {
			newCode = code.substring(0, startPosition) + "getMatchingTiddlers(when.formatString(format) + (tags && tags.filter ? ' AND (' + tags.filter + ')' : '')" + code.substring(endPosition);
			code = newCode;
		}
	}
}
var startPosition = code.indexOf("createTiddlyElement", startPosition);
if (startPosition > -1) {
	var endPosition = code.indexOf(";", startPosition);
	if (endPosition > -1) {
		newCode = code.substring(0, startPosition) + "createTiddlyElement(p,'div',null,null,((tags && tags.params && tags.params['taggedLabel']) ? tags.params['taggedLabel'] : 'tagged') + ' ('+tagged.length+')')" + code.substring(endPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval("addTaggedToPopup = function addTaggedToPopup(p,when,format,tags" + newCode.substring(newCode.indexOf(")")));
}
//}}}
[[DateTime Data]]: delete or rename the tiddler to clear all settings

<html>
<table border="0" cellspacing="0" cellpadding="0" align="center" style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse; text-align: center;">
<tr style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenyear" size="4" maxlength="4" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">-</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenmonth" size="3" maxlength="3" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">-</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenday" size="3" maxlength="3" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenhours" size="3" maxlength="3" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">:</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenminutes" size="3" maxlength="3" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">:</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenseconds" size="3" maxlength="3" /></td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">.</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddenmilliseconds" size="4" maxlength="4" /></td>
</tr>
<tr style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">YYYY</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">M</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">D</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">H</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">M</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">S</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">millisec</td>
</tr>
</table>
<br />
<table border="0" cellspacing="0" cellpadding="0" style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">
<tr style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">Time Zone Offset:&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><input type="text" id="overriddentimezoneoffset" size="4" maxlength="4" />&nbsp;minutes (UTC-5 would be 300)</td>
</tr>
<tr style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;">Time Zone:&nbsp;</td>
<td style="border: 0px; padding: 0px; border-spacing: 0; border-collapse: collapse;"><select id="overriddentimezone">
<option value=""></option>
<option value="ET">Eastern Time (US & Canada)</option>
</select></td>
</tr>
</table>
</html>
<script>

var dateTimeTiddlerName = "DateTime Data";
var value = store.getValue(dateTimeTiddlerName, "overriddenyear");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenyear").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenmonth");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenmonth").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenday");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenday").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenhours");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenhours").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenminutes");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenminutes").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenseconds");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenseconds").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddenmilliseconds");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddenmilliseconds").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddentimezoneoffset");
if (!isNaN(parseFloat(value)) && isFinite(value)) $("overriddentimezoneoffset").value = value;
value = store.getValue(dateTimeTiddlerName, "overriddentimezone");
if (value) jQuery("#overriddentimezone").val(value);

</script>
<script label="set">

store.suspendNotifications();

var dateTimeTiddlerName = "DateTime Data";
if (!store.getTiddler(dateTimeTiddlerName)) {
	store.saveTiddler(dateTimeTiddlerName, dateTimeTiddlerName, "", config.options.txtUserName, new Date(), []);
}
var value = $("overriddenyear").value;
store.setValue(dateTimeTiddlerName, "overriddenyear", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Math.abs(Number(value)) | 0) : "");
value = $("overriddenmonth").value;
store.setValue(dateTimeTiddlerName, "overriddenmonth", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddenday").value;
store.setValue(dateTimeTiddlerName, "overriddenday", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddenhours").value;
store.setValue(dateTimeTiddlerName, "overriddenhours", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddenminutes").value;
store.setValue(dateTimeTiddlerName, "overriddenminutes", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddenseconds").value;
store.setValue(dateTimeTiddlerName, "overriddenseconds", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddenmilliseconds").value;
store.setValue(dateTimeTiddlerName, "overriddenmilliseconds", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = $("overriddentimezoneoffset").value;
store.setValue(dateTimeTiddlerName, "overriddentimezoneoffset", (!isNaN(parseFloat(value)) && isFinite(value)) ? "" + (Number(value) | 0) : "");
value = jQuery("#overriddentimezone").val();
store.setValue(dateTimeTiddlerName, "overriddentimezone", "" + value);
store.resumeNotifications();
story.refreshTiddler("Date and Time Settings", null, true);
store.notifyAll();
displayMessage("The date and time settings have been applied");

</script>
/***
|Name|DcTableOfContentsPlugin|
|Author|[[Doug Compton|http://www.zagware.com/tw/plugins.html#DcTableOfContentsPlugin]]|
|Contributors|[[Lewcid|http://lewcid.org]], [[FND|http://devpad.tiddlyspot.com]], [[ELS|http://www.tiddlytools.com]]|
|Source|[[FND's DevPad|http://devpad.tiddlyspot.com#DcTableOfContentsPlugin]]|
|Version|0.4.1|
|~CoreVersion|2.2|
<<showtoc>>
!Description
This macro will insert a table of contents reflecting the headings that are used in a tiddler and will be automatically updated when you make changes.  Each item in the table of contents can be clicked on to jump to that heading.  It can be used either inside of select tiddlers or inside a system wide template.

A parameter can be used to show the table of contents of a seperate tiddler, &lt;<showtoc tiddlerTitle>&gt;

It will also place a link beside each header which will jump the screen to the top of the current tiddler.  This will only be displayed if the current tiddler is using the &lt;<showtoc>&gt; macro.

The appearance of the table of contents and the link to jump to the top can be modified using CSS.  An example of this is given below.

!Usage
!!Only in select tiddlers
The table of contents above is an example of how to use this macro in a tiddler.  Just insert &lt;<showtoc>&gt; in a tiddler on a line by itself.

It can also display the table of contents of another tiddler by using the macro with a parameter, &lt;<showtoc tiddlerTitle>&gt;
!!On every tiddler
It can also be used in a template to have it show on every tiddler.  An example ViewTemplate is shown below.

//{{{
<div class='toolbar' macro='toolbar -closeTiddler closeOthers +editTiddler permalink references jump'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'>Created <span macro='view created date DD-MM-YY'></span>, updated <span macro='view modified date DD-MM-YY'></span></div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class="toc" macro='showtoc'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
//}}}

!Examples
If you had a tiddler with the following headings:
{{{
!Heading1a
!!Heading2a
!!Heading2b
!!!Heading3
!Heading1b
}}}
this table of contents would be automatically generated:
* Heading1a
** Heading2a
** Heading2b
*** Heading3
* Heading1b
!Changing how it looks
To modifiy the appearance, you can use CSS similiar to the below.
//{{{
.dcTOC ul {
	color: red;
	list-style-type: lower-roman;
}
.dcTOC a {
	color: green;
	border: none;
}

.dcTOC a:hover {
	background: white;
	border: solid 1px;
}
.dcTOCTop {
	font-size: 2em;
	color: green;
}
//}}}

!Revision History
!!v0.1.0 (2006-04-07)
* initial release
!!v0.2.0 (2006-04-10)
* added the [top] link on headings to jump to the top of the current tiddler
* appearance can now be customized using CSS
* all event handlers now return false
!!v0.3.0 (2006-04-12)
* added the ability to show the table of contents of a seperate tiddler
* fixed an error when a heading had a ~WikiLink in it
!!v0.3.5 (2007-10-16)
* updated formatter object for compatibility with TiddlyWiki v2.2 (by Lewcid)
!!v0.4.0 (2007-11-14)
* added toggle button for collapsing/expanding table of contents element
* refactored documentation
!To Do
* code sanitizing/rewrite
* documentation refactoring
* use shadow tiddler for styles
!Code
***/
//{{{

version.extensions.DcTableOfContentsPlugin= {
	major: 0, minor: 4, revision: 0,
	type: "macro",
	source: "http://devpad.tiddlyspot.com#DcTableOfContentsPlugin"
};

// Replace heading formatter with our own
for (var n=0; n<config.formatters.length; n++) {
	var format = config.formatters[n];
	if (format.name == 'heading') {
		format.handler = function(w) {
			// following two lines is the default handler
			var e = createTiddlyElement(w.output, "h" + w.matchLength);
			w.subWikifyTerm(e, this.termRegExp); //updated for TW 2.2+

			// Only show [top] if current tiddler is using showtoc
			if (w.tiddler && w.tiddler.isTOCInTiddler == 1) {
				// Create a container for the default CSS values
				var c = createTiddlyElement(e, "div");
				c.setAttribute("style", "font-size: 0.5em; color: blue;");
				// Create the link to jump to the top
				createTiddlyButton(c, " [top]", "Go to top of tiddler", window.scrollToTop, "dcTOCTop", null, null);
			}
		}
		break;
	}
}

config.macros.showtoc = {
	handler: function(place, macroName, params, wikifier, paramString, tiddler) {
		var text = "";
		var title = "";
		var myTiddler = null;

		// Did they pass in a tiddler?
		if (params.length) {
			title = params[0];
			myTiddler = store.getTiddler(title);
		} else {
			myTiddler = tiddler;
		}

		if (myTiddler == null) {
			wikify("ERROR: Could not find " + title, place);
			return;
		}

		var lines = myTiddler .text.split("\n");
		myTiddler.isTOCInTiddler = 1;

		// Create a parent container so the TOC can be customized using CSS
		var r = createTiddlyElement(place, "div", null, "dcTOC");
		// create toggle button
		createTiddlyButton(r, "toggle", "show/collapse table of contents",
			function() { config.macros.showtoc.toggleElement(this.nextSibling); },
			"toggleButton")
		// Create a container so the TOC can be customized using CSS
		var c = createTiddlyElement(r, "div");

		if (lines != null) {
			for (var x=0; x<lines.length; x++) {
				var line = lines[x];
				if (line.substr(0,1) == "!") {
					// Find first non ! char
					for (var i=0; i<line.length; i++) {
						if (line.substr(i, 1) != "!") {
							break;
						}
					}
					var desc = line.substring(i);
					// Remove WikiLinks
					desc = desc.replace(/\[\[/g, "");
					desc = desc.replace(/\]\]/g, "");

					text += line.substr(0, i).replace(/[!]/g, '*');
					text += '<html><a href="javascript:;" onClick="window.scrollToHeading(\'' + title + '\', \'' + desc+ '\', event)">' + desc+ '</a></html>\n';
				}
			}
		}
		wikify(text, c);
	}
}

config.macros.showtoc.toggleElement = function(e) {
	if(e) {
		if(e.style.display != "none") {
			e.style.display = "none";
		} else {
			e.style.display = "";
		}
	}
};

window.scrollToTop = function(evt) {
	if (! evt)
		var evt = window.event;

	var target = resolveTarget(evt);
	var tiddler = story.findContainingTiddler(target);

	if (! tiddler)
		return false;

	window.scrollTo(0, ensureVisible(tiddler));

	return false;
};

window.scrollToHeading = function(title, anchorName, evt) {
	var tiddler = null;

	if (! evt)
		var evt = window.event;

	if (title) {
		story.displayTiddler(store.getTiddler(title), title, null, false);
		tiddler = document.getElementById(story.idPrefix + title);
	} else {
		var target = resolveTarget(evt);
		tiddler = story.findContainingTiddler(target);
	}

	if (tiddler == null)
		return false;
	
	var children1 = tiddler.getElementsByTagName("h1");
	var children2 = tiddler.getElementsByTagName("h2");
	var children3 = tiddler.getElementsByTagName("h3");
	var children4 = tiddler.getElementsByTagName("h4");
	var children5 = tiddler.getElementsByTagName("h5");

	var children = new Array();
	children = children.concat(children1, children2, children3, children4, children5);

	for (var i = 0; i < children.length; i++) {
		for (var j = 0; j < children[i].length; j++) {
			var heading = children[i][j].innerHTML;

			// Remove all HTML tags
			while (heading.indexOf("<") >= 0) {
				heading = heading.substring(0, heading.indexOf("<")) + heading.substring(heading.indexOf(">") + 1);
			}

			// Cut off the code added in showtoc for TOP
			heading = heading.substr(0, heading.length-6);

			if (heading == anchorName) {
				var y = findPosY(children[i][j]);
				window.scrollTo(0,y);
				return false;
			}
		}
	}
	return false
};
//}}}
//{{{
window.scrollToHeading = function(title, anchorName, evt) {
	var tiddler = null;

	if (! evt)
		var evt = window.event;

	if (title) {
		story.displayTiddler(store.getTiddler(title), title, null, false);
		tiddler = document.getElementById(story.idPrefix + title);
	} else {
		var target = resolveTarget(evt);
		tiddler = story.findContainingTiddler(target);
	}

	if (tiddler == null)
		return false;
	
	var children1 = tiddler.getElementsByTagName("h1");
	var children2 = tiddler.getElementsByTagName("h2");
	var children3 = tiddler.getElementsByTagName("h3");
	var children4 = tiddler.getElementsByTagName("h4");
	var children5 = tiddler.getElementsByTagName("h5");

	var children = new Array();
	children = children.concat(children1, children2, children3, children4, children5);

	for (var i = 0; i < children.length; i++) {
		for (var j = 0; j < children[i].length; j++) {
			var heading = children[i][j].innerHTML;

			// Remove all HTML tags
			if (heading.indexOf("<") >= 0) {
				heading = heading.substring(0, heading.indexOf("<"));
			}

			if (heading == anchorName) {
				var y = findPosY(children[i][j]);
				window.scrollTo(0,y);
				return false;
			}
		}
	}
	return false
};
//}}}
//{{{
for (var n=0; n<config.formatters.length; n++) {
	var format = config.formatters[n];
	if (format.name == 'heading') {
		var code = format.handler.toString();
		var newCode = null;
		var startPosition = code.indexOf("createTiddlyButton");
		if (startPosition > -1) {
			startPosition = code.indexOf(" [top]", startPosition);
			if (startPosition > -1) {
				var endPosition = code.indexOf('tiddler', startPosition);
				if (endPosition > -1) {
					newCode = code.substring(0, startPosition - 1) + ' " [^]", null ' + code.substring(endPosition + 'tiddler'.length + 1);
					code = newCode;
				}
			}
		}
		if (newCode != null) {
			eval("format.handler = function formatHandler" + newCode.substring(newCode.indexOf("(")));
		}
		break;
	}
}

var code = eval("config.macros.showtoc.handler").toString();
var newCode = null;
var startPosition = code.indexOf("createTiddlyButton");
if (startPosition > -1) {
	startPosition = code.indexOf("toggle", startPosition);
	if (startPosition > -1) {
		var endPosition = code.indexOf('contents', startPosition);
		if (endPosition > -1) {
			newCode = code.substring(0, startPosition - 1) + ' "+/-", null ' + code.substring(endPosition + 'contents'.length + 1);
			code = newCode;
		}
	}
}
var startPosition = code.indexOf("desc");
if (startPosition > -1) {
	startPosition = code.indexOf(";", startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition + 1) + ' if (desc.indexOf("~") == 0) continue; ' + code.substring(startPosition + 1);
		code = newCode;
	}
}
if (newCode != null) {
	eval("config.macros.showtoc.handler = function showtocHandler" + newCode.substring(newCode.indexOf("(")));
}
//}}}
TiddlyWiki FireFox TiddlyTools TiddlyTech HowTo $1
/***
|Name|DisableWikiLinksPlugin|
|Source|http://www.TiddlyTools.com/#DisableWikiLinksPlugin|
|Version|1.6.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|selectively disable TiddlyWiki's automatic ~WikiWord linking behavior|
This plugin allows you to disable TiddlyWiki's automatic ~WikiWord linking behavior, so that WikiWords embedded in tiddler content will be rendered as regular text, instead of being automatically converted to tiddler links.  To create a tiddler link when automatic linking is disabled, you must enclose the link text within {{{[[...]]}}}.
!!!!!Usage
<<<
You can block automatic WikiWord linking behavior for any specific tiddler by ''tagging it with<<tag excludeWikiWords>>'' (see configuration below) or, check a plugin option to disable automatic WikiWord links to non-existing tiddler titles, while still linking WikiWords that correspond to existing tiddlers titles or shadow tiddler titles.  You can also block specific selected WikiWords from being automatically linked by listing them in [[DisableWikiLinksList]] (see configuration below), separated by whitespace.  This tiddler is optional and, when present, causes the listed words to always be excluded, even if automatic linking of other WikiWords is being permitted.  

Note: WikiWords contained in default ''shadow'' tiddlers will be automatically linked unless you select an additional checkbox option lets you disable these automatic links as well, though this is not recommended, since it can make it more difficult to access some TiddlyWiki standard default content (such as AdvancedOptions or SideBarTabs)
<<<
!!!!!Configuration
<<<
<<option chkDisableWikiLinks>> Disable ALL automatic WikiWord tiddler links
<<option chkAllowLinksFromShadowTiddlers>> ... except for WikiWords //contained in// shadow tiddlers
<<option chkDisableNonExistingWikiLinks>> Disable automatic WikiWord links for non-existing tiddlers
Disable automatic WikiWord links for words listed in: <<option txtDisableWikiLinksList>>
Disable automatic WikiWord links for tiddlers tagged with: <<option txtDisableWikiLinksTag>>
<<<
!!!!!Revisions
<<<
2008.07.22 [1.6.0] hijack tiddler changed() method to filter disabled wiki words from internal links[] array (so they won't appear in the missing tiddlers list)
2007.06.09 [1.5.0] added configurable txtDisableWikiLinksTag (default value: "excludeWikiWords") to allows selective disabling of automatic WikiWord links for any tiddler tagged with that value.
2006.12.31 [1.4.0] in formatter, test for chkDisableNonExistingWikiLinks
2006.12.09 [1.3.0] in formatter, test for excluded wiki words specified in DisableWikiLinksList
2006.12.09 [1.2.2] fix logic in autoLinkWikiWords() (was allowing links TO shadow tiddlers, even when chkDisableWikiLinks is TRUE).  
2006.12.09 [1.2.1] revised logic for handling links in shadow content
2006.12.08 [1.2.0] added hijack of Tiddler.prototype.autoLinkWikiWords so regular (non-bracketed) WikiWords won't be added to the missing list
2006.05.24 [1.1.0] added option to NOT bypass automatic wikiword links when displaying default shadow content (default is to auto-link shadow content)
2006.02.05 [1.0.1] wrapped wikifier hijack in init function to eliminate globals and avoid FireFox 1.5.0.1 crash bug when referencing globals
2005.12.09 [1.0.0] initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.DisableWikiLinksPlugin= {major: 1, minor: 6, revision: 0, date: new Date(2008,7,22)};

if (config.options.chkDisableNonExistingWikiLinks==undefined) config.options.chkDisableNonExistingWikiLinks= false;
if (config.options.chkDisableWikiLinks==undefined) config.options.chkDisableWikiLinks=false;
if (config.options.txtDisableWikiLinksList==undefined) config.options.txtDisableWikiLinksList="DisableWikiLinksList";
if (config.options.chkAllowLinksFromShadowTiddlers==undefined) config.options.chkAllowLinksFromShadowTiddlers=true;
if (config.options.txtDisableWikiLinksTag==undefined) config.options.txtDisableWikiLinksTag="excludeWikiWords";

// find the formatter for wikiLink and replace handler with 'pass-thru' rendering
initDisableWikiLinksFormatter();
function initDisableWikiLinksFormatter() {
	for (var i=0; i<config.formatters.length && config.formatters[i].name!="wikiLink"; i++);
	config.formatters[i].coreHandler=config.formatters[i].handler;
	config.formatters[i].handler=function(w) {
		// supress any leading "~" (if present)
		var skip=(w.matchText.substr(0,1)==config.textPrimitives.unWikiLink)?1:0;
		var title=w.matchText.substr(skip);
		var exists=store.tiddlerExists(title);
		var inShadow=w.tiddler && store.isShadowTiddler(w.tiddler.title);
		// check for excluded Tiddler
		if (w.tiddler && w.tiddler.isTagged(config.options.txtDisableWikiLinksTag))
			{ w.outputText(w.output,w.matchStart+skip,w.nextMatch); return; }
		// check for specific excluded wiki words
		var t=store.getTiddlerText(config.options.txtDisableWikiLinksList);
		if (t && t.length && t.indexOf(w.matchText)!=-1)
			{ w.outputText(w.output,w.matchStart+skip,w.nextMatch); return; }
		// if not disabling links from shadows (default setting)
		if (config.options.chkAllowLinksFromShadowTiddlers && inShadow)
			return this.coreHandler(w);
		// check for non-existing non-shadow tiddler
		if (config.options.chkDisableNonExistingWikiLinks && !exists)
			{ w.outputText(w.output,w.matchStart+skip,w.nextMatch); return; }
		// if not enabled, just do standard WikiWord link formatting
		if (!config.options.chkDisableWikiLinks)
			return this.coreHandler(w);
		// just return text without linking
		w.outputText(w.output,w.matchStart+skip,w.nextMatch)
	}
}

Tiddler.prototype.coreAutoLinkWikiWords = Tiddler.prototype.autoLinkWikiWords;
Tiddler.prototype.autoLinkWikiWords = function()
{
	// if all automatic links are not disabled, just return results from core function
	if (!config.options.chkDisableWikiLinks)
		return this.coreAutoLinkWikiWords.apply(this,arguments);
	return false;
}

Tiddler.prototype.disableWikiLinks_changed = Tiddler.prototype.changed;
Tiddler.prototype.changed = function()
{
	this.disableWikiLinks_changed.apply(this,arguments);
	// remove excluded wiki words from links array
	var t=store.getTiddlerText(config.options.txtDisableWikiLinksList,"").readBracketedList();
	if (t.length) for (var i=0; i<t.length; i++)
		if (this.links.contains(t[i]))
			this.links.splice(this.links.indexOf(t[i]),1);
};
//}}}
//{{{
for (var i = 0; i < config.formatters.length; i++) {
	if (config.formatters[i].name == "image") {
		config.textPrimitives.imageLink = config.formatters[i].lookaheadRegExp.source;
		break;
	}
}

config.textPrimitives.tiddlerForcedLinkAndImageRegExp = config.textPrimitives.imageLink ? new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
	config.textPrimitives.brackettedLink + ")|(?:" +
	config.textPrimitives.urlPattern + ")|(?:" +
	config.textPrimitives.imageLink + ")","mg") : config.textPrimitives.tiddlerForcedLinkRegExp;
config.textPrimitives.tiddlerAnyLinkAndImageRegExp = config.textPrimitives.imageLink ? new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
	config.textPrimitives.titledBrackettedLink + ")|(?:" +
	config.textPrimitives.brackettedLink + ")|(?:" +
	config.textPrimitives.urlPattern + ")|(?:" +
	config.textPrimitives.imageLink + ")","mg") : config.textPrimitives.tiddlerAnyLinkRegExp;
//}}}
//{{{
var code = eval(((typeof DataTiddler != "undefined") && DataTiddler.originalTiddlerChangedFunction) ? "DataTiddler.originalTiddlerChangedFunction" : "Tiddler.prototype.disableWikiLinks_changed").toString();
var newCode = null;
var startPosition = code.indexOf("config.textPrimitives.tiddlerAnyLinkRegExp");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition) + "config.textPrimitives.tiddlerAnyLinkAndImageRegExp" +  code.substring(startPosition +  "config.textPrimitives.tiddlerAnyLinkRegExp".length);
	code = newCode;
}
var startPosition = code.indexOf("config.textPrimitives.tiddlerForcedLinkRegExp");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition) + "config.textPrimitives.tiddlerForcedLinkAndImageRegExp" +  code.substring(startPosition +  "config.textPrimitives.tiddlerForcedLinkRegExp".length);
	code = newCode;
}
var startPosition = code.lastIndexOf("else ");
if (startPosition > -1) {
	startPosition = code.indexOf("tiddlerLinkRegExp", startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition)
				+ " else if (formatMatch[8 - t]) { "
					+ "if (!config.formatterHelpers.isExternalLink(formatMatch[8 - t])) { "
						+ "this.links.pushUnique(formatMatch[8 - t]); "
					+ "} "
					+ "if (formatMatch[9 - t] && !config.formatterHelpers.isExternalLink(formatMatch[9 - t])) { "
						+ "this.links.pushUnique(formatMatch[9 - t]); "
					+ "} "
				+ "} "
				+ code.substring(startPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval((((typeof DataTiddler != "undefined") && DataTiddler.originalTiddlerChangedFunction) ? "DataTiddler.originalTiddlerChangedFunction = function originalTiddlerChangedFunction" : "Tiddler.prototype.disableWikiLinks_changed = function disableWikiLinks_changed") + code.substring(newCode.indexOf("(")));
}
//}}}
//{{{
window.determineGoodFriday = function(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound) {
	var easter = easterDate(baseDate.getFullYear());
	easter.setDate(easter.getDate() - 2);
	dateHash["year"] = easter.getFullYear();
	dateHash["month"] = easter.getMonth() + 1;
	dateHash["day"] = easter.getDate();
}

window.determineEasterSunday = function(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound) {
	var easter = easterDate(baseDate.getFullYear());
	dateHash["year"] = easter.getFullYear();
	dateHash["month"] = easter.getMonth() + 1;
	dateHash["day"] = easter.getDate();
}

window.determineEasterMonday = function(dateHash, baseDate, leadTimeLowerBound, leadTimeUpperBound) {
	var easter = easterDate(baseDate.getFullYear());
	easter.setDate(easter.getDate() + 1);
	dateHash["year"] = easter.getFullYear();
	dateHash["month"] = easter.getMonth() + 1;
	dateHash["day"] = easter.getDate();
}

// see http://www.assa.org.au/edm.html
/*
Sub EasterDate (d, m, y)

' EASTER DATE CALCULATION FOR YEARS 1583 TO 4099

' y is a 4 digit year 1583 to 4099
' d returns the day of the month of Easter
' m returns the month of Easter

' Easter Sunday is the Sunday following the Paschal Full Moon
' (PFM) date for the year

' This algorithm is an arithmetic interpretation of the 3 step
' Easter Dating Method developed by Ron Mallen 1985, as a vast
' improvement on the method described in the Common Prayer Book

' Because this algorithm is a direct translation of the
' official tables, it can be easily proved to be 100% correct

' This algorithm derives values by sequential inter-dependent
' calculations, so ... DO NOT MODIFY THE ORDER OF CALCULATIONS!

' The \ operator may be unfamiliar - it means integer division
' for example, 30 \ 7 = 4 (the remainder is ignored)

' All variables are integer data types

' It's free!  Please do not modify code or comments!
' ==========================================================

   Dim FirstDig, Remain19, temp    'intermediate results
   Dim tA, tB, tC, tD, tE          'table A to E results

   FirstDig = y \ 100              'first 2 digits of year
   Remain19 = y Mod 19             'remainder of year / 19

' calculate PFM date
   temp = (FirstDig - 15) \ 2 + 202 - 11 * Remain19
    
   Select Case FirstDig
      Case 21, 24, 25, 27 To 32, 34, 35, 38
         temp = temp - 1
      Case 33, 36, 37, 39, 40
         temp = temp - 2
   End Select
   temp = temp Mod 30

   tA = temp + 21
   If temp = 29 Then tA = tA - 1
   If (temp = 28 And Remain19 > 10) Then tA = tA - 1

'find the next Sunday
   tB = (tA - 19) Mod 7
    
   tC = (40 - FirstDig) Mod 4
   If tC = 3 Then tC = tC + 1
   If tC > 1 Then tC = tC + 1
        
   temp = y Mod 100
   tD = (temp + temp \ 4) Mod 7
    
   tE = ((20 - tB - tC - tD) Mod 7) + 1
   d = tA + tE

'return the date
   If d > 31 Then
      d = d - 31
      m = 4
   Else
      m = 3
   End If

End Sub
*/
function easterDate(y) {
	var FirstDig = Math.floor(y / 100);
	var Remain19 = y % 19;
	
	var temp = Math.floor((FirstDig - 15) / 2) + 202 - 11 * Remain19;
	
	switch (FirstDig) {
		case 21: case 24: case 25: case 27: case 28: case 29: case 30: case 31: case 32: case 34: case 35: case 38:
			temp = temp - 1;
			break;
		case 33: case 36: case 37: case 39: case 40:
			temp = temp - 2;
			break;
	}
	temp = temp % 30;
	
	var tA = temp + 21;
	if (temp == 29) { tA = tA - 1; }
	if ((temp == 28) && (Remain19 > 10)) { tA = tA - 1; }
	
	var tB = (tA - 19) % 7;
	
	var tC = (40 - FirstDig) % 4;
	if (tC == 3) { tC = tC + 1; }
	if (tC > 1) { tC = tC + 1; }
	
	temp = y % 100;
	var tD = (temp + Math.floor(temp / 4)) % 7;
	
	var tE = ((20 - tB - tC - tD) % 7) + 1;
	var d = tA + tE;
	
	var m;
	if (d > 31) {
		d = d - 31
		m = 4
	} else {
		m = 3
	}
	
	return new Date(y, m - 1, d);
}
//}}}
/***
|Name|[[EditSectionPlugin]]|
|Source|http://www.TiddlyTools.com/#EditSectionPlugin|
|Documentation|http://www.TiddlyTools.com/#EditSectionPlugin|
|Version|1.8.1|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|invoke popup 'section editor' for specified section of a tiddler|
!!!!!Usage
<<<
{{{
<<editSection TiddlerName##sectionname label tooltip>>
}}}
This macro adds a command link that invokes a popup editor for a specific section, where:
*''~TiddlerName##sectionname'' specifies the tiddler and section you want to edit.
**If you omit the "##sectionname" portion (i.e., only enter "~TiddlerName"), the entire content of the indicated tiddler is edited.
**If you omit the "~TiddlerName" portion (i.e., only enter "##sectionname"), the current containing tiddler (if any) is assumed.
**Changing the section name in the popup editor //renames// that section within the tiddler.
**Changing the tiddler title in the popup editor //copies// that section to another tiddler.
**If the indicated tiddler and/or section does not yet exist, it will be created when you press 'save'.
*''label'' and ''tooltip'' (both //optional//) specify the link label and mouseover help text for the 'edit section' command link.
You can also add the following macro, //at the end of a tiddler//, to automatically add an 'edit section' command link for each section shown in the tiddler.
{{{
<<editSections label tooltip>>
}}}
*''label'' and ''tooltip'' (both //optional//) specify the link label and mouseover help text for the 'edit section' command link.
>//Note: when a document is viewed in 'readOnly' mode, both of these macros are bypassed and no output is produced.//
<<<
!!!!!Sample
<<<
This is an example section for you to try
<<<
!!!!!Example
<<<
{{{
<<editSection ##Sample>>
}}}
<<editSection ##Sample>>
<<<
!!!!!Configuration
<<<
To customize and/or translate the HTML form layout used to render the section editor, edit the [[EditSectionTemplate]] shadow tiddler.
<<<
!!!!!Revisions
<<<
2012.01.29 1.8.1 invoke autoSaveChanges() when tiddlers are modified.
2011.12.22 1.8.0 added {{{<<editSections>>}}} macro for automatic adding of 'edit section...' links to headings
2011.12.20 1.7.0 added drag handling for editor panels
2011.10.28 1.6.8 fixed getMX()/getMY() for Chrome scroll offset handling
2011.09.02 1.6.7 more refactoring and cleanup of default form init/save handlers
2011.08.02 1.6.6 major code refactor to allow customization of form handling for type-specific [[PasteUpHelperPlugin]] extensions
2011.07.30 1.6.5 in removePanel(), call Popup.remove() so 'child' popups are closed when panel is closed
2011.07.24 1.6.4 refactored save() to provide updateTiddler() entry point for use with PasteUpHelperPlugin 'quickmenu' commands. Added getMX() and getMY() for cross-browser mouse coordinates
2011.06.05 1.6.3 added TiddlySpace cloneFields() to delete and save handlers so editing sections automatically copies/owns an included tiddler
2011.05.05 1.6.2 renamed delete() to deleteSection() to avoid javascript keyword errors
2011.05.04 1.6.1 in delete(), use removeTiddler() for proper notification handling
2011.05.01 1.6.0 added delete() functionality
2011.01.09 1.5.1 in handler(), don't render command link if document is readOnly
2010.12.24 1.5.0 replace use of core Popups with custom panel handling (bypass core interactions and errors)
2010.11.07 1.4.0 in popup(), render HTML form from EditSectionTemplate, and then applyHtmlMacros() to render wiki-syntax macro-generated form elements (e.g., {{{<<option>>, <<select>>}}}, etc.).  Also, refactored form init/save handling to add customization 'hooks'
2010.10.25 1.3.0 added support for editing complete tiddlers using "~TiddlerName" syntax (omit "##sectionname")
2010.07.15 1.2.0 added confirmation when section rename will overwrite other section
2010.07.13 1.1.1 added 'dirty flag' confirmation to popup handling to avoid discarding unsaved changes
2010.07.11 1.1.0 fixed handling for creating new sections in existing tiddlers. Copied StickyPopupPlugin to eliminate dependency. Added Popup.showHere()
2010.05.24 1.0.2 in save(), escape regexp chars in section titles (e.g. "!SectionNameWith?InIt")
2009.09.07 1.0.1 documentation/code cleanup
2009.09.01 1.0.0 initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.EditSectionPlugin= {major: 1, minor: 8, revision: 1, date: new Date(2012,1,29)};

config.macros.editSection = {
	label: 'edit section...',
	tip: 'edit %0',
	sectionfmt: '{{hidden{\n!%0\n%1\n!end\n}}}',
	sectionerr: 'Invalid tiddler/section name: %0',
	newtiddlertxt: '',
	discardmsg: 'Discard unsaved changes for %0?',
	deletemsg: 'Are you sure you want to delete %0?',
	overwritemsg: '%0##%2 already exists. Choose OK to overwrite it',
	template: 'EditSectionTemplate', // DEFAULT FORM TEMPLATE
//}}}
// // PLUGIN INITIALIZATION
//{{{
	init: function() {
		// SHADOW PAYLOAD FOR DEFAULT EditSectionTemplate FORM DEFINITION
		config.shadowTiddlers[this.template]
			=store.getTiddlerText('EditSectionPlugin##HTML','');

		// CLOSE PANELS IF CLICK on other than POPUP OR EDITOR PANEL
		addEvent(document,'click',function(ev) {
			var p=resolveTarget(ev||window.event);
			while (p) {
				if (hasClass(p,'editSectionPanel')) break;
				if (hasClass(p,'popup')) break;
				p=p.parentNode;
			}
			if (!p) config.macros.editSection.removeAllPanels();
			return true;
		});

		// HIJACK QuickEditPlugin's getField() to support use with editSectionPanel
		if (config.quickEdit) {
			config.quickEdit.getTiddlerField=config.quickEdit.getField;
			config.quickEdit.getField=function(where) {
				var e=where; while(e) {	if (hasClass(e,'editSectionPanel')) break; e=e.parentNode; }
				return e?e.getElementsByTagName('textarea')[0]:this.getTiddlerField(where);
			}

		}
	},
//}}}
// // GENERAL UTILITIES
//{{{
	ok: function(ev) { var ev=ev||window.event;
		ev.cancelBubble=true;
		if(ev.stopPropagation)ev.stopPropagation();
		return false;
	},
	getMX: function(ev) { var ev=ev||window.event; // GET MOUSE X
		if (config.browser.isIE)	return ev.clientX+findScrollX();// IE
		if (config.userAgent.indexOf('chrome')!=-1) return ev.pageX;	// Chrome
		if (config.browser.isSafari) 	return ev.pageX+findScrollX(); 	// Webkit
		else				return ev.pageX;		// Firefox/other
	},
	getMY: function(ev) { var ev=ev||window.event; // GET MOUSE Y
		if (config.browser.isIE)	return ev.clientY+findScrollY();// IE
		if (config.userAgent.indexOf('chrome')!=-1) return ev.pageY;	// Chrome
		if (config.browser.isSafari) 	return ev.pageY+findScrollY();	// Webkit
		else				return ev.pageY;		// Firefox/other
	},
	cloneFields: function(fields) { // for TIDDLYSPACE compatibility
		var f=merge({},fields); // copy object
		if (f["server.workspace"]!=config.defaultCustomFields["server.workspace"]) {
			f=merge(f,config.defaultCustomFields); // overwrite with defaults
			f["server.permissions"] = "read, write, create, delete";
			delete f["server.page.revision"];
			delete f["server.title"];
			delete f["server.etag"];
		}
		return f;
	},
//}}}
// // MACRO/CLICK HANDLER
//{{{
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		if (readOnly) return;
		var here=story.findContainingTiddler(place);
		var tid=params[0];
		var label=params[1]||this.label.format([tid]);
		var tip=params[2]||this.tip.format([tid]);
		var btn=createTiddlyButton(place,label,tip,this.click);
		btn.setAttribute('tid',tid);
	},
	click: function(ev,type) { // note: optional 'type' is passed in from PasteUpPluginHelper
		var parts=this.getAttribute('tid').split('##');
		var title=parts[0]; var section=parts[1];
		var here=story.findContainingTiddler(this);
		if (!title&&here) title=here.getAttribute('tiddler');
		if (!title) return false;
		var tid=title; if (section&&section.length) tid=[title,section].join('##');
		return config.macros.editSection.createPanel(this,ev,tid,title,section,type);
	},
//}}}
// // EDITOR PANEL HANDLER
//{{{
	createPanel: function(here,ev,tid,title,section,type) {
		if (!this.removeAllPanels()) return this.ok(ev);
		var p=createTiddlyElement(document.body,"ol",
			"editSectionPanel","popup smallform editSectionPanel");
		p.root=here; 
		p.setAttribute('dirty',null);
		p.setAttribute('message',this.discardmsg.format([tid]));
		p.onmousedown=this.mousedown; p.style.cursor='move'; // for panel dragging
		p.innerHTML=store.getRecursiveTiddlerText(this.getForm(tid,type),'',10);
		applyHtmlMacros(p,store.getTiddler(title));
		var f=p.getElementsByTagName('form')[0];
		f.panel=p;
		f.title.value=title;
		f.section.value=section||'';
		f.init=this.getInitForm(tid,type);
		f.save=this.getSaveForm(tid,type);
		f.init(here,f,title,section,type);
		this.showPanel(p,here,ev);
		return this.ok(ev);
	},
	showPanel: function(p,here,ev) {
		var x=this.getMX(ev); var y=this.getMY(ev);
		var winw=findWindowWidth();
		var scrollw=winw-document.body.offsetWidth;
		if(p.offsetWidth>winw*0.75) p.style.width=winw*0.75 + "px";
		if(x+p.offsetWidth>winw-scrollw-1) x=winw-p.offsetWidth-scrollw-1;
		var s=p.style; s.left=x+'px'; s.top=y+'px'; s.display='block';
		if(config.options.chkAnimate && anim)	anim.startAnimating(new Scroller(p));
		else					window.scrollTo(0,ensureVisible(p));
	},
	removePanel: function(p) {
		Popup.remove(); // child popup (if any) is closed when panel is closed
		if (!p || p.getAttribute('dirty')!='true' || confirm(p.getAttribute('message')))
			{ if (p) removeNode(p); return true; }
		return false;
	},
	removeAllPanels: function() {
		var ok=true;
		jQuery('.editSectionPanel').each(function(index){
			var f=this.getElementsByTagName('form')[0];
			if (f.content) f.content.blur(); // force onchange (sets 'dirty' flag as needed)
			ok=config.macros.editSection.removePanel(this);
			return true;
		});
		return ok; // FALSE if panels remain
	},
//}}}
// // PANEL DRAG HANDLER
//{{{
	mousedown: function(ev) { ev=ev||window.event; // MOVE PANEL
		var cme=config.macros.editSection; // abbrev

		// ignore clickthrough from form fields, links, and images
		var target=resolveTarget(ev);
		if (['TEXTAREA','SELECT','INPUT','A','IMG'].contains(target.nodeName.toUpperCase()))
			return true;

		// GET TRACKING ELEMENT
		var track=this; // if 'capture' not supported, track in element only
		if (document.body.setCapture) var track=document.body; // IE
		if (window.captureEvents) var track=window; // moz
		if (!track.save_onmousemove) track.save_onmousemove=track.onmousemove;
		if (!track.save_onmouseup)   track.save_onmouseup  =track.onmouseup;
		if (!track.save_onkeyup)     track.save_onkeyup    =track.onkeyup;
		track.onmousemove=cme.dragmove;
		track.onmouseup	 =cme.dragup;
		track.onkeyup	 =cme.dragkeyup;
		// SAVE INITIAL POSITION
		track.elem=this;		// panel element
		track.start={
			X: cme.getMX(ev),  Y: cme.getMY(ev),	// mouse position
			T: this.offsetTop, L: this.offsetLeft,	// panel position
		}
		return cme.ok(ev);
	},
	dragmove: function(ev) { ev=ev||window.event; // MOVE PANEL
		var cme=config.macros.editSection; // abbrev
		// CAPTURE MOUSE EVENTS DURING DRAG
		if (document.body.setCapture) document.body.setCapture(); // IE
		if (window.captureEvents) window.captureEvents(Event.MouseMove|Event.MouseUp,true); // moz
		var e=this.elem; var s=e.style;
		var dX=cme.getMX(ev)-this.start.X;
		var dY=cme.getMY(ev)-this.start.Y;
		e.changed=e.changed||(Math.abs(dX)>1)||(Math.abs(dY)>1); // MINIMUM 2px MOVEMENT
		if (!e.changed) return cme.ok(ev);
		s.top =this.start.T+dY+'px';
		s.left=this.start.L+dX+'px';
		return cme.ok(ev);
	},
	dragkeyup: function(ev) { ev=ev||window.event; // STOP DRAG (ESC key)
		if (ev.keyCode==27) {
			var s=this.elem.style;
			s.top=this.start.T+'px';
			s.left=this.start.L+'px';
			return this.onmouseup(ev);
		}
	},
	dragup: function(ev) { ev=ev||window.event; // RELEASE MOUSE
		var cme=config.macros.editSection; // abbrev
		if (document.body.releaseCapture) document.body.releaseCapture(); // IE
		if (window.releaseEvents) window.releaseEvents(Event.MouseMove|Event.MouseUp); // moz
		this.onmousemove=this.save_onmousemove;
		this.onmouseup  =this.save_onmouseup;
		this.onkeyup    =this.save_onkeyup;
		return cme.ok(ev);
	},
//}}}
// // EDITOR FORM HANDLER
//{{{
	getForm:	function(tid,type) { return this.template; }, // see PasteUpHelperPlugin
	getInitForm:	function(tid,type) { return this.initForm; }, // see PasteUpHelperPlugin
	getSaveForm:	function(tid,type) { return this.saveForm; }, // see PasteUpHelperPlugin
	initForm: function(here,form,title,section,type) { // SET FORM CONTENT FROM TIDDLER SECTION
		var tid=title; if (section) tid=[title,section].join('##');
		form.newsection.value=tid; // target for output
		form.content.value=store.getTiddlerText(tid,'');
		if (version.extensions.TextAreaPlugin) new window.TextAreaResizer(form.content);
	},
	saveForm: function(here,ev) { // GET SECTION CONTENT FROM FORM (DEFAULT=TEXT CONTENT ONLY)
		var f=here.form;

		// GET TARGET TITLE/SECTION
		var tid=f.newsection.value;
		var parts=tid.split('##');
		var title=parts[0];
		var section=parts[1];
		var oldsection=f.section.value;
		var where=f.panel.root;
		if (!title) title=story.findContainingTiddler(where).getAttribute('tiddler');
		if (!title) {
			displayMessage(this.sectionerr.format([f.newsection.value]));
			f.newsection.focus(); f.newsection.select(); return false;
		}
		// CHECK FOR TIDDLER OVERWRITE
		if (!section && title!=f.title.value && store.tiddlerExists(title)) {
			if (!confirm(config.messages.overwriteWarning.format([title])))
				{ f.newsection.focus(); f.newsection.select(); return this.ok(ev); }

		}
		// WRITE TIDDLER CONTENT and CLOSE PANEL
		this.updateTiddler(f.content.value,title,section,oldsection);
		f.panel.setAttribute('dirty',null); this.removePanel(f.panel);
		return this.ok(ev);
	},
	changed: function(here,ev) {
		here.form.panel.setAttribute('dirty','true');
		return this.ok(ev);
	},
	cancel: function(here,ev) {
		this.removePanel(here.form.panel);
		return this.ok(ev);
	},
	remove: function(here,ev) {
		var f=here.form;
		var title=f.title.value;
		var section=f.section.value;
		var tid=title; if (section.length) tid=[title,section].join('##');
		var msg=this.deletemsg.format([tid]);
		if (!confirm(msg)) return this.ok(ev);
		this.deleteSection(title,section);
		f.panel.setAttribute('dirty',null);
		this.removePanel(f.panel);
		return this.ok(ev);
	},
//}}}
// // TIDDLER I/O
//{{{
	updateTiddler: function(txt,title,section,oldsection) {
		// GET (or CREATE) TIDDLER
		var t=store.getTiddler(title);
		var who =t&&config.options.chkForceMinorUpdate?t.modifier:config.options.txtUserName;
		var when=t&&config.options.chkForceMinorUpdate?t.modified:new Date();
		if (!t) {
			t=new Tiddler(); t.text=store.getTiddlerText(title,'');
			if (section&&!t.text.length) t.text=this.newtiddlertxt.format([title,section]);
		}
		// ADD/REVISE/RENAME SECTION CONTENT (if any)
		if (section) {
			// GET SECTION VALUES
			if (!oldsection) var oldsection=section;
			var oldval=store.getTiddlerText(title+'##'+oldsection); // previous section value
			var newval=txt; // revised section value
			var existingval=store.getTiddlerText(title+'##'+section); // existing section value
			// REVISE TIDDLER TEXT
			var txt=t.text; // default tiddler text = unchanged
			var pattern=new RegExp('(.*!{1,6})'+oldsection.escapeRegExp()+'\\n'
				+(oldval||'').escapeRegExp()+'((?:\\n!{1,6}|$).*)');
			var altpattern=this.sectionfmt.format([oldsection,oldval||'']);
			if (section!=oldsection && existingval) { // rename overwrites another section...
				if (!confirm(this.overwritemsg.format([title,section])))
					return this.ok(ev);
				txt=txt.replace(altpattern,''); // REMOVE old section (auto-generated)
				txt=txt.replace(pattern,'$2');  // REMOVE old section (generic format)
				// TARGET new section name and value
				pattern=new RegExp('(.*!{1,6})'+section.escapeRegExp()+'\\n'
					+existingval.escapeRegExp()+'((?:\\n!{1,6}|$).*)');
				oldval=existingval;
			}
			if (typeof oldval=="string") // section exists... update/rename it
				txt=txt.replace(pattern,'$1'+section+'\n'+newval+'$2');
			else // otherwise, append a new section to end of tiddler
				txt=txt+this.sectionfmt.format([section,newval]);
		}
		// SAVE TIDDLER
		var fields=this.cloneFields(t.fields);
		store.saveTiddler(title,title,txt,who,when,t.tags,fields);
		story.refreshTiddler(title,null,true);
		autoSaveChanges();
	},
	deleteSection: function(title,section) {
		// GET TIDDLER
		var t=store.getTiddler(title); if (!t) return;
		var who =t&&config.options.chkForceMinorUpdate?t.modifier:config.options.txtUserName;
		var when=t&&config.options.chkForceMinorUpdate?t.modified:new Date();
		if (!section) { // REMOVE TIDDLER
			store.removeTiddler(title);
		} else { // REMOVE SECTION FROM TIDDLER
			var val=store.getTiddlerText(title+'##'+section); // CURRENT SECTION VALUE
			if (typeof val=="string") { // section exists
				var txt=t.text; // default tiddler text = unchanged
				var pattern=new RegExp('(.*!{1,6})'+section.escapeRegExp()+'\\n'
					+(val||'').escapeRegExp()+'((?:\\n!{1,6}|$).*)');
				var altpattern=this.sectionfmt.format([section,val||'']);
				txt=txt.replace(altpattern,''); // REMOVE old section (auto-generated)
				txt=txt.replace(pattern,'$2');  // REMOVE old section (generic format)
				var fields=this.cloneFields(t.fields);
				store.saveTiddler(title,title,txt,who,when,t.tags,fields);
				story.refreshTiddler(title,null,true);
				autoSaveChanges();
			}
		}
	}
}
//}}}
// // EDIT SECTIONS MACRO
//{{{
config.macros.editSections = {
	label: 'edit...',
	tip: 'edit this section',
	command: '~~<<editSection [[##%0]] "%1" "%2">>~~',
	handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		if (readOnly) return;
		var elems=place.parentNode.getElementsByTagName("*");
		for (var i=0; i<elems.length; i++) { var e=elems[i]; // for each heading element
			if (!['H1','H2','H3','H4','H5'].contains(e.nodeName)) continue;
			var section=e.textContent;
			var label=(params[0]||this.label).format([section]);
			var tip  =(params[1]||this.tip  ).format([section]);
			wikify(this.command.format([section,label,tip]),e);
		}		
	}
}
//}}}
/***
//{{{
!HTML
<!--{{{-->
<!--
|Name|EditSectionTemplate|
|Source||
|Version||
|Author||
|License|http://www.TiddlyTools.com/#LegalStatements|
|Type|template|
|Requires|EditSectionPlugin|
|Description|popup editor form template used by EditSectionPlugin|
-->
<form action='javascript:;' style="white-space:nowrap">
<input type="hidden" name="title" value="">
<input type="hidden" name="section" value="">
<input type="text" name="newsection" value="" autocomplete="off" style="width:61%"
	onchange="return config.macros.editSection.changed(this,event);">
<input type=button value="save" style="width:12%"
	onclick="return config.macros.editSection.saveForm(this,event)">
<input type=button value="cancel" style="width:12%"
	onclick="return config.macros.editSection.cancel(this,event)">
<input type=button value="delete" style="width:12%"
	onclick="return config.macros.editSection.remove(this,event)">
<div macro="tiddler QuickEditToolbar"></div>
<textarea name="content" rows="15" cols="80" autocomplete="off"
	onchange="return config.macros.editSection.changed(this,event)"></textarea>
</form>
<!--}}}-->
!end
//}}}
***/
// //<<editSections "edit">> 
//{{{
config.macros.editSection.initOverride_base = config.macros.editSection.init;
config.macros.editSection.init = function () {
	config.macros.editSection.initOverride_base();
	config.shadowTiddlers[this.template] = store.getTiddlerText("TWNotesEditSectionTemplate", config.shadowTiddlers[this.template]);
}
//}}}
//{{{
config.macros.editSections.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if (readOnly) return;
	var elems=place.parentNode.getElementsByTagName("*");
	for (var i=0; i<elems.length; i++) { var e=elems[i]; // for each heading element
		if (!['H1','H2','H3','H4','H5'].contains(e.nodeName)) continue;
		var section=jQuery(e).clone()    // clone the element
		                     .children() // select all the children
		                     .remove()   // remove all the children
		                     .end()      // again go back to selected element
		                     .text();    // get the text of element   (http://viralpatel.net/blogs/2011/02/jquery-get-text-element-without-child-element.html)
		var label=(params[0]||this.label).format([section]);
		var tip  =(params[1]||this.tip  ).format([section]);
		wikify(this.command.format([section,label,tip]),e);
	}
}
//}}}
//{{{
config.macros.email = {
	unicode: {
		// http://www.lingua-systems.com/knowledge/unicode-mappings
		"iso-8859-2": {"00": 0x0000, "01": 0x0001, "02": 0x0002, "03": 0x0003, "04": 0x0004, "05": 0x0005, "06": 0x0006, "07": 0x0007, "08": 0x0008, "09": 0x0009, "0A": 0x000A, "0B": 0x000B, "0C": 0x000C, "0D": 0x000D, "0E": 0x000E, "0F": 0x000F, "10": 0x0010, "11": 0x0011, "12": 0x0012, "13": 0x0013, "14": 0x0014, "15": 0x0015, "16": 0x0016, "17": 0x0017, "18": 0x0018, "19": 0x0019, "1A": 0x001A, "1B": 0x001B, "1C": 0x001C, "1D": 0x001D, "1E": 0x001E, "1F": 0x001F, "20": 0x0020, "21": 0x0021, "22": 0x0022, "23": 0x0023, "24": 0x0024, "25": 0x0025, "26": 0x0026, "27": 0x0027, "28": 0x0028, "29": 0x0029, "2A": 0x002A, "2B": 0x002B, "2C": 0x002C, "2D": 0x002D, "2E": 0x002E, "2F": 0x002F, "30": 0x0030, "31": 0x0031, "32": 0x0032, "33": 0x0033, "34": 0x0034, "35": 0x0035, "36": 0x0036, "37": 0x0037, "38": 0x0038, "39": 0x0039, "3A": 0x003A, "3B": 0x003B, "3C": 0x003C, "3D": 0x003D, "3E": 0x003E, "3F": 0x003F, "40": 0x0040, "41": 0x0041, "42": 0x0042, "43": 0x0043, "44": 0x0044, "45": 0x0045, "46": 0x0046, "47": 0x0047, "48": 0x0048, "49": 0x0049, "4A": 0x004A, "4B": 0x004B, "4C": 0x004C, "4D": 0x004D, "4E": 0x004E, "4F": 0x004F, "50": 0x0050, "51": 0x0051, "52": 0x0052, "53": 0x0053, "54": 0x0054, "55": 0x0055, "56": 0x0056, "57": 0x0057, "58": 0x0058, "59": 0x0059, "5A": 0x005A, "5B": 0x005B, "5C": 0x005C, "5D": 0x005D, "5E": 0x005E, "5F": 0x005F, "60": 0x0060, "61": 0x0061, "62": 0x0062, "63": 0x0063, "64": 0x0064, "65": 0x0065, "66": 0x0066, "67": 0x0067, "68": 0x0068, "69": 0x0069, "6A": 0x006A, "6B": 0x006B, "6C": 0x006C, "6D": 0x006D, "6E": 0x006E, "6F": 0x006F, "70": 0x0070, "71": 0x0071, "72": 0x0072, "73": 0x0073, "74": 0x0074, "75": 0x0075, "76": 0x0076, "77": 0x0077, "78": 0x0078, "79": 0x0079, "7A": 0x007A, "7B": 0x007B, "7C": 0x007C, "7D": 0x007D, "7E": 0x007E, "7F": 0x007F, "80": 0x0080, "81": 0x0081, "82": 0x0082, "83": 0x0083, "84": 0x0084, "85": 0x0085, "86": 0x0086, "87": 0x0087, "88": 0x0088, "89": 0x0089, "8A": 0x008A, "8B": 0x008B, "8C": 0x008C, "8D": 0x008D, "8E": 0x008E, "8F": 0x008F, "90": 0x0090, "91": 0x0091, "92": 0x0092, "93": 0x0093, "94": 0x0094, "95": 0x0095, "96": 0x0096, "97": 0x0097, "98": 0x0098, "99": 0x0099, "9A": 0x009A, "9B": 0x009B, "9C": 0x009C, "9D": 0x009D, "9E": 0x009E, "9F": 0x009F, "A0": 0x00A0, "A1": 0x0104, "A2": 0x02D8, "A3": 0x0141, "A4": 0x00A4, "A5": 0x013D, "A6": 0x015A, "A7": 0x00A7, "A8": 0x00A8, "A9": 0x0160, "AA": 0x015E, "AB": 0x0164, "AC": 0x0179, "AD": 0x00AD, "AE": 0x017D, "AF": 0x017B, "B0": 0x00B0, "B1": 0x0105, "B2": 0x02DB, "B3": 0x0142, "B4": 0x00B4, "B5": 0x013E, "B6": 0x015B, "B7": 0x02C7, "B8": 0x00B8, "B9": 0x0161, "BA": 0x015F, "BB": 0x0165, "BC": 0x017A, "BD": 0x02DD, "BE": 0x017E, "BF": 0x017C, "C0": 0x0154, "C1": 0x00C1, "C2": 0x00C2, "C3": 0x0102, "C4": 0x00C4, "C5": 0x0139, "C6": 0x0106, "C7": 0x00C7, "C8": 0x010C, "C9": 0x00C9, "CA": 0x0118, "CB": 0x00CB, "CC": 0x011A, "CD": 0x00CD, "CE": 0x00CE, "CF": 0x010E, "D0": 0x0110, "D1": 0x0143, "D2": 0x0147, "D3": 0x00D3, "D4": 0x00D4, "D5": 0x0150, "D6": 0x00D6, "D7": 0x00D7, "D8": 0x0158, "D9": 0x016E, "DA": 0x00DA, "DB": 0x0170, "DC": 0x00DC, "DD": 0x00DD, "DE": 0x0162, "DF": 0x00DF, "E0": 0x0155, "E1": 0x00E1, "E2": 0x00E2, "E3": 0x0103, "E4": 0x00E4, "E5": 0x013A, "E6": 0x0107, "E7": 0x00E7, "E8": 0x010D, "E9": 0x00E9, "EA": 0x0119, "EB": 0x00EB, "EC": 0x011B, "ED": 0x00ED, "EE": 0x00EE, "EF": 0x010F, "F0": 0x0111, "F1": 0x0144, "F2": 0x0148, "F3": 0x00F3, "F4": 0x00F4, "F5": 0x0151, "F6": 0x00F6, "F7": 0x00F7, "F8": 0x0159, "F9": 0x016F, "FA": 0x00FA, "FB": 0x0171, "FC": 0x00FC, "FD": 0x00FD, "FE": 0x0163, "FF": 0x02D9}
	},
	
	parseHeaders: function (source) {
		var headers = null;
		
		if (source) {
			var lineEndIndex = source.indexOf("\n\n");
			if (lineEndIndex > -1) {
				var headersText = source.substring(0, lineEndIndex + 1);
				
				lineEndIndex = headersText.indexOf("\n");
				while (lineEndIndex > -1) {
					var headerName = null;
					var header = "";
					do {
						var text = headersText.substring(0, lineEndIndex);
						if ((text.indexOf(" ") != 0) && (text.indexOf("\t") != 0)) {
							if (text.indexOf(":") != -1) {
								headerName = text.substring(0, text.indexOf(":")).trim().toLowerCase();
								text = text.substring(text.indexOf(":") + 1).trim();
							}
							header = text;
						} else {
							header = header + "\n" + text;
						}
						headersText = headersText.substring(lineEndIndex + 1);
						lineEndIndex = headersText.indexOf("\n");
					} while ((headersText.indexOf(" ") == 0) || headersText.indexOf("\t") == 0);
					
					if (headerName != null) {
						headers = (headers == null) ? {} : headers;
						headers[headerName] = (headers[headerName] == null) ? [] : headers[headerName];
						headers[headerName][headers[headerName].length] = header;
					}
				}
			}
		}
		
		return headers;
	},
	
	parseMessage: function (source) {
		var cme = config.macros.email;
		
		var message = null;
		
		var headers = cme.parseHeaders(source);
		
		if (headers) {
			var body = source.substring(source.indexOf("\n\n") + 2);
			
			var boundary = null;
			if (headers["content-type"]) {
				for (var i = 0; i < headers["content-type"].length; i++) {
					var match = (/boundary\s*?=\s*?["|']?([^"'\s]*)["|']?/gi).exec(headers["content-type"][i]);
					boundary = (match && match.length) ? match[1] : null;
					if (boundary) {
						break;
					}
				}
			}
			
			if (boundary) {
				var parts = [];
				
				boundary = "--" + boundary;
				var boundaryIndex = body.indexOf(boundary);
				if (boundaryIndex > -1) {
					body = body.substring(boundaryIndex + boundary.length + 1);
					
					boundaryIndex = body.indexOf(boundary);
					while (boundaryIndex > -1) {
						var part = cme.parseMessage(body.substring(0, boundaryIndex));
						if (part) {
							parts[parts.length] = part;
						}
						body = body.substring(boundaryIndex + boundary.length + 1);
						boundaryIndex = body.indexOf(boundary);
					}
					
					var part = cme.parseMessage(body);
					if (part) {
						parts[parts.length] = part;
					}
				}
				
				if (parts.length) {
					message = {headers: headers, parts: parts};
				}
			} else {
				message = {headers: headers, body: body};
			}
		}
		
		return message;
	},
	
	processMessage: function (source, preferredMimeType) {
		var cme = config.macros.email;
		
		var email = null;
		
		if (source) {
			source = source.replace(/(\r\n|\n|\r)/gm, "\n");
			
			var message = cme.parseMessage(source);
			
			if (message) {
				var isPart = function (part, mimeType) {
					var result = false;
					if (mimeType && part.body) {
						if (part.headers["content-type"]) {
							var contentType = part.headers["content-type"][0].toLowerCase();
							result = contentType.indexOf(mimeType) > -1;
						} else {
							result = "text/plain".indexOf(mimeType) > -1;
						}
					}
					return result;
				}
				
				preferredMimeType = preferredMimeType ? preferredMimeType : "text/html";
				
				var preferredMimeTypeMessage = isPart(message, preferredMimeType) ? message : null;
				var textMessage = isPart(message, "text/plain") ? message : null;
				var htmlMessage = isPart(message, "text/html") ? message : null;
				
				var contentParts = {};
				var attachments = [];
				
				var catalogContent = function (message) {
					if (message.parts) {
						for (var i = 0; i < message.parts.length; i++) {
							preferredMimeTypeMessage = preferredMimeTypeMessage ? preferredMimeTypeMessage : (isPart(message.parts[i], preferredMimeType) ? message.parts[i] : null);
							textMessage = textMessage ? textMessage : (isPart(message.parts[i], "text/plain") ? message.parts[i] : null);
							htmlMessage = htmlMessage ? htmlMessage : (isPart(message.parts[i], "text/html") ? message.parts[i] : null);
							
							var contentDispositions = message.parts[i].headers["content-disposition"];
							for (var j = 0; contentDispositions && (j < contentDispositions.length); j++) {
								var contentDisposition = contentDispositions[j].trim().toLowerCase();
								if (contentDisposition.indexOf("attachment") === 0) {
									attachments[attachments.length] = message.parts[i];
									break;
								}
							}
							
							var contentIds = message.parts[i].headers["content-id"];
							if (contentIds && message.parts[i].body) {
								var contentId = null;
								for (var j = 0; j < contentIds.length; j++) {
									var contentId = contentIds[j];
									contentId = (contentId.indexOf("<") == 0) ? contentId.substring(1) : contentId;
									contentId = (contentId.lastIndexOf(">") == contentId.length - 1) ? contentId.substring(0, contentId.length - 1) : contentId;
									contentId = contentId.trim();
									if (contentId) {
										break;
									}
								}
								if (contentId) {
									contentParts[contentId] = message.parts[i];
								}
							}
							if (message.parts[i].parts) {
								catalogContent(message.parts[i]);
							}
						}
					}
				}
				catalogContent(message);
				
				var mainMessage = preferredMimeTypeMessage ? preferredMimeTypeMessage : (htmlMessage ? htmlMessage : textMessage);
				
				if (mainMessage) {
					var messageText = mainMessage.body;
					var charset = null;
					
					if (mainMessage.headers["content-transfer-encoding"]) {
						if (mainMessage.headers["content-type"]) {
							for (var i = 0; i < mainMessage.headers["content-type"].length; i++) {
								var match = (/charset\s*?=\s*?["|']?([^"'\s;]*)["|']?/gi).exec(mainMessage.headers["content-type"][i]);
								charset = (match && match.length) ? match[1] : null;
								if (charset) {
									break;
								}
							}
						}
						for (var i = 0; i < mainMessage.headers["content-transfer-encoding"].length; i++) {
							if (mainMessage.headers["content-transfer-encoding"][i].toLowerCase().indexOf("base64") > -1) {
								messageText = (messageText.indexOf("%/") > -1) && (messageText.substring(messageText.lastIndexOf("%/") + 2).trim().length == 0) ? messageText = messageText.substring(0, messageText.lastIndexOf("%/")) : messageText;
								messageText = config.macros.attach.decodeBase64(messageText.trim());
							}
						}
						for (var i = 0; i < mainMessage.headers["content-transfer-encoding"].length; i++) {
							if (mainMessage.headers["content-transfer-encoding"][i].toLowerCase().indexOf("quoted-printable") > -1) {
								messageText = cme.decodeQuotedPrintable(messageText, charset);
							}
						}
					}
					
					if (!charset || (charset.toLowerCase() == "utf-8")) {
						var validUTF8 = config.macros.fileDrop.validUTF8(messageText);
						if (!validUTF8) {
							var normalizedMessageText = messageText
									.replace(/‎/g, "")
									.replace(/’/g, "'")
									.replace(/Ã /g, String.fromCharCode(0xC3, 0xA0))
									.replace(/Â /g, String.fromCharCode(0xC2, 0xA0))
									.replace(/Ç/g, String.fromCharCode(0xC3, 0x87))
									.replace(/€™/g, String.fromCharCode(0x80, 0x99))
									.replace(/À/g, String.fromCharCode(0xC3, 0x80));
							validUTF8 = config.macros.fileDrop.validUTF8(normalizedMessageText);
							messageText = validUTF8 ? normalizedMessageText : messageText;
						}
						if (validUTF8) {
							messageText = convertUTF8ToUnicode(messageText);
						}
					}
					
					messageText = cme.encodeHTMLEntities(messageText);
					
					if (isPart(mainMessage, "text/html")) {
						messageText = messageText
								.replace(/\<html[\s\S]*?\>/g, "").replace(/\<\/html[\s\S]*?\>/g, "")
								.replace(/\<body[\s\S]*?\>/g, "").replace(/\<\/body[\s\S]*?\>/g, "");
						
						var index = 0;
						while ((index = messageText.indexOf("cid:", index)) > -1) {
							var endIndex = -1;
							if (messageText.charAt(index - 1) == "=") {
								endIndex = (messageText.indexOf(">", index) < messageText.indexOf(" ", index)) ? messageText.indexOf(">", index) : messageText.indexOf(" ", index);
							} else {
								endIndex = messageText.indexOf(messageText.charAt(index - 1), index);
							}
							var contentId = messageText.substring(index + "cid:".length, endIndex);
							var contentPart = contentParts[contentId];
							if (contentPart) {
								var contentType = "application/octet-stream";
								var contentTypeHeader = contentPart.headers["content-type"];
								if (contentTypeHeader && contentTypeHeader.length) {
									contentType = contentTypeHeader[0];
									contentType = (contentType.indexOf(";") > -1) ? contentType.substring(0, contentType.indexOf(";")) : contentType;
								}
								var content = contentPart.body.replace(/(\r\n|\n|\r)/gm, "");
								var contentURL = "data:" + contentType + ";base64," + content.trim();
								var contentIdURL = "cid:" + contentId;
								while (messageText.indexOf(contentIdURL) > -1) {
									messageText = messageText.replace(contentIdURL, contentURL);
								}
							}
							index = index + 1;
						}
					}
					
					if (!message.parts && (messageText.indexOf("%/") > -1) && (messageText.substring(messageText.lastIndexOf("%/") + 2).trim().length == 0)) {
						messageText = messageText.substring(0, messageText.lastIndexOf("%/"));
					}
					
					email = {message: message, main: mainMessage, text: messageText, attachments: attachments};
				}
			}
		}
		
		return email;
	},
	
	decodeQuotedPrintable: function (source, encoding) {
		if (source) {
			var codes = encoding ? config.macros.email.unicode[encoding.toLowerCase()] : null;
			source = source
					.replace(/=\n/g, "")
					.replace(/=([A-Fa-f0-9]{2})/g, function (match, p1, offset, string) {
						return String.fromCharCode((codes && (codes[p1] !== undefined)) ? codes[p1] : parseInt(p1, 16));
					});
		}
		return source;
	},
	
	encodeHTMLEntities: function (source) {
		if (source) {
			source = source
					.replace(/\x99/g, "&trade;");
		}
		return source;
	},
	
	processHeaderValue: function (value) {
		if (value) {
			var cme = config.macros.email;
			
			value = value.replace(/\n\s*/g, (/^=\?(.*?)\?[B|Q]\?(.*?)\?=/i).test(value) ? "" : " ");
			
			value = value.replace(/=\?UTF-8\?B\?(.*?)\?=/gi, function (match, p1, offset, string) {
				var text = config.macros.attach.decodeBase64(p1.trim());
				return text;
			});
			
			var charset = null;
			value = value.replace(/=\?(.*?)\?Q\?(.*?)\?=/gi, function (match, p1, p2, offset, string) {
				charset = p1;
				return cme.decodeQuotedPrintable(p2.replace(/_/g, " "), charset);
			});
			if ((!charset || (charset.toLowerCase() == "utf-8")) && config.macros.fileDrop.validUTF8(value)) {
				value = convertUTF8ToUnicode(value);
			}
			
			value = cme.encodeHTMLEntities(value);
			value = value.replace(/</g, "&lt;").replace(/>/g, "&gt;");
		}
		
		return value;
	},
	
	diplayedInfo: [["from", "From:"], ["to", "To:"], ["cc", "Cc:"], ["bcc", "Bcc:"], ["date", "Date:"], ["subject", "Subject:"], ["attachments", "Attachments:"]],
	
	handler: function (place, macroName, params, wikifier, paramString, tiddler) {
		var cme = config.macros.email;
		
		var parsedParams = paramString.parseParams("anon", null, true, false, false);
		
		var title = null;
		var filter = parsedParams.length ? parsedParams[0].filter : null;
		if (filter) {
			filter = "" + filter;
			
			var related = null;
			if (filter.indexOf("guid:") == 0) {
				filter = filter.substring("guid:".length);
				try {
					filter = eval(filter);
				} catch (e) {}
				
				var storeTiddlers = store.getTiddlers();
				for (var i = 0; i < storeTiddlers.length; i++) {
					if (storeTiddlers[i].fields && storeTiddlers[i].fields["guid"]) {
						if (filter.test) {
							if (filter.test(storeTiddlers[i].fields["guid"])) {
								title = storeTiddlers[i].title;
								break;
							}
						} else if (storeTiddlers[i].fields["guid"].indexOf(filter) > -1) {
							title = storeTiddlers[i].title;
							break;
						}
					}
				}
				related = tiddler ? store.getTaggedTiddlers(tiddler.title) : related;
				filter = filter.substring("tagged:".length);
			} else {
				if (filter.indexOf("tagged:") == 0) {
					related = tiddler ? store.getTaggedTiddlers(tiddler.title) : related;
					filter = filter.substring("tagged:".length);
				} else if (filter.indexOf("tag:") == 0) {
					related =  tiddler ? tiddler.tags : related;
					filter = filter.substring("tag:".length);
				}
				related = related ? related : store.getTiddlers();
				
				try {
					filter = eval(filter);
				} catch (e) {}
				
				for (var i = 0; i < (related ? related.length : 0); i++) {
					var currentTitle = related[i].title ? related[i].title : related[i];
					if (filter.test) {
						if (filter.test(currentTitle)) {
							title = currentTitle;
							break;
						}
					} else if (currentTitle.indexOf(filter) > -1) {
						title = currentTitle;
						break;
					}
				}
			}
		}
		
		var processor = parsedParams.length ? parsedParams[0].process : null;
		
		var sourceReference = (parsedParams.length && parsedParams[0].source) ? "" + parsedParams[0].source : null;
		var source = null;
		if (title !== null) {
			source = (sourceReference !== null) ? store.getTiddlerText(title + sourceReference) : null;
			source = (source !== null) ? source : store.getTiddlerText(title + (processor ? "" : "##email"));
		}
		if (source === null) {
			var tiddlerTitle = tiddler ? tiddler.title : "";
			source = (sourceReference !== null) ? store.getTiddlerText(sourceReference) : null;
			source = (source !== null) ? source : store.getTiddlerText(tiddlerTitle + ((sourceReference !== null) ? sourceReference : (processor ? "" : "##email")));
			source = (source !== null) ? source : store.getTiddlerText(tiddlerTitle + (processor ? "" : "##email"));
		}
		
		if (processor) {
			try {
				eval("" + processor);
			} catch (e) {
				throw "processing error: " + e;
			}
		}
		
		var email = cme.processMessage(source, parsedParams.length ? parsedParams[0].format : false);
		
		if (email) {
			var headers = email.message.headers;
			var diplayedInfo = cme.diplayedInfo;
			diplayedInfo = diplayedInfo ? diplayedInfo : [];
			var messageInfo = "";
			for (var i = 0; i < diplayedInfo.length; i++) {
				if (diplayedInfo[i][0] == "attachments") {
					if (email.attachments.length > 0) {
						var dataURItoObjectURL = function (dataURI, mimeType) {
							// http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata
							// convert base64/URLEncoded data component to raw binary data held in a string
							var byteString;
							if (dataURI.split(',')[0].indexOf('base64') >= 0) {
								byteString = atob(dataURI.split(',')[1]);
							} else {
								byteString = unescape(dataURI.split(',')[1]);
							}
							
							// separate out the mime component
							var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
							
							// write the bytes of the string to an ArrayBuffer
							var ab = new ArrayBuffer(byteString.length);
							var ia = new Uint8Array(ab);
							for (var i = 0; i < byteString.length; i++) {
								ia[i] = byteString.charCodeAt(i);
							}
							
							// write the ArrayBuffer to a blob, and you're done
							// http://stackoverflow.com/questions/10412299/whats-the-difference-between-blobbuilder-and-the-new-blob-constructor
							var dataView = new DataView(ab);
							var blob = new Blob([dataView], { type: (mimeType ? mimeType : (mimeString ? mimeString : "application/octet-stream")) });
							
							var DOMURL = self.URL || self.webkitURL || self;
							return "" + DOMURL.createObjectURL(blob);
						};
						
						var attachmentInfo = "";
						
						for (var j = 0; j < email.attachments.length; j++) {
							var attachment = email.attachments[j];
							
							var fileName = null;
							var contentType = "application/octet-stream";
							
							var contentTypeHeader = attachment.headers["content-type"];
							if (contentTypeHeader && contentTypeHeader.length) {
								contentType = contentTypeHeader[0];
								contentType = (contentType.indexOf(";") > -1) ? contentType.substring(0, contentType.indexOf(";")) : contentType;
								
								for (var k = 0; k < contentTypeHeader.length; k++) {
									var match = (/name\s*?=\s*?["|']?([\s\S]*)/gi).exec(contentTypeHeader[k]);
									fileName = (match && match.length) ? match[1] : null;
									if (fileName) {
										break;
									}
								}
							}
							
							if (!fileName) {
								if (attachment.headers["content-disposition"]) {
									for (var k = 0; k < attachment.headers["content-disposition"].length; k++) {
										var match = (/filename\s*?=\s*?["|']?([\s\S]*)/gi).exec(attachment.headers["content-disposition"][k]);
										fileName = (match && match.length) ? match[1] : null;
										if (fileName) {
											break;
										}
									}
								}
							}
							
							if (fileName && fileName.length && ((fileName.charAt(fileName.length - 1) == '"') || (fileName.charAt(fileName.length - 1) == '"'))) {
								fileName = fileName.substring(0, fileName.length - 1);
							}
							
							fileName = fileName ? cme.processHeaderValue(fileName) : "attachment.bin";
							
							var isBase64 = false;
							var encodingHeader = attachment.headers["content-transfer-encoding"];
							if (encodingHeader) {
								for (var k = 0; k < encodingHeader.length; k++) {
									if (encodingHeader[k].toLowerCase().indexOf("base64") > -1) {
										isBase64 = true;
										break;
									}
								}
							}
							var content = isBase64 ? attachment.body.replace(/(\r\n|\n|\r)/gm, "") : config.macros.attach.encodeBase64(attachment.body);
							var contentURL = "data:" + contentType + ";base64," + content.trim();
							
							attachmentInfo = attachmentInfo + ((attachmentInfo.length > 0) ? ', ' : '') + '<a href="' + dataURItoObjectURL(contentURL) + '"' + (fileName ? 'download="' + fileName + '"' : '') + ' target="_blank">' + fileName + '</a>';
						}
						
						messageInfo = messageInfo + "<tr><td class='label'>" + diplayedInfo[i][1] + "</td><td>" + attachmentInfo + "</td></tr>";
					}
				} else {
					var displayedHeaders = headers[diplayedInfo[i][0]];
					if (displayedHeaders) {
						for (var j = 0; j < displayedHeaders.length; j++) {
							var value = cme.processHeaderValue(displayedHeaders[j]);
							
							if (diplayedInfo[i][0] == "date") {
								// http://stackoverflow.com/questions/9352003/rfc-2822-date-regex
								var rfc2822DateRegexp = /^(?:(Sun|Mon|Tue|Wed|Thu|Fri|Sat),\s+)?(0[1-9]|[1-2]?[0-9]|3[01])\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(19[0-9]{2}|[2-9][0-9]{3})\s+(2[0-3]|[0-1][0-9]):([0-5][0-9])(?::(60|[0-5][0-9]))?\s+([-\+][0-9]{2}[0-5][0-9]|(?:UT|GMT|(?:E|C|M|P)(?:ST|ET|DT)|[A-IK-Z]))(\s+|\(([^\(\)]+|\\\(|\\\))*\))*$/;
								
								var matches = rfc2822DateRegexp.exec(value);
								if (matches.length == 11) {
									var monthMap = {Jan: 1, Feb: 2, Mar: 3, Apr: 4, May: 5, Jun: 6, Jul: 7, Aug: 8, Sep: 9, Oct: 10, Nov: 11, Dec: 12};
									var date = new Date(matches[4], monthMap[matches[3]] - 1, matches[2], matches[5], matches[6], matches[7], 0);
									
									var timezone = matches[8];
									if (!isNaN(parseFloat(timezone)) && isFinite(timezone) && (timezone.length > 3)) {
										var timezoneOffset = ((timezone.charAt(0) == "-") ? -1 : 1) * ((parseInt(timezone.substring(timezone.length - 4, timezone.length - 2)) * 60) + parseInt(timezone.substring(timezone.length - 2)));
										date.setMinutes(date.getMinutes() + ((-1 * date.getTimezoneOffset()) - timezoneOffset));
										
										var timezoneHours = (date.getTimezoneOffset() / 60) | 0;
										var timezoneMinutes = Math.abs(date.getTimezoneOffset() - (timezoneHours * 60));
										var timezone = (Math.abs(timezoneHours) < 10 ? "0" : "") + Math.abs(timezoneHours) + (timezoneMinutes < 10 ? "0" : "") + timezoneMinutes;
										timezone = (date.getTimezoneOffset() <= 0 ? "+" : "-") + timezone;
									}
									
									value = "" + date.getFullYear() + "-" + (date.getMonth() + 1 < 10 ? "0" : "") + (date.getMonth() + 1) + "-" + (date.getDate() < 10 ? "0" : "") + date.getDate() + " " + (date.getHours() < 10 ? "0" : "") + date.getHours() + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + ":" + (date.getSeconds() < 10 ? "0" : "") + date.getSeconds() + " " + timezone;
								}
							}
							
							messageInfo = messageInfo + "<tr><td class='label'>" + diplayedInfo[i][1] + "</td><td>" + value + "</td></tr>";
						}
					}
				}
			}

			messageInfo = (messageInfo == "") ? "" : "<table class='info'>" + messageInfo + "</table><br />";
			
			var message = email.text;
			if (!email.main.headers["content-type"] || (email.main.headers["content-type"][0].indexOf("text/html") == -1)) {
				message = "<pre>" + message.replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</pre>";
			}
			message = messageInfo + message;
			
			setStylesheet("\
					.EmailPluginMessage table {\
						margin: 0;\
					}\
					.EmailPluginMessage table, .EmailPluginMessage tr, .EmailPluginMessage td {\
						border: none;\
					}\
					.EmailPluginMessage .info td {\
						vertical-align: top;\
					}\
					.EmailPluginMessage .info td.label {\
						text-align: right;\
						font-weight: bold;\
					}\
					.EmailPluginMessage pre {\
						border: none;\
						background: " + store.getTiddlerSlice("ColorPalette", "Background") + ";\
						background-color: transparent;\
						color: " + store.getTiddlerSlice("ColorPalette", "Foreground") + ";\
					}", "EmailPlugin");
			
			var divId = "div"+ ((Math.random() * 10000) | 0);
			wikify("<html><div class='EmailPluginMessage' id='" + divId + "'/></html>", place);
			jQuery("#" + divId).html(message);
		}
	}
}
//}}}
<<list filter "[tag[events AND NOT Trash]]">>
<<list filter "[tag[examples AND NOT Trash]]">>
Firefox extension to enable TiddlyWiki to save changes directly to the file system : [[tiddlyfox.xpi]]
/***
|Name|FileDropPlugin|
|Source|http://www.TiddlyTools.com/#FileDropPlugin|
|Version|2.1.4|
|Author|BradleyMeck and Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|drag-and-drop files/directories to create tiddlers|
''requires FireFox or another Mozilla-compatible browser.''
!!!!!Usage
<<<
This plugin automatically creates tiddlers from files that are dropped onto an open TiddlyWiki document.  You can drop multiple selected files and/or folders to create many tiddlers at once.  New tiddler titles are created using the filename of each dropped file (i.e., omitting the path).  If a title is already in use, you are prompted to enter a new title for that file.  If you drop a folder, you will be asked if you want to create a simple 'directory list' of files in a single tiddler or create one tiddler for each file in that folder.  

By default, it is assumed that all dropped files contain text.  However, if [[AttachFilePlugin]], [[AttachFilePluginFormatters]] and [[AttachFileMIMETypes]] are installed, then you can drop ''//binary data files//'' as well as text files.  If the MIME type of a dropped file is not "text/plain", then AttachFilePlugin is used to create an 'attachment' tiddler, rather than creating a simple text tiddler.

When creating text tiddlers, you can embed a //link// to the original external file at the top of the new tiddler, in addition to (or instead of) the text content itself.  The format for this link (see Configuration, below) uses embedded ''//replacement markers//'' that allow you to generate a variety of wiki-formatted output, where:
*%0 = filename (without path)
*%1 = local """file://...""" URL
*%2 = local path and filename (OS-native format)
*%3 = relative path (if subdirectory of current document directory)
*%4 = file size
*%5 = file date
*%6 = current date
*%7 = current ~TiddlyWiki username
*\n = newline
By default, the link format uses the filename (%0) and local URL (%1), enclosed within a //hidden section// syntax, like this:
{{{
/%
!link
[[%0|%1]]
!end
%/
}}}
This permits the link to be embedded along with the text content, without changing the appearance of that content when the tiddler is viewed.  To display the link in your tiddler content, use:
{{{
<<tiddler TiddlerName##link>>
}}}
<<<
!!!!!Configuration
<<<
__FileDropPlugin options:__
<<option chkFileDropContent>>Copy file content into tiddlers if smaller than: <<option txtFileDropDataLimit>> bytes
&nbsp; //(note: excess text content will be truncated, oversized binary files will skipped, 0=no limit)//
<<option chkFileDropLink>>Generate external links to files, using this format:{{editor{<html><nowiki><textarea rows="4" onchange="
config.macros.option.propagateOption('txtFileDropLinkFormat','value',this.value.escapeLineBreaks(),'input');
"></textarea></html><<tiddler {{
	var ta=place.lastChild.getElementsByTagName('textarea')[0];
	var v=config.options.txtFileDropLinkFormat.unescapeLineBreaks();
	ta.value=v;
"";}}>>}}}<<option chkFileDropTrimFilename>>Omit file extensions from tiddler titles
<<option chkFileDropDisplay>>Automatically display newly created tiddlers
Tag newly created tiddlers with: <<option txtFileDropTags>>

__FileDropPlugin+AttachFilePlugin options:__ //(binary file data as encoded 'base64' text)//
<<option chkFileDropAttachLocalLink>> attachment includes reference to local path/filename
>Note: if the plugin does not seem to work, enter ''about:config'' in the Firefox address bar, and make sure that {{{signed.applets.codebase_principal_support}}} is set to ''true''
<<<
!!!!!Examples (custom handler functions)
<<<
Adds a single file with confirmation and prompting for title:
{{{
config.macros.fileDrop.addEventListener('application/x-moz-file',
	function(nsiFile) {
		var msg='You have dropped the file:\n'
			+nsiFile.path+'\n'
			+'onto the page, it will be imported as a tiddler. Is that ok?'
		if(confirm(msg)) {
			var newDate = new Date();
			var title = prompt('what would you like to name the tiddler?');
			store.saveTiddler(title,title,loadFile(nsiFile.path),config.options.txtUserName,newDate,[]);
		}
		return true;
	});
}}}
Adds a single file without confirmation, using path/filename as tiddler title:
{{{
config.macros.fileDrop.addEventListener('application/x-moz-file',
	function(nsiFile) {
		var newDate = new Date();
		store.saveTiddler(nsiFile.path,nsiFile.path,loadFile(nsiFile.path),config.options.txtUserName,newDate,[]);
		story.displayTiddler(null,nsiFile.path)
		return true;
	});
}}}
<<<
!!!!!Revisions
<<<
2010.03.06 2.1.4 added event listener for 'dragover' (for FireFox 3.6+)
2009.10.10 2.1.3 fixed IE code error
2009.10.08 2.1.2 fixed chkFileDropContent bypass handling for binary attachments
2009.10.07 2.1.0 added chkFileDropContent and chkFileDropLink/txtFileDropLinkFormat
2009.08.19 2.0.0 fixed event listener registration for FireFox 3.5+.  Also, merged with FileDropPluginConfig, with code cleanup/reduction
2008.08.11 1.5.1 added chkFileDropAttachLocalLink option to allow suppression of local path/file link
2007.xx.xx *.*.* add suspend/resume of notifications to improve performance when multiple files are handled
2007.01.01 0.9.9 extensions for AttachFilePlugin
2006.11.04 0.1.1 initial release by Bradley Meck
<<<
!!!!!Code
***/
//{{{
version.extensions.FileDropPlugin={major:2, minor:1, revision:4, date: new Date(2010,3,6)};

config.macros.fileDrop = {
	customDropHandlers: [],
	addEventListener: function(paramflavor,func,inFront) {
		var obj={}; obj.flavor=paramflavor; obj.handler=func;
		if (!inFront) this.customDropHandlers.push(obj);
		else this.customDropHandlers.shift(obj);
	},
	dragDropHandler: function(evt) {
		netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
		var dragService = Components.classes['@mozilla.org/widget/dragservice;1'].getService(Components.interfaces.nsIDragService);
		var dragSession = dragService.getCurrentSession();
		var transferObject = Components.classes['@mozilla.org/widget/transferable;1'].createInstance();
		transferObject = transferObject.QueryInterface(Components.interfaces.nsITransferable);
		transferObject.addDataFlavor('application/x-moz-file');
		var numItems = dragSession.numDropItems;
		if (numItems>1) {
			clearMessage();
			displayMessage('Reading '+numItems+' files...');
			store.suspendNotifications();
		}
		for (var i = 0; i < numItems; i++) {
			dragSession.getData(transferObject, i);
			var dataObj = {};
			var dropSizeObj = {};
			for(var ind=0; ind<config.macros.fileDrop.customDropHandlers.length; ind++) {
				var item = config.macros.fileDrop.customDropHandlers[ind];
				if(dragSession.isDataFlavorSupported(item.flavor)) {
					transferObject.getTransferData(item.flavor, dataObj, dropSizeObj);
					var droppedFile = dataObj.value.QueryInterface(Components.interfaces.nsIFile);
					var result = item.handler.call(item,droppedFile);
					evt.stopPropagation();
					evt.preventDefault();
					if (result) break;
				}
			}
		}
		if (numItems>1) {
			store.resumeNotifications();
			store.notifyAll();
			displayMessage(numItems+' files have been processed');
		}
	}
}
//}}}
/***
!!!!!window event handlers
***/
//{{{
if(!window.event) {
	window.addEventListener('dragdrop',	// FireFox3.1-
		config.macros.fileDrop.dragDropHandler, true);
	window.addEventListener('drop',		// FireFox3.5+
		config.macros.fileDrop.dragDropHandler, true);
	window.addEventListener('dragover',	// FireFox3.6+
		function(e){e.stopPropagation();e.preventDefault();}, true); 
}
//}}}
/***
!!!!!handler for files, directories and binary attachments (see [[AttachFilePlugin]])
***/
//{{{
var defaults={
	chkFileDropDisplay:		true,
	chkFileDropTrimFilename:	false,
	chkFileDropContent:		true,
	chkFileDropLink:		true,
	txtFileDropLinkFormat:		'/%\\n!link\\n[[%0|%1]]\\n!end\\n%/',
	txtFileDropDataLimit:		'32768',
	chkFileDropAttachLocalLink:	true,
	txtFileDropTags:		''
};
for (var id in defaults) if (config.options[id]===undefined)
	config.options[id]=defaults[id];

config.macros.fileDrop.addEventListener('application/x-moz-file',function(nsiFile) {
	var co=config.options; // abbrev
	var header='Index of %0\n^^(as of %1)^^\n|!filename| !size | !modified |\n';
	var item='|[[%0|%1]]| %2|%3|\n';
	var footer='Total of %0 bytes in %1 files\n';
	var now=new Date();
	var files=[nsiFile];
	if (nsiFile.isDirectory()) {
		var folder=nsiFile.directoryEntries;
		var files=[];
		while (folder.hasMoreElements()) {
			var f=folder.getNext().QueryInterface(Components.interfaces.nsILocalFile);
			if (f instanceof Components.interfaces.nsILocalFile && !f.isDirectory()) files.push(f);
		}
		var msg=nsiFile.path.replace(/\\/g,'/')+'\n\n';
		msg+='contains '+files.length+' files... ';
		msg+='select OK to attach all files or CANCEL to create a list...';
		if (!confirm(msg)) { // create a list in a tiddler
			var title=nsiFile.leafName; // tiddler name is last directory name in path
			while (title && title.length && store.tiddlerExists(title)) {
				if (confirm(config.messages.overwriteWarning.format([title]))) break;
				title=prompt('Enter a new tiddler title',nsiFile.path.replace(/\\/g,'/'));
			}
			if (!title || !title.length) return true; // cancelled
			var text=header.format([nsiFile.path.replace(/\\/g,'/'),now.toLocaleString()]);
			var total=0;
			for (var i=0; i<files.length; i++) { var f=files[i];
				var name=f.leafName;
				if (co.chkFileDropTrimFilename)
					{ var p=name.split('.'); if (p.length>1) p.pop(); name=p.join('.'); }
				var path='file:///'+f.path.replace(/\\/g,'/');
				var size=f.fileSize; total+=size;
				var when=new Date(f.lastModifiedTime).formatString('YYYY.0MM.0DD 0hh:0mm:0ss');
				text+=item.format([name,path,size,when]);
			}
			text+=footer.format([total,files.length]);
			var newtags=co.txtFileDropTags?co.txtFileDropTags.readBracketedList():[];
			store.saveTiddler(null,title,text,co.txtUserName,now,newtags);
			if (co.chkFileDropDisplay) story.displayTiddler(null,title);
			return true;
		}
	}
	if (files.length>1) store.suspendNotifications();
	for (i=0; i<files.length; i++) {
		var file=files[i];
		if (file.isDirectory()) continue; // skip over nested directories
		var type='text/plain';
		var title=file.leafName; // tiddler name is file name
		if (co.chkFileDropTrimFilename)
			{ var p=title.split('.'); if (p.length>1) p.pop(); title=p.join('.'); }
		var name=file.leafName;
		var path=file.path;
		var url='file:///'+path.replace(/\\/g,'/');
		var size=file.fileSize;
		var when=new Date(file.lastModifiedTime);
		var now=new Date();
		var who=config.options.txtUserName;
		var h=document.location.href;
		var cwd=getLocalPath(decodeURIComponent(h.substr(0,h.lastIndexOf('/')+1)));
		var relpath=path.startsWith(cwd)?'./'+path.substr(cwd.length):path;
		while (title && title.length && store.tiddlerExists(title)) {
			if (confirm(config.messages.overwriteWarning.format([title]))) break;
			title=prompt('Enter a new tiddler title',path.replace(/\\/g,'/'));
		}
		if (!title || !title.length) continue; // cancelled
		if (config.macros.attach) {
			type=config.macros.attach.getMIMEType(name,'');
			if (!type.length)
				type=prompt('Unknown file type.  Enter a MIME type','text/plain');
			if (!type||!type.length) continue; // cancelled
		}
		var newtags=co.txtFileDropTags?co.txtFileDropTags.readBracketedList():[];
		if (type=='text/plain' || !co.chkFileDropContent) {
			var txt=''; var fmt=co.txtFileDropLinkFormat.unescapeLineBreaks();
			if (co.chkFileDropLink) txt+=fmt.format([name,url,path,relpath,size,when,now,who]);
			if (co.chkFileDropContent) {
				var out=loadFile(path); var lim=co.txtFileDropDataLimit;
				txt+=co.txtFileDropDataLimit?out.substr(0,lim):out;
				if (size>lim) txt+='\n----\nfilesize ('+size+')'
					+' is larger than FileDrop limit ('+lim+')...\n'
					+'additional content has been omitted';
			}
			store.saveTiddler(null,title,txt,co.txtUserName,now,newtags);
		} else {
			var embed=co.chkFileDropContent
				&& (!co.txtFileDropDataLimit||size<co.txtFileDropDataLimit);
			newtags.push('attachment'); newtags.push('excludeMissing');
			config.macros.attach.createAttachmentTiddler(path,
				now.formatString(config.macros.timeline.dateFormat),
				'attached by FileDropPlugin', newtags, title,
				embed, co.chkFileDropAttachLocalLink, false,
				relpath, '', type,!co.chkFileDropDisplay);
		}
		if (co.chkFileDropDisplay) story.displayTiddler(null,title);
	}
	if (files.length>1) { store.resumeNotifications(); store.notifyAll(); }
	return true;
})
//}}}
//{{{
config.macros.fileDrop.addEventListener = function(paramflavor,func,inFront) {
	var obj={}; obj.flavor=paramflavor; obj.handler=func;
	if (!inFront) this.customDropHandlers.push(obj);
	else this.customDropHandlers.unshift(obj);
};
//}}}
//{{{
config.macros.fileDrop.dragDropHandler_original = config.macros.fileDrop.dragDropHandler;
config.macros.fileDrop.dragDropHandler = function(evt) {
	try {
		netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
		config.macros.fileDrop.dragDropHandler_original(evt);
	} catch (e) {
		config.macros.fileDrop.dragDropHandler_safe(evt);
	}
}

config.macros.fileDrop.dragDropHandler_safe = function(event) {
	var fullPathSupport = false;
	try {
		netscape.security.PrivilegeManager.enablePrivilege("UniversalFileRead");
		fullPathSupport = true;
	} catch (e) {}
	
	var files = event.dataTransfer.files;
	var numItems = files.length;
	var numItemsLeft = { initial: numItems, count: numItems };
	
	if (numItems>1) {
		clearMessage();
		displayMessage('Reading '+numItems+' files...');
		store.suspendNotifications();
	}
	for (var i = 0; i < numItems; i++) {
		files[i].leafName = files[i].name;
		files[i].isDirectory = function () { return false; };
		files[i].path = fullPathSupport ? files[i].mozFullPath : files[i].name;
		files[i].fileSize = files[i].size;
		
		var reader = new FileReader;
		reader.originalFile = files[i];
		reader.numItemsLeft = numItemsLeft;
		reader.onload = function (e) {
			var loadedFile = e.target.originalFile;
			loadedFile.fileData = e.target.result;
			for(var ind=0; ind<config.macros.fileDrop.customDropHandlers.length; ind++) {
				var item = config.macros.fileDrop.customDropHandlers[ind];
				var result = null;
				try {
					result = item.handler.call(item,loadedFile);
				} catch (e) {}
				if (result) break;
			}
		};
		reader.onloadend = function (e) {
			e.target.numItemsLeft.count--;
			if (e.target.numItemsLeft.count<1) {
				store.resumeNotifications();
				store.notifyAll();
				displayMessage(e.target.numItemsLeft.initial+' files have been processed');
			}
		};
		reader.readAsBinaryString(files[i]);
	}
	event.stopPropagation();
	event.preventDefault();
}
//}}}
//{{{
if(!window.event) {
	window.removeEventListener('dragdrop',	// FireFox3.1-
		config.macros.fileDrop.dragDropHandler_original, true);
	window.addEventListener('dragdrop',	// FireFox3.1-
		config.macros.fileDrop.dragDropHandler, true);
	window.removeEventListener('drop',	// FireFox3.5+
		config.macros.fileDrop.dragDropHandler_original, true);
	window.addEventListener('drop',		// FireFox3.5+
		config.macros.fileDrop.dragDropHandler, true);
}
//}}}
//{{{
if (config.macros.fileDrop.customDropHandlers && config.macros.fileDrop.customDropHandlers[0] && config.macros.fileDrop.customDropHandlers[0].handler) {
	var code = config.macros.fileDrop.customDropHandlers[0].handler.toString();
	var newCode = null;
	var startPosition = code.indexOf("config.macros.attach.createAttachmentTiddler(path,");
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition) + "config.macros.attach.createAttachmentTiddler(file.fileData ? file : path," + code.substring(startPosition + "config.macros.attach.createAttachmentTiddler(path,".length);
		code = newCode;
	}
	if (newCode != null) {
		eval("config.macros.fileDrop.customDropHandlers[0].handler = function handler" + newCode.substring(newCode.indexOf("(")));
	}
}
//}}}
//{{{
// http://phpjs.org/functions/utf8_encode (commit e666d231c93d50d39ef5ada4d4c8a0916279659b 2012-10-08)
config.macros.fileDrop.utf8_encode = function (argString) {
  // http://kevin.vanzonneveld.net
  // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: sowberry
  // +    tweaked by: Jack
  // +   bugfixed by: Onno Marsman
  // +   improved by: Yves Sucaet
  // +   bugfixed by: Onno Marsman
  // +   bugfixed by: Ulrich
  // +   bugfixed by: Rafal Kukawski
  // +   improved by: kirilloid
  // *     example 1: utf8_encode('Kevin van Zonneveld');
  // *     returns 1: 'Kevin van Zonneveld'

  if (argString === null || typeof argString === "undefined") {
    return "";
  }

  var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  var utftext = '',
    start, end, stringl = 0;

  start = end = 0;
  stringl = string.length;
  for (var n = 0; n < stringl; n++) {
    var c1 = string.charCodeAt(n);
    var enc = null;

    if (c1 < 128) {
      end++;
    } else if (c1 > 127 && c1 < 2048) {
      enc = String.fromCharCode((c1 >> 6) | 192, (c1 & 63) | 128);
    } else {
      enc = String.fromCharCode((c1 >> 12) | 224, ((c1 >> 6) & 63) | 128, (c1 & 63) | 128);
    }
    if (enc !== null) {
      if (end > start) {
        utftext += string.slice(start, end);
      }
      utftext += enc;
      start = end = n + 1;
    }
  }

  if (end > start) {
    utftext += string.slice(start, stringl);
  }

  return utftext;
};

// http://stackoverflow.com/questions/887148/how-to-determine-if-a-string-contains-invalid-encoded-characters
config.macros.fileDrop.validUTF8 = function(input) {
	var Ox80 = parseInt("80", 16);
	var OxBB = parseInt("BB", 16);
	var OxBF = parseInt("BF", 16);
	var OxC0 = parseInt("C0", 16);
	var OxE0 = parseInt("E0", 16);
	var OxEF = parseInt("EF", 16);
	var OxF0 = parseInt("F0", 16);
	var OxF8 = parseInt("F8", 16);
	var OxFF = parseInt("FF", 16);
	
	var i = 0;
	// Check for BOM
	if (input.length >= 3 && (input.charCodeAt(0) & OxFF) == OxEF
			&& (input.charCodeAt(1) & OxFF) == OxBB & (input.charCodeAt(2) & OxFF) == OxBF) {
		i = 3;
	}
	
	var end;
	for (var j = input.length; i < j; ++i) {
		var octet = input.charCodeAt(i);
		if ((octet & Ox80) == 0) {
			continue; // ASCII
		}
		
		// Check for UTF-8 leading byte
		if ((octet & OxE0) == OxC0) {
			end = i + 1;
		} else if ((octet & OxF0) == OxE0) {
			end = i + 2;
		} else if ((octet & OxF8) == OxF0) {
			end = i + 3;
		} else {
			// Java only supports BMP so 3 is max
			return false;
		}
		
		while (i < end) {
			i++;
			octet = input.charCodeAt(i);
			if ((octet & OxC0) != Ox80) {
				// Not a valid trailing byte
				return false;
			}
		}
	}
	return true;
};
//}}}
//{{{
// http://phpjs.org/functions/utf8_decode (commit e666d231c93d50d39ef5ada4d4c8a0916279659b 2012-10-08)
config.macros.fileDrop.utf8_decode = function utf8_decode (str_data) {
  // http://kevin.vanzonneveld.net
  // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
  // +      input by: Aman Gupta
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: Norman "zEh" Fuchs
  // +   bugfixed by: hitwork
  // +   bugfixed by: Onno Marsman
  // +      input by: Brett Zamir (http://brett-zamir.me)
  // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // *     example 1: utf8_decode('Kevin van Zonneveld');
  // *     returns 1: 'Kevin van Zonneveld'
  var tmp_arr = [],
    i = 0,
    ac = 0,
    c1 = 0,
    c2 = 0,
    c3 = 0;

  str_data += '';

  while (i < str_data.length) {
    c1 = str_data.charCodeAt(i);
    if (c1 < 128) {
      tmp_arr[ac++] = String.fromCharCode(c1);
      i++;
    } else if (c1 > 191 && c1 < 224) {
      c2 = str_data.charCodeAt(i + 1);
      tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
      i += 2;
    } else {
      c2 = str_data.charCodeAt(i + 1);
      c3 = str_data.charCodeAt(i + 2);
      tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
      i += 3;
    }
  }

  return tmp_arr.join('');
};

manualConvertUTF8ToUnicode = config.macros.fileDrop.utf8_decode;
//}}}
//{{{
if (config.macros.fileDrop) {
	config.macros.fileDrop.icalendartag = "agenda";
	
	config.macros.fileDrop.addEventListener(
		'application/x-moz-file',
		function(nsiFile) {
			var emlText = null;
			var text = null;
			var error = null;
			
			var isICS = function() {
				return nsiFile && !nsiFile.isDirectory() && nsiFile.leafName && (nsiFile.leafName.toLowerCase().lastIndexOf(".ics") == nsiFile.leafName.length - ".ics".length);
			};
			var isEML = function() {
				return nsiFile && !nsiFile.isDirectory() && nsiFile.leafName && (nsiFile.leafName.toLowerCase().lastIndexOf(".eml") == nsiFile.leafName.length - ".eml".length);
			};
			
			var loadFileText = function () {
				try {
					if (nsiFile.fileData) {
						text = nsiFile.fileData;
					}
					if ((text == null) || (text == false) || (text == "")) {
						text = mozillaLoadFile(path);
					}
					if ((text == null) || (text == false) || (text == "")) {
						text = ieLoadFile(path);
					}
					if ((text == null) || (text == false) || (text == "")) {
						var code = eval("javaLoadFile").toString().replace(/UTF-8/gi, 'ISO-8859-1');
						eval("var localJavaLoadFile = " + code);
						text = localJavaLoadFile(path);
					}
					text = text ? text : "";
				} catch (err) {
					error = err;
					alert("There was a problem while retrieving the file.");
				}				
			};
			
			if (isEML()) {
				if (text === null) {
					loadFileText();
					if (error) {
						return true;
					}
				}
				emlText = text;
				var email = config.macros.email.processMessage(text, "calendar");
				if (!email.main.headers["content-type"] || (email.main.headers["content-type"][0].indexOf("calendar") == -1)) {
					return false;
				}
				text = email.text;
			}
			
			var title = nsiFile.leafName;
			if (isEML() || isICS()) {
				var path = nsiFile.path;
				
				while (title && title.length && store.tiddlerExists(title)) {
					if (confirm(config.messages.overwriteWarning.format([title]))) {
						break;
					}
					var currentTime = new Date();
					var timestamp = "" + currentTime.getFullYear() + (currentTime.getMonth() + 1 < 10 ? "0" : "") + (currentTime.getMonth() + 1) + (currentTime.getDate() < 10 ? "0" : "") + currentTime.getDate();
					timestamp = timestamp + (currentTime.getHours() < 10 ? "0" : "") + currentTime.getHours() + (currentTime.getMinutes() < 10 ? "0" : "") + currentTime.getMinutes() + (currentTime.getSeconds() < 10 ? "0" : "") + currentTime.getSeconds();
					timestamp = timestamp + (currentTime.getMilliseconds() < 10 ? "0" : "") + (currentTime.getMilliseconds() < 100 ? "0" : "") + currentTime.getMilliseconds()
					title = title.lastIndexOf(".") > -1 ? title.substring(0, title.lastIndexOf(".")) + timestamp + title.substring(title.lastIndexOf(".")) : title + timestamp;
					title = prompt('Enter a new tiddler title', title);
				}
				if (!title || !title.length) {
					return true;
				}
				
				var newDate = new Date();
				
				if (text === null) {
					loadFileText();
					if (error) {
						return true;
					}
				}
				
				var limit75 = function (text) {
					var out = '';
					while (text.length > 75) {
						out += text.substr(0, 75) + '\r\n';
						text = ' ' + text.substr(75);
					}
					out += text;
					return out;
				};
				var FOLDED = /^\s(.*)$/;
				var lines = text.replace(/\r\n/g, '\n').split('\n');
				for (var i = lines.length - 1; i > 0; i--) {
					var matches = FOLDED.exec(lines[i]);
					if (matches) {
						lines[i - 1] += matches[1];
						lines[i] = '';
					}
				}
				text = '';
				for (var i = 0; i < lines.length; i++) {
					if (lines[i] != '') {
						lines[i] = limit75(config.macros.fileDrop.validUTF8(lines[i]) ? config.macros.fileDrop.utf8_decode(lines[i]) : lines[i]);
						text = text + lines[i] + "\r\n";
					}
				}
				if (text.length) {
					text = "{{{\n" + text + "\n}}}";
				}
				if (emlText !== null) {
					if (confirm("Include the original email?")) {
						text = "<" + "<email>" + ">\n\n/" + "%\n!email\n" + emlText.replace(/\r\n/g, '\n') + "\n%" + "/\n\n" + text;
					}
				}
				
				var tiddlerTags = config.macros.fileDrop.icalendartag ? ["" + config.macros.fileDrop.icalendartag] : [];
				if (store.tiddlerExists(title)) {
					tiddlerTags = store.getTiddler(title).getTags();
				}
				
				store.saveTiddler(title, title, text, config.options.txtUserName, newDate, tiddlerTags);
				
				story.displayTiddler(null, title);
				story.refreshTiddler(title, null, true);
				
				return true;
			}
			return false;
		},
		true);
}
//}}}
// // [[LoadTiddlersPlugin]]
//{{{
if (config.options.txtFileDropExcludeFunction === undefined) {
	config.options.txtFileDropExcludeFunction = "";
}
if (config.options.txtFileDropExcludeTags === undefined) {
	config.options.txtFileDropExcludeTags = "doNotImport";
}
merge(config.optionsDesc, {
	txtFileDropExcludeFunction: "The function used to determine the tiddlers that are not to be imported during a file drop operation; the tiddler object, which migh be null, is passed as a parameter; the function must return true if the tiddler is to be excluded and false if it is to be imported; if specified, the txtFileDropExcludeTags option is ignored",
	txtFileDropExcludeTags: "The bracketed list of tags of tiddlers that are not to be imported during a file drop operation; this option is ignored if the txtFileDropExcludeFunction option is specified"
});
//}}}
//{{{
if (config.macros.fileDrop) {
	config.macros.fileDrop.FileDropPluginTiddlerOverrideParams = {
		quiet: true,
		ask: true,
		filter: 'updates', // 'all' 'new' 'changes' 'updates' 'tiddler:TiddlerName' 'tag:value'
		force: false,
		init: false,
		nodirty: false,
		norefresh: false,
		noreport: true,
		autosave: false,
		newtags: [],
		tiddlerValidator: {
			exclude: function (tiddler) {
				if (config.options.txtFileDropExcludeFunction) {
					try {
						eval("var excludeFunction = " + config.options.txtFileDropExcludeFunction);
						return excludeFunction(tiddler);
					} catch (e) {}
				}
				if (tiddler && tiddler.tags) {
					var excludeTags = config.options.txtFileDropExcludeTags ? config.options.txtFileDropExcludeTags.readBracketedList() : null;
					if (excludeTags) {
						for (var i = 0; i < excludeTags.length; i++) {
							if (tiddler.tags.indexOf(excludeTags[i]) != -1) {
								return true;
							}
						}
					}
				}
				return false;
			}
		}
	};
}
//}}}
//{{{
if (config.macros.fileDrop) {	
	config.macros.fileDrop.addEventListener(
		'application/x-moz-file',
		function(nsiFile) {
			var title = nsiFile.leafName;
			if (config.macros.loadTiddlers && !nsiFile.isDirectory() && ((title.toLowerCase().lastIndexOf(".html") == title.length - ".html".length) || (title.toLowerCase().lastIndexOf(".htm") == title.length - ".htm".length))) {
				var path = nsiFile.path;
				
				var text = "";
				try {
					if (nsiFile.fileData) {
						text = nsiFile.fileData;
					}
					if ((text == null) || (text == false) || (text == "")) {
						text = loadFile(path);
					}
				} catch (err) {
					alert("There was a problem while retrieving the file.");
					return true;
				}
				text = text ? text : "";
				if (text.length) {
					if (version.major + (version.minor * 0.1) + (version.revision * 0.01) != 2.52) {
						try {
							var textTemp = "";
							while (text.length > 0) {
								var index = text.indexOf(" ", 10000);
								index = (index > -1) ? index : text.length;
								textTemp = textTemp + convertUTF8ToUnicode(text.substring(0, index));
								text = text.substring(index);
							}
							text = textTemp;
						} catch (err) {
							alert(err);
							return false;
						}
					}
					if (text.indexOf("TiddlyWiki") > -1) {
						config.macros.loadTiddlers.newTags = config.macros.fileDrop.FileDropPluginTiddlerOverrideParams["newtags"];
						config.macros.loadTiddlers.doImport(true, config.macros.fileDrop.FileDropPluginTiddlerOverrideParams, text, path, null);
						displayMessage("The TiddlyWiki was successfully processed.");
						return true;
					}
				}
			}
			return false;
		},
		true);
}
//}}}
/***
|''Name:''|ForEachTiddlerPlugin|
|''Version:''|1.0.8 (2007-04-12)|
|''Source:''|http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''Licence:''|[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]|
|''Copyright:''|&copy; 2005-2007 [[abego Software|http://www.abego-software.de]]|
|''TiddlyWiki:''|1.2.38+, 2.0|
|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|
!Description

Create customizable lists, tables etc. for your selections of tiddlers. Specify the tiddlers to include and their order through a powerful language.

''Syntax:'' 
|>|{{{<<}}}''forEachTiddler'' [''in'' //tiddlyWikiPath//] [''where'' //whereCondition//] [''sortBy'' //sortExpression// [''ascending'' //or// ''descending'']] [''script'' //scriptText//] [//action// [//actionParameters//]]{{{>>}}}|
|//tiddlyWikiPath//|The filepath to the TiddlyWiki the macro should work on. When missing the current TiddlyWiki is used.|
|//whereCondition//|(quoted) JavaScript boolean expression. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//sortExpression//|(quoted) JavaScript expression returning "comparable" objects (using '{{{<}}}','{{{>}}}','{{{==}}}'. May refer to the build-in variables {{{tiddler}}} and  {{{context}}}.|
|//scriptText//|(quoted) JavaScript text. Typically defines JavaScript functions that are called by the various JavaScript expressions (whereClause, sortClause, action arguments,...)|
|//action//|The action that should be performed on every selected tiddler, in the given order. By default the actions [[addToList|AddToListAction]] and [[write|WriteAction]] are supported. When no action is specified [[addToList|AddToListAction]]  is used.|
|//actionParameters//|(action specific) parameters the action may refer while processing the tiddlers (see action descriptions for details). <<tiddler [[JavaScript in actionParameters]]>>|
|>|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|

See details see [[ForEachTiddlerMacro]] and [[ForEachTiddlerExamples]].

!Revision history
* v1.0.8 (2007-04-12)
** Adapted to latest TiddlyWiki 2.2 Beta importTiddlyWiki API (introduced with changeset 2004). TiddlyWiki 2.2 Beta builds prior to changeset 2004 are no longer supported (but TiddlyWiki 2.1 and earlier, of cause)
* v1.0.7 (2007-03-28)
** Also support "pre" formatted TiddlyWikis (introduced with TW 2.2) (when using "in" clause to work on external tiddlers)
* v1.0.6 (2006-09-16)
** Context provides "viewerTiddler", i.e. the tiddler used to view the macro. Most times this is equal to the "inTiddler", but when using the "tiddler" macro both may be different.
** Support "begin", "end" and "none" expressions in "write" action
* v1.0.5 (2006-02-05)
** Pass tiddler containing the macro with wikify, context object also holds reference to tiddler containing the macro ("inTiddler"). Thanks to SimonBaird.
** Support Firefox 1.5.0.1
** Internal
*** Make "JSLint" conform
*** "Only install once"
* v1.0.4 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.3 (2005-12-22)
** Features: 
*** Write output to a file supports multi-byte environments (Thanks to Bram Chen) 
*** Provide API to access the forEachTiddler functionality directly through JavaScript (see getTiddlers and performMacro)
** Enhancements:
*** Improved error messages on InternetExplorer.
* v1.0.2 (2005-12-10)
** Features: 
*** context object also holds reference to store (TiddlyWiki)
** Fixed Bugs: 
*** ForEachTiddler 1.0.1 has broken support on win32 Opera 8.51 (Thanks to BrunoSabin for reporting)
* v1.0.1 (2005-12-08)
** Features: 
*** Access tiddlers stored in separated TiddlyWikis through the "in" option. I.e. you are no longer limited to only work on the "current TiddlyWiki".
*** Write output to an external file using the "toFile" option of the "write" action. With this option you may write your customized tiddler exports.
*** Use the "script" section to define "helper" JavaScript functions etc. to be used in the various JavaScript expressions (whereClause, sortClause, action arguments,...).
*** Access and store context information for the current forEachTiddler invocation (through the build-in "context" object) .
*** Improved script evaluation (for where/sort clause and write scripts).
* v1.0.0 (2005-11-20)
** initial version

!Code
***/
//{{{

	
//============================================================================
//============================================================================
//		   ForEachTiddlerPlugin
//============================================================================
//============================================================================

// Only install once
if (!version.extensions.ForEachTiddlerPlugin) {

if (!window.abego) window.abego = {};

version.extensions.ForEachTiddlerPlugin = {
	major: 1, minor: 0, revision: 8, 
	date: new Date(2007,3,12), 
	source: "http://tiddlywiki.abego-software.de/#ForEachTiddlerPlugin",
	licence: "[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]",
	copyright: "Copyright (c) abego Software GmbH, 2005-2007 (www.abego-software.de)"
};

// For backward compatibility with TW 1.2.x
//
if (!TiddlyWiki.prototype.forEachTiddler) {
	TiddlyWiki.prototype.forEachTiddler = function(callback) {
		for(var t in this.tiddlers) {
			callback.call(this,t,this.tiddlers[t]);
		}
	};
}

//============================================================================
// forEachTiddler Macro
//============================================================================

version.extensions.forEachTiddler = {
	major: 1, minor: 0, revision: 8, date: new Date(2007,3,12), provider: "http://tiddlywiki.abego-software.de"};

// ---------------------------------------------------------------------------
// Configurations and constants 
// ---------------------------------------------------------------------------

config.macros.forEachTiddler = {
	 // Standard Properties
	 label: "forEachTiddler",
	 prompt: "Perform actions on a (sorted) selection of tiddlers",

	 // actions
	 actions: {
		 addToList: {},
		 write: {}
	 }
};

// ---------------------------------------------------------------------------
//  The forEachTiddler Macro Handler 
// ---------------------------------------------------------------------------

config.macros.forEachTiddler.getContainingTiddler = function(e) {
	while(e && !hasClass(e,"tiddler"))
		e = e.parentNode;
	var title = e ? e.getAttribute("tiddler") : null; 
	return title ? store.getTiddler(title) : null;
};

config.macros.forEachTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	// config.macros.forEachTiddler.traceMacroCall(place,macroName,params,wikifier,paramString,tiddler);

	if (!tiddler) tiddler = config.macros.forEachTiddler.getContainingTiddler(place);
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params
	// Parse the "in" clause
	var tiddlyWikiPath = undefined;
	if ((i < params.length) && params[i] == "in") {
		i++;
		if (i >= params.length) {
			this.handleError(place, "TiddlyWiki path expected behind 'in'.");
			return;
		}
		tiddlyWikiPath = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the where clause
	var whereClause ="true";
	if ((i < params.length) && params[i] == "where") {
		i++;
		whereClause = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the sort stuff
	var sortClause = null;
	var sortAscending = true; 
	if ((i < params.length) && params[i] == "sortBy") {
		i++;
		if (i >= params.length) {
			this.handleError(place, "sortClause missing behind 'sortBy'.");
			return;
		}
		sortClause = this.paramEncode(params[i]);
		i++;

		if ((i < params.length) && (params[i] == "ascending" || params[i] == "descending")) {
			 sortAscending = params[i] == "ascending";
			 i++;
		}
	}

	// Parse the script
	var scriptText = null;
	if ((i < params.length) && params[i] == "script") {
		i++;
		scriptText = this.paramEncode((i < params.length) ? params[i] : "");
		i++;
	}

	// Parse the action. 
	// When we are already at the end use the default action
	var actionName = "addToList";
	if (i < params.length) {
	   if (!config.macros.forEachTiddler.actions[params[i]]) {
			this.handleError(place, "Unknown action '"+params[i]+"'.");
			return;
		} else {
			actionName = params[i]; 
			i++;
		}
	} 
	
	// Get the action parameter
	// (the parsing is done inside the individual action implementation.)
	var actionParameter = params.slice(i);


	// --- Processing ------------------------------------------
	try {
		this.performMacro({
				place: place, 
				inTiddler: tiddler,
				whereClause: whereClause, 
				sortClause: sortClause, 
				sortAscending: sortAscending, 
				actionName: actionName, 
				actionParameter: actionParameter, 
				scriptText: scriptText, 
				tiddlyWikiPath: tiddlyWikiPath});

	} catch (e) {
		this.handleError(place, e);
	}
};

// Returns an object with properties "tiddlers" and "context".
// tiddlers holds the (sorted) tiddlers selected by the parameter,
// context the context of the execution of the macro.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlersAndContext = function(parameter) {

	var context = config.macros.forEachTiddler.createContext(parameter.place, parameter.whereClause, parameter.sortClause, parameter.sortAscending, parameter.actionName, parameter.actionParameter, parameter.scriptText, parameter.tiddlyWikiPath, parameter.inTiddler);

	var tiddlyWiki = parameter.tiddlyWikiPath ? this.loadTiddlyWiki(parameter.tiddlyWikiPath) : store;
	context["tiddlyWiki"] = tiddlyWiki;
	
	// Get the tiddlers, as defined by the whereClause
	var tiddlers = this.findTiddlers(parameter.whereClause, context, tiddlyWiki);
	context["tiddlers"] = tiddlers;

	// Sort the tiddlers, when sorting is required.
	if (parameter.sortClause) {
		this.sortTiddlers(tiddlers, parameter.sortClause, parameter.sortAscending, context);
	}

	return {tiddlers: tiddlers, context: context};
};

// Returns the (sorted) tiddlers selected by the parameter.
//
// The action is not yet performed.
//
// @parameter see performMacro
//
config.macros.forEachTiddler.getTiddlers = function(parameter) {
	return this.getTiddlersAndContext(parameter).tiddlers;
};

// Performs the macros with the given parameter.
//
// @param parameter holds the parameter of the macro as separate properties.
//				  The following properties are supported:
//
//						place
//						whereClause
//						sortClause
//						sortAscending
//						actionName
//						actionParameter
//						scriptText
//						tiddlyWikiPath
//
//					All properties are optional. 
//					For most actions the place property must be defined.
//
config.macros.forEachTiddler.performMacro = function(parameter) {
	var tiddlersAndContext = this.getTiddlersAndContext(parameter);

	// Perform the action
	var actionName = parameter.actionName ? parameter.actionName : "addToList";
	var action = config.macros.forEachTiddler.actions[actionName];
	if (!action) {
		this.handleError(parameter.place, "Unknown action '"+actionName+"'.");
		return;
	}

	var actionHandler = action.handler;
	actionHandler(parameter.place, tiddlersAndContext.tiddlers, parameter.actionParameter, tiddlersAndContext.context);
};

// ---------------------------------------------------------------------------
//  The actions 
// ---------------------------------------------------------------------------

// Internal.
//
// --- The addToList Action -----------------------------------------------
//
config.macros.forEachTiddler.actions.addToList.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;

	// Check for extra parameters
	if (parameter.length > p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, "addToList", parameter, p);
		return;
	}

	// Perform the action.
	var list = document.createElement("ul");
	place.appendChild(list);
	for (var i = 0; i < tiddlers.length; i++) {
		var tiddler = tiddlers[i];
		var listItem = document.createElement("li");
		list.appendChild(listItem);
		createTiddlyLink(listItem, tiddler.title, true);
	}
};

abego.parseNamedParameter = function(name, parameter, i) {
	var beginExpression = null;
	if ((i < parameter.length) && parameter[i] == name) {
		i++;
		if (i >= parameter.length) {
			throw "Missing text behind '%0'".format([name]);
		}
		
		return config.macros.forEachTiddler.paramEncode(parameter[i]);
	}
	return null;
}

// Internal.
//
// --- The write Action ---------------------------------------------------
//
config.macros.forEachTiddler.actions.write.handler = function(place, tiddlers, parameter, context) {
	// Parse the parameter
	var p = 0;
	if (p >= parameter.length) {
		this.handleError(place, "Missing expression behind 'write'.");
		return;
	}

	var textExpression = config.macros.forEachTiddler.paramEncode(parameter[p]);
	p++;

	// Parse the "begin" option
	var beginExpression = abego.parseNamedParameter("begin", parameter, p);
	if (beginExpression !== null) 
		p += 2;
	var endExpression = abego.parseNamedParameter("end", parameter, p);
	if (endExpression !== null) 
		p += 2;
	var noneExpression = abego.parseNamedParameter("none", parameter, p);
	if (noneExpression !== null) 
		p += 2;

	// Parse the "toFile" option
	var filename = null;
	var lineSeparator = undefined;
	if ((p < parameter.length) && parameter[p] == "toFile") {
		p++;
		if (p >= parameter.length) {
			this.handleError(place, "Filename expected behind 'toFile' of 'write' action.");
			return;
		}
		
		filename = config.macros.forEachTiddler.getLocalPath(config.macros.forEachTiddler.paramEncode(parameter[p]));
		p++;
		if ((p < parameter.length) && parameter[p] == "withLineSeparator") {
			p++;
			if (p >= parameter.length) {
				this.handleError(place, "Line separator text expected behind 'withLineSeparator' of 'write' action.");
				return;
			}
			lineSeparator = config.macros.forEachTiddler.paramEncode(parameter[p]);
			p++;
		}
	}
	
	// Check for extra parameters
	if (parameter.length > p) {
		config.macros.forEachTiddler.createExtraParameterErrorElement(place, "write", parameter, p);
		return;
	}

	// Perform the action.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(textExpression, context);
	var count = tiddlers.length;
	var text = "";
	if (count > 0 && beginExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(beginExpression, context)(undefined, context, count, undefined);
	
	for (var i = 0; i < count; i++) {
		var tiddler = tiddlers[i];
		text += func(tiddler, context, count, i);
	}
	
	if (count > 0 && endExpression)
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(endExpression, context)(undefined, context, count, undefined);

	if (count == 0 && noneExpression) 
		text += config.macros.forEachTiddler.getEvalTiddlerFunction(noneExpression, context)(undefined, context, count, undefined);
		

	if (filename) {
		if (lineSeparator !== undefined) {
			lineSeparator = lineSeparator.replace(/\\n/mg, "\n").replace(/\\r/mg, "\r");
			text = text.replace(/\n/mg,lineSeparator);
		}
		saveFile(filename, convertUnicodeToUTF8(text));
	} else {
		var wrapper = createTiddlyElement(place, "span");
		wikify(text, wrapper, null/* highlightRegExp */, context.inTiddler);
	}
};


// ---------------------------------------------------------------------------
//  Helpers
// ---------------------------------------------------------------------------

// Internal.
//
config.macros.forEachTiddler.createContext = function(placeParam, whereClauseParam, sortClauseParam, sortAscendingParam, actionNameParam, actionParameterParam, scriptText, tiddlyWikiPathParam, inTiddlerParam) {
	return {
		place : placeParam, 
		whereClause : whereClauseParam, 
		sortClause : sortClauseParam, 
		sortAscending : sortAscendingParam, 
		script : scriptText,
		actionName : actionNameParam, 
		actionParameter : actionParameterParam,
		tiddlyWikiPath : tiddlyWikiPathParam,
		inTiddler : inTiddlerParam, // the tiddler containing the <<forEachTiddler ...>> macro call.
		viewerTiddler : config.macros.forEachTiddler.getContainingTiddler(placeParam) // the tiddler showing the forEachTiddler result
	};
};

// Internal.
//
// Returns a TiddlyWiki with the tiddlers loaded from the TiddlyWiki of 
// the given path.
//
config.macros.forEachTiddler.loadTiddlyWiki = function(path, idPrefix) {
	if (!idPrefix) {
		idPrefix = "store";
	}
	var lenPrefix = idPrefix.length;
	
	// Read the content of the given file
	var content = loadFile(this.getLocalPath(path));
	if(content === null) {
		throw "TiddlyWiki '"+path+"' not found.";
	}
	
	var tiddlyWiki = new TiddlyWiki();

	// Starting with TW 2.2 there is a helper function to import the tiddlers
	if (tiddlyWiki.importTiddlyWiki) {
		if (!tiddlyWiki.importTiddlyWiki(content))
			throw "File '"+path+"' is not a TiddlyWiki.";
		tiddlyWiki.dirty = false;
		return tiddlyWiki;
	}
	
	// The legacy code, for TW < 2.2
	
	// Locate the storeArea div's
	var posOpeningDiv = content.indexOf(startSaveArea);
	var posClosingDiv = content.lastIndexOf(endSaveArea);
	if((posOpeningDiv == -1) || (posClosingDiv == -1)) {
		throw "File '"+path+"' is not a TiddlyWiki.";
	}
	var storageText = content.substr(posOpeningDiv + startSaveArea.length, posClosingDiv);
	
	// Create a "div" element that contains the storage text
	var myStorageDiv = document.createElement("div");
	myStorageDiv.innerHTML = storageText;
	myStorageDiv.normalize();
	
	// Create all tiddlers in a new TiddlyWiki
	// (following code is modified copy of TiddlyWiki.prototype.loadFromDiv)
	var store = myStorageDiv.childNodes;
	for(var t = 0; t < store.length; t++) {
		var e = store[t];
		var title = null;
		if(e.getAttribute)
			title = e.getAttribute("tiddler");
		if(!title && e.id && e.id.substr(0,lenPrefix) == idPrefix)
			title = e.id.substr(lenPrefix);
		if(title && title !== "") {
			var tiddler = tiddlyWiki.createTiddler(title);
			tiddler.loadFromDiv(e,title);
		}
	}
	tiddlyWiki.dirty = false;

	return tiddlyWiki;
};


	
// Internal.
//
// Returns a function that has a function body returning the given javaScriptExpression.
// The function has the parameters:
// 
//	 (tiddler, context, count, index)
//
config.macros.forEachTiddler.getEvalTiddlerFunction = function (javaScriptExpression, context) {
	var script = context["script"];
	var functionText = "var theFunction = function(tiddler, context, count, index) { return "+javaScriptExpression+"}";
	var fullText = (script ? script+";" : "")+functionText+";theFunction;";
	return eval(fullText);
};

// Internal.
//
config.macros.forEachTiddler.findTiddlers = function(whereClause, context, tiddlyWiki) {
	var result = [];
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(whereClause, context);
	tiddlyWiki.forEachTiddler(function(title,tiddler) {
		if (func(tiddler, context, undefined, undefined)) {
			result.push(tiddler);
		}
	});
	return result;
};

// Internal.
//
config.macros.forEachTiddler.createExtraParameterErrorElement = function(place, actionName, parameter, firstUnusedIndex) {
	var message = "Extra parameter behind '"+actionName+"':";
	for (var i = firstUnusedIndex; i < parameter.length; i++) {
		message += " "+parameter[i];
	}
	this.handleError(place, message);
};

// Internal.
//
config.macros.forEachTiddler.sortAscending = function(tiddlerA, tiddlerB) {
	var result = 
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue) 
			? 0
			: (tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
			   ? -1 
			   : +1; 
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortDescending = function(tiddlerA, tiddlerB) {
	var result = 
		(tiddlerA.forEachTiddlerSortValue == tiddlerB.forEachTiddlerSortValue) 
			? 0
			: (tiddlerA.forEachTiddlerSortValue < tiddlerB.forEachTiddlerSortValue)
			   ? +1 
			   : -1; 
	return result;
};

// Internal.
//
config.macros.forEachTiddler.sortTiddlers = function(tiddlers, sortClause, ascending, context) {
	// To avoid evaluating the sortClause whenever two items are compared 
	// we pre-calculate the sortValue for every item in the array and store it in a 
	// temporary property ("forEachTiddlerSortValue") of the tiddlers.
	var func = config.macros.forEachTiddler.getEvalTiddlerFunction(sortClause, context);
	var count = tiddlers.length;
	var i;
	for (i = 0; i < count; i++) {
		var tiddler = tiddlers[i];
		tiddler.forEachTiddlerSortValue = func(tiddler,context, undefined, undefined);
	}

	// Do the sorting
	tiddlers.sort(ascending ? this.sortAscending : this.sortDescending);

	// Delete the temporary property that holds the sortValue.	
	for (i = 0; i < tiddlers.length; i++) {
		delete tiddlers[i].forEachTiddlerSortValue;
	}
};


// Internal.
//
config.macros.forEachTiddler.trace = function(message) {
	displayMessage(message);
};

// Internal.
//
config.macros.forEachTiddler.traceMacroCall = function(place,macroName,params) {
	var message ="<<"+macroName;
	for (var i = 0; i < params.length; i++) {
		message += " "+params[i];
	}
	message += ">>";
	displayMessage(message);
};


// Internal.
//
// Creates an element that holds an error message
// 
config.macros.forEachTiddler.createErrorElement = function(place, exception) {
	var message = (exception.description) ? exception.description : exception.toString();
	return createTiddlyElement(place,"span",null,"forEachTiddlerError","<<forEachTiddler ...>>: "+message);
};

// Internal.
//
// @param place [may be null]
//
config.macros.forEachTiddler.handleError = function(place, exception) {
	if (place) {
		this.createErrorElement(place, exception);
	} else {
		throw exception;
	}
};

// Internal.
//
// Encodes the given string.
//
// Replaces 
//	 "$))" to ">>"
//	 "$)" to ">"
//
config.macros.forEachTiddler.paramEncode = function(s) {
	var reGTGT = new RegExp("\\$\\)\\)","mg");
	var reGT = new RegExp("\\$\\)","mg");
	return s.replace(reGTGT, ">>").replace(reGT, ">");
};

// Internal.
//
// Returns the given original path (that is a file path, starting with "file:")
// as a path to a local file, in the systems native file format.
//
// Location information in the originalPath (i.e. the "#" and stuff following)
// is stripped.
// 
config.macros.forEachTiddler.getLocalPath = function(originalPath) {
	// Remove any location part of the URL
	var hashPos = originalPath.indexOf("#");
	if(hashPos != -1)
		originalPath = originalPath.substr(0,hashPos);
	// Convert to a native file format assuming
	// "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
	// "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
	// "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
	// "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
	var localPath;
	if(originalPath.charAt(9) == ":") // pc local file
		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file://///") === 0) // FireFox pc network file
		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
	else if(originalPath.indexOf("file:///") === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(7));
	else if(originalPath.indexOf("file:/") === 0) // mac/unix local file
		localPath = unescape(originalPath.substr(5));
	else // pc network file
		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");	
	return localPath;
};

// ---------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// ---------------------------------------------------------------------------
//
setStylesheet(
	".forEachTiddlerError{color: #ffffff;background-color: #880000;}",
	"forEachTiddler");

//============================================================================
// End of forEachTiddler Macro
//============================================================================


//============================================================================
// String.startsWith Function
//============================================================================
//
// Returns true if the string starts with the given prefix, false otherwise.
//
version.extensions["String.startsWith"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.startsWith = function(prefix) {
	var n =  prefix.length;
	return (this.length >= n) && (this.slice(0, n) == prefix);
};



//============================================================================
// String.endsWith Function
//============================================================================
//
// Returns true if the string ends with the given suffix, false otherwise.
//
version.extensions["String.endsWith"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.endsWith = function(suffix) {
	var n = suffix.length;
	return (this.length >= n) && (this.right(n) == suffix);
};


//============================================================================
// String.contains Function
//============================================================================
//
// Returns true when the string contains the given substring, false otherwise.
//
version.extensions["String.contains"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
String.prototype.contains = function(substring) {
	return this.indexOf(substring) >= 0;
};

//============================================================================
// Array.indexOf Function
//============================================================================
//
// Returns the index of the first occurance of the given item in the array or 
// -1 when no such item exists.
//
// @param item [may be null]
//
version.extensions["Array.indexOf"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.indexOf = function(item) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == item) {
			return i;
		}
	}
	return -1;
};

//============================================================================
// Array.contains Function
//============================================================================
//
// Returns true when the array contains the given item, otherwise false. 
//
// @param item [may be null]
//
version.extensions["Array.contains"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.contains = function(item) {
	return (this.indexOf(item) >= 0);
};

//============================================================================
// Array.containsAny Function
//============================================================================
//
// Returns true when the array contains at least one of the elements 
// of the item. Otherwise (or when items contains no elements) false is returned.
//
version.extensions["Array.containsAny"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.containsAny = function(items) {
	for(var i = 0; i < items.length; i++) {
		if (this.contains(items[i])) {
			return true;
		}
	}
	return false;
};


//============================================================================
// Array.containsAll Function
//============================================================================
//
// Returns true when the array contains all the items, otherwise false.
// 
// When items is null false is returned (even if the array contains a null).
//
// @param items [may be null] 
//
version.extensions["Array.containsAll"] = {major: 1, minor: 0, revision: 0, date: new Date(2005,11,20), provider: "http://tiddlywiki.abego-software.de"};
//
Array.prototype.containsAll = function(items) {
	for(var i = 0; i < items.length; i++) {
		if (!this.contains(items[i])) {
			return false;
		}
	}
	return true;
};


} // of "install only once"

// Used Globals (for JSLint) ==============
// ... DOM
/*global 	document */
// ... TiddlyWiki Core
/*global 	convertUnicodeToUTF8, createTiddlyElement, createTiddlyLink, 
			displayMessage, endSaveArea, hasClass, loadFile, saveFile, 
			startSaveArea, store, wikify */
//}}}


/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005 ([[www.abego-software.de|http://www.abego-software.de]])

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/

/***
<<checkForDataTiddlerPlugin>>
|''Name:''|FormTiddlerPlugin|
|''Version:''|1.0.7 (2012-04-19)|
|''Summary:''|Use form-based tiddlers to enter your tiddler data using text fields, listboxes, checkboxes etc. (All standard HTML Form input elements supported).|
|''Documentation:''|[[Introduction|FormTiddler Introduction]], [[Examples|FormTiddler Examples]]|
|''Source:''|http://tiddlywiki.abego-software.de/#FormTiddlerPlugin|
|''Twitter:''|[[@abego|https://twitter.com/#!/abego]]|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''License:''|[[BSD open source license|http://www.abego-software.de/legal/apl-v10.html]]|
|''Requires:''|DataTiddlerPlugin|
!Description
Use form-based tiddlers to enter your tiddler data using text fields, listboxes, checkboxes etc. (All standard HTML Form input elements supported).

''Syntax:'' 
|>|{{{<<}}}''formTiddler'' //tiddlerName//{{{>>}}}|
|//tiddlerName//|The name of the FormTemplate tiddler to be used to edit the data of the tiddler containing the macro.|

|>|{{{<<}}}''newTiddlerWithForm'' //formTemplateName// //buttonLabel// [//titleExpression// [''askUser'']] {{{>>}}}|
|//formTemplateName//|The name of the tiddler that defines the form the new tiddler should use.|
|//buttonLabel//|The label of the button|
|//titleExpression//|A (quoted) JavaScript String expression that defines the title (/name) of the new tiddler.|
|''askUser''|Typically the user is not asked for the title when a title is specified (and not yet used). When ''askUser'' is given the user will be asked in any case. This may be used when the calculated title is just a suggestion that must be confirmed by the user|
|>|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|

For details and how to use the macros see the [[introduction|FormTiddler Introduction]] and the [[examples|FormTiddler Examples]].

!Revision history
* v1.0.7 (2012-04-19)
** Bugfix: FormTiddlerPlugin fails to handle tiddlers with certain names in TW 2.6.5. Thanks to Terence Jacyno, Thomas Manley, Sebastjan Hribar, and Eric Shulman for reporting or suggesting a solution.
** remove warnings
* v1.0.6 (2007-06-24)
** Fixed problem when using SELECT component in Internet Explorer (thanks to MaikBoenig for reporting)
* v1.0.5 (2006-02-24)
** Removed "debugger;" instruction
* v1.0.4 (2006-02-07)
** Bug: On IE no data is written to data section when field values changed (thanks to KenGirard for reporting)
* v1.0.3 (2006-02-05)
** Bug: {{{"No form template specified in <<formTiddler>>"}}} when using formTiddler macro on InternetExplorer (thanks to KenGirard for reporting)
* v1.0.2 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.1 (2005-12-22)
** Features: 
*** Support InternetExplorer
*** Added newTiddlerWithForm Macro
* v1.0.0 (2005-12-14)
** initial version
!Source Code
***/
//{{{

//============================================================================
//============================================================================
//						FormTiddlerPlugin
//============================================================================
//============================================================================

if (!window.abego) window.abego = {};

abego.getOptionsValue = function(element,i) {
	var v = element.options[i].value;
	if (!v && element.options[i].text)
		v = element.options[i].text;
	return v;
};

version.extensions.FormTiddlerPlugin = {
	major: 1, minor: 0, revision: 7,
	date: new Date(2012, 3, 19), 
	type: 'plugin',
	source: "http://tiddlywiki.abego-software.de/#FormTiddlerPlugin"
};

// For backward compatibility with v1.2.x
//
if (!window.story) window.story=window; 
if (!TiddlyWiki.prototype.getTiddler) TiddlyWiki.prototype.getTiddler = function(title) { return t = this.tiddlers[title]; return (t != undefined && t instanceof Tiddler) ? t : null; }; 

//============================================================================
// formTiddler Macro
//============================================================================

// -------------------------------------------------------------------------------
// Configurations and constants 
// -------------------------------------------------------------------------------

config.macros.formTiddler = {
	// Standard Properties
	label: "formTiddler",
	prompt: "Edit tiddler data using forms",

	// Define the "setters" that set the values of INPUT elements of a given type
	// (must match the corresponding "getter")
	setter: {  
		button:				function(e, value) {/*contains no data */ },
		checkbox:			function(e, value) {e.checked = value;},
		file:				function(e, value) {try {e.value = value;} catch(e) {/* ignore, possibly security error*/}},
		hidden:				function(e, value) {e.value = value;},
		password:			function(e, value) {e.value = value;},
		radio:				function(e, value) {e.checked = (e.value == value);},
		reset:				function(e, value) {/*contains no data */ },
		"select-one":		function(e, value) {config.macros.formTiddler.setSelectOneValue(e,value);},
		"select-multiple":	function(e, value) {config.macros.formTiddler.setSelectMultipleValue(e,value);},
		submit:				function(e, value) {/*contains no data */},
		text:				function(e, value) {e.value = value;},
		textarea:			function(e, value) {e.value = value;}
	},

	// Define the "getters" that return the value of INPUT elements of a given type
	// Return undefined to not store any data.
	getter: {  
		button:				function(e, value) {return undefined;},
		checkbox:			function(e, value) {return e.checked;},
		file:				function(e, value) {return e.value;},
		hidden:				function(e, value) {return e.value;},
		password:			function(e, value) {return e.value;},
		radio:				function(e, value) {return e.checked ? e.value : undefined;},
		reset:				function(e, value) {return undefined;},
		"select-one":		function(e, value) {return config.macros.formTiddler.getSelectOneValue(e);},
		"select-multiple":	function(e, value) {return config.macros.formTiddler.getSelectMultipleValue(e);},
		submit:				function(e, value) {return undefined;},
		text:				function(e, value) {return e.value;},
		textarea:			function(e, value) {return e.value;}
	}
};


// -------------------------------------------------------------------------------
// The formTiddler Macro Handler 
// -------------------------------------------------------------------------------

config.macros.formTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if (!config.macros.formTiddler.checkForExtensions(place, macroName)) {
		return;
	}
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params

	// get the name of the form template tiddler
	var formTemplateName = undefined;
	if (i < params.length) {
		formTemplateName = params[i];
		i++;
	}

	if (!formTemplateName) {
		config.macros.formTiddler.createErrorElement(place, "No form template specified in <<" + macroName + ">>.");
		return;
	}


	// --- Processing ------------------------------------------

	// Get the form template text. 
	// (This contains the INPUT elements for the form.)
	var formTemplateTiddler = store.getTiddler(formTemplateName);
	if (!formTemplateTiddler) {
		config.macros.formTiddler.createErrorElement(place, "Form template '" + formTemplateName + "' not found.");
		return;
	}
	var templateText = formTemplateTiddler.text;
	if(!templateText) {
		// Shortcut: when template text is empty we do nothing.
		return;
	}

	// Get the name of the tiddler containing this "formTiddler" macro
	// (i.e. the tiddler, that will be edited and that contains the data)
	var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(place);

	// Append a "form" element. 
	var formName = "form"+formTemplateName+"__"+tiddlerName;
	var e = document.createElement("form");
	e.setAttribute("name", formName);
	place.appendChild(e);

	// "Embed" the elements defined by the templateText (i.e. the INPUT elements) 
	// into the "form" element we just created
	wikify(templateText, e);

	// Initialize the INPUT elements.
	config.macros.formTiddler.initValuesAndHandlersInFormElements(formName, DataTiddler.getDataObject(tiddlerName));
};


// -------------------------------------------------------------------------------
// Form Data Access 
// -------------------------------------------------------------------------------

// Internal.
//
// Initialize the INPUT elements of the form with the values of their "matching"
// data fields in the tiddler. Also setup the onChange handler to ensure that
// changes in the INPUT elements are stored in the tiddler's data.
//
config.macros.formTiddler.initValuesAndHandlersInFormElements = function(formName, data) {
	// config.macros.formTiddler.trace("initValuesAndHandlersInFormElements(formName="+formName+", data="+data+")");

	// find the form
	var form = config.macros.formTiddler.findForm(formName);
	if (!form) {
		return;
	}

	try {
		var elems = form.elements;
		for (var i = 0; i < elems.length; i++) {
			var c = elems[i];
		
			var setter = config.macros.formTiddler.setter[c.type];
			if (setter) {
				var value = data[c.name];
				if (value != null) {
					setter(c, value);
				}
				c.onchange = onFormTiddlerChange;
			} else {
				config.macros.formTiddler.displayFormTiddlerError("No setter defined for INPUT element of type '"+c.type+"'. (Element '"+c.name+"' in form '"+formName+"')");
			}
		}
	} catch(e) {
		config.macros.formTiddler.displayFormTiddlerError("Error when updating elements with new formData. "+e);
	}
};


// Internal.
//
// @return [may be null]
//
config.macros.formTiddler.findForm = function(formName) {
	// We must manually iterate through the document's forms, since
	// IE does not support the "document[formName]" approach

	var forms = window.document.forms;
	for (var i = 0; i < forms.length; i++) {
		var form = forms[i];
		if (form.name == formName) {
			return form;
		}
	}

	return null;
};


// Internal.
//
config.macros.formTiddler.setSelectOneValue = function(element,value) {
	var n = element.options.length;
	for (var i = 0; i < n; i++) {
		element.options[i].selected = abego.getOptionsValue(element,i) == value;
	}
};

// Internal.
//
config.macros.formTiddler.setSelectMultipleValue = function(element,value) {
	var values = {};
	for (var i = 0; i < value.length; i++) {
		values[value[i]] = true;
	}
	
	var n = element.length;
	for (var i = 0; i < n; i++) {
		element.options[i].selected = !(!values[abego.getOptionsValue(element,i)]);
	}
};

// Internal.
//
config.macros.formTiddler.getSelectOneValue = function(element) {
	var i = element.selectedIndex;
	return (i >= 0) ? abego.getOptionsValue(element,i) : null;
};

// Internal.
//
config.macros.formTiddler.getSelectMultipleValue = function(element) {
	var values = [];
	var n = element.length;
	for (var i = 0; i < n; i++) {
		if (element.options[i].selected) {
			values.push(abego.getOptionsValue(element,i));
		}
	}
	return values;
};



// -------------------------------------------------------------------------------
// Helpers 
// -------------------------------------------------------------------------------

// Internal.
//
config.macros.formTiddler.checkForExtensions = function(place,macroName) {
	if (!version.extensions.DataTiddlerPlugin) {
		config.macros.formTiddler.createErrorElement(place, "<<" + macroName + ">> requires the DataTiddlerPlugin. (You can get it from http://tiddlywiki.abego-software.de/#DataTiddlerPlugin)");
		return false;
	}
	return true;
};

// Internal.
//
// Displays a trace message in the "TiddlyWiki" message pane.
// (used for debugging)
//
config.macros.formTiddler.trace = function(s) {
	displayMessage("Trace: "+s);
};

// Internal.
//
// Display some error message in the "TiddlyWiki" message pane.
//
config.macros.formTiddler.displayFormTiddlerError = function(s) {
	alert("FormTiddlerPlugin Error: "+s);
};

// Internal.
//
// Creates an element that holds an error message
// 
config.macros.formTiddler.createErrorElement = function(place, message) {
	return createTiddlyElement(place,"span",null,"formTiddlerError",message);
};

// Internal.
//
// Returns the name of the tiddler containing the given element.
// 
config.macros.formTiddler.getContainingTiddlerName = function(element) {
	return story.findContainingTiddler(element).getAttribute("tiddler");
};

// -------------------------------------------------------------------------------
// Event Handlers 
// -------------------------------------------------------------------------------

// This function must be called by the INPUT elements whenever their
// data changes. Typically this is done through an "onChange" handler.
//
function onFormTiddlerChange (e) {
	// config.macros.formTiddler.trace("onFormTiddlerChange "+e);

	if (!e) e = window.event;

	var target = resolveTarget(e);
	var tiddlerName = config.macros.formTiddler.getContainingTiddlerName(target);
	var getter = config.macros.formTiddler.getter[target.type];
	if (getter) {
		var value = getter(target);
		DataTiddler.setData(tiddlerName, target.name, value);
	} else {
		config.macros.formTiddler.displayFormTiddlerError("No getter defined for INPUT element of type '"+target.type+"'. (Element '"+target.name+"' used in tiddler '"+tiddlerName+"')");
	}
}

// ensure that the function can be used in HTML event handler
window.onFormTiddlerChange = onFormTiddlerChange;


// -------------------------------------------------------------------------------
// Stylesheet Extensions (may be overridden by local StyleSheet)
// -------------------------------------------------------------------------------

setStylesheet(
	".formTiddlerError{color: #ffffff;background-color: #880000;}",
	"formTiddler");


//============================================================================
// checkForDataTiddlerPlugin Macro
//============================================================================

config.macros.checkForDataTiddlerPlugin = {
	// Standard Properties
	label: "checkForDataTiddlerPlugin",
	version: {major: 1, minor: 0, revision: 0, date: new Date(2005, 12, 14)},
	prompt: "Check if the DataTiddlerPlugin exists"
};

config.macros.checkForDataTiddlerPlugin.handler = function(place,macroName,params) {
	config.macros.formTiddler.checkForExtensions(place, config.macros.formTiddler.label);
};



//============================================================================
// newTiddlerWithForm Macro
//============================================================================

config.macros.newTiddlerWithForm = {
	// Standard Properties
	label: "newTiddlerWithForm",
	version: {major: 1, minor: 0, revision: 1, date: new Date(2006, 1, 6)},
	prompt: "Creates a new Tiddler with a <<formTiddler ...>> macro"
};

config.macros.newTiddlerWithForm.handler = function(place,macroName,params) {
	// --- Parsing ------------------------------------------

	var i = 0; // index running over the params

	// get the name of the form template tiddler
	var formTemplateName = undefined;
	if (i < params.length) {
		formTemplateName = params[i];
		i++;
	}

	if (!formTemplateName) {
		config.macros.formTiddler.createErrorElement(place, "No form template specified in <<" + macroName + ">>.");
		return;
	}

	// get the button label
	var buttonLabel = undefined;
	if (i < params.length) {
		buttonLabel = params[i];
		i++;
	}

	if (!buttonLabel) {
		config.macros.formTiddler.createErrorElement(place, "No button label specified in <<" + macroName + ">>.");
		return;
	}

	// get the (optional) tiddlerName script and "askUser"
	var tiddlerNameScript = undefined;
	var askUser = false;
	if (i < params.length) {
		tiddlerNameScript = params[i];
		i++;

		if (i < params.length && params[i] == "askUser") {
			askUser = true;
			i++;
		}
	}

	// --- Processing ------------------------------------------

	if(!readOnly) {
		var onClick = function() {
			var tiddlerName = undefined;
			if (tiddlerNameScript) {
				try {
					tiddlerName = eval(tiddlerNameScript);
				} catch (ex) {
				}
			}
			if (!tiddlerName || askUser) {
				tiddlerName = prompt("Please specify a tiddler name.", askUser ? tiddlerName : "");
			}
			while (tiddlerName && store.getTiddler(tiddlerName)) {
				tiddlerName = prompt("A tiddler named '"+tiddlerName+"' already exists.\n\n"+"Please specify a tiddler name.", tiddlerName);
			}

			// tiddlerName is either null (user canceled) or a name that is not yet in the store.
			if (tiddlerName) {
				var body = "<<formTiddler [["+formTemplateName+"]]>>";
				var tags = [];
				store.saveTiddler(tiddlerName,tiddlerName,body,config.options.txtUserName,new Date(),tags);
				story.displayTiddler(null,tiddlerName,1);
			}
		};

		createTiddlyButton(place,buttonLabel,buttonLabel,onClick);
    }
};

//}}}


/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005 ([[www.abego-software.de|http://www.abego-software.de]])

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.

Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
//{{{
config.macros.formTiddler.getContainingTiddlerNameOverride_base = config.macros.formTiddler.getContainingTiddlerName;
config.macros.formTiddler.getContainingTiddlerName = function (element) {
	var e = element;
	while (e) {
		var baseTiddler = e.getAttribute ? e.getAttribute("basetiddler") : null;
		if ((baseTiddler !== undefined) && (baseTiddler !== null)) {
			return baseTiddler;
		}
		e = jQuery(e).hasClass("popup") && Popup.stack[0] ? Popup.stack[0].root : e.parentNode;
	}
	return config.macros.formTiddler.getContainingTiddlerNameOverride_base(element);
};
//}}}
//{{{
var code = eval("config.macros.tiddler.handler").toString();
var newCode = null;
var startPosition = code.indexOf("refresh");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition - 1) + "basetiddler: tiddler ? tiddler.title : null, " + code.substring(startPosition - 1);
	code = newCode;
}
if (newCode != null) {
	eval("config.macros.tiddler.handler = " + newCode);
}
//}}}
//{{{
var insertionIndex = config.textPrimitives.urlPattern.indexOf("|mailto|");
config.textPrimitives.urlPattern = config.textPrimitives.urlPattern.substring(0, insertionIndex) + "|tel" + config.textPrimitives.urlPattern.substring(insertionIndex);
for (var i = 0; i < config.formatters.length; i++) {
	if (config.formatters[i]["name"] == "urlLink") {
		config.formatters[i]["match"] = config.textPrimitives.urlPattern;
		break;
	}
}
//}}}
<html><input type="checkbox" id="freeBusyPublishOnServer" value="1" /></html> [[publish on server|update.php]] ([[calendar|FreeBusy Calendar Info]], [[server|FreeBusy Server]], [[password|FreeBusy Server Password]], [[template|FreeBusy Calendar Template]])

<html>tag filter: <input type="text" id="freebusy_filter_tag" size="75" /></html>

<script>

window.determineDaysBetweenDates = function determineDaysBetweenDates(date1, date2) {
	return ((date2.getTime() - date1.getTime() + ((date1.getTimezoneOffset() - date2.getTimezoneOffset()) * 60 * 1000)) / 1000 / 60 / 60 / 24) | 0;   // "| 0" casts the division result to an integer
}

var excludeTags = config.options.txtReminderPublishExcludeTags ? config.options.txtReminderPublishExcludeTags.readBracketedList() : null;
var excludeTagsCondition = "";
for (var i = 0; excludeTags && (i < excludeTags.length); i++) {
	excludeTagsCondition = excludeTagsCondition  + (excludeTagsCondition.length ? " AND " : "") + "NOT " + excludeTags[i];
}
$("freebusy_filter_tag").value = excludeTagsCondition;

</script><script label="Generate Free/Busy">

var formatUTCDateString = function(date) {
	return date.getUTCFullYear()
			+ (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1)
			+ (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate()
			+ "T"
			+ (date.getUTCHours() < 10 ? "0" : "") + date.getUTCHours()
			+ (date.getUTCMinutes() < 10 ? "0" : "") + date.getUTCMinutes()
			+ (date.getUTCSeconds() < 10 ? "0" : "") + date.getUTCSeconds()
			+ "Z";
}

var currentDate = new Date();
var startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1, 0, 0, 0, 0);
var endDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 2, 1, 0, 0, 0, 0);
var numberOfDays = window.determineDaysBetweenDates(startDate, endDate);

var organizerName = "Some User";
var organizerEmail = "someuser@example.com";
if (store.getTiddler('FreeBusy Calendar Info')) {
	var tiddlerText = store.getTiddler('FreeBusy Calendar Info').text;
	try {
		var tiddlerMap = eval("function _convertFilenameMap() {return " + tiddlerText + "}; _convertFilenameMap();");
		if (tiddlerMap && tiddlerMap["organizer"]) {
			organizerName = tiddlerMap["organizer"]["name"] ? tiddlerMap["organizer"]["name"] : organizerName;
			organizerEmail = tiddlerMap["organizer"]["email"] ? tiddlerMap["organizer"]["email"] : organizerEmail;
		}
	} catch (e) {}
}

var freebusy
		= "BEGIN:VCALENDAR\r\n"
		+ "PRODID:-//Galasoft Inc.//TW Notes v1.0//EN\r\n"
		+ "VERSION:2.0\r\n"
		+ "METHOD:PUBLISH\r\n"
		+ "BEGIN:VFREEBUSY\r\n"
		+ "ORGANIZER;CN=" + organizerName + ":MAILTO:" + organizerEmail + "\r\n"
		+ "DTSTAMP:" + formatUTCDateString(currentDate) + "\r\n"
		+ "DTSTART:" + formatUTCDateString(startDate) + "\r\n"
		+ "DTEND:" + formatUTCDateString(endDate) + "\r\n";

var freeBusyEvents = new ScheduleReminderEvents();

var label = place.innerHTML;

var determineFreeBusyForDay = function() {
	var eventDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + dayCounter, 0, 0, 0, 0);
	
	var date = eventDate;
	
	var excludeTagsCondition = $("freebusy_filter_tag").value.length ? " AND " + $("freebusy_filter_tag").value : "";
	
	var freeBusyEventList = [];
	freeBusyEvents.determineEvents(date, "(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT Trash" + excludeTagsCondition);
	for (var i = 0; i < freeBusyEvents.list.length; i++) {
		if (freeBusyEvents.list[i].start && freeBusyEvents.list[i].end && (freeBusyEvents.list[i].start != freeBusyEvents.list[i].end) && !freeBusyEvents.list[i].reminder.isTagged(excludeTagsCondition)) {
			var eventStartDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventStartDateTime.setHours(Number(freeBusyEvents.list[i].start.substring(0, freeBusyEvents.list[i].start.indexOf(":"))));
			eventStartDateTime.setMinutes(Number(freeBusyEvents.list[i].start.substring(freeBusyEvents.list[i].start.indexOf(":") + 1)));
			
			var eventEndDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventEndDateTime.setHours(Number(freeBusyEvents.list[i].end.substring(0, freeBusyEvents.list[i].end.indexOf(":"))));
			eventEndDateTime.setMinutes(Number(freeBusyEvents.list[i].end.substring(freeBusyEvents.list[i].end.indexOf(":") + 1)));
			
			var event = (eventEndDateTime.getTime() > eventStartDateTime.getTime()) ? {start: eventStartDateTime, end: eventEndDateTime} : {start: eventEndDateTime, end: eventStartDateTime};
			
			for (var j = 0; j <= freeBusyEventList.length; j++) {
				if (j == freeBusyEventList.length) {
					freeBusyEventList[freeBusyEventList.length] = event;
					break;
				} else if ((event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) && (event["end"].getTime() < freeBusyEventList[j]["start"].getTime())) {
					freeBusyEventList.splice(j, 0, event);
					break;
				} else if (!((event["start"].getTime() > freeBusyEventList[j]["end"].getTime()) && (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()))) {
					if (event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) {
						freeBusyEventList[j]["start"] = event["start"];
					}
					if (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()) {
						freeBusyEventList[j]["end"] = event["end"];
					}
					break;
				}
			}
		}
	}
	
	for (var i = 0; i < freeBusyEventList.length; i++) {
		freebusy = freebusy + "FREEBUSY:" + formatUTCDateString(freeBusyEventList[i]["start"]);
		freebusy = freebusy + "/" + formatUTCDateString(freeBusyEventList[i]["end"]) + "\r\n";
	}
}

var dayCounter = 0;

window.determineFreeBusy = function() {
	determineFreeBusyForDay();
	dayCounter++;
	
	place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
	
	if (dayCounter < numberOfDays) {
		setTimeout("window.determineFreeBusy()", 50);
	} else {
		freebusy = freebusy 
				+ "END:VFREEBUSY\r\n"
				+ "END:VCALENDAR\r\n";
		
		place.innerHTML = label;
		
		wikify("{{{\n" + freebusy.replace(/\r\n/g, "\n") + "}}}", place.parentNode);
		
		if (jQuery('#freeBusyPublishOnServer').is(':checked') && store.getTiddler('FreeBusy Calendar Info') && store.getTiddler('FreeBusy Server') && store.getTiddler('FreeBusy Server Password') && (store.getTiddler('FreeBusy Server Password').text.length > 0)) {
			var filename = store.getTiddler('FreeBusy Calendar Info').text;
			try {
				var filenameMap = eval("function _convertFilenameMap() {return " + filename + "}; _convertFilenameMap();");
				if (filenameMap && filenameMap["freebusy"]) {
					filename = filenameMap["freebusy"];
				}
			} catch (e) {}
			var encryptedData = Aes.Ctr.encrypt("filename:" + filename + "\n" + freebusy, store.getTiddler('FreeBusy Server Password').text, 256);
			
			var html = '<html><head><title></title></head><body><form action="' + store.getTiddler('FreeBusy Server').text + '" method="post"><input type="hidden" name="data" value="' + encryptedData + '"/></form></body></html>';
			
			var iframe = document.getElementById("freeBusyFrame");
			if (iframe) {
				document.body.removeChild(iframe);
			}
			iframe = createTiddlyElement(document.body, "iframe", "freeBusyFrame");
			iframe.style.width="0px";
			iframe.style.height="0px";
			iframe.style.border="0px";
			
			var iframeDocument = iframe.document;
			if (iframe.contentDocument) {
				// For NS6
				iframeDocument = iframe.contentDocument;
			} else if (iframe.contentWindow) {
				// For IE5.5 and IE6
				iframeDocument = iframe.contentWindow.document;
			}
			iframeDocument.open();
			iframeDocument.writeln(html);
			iframeDocument.close();
			
			var publicationResult = "unknown";
			
			jQuery.receiveMessage(function(e) {
				publicationResult = e.data;
				if (publicationResult == "success") {
					displayMessage("The calendar was published successfully.");
				} else if (publicationResult == "failure") {
					displayMessage("The calendar could not be published.  Check server details.");
				}
			});
			
			try {
				iframeDocument.getElementsByTagName("form")[0].submit();
			} catch (e) {
				displayMessage("The calendar could not be published.  Check server details.");
			}
		}
	}
}

place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
setTimeout("window.determineFreeBusy()", 50);

</script>

<script label="Generate iCalendar">

var formatUTCDateString = function(date) {
	return date.getUTCFullYear()
			+ (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1)
			+ (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate()
			+ "T"
			+ (date.getUTCHours() < 10 ? "0" : "") + date.getUTCHours()
			+ (date.getUTCMinutes() < 10 ? "0" : "") + date.getUTCMinutes()
			+ (date.getUTCSeconds() < 10 ? "0" : "") + date.getUTCSeconds()
			+ "Z";
}

var currentDate = new Date();
var startDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1, 0, 0, 0, 0);
var endDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 2, 1, 0, 0, 0, 0);
var numberOfDays =  window.determineDaysBetweenDates(startDate, endDate);

var freebusy
		= "BEGIN:VCALENDAR\r\n"
		+ "PRODID:-//Galasoft Inc.//TW Notes v1.0//EN\r\n"
		+ "VERSION:2.0\r\n";

var freeBusyEvents = new ScheduleReminderEvents();

var label = place.innerHTML;

var counter = 0;

var organizerName = "Some User";
var organizerEmail = "someuser@example.com";
if (store.getTiddler('FreeBusy Calendar Info')) {
	var tiddlerText = store.getTiddler('FreeBusy Calendar Info').text;
	try {
		var tiddlerMap = eval("function _convertFilenameMap() {return " + tiddlerText + "}; _convertFilenameMap();");
		if (tiddlerMap && tiddlerMap["organizer"]) {
			organizerName = tiddlerMap["organizer"]["name"] ? tiddlerMap["organizer"]["name"] : organizerName;
			organizerEmail = tiddlerMap["organizer"]["email"] ? tiddlerMap["organizer"]["email"] : organizerEmail;
		}
	} catch (e) {}
}

var determineFreeBusyForDay = function() {
	var eventDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + dayCounter, 0, 0, 0, 0);
	
	var date = eventDate;
	
	var excludeTagsCondition = $("freebusy_filter_tag").value.length ? " AND " + $("freebusy_filter_tag").value : "";
	
	var freeBusyEventList = [];
	freeBusyEvents.determineEvents(date, "(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT Trash" + excludeTagsCondition);
	for (var i = 0; i < freeBusyEvents.list.length; i++) {
		if (freeBusyEvents.list[i].start && freeBusyEvents.list[i].end && (freeBusyEvents.list[i].start != freeBusyEvents.list[i].end) && !freeBusyEvents.list[i].reminder.isTagged(excludeTagsCondition)) {
			var eventStartDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventStartDateTime.setHours(Number(freeBusyEvents.list[i].start.substring(0, freeBusyEvents.list[i].start.indexOf(":"))));
			eventStartDateTime.setMinutes(Number(freeBusyEvents.list[i].start.substring(freeBusyEvents.list[i].start.indexOf(":") + 1)));
			
			var eventEndDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventEndDateTime.setHours(Number(freeBusyEvents.list[i].end.substring(0, freeBusyEvents.list[i].end.indexOf(":"))));
			eventEndDateTime.setMinutes(Number(freeBusyEvents.list[i].end.substring(freeBusyEvents.list[i].end.indexOf(":") + 1)));
			
			var event = (eventEndDateTime.getTime() > eventStartDateTime.getTime()) ? {start: eventStartDateTime, end: eventEndDateTime} : {start: eventEndDateTime, end: eventStartDateTime};
			event["uid"] = "twnuid" + jQuery.encoding.digests.hexSha1Str(formatUTCDateString(eventStartDateTime) + "/" + formatUTCDateString(eventEndDateTime ) + " : " + (freeBusyEvents.list[i].title ? freeBusyEvents.list[i].title : "" + currentDate.getTime() + "c" + (counter++)));
			
			for (var j = 0; j <= freeBusyEventList.length; j++) {
				if (j == freeBusyEventList.length) {
					freeBusyEventList[freeBusyEventList.length] = event;
					break;
				} else if ((event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) && (event["end"].getTime() < freeBusyEventList[j]["start"].getTime())) {
					freeBusyEventList.splice(j, 0, event);
					break;
				} else if (!((event["start"].getTime() > freeBusyEventList[j]["end"].getTime()) && (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()))) {
					if (event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) {
						freeBusyEventList[j]["start"] = event["start"];
					}
					if (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()) {
						freeBusyEventList[j]["end"] = event["end"];
					}
					break;
				}
			}
		}
	}
	
	for (var i = 0; i < freeBusyEventList.length; i++) {
		freebusy = freebusy
				+ "BEGIN:VEVENT\r\n"
				+ "UID:" + freeBusyEventList[i]["uid"] + "\r\n"
				+ "DTSTAMP:" + formatUTCDateString(currentDate) + "\r\n"
				+ "ORGANIZER;CN=" + organizerName + ":MAILTO:" + organizerEmail + "\r\n"
				+ "DTSTART:" + formatUTCDateString(freeBusyEventList[i]["start"]) + "\r\n"
				+ "DTEND:" + formatUTCDateString(freeBusyEventList[i]["end"]) + "\r\n"
				+ "SUMMARY:Busy\r\n"
				+ (jQuery('#freeBusyGenerateICalendarGroupWiseBusy').is(':checked') ? "X-GWSHOW-AS:BUSY\r\n" : "")
				+ "END:VEVENT\r\n";
	}
}

var dayCounter = 0;

window.determineFreeBusy = function() {
	determineFreeBusyForDay();
	dayCounter++;
	
	place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
	
	if (dayCounter < numberOfDays) {
		setTimeout("window.determineFreeBusy()", 50);
	} else {
		freebusy = freebusy 
				+ "END:VCALENDAR\r\n";
		
		place.innerHTML = label;
		
		wikify("{{{\n" + freebusy.replace(/\r\n/g, "\n") + "}}}", place.parentNode);
		
		if (jQuery('#freeBusyPublishOnServer').is(':checked') && store.getTiddler('FreeBusy Calendar Info') && store.getTiddler('FreeBusy Server') && store.getTiddler('FreeBusy Server Password') && (store.getTiddler('FreeBusy Server Password').text.length > 0)) {
			var filename = store.getTiddler('FreeBusy Calendar Info').text;
			try {
				var filenameMap = eval("function _convertFilenameMap() {return " + filename + "}; _convertFilenameMap();");
				if (filenameMap && filenameMap["icalendar"]) {
					filename = filenameMap["icalendar"];
				}
			} catch (e) {}
			var encryptedData = Aes.Ctr.encrypt("filename:" + filename + "\n" + freebusy, store.getTiddler('FreeBusy Server Password').text, 256);
			
			var html = '<html><head><title></title></head><body><form action="' + store.getTiddler('FreeBusy Server').text + '" method="post"><input type="hidden" name="data" value="' + encryptedData + '"/></form></body></html>';
			
			var iframe = document.getElementById("freeBusyFrame");
			if (iframe) {
				document.body.removeChild(iframe);
			}
			iframe = createTiddlyElement(document.body, "iframe", "freeBusyFrame");
			iframe.style.width="0px";
			iframe.style.height="0px";
			iframe.style.border="0px";
			
			var iframeDocument = iframe.document;
			if (iframe.contentDocument) {
				// For NS6
				iframeDocument = iframe.contentDocument;
			} else if (iframe.contentWindow) {
				// For IE5.5 and IE6
				iframeDocument = iframe.contentWindow.document;
			}
			iframeDocument.open();
			iframeDocument.writeln(html);
			iframeDocument.close();
			
			var publicationResult = "unknown";
			
			jQuery.receiveMessage(function(e) {
				publicationResult = e.data;
				if (publicationResult == "success") {
					displayMessage("The calendar was published successfully.");
				} else if (publicationResult == "failure") {
					displayMessage("The calendar could not be published.  Check server details.");
				}
			});
			
			try {
				iframeDocument.getElementsByTagName("form")[0].submit();
			} catch (e) {
				displayMessage("The calendar could not be published.  Check server details.");
			}
		}
	}
}

place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
setTimeout("window.determineFreeBusy()", 50);

</script> &nbsp;&nbsp;&nbsp; Include indicators: <html><input type="checkbox" id="freeBusyGenerateICalendarGroupWiseBusy" value="1" /></html> Novell GroupWise //BUSY//

<script label="Generate Calendar">

var calendarTemplate = {en:
		[ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
		+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n'
		+ '<head>\n'
		+ '<meta http-equiv="Cache-Control" content="no-cache" />\n'
		+ '<meta http-equiv="Expires" content="Sat, 1 Jan 2000 12:00:00 GMT" />\n'
		+ '<meta http-equiv="Pragma" content="no-cache" />\n'
		+ '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n'
		+ '@@calendar-style@@\n'
		+ '<title>Calendar</title>\n'
		+ '</head>\n'
		+ '<body>\n'
		+ '@@calendar@@\n'
		+ '</body>\n'
		+ '</html>\n']};
calendarTemplate["en"][1] = calendarTemplate["en"][0];
calendarTemplate["fr"] = [calendarTemplate["en"][0], calendarTemplate["en"][1]];
calendarTemplate["en-files"] = ["1.html", "2.html"];
calendarTemplate["fr-files"] = ["1_fr.html", "2_fr.html"];
if (store.getTiddler('FreeBusy Calendar Template')) {
	var tiddlerTemplate = store.getTiddler('FreeBusy Calendar Template').text;
	while (tiddlerTemplate != null) {
		var templateStart = tiddlerTemplate.indexOf("<!-- template begin");
		var templateEnd = tiddlerTemplate.indexOf("<!-- template end");
		if ((templateStart > -1) && (templateEnd > -1)) {
			var infoStart = tiddlerTemplate.indexOf("{", templateStart);
			var infoEnd = tiddlerTemplate.indexOf("}", templateStart);
			if ((infoStart > -1) && (infoEnd > -1)) {
				var infoMap = null;
				try {
					infoMap = eval("function _convertInfoMap() {return " + tiddlerTemplate.substring(infoStart, infoEnd + 1) + "}; _convertInfoMap();");
				} catch (e) {}
				if (infoMap != null) {
					var lang = infoMap["lang"];
					var file = infoMap["file"];
					var offset = infoMap["offset"];
					if (lang && file && offset && !isNaN(offset)) {
						offset = Number(offset);
						calendarTemplate[lang][offset] = tiddlerTemplate.substring(tiddlerTemplate.indexOf("-->", templateStart) + "-->".length, templateEnd);
						calendarTemplate[lang + "-files"][offset] = file;
					}
				}
			}
			tiddlerTemplate = tiddlerTemplate.substring(templateEnd + 1);
		} else {
			tiddlerTemplate = null;
		}
	}
}
var calendarStyle
		= '<style type="text/css">\n'
		+ '@media screen, projection {\n'
		+ '	a {\n'
		+ '		cursor: pointer;\n'
		+ '		text-decoration: none;\n'
		+ '	}\n'
		+ '	\n'
		+ '	body {\n'
		+ '		color: #000000;\n'
		+ '		cursor: default;\n'
		+ '		margin: 0;\n'
		+ '		padding: 0;\n'
		+ '	}\n'
		+ '	\n'
		+ '	caption {\n'
		+ '		margin: 0 auto;\n'
		+ '		text-align: right;\n'
		+ '		font: 20px/30px Georgia, serif;\n'
		+ '	}\n'
		+ '	\n'
		+ '	p {\n'
		+ '		margin: 0;\n'
		+ '	}\n'
		+ '	\n'
		+ '	strong {\n'
		+ '		text-transform: uppercase;\n'
		+ '	}\n'
		+ '	\n'
		+ '	table {\n'
		+ '		background: #fff;\n'
		+ '		border-bottom: 3px solid #ccc;\n'
		+ '		border-right: 1px solid #999;\n'
		+ '		margin: 0 auto;\n'
		+ '		line-height: 1em;\n'
		+ '	}\n'
		+ '	\n'
		+ '	th, td {\n'
		+ '		border-left: 1px solid #999;\n'
		+ '		border-top: 1px solid #999;\n'
		+ '		font-family: Verdana, sans-serif;\n'
		+ '		padding: 2px;\n'
		+ '		vertical-align: top;\n'
		+ '		width: 100px;\n'
		+ '		height: inherit;\n'
		+ '	}\n'
		+ '	\n'
		+ '	td a {\n'
		+ '		color: #000;\n'
		+ '		padding: 0;\n'
		+ '	}\n'
		+ '	\n'
		+ '	td a:hover {\n'
		+ '		text-decoration: underline;\n'
		+ '	}\n'
		+ '	\n'
		+ '	col.sat, col.sun {\n'
		+ '		background: #eef;\n'
		+ '	}\n'
		+ '	\n'
		+ '	td.today {\n'
		+ '		background: #fcf8e3;\n'
		+ '	}\n'
		+ '	\n'
		+ '	div.buttons {\n'
		+ '		font: 10px Verdana, sans-serif;\n'
		+ '		padding: 7px;\n'
		+ '		text-align: center;\n'
		+ '	}\n'
		+ '	\n'
		+ '	div.buttons a {\n'
		+ '		background: #ddd;\n'
		+ '		border: 1px solid #bbb;\n'
		+ '		color: #000;\n'
		+ '		padding: 3px 5px;\n'
		+ '	}\n'
		+ '	\n'
		+ '	div.buttons a:hover {\n'
		+ '		background-color: #eef;\n'
		+ '		color: #369;\n'
		+ '	}\n'
		+ '	\n'
		+ '	tr.day {\n'
		+ '		color: #666;\n'
		+ '		font-size: 9px;\n'
		+ '		height: 90px;\n'
		+ '	}\n'
		+ '	\n'
		+ '	tr.number {\n'
		+ '		background: #ddd;\n'
		+ '		color: #888;\n'
		+ '		font-size: 10px;\n'
		+ '		height: 15px;\n'
		+ '		text-align: right;\n'
		+ '		vertical-align: middle;\n'
		+ '	}\n'
		+ '	\n'
		+ '	th {\n'
		+ '		background: #fff;\n'
		+ '		color: #999;\n'
		+ '		font-size: 11px;\n'
		+ '		font-variant: small-caps;\n'
		+ '		font-weight: normal;\n'
		+ '		height: 15px;\n'
		+ '		text-transform: capitalize;\n'
		+ '		text-align: center;\n'
		+ '		vertical-align: middle;\n'
		+ '	}\n'
		+ '}\n'
		+ '\n'
		+ '@media print {\n'
		+ '	* {\n'
		+ '		background: #fff;\n'
		+ '		color: #000;\n'
		+ '	}\n'
		+ '	\n'
		+ '	a {\n'
		+ '		text-decoration: none;\n'
		+ '	}\n'
		+ '	\n'
		+ '	body {\n'
		+ '		margin: 0;\n'
		+ '		padding: 0;\n'
		+ '	}\n'
		+ '	\n'
		+ '	caption {\n'
		+ '		text-align: center;\n'
		+ '		text-transform: capitalize;\n'
		+ '		font: small-caps 20px/30px Georgia, serif;\n'
		+ '	}\n'
		+ '	\n'
		+ '	p {\n'
		+ '		margin: 0;\n'
		+ '	}\n'
		+ '	\n'
		+ '	strong {\n'
		+ '		text-transform: uppercase;\n'
		+ '	}\n'
		+ '	\n'
		+ '	table {\n'
		+ '		border: solid #000 !important;\n'
		+ '		border-width: 1px 0 0 1px !important;\n'
		+ '		border-collapse:collapse;\n'
		+ '	}\n'
		+ '	\n'
		+ '	th, td {\n'
		+ '		border: solid #000 !important;\n'
		+ '		border-width: 0 1px 1px 0 !important;\n'
		+ '		font-family: Verdana, sans-serif;\n'
		+ '		padding: 2px;\n'
		+ '		vertical-align: top;\n'
		+ '		width: 100px;\n'
		+ '		height: inherit;\n'
		+ '	}\n'
		+ '	\n'
		+ '	div.buttons {\n'
		+ '		display: none;\n'
		+ '	}\n'
		+ '	\n'
		+ '	tr.day {\n'
		+ '		font-size: 9px;\n'
		+ '		height: 90px;\n'
		+ '	}\n'
		+ '	\n'
		+ '	tr.number {\n'
		+ '		font-size: 10px;\n'
		+ '		height: 15px;\n'
		+ '		text-align: right;\n'
		+ '		vertical-align: middle;\n'
		+ '	}\n'
		+ '	\n'
		+ '	th {\n'
		+ '		font-size: 11px;\n'
		+ '		font-variant: small-caps;\n'
		+ '		font-weight: normal;\n'
		+ '		height: 15px;\n'
		+ '		text-transform: capitalize;\n'
		+ '		text-align: center;\n'
		+ '		vertical-align: middle;\n'
		+ '	}\n'
		+ '}\n'
		+ '</style>\n';
var calendarPrefix1
		= '<table cellspacing="0">\n'
		+ '	<caption>';
var calendarPrefix2
		= '</caption>\n'
		+ '	<colgroup>\n'
		+ '		<col class="sun" />\n'
		+ '		<col class="mon" />\n'
		+ '		<col class="tue" />\n'
		+ '		<col class="wed" />\n'
		+ '		<col class="thu" />\n'
		+ '		<col class="fri" />\n'
		+ '		<col class="sat" />\n'
		+ '	</colgroup>\n'
		+ '	<thead>\n'
var calendarPrefix3 = {
		en: '		<tr>\n'
		+ '			<th scope="col">Sunday</th>\n'
		+ '			<th scope="col">Monday</th>\n'
		+ '			<th scope="col">Tuesday</th>\n'
		+ '			<th scope="col">Wednesday</th>\n'
		+ '			<th scope="col">Thursday</th>\n'
		+ '			<th scope="col">Friday</th>\n'
		+ '			<th scope="col">Saturday</th>\n'
		+ '		</tr>\n'
		+ '	</thead>\n'
		+ '	<tbody>\n',
		fr: '		<tr>\n'
		+ '			<th scope="col">dimanche</th>\n'
		+ '			<th scope="col">lundi</th>\n'
		+ '			<th scope="col">mardi</th>\n'
		+ '			<th scope="col">mercredi</th>\n'
		+ '			<th scope="col">jeudi</th>\n'
		+ '			<th scope="col">vendredi</th>\n'
		+ '			<th scope="col">samedi</th>\n'
		+ '		</tr>\n'
		+ '	</thead>\n'
		+ '	<tbody>\n'};
calendarNavigation = {en: ["Next Month &gt;&gt;", "&lt;&lt; Previous Month"], fr: ["Mois suivant &gt;&gt;", "&lt;&lt; Mois pr&eacute;c&eacute;dent"]};
calendarBusy = {en: "Busy", fr: "Occup&eacute;"};

var generateDayStructure = function(year, month, day)  {
	var date = new Date(year, month - 1, day, 0, 0, 0, 0);
	if (date.getMonth() + 1 != month) {
		return null;
	}
	
	var daysInMonth = window.determineDaysBetweenDates(new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0), new Date(date.getFullYear(), date.getMonth() + 1, 1, 0, 0, 0, 0));
	if (day > daysInMonth) {
		return null;
	}
	
	var dayStructure = "";
	if ((date.getDay() == 0) || (day == 1)) {
		var blankDayStructure = "";
		dayStructure = '		<tr class="number">\n';
		var weekdayCounter = 0;
		if (day == 1) {
			for (var i = 0; i < date.getDay(); i++) {
				blankDayStructure = blankDayStructure + '			<td>&nbsp;</td>\n';
				weekdayCounter++;
			}
			dayStructure = dayStructure + blankDayStructure;
		} else {
			dayStructure = '		</tr>\n' + dayStructure;
		}
		for (var i = 0; i + weekdayCounter < 7; i++) {
			if (date.getDate() + i > daysInMonth) {
				dayStructure = dayStructure + '			<td>&nbsp;</td>\n';
			} else {
				dayStructure = dayStructure + '			<td>' + (date.getDate() + i) + '</td>\n';
			}
		}
		dayStructure = dayStructure + '		</tr>\n';
		dayStructure = dayStructure + '		<tr class="day">\n';
		dayStructure = dayStructure + blankDayStructure;
	}
	
	var freeBusyEvents = new ScheduleReminderEvents();
	
	dayStructure = dayStructure + '			<td id="y' + date.getFullYear() + 'm' + (date.getMonth() + 1)+ 'd' + date.getDate() + '">';
	
	var excludeTagsCondition = $("freebusy_filter_tag").value.length ? " AND " + $("freebusy_filter_tag").value : "";
	
	var freeBusyEventList = [];
	freeBusyEvents.determineEvents(date, "(NOT meeting OR NOT cancelled) AND (NOT task OR NOT done) AND NOT Trash" + excludeTagsCondition);
	for (var i = 0; i < freeBusyEvents.list.length; i++) {
		if (freeBusyEvents.list[i].start && freeBusyEvents.list[i].end && (freeBusyEvents.list[i].start != freeBusyEvents.list[i].end) && !freeBusyEvents.list[i].reminder.isTagged(excludeTagsCondition)) {
			var eventStartDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventStartDateTime.setHours(Number(freeBusyEvents.list[i].start.substring(0, freeBusyEvents.list[i].start.indexOf(":"))));
			eventStartDateTime.setMinutes(Number(freeBusyEvents.list[i].start.substring(freeBusyEvents.list[i].start.indexOf(":") + 1)));
			
			var eventEndDateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
			eventEndDateTime.setHours(Number(freeBusyEvents.list[i].end.substring(0, freeBusyEvents.list[i].end.indexOf(":"))));
			eventEndDateTime.setMinutes(Number(freeBusyEvents.list[i].end.substring(freeBusyEvents.list[i].end.indexOf(":") + 1)));
			
			var event = (eventEndDateTime.getTime() > eventStartDateTime.getTime()) ? {start: eventStartDateTime, end: eventEndDateTime} : {start: eventEndDateTime, end: eventStartDateTime};
			
			for (var j = 0; j <= freeBusyEventList.length; j++) {
				if (j == freeBusyEventList.length) {
					freeBusyEventList[freeBusyEventList.length] = event;
					break;
				} else if ((event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) && (event["end"].getTime() < freeBusyEventList[j]["start"].getTime())) {
					freeBusyEventList.splice(j, 0, event);
					break;
				} else if (!((event["start"].getTime() > freeBusyEventList[j]["end"].getTime()) && (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()))) {
					if (event["start"].getTime() < freeBusyEventList[j]["start"].getTime()) {
						freeBusyEventList[j]["start"] = event["start"];
					}
					if (event["end"].getTime() > freeBusyEventList[j]["end"].getTime()) {
						freeBusyEventList[j]["end"] = event["end"];
					}
					break;
				}
			}
		}
	}
	
	for (var i = 0; i < freeBusyEventList.length; i++) {
		var startTime = (freeBusyEventList[i]["start"].getHours() < 10 ? "0" : "") + freeBusyEventList[i]["start"].getHours() + ":" + (freeBusyEventList[i]["start"].getMinutes() < 10 ? "0" : "") + freeBusyEventList[i]["start"].getMinutes();
		
		var endTime = (freeBusyEventList[i]["end"].getHours() < 10 ? "0" : "") + freeBusyEventList[i]["end"].getHours() + ":" + (freeBusyEventList[i]["end"].getMinutes() < 10 ? "0" : "") + freeBusyEventList[i]["end"].getMinutes();
		
		if (i > 0) {
			dayStructure = dayStructure + '<br />';
		}
		
		var startEndTime = startTime + " - " + endTime;
		var timezoneOffset = -date.getTimezoneOffset();
		var hoursOffset = (Math.abs(timezoneOffset) / 60) | 0;
		var minutesOffset = Math.abs(timezoneOffset) - hoursOffset * 60;
		startEndTime = startEndTime + " UTC" + ((timezoneOffset > 0 ) ? "+" : "-") + (hoursOffset < 10 ? "0" : "") + hoursOffset + "" + (minutesOffset < 10 ? "0" : "") + minutesOffset;
		dayStructure = dayStructure + ' <a href="#" title="Busy ' + startEndTime + '" onclick="alert(\'Busy ' + date.getFullYear() + '-' + (date.getMonth() + 1 < 10 ? "0" : "") + (date.getMonth() + 1) + '-' + (date.getDate() < 10 ? "0" : "") + date.getDate() + ' ' + startEndTime + '\'); return false;">' + startTime + ' - ' + endTime + '</a>';
	}

	if (freeBusyEventList.length == 0) {
		dayStructure = dayStructure + '&nbsp;';
	}
	
	dayStructure = dayStructure + '</td>\n';
	
	if (day == daysInMonth) {
		for (var i = 0; i + date.getDay() + 1 < 7; i++) {
			dayStructure = dayStructure + '			<td>&nbsp;</td>\n';
		}
		dayStructure = dayStructure + '		</tr>\n';
	}
	
	return dayStructure;
}

var daysStructureMonth = ["", null];

var currentDate = new Date();
var calentarMonth = [new Date(currentDate.getFullYear(), currentDate.getMonth(), 1, 0, 0, 0, 0), new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1, 0, 0, 0, 0)];
var numberOfDays = window.determineDaysBetweenDates(calentarMonth[0], new Date(currentDate.getFullYear(), currentDate.getMonth() + 2, 1, 0, 0, 0, 0));

var dayCounter = 1;
var previousCounter = 0;

var label = place.innerHTML;

window.determineFreeBusy = function() {
	var dayStructure = generateDayStructure((daysStructureMonth[1] == null) ? calentarMonth[0].getFullYear() : calentarMonth[1].getFullYear(), ((daysStructureMonth[1] == null) ? calentarMonth[0].getMonth() : calentarMonth[1].getMonth()) + 1, dayCounter - previousCounter);
	
	if ((dayStructure == null) && (daysStructureMonth[1] == null)) {
		daysStructureMonth[1] = "";
		previousCounter = dayCounter - 1;
		setTimeout("window.determineFreeBusy()", 50);
		return;
	}
	
	if (dayStructure != null) {
		place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
		dayCounter++;
		
		if (daysStructureMonth[1] != null) {
			daysStructureMonth[1] = daysStructureMonth[1] + dayStructure;
		} else {
			daysStructureMonth[0] = daysStructureMonth[0] + dayStructure;
		}
		setTimeout("window.determineFreeBusy()", 50);
	} else {
		place.innerHTML = label;
		
		var makeCalendar = function(lang, offset) {
			var calendar = calendarTemplate[lang][offset];
			
			if (calendar.indexOf("@@calendar-style@@") > -1) {
				calendar = calendar.substring(0, calendar.indexOf("@@calendar-style@@")) + calendarStyle + calendar.substring(calendar.indexOf("@@calendar-style@@") +"@@calendar-style@@".length);
			}
			if (calendar.indexOf("@@calendar@@") > -1) {
				var calendarContent = calendarPrefix1 + calentarMonth[offset].getFullYear() + "-" + (calentarMonth[offset].getMonth() + 1 < 10 ? "0" : "") + (calentarMonth[offset].getMonth() + 1) + calendarPrefix2 + calendarPrefix3[lang] + daysStructureMonth[offset].replace(/Busy/g, calendarBusy[lang]);
				calendarContent = calendarContent
						+ '	</tbody>\n'
						+ '</table>\n'
						+ '<div class="buttons">\n'
						+ '<a onclick="this.href = this.href + \'?t=\' + (new Date()).getTime(); return true;" href="';
				var relatedOffset = (offset == 1) ? 0 : 1;
				calendarContent = calendarContent + ((calendarTemplate[lang + "-files"][relatedOffset].indexOf("/") > -1) ? calendarTemplate[lang + "-files"][relatedOffset].substring(calendarTemplate[lang + "-files"][relatedOffset].lastIndexOf("/") + 1) : calendarTemplate[lang + "-files"][relatedOffset]);
				calendarContent = calendarContent
						+ '">' + calendarNavigation[lang][offset] + '</a>\n'
						+ '</div>\n'
						+ '<script type="text/javascript">\n'
						+ '	<!--\n'
						+ '	var date = new Date();\n'
						+ '	var dayCell= document.getElementById("y" + date.getFullYear() + "m" + (date.getMonth() + 1)+ "d" + date.getDate());\n'
						+ '	if (dayCell) {\n'
						+ '		dayCell.className = dayCell.className + " today";\n'
						+ '	}\n'
						+ '	//-->\n'
						+ '<' + '/script>\n';
				calendar = calendar.substring(0, calendar.indexOf("@@calendar@@")) + calendarContent + calendar.substring(calendar.indexOf("@@calendar@@") + "@@calendar@@".length);
			}
			return calendar;
		}
		var calendars = [makeCalendar("en", 0), makeCalendar("en", 1), makeCalendar("fr", 0), makeCalendar("fr", 1)];
		wikify("{{{\n" + calendars[0] + "\n}}}\n" + "{{{\n" + calendars[1] + "\n}}}\n" + "{{{\n" + calendars[2] + "\n}}}\n" + "{{{\n" + calendars[3] + "\n}}}", place.parentNode);
		
		if (jQuery('#freeBusyPublishOnServer').is(':checked') && store.getTiddler('FreeBusy Server') && store.getTiddler('FreeBusy Server Password') && (store.getTiddler('FreeBusy Server Password').text.length > 0)) {
			var calendarFileNames = [calendarTemplate["en-files"][0], calendarTemplate["en-files"][1], calendarTemplate["fr-files"][0], calendarTemplate["fr-files"][1]];
			
			var encryptedData = [];
			for (var i = 0; i < calendars.length; i++) {
				encryptedData[encryptedData.length] = Aes.Ctr.encrypt("filename:" + calendarFileNames[i] + "\n" + calendars[i], store.getTiddler('FreeBusy Server Password').text, 256);
			}
			
			var html = '<html><head><title></title></head><body><form action="' + store.getTiddler('FreeBusy Server').text + '" method="post"><input type="hidden" name="data1" value="' + encryptedData[0] + '"/><input type="hidden" name="data2" value="' + encryptedData[1] + '"/><input type="hidden" name="data3" value="' + encryptedData[2] + '"/><input type="hidden" name="data4" value="' + encryptedData[3] + '"/></form></body></html>';
			
			var iframe = document.getElementById("freeBusyFrame");
			if (iframe) {
				document.body.removeChild(iframe);
			}
			iframe = createTiddlyElement(document.body, "iframe", "freeBusyFrame");
			iframe.style.width="0px";
			iframe.style.height="0px";
			iframe.style.border="0px";
			
			var iframeDocument = iframe.document;
			if (iframe.contentDocument) {
				// For NS6
				iframeDocument = iframe.contentDocument;
			} else if (iframe.contentWindow) {
				// For IE5.5 and IE6
				iframeDocument = iframe.contentWindow.document;
			}
			iframeDocument.open();
			iframeDocument.writeln(html);
			iframeDocument.close();
			
			var publicationResult = "unknown";
			
			jQuery.receiveMessage(function(e) {
				publicationResult = e.data;
				if (publicationResult == "success") {
					displayMessage("The calendar was published successfully.");
				} else if (publicationResult == "failure") {
					displayMessage("The calendar could not be published.  Check server details.");
				}
			});
			
			try {
				iframeDocument.getElementsByTagName("form")[0].submit();
			} catch (e) {
				displayMessage("The calendar could not be published.  Check server details.");
			}
		}
	}
}

place.innerHTML = "Generating: " + Math.round(dayCounter / numberOfDays * 100) + "%";
setTimeout("window.determineFreeBusy()", 50);
</script>
* [[init|##init]]
* [[reset.css|##reset.css]]
* [[fullcalendar.css|##fullcalendar.css]]
* [[fullcalendar.print.css|##fullcalendar.print.css]]
* [[fullcalendar.js|##fullcalendar.js]]
* [[jquery.ui.js|##jquery.ui.js]]
* [[jquery.ui.touch-punch.js|##jquery.ui.touch-punch.js]]

!Date formatting
* s - seconds
* ss - seconds, 2 digits
* m - minutes
* mm - minutes, 2 digits
* h - hours, 12-hour format
* hh - hours, 12-hour format, 2 digits
* H - hours, 24-hour format
* HH - hours, 24-hour format, 2 digits
* d - date number
* dd - date number, 2 digits
* ddd - date name, short
* dddd - date name, full
* M - month number
* MM - month number, 2 digits
* MMM - month name, short
* MMMM - month name, full
* yy - year, 2 digits
* yyyy - year, 4 digits
* t - 'a' or 'p'
* tt - 'am' or 'pm'
* T - 'A' or 'P'
* TT - 'AM' or 'PM'
* u - ISO8601 format
* S - 'st', 'nd', 'rd', 'th' for the date
* W - the ISO8601 week number
* '...' - literal text
* """''""" - single quote (represented by two single quotes)
* (...) - only displays format if one of the enclosed variables is non-zero 

<html><div id='calendar' class='fullcalendar'></div></html>

http://arshaw.com/fullcalendar/docs/usage

<script>
eval(store.getTiddlerText('FullCalendar##init'));

var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();

var calendar = jQuery('#calendar').fullCalendar({
	header: {
		left: 'prev,next today',
		center: 'title',
		right: 'month,agendaWeek,agendaDay'
	},
	selectable: true,
	selectHelper: true,
	select: function(start, end, allDay) {
		var title = prompt('Event Title:');
		if (title) {
			calendar.fullCalendar('renderEvent',
				{
					title: title,
					start: start,
					end: end,
					allDay: allDay
				},
				true // make the event "stick"
			);
		}
		calendar.fullCalendar('unselect');
	},
	editable: true,
	events: [
		{
			title: 'All Day Event',
			start: new Date(y, m, 1)
		},
		{
			title: 'Long Event',
			start: new Date(y, m, d-5),
			end: new Date(y, m, d-2)
		},
		{
			id: 999,
			title: 'Repeating Event',
			start: new Date(y, m, d-3, 16, 0),
			allDay: false
		},
		{
			id: 999,
			title: 'Repeating Event',
			start: new Date(y, m, d+4, 16, 0),
			allDay: false
		},
		{
			title: 'Meeting',
			start: new Date(y, m, d, 10, 30),
			allDay: false
		},
		{
			title: 'Lunch',
			start: new Date(y, m, d, 12, 0),
			end: new Date(y, m, d, 14, 0),
			allDay: false
		},
		{
			title: 'Birthday Party',
			start: new Date(y, m, d+1, 19, 0),
			end: new Date(y, m, d+1, 22, 30),
			allDay: false
		},
		{
			title: 'Click for Google',
			start: new Date(y, m, 28),
			end: new Date(y, m, 29),
			url: 'http://google.com/'
		}
	]
});
</script>
!init
//{{{
if (!jQuery.fullCalendar) {
	setStylesheet(store.getTiddlerText('FullCalendar##reset.css.start') + '\n' + store.getTiddlerText('FullCalendar##fullcalendar.css.start') + '\n@media print {\n' + store.getTiddlerText('FullCalendar##fullcalendar.print.css.start') + '\n}', 'fullcalendar.css');
	
	eval(store.getTiddlerText('FullCalendar##jquery.ui.core.js'));
	eval(store.getTiddlerText('FullCalendar##jquery.ui.widget.js'));
	eval(store.getTiddlerText('FullCalendar##jquery.ui.mouse.js'));
	eval(store.getTiddlerText('FullCalendar##jquery.ui.draggable.js'));
	eval(store.getTiddlerText('FullCalendar##jquery.ui.resizable.js'));
	
	eval(store.getTiddlerText('FullCalendar##jquery.ui.touch-punch.js'));
	
	eval(store.getTiddlerText('FullCalendar##fullcalendar.js'));
}
//}}}
!reset.css
//{{{
!reset.css.start
.fullcalendar {
	width: 100%;
	margin: 0 auto;
	text-align: center;
	font-size: 14px;
	font-family: "Lucida Grande",Helvetica,Arial,Verdana,sans-serif;
}
.fullcalendar table {
	margin: 0;
}
.fullcalendar th, .fullcalendar td {
	border-color: #cccccc;
}
.fullcalendar .fc-agenda-allday td {
	border: none;
}
.fullcalendar table, .fullcalendar tr {
	border: none;
}
.fullcalendar .fc-header td {
	border: none;
}
.fullcalendar th, .fullcalendar thead td {
	background-color: #ffffff;
	color: #000000;
}
.fullcalendar h2 {
	border: none;
	color: #000000;
}
.fullcalendar .fc-header-title h2 sub {
	display: none;
}
!reset.css.end
//}}}
!fullcalendar.css
//{{{
!fullcalendar.css.start
/*!
 * FullCalendar v1.6.4 Stylesheet
 * Docs & License: http://arshaw.com/fullcalendar/
 * (c) 2013 Adam Shaw
 */


.fc {
	direction: ltr;
	text-align: left;
	}
	
.fc table {
	border-collapse: collapse;
	border-spacing: 0;
	}
	
html .fc,
.fc table {
	font-size: 1em;
	}
	
.fc td,
.fc th {
	padding: 0;
	vertical-align: top;
	}



/* Header
------------------------------------------------------------------------*/

.fc-header td {
	white-space: nowrap;
	}

.fc-header-left {
	width: 25%;
	text-align: left;
	}
	
.fc-header-center {
	text-align: center;
	}
	
.fc-header-right {
	width: 25%;
	text-align: right;
	}
	
.fc-header-title {
	display: inline-block;
	vertical-align: top;
	}
	
.fc-header-title h2 {
	margin-top: 0;
	white-space: nowrap;
	}
	
.fc .fc-header-space {
	padding-left: 10px;
	}
	
.fc-header .fc-button {
	margin-bottom: 1em;
	vertical-align: top;
	}
	
/* buttons edges butting together */

.fc-header .fc-button {
	margin-right: -1px;
	}
	
.fc-header .fc-corner-right,  /* non-theme */
.fc-header .ui-corner-right { /* theme */
	margin-right: 0; /* back to normal */
	}
	
/* button layering (for border precedence) */
	
.fc-header .fc-state-hover,
.fc-header .ui-state-hover {
	z-index: 2;
	}
	
.fc-header .fc-state-down {
	z-index: 3;
	}

.fc-header .fc-state-active,
.fc-header .ui-state-active {
	z-index: 4;
	}
	
	
	
/* Content
------------------------------------------------------------------------*/
	
.fc-content {
	clear: both;
	zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
	}
	
.fc-view {
	width: 100%;
	overflow: hidden;
	}
	
	

/* Cell Styles
------------------------------------------------------------------------*/

.fc-widget-header,    /* <th>, usually */
.fc-widget-content {  /* <td>, usually */
	border: 1px solid #ddd;
	}
	
.fc-state-highlight { /* <td> today cell */ /* TODO: add .fc-today to <th> */
	background: #fcf8e3;
	}
	
.fc-cell-overlay { /* semi-transparent rectangle while dragging */
	background: #bce8f1;
	opacity: .3;
	filter: alpha(opacity=30); /* for IE */
	}
	


/* Buttons
------------------------------------------------------------------------*/

.fc-button {
	position: relative;
	display: inline-block;
	padding: 0 .6em;
	overflow: hidden;
	height: 1.9em;
	line-height: 1.9em;
	white-space: nowrap;
	cursor: pointer;
	}
	
.fc-state-default { /* non-theme */
	border: 1px solid;
	}

.fc-state-default.fc-corner-left { /* non-theme */
	border-top-left-radius: 4px;
	border-bottom-left-radius: 4px;
	}

.fc-state-default.fc-corner-right { /* non-theme */
	border-top-right-radius: 4px;
	border-bottom-right-radius: 4px;
	}

/*
	Our default prev/next buttons use HTML entities like &lsaquo; &rsaquo; &laquo; &raquo;
	and we'll try to make them look good cross-browser.
*/

.fc-text-arrow {
	margin: 0 .1em;
	font-size: 2em;
	font-family: "Courier New", Courier, monospace;
	vertical-align: baseline; /* for IE7 */
	}

.fc-button-prev .fc-text-arrow,
.fc-button-next .fc-text-arrow { /* for &lsaquo; &rsaquo; */
	font-weight: bold;
	}
	
/* icon (for jquery ui) */
	
.fc-button .fc-icon-wrap {
	position: relative;
	float: left;
	top: 50%;
	}
	
.fc-button .ui-icon {
	position: relative;
	float: left;
	margin-top: -50%;
	*margin-top: 0;
	*top: -50%;
	}
	
/*
  button states
  borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
*/

.fc-state-default {
	background-color: #f5f5f5;
	background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
	background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
	background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
	background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
	background-repeat: repeat-x;
	border-color: #e6e6e6 #e6e6e6 #bfbfbf;
	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
	color: #333;
	text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
	}

.fc-state-hover,
.fc-state-down,
.fc-state-active,
.fc-state-disabled {
	color: #333333;
	background-color: #e6e6e6;
	}

.fc-state-hover {
	color: #333333;
	text-decoration: none;
	background-position: 0 -15px;
	-webkit-transition: background-position 0.1s linear;
	   -moz-transition: background-position 0.1s linear;
	     -o-transition: background-position 0.1s linear;
	        transition: background-position 0.1s linear;
	}

.fc-state-down,
.fc-state-active {
	background-color: #cccccc;
	background-image: none;
	outline: 0;
	box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
	}

.fc-state-disabled {
	cursor: default;
	background-image: none;
	opacity: 0.65;
	filter: alpha(opacity=65);
	box-shadow: none;
	}

	

/* Global Event Styles
------------------------------------------------------------------------*/

.fc-event-container > * {
	z-index: 8;
	}

.fc-event-container > .ui-draggable-dragging,
.fc-event-container > .ui-resizable-resizing {
	z-index: 9;
	}
	 
.fc-event {
	border: 1px solid #3a87ad; /* default BORDER color */
	background-color: #3a87ad; /* default BACKGROUND color */
	color: #fff;               /* default TEXT color */
	font-size: .85em;
	cursor: default;
	}

a.fc-event {
	text-decoration: none;
	}
	
a.fc-event,
.fc-event-draggable {
	cursor: pointer;
	}
	
.fc-rtl .fc-event {
	text-align: right;
	}

.fc-event-inner {
	width: 100%;
	height: 100%;
	overflow: hidden;
	}
	
.fc-event-time,
.fc-event-title {
	padding: 0 1px;
	}
	
.fc .ui-resizable-handle {
	display: block;
	position: absolute;
	z-index: 99999;
	overflow: hidden; /* hacky spaces (IE6/7) */
	font-size: 300%;  /* */
	line-height: 50%; /* */
	}
	
	
	
/* Horizontal Events
------------------------------------------------------------------------*/

.fc-event-hori {
	border-width: 1px 0;
	margin-bottom: 1px;
	}

.fc-ltr .fc-event-hori.fc-event-start,
.fc-rtl .fc-event-hori.fc-event-end {
	border-left-width: 1px;
	border-top-left-radius: 3px;
	border-bottom-left-radius: 3px;
	}

.fc-ltr .fc-event-hori.fc-event-end,
.fc-rtl .fc-event-hori.fc-event-start {
	border-right-width: 1px;
	border-top-right-radius: 3px;
	border-bottom-right-radius: 3px;
	}
	
/* resizable */
	
.fc-event-hori .ui-resizable-e {
	top: 0           !important; /* importants override pre jquery ui 1.7 styles */
	right: -3px      !important;
	width: 7px       !important;
	height: 100%     !important;
	cursor: e-resize;
	}
	
.fc-event-hori .ui-resizable-w {
	top: 0           !important;
	left: -3px       !important;
	width: 7px       !important;
	height: 100%     !important;
	cursor: w-resize;
	}
	
.fc-event-hori .ui-resizable-handle {
	_padding-bottom: 14px; /* IE6 had 0 height */
	}
	
	
	
/* Reusable Separate-border Table
------------------------------------------------------------*/

table.fc-border-separate {
	border-collapse: separate;
	}
	
.fc-border-separate th,
.fc-border-separate td {
	border-width: 1px 0 0 1px;
	}
	
.fc-border-separate th.fc-last,
.fc-border-separate td.fc-last {
	border-right-width: 1px;
	}
	
.fc-border-separate tr.fc-last th,
.fc-border-separate tr.fc-last td {
	border-bottom-width: 1px;
	}
	
.fc-border-separate tbody tr.fc-first td,
.fc-border-separate tbody tr.fc-first th {
	border-top-width: 0;
	}
	
	

/* Month View, Basic Week View, Basic Day View
------------------------------------------------------------------------*/

.fc-grid th {
	text-align: center;
	}

.fc .fc-week-number {
	width: 22px;
	text-align: center;
	}

.fc .fc-week-number div {
	padding: 0 2px;
	}
	
.fc-grid .fc-day-number {
	float: right;
	padding: 0 2px;
	}
	
.fc-grid .fc-other-month .fc-day-number {
	opacity: 0.3;
	filter: alpha(opacity=30); /* for IE */
	/* opacity with small font can sometimes look too faded
	   might want to set the 'color' property instead
	   making day-numbers bold also fixes the problem */
	}
	
.fc-grid .fc-day-content {
	clear: both;
	padding: 2px 2px 1px; /* distance between events and day edges */
	}
	
/* event styles */
	
.fc-grid .fc-event-time {
	font-weight: bold;
	}
	
/* right-to-left */
	
.fc-rtl .fc-grid .fc-day-number {
	float: left;
	}
	
.fc-rtl .fc-grid .fc-event-time {
	float: right;
	}
	
	

/* Agenda Week View, Agenda Day View
------------------------------------------------------------------------*/

.fc-agenda table {
	border-collapse: separate;
	}
	
.fc-agenda-days th {
	text-align: center;
	}
	
.fc-agenda .fc-agenda-axis {
	width: 50px;
	padding: 0 4px;
	vertical-align: middle;
	text-align: right;
	white-space: nowrap;
	font-weight: normal;
	}

.fc-agenda .fc-week-number {
	font-weight: bold;
	}
	
.fc-agenda .fc-day-content {
	padding: 2px 2px 1px;
	}
	
/* make axis border take precedence */
	
.fc-agenda-days .fc-agenda-axis {
	border-right-width: 1px;
	}
	
.fc-agenda-days .fc-col0 {
	border-left-width: 0;
	}
	
/* all-day area */
	
.fc-agenda-allday th {
	border-width: 0 1px;
	}
	
.fc-agenda-allday .fc-day-content {
	min-height: 34px; /* TODO: doesnt work well in quirksmode */
	_height: 34px;
	}
	
/* divider (between all-day and slots) */
	
.fc-agenda-divider-inner {
	height: 2px;
	overflow: hidden;
	}
	
.fc-widget-header .fc-agenda-divider-inner {
	background: #eee;
	}
	
/* slot rows */
	
.fc-agenda-slots th {
	border-width: 1px 1px 0;
	}
	
.fc-agenda-slots td {
	border-width: 1px 0 0;
	background: none;
	}
	
.fc-agenda-slots td div {
	height: 20px;
	}
	
.fc-agenda-slots tr.fc-slot0 th,
.fc-agenda-slots tr.fc-slot0 td {
	border-top-width: 0;
	}

.fc-agenda-slots tr.fc-minor th,
.fc-agenda-slots tr.fc-minor td {
	border-top-style: dotted;
	}
	
.fc-agenda-slots tr.fc-minor th.ui-widget-header {
	*border-top-style: solid; /* doesn't work with background in IE6/7 */
	}
	


/* Vertical Events
------------------------------------------------------------------------*/

.fc-event-vert {
	border-width: 0 1px;
	}

.fc-event-vert.fc-event-start {
	border-top-width: 1px;
	border-top-left-radius: 3px;
	border-top-right-radius: 3px;
	}

.fc-event-vert.fc-event-end {
	border-bottom-width: 1px;
	border-bottom-left-radius: 3px;
	border-bottom-right-radius: 3px;
	}
	
.fc-event-vert .fc-event-time {
	white-space: nowrap;
	font-size: 10px;
	}

.fc-event-vert .fc-event-inner {
	position: relative;
	z-index: 2;
	}
	
.fc-event-vert .fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay  */
	position: absolute;
	z-index: 1;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background: #fff;
	opacity: .25;
	filter: alpha(opacity=25);
	}
	
.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */
.fc-select-helper .fc-event-bg {
	display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work */
	}
	
/* resizable */
	
.fc-event-vert .ui-resizable-s {
	bottom: 0        !important; /* importants override pre jquery ui 1.7 styles */
	width: 100%      !important;
	height: 8px      !important;
	overflow: hidden !important;
	line-height: 8px !important;
	font-size: 11px  !important;
	font-family: monospace;
	text-align: center;
	cursor: s-resize;
	}
	
.fc-agenda .ui-resizable-resizing { /* TODO: better selector */
	_overflow: hidden;
	}
	
	
!fullcalendar.css.end
//}}}
!fullcalendar.print.css
//{{{
!fullcalendar.print.css.start
/*!
 * FullCalendar v1.6.4 Print Stylesheet
 * Docs & License: http://arshaw.com/fullcalendar/
 * (c) 2013 Adam Shaw
 */

/*
 * Include this stylesheet on your page to get a more printer-friendly calendar.
 * When including this stylesheet, use the media='print' attribute of the <link> tag.
 * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
 */
 
 
 /* Events
-----------------------------------------------------*/
 
.fc-event {
	background: #fff !important;
	color: #000 !important;
	}
	
/* for vertical events */
	
.fc-event-bg {
	display: none !important;
	}
	
.fc-event .ui-resizable-handle {
	display: none !important;
	}
	
	
!fullcalendar.print.css.end
//}}}
!fullcalendar.js
//{{{
/*!
 * FullCalendar v1.6.4
 * Docs & License: http://arshaw.com/fullcalendar/
 * (c) 2013 Adam Shaw
 */

/*
 * Use fullcalendar.css for basic styling.
 * For event drag & drop, requires jQuery UI draggable.
 * For event resizing, requires jQuery UI resizable.
 */
 
(function($, undefined) {


;;

var defaults = {

	// display
	defaultView: 'month',
	aspectRatio: 1.35,
	header: {
		left: 'title',
		center: '',
		right: 'today prev,next'
	},
	weekends: true,
	weekNumbers: false,
	weekNumberCalculation: 'iso',
	weekNumberTitle: 'W',
	
	// editing
	//editable: false,
	//disableDragging: false,
	//disableResizing: false,
	
	allDayDefault: true,
	ignoreTimezone: true,
	
	// event ajax
	lazyFetching: true,
	startParam: 'start',
	endParam: 'end',
	
	// time formats
	titleFormat: {
		month: 'MMMM yyyy',
		week: "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}",
		day: 'dddd, MMM d, yyyy'
	},
	columnFormat: {
		month: 'ddd',
		week: 'ddd M/d',
		day: 'dddd M/d'
	},
	timeFormat: { // for event elements
		'': 'h(:mm)t' // default
	},
	
	// locale
	isRTL: false,
	firstDay: 0,
	monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
	monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
	dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
	dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
	buttonText: {
		prev: "<span class='fc-text-arrow'>&lsaquo;</span>",
		next: "<span class='fc-text-arrow'>&rsaquo;</span>",
		prevYear: "<span class='fc-text-arrow'>&laquo;</span>",
		nextYear: "<span class='fc-text-arrow'>&raquo;</span>",
		today: 'today',
		month: 'month',
		week: 'week',
		day: 'day'
	},
	
	// jquery-ui theming
	theme: false,
	buttonIcons: {
		prev: 'circle-triangle-w',
		next: 'circle-triangle-e'
	},
	
	//selectable: false,
	unselectAuto: true,
	
	dropAccept: '*',
	
	handleWindowResize: true
	
};

// right-to-left defaults
var rtlDefaults = {
	header: {
		left: 'next,prev today',
		center: '',
		right: 'title'
	},
	buttonText: {
		prev: "<span class='fc-text-arrow'>&rsaquo;</span>",
		next: "<span class='fc-text-arrow'>&lsaquo;</span>",
		prevYear: "<span class='fc-text-arrow'>&raquo;</span>",
		nextYear: "<span class='fc-text-arrow'>&laquo;</span>"
	},
	buttonIcons: {
		prev: 'circle-triangle-e',
		next: 'circle-triangle-w'
	}
};



;;

var fc = $.fullCalendar = { version: "1.6.4" };
var fcViews = fc.views = {};


$.fn.fullCalendar = function(options) {


	// method calling
	if (typeof options == 'string') {
		var args = Array.prototype.slice.call(arguments, 1);
		var res;
		this.each(function() {
			var calendar = $.data(this, 'fullCalendar');
			if (calendar && $.isFunction(calendar[options])) {
				var r = calendar[options].apply(calendar, args);
				if (res === undefined) {
					res = r;
				}
				if (options == 'destroy') {
					$.removeData(this, 'fullCalendar');
				}
			}
		});
		if (res !== undefined) {
			return res;
		}
		return this;
	}

	options = options || {};
	
	// would like to have this logic in EventManager, but needs to happen before options are recursively extended
	var eventSources = options.eventSources || [];
	delete options.eventSources;
	if (options.events) {
		eventSources.push(options.events);
		delete options.events;
	}
	

	options = $.extend(true, {},
		defaults,
		(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
		options
	);
	
	
	this.each(function(i, _element) {
		var element = $(_element);
		var calendar = new Calendar(element, options, eventSources);
		element.data('fullCalendar', calendar); // TODO: look into memory leak implications
		calendar.render();
	});
	
	
	return this;
	
};


// function for adding/overriding defaults
function setDefaults(d) {
	$.extend(true, defaults, d);
}



;;

 
function Calendar(element, options, eventSources) {
	var t = this;
	
	
	// exports
	t.options = options;
	t.render = render;
	t.destroy = destroy;
	t.refetchEvents = refetchEvents;
	t.reportEvents = reportEvents;
	t.reportEventChange = reportEventChange;
	t.rerenderEvents = rerenderEvents;
	t.changeView = changeView;
	t.select = select;
	t.unselect = unselect;
	t.prev = prev;
	t.next = next;
	t.prevYear = prevYear;
	t.nextYear = nextYear;
	t.today = today;
	t.gotoDate = gotoDate;
	t.incrementDate = incrementDate;
	t.formatDate = function(format, date) { return formatDate(format, date, options) };
	t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
	t.getDate = getDate;
	t.getView = getView;
	t.option = option;
	t.trigger = trigger;
	
	
	// imports
	EventManager.call(t, options, eventSources);
	var isFetchNeeded = t.isFetchNeeded;
	var fetchEvents = t.fetchEvents;
	
	
	// locals
	var _element = element[0];
	var header;
	var headerElement;
	var content;
	var tm; // for making theme classes
	var currentView;
	var elementOuterWidth;
	var suggestedViewHeight;
	var resizeUID = 0;
	var ignoreWindowResize = 0;
	var date = new Date();
	var events = [];
	var _dragElement;
	
	
	
	/* Main Rendering
	-----------------------------------------------------------------------------*/
	
	
	setYMD(date, options.year, options.month, options.date);
	
	
	function render(inc) {
		if (!content) {
			initialRender();
		}
		else if (elementVisible()) {
			// mainly for the public API
			calcSize();
			_renderView(inc);
		}
	}
	
	
	function initialRender() {
		tm = options.theme ? 'ui' : 'fc';
		element.addClass('fc');
		if (options.isRTL) {
			element.addClass('fc-rtl');
		}
		else {
			element.addClass('fc-ltr');
		}
		if (options.theme) {
			element.addClass('ui-widget');
		}

		content = $("<div class='fc-content' style='position:relative'/>")
			.prependTo(element);

		header = new Header(t, options);
		headerElement = header.render();
		if (headerElement) {
			element.prepend(headerElement);
		}

		changeView(options.defaultView);

		if (options.handleWindowResize) {
			$(window).resize(windowResize);
		}

		// needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
		if (!bodyVisible()) {
			lateRender();
		}
	}
	
	
	// called when we know the calendar couldn't be rendered when it was initialized,
	// but we think it's ready now
	function lateRender() {
		setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
			if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
				renderView();
			}
		},0);
	}
	
	
	function destroy() {

		if (currentView) {
			trigger('viewDestroy', currentView, currentView, currentView.element);
			currentView.triggerEventDestroy();
		}

		$(window).unbind('resize', windowResize);

		header.destroy();
		content.remove();
		element.removeClass('fc fc-rtl ui-widget');
	}
	
	
	function elementVisible() {
		return element.is(':visible');
	}
	
	
	function bodyVisible() {
		return $('body').is(':visible');
	}
	
	
	
	/* View Rendering
	-----------------------------------------------------------------------------*/
	

	function changeView(newViewName) {
		if (!currentView || newViewName != currentView.name) {
			_changeView(newViewName);
		}
	}


	function _changeView(newViewName) {
		ignoreWindowResize++;

		if (currentView) {
			trigger('viewDestroy', currentView, currentView, currentView.element);
			unselect();
			currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
			freezeContentHeight();
			currentView.element.remove();
			header.deactivateButton(currentView.name);
		}

		header.activateButton(newViewName);

		currentView = new fcViews[newViewName](
			$("<div class='fc-view fc-view-" + newViewName + "' style='position:relative'/>")
				.appendTo(content),
			t // the calendar object
		);

		renderView();
		unfreezeContentHeight();

		ignoreWindowResize--;
	}


	function renderView(inc) {
		if (
			!currentView.start || // never rendered before
			inc || date < currentView.start || date >= currentView.end // or new date range
		) {
			if (elementVisible()) {
				_renderView(inc);
			}
		}
	}


	function _renderView(inc) { // assumes elementVisible
		ignoreWindowResize++;

		if (currentView.start) { // already been rendered?
			trigger('viewDestroy', currentView, currentView, currentView.element);
			unselect();
			clearEvents();
		}

		freezeContentHeight();
		currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else
		setSize();
		unfreezeContentHeight();
		(currentView.afterRender || noop)();

		updateTitle();
		updateTodayButton();

		trigger('viewRender', currentView, currentView, currentView.element);
		currentView.trigger('viewDisplay', _element); // deprecated

		ignoreWindowResize--;

		getAndRenderEvents();
	}
	
	

	/* Resizing
	-----------------------------------------------------------------------------*/
	
	
	function updateSize() {
		if (elementVisible()) {
			unselect();
			clearEvents();
			calcSize();
			setSize();
			renderEvents();
		}
	}
	
	
	function calcSize() { // assumes elementVisible
		if (options.contentHeight) {
			suggestedViewHeight = options.contentHeight;
		}
		else if (options.height) {
			suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
		}
		else {
			suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
		}
	}
	
	
	function setSize() { // assumes elementVisible

		if (suggestedViewHeight === undefined) {
			calcSize(); // for first time
				// NOTE: we don't want to recalculate on every renderView because
				// it could result in oscillating heights due to scrollbars.
		}

		ignoreWindowResize++;
		currentView.setHeight(suggestedViewHeight);
		currentView.setWidth(content.width());
		ignoreWindowResize--;

		elementOuterWidth = element.outerWidth();
	}
	
	
	function windowResize() {
		if (!ignoreWindowResize) {
			if (currentView.start) { // view has already been rendered
				var uid = ++resizeUID;
				setTimeout(function() { // add a delay
					if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
						if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
							ignoreWindowResize++; // in case the windowResize callback changes the height
							updateSize();
							currentView.trigger('windowResize', _element);
							ignoreWindowResize--;
						}
					}
				}, 200);
			}else{
				// calendar must have been initialized in a 0x0 iframe that has just been resized
				lateRender();
			}
		}
	}
	
	
	
	/* Event Fetching/Rendering
	-----------------------------------------------------------------------------*/
	// TODO: going forward, most of this stuff should be directly handled by the view


	function refetchEvents() { // can be called as an API method
		clearEvents();
		fetchAndRenderEvents();
	}


	function rerenderEvents(modifiedEventID) { // can be called as an API method
		clearEvents();
		renderEvents(modifiedEventID);
	}


	function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack
		if (elementVisible()) {
			currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
			currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
			currentView.trigger('eventAfterAllRender');
		}
	}


	function clearEvents() {
		currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
		currentView.clearEvents(); // actually remove the DOM elements
		currentView.clearEventData(); // for View.js, TODO: unify with clearEvents
	}
	

	function getAndRenderEvents() {
		if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
			fetchAndRenderEvents();
		}
		else {
			renderEvents();
		}
	}


	function fetchAndRenderEvents() {
		fetchEvents(currentView.visStart, currentView.visEnd);
			// ... will call reportEvents
			// ... which will call renderEvents
	}

	
	// called when event data arrives
	function reportEvents(_events) {
		events = _events;
		renderEvents();
	}


	// called when a single event's data has been changed
	function reportEventChange(eventID) {
		rerenderEvents(eventID);
	}



	/* Header Updating
	-----------------------------------------------------------------------------*/


	function updateTitle() {
		header.updateTitle(currentView.title);
	}


	function updateTodayButton() {
		var today = new Date();
		if (today >= currentView.start && today < currentView.end) {
			header.disableButton('today');
		}
		else {
			header.enableButton('today');
		}
	}
	


	/* Selection
	-----------------------------------------------------------------------------*/
	

	function select(start, end, allDay) {
		currentView.select(start, end, allDay===undefined ? true : allDay);
	}
	

	function unselect() { // safe to be called before renderView
		if (currentView) {
			currentView.unselect();
		}
	}
	
	
	
	/* Date
	-----------------------------------------------------------------------------*/
	
	
	function prev() {
		renderView(-1);
	}
	
	
	function next() {
		renderView(1);
	}
	
	
	function prevYear() {
		addYears(date, -1);
		renderView();
	}
	
	
	function nextYear() {
		addYears(date, 1);
		renderView();
	}
	
	
	function today() {
		date = new Date();
		renderView();
	}
	
	
	function gotoDate(year, month, dateOfMonth) {
		if (year instanceof Date) {
			date = cloneDate(year); // provided 1 argument, a Date
		}else{
			setYMD(date, year, month, dateOfMonth);
		}
		renderView();
	}
	
	
	function incrementDate(years, months, days) {
		if (years !== undefined) {
			addYears(date, years);
		}
		if (months !== undefined) {
			addMonths(date, months);
		}
		if (days !== undefined) {
			addDays(date, days);
		}
		renderView();
	}
	
	
	function getDate() {
		return cloneDate(date);
	}



	/* Height "Freezing"
	-----------------------------------------------------------------------------*/


	function freezeContentHeight() {
		content.css({
			width: '100%',
			height: content.height(),
			overflow: 'hidden'
		});
	}


	function unfreezeContentHeight() {
		content.css({
			width: '',
			height: '',
			overflow: ''
		});
	}
	
	
	
	/* Misc
	-----------------------------------------------------------------------------*/
	
	
	function getView() {
		return currentView;
	}
	
	
	function option(name, value) {
		if (value === undefined) {
			return options[name];
		}
		if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
			options[name] = value;
			updateSize();
		}
	}
	
	
	function trigger(name, thisObj) {
		if (options[name]) {
			return options[name].apply(
				thisObj || _element,
				Array.prototype.slice.call(arguments, 2)
			);
		}
	}
	
	
	
	/* External Dragging
	------------------------------------------------------------------------*/
	
	if (options.droppable) {
		$(document)
			.bind('dragstart', function(ev, ui) {
				var _e = ev.target;
				var e = $(_e);
				if (!e.parents('.fc').length) { // not already inside a calendar
					var accept = options.dropAccept;
					if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
						_dragElement = _e;
						currentView.dragStart(_dragElement, ev, ui);
					}
				}
			})
			.bind('dragstop', function(ev, ui) {
				if (_dragElement) {
					currentView.dragStop(_dragElement, ev, ui);
					_dragElement = null;
				}
			});
	}
	

}

;;

function Header(calendar, options) {
	var t = this;
	
	
	// exports
	t.render = render;
	t.destroy = destroy;
	t.updateTitle = updateTitle;
	t.activateButton = activateButton;
	t.deactivateButton = deactivateButton;
	t.disableButton = disableButton;
	t.enableButton = enableButton;
	
	
	// locals
	var element = $([]);
	var tm;
	


	function render() {
		tm = options.theme ? 'ui' : 'fc';
		var sections = options.header;
		if (sections) {
			element = $("<table class='fc-header' style='width:100%'/>")
				.append(
					$("<tr/>")
						.append(renderSection('left'))
						.append(renderSection('center'))
						.append(renderSection('right'))
				);
			return element;
		}
	}
	
	
	function destroy() {
		element.remove();
	}
	
	
	function renderSection(position) {
		var e = $("<td class='fc-header-" + position + "'/>");
		var buttonStr = options.header[position];
		if (buttonStr) {
			$.each(buttonStr.split(' '), function(i) {
				if (i > 0) {
					e.append("<span class='fc-header-space'/>");
				}
				var prevButton;
				$.each(this.split(','), function(j, buttonName) {
					if (buttonName == 'title') {
						e.append("<span class='fc-header-title'><h2>&nbsp;</h2></span>");
						if (prevButton) {
							prevButton.addClass(tm + '-corner-right');
						}
						prevButton = null;
					}else{
						var buttonClick;
						if (calendar[buttonName]) {
							buttonClick = calendar[buttonName]; // calendar method
						}
						else if (fcViews[buttonName]) {
							buttonClick = function() {
								button.removeClass(tm + '-state-hover'); // forget why
								calendar.changeView(buttonName);
							};
						}
						if (buttonClick) {
							var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
							var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
							var button = $(
								"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default'>" +
									(icon ?
										"<span class='fc-icon-wrap'>" +
											"<span class='ui-icon ui-icon-" + icon + "'/>" +
										"</span>" :
										text
										) +
								"</span>"
								)
								.click(function() {
									if (!button.hasClass(tm + '-state-disabled')) {
										buttonClick();
									}
								})
								.mousedown(function() {
									button
										.not('.' + tm + '-state-active')
										.not('.' + tm + '-state-disabled')
										.addClass(tm + '-state-down');
								})
								.mouseup(function() {
									button.removeClass(tm + '-state-down');
								})
								.hover(
									function() {
										button
											.not('.' + tm + '-state-active')
											.not('.' + tm + '-state-disabled')
											.addClass(tm + '-state-hover');
									},
									function() {
										button
											.removeClass(tm + '-state-hover')
											.removeClass(tm + '-state-down');
									}
								)
								.appendTo(e);
							disableTextSelection(button);
							if (!prevButton) {
								button.addClass(tm + '-corner-left');
							}
							prevButton = button;
						}
					}
				});
				if (prevButton) {
					prevButton.addClass(tm + '-corner-right');
				}
			});
		}
		return e;
	}
	
	
	function updateTitle(html) {
		element.find('h2')
			.html(html);
	}
	
	
	function activateButton(buttonName) {
		element.find('span.fc-button-' + buttonName)
			.addClass(tm + '-state-active');
	}
	
	
	function deactivateButton(buttonName) {
		element.find('span.fc-button-' + buttonName)
			.removeClass(tm + '-state-active');
	}
	
	
	function disableButton(buttonName) {
		element.find('span.fc-button-' + buttonName)
			.addClass(tm + '-state-disabled');
	}
	
	
	function enableButton(buttonName) {
		element.find('span.fc-button-' + buttonName)
			.removeClass(tm + '-state-disabled');
	}


}

;;

fc.sourceNormalizers = [];
fc.sourceFetchers = [];

var ajaxDefaults = {
	dataType: 'json',
	cache: false
};

var eventGUID = 1;


function EventManager(options, _sources) {
	var t = this;
	
	
	// exports
	t.isFetchNeeded = isFetchNeeded;
	t.fetchEvents = fetchEvents;
	t.addEventSource = addEventSource;
	t.removeEventSource = removeEventSource;
	t.updateEvent = updateEvent;
	t.renderEvent = renderEvent;
	t.removeEvents = removeEvents;
	t.clientEvents = clientEvents;
	t.normalizeEvent = normalizeEvent;
	
	
	// imports
	var trigger = t.trigger;
	var getView = t.getView;
	var reportEvents = t.reportEvents;
	
	
	// locals
	var stickySource = { events: [] };
	var sources = [ stickySource ];
	var rangeStart, rangeEnd;
	var currentFetchID = 0;
	var pendingSourceCnt = 0;
	var loadingLevel = 0;
	var cache = [];
	
	
	for (var i=0; i<_sources.length; i++) {
		_addEventSource(_sources[i]);
	}
	
	
	
	/* Fetching
	-----------------------------------------------------------------------------*/
	
	
	function isFetchNeeded(start, end) {
		return !rangeStart || start < rangeStart || end > rangeEnd;
	}
	
	
	function fetchEvents(start, end) {
		rangeStart = start;
		rangeEnd = end;
		cache = [];
		var fetchID = ++currentFetchID;
		var len = sources.length;
		pendingSourceCnt = len;
		for (var i=0; i<len; i++) {
			fetchEventSource(sources[i], fetchID);
		}
	}
	
	
	function fetchEventSource(source, fetchID) {
		_fetchEventSource(source, function(events) {
			if (fetchID == currentFetchID) {
				if (events) {

					if (options.eventDataTransform) {
						events = $.map(events, options.eventDataTransform);
					}
					if (source.eventDataTransform) {
						events = $.map(events, source.eventDataTransform);
					}
					// TODO: this technique is not ideal for static array event sources.
					//  For arrays, we'll want to process all events right in the beginning, then never again.
				
					for (var i=0; i<events.length; i++) {
						events[i].source = source;
						normalizeEvent(events[i]);
					}
					cache = cache.concat(events);
				}
				pendingSourceCnt--;
				if (!pendingSourceCnt) {
					reportEvents(cache);
				}
			}
		});
	}
	
	
	function _fetchEventSource(source, callback) {
		var i;
		var fetchers = fc.sourceFetchers;
		var res;
		for (i=0; i<fetchers.length; i++) {
			res = fetchers[i](source, rangeStart, rangeEnd, callback);
			if (res === true) {
				// the fetcher is in charge. made its own async request
				return;
			}
			else if (typeof res == 'object') {
				// the fetcher returned a new source. process it
				_fetchEventSource(res, callback);
				return;
			}
		}
		var events = source.events;
		if (events) {
			if ($.isFunction(events)) {
				pushLoading();
				events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
					callback(events);
					popLoading();
				});
			}
			else if ($.isArray(events)) {
				callback(events);
			}
			else {
				callback();
			}
		}else{
			var url = source.url;
			if (url) {
				var success = source.success;
				var error = source.error;
				var complete = source.complete;

				// retrieve any outbound GET/POST $.ajax data from the options
				var customData;
				if ($.isFunction(source.data)) {
					// supplied as a function that returns a key/value object
					customData = source.data();
				}
				else {
					// supplied as a straight key/value object
					customData = source.data;
				}

				// use a copy of the custom data so we can modify the parameters
				// and not affect the passed-in object.
				var data = $.extend({}, customData || {});

				var startParam = firstDefined(source.startParam, options.startParam);
				var endParam = firstDefined(source.endParam, options.endParam);
				if (startParam) {
					data[startParam] = Math.round(+rangeStart / 1000);
				}
				if (endParam) {
					data[endParam] = Math.round(+rangeEnd / 1000);
				}

				pushLoading();
				$.ajax($.extend({}, ajaxDefaults, source, {
					data: data,
					success: function(events) {
						events = events || [];
						var res = applyAll(success, this, arguments);
						if ($.isArray(res)) {
							events = res;
						}
						callback(events);
					},
					error: function() {
						applyAll(error, this, arguments);
						callback();
					},
					complete: function() {
						applyAll(complete, this, arguments);
						popLoading();
					}
				}));
			}else{
				callback();
			}
		}
	}
	
	
	
	/* Sources
	-----------------------------------------------------------------------------*/
	

	function addEventSource(source) {
		source = _addEventSource(source);
		if (source) {
			pendingSourceCnt++;
			fetchEventSource(source, currentFetchID); // will eventually call reportEvents
		}
	}
	
	
	function _addEventSource(source) {
		if ($.isFunction(source) || $.isArray(source)) {
			source = { events: source };
		}
		else if (typeof source == 'string') {
			source = { url: source };
		}
		if (typeof source == 'object') {
			normalizeSource(source);
			sources.push(source);
			return source;
		}
	}
	

	function removeEventSource(source) {
		sources = $.grep(sources, function(src) {
			return !isSourcesEqual(src, source);
		});
		// remove all client events from that source
		cache = $.grep(cache, function(e) {
			return !isSourcesEqual(e.source, source);
		});
		reportEvents(cache);
	}
	
	
	
	/* Manipulation
	-----------------------------------------------------------------------------*/
	
	
	function updateEvent(event) { // update an existing event
		var i, len = cache.length, e,
			defaultEventEnd = getView().defaultEventEnd, // getView???
			startDelta = event.start - event._start,
			endDelta = event.end ?
				(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
				: 0;                                                      // was null and event was just resized
		for (i=0; i<len; i++) {
			e = cache[i];
			if (e._id == event._id && e != event) {
				e.start = new Date(+e.start + startDelta);
				if (event.end) {
					if (e.end) {
						e.end = new Date(+e.end + endDelta);
					}else{
						e.end = new Date(+defaultEventEnd(e) + endDelta);
					}
				}else{
					e.end = null;
				}
				e.title = event.title;
				e.url = event.url;
				e.allDay = event.allDay;
				e.className = event.className;
				e.editable = event.editable;
				e.color = event.color;
				e.backgroundColor = event.backgroundColor;
				e.borderColor = event.borderColor;
				e.textColor = event.textColor;
				normalizeEvent(e);
			}
		}
		normalizeEvent(event);
		reportEvents(cache);
	}
	
	
	function renderEvent(event, stick) {
		normalizeEvent(event);
		if (!event.source) {
			if (stick) {
				stickySource.events.push(event);
				event.source = stickySource;
			}
			cache.push(event);
		}
		reportEvents(cache);
	}
	
	
	function removeEvents(filter) {
		if (!filter) { // remove all
			cache = [];
			// clear all array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = [];
				}
			}
		}else{
			if (!$.isFunction(filter)) { // an event ID
				var id = filter + '';
				filter = function(e) {
					return e._id == id;
				};
			}
			cache = $.grep(cache, filter, true);
			// remove events from array sources
			for (var i=0; i<sources.length; i++) {
				if ($.isArray(sources[i].events)) {
					sources[i].events = $.grep(sources[i].events, filter, true);
				}
			}
		}
		reportEvents(cache);
	}
	
	
	function clientEvents(filter) {
		if ($.isFunction(filter)) {
			return $.grep(cache, filter);
		}
		else if (filter) { // an event ID
			filter += '';
			return $.grep(cache, function(e) {
				return e._id == filter;
			});
		}
		return cache; // else, return all
	}
	
	
	
	/* Loading State
	-----------------------------------------------------------------------------*/
	
	
	function pushLoading() {
		if (!loadingLevel++) {
			trigger('loading', null, true, getView());
		}
	}
	
	
	function popLoading() {
		if (!--loadingLevel) {
			trigger('loading', null, false, getView());
		}
	}
	
	
	
	/* Event Normalization
	-----------------------------------------------------------------------------*/
	
	
	function normalizeEvent(event) {
		var source = event.source || {};
		var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
		event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
		if (event.date) {
			if (!event.start) {
				event.start = event.date;
			}
			delete event.date;
		}
		event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
		event.end = parseDate(event.end, ignoreTimezone);
		if (event.end && event.end <= event.start) {
			event.end = null;
		}
		event._end = event.end ? cloneDate(event.end) : null;
		if (event.allDay === undefined) {
			event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
		}
		if (event.className) {
			if (typeof event.className == 'string') {
				event.className = event.className.split(/\s+/);
			}
		}else{
			event.className = [];
		}
		// TODO: if there is no start date, return false to indicate an invalid event
	}
	
	
	
	/* Utils
	------------------------------------------------------------------------------*/
	
	
	function normalizeSource(source) {
		if (source.className) {
			// TODO: repeat code, same code for event classNames
			if (typeof source.className == 'string') {
				source.className = source.className.split(/\s+/);
			}
		}else{
			source.className = [];
		}
		var normalizers = fc.sourceNormalizers;
		for (var i=0; i<normalizers.length; i++) {
			normalizers[i](source);
		}
	}
	
	
	function isSourcesEqual(source1, source2) {
		return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
	}
	
	
	function getSourcePrimitive(source) {
		return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
	}


}

;;


fc.addDays = addDays;
fc.cloneDate = cloneDate;
fc.parseDate = parseDate;
fc.parseISO8601 = parseISO8601;
fc.parseTime = parseTime;
fc.formatDate = formatDate;
fc.formatDates = formatDates;



/* Date Math
-----------------------------------------------------------------------------*/

var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
	DAY_MS = 86400000,
	HOUR_MS = 3600000,
	MINUTE_MS = 60000;
	

function addYears(d, n, keepTime) {
	d.setFullYear(d.getFullYear() + n);
	if (!keepTime) {
		clearTime(d);
	}
	return d;
}


function addMonths(d, n, keepTime) { // prevents day overflow/underflow
	if (+d) { // prevent infinite looping on invalid dates
		var m = d.getMonth() + n,
			check = cloneDate(d);
		check.setDate(1);
		check.setMonth(m);
		d.setMonth(m);
		if (!keepTime) {
			clearTime(d);
		}
		while (d.getMonth() != check.getMonth()) {
			d.setDate(d.getDate() + (d < check ? 1 : -1));
		}
	}
	return d;
}


function addDays(d, n, keepTime) { // deals with daylight savings
	if (+d) {
		var dd = d.getDate() + n,
			check = cloneDate(d);
		check.setHours(9); // set to middle of day
		check.setDate(dd);
		d.setDate(dd);
		if (!keepTime) {
			clearTime(d);
		}
		fixDate(d, check);
	}
	return d;
}


function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
	if (+d) { // prevent infinite looping on invalid dates
		while (d.getDate() != check.getDate()) {
			d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
		}
	}
}


function addMinutes(d, n) {
	d.setMinutes(d.getMinutes() + n);
	return d;
}


function clearTime(d) {
	d.setHours(0);
	d.setMinutes(0);
	d.setSeconds(0); 
	d.setMilliseconds(0);
	return d;
}


function cloneDate(d, dontKeepTime) {
	if (dontKeepTime) {
		return clearTime(new Date(+d));
	}
	return new Date(+d);
}


function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
	var i=0, d;
	do {
		d = new Date(1970, i++, 1);
	} while (d.getHours()); // != 0
	return d;
}


function dayDiff(d1, d2) { // d1 - d2
	return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
}


function setYMD(date, y, m, d) {
	if (y !== undefined && y != date.getFullYear()) {
		date.setDate(1);
		date.setMonth(0);
		date.setFullYear(y);
	}
	if (m !== undefined && m != date.getMonth()) {
		date.setDate(1);
		date.setMonth(m);
	}
	if (d !== undefined) {
		date.setDate(d);
	}
}



/* Date Parsing
-----------------------------------------------------------------------------*/


function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
	if (typeof s == 'object') { // already a Date object
		return s;
	}
	if (typeof s == 'number') { // a UNIX timestamp
		return new Date(s * 1000);
	}
	if (typeof s == 'string') {
		if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
			return new Date(parseFloat(s) * 1000);
		}
		if (ignoreTimezone === undefined) {
			ignoreTimezone = true;
		}
		return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
	}
	// TODO: never return invalid dates (like from new Date(<string>)), return null instead
	return null;
}


function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
	// derived from http://delete.me.uk/2005/03/iso8601.html
	// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
	var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
	if (!m) {
		return null;
	}
	var date = new Date(m[1], 0, 1);
	if (ignoreTimezone || !m[13]) {
		var check = new Date(m[1], 0, 1, 9, 0);
		if (m[3]) {
			date.setMonth(m[3] - 1);
			check.setMonth(m[3] - 1);
		}
		if (m[5]) {
			date.setDate(m[5]);
			check.setDate(m[5]);
		}
		fixDate(date, check);
		if (m[7]) {
			date.setHours(m[7]);
		}
		if (m[8]) {
			date.setMinutes(m[8]);
		}
		if (m[10]) {
			date.setSeconds(m[10]);
		}
		if (m[12]) {
			date.setMilliseconds(Number("0." + m[12]) * 1000);
		}
		fixDate(date, check);
	}else{
		date.setUTCFullYear(
			m[1],
			m[3] ? m[3] - 1 : 0,
			m[5] || 1
		);
		date.setUTCHours(
			m[7] || 0,
			m[8] || 0,
			m[10] || 0,
			m[12] ? Number("0." + m[12]) * 1000 : 0
		);
		if (m[14]) {
			var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
			offset *= m[15] == '-' ? 1 : -1;
			date = new Date(+date + (offset * 60 * 1000));
		}
	}
	return date;
}


function parseTime(s) { // returns minutes since start of day
	if (typeof s == 'number') { // an hour
		return s * 60;
	}
	if (typeof s == 'object') { // a Date object
		return s.getHours() * 60 + s.getMinutes();
	}
	var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
	if (m) {
		var h = parseInt(m[1], 10);
		if (m[3]) {
			h %= 12;
			if (m[3].toLowerCase().charAt(0) == 'p') {
				h += 12;
			}
		}
		return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
	}
}



/* Date Formatting
-----------------------------------------------------------------------------*/
// TODO: use same function formatDate(date, [date2], format, [options])


function formatDate(date, format, options) {
	return formatDates(date, null, format, options);
}


function formatDates(date1, date2, format, options) {
	options = options || defaults;
	var date = date1,
		otherDate = date2,
		i, len = format.length, c,
		i2, formatter,
		res = '';
	for (i=0; i<len; i++) {
		c = format.charAt(i);
		if (c == "'") {
			for (i2=i+1; i2<len; i2++) {
				if (format.charAt(i2) == "'") {
					if (date) {
						if (i2 == i+1) {
							res += "'";
						}else{
							res += format.substring(i+1, i2);
						}
						i = i2;
					}
					break;
				}
			}
		}
		else if (c == '(') {
			for (i2=i+1; i2<len; i2++) {
				if (format.charAt(i2) == ')') {
					var subres = formatDate(date, format.substring(i+1, i2), options);
					if (parseInt(subres.replace(/\D/, ''), 10)) {
						res += subres;
					}
					i = i2;
					break;
				}
			}
		}
		else if (c == '[') {
			for (i2=i+1; i2<len; i2++) {
				if (format.charAt(i2) == ']') {
					var subformat = format.substring(i+1, i2);
					var subres = formatDate(date, subformat, options);
					if (subres != formatDate(otherDate, subformat, options)) {
						res += subres;
					}
					i = i2;
					break;
				}
			}
		}
		else if (c == '{') {
			date = date2;
			otherDate = date1;
		}
		else if (c == '}') {
			date = date1;
			otherDate = date2;
		}
		else {
			for (i2=len; i2>i; i2--) {
				if (formatter = dateFormatters[format.substring(i, i2)]) {
					if (date) {
						res += formatter(date, options);
					}
					i = i2 - 1;
					break;
				}
			}
			if (i2 == i) {
				if (date) {
					res += c;
				}
			}
		}
	}
	return res;
};


var dateFormatters = {
	s	: function(d)	{ return d.getSeconds() },
	ss	: function(d)	{ return zeroPad(d.getSeconds()) },
	m	: function(d)	{ return d.getMinutes() },
	mm	: function(d)	{ return zeroPad(d.getMinutes()) },
	h	: function(d)	{ return d.getHours() % 12 || 12 },
	hh	: function(d)	{ return zeroPad(d.getHours() % 12 || 12) },
	H	: function(d)	{ return d.getHours() },
	HH	: function(d)	{ return zeroPad(d.getHours()) },
	d	: function(d)	{ return d.getDate() },
	dd	: function(d)	{ return zeroPad(d.getDate()) },
	ddd	: function(d,o)	{ return o.dayNamesShort[d.getDay()] },
	dddd: function(d,o)	{ return o.dayNames[d.getDay()] },
	M	: function(d)	{ return d.getMonth() + 1 },
	MM	: function(d)	{ return zeroPad(d.getMonth() + 1) },
	MMM	: function(d,o)	{ return o.monthNamesShort[d.getMonth()] },
	MMMM: function(d,o)	{ return o.monthNames[d.getMonth()] },
	yy	: function(d)	{ return (d.getFullYear()+'').substring(2) },
	yyyy: function(d)	{ return d.getFullYear() },
	t	: function(d)	{ return d.getHours() < 12 ? 'a' : 'p' },
	tt	: function(d)	{ return d.getHours() < 12 ? 'am' : 'pm' },
	T	: function(d)	{ return d.getHours() < 12 ? 'A' : 'P' },
	TT	: function(d)	{ return d.getHours() < 12 ? 'AM' : 'PM' },
	u	: function(d)	{ return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
	S	: function(d)	{
		var date = d.getDate();
		if (date > 10 && date < 20) {
			return 'th';
		}
		return ['st', 'nd', 'rd'][date%10-1] || 'th';
	},
	w   : function(d, o) { // local
		return o.weekNumberCalculation(d);
	},
	W   : function(d) { // ISO
		return iso8601Week(d);
	}
};
fc.dateFormatters = dateFormatters;


/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js)
 * 
 * Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
 * `date` - the date to get the week for
 * `number` - the number of the week within the year that contains this date
 */
function iso8601Week(date) {
	var time;
	var checkDate = new Date(date.getTime());

	// Find Thursday of this week starting on Monday
	checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));

	time = checkDate.getTime();
	checkDate.setMonth(0); // Compare with Jan 1
	checkDate.setDate(1);
	return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
}


;;

fc.applyAll = applyAll;


/* Event Date Math
-----------------------------------------------------------------------------*/


function exclEndDay(event) {
	if (event.end) {
		return _exclEndDay(event.end, event.allDay);
	}else{
		return addDays(cloneDate(event.start), 1);
	}
}


function _exclEndDay(end, allDay) {
	end = cloneDate(end);
	return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
	// why don't we check for seconds/ms too?
}



/* Event Element Binding
-----------------------------------------------------------------------------*/


function lazySegBind(container, segs, bindHandlers) {
	container.unbind('mouseover').mouseover(function(ev) {
		var parent=ev.target, e,
			i, seg;
		while (parent != this) {
			e = parent;
			parent = parent.parentNode;
		}
		if ((i = e._fci) !== undefined) {
			e._fci = undefined;
			seg = segs[i];
			bindHandlers(seg.event, seg.element, seg);
			$(ev.target).trigger(ev);
		}
		ev.stopPropagation();
	});
}



/* Element Dimensions
-----------------------------------------------------------------------------*/


function setOuterWidth(element, width, includeMargins) {
	for (var i=0, e; i<element.length; i++) {
		e = $(element[i]);
		e.width(Math.max(0, width - hsides(e, includeMargins)));
	}
}


function setOuterHeight(element, height, includeMargins) {
	for (var i=0, e; i<element.length; i++) {
		e = $(element[i]);
		e.height(Math.max(0, height - vsides(e, includeMargins)));
	}
}


function hsides(element, includeMargins) {
	return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
}


function hpadding(element) {
	return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +
	       (parseFloat($.css(element[0], 'paddingRight', true)) || 0);
}


function hmargins(element) {
	return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +
	       (parseFloat($.css(element[0], 'marginRight', true)) || 0);
}


function hborders(element) {
	return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +
	       (parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);
}


function vsides(element, includeMargins) {
	return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
}


function vpadding(element) {
	return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +
	       (parseFloat($.css(element[0], 'paddingBottom', true)) || 0);
}


function vmargins(element) {
	return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +
	       (parseFloat($.css(element[0], 'marginBottom', true)) || 0);
}


function vborders(element) {
	return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +
	       (parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);
}



/* Misc Utils
-----------------------------------------------------------------------------*/


//TODO: arraySlice
//TODO: isFunction, grep ?


function noop() { }


function dateCompare(a, b) {
	return a - b;
}


function arrayMax(a) {
	return Math.max.apply(Math, a);
}


function zeroPad(n) {
	return (n < 10 ? '0' : '') + n;
}


function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
	if (obj[name] !== undefined) {
		return obj[name];
	}
	var parts = name.split(/(?=[A-Z])/),
		i=parts.length-1, res;
	for (; i>=0; i--) {
		res = obj[parts[i].toLowerCase()];
		if (res !== undefined) {
			return res;
		}
	}
	return obj[''];
}


function htmlEscape(s) {
	return s.replace(/&/g, '&amp;')
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
		.replace(/'/g, '&#039;')
		.replace(/"/g, '&quot;')
		.replace(/\n/g, '<br />');
}


function disableTextSelection(element) {
	element
		.attr('unselectable', 'on')
		.css('MozUserSelect', 'none')
		.bind('selectstart.ui', function() { return false; });
}


/*
function enableTextSelection(element) {
	element
		.attr('unselectable', 'off')
		.css('MozUserSelect', '')
		.unbind('selectstart.ui');
}
*/


function markFirstLast(e) {
	e.children()
		.removeClass('fc-first fc-last')
		.filter(':first-child')
			.addClass('fc-first')
		.end()
		.filter(':last-child')
			.addClass('fc-last');
}


function setDayID(cell, date) {
	cell.each(function(i, _cell) {
		_cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
		// TODO: make a way that doesn't rely on order of classes
	});
}


function getSkinCss(event, opt) {
	var source = event.source || {};
	var eventColor = event.color;
	var sourceColor = source.color;
	var optionColor = opt('eventColor');
	var backgroundColor =
		event.backgroundColor ||
		eventColor ||
		source.backgroundColor ||
		sourceColor ||
		opt('eventBackgroundColor') ||
		optionColor;
	var borderColor =
		event.borderColor ||
		eventColor ||
		source.borderColor ||
		sourceColor ||
		opt('eventBorderColor') ||
		optionColor;
	var textColor =
		event.textColor ||
		source.textColor ||
		opt('eventTextColor');
	var statements = [];
	if (backgroundColor) {
		statements.push('background-color:' + backgroundColor);
	}
	if (borderColor) {
		statements.push('border-color:' + borderColor);
	}
	if (textColor) {
		statements.push('color:' + textColor);
	}
	return statements.join(';');
}


function applyAll(functions, thisObj, args) {
	if ($.isFunction(functions)) {
		functions = [ functions ];
	}
	if (functions) {
		var i;
		var ret;
		for (i=0; i<functions.length; i++) {
			ret = functions[i].apply(thisObj, args) || ret;
		}
		return ret;
	}
}


function firstDefined() {
	for (var i=0; i<arguments.length; i++) {
		if (arguments[i] !== undefined) {
			return arguments[i];
		}
	}
}


;;

fcViews.month = MonthView;

function MonthView(element, calendar) {
	var t = this;
	
	
	// exports
	t.render = render;
	
	
	// imports
	BasicView.call(t, element, calendar, 'month');
	var opt = t.opt;
	var renderBasic = t.renderBasic;
	var skipHiddenDays = t.skipHiddenDays;
	var getCellsPerWeek = t.getCellsPerWeek;
	var formatDate = calendar.formatDate;
	
	
	function render(date, delta) {

		if (delta) {
			addMonths(date, delta);
			date.setDate(1);
		}

		var firstDay = opt('firstDay');

		var start = cloneDate(date, true);
		start.setDate(1);

		var end = addMonths(cloneDate(start), 1);

		var visStart = cloneDate(start);
		addDays(visStart, -((visStart.getDay() - firstDay + 7) % 7));
		skipHiddenDays(visStart);

		var visEnd = cloneDate(end);
		addDays(visEnd, (7 - visEnd.getDay() + firstDay) % 7);
		skipHiddenDays(visEnd, -1, true);

		var colCnt = getCellsPerWeek();
		var rowCnt = Math.round(dayDiff(visEnd, visStart) / 7); // should be no need for Math.round

		if (opt('weekMode') == 'fixed') {
			addDays(visEnd, (6 - rowCnt) * 7); // add weeks to make up for it
			rowCnt = 6;
		}

		t.title = formatDate(start, opt('titleFormat'));

		t.start = start;
		t.end = end;
		t.visStart = visStart;
		t.visEnd = visEnd;

		renderBasic(rowCnt, colCnt, true);
	}
	
	
}

;;

fcViews.basicWeek = BasicWeekView;

function BasicWeekView(element, calendar) {
	var t = this;
	
	
	// exports
	t.render = render;
	
	
	// imports
	BasicView.call(t, element, calendar, 'basicWeek');
	var opt = t.opt;
	var renderBasic = t.renderBasic;
	var skipHiddenDays = t.skipHiddenDays;
	var getCellsPerWeek = t.getCellsPerWeek;
	var formatDates = calendar.formatDates;
	
	
	function render(date, delta) {

		if (delta) {
			addDays(date, delta * 7);
		}

		var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
		var end = addDays(cloneDate(start), 7);

		var visStart = cloneDate(start);
		skipHiddenDays(visStart);

		var visEnd = cloneDate(end);
		skipHiddenDays(visEnd, -1, true);

		var colCnt = getCellsPerWeek();

		t.start = start;
		t.end = end;
		t.visStart = visStart;
		t.visEnd = visEnd;

		t.title = formatDates(
			visStart,
			addDays(cloneDate(visEnd), -1),
			opt('titleFormat')
		);

		renderBasic(1, colCnt, false);
	}
	
	
}

;;

fcViews.basicDay = BasicDayView;


function BasicDayView(element, calendar) {
	var t = this;
	
	
	// exports
	t.render = render;
	
	
	// imports
	BasicView.call(t, element, calendar, 'basicDay');
	var opt = t.opt;
	var renderBasic = t.renderBasic;
	var skipHiddenDays = t.skipHiddenDays;
	var formatDate = calendar.formatDate;
	
	
	function render(date, delta) {

		if (delta) {
			addDays(date, delta);
		}
		skipHiddenDays(date, delta < 0 ? -1 : 1);

		var start = cloneDate(date, true);
		var end = addDays(cloneDate(start), 1);

		t.title = formatDate(date, opt('titleFormat'));

		t.start = t.visStart = start;
		t.end = t.visEnd = end;

		renderBasic(1, 1, false);
	}
	
	
}

;;

setDefaults({
	weekMode: 'fixed'
});


function BasicView(element, calendar, viewName) {
	var t = this;
	
	
	// exports
	t.renderBasic = renderBasic;
	t.setHeight = setHeight;
	t.setWidth = setWidth;
	t.renderDayOverlay = renderDayOverlay;
	t.defaultSelectionEnd = defaultSelectionEnd;
	t.renderSelection = renderSelection;
	t.clearSelection = clearSelection;
	t.reportDayClick = reportDayClick; // for selection (kinda hacky)
	t.dragStart = dragStart;
	t.dragStop = dragStop;
	t.defaultEventEnd = defaultEventEnd;
	t.getHoverListener = function() { return hoverListener };
	t.colLeft = colLeft;
	t.colRight = colRight;
	t.colContentLeft = colContentLeft;
	t.colContentRight = colContentRight;
	t.getIsCellAllDay = function() { return true };
	t.allDayRow = allDayRow;
	t.getRowCnt = function() { return rowCnt };
	t.getColCnt = function() { return colCnt };
	t.getColWidth = function() { return colWidth };
	t.getDaySegmentContainer = function() { return daySegmentContainer };
	
	
	// imports
	View.call(t, element, calendar, viewName);
	OverlayManager.call(t);
	SelectionManager.call(t);
	BasicEventRenderer.call(t);
	var opt = t.opt;
	var trigger = t.trigger;
	var renderOverlay = t.renderOverlay;
	var clearOverlays = t.clearOverlays;
	var daySelectionMousedown = t.daySelectionMousedown;
	var cellToDate = t.cellToDate;
	var dateToCell = t.dateToCell;
	var rangeToSegments = t.rangeToSegments;
	var formatDate = calendar.formatDate;
	
	
	// locals
	
	var table;
	var head;
	var headCells;
	var body;
	var bodyRows;
	var bodyCells;
	var bodyFirstCells;
	var firstRowCellInners;
	var firstRowCellContentInners;
	var daySegmentContainer;
	
	var viewWidth;
	var viewHeight;
	var colWidth;
	var weekNumberWidth;
	
	var rowCnt, colCnt;
	var showNumbers;
	var coordinateGrid;
	var hoverListener;
	var colPositions;
	var colContentPositions;
	
	var tm;
	var colFormat;
	var showWeekNumbers;
	var weekNumberTitle;
	var weekNumberFormat;
	
	
	
	/* Rendering
	------------------------------------------------------------*/
	
	
	disableTextSelection(element.addClass('fc-grid'));
	
	
	function renderBasic(_rowCnt, _colCnt, _showNumbers) {
		rowCnt = _rowCnt;
		colCnt = _colCnt;
		showNumbers = _showNumbers;
		updateOptions();

		if (!body) {
			buildEventContainer();
		}

		buildTable();
	}
	
	
	function updateOptions() {
		tm = opt('theme') ? 'ui' : 'fc';
		colFormat = opt('columnFormat');

		// week # options. (TODO: bad, logic also in other views)
		showWeekNumbers = opt('weekNumbers');
		weekNumberTitle = opt('weekNumberTitle');
		if (opt('weekNumberCalculation') != 'iso') {
			weekNumberFormat = "w";
		}
		else {
			weekNumberFormat = "W";
		}
	}
	
	
	function buildEventContainer() {
		daySegmentContainer =
			$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
				.appendTo(element);
	}
	
	
	function buildTable() {
		var html = buildTableHTML();

		if (table) {
			table.remove();
		}
		table = $(html).appendTo(element);

		head = table.find('thead');
		headCells = head.find('.fc-day-header');
		body = table.find('tbody');
		bodyRows = body.find('tr');
		bodyCells = body.find('.fc-day');
		bodyFirstCells = bodyRows.find('td:first-child');

		firstRowCellInners = bodyRows.eq(0).find('.fc-day > div');
		firstRowCellContentInners = bodyRows.eq(0).find('.fc-day-content > div');
		
		markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
		markFirstLast(bodyRows); // marks first+last td's
		bodyRows.eq(0).addClass('fc-first');
		bodyRows.filter(':last').addClass('fc-last');

		bodyCells.each(function(i, _cell) {
			var date = cellToDate(
				Math.floor(i / colCnt),
				i % colCnt
			);
			trigger('dayRender', t, date, $(_cell));
		});

		dayBind(bodyCells);
	}



	/* HTML Building
	-----------------------------------------------------------*/


	function buildTableHTML() {
		var html =
			"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
			buildHeadHTML() +
			buildBodyHTML() +
			"</table>";

		return html;
	}


	function buildHeadHTML() {
		var headerClass = tm + "-widget-header";
		var html = '';
		var col;
		var date;

		html += "<thead><tr>";

		if (showWeekNumbers) {
			html +=
				"<th class='fc-week-number " + headerClass + "'>" +
				htmlEscape(weekNumberTitle) +
				"</th>";
		}

		for (col=0; col<colCnt; col++) {
			date = cellToDate(0, col);
			html +=
				"<th class='fc-day-header fc-" + dayIDs[date.getDay()] + " " + headerClass + "'>" +
				htmlEscape(formatDate(date, colFormat)) +
				"</th>";
		}

		html += "</tr></thead>";

		return html;
	}


	function buildBodyHTML() {
		var contentClass = tm + "-widget-content";
		var html = '';
		var row;
		var col;
		var date;

		html += "<tbody>";

		for (row=0; row<rowCnt; row++) {

			html += "<tr class='fc-week'>";

			if (showWeekNumbers) {
				date = cellToDate(row, 0);
				html +=
					"<td class='fc-week-number " + contentClass + "'>" +
					"<div>" +
					htmlEscape(formatDate(date, weekNumberFormat)) +
					"</div>" +
					"</td>";
			}

			for (col=0; col<colCnt; col++) {
				date = cellToDate(row, col);
				html += buildCellHTML(date);
			}

			html += "</tr>";
		}

		html += "</tbody>";

		return html;
	}


	function buildCellHTML(date) {
		var contentClass = tm + "-widget-content";
		var month = t.start.getMonth();
		var today = clearTime(new Date());
		var html = '';
		var classNames = [
			'fc-day',
			'fc-' + dayIDs[date.getDay()],
			contentClass
		];

		if (date.getMonth() != month) {
			classNames.push('fc-other-month');
		}
		if (+date == +today) {
			classNames.push(
				'fc-today',
				tm + '-state-highlight'
			);
		}
		else if (date < today) {
			classNames.push('fc-past');
		}
		else {
			classNames.push('fc-future');
		}

		html +=
			"<td" +
			" class='" + classNames.join(' ') + "'" +
			" data-date='" + formatDate(date, 'yyyy-MM-dd') + "'" +
			">" +
			"<div>";

		if (showNumbers) {
			html += "<div class='fc-day-number'>" + date.getDate() + "</div>";
		}

		html +=
			"<div class='fc-day-content'>" +
			"<div style='position:relative'>&nbsp;</div>" +
			"</div>" +
			"</div>" +
			"</td>";

		return html;
	}



	/* Dimensions
	-----------------------------------------------------------*/
	
	
	function setHeight(height) {
		viewHeight = height;
		
		var bodyHeight = viewHeight - head.height();
		var rowHeight;
		var rowHeightLast;
		var cell;
			
		if (opt('weekMode') == 'variable') {
			rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
		}else{
			rowHeight = Math.floor(bodyHeight / rowCnt);
			rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
		}
		
		bodyFirstCells.each(function(i, _cell) {
			if (i < rowCnt) {
				cell = $(_cell);
				cell.find('> div').css(
					'min-height',
					(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
				);
			}
		});
		
	}
	
	
	function setWidth(width) {
		viewWidth = width;
		colPositions.clear();
		colContentPositions.clear();

		weekNumberWidth = 0;
		if (showWeekNumbers) {
			weekNumberWidth = head.find('th.fc-week-number').outerWidth();
		}

		colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt);
		setOuterWidth(headCells.slice(0, -1), colWidth);
	}
	
	
	
	/* Day clicking and binding
	-----------------------------------------------------------*/
	
	
	function dayBind(days) {
		days.click(dayClick)
			.mousedown(daySelectionMousedown);
	}
	
	
	function dayClick(ev) {
		if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
			var date = parseISO8601($(this).data('date'));
			trigger('dayClick', this, date, true, ev);
		}
	}
	
	
	
	/* Semi-transparent Overlay Helpers
	------------------------------------------------------*/
	// TODO: should be consolidated with AgendaView's methods


	function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive

		if (refreshCoordinateGrid) {
			coordinateGrid.build();
		}

		var segments = rangeToSegments(overlayStart, overlayEnd);

		for (var i=0; i<segments.length; i++) {
			var segment = segments[i];
			dayBind(
				renderCellOverlay(
					segment.row,
					segment.leftCol,
					segment.row,
					segment.rightCol
				)
			);
		}
	}

	
	function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
		var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
		return renderOverlay(rect, element);
	}
	
	
	
	/* Selection
	-----------------------------------------------------------------------*/
	
	
	function defaultSelectionEnd(startDate, allDay) {
		return cloneDate(startDate);
	}
	
	
	function renderSelection(startDate, endDate, allDay) {
		renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
	}
	
	
	function clearSelection() {
		clearOverlays();
	}
	
	
	function reportDayClick(date, allDay, ev) {
		var cell = dateToCell(date);
		var _element = bodyCells[cell.row*colCnt + cell.col];
		trigger('dayClick', _element, date, allDay, ev);
	}
	
	
	
	/* External Dragging
	-----------------------------------------------------------------------*/
	
	
	function dragStart(_dragElement, ev, ui) {
		hoverListener.start(function(cell) {
			clearOverlays();
			if (cell) {
				renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
			}
		}, ev);
	}
	
	
	function dragStop(_dragElement, ev, ui) {
		var cell = hoverListener.stop();
		clearOverlays();
		if (cell) {
			var d = cellToDate(cell);
			trigger('drop', _dragElement, d, true, ev, ui);
		}
	}
	
	
	
	/* Utilities
	--------------------------------------------------------*/
	
	
	function defaultEventEnd(event) {
		return cloneDate(event.start);
	}
	
	
	coordinateGrid = new CoordinateGrid(function(rows, cols) {
		var e, n, p;
		headCells.each(function(i, _e) {
			e = $(_e);
			n = e.offset().left;
			if (i) {
				p[1] = n;
			}
			p = [n];
			cols[i] = p;
		});
		p[1] = n + e.outerWidth();
		bodyRows.each(function(i, _e) {
			if (i < rowCnt) {
				e = $(_e);
				n = e.offset().top;
				if (i) {
					p[1] = n;
				}
				p = [n];
				rows[i] = p;
			}
		});
		p[1] = n + e.outerHeight();
	});
	
	
	hoverListener = new HoverListener(coordinateGrid);
	
	colPositions = new HorizontalPositionCache(function(col) {
		return firstRowCellInners.eq(col);
	});

	colContentPositions = new HorizontalPositionCache(function(col) {
		return firstRowCellContentInners.eq(col);
	});


	function colLeft(col) {
		return colPositions.left(col);
	}


	function colRight(col) {
		return colPositions.right(col);
	}
	
	
	function colContentLeft(col) {
		return colContentPositions.left(col);
	}
	
	
	function colContentRight(col) {
		return colContentPositions.right(col);
	}
	
	
	function allDayRow(i) {
		return bodyRows.eq(i);
	}
	
}

;;

function BasicEventRenderer() {
	var t = this;
	
	
	// exports
	t.renderEvents = renderEvents;
	t.clearEvents = clearEvents;
	

	// imports
	DayEventRenderer.call(t);

	
	function renderEvents(events, modifiedEventId) {
		t.renderDayEvents(events, modifiedEventId);
	}
	
	
	function clearEvents() {
		t.getDaySegmentContainer().empty();
	}


	// TODO: have this class (and AgendaEventRenderer) be responsible for creating the event container div

}

;;

fcViews.agendaWeek = AgendaWeekView;

function AgendaWeekView(element, calendar) {
	var t = this;
	
	
	// exports
	t.render = render;
	
	
	// imports
	AgendaView.call(t, element, calendar, 'agendaWeek');
	var opt = t.opt;
	var renderAgenda = t.renderAgenda;
	var skipHiddenDays = t.skipHiddenDays;
	var getCellsPerWeek = t.getCellsPerWeek;
	var formatDates = calendar.formatDates;

	
	function render(date, delta) {

		if (delta) {
			addDays(date, delta * 7);
		}

		var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
		var end = addDays(cloneDate(start), 7);

		var visStart = cloneDate(start);
		skipHiddenDays(visStart);

		var visEnd = cloneDate(end);
		skipHiddenDays(visEnd, -1, true);

		var colCnt = getCellsPerWeek();

		t.title = formatDates(
			visStart,
			addDays(cloneDate(visEnd), -1),
			opt('titleFormat')
		);

		t.start = start;
		t.end = end;
		t.visStart = visStart;
		t.visEnd = visEnd;

		renderAgenda(colCnt);
	}

}

;;

fcViews.agendaDay = AgendaDayView;


function AgendaDayView(element, calendar) {
	var t = this;
	
	
	// exports
	t.render = render;
	
	
	// imports
	AgendaView.call(t, element, calendar, 'agendaDay');
	var opt = t.opt;
	var renderAgenda = t.renderAgenda;
	var skipHiddenDays = t.skipHiddenDays;
	var formatDate = calendar.formatDate;
	
	
	function render(date, delta) {

		if (delta) {
			addDays(date, delta);
		}
		skipHiddenDays(date, delta < 0 ? -1 : 1);

		var start = cloneDate(date, true);
		var end = addDays(cloneDate(start), 1);

		t.title = formatDate(date, opt('titleFormat'));

		t.start = t.visStart = start;
		t.end = t.visEnd = end;

		renderAgenda(1);
	}
	

}

;;

setDefaults({
	allDaySlot: true,
	allDayText: 'all-day',
	firstHour: 6,
	slotMinutes: 30,
	defaultEventMinutes: 120,
	axisFormat: 'h(:mm)tt',
	timeFormat: {
		agenda: 'h:mm{ - h:mm}'
	},
	dragOpacity: {
		agenda: .5
	},
	minTime: 0,
	maxTime: 24,
	slotEventOverlap: true
});


// TODO: make it work in quirks mode (event corners, all-day height)
// TODO: test liquid width, especially in IE6


function AgendaView(element, calendar, viewName) {
	var t = this;
	
	
	// exports
	t.renderAgenda = renderAgenda;
	t.setWidth = setWidth;
	t.setHeight = setHeight;
	t.afterRender = afterRender;
	t.defaultEventEnd = defaultEventEnd;
	t.timePosition = timePosition;
	t.getIsCellAllDay = getIsCellAllDay;
	t.allDayRow = getAllDayRow;
	t.getCoordinateGrid = function() { return coordinateGrid }; // specifically for AgendaEventRenderer
	t.getHoverListener = function() { return hoverListener };
	t.colLeft = colLeft;
	t.colRight = colRight;
	t.colContentLeft = colContentLeft;
	t.colContentRight = colContentRight;
	t.getDaySegmentContainer = function() { return daySegmentContainer };
	t.getSlotSegmentContainer = function() { return slotSegmentContainer };
	t.getMinMinute = function() { return minMinute };
	t.getMaxMinute = function() { return maxMinute };
	t.getSlotContainer = function() { return slotContainer };
	t.getRowCnt = function() { return 1 };
	t.getColCnt = function() { return colCnt };
	t.getColWidth = function() { return colWidth };
	t.getSnapHeight = function() { return snapHeight };
	t.getSnapMinutes = function() { return snapMinutes };
	t.defaultSelectionEnd = defaultSelectionEnd;
	t.renderDayOverlay = renderDayOverlay;
	t.renderSelection = renderSelection;
	t.clearSelection = clearSelection;
	t.reportDayClick = reportDayClick; // selection mousedown hack
	t.dragStart = dragStart;
	t.dragStop = dragStop;
	
	
	// imports
	View.call(t, element, calendar, viewName);
	OverlayManager.call(t);
	SelectionManager.call(t);
	AgendaEventRenderer.call(t);
	var opt = t.opt;
	var trigger = t.trigger;
	var renderOverlay = t.renderOverlay;
	var clearOverlays = t.clearOverlays;
	var reportSelection = t.reportSelection;
	var unselect = t.unselect;
	var daySelectionMousedown = t.daySelectionMousedown;
	var slotSegHtml = t.slotSegHtml;
	var cellToDate = t.cellToDate;
	var dateToCell = t.dateToCell;
	var rangeToSegments = t.rangeToSegments;
	var formatDate = calendar.formatDate;
	
	
	// locals
	
	var dayTable;
	var dayHead;
	var dayHeadCells;
	var dayBody;
	var dayBodyCells;
	var dayBodyCellInners;
	var dayBodyCellContentInners;
	var dayBodyFirstCell;
	var dayBodyFirstCellStretcher;
	var slotLayer;
	var daySegmentContainer;
	var allDayTable;
	var allDayRow;
	var slotScroller;
	var slotContainer;
	var slotSegmentContainer;
	var slotTable;
	var selectionHelper;
	
	var viewWidth;
	var viewHeight;
	var axisWidth;
	var colWidth;
	var gutterWidth;
	var slotHeight; // TODO: what if slotHeight changes? (see issue 650)

	var snapMinutes;
	var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
	var snapHeight; // holds the pixel hight of a "selection" slot
	
	var colCnt;
	var slotCnt;
	var coordinateGrid;
	var hoverListener;
	var colPositions;
	var colContentPositions;
	var slotTopCache = {};
	
	var tm;
	var rtl;
	var minMinute, maxMinute;
	var colFormat;
	var showWeekNumbers;
	var weekNumberTitle;
	var weekNumberFormat;
	

	
	/* Rendering
	-----------------------------------------------------------------------------*/
	
	
	disableTextSelection(element.addClass('fc-agenda'));
	
	
	function renderAgenda(c) {
		colCnt = c;
		updateOptions();

		if (!dayTable) { // first time rendering?
			buildSkeleton(); // builds day table, slot area, events containers
		}
		else {
			buildDayTable(); // rebuilds day table
		}
	}
	
	
	function updateOptions() {

		tm = opt('theme') ? 'ui' : 'fc';
		rtl = opt('isRTL')
		minMinute = parseTime(opt('minTime'));
		maxMinute = parseTime(opt('maxTime'));
		colFormat = opt('columnFormat');

		// week # options. (TODO: bad, logic also in other views)
		showWeekNumbers = opt('weekNumbers');
		weekNumberTitle = opt('weekNumberTitle');
		if (opt('weekNumberCalculation') != 'iso') {
			weekNumberFormat = "w";
		}
		else {
			weekNumberFormat = "W";
		}

		snapMinutes = opt('snapMinutes') || opt('slotMinutes');
	}



	/* Build DOM
	-----------------------------------------------------------------------*/


	function buildSkeleton() {
		var headerClass = tm + "-widget-header";
		var contentClass = tm + "-widget-content";
		var s;
		var d;
		var i;
		var maxd;
		var minutes;
		var slotNormal = opt('slotMinutes') % 15 == 0;
		
		buildDayTable();
		
		slotLayer =
			$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
				.appendTo(element);
				
		if (opt('allDaySlot')) {
		
			daySegmentContainer =
				$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
					.appendTo(slotLayer);
		
			s =
				"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
				"<tr>" +
				"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
				"<td>" +
				"<div class='fc-day-content'><div style='position:relative'/></div>" +
				"</td>" +
				"<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
				"</tr>" +
				"</table>";
			allDayTable = $(s).appendTo(slotLayer);
			allDayRow = allDayTable.find('tr');
			
			dayBind(allDayRow.find('td'));
			
			slotLayer.append(
				"<div class='fc-agenda-divider " + headerClass + "'>" +
				"<div class='fc-agenda-divider-inner'/>" +
				"</div>"
			);
			
		}else{
		
			daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
		
		}
		
		slotScroller =
			$("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
				.appendTo(slotLayer);
				
		slotContainer =
			$("<div style='position:relative;width:100%;overflow:hidden'/>")
				.appendTo(slotScroller);
				
		slotSegmentContainer =
			$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
				.appendTo(slotContainer);
		
		s =
			"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
			"<tbody>";
		d = zeroDate();
		maxd = addMinutes(cloneDate(d), maxMinute);
		addMinutes(d, minMinute);
		slotCnt = 0;
		for (i=0; d < maxd; i++) {
			minutes = d.getMinutes();
			s +=
				"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
				"<th class='fc-agenda-axis " + headerClass + "'>" +
				((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
				"</th>" +
				"<td class='" + contentClass + "'>" +
				"<div style='position:relative'>&nbsp;</div>" +
				"</td>" +
				"</tr>";
			addMinutes(d, opt('slotMinutes'));
			slotCnt++;
		}
		s +=
			"</tbody>" +
			"</table>";
		slotTable = $(s).appendTo(slotContainer);
		
		slotBind(slotTable.find('td'));
	}



	/* Build Day Table
	-----------------------------------------------------------------------*/


	function buildDayTable() {
		var html = buildDayTableHTML();

		if (dayTable) {
			dayTable.remove();
		}
		dayTable = $(html).appendTo(element);

		dayHead = dayTable.find('thead');
		dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter
		dayBody = dayTable.find('tbody');
		dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter
		dayBodyCellInners = dayBodyCells.find('> div');
		dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');

		dayBodyFirstCell = dayBodyCells.eq(0);
		dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
		
		markFirstLast(dayHead.add(dayHead.find('tr')));
		markFirstLast(dayBody.add(dayBody.find('tr')));

		// TODO: now that we rebuild the cells every time, we should call dayRender
	}


	function buildDayTableHTML() {
		var html =
			"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
			buildDayTableHeadHTML() +
			buildDayTableBodyHTML() +
			"</table>";

		return html;
	}


	function buildDayTableHeadHTML() {
		var headerClass = tm + "-widget-header";
		var date;
		var html = '';
		var weekText;
		var col;

		html +=
			"<thead>" +
			"<tr>";

		if (showWeekNumbers) {
			date = cellToDate(0, 0);
			weekText = formatDate(date, weekNumberFormat);
			if (rtl) {
				weekText += weekNumberTitle;
			}
			else {
				weekText = weekNumberTitle + weekText;
			}
			html +=
				"<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +
				htmlEscape(weekText) +
				"</th>";
		}
		else {
			html += "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
		}

		for (col=0; col<colCnt; col++) {
			date = cellToDate(0, col);
			html +=
				"<th class='fc-" + dayIDs[date.getDay()] + " fc-col" + col + ' ' + headerClass + "'>" +
				htmlEscape(formatDate(date, colFormat)) +
				"</th>";
		}

		html +=
			"<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
			"</tr>" +
			"</thead>";

		return html;
	}


	function buildDayTableBodyHTML() {
		var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
		var contentClass = tm + "-widget-content";
		var date;
		var today = clearTime(new Date());
		var col;
		var cellsHTML;
		var cellHTML;
		var classNames;
		var html = '';

		html +=
			"<tbody>" +
			"<tr>" +
			"<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";

		cellsHTML = '';

		for (col=0; col<colCnt; col++) {

			date = cellToDate(0, col);

			classNames = [
				'fc-col' + col,
				'fc-' + dayIDs[date.getDay()],
				contentClass
			];
			if (+date == +today) {
				classNames.push(
					tm + '-state-highlight',
					'fc-today'
				);
			}
			else if (date < today) {
				classNames.push('fc-past');
			}
			else {
				classNames.push('fc-future');
			}

			cellHTML =
				"<td class='" + classNames.join(' ') + "'>" +
				"<div>" +
				"<div class='fc-day-content'>" +
				"<div style='position:relative'>&nbsp;</div>" +
				"</div>" +
				"</div>" +
				"</td>";

			cellsHTML += cellHTML;
		}

		html += cellsHTML;
		html +=
			"<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
			"</tr>" +
			"</tbody>";

		return html;
	}


	// TODO: data-date on the cells

	
	
	/* Dimensions
	-----------------------------------------------------------------------*/

	
	function setHeight(height) {
		if (height === undefined) {
			height = viewHeight;
		}
		viewHeight = height;
		slotTopCache = {};
	
		var headHeight = dayBody.position().top;
		var allDayHeight = slotScroller.position().top; // including divider
		var bodyHeight = Math.min( // total body height, including borders
			height - headHeight,   // when scrollbars
			slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
		);

		dayBodyFirstCellStretcher
			.height(bodyHeight - vsides(dayBodyFirstCell));
		
		slotLayer.css('top', headHeight);
		
		slotScroller.height(bodyHeight - allDayHeight - 1);
		
		// the stylesheet guarantees that the first row has no border.
		// this allows .height() to work well cross-browser.
		slotHeight = slotTable.find('tr:first').height() + 1; // +1 for bottom border

		snapRatio = opt('slotMinutes') / snapMinutes;
		snapHeight = slotHeight / snapRatio;
	}
	
	
	function setWidth(width) {
		viewWidth = width;
		colPositions.clear();
		colContentPositions.clear();

		var axisFirstCells = dayHead.find('th:first');
		if (allDayTable) {
			axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
		}
		axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
		
		axisWidth = 0;
		setOuterWidth(
			axisFirstCells
				.width('')
				.each(function(i, _cell) {
					axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
				}),
			axisWidth
		);
		
		var gutterCells = dayTable.find('.fc-agenda-gutter');
		if (allDayTable) {
			gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
		}

		var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
		
		gutterWidth = slotScroller.width() - slotTableWidth;
		if (gutterWidth) {
			setOuterWidth(gutterCells, gutterWidth);
			gutterCells
				.show()
				.prev()
				.removeClass('fc-last');
		}else{
			gutterCells
				.hide()
				.prev()
				.addClass('fc-last');
		}
		
		colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
		setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
	}
	


	/* Scrolling
	-----------------------------------------------------------------------*/


	function resetScroll() {
		var d0 = zeroDate();
		var scrollDate = cloneDate(d0);
		scrollDate.setHours(opt('firstHour'));
		var top = timePosition(d0, scrollDate) + 1; // +1 for the border
		function scroll() {
			slotScroller.scrollTop(top);
		}
		scroll();
		setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
	}


	function afterRender() { // after the view has been freshly rendered and sized
		resetScroll();
	}
	
	
	
	/* Slot/Day clicking and binding
	-----------------------------------------------------------------------*/
	

	function dayBind(cells) {
		cells.click(slotClick)
			.mousedown(daySelectionMousedown);
	}


	function slotBind(cells) {
		cells.click(slotClick)
			.mousedown(slotSelectionMousedown);
	}
	
	
	function slotClick(ev) {
		if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
			var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
			var date = cellToDate(0, col);
			var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
			if (rowMatch) {
				var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
				var hours = Math.floor(mins/60);
				date.setHours(hours);
				date.setMinutes(mins%60 + minMinute);
				trigger('dayClick', dayBodyCells[col], date, false, ev);
			}else{
				trigger('dayClick', dayBodyCells[col], date, true, ev);
			}
		}
	}
	
	
	
	/* Semi-transparent Overlay Helpers
	-----------------------------------------------------*/
	// TODO: should be consolidated with BasicView's methods


	function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive

		if (refreshCoordinateGrid) {
			coordinateGrid.build();
		}

		var segments = rangeToSegments(overlayStart, overlayEnd);

		for (var i=0; i<segments.length; i++) {
			var segment = segments[i];
			dayBind(
				renderCellOverlay(
					segment.row,
					segment.leftCol,
					segment.row,
					segment.rightCol
				)
			);
		}
	}
	
	
	function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
		var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
		return renderOverlay(rect, slotLayer);
	}
	

	function renderSlotOverlay(overlayStart, overlayEnd) {
		for (var i=0; i<colCnt; i++) {
			var dayStart = cellToDate(0, i);
			var dayEnd = addDays(cloneDate(dayStart), 1);
			var stretchStart = new Date(Math.max(dayStart, overlayStart));
			var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
			if (stretchStart < stretchEnd) {
				var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // only use it for horizontal coords
				var top = timePosition(dayStart, stretchStart);
				var bottom = timePosition(dayStart, stretchEnd);
				rect.top = top;
				rect.height = bottom - top;
				slotBind(
					renderOverlay(rect, slotContainer)
				);
			}
		}
	}
	
	
	
	/* Coordinate Utilities
	-----------------------------------------------------------------------------*/
	
	
	coordinateGrid = new CoordinateGrid(function(rows, cols) {
		var e, n, p;
		dayHeadCells.each(function(i, _e) {
			e = $(_e);
			n = e.offset().left;
			if (i) {
				p[1] = n;
			}
			p = [n];
			cols[i] = p;
		});
		p[1] = n + e.outerWidth();
		if (opt('allDaySlot')) {
			e = allDayRow;
			n = e.offset().top;
			rows[0] = [n, n+e.outerHeight()];
		}
		var slotTableTop = slotContainer.offset().top;
		var slotScrollerTop = slotScroller.offset().top;
		var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
		function constrain(n) {
			return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
		}
		for (var i=0; i<slotCnt*snapRatio; i++) { // adapt slot count to increased/decreased selection slot count
			rows.push([
				constrain(slotTableTop + snapHeight*i),
				constrain(slotTableTop + snapHeight*(i+1))
			]);
		}
	});
	
	
	hoverListener = new HoverListener(coordinateGrid);
	
	colPositions = new HorizontalPositionCache(function(col) {
		return dayBodyCellInners.eq(col);
	});
	
	colContentPositions = new HorizontalPositionCache(function(col) {
		return dayBodyCellContentInners.eq(col);
	});
	
	
	function colLeft(col) {
		return colPositions.left(col);
	}


	function colContentLeft(col) {
		return colContentPositions.left(col);
	}


	function colRight(col) {
		return colPositions.right(col);
	}
	
	
	function colContentRight(col) {
		return colContentPositions.right(col);
	}


	function getIsCellAllDay(cell) {
		return opt('allDaySlot') && !cell.row;
	}


	function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system
		var d = cellToDate(0, cell.col);
		var slotIndex = cell.row;
		if (opt('allDaySlot')) {
			slotIndex--;
		}
		if (slotIndex >= 0) {
			addMinutes(d, minMinute + slotIndex * snapMinutes);
		}
		return d;
	}
	
	
	// get the Y coordinate of the given time on the given day (both Date objects)
	function timePosition(day, time) { // both date objects. day holds 00:00 of current day
		day = cloneDate(day, true);
		if (time < addMinutes(cloneDate(day), minMinute)) {
			return 0;
		}
		if (time >= addMinutes(cloneDate(day), maxMinute)) {
			return slotTable.height();
		}
		var slotMinutes = opt('slotMinutes'),
			minutes = time.getHours()*60 + time.getMinutes() - minMinute,
			slotI = Math.floor(minutes / slotMinutes),
			slotTop = slotTopCache[slotI];
		if (slotTop === undefined) {
			slotTop = slotTopCache[slotI] =
				slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop;
				// .eq() is faster than ":eq()" selector
				// [0].offsetTop is faster than .position().top (do we really need this optimization?)
				// a better optimization would be to cache all these divs
		}
		return Math.max(0, Math.round(
			slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
		));
	}
	
	
	function getAllDayRow(index) {
		return allDayRow;
	}
	
	
	function defaultEventEnd(event) {
		var start = cloneDate(event.start);
		if (event.allDay) {
			return start;
		}
		return addMinutes(start, opt('defaultEventMinutes'));
	}
	
	
	
	/* Selection
	---------------------------------------------------------------------------------*/
	
	
	function defaultSelectionEnd(startDate, allDay) {
		if (allDay) {
			return cloneDate(startDate);
		}
		return addMinutes(cloneDate(startDate), opt('slotMinutes'));
	}
	
	
	function renderSelection(startDate, endDate, allDay) { // only for all-day
		if (allDay) {
			if (opt('allDaySlot')) {
				renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
			}
		}else{
			renderSlotSelection(startDate, endDate);
		}
	}
	
	
	function renderSlotSelection(startDate, endDate) {
		var helperOption = opt('selectHelper');
		coordinateGrid.build();
		if (helperOption) {
			var col = dateToCell(startDate).col;
			if (col >= 0 && col < colCnt) { // only works when times are on same day
				var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
				var top = timePosition(startDate, startDate);
				var bottom = timePosition(startDate, endDate);
				if (bottom > top) { // protect against selections that are entirely before or after visible range
					rect.top = top;
					rect.height = bottom - top;
					rect.left += 2;
					rect.width -= 5;
					if ($.isFunction(helperOption)) {
						var helperRes = helperOption(startDate, endDate);
						if (helperRes) {
							rect.position = 'absolute';
							selectionHelper = $(helperRes)
								.css(rect)
								.appendTo(slotContainer);
						}
					}else{
						rect.isStart = true; // conside rect a "seg" now
						rect.isEnd = true;   //
						selectionHelper = $(slotSegHtml(
							{
								title: '',
								start: startDate,
								end: endDate,
								className: ['fc-select-helper'],
								editable: false
							},
							rect
						));
						selectionHelper.css('opacity', opt('dragOpacity'));
					}
					if (selectionHelper) {
						slotBind(selectionHelper);
						slotContainer.append(selectionHelper);
						setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
						setOuterHeight(selectionHelper, rect.height, true);
					}
				}
			}
		}else{
			renderSlotOverlay(startDate, endDate);
		}
	}
	
	
	function clearSelection() {
		clearOverlays();
		if (selectionHelper) {
			selectionHelper.remove();
			selectionHelper = null;
		}
	}
	
	
	function slotSelectionMousedown(ev) {
		if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
			unselect(ev);
			var dates;
			hoverListener.start(function(cell, origCell) {
				clearSelection();
				if (cell && cell.col == origCell.col && !getIsCellAllDay(cell)) {
					var d1 = realCellToDate(origCell);
					var d2 = realCellToDate(cell);
					dates = [
						d1,
						addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes 
						d2,
						addMinutes(cloneDate(d2), snapMinutes)
					].sort(dateCompare);
					renderSlotSelection(dates[0], dates[3]);
				}else{
					dates = null;
				}
			}, ev);
			$(document).one('mouseup', function(ev) {
				hoverListener.stop();
				if (dates) {
					if (+dates[0] == +dates[1]) {
						reportDayClick(dates[0], false, ev);
					}
					reportSelection(dates[0], dates[3], false, ev);
				}
			});
		}
	}


	function reportDayClick(date, allDay, ev) {
		trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev);
	}
	
	
	
	/* External Dragging
	--------------------------------------------------------------------------------*/
	
	
	function dragStart(_dragElement, ev, ui) {
		hoverListener.start(function(cell) {
			clearOverlays();
			if (cell) {
				if (getIsCellAllDay(cell)) {
					renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
				}else{
					var d1 = realCellToDate(cell);
					var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
					renderSlotOverlay(d1, d2);
				}
			}
		}, ev);
	}
	
	
	function dragStop(_dragElement, ev, ui) {
		var cell = hoverListener.stop();
		clearOverlays();
		if (cell) {
			trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui);
		}
	}
	

}

;;

function AgendaEventRenderer() {
	var t = this;
	
	
	// exports
	t.renderEvents = renderEvents;
	t.clearEvents = clearEvents;
	t.slotSegHtml = slotSegHtml;
	
	
	// imports
	DayEventRenderer.call(t);
	var opt = t.opt;
	var trigger = t.trigger;
	var isEventDraggable = t.isEventDraggable;
	var isEventResizable = t.isEventResizable;
	var eventEnd = t.eventEnd;
	var eventElementHandlers = t.eventElementHandlers;
	var setHeight = t.setHeight;
	var getDaySegmentContainer = t.getDaySegmentContainer;
	var getSlotSegmentContainer = t.getSlotSegmentContainer;
	var getHoverListener = t.getHoverListener;
	var getMaxMinute = t.getMaxMinute;
	var getMinMinute = t.getMinMinute;
	var timePosition = t.timePosition;
	var getIsCellAllDay = t.getIsCellAllDay;
	var colContentLeft = t.colContentLeft;
	var colContentRight = t.colContentRight;
	var cellToDate = t.cellToDate;
	var getColCnt = t.getColCnt;
	var getColWidth = t.getColWidth;
	var getSnapHeight = t.getSnapHeight;
	var getSnapMinutes = t.getSnapMinutes;
	var getSlotContainer = t.getSlotContainer;
	var reportEventElement = t.reportEventElement;
	var showEvents = t.showEvents;
	var hideEvents = t.hideEvents;
	var eventDrop = t.eventDrop;
	var eventResize = t.eventResize;
	var renderDayOverlay = t.renderDayOverlay;
	var clearOverlays = t.clearOverlays;
	var renderDayEvents = t.renderDayEvents;
	var calendar = t.calendar;
	var formatDate = calendar.formatDate;
	var formatDates = calendar.formatDates;


	// overrides
	t.draggableDayEvent = draggableDayEvent;

	
	
	/* Rendering
	----------------------------------------------------------------------------*/
	

	function renderEvents(events, modifiedEventId) {
		var i, len=events.length,
			dayEvents=[],
			slotEvents=[];
		for (i=0; i<len; i++) {
			if (events[i].allDay) {
				dayEvents.push(events[i]);
			}else{
				slotEvents.push(events[i]);
			}
		}

		if (opt('allDaySlot')) {
			renderDayEvents(dayEvents, modifiedEventId);
			setHeight(); // no params means set to viewHeight
		}

		renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
	}
	
	
	function clearEvents() {
		getDaySegmentContainer().empty();
		getSlotSegmentContainer().empty();
	}

	
	function compileSlotSegs(events) {
		var colCnt = getColCnt(),
			minMinute = getMinMinute(),
			maxMinute = getMaxMinute(),
			d,
			visEventEnds = $.map(events, slotEventEnd),
			i,
			j, seg,
			colSegs,
			segs = [];

		for (i=0; i<colCnt; i++) {

			d = cellToDate(0, i);
			addMinutes(d, minMinute);

			colSegs = sliceSegs(
				events,
				visEventEnds,
				d,
				addMinutes(cloneDate(d), maxMinute-minMinute)
			);

			colSegs = placeSlotSegs(colSegs); // returns a new order

			for (j=0; j<colSegs.length; j++) {
				seg = colSegs[j];
				seg.col = i;
				segs.push(seg);
			}
		}

		return segs;
	}


	function sliceSegs(events, visEventEnds, start, end) {
		var segs = [],
			i, len=events.length, event,
			eventStart, eventEnd,
			segStart, segEnd,
			isStart, isEnd;
		for (i=0; i<len; i++) {
			event = events[i];
			eventStart = event.start;
			eventEnd = visEventEnds[i];
			if (eventEnd > start && eventStart < end) {
				if (eventStart < start) {
					segStart = cloneDate(start);
					isStart = false;
				}else{
					segStart = eventStart;
					isStart = true;
				}
				if (eventEnd > end) {
					segEnd = cloneDate(end);
					isEnd = false;
				}else{
					segEnd = eventEnd;
					isEnd = true;
				}
				segs.push({
					event: event,
					start: segStart,
					end: segEnd,
					isStart: isStart,
					isEnd: isEnd
				});
			}
		}
		return segs.sort(compareSlotSegs);
	}


	function slotEventEnd(event) {
		if (event.end) {
			return cloneDate(event.end);
		}else{
			return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
		}
	}
	
	
	// renders events in the 'time slots' at the bottom
	// TODO: when we refactor this, when user returns `false` eventRender, don't have empty space
	// TODO: refactor will include using pixels to detect collisions instead of dates (handy for seg cmp)
	
	function renderSlotSegs(segs, modifiedEventId) {
	
		var i, segCnt=segs.length, seg,
			event,
			top,
			bottom,
			columnLeft,
			columnRight,
			columnWidth,
			width,
			left,
			right,
			html = '',
			eventElements,
			eventElement,
			triggerRes,
			titleElement,
			height,
			slotSegmentContainer = getSlotSegmentContainer(),
			isRTL = opt('isRTL');
			
		// calculate position/dimensions, create html
		for (i=0; i<segCnt; i++) {
			seg = segs[i];
			event = seg.event;
			top = timePosition(seg.start, seg.start);
			bottom = timePosition(seg.start, seg.end);
			columnLeft = colContentLeft(seg.col);
			columnRight = colContentRight(seg.col);
			columnWidth = columnRight - columnLeft;

			// shave off space on right near scrollbars (2.5%)
			// TODO: move this to CSS somehow
			columnRight -= columnWidth * .025;
			columnWidth = columnRight - columnLeft;

			width = columnWidth * (seg.forwardCoord - seg.backwardCoord);

			if (opt('slotEventOverlap')) {
				// double the width while making sure resize handle is visible
				// (assumed to be 20px wide)
				width = Math.max(
					(width - (20/2)) * 2,
					width // narrow columns will want to make the segment smaller than
						// the natural width. don't allow it
				);
			}

			if (isRTL) {
				right = columnRight - seg.backwardCoord * columnWidth;
				left = right - width;
			}
			else {
				left = columnLeft + seg.backwardCoord * columnWidth;
				right = left + width;
			}

			// make sure horizontal coordinates are in bounds
			left = Math.max(left, columnLeft);
			right = Math.min(right, columnRight);
			width = right - left;

			seg.top = top;
			seg.left = left;
			seg.outerWidth = width;
			seg.outerHeight = bottom - top;
			html += slotSegHtml(event, seg);
		}

		slotSegmentContainer[0].innerHTML = html; // faster than html()
		eventElements = slotSegmentContainer.children();
		
		// retrieve elements, run through eventRender callback, bind event handlers
		for (i=0; i<segCnt; i++) {
			seg = segs[i];
			event = seg.event;
			eventElement = $(eventElements[i]); // faster than eq()
			triggerRes = trigger('eventRender', event, event, eventElement);
			if (triggerRes === false) {
				eventElement.remove();
			}else{
				if (triggerRes && triggerRes !== true) {
					eventElement.remove();
					eventElement = $(triggerRes)
						.css({
							position: 'absolute',
							top: seg.top,
							left: seg.left
						})
						.appendTo(slotSegmentContainer);
				}
				seg.element = eventElement;
				if (event._id === modifiedEventId) {
					bindSlotSeg(event, eventElement, seg);
				}else{
					eventElement[0]._fci = i; // for lazySegBind
				}
				reportEventElement(event, eventElement);
			}
		}
		
		lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
		
		// record event sides and title positions
		for (i=0; i<segCnt; i++) {
			seg = segs[i];
			if (eventElement = seg.element) {
				seg.vsides = vsides(eventElement, true);
				seg.hsides = hsides(eventElement, true);
				titleElement = eventElement.find('.fc-event-title');
				if (titleElement.length) {
					seg.contentTop = titleElement[0].offsetTop;
				}
			}
		}
		
		// set all positions/dimensions at once
		for (i=0; i<segCnt; i++) {
			seg = segs[i];
			if (eventElement = seg.element) {
				eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
				height = Math.max(0, seg.outerHeight - seg.vsides);
				eventElement[0].style.height = height + 'px';
				event = seg.event;
				if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
					// not enough room for title, put it in the time (TODO: maybe make both display:inline instead)
					eventElement.find('div.fc-event-time')
						.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
					eventElement.find('div.fc-event-title')
						.remove();
				}
				trigger('eventAfterRender', event, event, eventElement);
			}
		}
					
	}
	
	
	function slotSegHtml(event, seg) {
		var html = "<";
		var url = event.url;
		var skinCss = getSkinCss(event, opt);
		var classes = ['fc-event', 'fc-event-vert'];
		if (isEventDraggable(event)) {
			classes.push('fc-event-draggable');
		}
		if (seg.isStart) {
			classes.push('fc-event-start');
		}
		if (seg.isEnd) {
			classes.push('fc-event-end');
		}
		classes = classes.concat(event.className);
		if (event.source) {
			classes = classes.concat(event.source.className || []);
		}
		if (url) {
			html += "a href='" + htmlEscape(event.url) + "'";
		}else{
			html += "div";
		}
		html +=
			" class='" + classes.join(' ') + "'" +
			" style=" +
				"'" +
				"position:absolute;" +
				"top:" + seg.top + "px;" +
				"left:" + seg.left + "px;" +
				skinCss +
				"'" +
			">" +
			"<div class='fc-event-inner'>" +
			"<div class='fc-event-time'>" +
			htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
			"</div>" +
			"<div class='fc-event-title'>" +
			htmlEscape(event.title || '') +
			"</div>" +
			"</div>" +
			"<div class='fc-event-bg'></div>";
		if (seg.isEnd && isEventResizable(event)) {
			html +=
				"<div class='ui-resizable-handle ui-resizable-s'>=</div>";
		}
		html +=
			"</" + (url ? "a" : "div") + ">";
		return html;
	}
	
	
	function bindSlotSeg(event, eventElement, seg) {
		var timeElement = eventElement.find('div.fc-event-time');
		if (isEventDraggable(event)) {
			draggableSlotEvent(event, eventElement, timeElement);
		}
		if (seg.isEnd && isEventResizable(event)) {
			resizableSlotEvent(event, eventElement, timeElement);
		}
		eventElementHandlers(event, eventElement);
	}
	
	
	
	/* Dragging
	-----------------------------------------------------------------------------------*/
	
	
	// when event starts out FULL-DAY
	// overrides DayEventRenderer's version because it needs to account for dragging elements
	// to and from the slot area.
	
	function draggableDayEvent(event, eventElement, seg) {
		var isStart = seg.isStart;
		var origWidth;
		var revert;
		var allDay = true;
		var dayDelta;
		var hoverListener = getHoverListener();
		var colWidth = getColWidth();
		var snapHeight = getSnapHeight();
		var snapMinutes = getSnapMinutes();
		var minMinute = getMinMinute();
		eventElement.draggable({
			opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
			revertDuration: opt('dragRevertDuration'),
			start: function(ev, ui) {
				trigger('eventDragStart', eventElement, event, ev, ui);
				hideEvents(event, eventElement);
				origWidth = eventElement.width();
				hoverListener.start(function(cell, origCell) {
					clearOverlays();
					if (cell) {
						revert = false;
						var origDate = cellToDate(0, origCell.col);
						var date = cellToDate(0, cell.col);
						dayDelta = dayDiff(date, origDate);
						if (!cell.row) {
							// on full-days
							renderDayOverlay(
								addDays(cloneDate(event.start), dayDelta),
								addDays(exclEndDay(event), dayDelta)
							);
							resetElement();
						}else{
							// mouse is over bottom slots
							if (isStart) {
								if (allDay) {
									// convert event to temporary slot-event
									eventElement.width(colWidth - 10); // don't use entire width
									setOuterHeight(
										eventElement,
										snapHeight * Math.round(
											(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) /
												snapMinutes
										)
									);
									eventElement.draggable('option', 'grid', [colWidth, 1]);
									allDay = false;
								}
							}else{
								revert = true;
							}
						}
						revert = revert || (allDay && !dayDelta);
					}else{
						resetElement();
						revert = true;
					}
					eventElement.draggable('option', 'revert', revert);
				}, ev, 'drag');
			},
			stop: function(ev, ui) {
				hoverListener.stop();
				clearOverlays();
				trigger('eventDragStop', eventElement, event, ev, ui);
				if (revert) {
					// hasn't moved or is out of bounds (draggable has already reverted)
					resetElement();
					eventElement.css('filter', ''); // clear IE opacity side-effects
					showEvents(event, eventElement);
				}else{
					// changed!
					var minuteDelta = 0;
					if (!allDay) {
						minuteDelta = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight)
							* snapMinutes
							+ minMinute
							- (event.start.getHours() * 60 + event.start.getMinutes());
					}
					eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
				}
			}
		});
		function resetElement() {
			if (!allDay) {
				eventElement
					.width(origWidth)
					.height('')
					.draggable('option', 'grid', null);
				allDay = true;
			}
		}
	}
	
	
	// when event starts out IN TIMESLOTS
	
	function draggableSlotEvent(event, eventElement, timeElement) {
		var coordinateGrid = t.getCoordinateGrid();
		var colCnt = getColCnt();
		var colWidth = getColWidth();
		var snapHeight = getSnapHeight();
		var snapMinutes = getSnapMinutes();

		// states
		var origPosition; // original position of the element, not the mouse
		var origCell;
		var isInBounds, prevIsInBounds;
		var isAllDay, prevIsAllDay;
		var colDelta, prevColDelta;
		var dayDelta; // derived from colDelta
		var minuteDelta, prevMinuteDelta;

		eventElement.draggable({
			scroll: false,
			grid: [ colWidth, snapHeight ],
			axis: colCnt==1 ? 'y' : false,
			opacity: opt('dragOpacity'),
			revertDuration: opt('dragRevertDuration'),
			start: function(ev, ui) {

				trigger('eventDragStart', eventElement, event, ev, ui);
				hideEvents(event, eventElement);

				coordinateGrid.build();

				// initialize states
				origPosition = eventElement.position();
				origCell = coordinateGrid.cell(ev.pageX, ev.pageY);
				isInBounds = prevIsInBounds = true;
				isAllDay = prevIsAllDay = getIsCellAllDay(origCell);
				colDelta = prevColDelta = 0;
				dayDelta = 0;
				minuteDelta = prevMinuteDelta = 0;

			},
			drag: function(ev, ui) {

				// NOTE: this `cell` value is only useful for determining in-bounds and all-day.
				// Bad for anything else due to the discrepancy between the mouse position and the
				// element position while snapping. (problem revealed in PR #55)
				//
				// PS- the problem exists for draggableDayEvent() when dragging an all-day event to a slot event.
				// We should overhaul the dragging system and stop relying on jQuery UI.
				var cell = coordinateGrid.cell(ev.pageX, ev.pageY);

				// update states
				isInBounds = !!cell;
				if (isInBounds) {
					isAllDay = getIsCellAllDay(cell);

					// calculate column delta
					colDelta = Math.round((ui.position.left - origPosition.left) / colWidth);
					if (colDelta != prevColDelta) {
						// calculate the day delta based off of the original clicked column and the column delta
						var origDate = cellToDate(0, origCell.col);
						var col = origCell.col + colDelta;
						col = Math.max(0, col);
						col = Math.min(colCnt-1, col);
						var date = cellToDate(0, col);
						dayDelta = dayDiff(date, origDate);
					}

					// calculate minute delta (only if over slots)
					if (!isAllDay) {
						minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes;
					}
				}

				// any state changes?
				if (
					isInBounds != prevIsInBounds ||
					isAllDay != prevIsAllDay ||
					colDelta != prevColDelta ||
					minuteDelta != prevMinuteDelta
				) {

					updateUI();

					// update previous states for next time
					prevIsInBounds = isInBounds;
					prevIsAllDay = isAllDay;
					prevColDelta = colDelta;
					prevMinuteDelta = minuteDelta;
				}

				// if out-of-bounds, revert when done, and vice versa.
				eventElement.draggable('option', 'revert', !isInBounds);

			},
			stop: function(ev, ui) {

				clearOverlays();
				trigger('eventDragStop', eventElement, event, ev, ui);

				if (isInBounds && (isAllDay || dayDelta || minuteDelta)) { // changed!
					eventDrop(this, event, dayDelta, isAllDay ? 0 : minuteDelta, isAllDay, ev, ui);
				}
				else { // either no change or out-of-bounds (draggable has already reverted)

					// reset states for next time, and for updateUI()
					isInBounds = true;
					isAllDay = false;
					colDelta = 0;
					dayDelta = 0;
					minuteDelta = 0;

					updateUI();
					eventElement.css('filter', ''); // clear IE opacity side-effects

					// sometimes fast drags make event revert to wrong position, so reset.
					// also, if we dragged the element out of the area because of snapping,
					// but the *mouse* is still in bounds, we need to reset the position.
					eventElement.css(origPosition);

					showEvents(event, eventElement);
				}
			}
		});

		function updateUI() {
			clearOverlays();
			if (isInBounds) {
				if (isAllDay) {
					timeElement.hide();
					eventElement.draggable('option', 'grid', null); // disable grid snapping
					renderDayOverlay(
						addDays(cloneDate(event.start), dayDelta),
						addDays(exclEndDay(event), dayDelta)
					);
				}
				else {
					updateTimeText(minuteDelta);
					timeElement.css('display', ''); // show() was causing display=inline
					eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping
				}
			}
		}

		function updateTimeText(minuteDelta) {
			var newStart = addMinutes(cloneDate(event.start), minuteDelta);
			var newEnd;
			if (event.end) {
				newEnd = addMinutes(cloneDate(event.end), minuteDelta);
			}
			timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
		}

	}
	
	
	
	/* Resizing
	--------------------------------------------------------------------------------------*/
	
	
	function resizableSlotEvent(event, eventElement, timeElement) {
		var snapDelta, prevSnapDelta;
		var snapHeight = getSnapHeight();
		var snapMinutes = getSnapMinutes();
		eventElement.resizable({
			handles: {
				s: '.ui-resizable-handle'
			},
			grid: snapHeight,
			start: function(ev, ui) {
				snapDelta = prevSnapDelta = 0;
				hideEvents(event, eventElement);
				trigger('eventResizeStart', this, event, ev, ui);
			},
			resize: function(ev, ui) {
				// don't rely on ui.size.height, doesn't take grid into account
				snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight);
				if (snapDelta != prevSnapDelta) {
					timeElement.text(
						formatDates(
							event.start,
							(!snapDelta && !event.end) ? null : // no change, so don't display time range
								addMinutes(eventEnd(event), snapMinutes*snapDelta),
							opt('timeFormat')
						)
					);
					prevSnapDelta = snapDelta;
				}
			},
			stop: function(ev, ui) {
				trigger('eventResizeStop', this, event, ev, ui);
				if (snapDelta) {
					eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui);
				}else{
					showEvents(event, eventElement);
					// BUG: if event was really short, need to put title back in span
				}
			}
		});
	}
	

}



/* Agenda Event Segment Utilities
-----------------------------------------------------------------------------*/


// Sets the seg.backwardCoord and seg.forwardCoord on each segment and returns a new
// list in the order they should be placed into the DOM (an implicit z-index).
function placeSlotSegs(segs) {
	var levels = buildSlotSegLevels(segs);
	var level0 = levels[0];
	var i;

	computeForwardSlotSegs(levels);

	if (level0) {

		for (i=0; i<level0.length; i++) {
			computeSlotSegPressures(level0[i]);
		}

		for (i=0; i<level0.length; i++) {
			computeSlotSegCoords(level0[i], 0, 0);
		}
	}

	return flattenSlotSegLevels(levels);
}


// Builds an array of segments "levels". The first level will be the leftmost tier of segments
// if the calendar is left-to-right, or the rightmost if the calendar is right-to-left.
function buildSlotSegLevels(segs) {
	var levels = [];
	var i, seg;
	var j;

	for (i=0; i<segs.length; i++) {
		seg = segs[i];

		// go through all the levels and stop on the first level where there are no collisions
		for (j=0; j<levels.length; j++) {
			if (!computeSlotSegCollisions(seg, levels[j]).length) {
				break;
			}
		}

		(levels[j] || (levels[j] = [])).push(seg);
	}

	return levels;
}


// For every segment, figure out the other segments that are in subsequent
// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
function computeForwardSlotSegs(levels) {
	var i, level;
	var j, seg;
	var k;

	for (i=0; i<levels.length; i++) {
		level = levels[i];

		for (j=0; j<level.length; j++) {
			seg = level[j];

			seg.forwardSegs = [];
			for (k=i+1; k<levels.length; k++) {
				computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
			}
		}
	}
}


// Figure out which path forward (via seg.forwardSegs) results in the longest path until
// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
function computeSlotSegPressures(seg) {
	var forwardSegs = seg.forwardSegs;
	var forwardPressure = 0;
	var i, forwardSeg;

	if (seg.forwardPressure === undefined) { // not already computed

		for (i=0; i<forwardSegs.length; i++) {
			forwardSeg = forwardSegs[i];

			// figure out the child's maximum forward path
			computeSlotSegPressures(forwardSeg);

			// either use the existing maximum, or use the child's forward pressure
			// plus one (for the forwardSeg itself)
			forwardPressure = Math.max(
				forwardPressure,
				1 + forwardSeg.forwardPressure
			);
		}

		seg.forwardPressure = forwardPressure;
	}
}


// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
//
// The segment might be part of a "series", which means consecutive segments with the same pressure
// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
// coordinate of the first segment in the series.
function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {
	var forwardSegs = seg.forwardSegs;
	var i;

	if (seg.forwardCoord === undefined) { // not already computed

		if (!forwardSegs.length) {

			// if there are no forward segments, this segment should butt up against the edge
			seg.forwardCoord = 1;
		}
		else {

			// sort highest pressure first
			forwardSegs.sort(compareForwardSlotSegs);

			// this segment's forwardCoord will be calculated from the backwardCoord of the
			// highest-pressure forward segment.
			computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
			seg.forwardCoord = forwardSegs[0].backwardCoord;
		}

		// calculate the backwardCoord from the forwardCoord. consider the series
		seg.backwardCoord = seg.forwardCoord -
			(seg.forwardCoord - seriesBackwardCoord) / // available width for series
			(seriesBackwardPressure + 1); // # of segments in the series

		// use this segment's coordinates to computed the coordinates of the less-pressurized
		// forward segments
		for (i=0; i<forwardSegs.length; i++) {
			computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
		}
	}
}


// Outputs a flat array of segments, from lowest to highest level
function flattenSlotSegLevels(levels) {
	var segs = [];
	var i, level;
	var j;

	for (i=0; i<levels.length; i++) {
		level = levels[i];

		for (j=0; j<level.length; j++) {
			segs.push(level[j]);
		}
	}

	return segs;
}


// Find all the segments in `otherSegs` that vertically collide with `seg`.
// Append into an optionally-supplied `results` array and return.
function computeSlotSegCollisions(seg, otherSegs, results) {
	results = results || [];

	for (var i=0; i<otherSegs.length; i++) {
		if (isSlotSegCollision(seg, otherSegs[i])) {
			results.push(otherSegs[i]);
		}
	}

	return results;
}


// Do these segments occupy the same vertical space?
function isSlotSegCollision(seg1, seg2) {
	return seg1.end > seg2.start && seg1.start < seg2.end;
}


// A cmp function for determining which forward segment to rely on more when computing coordinates.
function compareForwardSlotSegs(seg1, seg2) {
	// put higher-pressure first
	return seg2.forwardPressure - seg1.forwardPressure ||
		// put segments that are closer to initial edge first (and favor ones with no coords yet)
		(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
		// do normal sorting...
		compareSlotSegs(seg1, seg2);
}


// A cmp function for determining which segment should be closer to the initial edge
// (the left edge on a left-to-right calendar).
function compareSlotSegs(seg1, seg2) {
	return seg1.start - seg2.start || // earlier start time goes first
		(seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first
		(seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
}


;;


function View(element, calendar, viewName) {
	var t = this;
	
	
	// exports
	t.element = element;
	t.calendar = calendar;
	t.name = viewName;
	t.opt = opt;
	t.trigger = trigger;
	t.isEventDraggable = isEventDraggable;
	t.isEventResizable = isEventResizable;
	t.setEventData = setEventData;
	t.clearEventData = clearEventData;
	t.eventEnd = eventEnd;
	t.reportEventElement = reportEventElement;
	t.triggerEventDestroy = triggerEventDestroy;
	t.eventElementHandlers = eventElementHandlers;
	t.showEvents = showEvents;
	t.hideEvents = hideEvents;
	t.eventDrop = eventDrop;
	t.eventResize = eventResize;
	// t.title
	// t.start, t.end
	// t.visStart, t.visEnd
	
	
	// imports
	var defaultEventEnd = t.defaultEventEnd;
	var normalizeEvent = calendar.normalizeEvent; // in EventManager
	var reportEventChange = calendar.reportEventChange;
	
	
	// locals
	var eventsByID = {}; // eventID mapped to array of events (there can be multiple b/c of repeating events)
	var eventElementsByID = {}; // eventID mapped to array of jQuery elements
	var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system
	var options = calendar.options;
	
	
	
	function opt(name, viewNameOverride) {
		var v = options[name];
		if ($.isPlainObject(v)) {
			return smartProperty(v, viewNameOverride || viewName);
		}
		return v;
	}

	
	function trigger(name, thisObj) {
		return calendar.trigger.apply(
			calendar,
			[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
		);
	}
	


	/* Event Editable Boolean Calculations
	------------------------------------------------------------------------------*/

	
	function isEventDraggable(event) {
		var source = event.source || {};
		return firstDefined(
				event.startEditable,
				source.startEditable,
				opt('eventStartEditable'),
				event.editable,
				source.editable,
				opt('editable')
			)
			&& !opt('disableDragging'); // deprecated
	}
	
	
	function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
		var source = event.source || {};
		return firstDefined(
				event.durationEditable,
				source.durationEditable,
				opt('eventDurationEditable'),
				event.editable,
				source.editable,
				opt('editable')
			)
			&& !opt('disableResizing'); // deprecated
	}
	
	
	
	/* Event Data
	------------------------------------------------------------------------------*/
	
	
	function setEventData(events) { // events are already normalized at this point
		eventsByID = {};
		var i, len=events.length, event;
		for (i=0; i<len; i++) {
			event = events[i];
			if (eventsByID[event._id]) {
				eventsByID[event._id].push(event);
			}else{
				eventsByID[event._id] = [event];
			}
		}
	}


	function clearEventData() {
		eventsByID = {};
		eventElementsByID = {};
		eventElementCouples = [];
	}
	
	
	// returns a Date object for an event's end
	function eventEnd(event) {
		return event.end ? cloneDate(event.end) : defaultEventEnd(event);
	}
	
	
	
	/* Event Elements
	------------------------------------------------------------------------------*/
	
	
	// report when view creates an element for an event
	function reportEventElement(event, element) {
		eventElementCouples.push({ event: event, element: element });
		if (eventElementsByID[event._id]) {
			eventElementsByID[event._id].push(element);
		}else{
			eventElementsByID[event._id] = [element];
		}
	}


	function triggerEventDestroy() {
		$.each(eventElementCouples, function(i, couple) {
			t.trigger('eventDestroy', couple.event, couple.event, couple.element);
		});
	}
	
	
	// attaches eventClick, eventMouseover, eventMouseout
	function eventElementHandlers(event, eventElement) {
		eventElement
			.click(function(ev) {
				if (!eventElement.hasClass('ui-draggable-dragging') &&
					!eventElement.hasClass('ui-resizable-resizing')) {
						return trigger('eventClick', this, event, ev);
					}
			})
			.hover(
				function(ev) {
					trigger('eventMouseover', this, event, ev);
				},
				function(ev) {
					trigger('eventMouseout', this, event, ev);
				}
			);
		// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
		// TODO: same for resizing
	}
	
	
	function showEvents(event, exceptElement) {
		eachEventElement(event, exceptElement, 'show');
	}
	
	
	function hideEvents(event, exceptElement) {
		eachEventElement(event, exceptElement, 'hide');
	}
	
	
	function eachEventElement(event, exceptElement, funcName) {
		// NOTE: there may be multiple events per ID (repeating events)
		// and multiple segments per event
		var elements = eventElementsByID[event._id],
			i, len = elements.length;
		for (i=0; i<len; i++) {
			if (!exceptElement || elements[i][0] != exceptElement[0]) {
				elements[i][funcName]();
			}
		}
	}
	
	
	
	/* Event Modification Reporting
	---------------------------------------------------------------------------------*/
	
	
	function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
		var oldAllDay = event.allDay;
		var eventId = event._id;
		moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
		trigger(
			'eventDrop',
			e,
			event,
			dayDelta,
			minuteDelta,
			allDay,
			function() {
				// TODO: investigate cases where this inverse technique might not work
				moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
				reportEventChange(eventId);
			},
			ev,
			ui
		);
		reportEventChange(eventId);
	}
	
	
	function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
		var eventId = event._id;
		elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
		trigger(
			'eventResize',
			e,
			event,
			dayDelta,
			minuteDelta,
			function() {
				// TODO: investigate cases where this inverse technique might not work
				elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
				reportEventChange(eventId);
			},
			ev,
			ui
		);
		reportEventChange(eventId);
	}
	
	
	
	/* Event Modification Math
	---------------------------------------------------------------------------------*/
	
	
	function moveEvents(events, dayDelta, minuteDelta, allDay) {
		minuteDelta = minuteDelta || 0;
		for (var e, len=events.length, i=0; i<len; i++) {
			e = events[i];
			if (allDay !== undefined) {
				e.allDay = allDay;
			}
			addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
			if (e.end) {
				e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
			}
			normalizeEvent(e, options);
		}
	}
	
	
	function elongateEvents(events, dayDelta, minuteDelta) {
		minuteDelta = minuteDelta || 0;
		for (var e, len=events.length, i=0; i<len; i++) {
			e = events[i];
			e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
			normalizeEvent(e, options);
		}
	}



	// ====================================================================================================
	// Utilities for day "cells"
	// ====================================================================================================
	// The "basic" views are completely made up of day cells.
	// The "agenda" views have day cells at the top "all day" slot.
	// This was the obvious common place to put these utilities, but they should be abstracted out into
	// a more meaningful class (like DayEventRenderer).
	// ====================================================================================================


	// For determining how a given "cell" translates into a "date":
	//
	// 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).
	//    Keep in mind that column indices are inverted with isRTL. This is taken into account.
	//
	// 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).
	//
	// 3. Convert the "day offset" into a "date" (a JavaScript Date object).
	//
	// The reverse transformation happens when transforming a date into a cell.


	// exports
	t.isHiddenDay = isHiddenDay;
	t.skipHiddenDays = skipHiddenDays;
	t.getCellsPerWeek = getCellsPerWeek;
	t.dateToCell = dateToCell;
	t.dateToDayOffset = dateToDayOffset;
	t.dayOffsetToCellOffset = dayOffsetToCellOffset;
	t.cellOffsetToCell = cellOffsetToCell;
	t.cellToDate = cellToDate;
	t.cellToCellOffset = cellToCellOffset;
	t.cellOffsetToDayOffset = cellOffsetToDayOffset;
	t.dayOffsetToDate = dayOffsetToDate;
	t.rangeToSegments = rangeToSegments;


	// internals
	var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden
	var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
	var cellsPerWeek;
	var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week
	var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week
	var isRTL = opt('isRTL');


	// initialize important internal variables
	(function() {

		if (opt('weekends') === false) {
			hiddenDays.push(0, 6); // 0=sunday, 6=saturday
		}

		// Loop through a hypothetical week and determine which
		// days-of-week are hidden. Record in both hashes (one is the reverse of the other).
		for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {
			dayToCellMap[dayIndex] = cellIndex;
			isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;
			if (!isHiddenDayHash[dayIndex]) {
				cellToDayMap[cellIndex] = dayIndex;
				cellIndex++;
			}
		}

		cellsPerWeek = cellIndex;
		if (!cellsPerWeek) {
			throw 'invalid hiddenDays'; // all days were hidden? bad.
		}

	})();


	// Is the current day hidden?
	// `day` is a day-of-week index (0-6), or a Date object
	function isHiddenDay(day) {
		if (typeof day == 'object') {
			day = day.getDay();
		}
		return isHiddenDayHash[day];
	}


	function getCellsPerWeek() {
		return cellsPerWeek;
	}


	// Keep incrementing the current day until it is no longer a hidden day.
	// If the initial value of `date` is not a hidden day, don't do anything.
	// Pass `isExclusive` as `true` if you are dealing with an end date.
	// `inc` defaults to `1` (increment one day forward each time)
	function skipHiddenDays(date, inc, isExclusive) {
		inc = inc || 1;
		while (
			isHiddenDayHash[ ( date.getDay() + (isExclusive ? inc : 0) + 7 ) % 7 ]
		) {
			addDays(date, inc);
		}
	}


	//
	// TRANSFORMATIONS: cell -> cell offset -> day offset -> date
	//

	// cell -> date (combines all transformations)
	// Possible arguments:
	// - row, col
	// - { row:#, col: # }
	function cellToDate() {
		var cellOffset = cellToCellOffset.apply(null, arguments);
		var dayOffset = cellOffsetToDayOffset(cellOffset);
		var date = dayOffsetToDate(dayOffset);
		return date;
	}

	// cell -> cell offset
	// Possible arguments:
	// - row, col
	// - { row:#, col:# }
	function cellToCellOffset(row, col) {
		var colCnt = t.getColCnt();

		// rtl variables. wish we could pre-populate these. but where?
		var dis = isRTL ? -1 : 1;
		var dit = isRTL ? colCnt - 1 : 0;

		if (typeof row == 'object') {
			col = row.col;
			row = row.row;
		}
		var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)

		return cellOffset;
	}

	// cell offset -> day offset
	function cellOffsetToDayOffset(cellOffset) {
		var day0 = t.visStart.getDay(); // first date's day of week
		cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week
		return Math.floor(cellOffset / cellsPerWeek) * 7 // # of days from full weeks
			+ cellToDayMap[ // # of days from partial last week
				(cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets
			]
			- day0; // adjustment for beginning-of-week normalization
	}

	// day offset -> date (JavaScript Date object)
	function dayOffsetToDate(dayOffset) {
		var date = cloneDate(t.visStart);
		addDays(date, dayOffset);
		return date;
	}


	//
	// TRANSFORMATIONS: date -> day offset -> cell offset -> cell
	//

	// date -> cell (combines all transformations)
	function dateToCell(date) {
		var dayOffset = dateToDayOffset(date);
		var cellOffset = dayOffsetToCellOffset(dayOffset);
		var cell = cellOffsetToCell(cellOffset);
		return cell;
	}

	// date -> day offset
	function dateToDayOffset(date) {
		return dayDiff(date, t.visStart);
	}

	// day offset -> cell offset
	function dayOffsetToCellOffset(dayOffset) {
		var day0 = t.visStart.getDay(); // first date's day of week
		dayOffset += day0; // normalize dayOffset to beginning-of-week
		return Math.floor(dayOffset / 7) * cellsPerWeek // # of cells from full weeks
			+ dayToCellMap[ // # of cells from partial last week
				(dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets
			]
			- dayToCellMap[day0]; // adjustment for beginning-of-week normalization
	}

	// cell offset -> cell (object with row & col keys)
	function cellOffsetToCell(cellOffset) {
		var colCnt = t.getColCnt();

		// rtl variables. wish we could pre-populate these. but where?
		var dis = isRTL ? -1 : 1;
		var dit = isRTL ? colCnt - 1 : 0;

		var row = Math.floor(cellOffset / colCnt);
		var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)
		return {
			row: row,
			col: col
		};
	}


	//
	// Converts a date range into an array of segment objects.
	// "Segments" are horizontal stretches of time, sliced up by row.
	// A segment object has the following properties:
	// - row
	// - cols
	// - isStart
	// - isEnd
	//
	function rangeToSegments(startDate, endDate) {
		var rowCnt = t.getRowCnt();
		var colCnt = t.getColCnt();
		var segments = []; // array of segments to return

		// day offset for given date range
		var rangeDayOffsetStart = dateToDayOffset(startDate);
		var rangeDayOffsetEnd = dateToDayOffset(endDate); // exclusive

		// first and last cell offset for the given date range
		// "last" implies inclusivity
		var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);
		var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;

		// loop through all the rows in the view
		for (var row=0; row<rowCnt; row++) {

			// first and last cell offset for the row
			var rowCellOffsetFirst = row * colCnt;
			var rowCellOffsetLast = rowCellOffsetFirst + colCnt - 1;

			// get the segment's cell offsets by constraining the range's cell offsets to the bounds of the row
			var segmentCellOffsetFirst = Math.max(rangeCellOffsetFirst, rowCellOffsetFirst);
			var segmentCellOffsetLast = Math.min(rangeCellOffsetLast, rowCellOffsetLast);

			// make sure segment's offsets are valid and in view
			if (segmentCellOffsetFirst <= segmentCellOffsetLast) {

				// translate to cells
				var segmentCellFirst = cellOffsetToCell(segmentCellOffsetFirst);
				var segmentCellLast = cellOffsetToCell(segmentCellOffsetLast);

				// view might be RTL, so order by leftmost column
				var cols = [ segmentCellFirst.col, segmentCellLast.col ].sort();

				// Determine if segment's first/last cell is the beginning/end of the date range.
				// We need to compare "day offset" because "cell offsets" are often ambiguous and
				// can translate to multiple days, and an edge case reveals itself when we the
				// range's first cell is hidden (we don't want isStart to be true).
				var isStart = cellOffsetToDayOffset(segmentCellOffsetFirst) == rangeDayOffsetStart;
				var isEnd = cellOffsetToDayOffset(segmentCellOffsetLast) + 1 == rangeDayOffsetEnd; // +1 for comparing exclusively

				segments.push({
					row: row,
					leftCol: cols[0],
					rightCol: cols[1],
					isStart: isStart,
					isEnd: isEnd
				});
			}
		}

		return segments;
	}
	

}

;;

function DayEventRenderer() {
	var t = this;

	
	// exports
	t.renderDayEvents = renderDayEvents;
	t.draggableDayEvent = draggableDayEvent; // made public so that subclasses can override
	t.resizableDayEvent = resizableDayEvent; // "
	
	
	// imports
	var opt = t.opt;
	var trigger = t.trigger;
	var isEventDraggable = t.isEventDraggable;
	var isEventResizable = t.isEventResizable;
	var eventEnd = t.eventEnd;
	var reportEventElement = t.reportEventElement;
	var eventElementHandlers = t.eventElementHandlers;
	var showEvents = t.showEvents;
	var hideEvents = t.hideEvents;
	var eventDrop = t.eventDrop;
	var eventResize = t.eventResize;
	var getRowCnt = t.getRowCnt;
	var getColCnt = t.getColCnt;
	var getColWidth = t.getColWidth;
	var allDayRow = t.allDayRow; // TODO: rename
	var colLeft = t.colLeft;
	var colRight = t.colRight;
	var colContentLeft = t.colContentLeft;
	var colContentRight = t.colContentRight;
	var dateToCell = t.dateToCell;
	var getDaySegmentContainer = t.getDaySegmentContainer;
	var formatDates = t.calendar.formatDates;
	var renderDayOverlay = t.renderDayOverlay;
	var clearOverlays = t.clearOverlays;
	var clearSelection = t.clearSelection;
	var getHoverListener = t.getHoverListener;
	var rangeToSegments = t.rangeToSegments;
	var cellToDate = t.cellToDate;
	var cellToCellOffset = t.cellToCellOffset;
	var cellOffsetToDayOffset = t.cellOffsetToDayOffset;
	var dateToDayOffset = t.dateToDayOffset;
	var dayOffsetToCellOffset = t.dayOffsetToCellOffset;


	// Render `events` onto the calendar, attach mouse event handlers, and call the `eventAfterRender` callback for each.
	// Mouse event will be lazily applied, except if the event has an ID of `modifiedEventId`.
	// Can only be called when the event container is empty (because it wipes out all innerHTML).
	function renderDayEvents(events, modifiedEventId) {

		// do the actual rendering. Receive the intermediate "segment" data structures.
		var segments = _renderDayEvents(
			events,
			false, // don't append event elements
			true // set the heights of the rows
		);

		// report the elements to the View, for general drag/resize utilities
		segmentElementEach(segments, function(segment, element) {
			reportEventElement(segment.event, element);
		});

		// attach mouse handlers
		attachHandlers(segments, modifiedEventId);

		// call `eventAfterRender` callback for each event
		segmentElementEach(segments, function(segment, element) {
			trigger('eventAfterRender', segment.event, segment.event, element);
		});
	}


	// Render an event on the calendar, but don't report them anywhere, and don't attach mouse handlers.
	// Append this event element to the event container, which might already be populated with events.
	// If an event's segment will have row equal to `adjustRow`, then explicitly set its top coordinate to `adjustTop`.
	// This hack is used to maintain continuity when user is manually resizing an event.
	// Returns an array of DOM elements for the event.
	function renderTempDayEvent(event, adjustRow, adjustTop) {

		// actually render the event. `true` for appending element to container.
		// Recieve the intermediate "segment" data structures.
		var segments = _renderDayEvents(
			[ event ],
			true, // append event elements
			false // don't set the heights of the rows
		);

		var elements = [];

		// Adjust certain elements' top coordinates
		segmentElementEach(segments, function(segment, element) {
			if (segment.row === adjustRow) {
				element.css('top', adjustTop);
			}
			elements.push(element[0]); // accumulate DOM nodes
		});

		return elements;
	}


	// Render events onto the calendar. Only responsible for the VISUAL aspect.
	// Not responsible for attaching handlers or calling callbacks.
	// Set `doAppend` to `true` for rendering elements without clearing the existing container.
	// Set `doRowHeights` to allow setting the height of each row, to compensate for vertical event overflow.
	function _renderDayEvents(events, doAppend, doRowHeights) {

		// where the DOM nodes will eventually end up
		var finalContainer = getDaySegmentContainer();

		// the container where the initial HTML will be rendered.
		// If `doAppend`==true, uses a temporary container.
		var renderContainer = doAppend ? $("<div/>") : finalContainer;

		var segments = buildSegments(events);
		var html;
		var elements;

		// calculate the desired `left` and `width` properties on each segment object
		calculateHorizontals(segments);

		// build the HTML string. relies on `left` property
		html = buildHTML(segments);

		// render the HTML. innerHTML is considerably faster than jQuery's .html()
		renderContainer[0].innerHTML = html;

		// retrieve the individual elements
		elements = renderContainer.children();

		// if we were appending, and thus using a temporary container,
		// re-attach elements to the real container.
		if (doAppend) {
			finalContainer.append(elements);
		}

		// assigns each element to `segment.event`, after filtering them through user callbacks
		resolveElements(segments, elements);

		// Calculate the left and right padding+margin for each element.
		// We need this for setting each element's desired outer width, because of the W3C box model.
		// It's important we do this in a separate pass from acually setting the width on the DOM elements
		// because alternating reading/writing dimensions causes reflow for every iteration.
		segmentElementEach(segments, function(segment, element) {
			segment.hsides = hsides(element, true); // include margins = `true`
		});

		// Set the width of each element
		segmentElementEach(segments, function(segment, element) {
			element.width(
				Math.max(0, segment.outerWidth - segment.hsides)
			);
		});

		// Grab each element's outerHeight (setVerticals uses this).
		// To get an accurate reading, it's important to have each element's width explicitly set already.
		segmentElementEach(segments, function(segment, element) {
			segment.outerHeight = element.outerHeight(true); // include margins = `true`
		});

		// Set the top coordinate on each element (requires segment.outerHeight)
		setVerticals(segments, doRowHeights);

		return segments;
	}


	// Generate an array of "segments" for all events.
	function buildSegments(events) {
		var segments = [];
		for (var i=0; i<events.length; i++) {
			var eventSegments = buildSegmentsForEvent(events[i]);
			segments.push.apply(segments, eventSegments); // append an array to an array
		}
		return segments;
	}


	// Generate an array of segments for a single event.
	// A "segment" is the same data structure that View.rangeToSegments produces,
	// with the addition of the `event` property being set to reference the original event.
	function buildSegmentsForEvent(event) {
		var startDate = event.start;
		var endDate = exclEndDay(event);
		var segments = rangeToSegments(startDate, endDate);
		for (var i=0; i<segments.length; i++) {
			segments[i].event = event;
		}
		return segments;
	}


	// Sets the `left` and `outerWidth` property of each segment.
	// These values are the desired dimensions for the eventual DOM elements.
	function calculateHorizontals(segments) {
		var isRTL = opt('isRTL');
		for (var i=0; i<segments.length; i++) {
			var segment = segments[i];

			// Determine functions used for calulating the elements left/right coordinates,
			// depending on whether the view is RTL or not.
			// NOTE:
			// colLeft/colRight returns the coordinate butting up the edge of the cell.
			// colContentLeft/colContentRight is indented a little bit from the edge.
			var leftFunc = (isRTL ? segment.isEnd : segment.isStart) ? colContentLeft : colLeft;
			var rightFunc = (isRTL ? segment.isStart : segment.isEnd) ? colContentRight : colRight;

			var left = leftFunc(segment.leftCol);
			var right = rightFunc(segment.rightCol);
			segment.left = left;
			segment.outerWidth = right - left;
		}
	}


	// Build a concatenated HTML string for an array of segments
	function buildHTML(segments) {
		var html = '';
		for (var i=0; i<segments.length; i++) {
			html += buildHTMLForSegment(segments[i]);
		}
		return html;
	}


	// Build an HTML string for a single segment.
	// Relies on the following properties:
	// - `segment.event` (from `buildSegmentsForEvent`)
	// - `segment.left` (from `calculateHorizontals`)
	function buildHTMLForSegment(segment) {
		var html = '';
		var isRTL = opt('isRTL');
		var event = segment.event;
		var url = event.url;

		// generate the list of CSS classNames
		var classNames = [ 'fc-event', 'fc-event-hori' ];
		if (isEventDraggable(event)) {
			classNames.push('fc-event-draggable');
		}
		if (segment.isStart) {
			classNames.push('fc-event-start');
		}
		if (segment.isEnd) {
			classNames.push('fc-event-end');
		}
		// use the event's configured classNames
		// guaranteed to be an array via `normalizeEvent`
		classNames = classNames.concat(event.className);
		if (event.source) {
			// use the event's source's classNames, if specified
			classNames = classNames.concat(event.source.className || []);
		}

		// generate a semicolon delimited CSS string for any of the "skin" properties
		// of the event object (`backgroundColor`, `borderColor` and such)
		var skinCss = getSkinCss(event, opt);

		if (url) {
			html += "<a href='" + htmlEscape(url) + "'";
		}else{
			html += "<div";
		}
		html +=
			" class='" + classNames.join(' ') + "'" +
			" style=" +
				"'" +
				"position:absolute;" +
				"left:" + segment.left + "px;" +
				skinCss +
				"'" +
			">" +
			"<div class='fc-event-inner'>";
		if (!event.allDay && segment.isStart) {
			html +=
				"<span class='fc-event-time'>" +
				htmlEscape(
					formatDates(event.start, event.end, opt('timeFormat'))
				) +
				"</span>";
		}
		html +=
			"<span class='fc-event-title'>" +
			htmlEscape(event.title || '') +
			"</span>" +
			"</div>";
		if (segment.isEnd && isEventResizable(event)) {
			html +=
				"<div class='ui-resizable-handle ui-resizable-" + (isRTL ? 'w' : 'e') + "'>" +
				"&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
				"</div>";
		}
		html += "</" + (url ? "a" : "div") + ">";

		// TODO:
		// When these elements are initially rendered, they will be briefly visibile on the screen,
		// even though their widths/heights are not set.
		// SOLUTION: initially set them as visibility:hidden ?

		return html;
	}


	// Associate each segment (an object) with an element (a jQuery object),
	// by setting each `segment.element`.
	// Run each element through the `eventRender` filter, which allows developers to
	// modify an existing element, supply a new one, or cancel rendering.
	function resolveElements(segments, elements) {
		for (var i=0; i<segments.length; i++) {
			var segment = segments[i];
			var event = segment.event;
			var element = elements.eq(i);

			// call the trigger with the original element
			var triggerRes = trigger('eventRender', event, event, element);

			if (triggerRes === false) {
				// if `false`, remove the event from the DOM and don't assign it to `segment.event`
				element.remove();
			}
			else {
				if (triggerRes && triggerRes !== true) {
					// the trigger returned a new element, but not `true` (which means keep the existing element)

					// re-assign the important CSS dimension properties that were already assigned in `buildHTMLForSegment`
					triggerRes = $(triggerRes)
						.css({
							position: 'absolute',
							left: segment.left
						});

					element.replaceWith(triggerRes);
					element = triggerRes;
				}

				segment.element = element;
			}
		}
	}



	/* Top-coordinate Methods
	-------------------------------------------------------------------------------------------------*/


	// Sets the "top" CSS property for each element.
	// If `doRowHeights` is `true`, also sets each row's first cell to an explicit height,
	// so that if elements vertically overflow, the cell expands vertically to compensate.
	function setVerticals(segments, doRowHeights) {
		var rowContentHeights = calculateVerticals(segments); // also sets segment.top
		var rowContentElements = getRowContentElements(); // returns 1 inner div per row
		var rowContentTops = [];

		// Set each row's height by setting height of first inner div
		if (doRowHeights) {
			for (var i=0; i<rowContentElements.length; i++) {
				rowContentElements[i].height(rowContentHeights[i]);
			}
		}

		// Get each row's top, relative to the views's origin.
		// Important to do this after setting each row's height.
		for (var i=0; i<rowContentElements.length; i++) {
			rowContentTops.push(
				rowContentElements[i].position().top
			);
		}

		// Set each segment element's CSS "top" property.
		// Each segment object has a "top" property, which is relative to the row's top, but...
		segmentElementEach(segments, function(segment, element) {
			element.css(
				'top',
				rowContentTops[segment.row] + segment.top // ...now, relative to views's origin
			);
		});
	}


	// Calculate the "top" coordinate for each segment, relative to the "top" of the row.
	// Also, return an array that contains the "content" height for each row
	// (the height displaced by the vertically stacked events in the row).
	// Requires segments to have their `outerHeight` property already set.
	function calculateVerticals(segments) {
		var rowCnt = getRowCnt();
		var colCnt = getColCnt();
		var rowContentHeights = []; // content height for each row
		var segmentRows = buildSegmentRows(segments); // an array of segment arrays, one for each row

		for (var rowI=0; rowI<rowCnt; rowI++) {
			var segmentRow = segmentRows[rowI];

			// an array of running total heights for each column.
			// initialize with all zeros.
			var colHeights = [];
			for (var colI=0; colI<colCnt; colI++) {
				colHeights.push(0);
			}

			// loop through every segment
			for (var segmentI=0; segmentI<segmentRow.length; segmentI++) {
				var segment = segmentRow[segmentI];

				// find the segment's top coordinate by looking at the max height
				// of all the columns the segment will be in.
				segment.top = arrayMax(
					colHeights.slice(
						segment.leftCol,
						segment.rightCol + 1 // make exclusive for slice
					)
				);

				// adjust the columns to account for the segment's height
				for (var colI=segment.leftCol; colI<=segment.rightCol; colI++) {
					colHeights[colI] = segment.top + segment.outerHeight;
				}
			}

			// the tallest column in the row should be the "content height"
			rowContentHeights.push(arrayMax(colHeights));
		}

		return rowContentHeights;
	}


	// Build an array of segment arrays, each representing the segments that will
	// be in a row of the grid, sorted by which event should be closest to the top.
	function buildSegmentRows(segments) {
		var rowCnt = getRowCnt();
		var segmentRows = [];
		var segmentI;
		var segment;
		var rowI;

		// group segments by row
		for (segmentI=0; segmentI<segments.length; segmentI++) {
			segment = segments[segmentI];
			rowI = segment.row;
			if (segment.element) { // was rendered?
				if (segmentRows[rowI]) {
					// already other segments. append to array
					segmentRows[rowI].push(segment);
				}
				else {
					// first segment in row. create new array
					segmentRows[rowI] = [ segment ];
				}
			}
		}

		// sort each row
		for (rowI=0; rowI<rowCnt; rowI++) {
			segmentRows[rowI] = sortSegmentRow(
				segmentRows[rowI] || [] // guarantee an array, even if no segments
			);
		}

		return segmentRows;
	}


	// Sort an array of segments according to which segment should appear closest to the top
	function sortSegmentRow(segments) {
		var sortedSegments = [];

		// build the subrow array
		var subrows = buildSegmentSubrows(segments);

		// flatten it
		for (var i=0; i<subrows.length; i++) {
			sortedSegments.push.apply(sortedSegments, subrows[i]); // append an array to an array
		}

		return sortedSegments;
	}


	// Take an array of segments, which are all assumed to be in the same row,
	// and sort into subrows.
	function buildSegmentSubrows(segments) {

		// Give preference to elements with certain criteria, so they have
		// a chance to be closer to the top.
		segments.sort(compareDaySegments);

		var subrows = [];
		for (var i=0; i<segments.length; i++) {
			var segment = segments[i];

			// loop through subrows, starting with the topmost, until the segment
			// doesn't collide with other segments.
			for (var j=0; j<subrows.length; j++) {
				if (!isDaySegmentCollision(segment, subrows[j])) {
					break;
				}
			}
			// `j` now holds the desired subrow index
			if (subrows[j]) {
				subrows[j].push(segment);
			}
			else {
				subrows[j] = [ segment ];
			}
		}

		return subrows;
	}


	// Return an array of jQuery objects for the placeholder content containers of each row.
	// The content containers don't actually contain anything, but their dimensions should match
	// the events that are overlaid on top.
	function getRowContentElements() {
		var i;
		var rowCnt = getRowCnt();
		var rowDivs = [];
		for (i=0; i<rowCnt; i++) {
			rowDivs[i] = allDayRow(i)
				.find('div.fc-day-content > div');
		}
		return rowDivs;
	}



	/* Mouse Handlers
	---------------------------------------------------------------------------------------------------*/
	// TODO: better documentation!


	function attachHandlers(segments, modifiedEventId) {
		var segmentContainer = getDaySegmentContainer();

		segmentElementEach(segments, function(segment, element, i) {
			var event = segment.event;
			if (event._id === modifiedEventId) {
				bindDaySeg(event, element, segment);
			}else{
				element[0]._fci = i; // for lazySegBind
			}
		});

		lazySegBind(segmentContainer, segments, bindDaySeg);
	}


	function bindDaySeg(event, eventElement, segment) {

		if (isEventDraggable(event)) {
			t.draggableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
		}

		if (
			segment.isEnd && // only allow resizing on the final segment for an event
			isEventResizable(event)
		) {
			t.resizableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
		}

		// attach all other handlers.
		// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
		eventElementHandlers(event, eventElement);
	}

	
	function draggableDayEvent(event, eventElement) {
		var hoverListener = getHoverListener();
		var dayDelta;
		eventElement.draggable({
			delay: 50,
			opacity: opt('dragOpacity'),
			revertDuration: opt('dragRevertDuration'),
			start: function(ev, ui) {
				trigger('eventDragStart', eventElement, event, ev, ui);
				hideEvents(event, eventElement);
				hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
					eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
					clearOverlays();
					if (cell) {
						var origDate = cellToDate(origCell);
						var date = cellToDate(cell);
						dayDelta = dayDiff(date, origDate);
						renderDayOverlay(
							addDays(cloneDate(event.start), dayDelta),
							addDays(exclEndDay(event), dayDelta)
						);
					}else{
						dayDelta = 0;
					}
				}, ev, 'drag');
			},
			stop: function(ev, ui) {
				hoverListener.stop();
				clearOverlays();
				trigger('eventDragStop', eventElement, event, ev, ui);
				if (dayDelta) {
					eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
				}else{
					eventElement.css('filter', ''); // clear IE opacity side-effects
					showEvents(event, eventElement);
				}
			}
		});
	}

	
	function resizableDayEvent(event, element, segment) {
		var isRTL = opt('isRTL');
		var direction = isRTL ? 'w' : 'e';
		var handle = element.find('.ui-resizable-' + direction); // TODO: stop using this class because we aren't using jqui for this
		var isResizing = false;
		
		// TODO: look into using jquery-ui mouse widget for this stuff
		disableTextSelection(element); // prevent native <a> selection for IE
		element
			.mousedown(function(ev) { // prevent native <a> selection for others
				ev.preventDefault();
			})
			.click(function(ev) {
				if (isResizing) {
					ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
					ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
					                               // (eventElementHandlers needs to be bound after resizableDayEvent)
				}
			});
		
		handle.mousedown(function(ev) {
			if (ev.which != 1) {
				return; // needs to be left mouse button
			}
			isResizing = true;
			var hoverListener = getHoverListener();
			var rowCnt = getRowCnt();
			var colCnt = getColCnt();
			var elementTop = element.css('top');
			var dayDelta;
			var helpers;
			var eventCopy = $.extend({}, event);
			var minCellOffset = dayOffsetToCellOffset( dateToDayOffset(event.start) );
			clearSelection();
			$('body')
				.css('cursor', direction + '-resize')
				.one('mouseup', mouseup);
			trigger('eventResizeStart', this, event, ev);
			hoverListener.start(function(cell, origCell) {
				if (cell) {

					var origCellOffset = cellToCellOffset(origCell);
					var cellOffset = cellToCellOffset(cell);

					// don't let resizing move earlier than start date cell
					cellOffset = Math.max(cellOffset, minCellOffset);

					dayDelta =
						cellOffsetToDayOffset(cellOffset) -
						cellOffsetToDayOffset(origCellOffset);

					if (dayDelta) {
						eventCopy.end = addDays(eventEnd(event), dayDelta, true);
						var oldHelpers = helpers;

						helpers = renderTempDayEvent(eventCopy, segment.row, elementTop);
						helpers = $(helpers); // turn array into a jQuery object

						helpers.find('*').css('cursor', direction + '-resize');
						if (oldHelpers) {
							oldHelpers.remove();
						}

						hideEvents(event);
					}
					else {
						if (helpers) {
							showEvents(event);
							helpers.remove();
							helpers = null;
						}
					}
					clearOverlays();
					renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start()
						event.start,
						addDays( exclEndDay(event), dayDelta )
						// TODO: instead of calling renderDayOverlay() with dates,
						// call _renderDayOverlay (or whatever) with cell offsets.
					);
				}
			}, ev);
			
			function mouseup(ev) {
				trigger('eventResizeStop', this, event, ev);
				$('body').css('cursor', '');
				hoverListener.stop();
				clearOverlays();
				if (dayDelta) {
					eventResize(this, event, dayDelta, 0, ev);
					// event redraw will clear helpers
				}
				// otherwise, the drag handler already restored the old events
				
				setTimeout(function() { // make this happen after the element's click event
					isResizing = false;
				},0);
			}
		});
	}
	

}



/* Generalized Segment Utilities
-------------------------------------------------------------------------------------------------*/


function isDaySegmentCollision(segment, otherSegments) {
	for (var i=0; i<otherSegments.length; i++) {
		var otherSegment = otherSegments[i];
		if (
			otherSegment.leftCol <= segment.rightCol &&
			otherSegment.rightCol >= segment.leftCol
		) {
			return true;
		}
	}
	return false;
}


function segmentElementEach(segments, callback) { // TODO: use in AgendaView?
	for (var i=0; i<segments.length; i++) {
		var segment = segments[i];
		var element = segment.element;
		if (element) {
			callback(segment, element, i);
		}
	}
}


// A cmp function for determining which segments should appear higher up
function compareDaySegments(a, b) {
	return (b.rightCol - b.leftCol) - (a.rightCol - a.leftCol) || // put wider events first
		b.event.allDay - a.event.allDay || // if tie, put all-day events first (booleans cast to 0/1)
		a.event.start - b.event.start || // if a tie, sort by event start date
		(a.event.title || '').localeCompare(b.event.title) // if a tie, sort by event title
}


;;

//BUG: unselect needs to be triggered when events are dragged+dropped

function SelectionManager() {
	var t = this;
	
	
	// exports
	t.select = select;
	t.unselect = unselect;
	t.reportSelection = reportSelection;
	t.daySelectionMousedown = daySelectionMousedown;
	
	
	// imports
	var opt = t.opt;
	var trigger = t.trigger;
	var defaultSelectionEnd = t.defaultSelectionEnd;
	var renderSelection = t.renderSelection;
	var clearSelection = t.clearSelection;
	
	
	// locals
	var selected = false;



	// unselectAuto
	if (opt('selectable') && opt('unselectAuto')) {
		$(document).mousedown(function(ev) {
			var ignore = opt('unselectCancel');
			if (ignore) {
				if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
					return;
				}
			}
			unselect(ev);
		});
	}
	

	function select(startDate, endDate, allDay) {
		unselect();
		if (!endDate) {
			endDate = defaultSelectionEnd(startDate, allDay);
		}
		renderSelection(startDate, endDate, allDay);
		reportSelection(startDate, endDate, allDay);
	}
	
	
	function unselect(ev) {
		if (selected) {
			selected = false;
			clearSelection();
			trigger('unselect', null, ev);
		}
	}
	
	
	function reportSelection(startDate, endDate, allDay, ev) {
		selected = true;
		trigger('select', null, startDate, endDate, allDay, ev);
	}
	
	
	function daySelectionMousedown(ev) { // not really a generic manager method, oh well
		var cellToDate = t.cellToDate;
		var getIsCellAllDay = t.getIsCellAllDay;
		var hoverListener = t.getHoverListener();
		var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
		if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
			unselect(ev);
			var _mousedownElement = this;
			var dates;
			hoverListener.start(function(cell, origCell) { // TODO: maybe put cellToDate/getIsCellAllDay info in cell
				clearSelection();
				if (cell && getIsCellAllDay(cell)) {
					dates = [ cellToDate(origCell), cellToDate(cell) ].sort(dateCompare);
					renderSelection(dates[0], dates[1], true);
				}else{
					dates = null;
				}
			}, ev);
			$(document).one('mouseup', function(ev) {
				hoverListener.stop();
				if (dates) {
					if (+dates[0] == +dates[1]) {
						reportDayClick(dates[0], true, ev);
					}
					reportSelection(dates[0], dates[1], true, ev);
				}
			});
		}
	}


}

;;
 
function OverlayManager() {
	var t = this;
	
	
	// exports
	t.renderOverlay = renderOverlay;
	t.clearOverlays = clearOverlays;
	
	
	// locals
	var usedOverlays = [];
	var unusedOverlays = [];
	
	
	function renderOverlay(rect, parent) {
		var e = unusedOverlays.shift();
		if (!e) {
			e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
		}
		if (e[0].parentNode != parent[0]) {
			e.appendTo(parent);
		}
		usedOverlays.push(e.css(rect).show());
		return e;
	}
	

	function clearOverlays() {
		var e;
		while (e = usedOverlays.shift()) {
			unusedOverlays.push(e.hide().unbind());
		}
	}


}

;;

function CoordinateGrid(buildFunc) {

	var t = this;
	var rows;
	var cols;
	
	
	t.build = function() {
		rows = [];
		cols = [];
		buildFunc(rows, cols);
	};
	
	
	t.cell = function(x, y) {
		var rowCnt = rows.length;
		var colCnt = cols.length;
		var i, r=-1, c=-1;
		for (i=0; i<rowCnt; i++) {
			if (y >= rows[i][0] && y < rows[i][1]) {
				r = i;
				break;
			}
		}
		for (i=0; i<colCnt; i++) {
			if (x >= cols[i][0] && x < cols[i][1]) {
				c = i;
				break;
			}
		}
		return (r>=0 && c>=0) ? { row:r, col:c } : null;
	};
	
	
	t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
		var origin = originElement.offset();
		return {
			top: rows[row0][0] - origin.top,
			left: cols[col0][0] - origin.left,
			width: cols[col1][1] - cols[col0][0],
			height: rows[row1][1] - rows[row0][0]
		};
	};

}

;;

function HoverListener(coordinateGrid) {


	var t = this;
	var bindType;
	var change;
	var firstCell;
	var cell;
	
	
	t.start = function(_change, ev, _bindType) {
		change = _change;
		firstCell = cell = null;
		coordinateGrid.build();
		mouse(ev);
		bindType = _bindType || 'mousemove';
		$(document).bind(bindType, mouse);
	};
	
	
	function mouse(ev) {
		_fixUIEvent(ev); // see below
		var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
		if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
			if (newCell) {
				if (!firstCell) {
					firstCell = newCell;
				}
				change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
			}else{
				change(newCell, firstCell);
			}
			cell = newCell;
		}
	}
	
	
	t.stop = function() {
		$(document).unbind(bindType, mouse);
		return cell;
	};
	
	
}



// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
// but keep this in here for 1.8.16 users
// and maybe remove it down the line

function _fixUIEvent(event) { // for issue 1168
	if (event.pageX === undefined) {
		event.pageX = event.originalEvent.pageX;
		event.pageY = event.originalEvent.pageY;
	}
}
;;

function HorizontalPositionCache(getElement) {

	var t = this,
		elements = {},
		lefts = {},
		rights = {};
		
	function e(i) {
		return elements[i] = elements[i] || getElement(i);
	}
	
	t.left = function(i) {
		return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
	};
	
	t.right = function(i) {
		return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
	};
	
	t.clear = function() {
		elements = {};
		lefts = {};
		rights = {};
	};
	
}

;;

})(jQuery);
//}}}
!jquery.ui.js
//{{{
!jquery.ui.core.js
/*!
 * jQuery UI Core 1.10.3
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/category/ui-core/
 */
(function( $, undefined ) {

var uuid = 0,
	runiqueId = /^ui-id-\d+$/;

// $.ui might exist from components with no dependencies, e.g., $.ui.position
$.ui = $.ui || {};

$.extend( $.ui, {
	version: "1.10.3",

	keyCode: {
		BACKSPACE: 8,
		COMMA: 188,
		DELETE: 46,
		DOWN: 40,
		END: 35,
		ENTER: 13,
		ESCAPE: 27,
		HOME: 36,
		LEFT: 37,
		NUMPAD_ADD: 107,
		NUMPAD_DECIMAL: 110,
		NUMPAD_DIVIDE: 111,
		NUMPAD_ENTER: 108,
		NUMPAD_MULTIPLY: 106,
		NUMPAD_SUBTRACT: 109,
		PAGE_DOWN: 34,
		PAGE_UP: 33,
		PERIOD: 190,
		RIGHT: 39,
		SPACE: 32,
		TAB: 9,
		UP: 38
	}
});

// plugins
$.fn.extend({
	focus: (function( orig ) {
		return function( delay, fn ) {
			return typeof delay === "number" ?
				this.each(function() {
					var elem = this;
					setTimeout(function() {
						$( elem ).focus();
						if ( fn ) {
							fn.call( elem );
						}
					}, delay );
				}) :
				orig.apply( this, arguments );
		};
	})( $.fn.focus ),

	scrollParent: function() {
		var scrollParent;
		if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
			scrollParent = this.parents().filter(function() {
				return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
			}).eq(0);
		} else {
			scrollParent = this.parents().filter(function() {
				return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
			}).eq(0);
		}

		return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
	},

	zIndex: function( zIndex ) {
		if ( zIndex !== undefined ) {
			return this.css( "zIndex", zIndex );
		}

		if ( this.length ) {
			var elem = $( this[ 0 ] ), position, value;
			while ( elem.length && elem[ 0 ] !== document ) {
				// Ignore z-index if position is set to a value where z-index is ignored by the browser
				// This makes behavior of this function consistent across browsers
				// WebKit always returns auto if the element is positioned
				position = elem.css( "position" );
				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
					// IE returns 0 when zIndex is not specified
					// other browsers return a string
					// we ignore the case of nested elements with an explicit value of 0
					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
					value = parseInt( elem.css( "zIndex" ), 10 );
					if ( !isNaN( value ) && value !== 0 ) {
						return value;
					}
				}
				elem = elem.parent();
			}
		}

		return 0;
	},

	uniqueId: function() {
		return this.each(function() {
			if ( !this.id ) {
				this.id = "ui-id-" + (++uuid);
			}
		});
	},

	removeUniqueId: function() {
		return this.each(function() {
			if ( runiqueId.test( this.id ) ) {
				$( this ).removeAttr( "id" );
			}
		});
	}
});

// selectors
function focusable( element, isTabIndexNotNaN ) {
	var map, mapName, img,
		nodeName = element.nodeName.toLowerCase();
	if ( "area" === nodeName ) {
		map = element.parentNode;
		mapName = map.name;
		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
			return false;
		}
		img = $( "img[usemap=#" + mapName + "]" )[0];
		return !!img && visible( img );
	}
	return ( /input|select|textarea|button|object/.test( nodeName ) ?
		!element.disabled :
		"a" === nodeName ?
			element.href || isTabIndexNotNaN :
			isTabIndexNotNaN) &&
		// the element and all of its ancestors must be visible
		visible( element );
}

function visible( element ) {
	return $.expr.filters.visible( element ) &&
		!$( element ).parents().addBack().filter(function() {
			return $.css( this, "visibility" ) === "hidden";
		}).length;
}

$.extend( $.expr[ ":" ], {
	data: $.expr.createPseudo ?
		$.expr.createPseudo(function( dataName ) {
			return function( elem ) {
				return !!$.data( elem, dataName );
			};
		}) :
		// support: jQuery <1.8
		function( elem, i, match ) {
			return !!$.data( elem, match[ 3 ] );
		},

	focusable: function( element ) {
		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
	},

	tabbable: function( element ) {
		var tabIndex = $.attr( element, "tabindex" ),
			isTabIndexNaN = isNaN( tabIndex );
		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
	}
});

// support: jQuery <1.8
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
	$.each( [ "Width", "Height" ], function( i, name ) {
		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
			type = name.toLowerCase(),
			orig = {
				innerWidth: $.fn.innerWidth,
				innerHeight: $.fn.innerHeight,
				outerWidth: $.fn.outerWidth,
				outerHeight: $.fn.outerHeight
			};

		function reduce( elem, size, border, margin ) {
			$.each( side, function() {
				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
				if ( border ) {
					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
				}
				if ( margin ) {
					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
				}
			});
			return size;
		}

		$.fn[ "inner" + name ] = function( size ) {
			if ( size === undefined ) {
				return orig[ "inner" + name ].call( this );
			}

			return this.each(function() {
				$( this ).css( type, reduce( this, size ) + "px" );
			});
		};

		$.fn[ "outer" + name] = function( size, margin ) {
			if ( typeof size !== "number" ) {
				return orig[ "outer" + name ].call( this, size );
			}

			return this.each(function() {
				$( this).css( type, reduce( this, size, true, margin ) + "px" );
			});
		};
	});
}

// support: jQuery <1.8
if ( !$.fn.addBack ) {
	$.fn.addBack = function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	};
}

// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
	$.fn.removeData = (function( removeData ) {
		return function( key ) {
			if ( arguments.length ) {
				return removeData.call( this, $.camelCase( key ) );
			} else {
				return removeData.call( this );
			}
		};
	})( $.fn.removeData );
}





// deprecated
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );

$.support.selectstart = "onselectstart" in document.createElement( "div" );
$.fn.extend({
	disableSelection: function() {
		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
			".ui-disableSelection", function( event ) {
				event.preventDefault();
			});
	},

	enableSelection: function() {
		return this.unbind( ".ui-disableSelection" );
	}
});

$.extend( $.ui, {
	// $.ui.plugin is deprecated. Use $.widget() extensions instead.
	plugin: {
		add: function( module, option, set ) {
			var i,
				proto = $.ui[ module ].prototype;
			for ( i in set ) {
				proto.plugins[ i ] = proto.plugins[ i ] || [];
				proto.plugins[ i ].push( [ option, set[ i ] ] );
			}
		},
		call: function( instance, name, args ) {
			var i,
				set = instance.plugins[ name ];
			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
				return;
			}

			for ( i = 0; i < set.length; i++ ) {
				if ( instance.options[ set[ i ][ 0 ] ] ) {
					set[ i ][ 1 ].apply( instance.element, args );
				}
			}
		}
	},

	// only used by resizable
	hasScroll: function( el, a ) {

		//If overflow is hidden, the element might have extra content, but the user wants to hide it
		if ( $( el ).css( "overflow" ) === "hidden") {
			return false;
		}

		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
			has = false;

		if ( el[ scroll ] > 0 ) {
			return true;
		}

		// TODO: determine which cases actually cause this to happen
		// if the element doesn't have the scroll set, see if it's possible to
		// set the scroll
		el[ scroll ] = 1;
		has = ( el[ scroll ] > 0 );
		el[ scroll ] = 0;
		return has;
	}
});

})( jQuery );
!jquery.ui.widget.js
/*!
 * jQuery UI Widget 1.10.3
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/jQuery.widget/
 */
(function( $, undefined ) {

var uuid = 0,
	slice = Array.prototype.slice,
	_cleanData = $.cleanData;
$.cleanData = function( elems ) {
	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
		try {
			$( elem ).triggerHandler( "remove" );
		// http://bugs.jquery.com/ticket/8235
		} catch( e ) {}
	}
	_cleanData( elems );
};

$.widget = function( name, base, prototype ) {
	var fullName, existingConstructor, constructor, basePrototype,
		// proxiedPrototype allows the provided prototype to remain unmodified
		// so that it can be used as a mixin for multiple widgets (#8876)
		proxiedPrototype = {},
		namespace = name.split( "." )[ 0 ];

	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
		return !!$.data( elem, fullName );
	};

	$[ namespace ] = $[ namespace ] || {};
	existingConstructor = $[ namespace ][ name ];
	constructor = $[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new constructor( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};
	// extend with the existing constructor to carry over any static properties
	$.extend( constructor, existingConstructor, {
		version: prototype.version,
		// copy the object used to create the prototype in case we need to
		// redefine the widget later
		_proto: $.extend( {}, prototype ),
		// track widgets that inherit from this widget in case this widget is
		// redefined after a widget inherits from it
		_childConstructors: []
	});

	basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( !$.isFunction( value ) ) {
			proxiedPrototype[ prop ] = value;
			return;
		}
		proxiedPrototype[ prop ] = (function() {
			var _super = function() {
					return base.prototype[ prop ].apply( this, arguments );
				},
				_superApply = function( args ) {
					return base.prototype[ prop ].apply( this, args );
				};
			return function() {
				var __super = this._super,
					__superApply = this._superApply,
					returnValue;

				this._super = _super;
				this._superApply = _superApply;

				returnValue = value.apply( this, arguments );

				this._super = __super;
				this._superApply = __superApply;

				return returnValue;
			};
		})();
	});
	constructor.prototype = $.widget.extend( basePrototype, {
		// TODO: remove support for widgetEventPrefix
		// always use the name + a colon as the prefix, e.g., draggable:start
		// don't prefix for widgets that aren't DOM-based
		widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
	}, proxiedPrototype, {
		constructor: constructor,
		namespace: namespace,
		widgetName: name,
		widgetFullName: fullName
	});

	// If this widget is being redefined then we need to find all widgets that
	// are inheriting from it and redefine all of them so that they inherit from
	// the new version of this widget. We're essentially trying to replace one
	// level in the prototype chain.
	if ( existingConstructor ) {
		$.each( existingConstructor._childConstructors, function( i, child ) {
			var childPrototype = child.prototype;

			// redefine the child widget using the same prototype that was
			// originally used, but inherit from the new version of the base
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
		});
		// remove the list of existing child constructors from the old constructor
		// so the old child constructors can be garbage collected
		delete existingConstructor._childConstructors;
	} else {
		base._childConstructors.push( constructor );
	}

	$.widget.bridge( name, constructor );
};

$.widget.extend = function( target ) {
	var input = slice.call( arguments, 1 ),
		inputIndex = 0,
		inputLength = input.length,
		key,
		value;
	for ( ; inputIndex < inputLength; inputIndex++ ) {
		for ( key in input[ inputIndex ] ) {
			value = input[ inputIndex ][ key ];
			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
				// Clone objects
				if ( $.isPlainObject( value ) ) {
					target[ key ] = $.isPlainObject( target[ key ] ) ?
						$.widget.extend( {}, target[ key ], value ) :
						// Don't extend strings, arrays, etc. with objects
						$.widget.extend( {}, value );
				// Copy everything else by reference
				} else {
					target[ key ] = value;
				}
			}
		}
	}
	return target;
};

$.widget.bridge = function( name, object ) {
	var fullName = object.prototype.widgetFullName || name;
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.widget.extend.apply( null, [ options ].concat(args) ) :
			options;

		if ( isMethodCall ) {
			this.each(function() {
				var methodValue,
					instance = $.data( this, fullName );
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue && methodValue.jquery ?
						returnValue.pushStack( methodValue.get() ) :
						methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, fullName );
				if ( instance ) {
					instance.option( options || {} )._init();
				} else {
					$.data( this, fullName, new object( options, this ) );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false,

		// callbacks
		create: null
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.uuid = uuid++;
		this.eventNamespace = "." + this.widgetName + this.uuid;
		this.options = $.widget.extend( {},
			this.options,
			this._getCreateOptions(),
			options );

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			$.data( element, this.widgetFullName, this );
			this._on( true, this.element, {
				remove: function( event ) {
					if ( event.target === element ) {
						this.destroy();
					}
				}
			});
			this.document = $( element.style ?
				// element within the document
				element.ownerDocument :
				// element is window or document
				element.document || element );
			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
		}

		this._create();
		this._trigger( "create", null, this._getCreateEventData() );
		this._init();
	},
	_getCreateOptions: $.noop,
	_getCreateEventData: $.noop,
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._on()
		this.element
			.unbind( this.eventNamespace )
			// 1.9 BC for #7810
			// TODO remove dual storage
			.removeData( this.widgetName )
			.removeData( this.widgetFullName )
			// support: jquery <1.6.3
			// http://bugs.jquery.com/ticket/9413
			.removeData( $.camelCase( this.widgetFullName ) );
		this.widget()
			.unbind( this.eventNamespace )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetFullName + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( this.eventNamespace );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key,
			parts,
			curOption,
			i;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.widget.extend( {}, this.options );
		}

		if ( typeof key === "string" ) {
			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
			options = {};
			parts = key.split( "." );
			key = parts.shift();
			if ( parts.length ) {
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
				for ( i = 0; i < parts.length - 1; i++ ) {
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
					curOption = curOption[ parts[ i ] ];
				}
				key = parts.pop();
				if ( value === undefined ) {
					return curOption[ key ] === undefined ? null : curOption[ key ];
				}
				curOption[ key ] = value;
			} else {
				if ( value === undefined ) {
					return this.options[ key ] === undefined ? null : this.options[ key ];
				}
				options[ key ] = value;
			}
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var key;

		for ( key in options ) {
			this._setOption( key, options[ key ] );
		}

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
			this.hoverable.removeClass( "ui-state-hover" );
			this.focusable.removeClass( "ui-state-focus" );
		}

		return this;
	},

	enable: function() {
		return this._setOption( "disabled", false );
	},
	disable: function() {
		return this._setOption( "disabled", true );
	},

	_on: function( suppressDisabledCheck, element, handlers ) {
		var delegateElement,
			instance = this;

		// no suppressDisabledCheck flag, shuffle arguments
		if ( typeof suppressDisabledCheck !== "boolean" ) {
			handlers = element;
			element = suppressDisabledCheck;
			suppressDisabledCheck = false;
		}

		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
			delegateElement = this.widget();
		} else {
			// accept selectors, DOM elements
			element = delegateElement = $( element );
			this.bindings = this.bindings.add( element );
		}

		$.each( handlers, function( event, handler ) {
			function handlerProxy() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( !suppressDisabledCheck &&
						( instance.options.disabled === true ||
							$( this ).hasClass( "ui-state-disabled" ) ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}

			// copy the guid so direct unbinding works
			if ( typeof handler !== "string" ) {
				handlerProxy.guid = handler.guid =
					handler.guid || handlerProxy.guid || $.guid++;
			}

			var match = event.match( /^(\w+)\s*(.*)$/ ),
				eventName = match[1] + instance.eventNamespace,
				selector = match[2];
			if ( selector ) {
				delegateElement.delegate( selector, eventName, handlerProxy );
			} else {
				element.bind( eventName, handlerProxy );
			}
		});
	},

	_off: function( element, eventName ) {
		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
		element.unbind( eventName ).undelegate( eventName );
	},

	_delay: function( handler, delay ) {
		function handlerProxy() {
			return ( typeof handler === "string" ? instance[ handler ] : handler )
				.apply( instance, arguments );
		}
		var instance = this;
		return setTimeout( handlerProxy, delay || 0 );
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._on( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._on( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var prop, orig,
			callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		// the original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );
		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
			event.isDefaultPrevented() );
	}
};

$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
		if ( typeof options === "string" ) {
			options = { effect: options };
		}
		var hasOptions,
			effectName = !options ?
				method :
				options === true || typeof options === "number" ?
					defaultEffect :
					options.effect || defaultEffect;
		options = options || {};
		if ( typeof options === "number" ) {
			options = { duration: options };
		}
		hasOptions = !$.isEmptyObject( options );
		options.complete = callback;
		if ( options.delay ) {
			element.delay( options.delay );
		}
		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
			element[ method ]( options );
		} else if ( effectName !== method && element[ effectName ] ) {
			element[ effectName ]( options.duration, options.easing, callback );
		} else {
			element.queue(function( next ) {
				$( this )[ method ]();
				if ( callback ) {
					callback.call( element[ 0 ] );
				}
				next();
			});
		}
	};
});

})( jQuery );
!jquery.ui.mouse.js
/*!
 * jQuery UI Mouse 1.10.3
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/mouse/
 *
 * Depends:
 *	jquery.ui.widget.js
 */
(function( $, undefined ) {

var mouseHandled = false;
$( document ).mouseup( function() {
	mouseHandled = false;
});

$.widget("ui.mouse", {
	version: "1.10.3",
	options: {
		cancel: "input,textarea,button,select,option",
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.bind("mousedown."+this.widgetName, function(event) {
				return that._mouseDown(event);
			})
			.bind("click."+this.widgetName, function(event) {
				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
					$.removeData(event.target, that.widgetName + ".preventClickEvent");
					event.stopImmediatePropagation();
					return false;
				}
			});

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.unbind("."+this.widgetName);
		if ( this._mouseMoveDelegate ) {
			$(document)
				.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
				.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
		}
	},

	_mouseDown: function(event) {
		// don't let more than one widget handle mouseStart
		if( mouseHandled ) { return; }

		// we may have missed mouseup (out of window)
		(this._mouseStarted && this._mouseUp(event));

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = (event.which === 1),
			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if (!this.mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				that.mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted = (this._mouseStart(event) !== false);
			if (!this._mouseStarted) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
			$.removeData(event.target, this.widgetName + ".preventClickEvent");
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(event) {
			return that._mouseMove(event);
		};
		this._mouseUpDelegate = function(event) {
			return that._mouseUp(event);
		};
		$(document)
			.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
			.bind("mouseup."+this.widgetName, this._mouseUpDelegate);

		event.preventDefault();

		mouseHandled = true;
		return true;
	},

	_mouseMove: function(event) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
			return this._mouseUp(event);
		}

		if (this._mouseStarted) {
			this._mouseDrag(event);
			return event.preventDefault();
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted =
				(this._mouseStart(this._mouseDownEvent, event) !== false);
			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
		}

		return !this._mouseStarted;
	},

	_mouseUp: function(event) {
		$(document)
			.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
			.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);

		if (this._mouseStarted) {
			this._mouseStarted = false;

			if (event.target === this._mouseDownEvent.target) {
				$.data(event.target, this.widgetName + ".preventClickEvent", true);
			}

			this._mouseStop(event);
		}

		return false;
	},

	_mouseDistanceMet: function(event) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - event.pageX),
				Math.abs(this._mouseDownEvent.pageY - event.pageY)
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function(/* event */) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function(/* event */) {},
	_mouseDrag: function(/* event */) {},
	_mouseStop: function(/* event */) {},
	_mouseCapture: function(/* event */) { return true; }
});

})(jQuery);
!jquery.ui.draggable.js
/*!
 * jQuery UI Draggable 1.10.3
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/draggable/
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */
(function( $, undefined ) {

$.widget("ui.draggable", $.ui.mouse, {
	version: "1.10.3",
	widgetEventPrefix: "drag",
	options: {
		addClasses: true,
		appendTo: "parent",
		axis: false,
		connectToSortable: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		grid: false,
		handle: false,
		helper: "original",
		iframeFix: false,
		opacity: false,
		refreshPositions: false,
		revert: false,
		revertDuration: 500,
		scope: "default",
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		snap: false,
		snapMode: "both",
		snapTolerance: 20,
		stack: false,
		zIndex: false,

		// callbacks
		drag: null,
		start: null,
		stop: null
	},
	_create: function() {

		if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
			this.element[0].style.position = "relative";
		}
		if (this.options.addClasses){
			this.element.addClass("ui-draggable");
		}
		if (this.options.disabled){
			this.element.addClass("ui-draggable-disabled");
		}

		this._mouseInit();

	},

	_destroy: function() {
		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
		this._mouseDestroy();
	},

	_mouseCapture: function(event) {

		var o = this.options;

		// among others, prevent a drag on a resizable-handle
		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
			return false;
		}

		//Quit if we're not on a valid handle
		this.handle = this._getHandle(event);
		if (!this.handle) {
			return false;
		}

		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
			$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
			.css({
				width: this.offsetWidth+"px", height: this.offsetHeight+"px",
				position: "absolute", opacity: "0.001", zIndex: 1000
			})
			.css($(this).offset())
			.appendTo("body");
		});

		return true;

	},

	_mouseStart: function(event) {

		var o = this.options;

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		this.helper.addClass("ui-draggable-dragging");

		//Cache the helper size
		this._cacheHelperProportions();

		//If ddmanager is used for droppables, set the global draggable
		if($.ui.ddmanager) {
			$.ui.ddmanager.current = this;
		}

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Store the helper's css position
		this.cssPosition = this.helper.css( "position" );
		this.scrollParent = this.helper.scrollParent();
		this.offsetParent = this.helper.offsetParent();
		this.offsetParentCssPosition = this.offsetParent.css( "position" );

		//The element's absolute position on the page minus margins
		this.offset = this.positionAbs = this.element.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		//Reset scroll cache
		this.offset.scroll = false;

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		//Generate the original position
		this.originalPosition = this.position = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Set a containment if given in the options
		this._setContainment();

		//Trigger event + callbacks
		if(this._trigger("start", event) === false) {
			this._clear();
			return false;
		}

		//Recache the helper size
		this._cacheHelperProportions();

		//Prepare the droppable offsets
		if ($.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(this, event);
		}


		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position

		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
		if ( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStart(this, event);
		}

		return true;
	},

	_mouseDrag: function(event, noPropagation) {
		// reset any necessary cached properties (see #5009)
		if ( this.offsetParentCssPosition === "fixed" ) {
			this.offset.parent = this._getParentOffset();
		}

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		//Call plugins and callbacks and use the resulting position if something is returned
		if (!noPropagation) {
			var ui = this._uiHash();
			if(this._trigger("drag", event, ui) === false) {
				this._mouseUp({});
				return false;
			}
			this.position = ui.position;
		}

		if(!this.options.axis || this.options.axis !== "y") {
			this.helper[0].style.left = this.position.left+"px";
		}
		if(!this.options.axis || this.options.axis !== "x") {
			this.helper[0].style.top = this.position.top+"px";
		}
		if($.ui.ddmanager) {
			$.ui.ddmanager.drag(this, event);
		}

		return false;
	},

	_mouseStop: function(event) {

		//If we are using droppables, inform the manager about the drop
		var that = this,
			dropped = false;
		if ($.ui.ddmanager && !this.options.dropBehaviour) {
			dropped = $.ui.ddmanager.drop(this, event);
		}

		//if a drop comes from outside (a sortable)
		if(this.dropped) {
			dropped = this.dropped;
			this.dropped = false;
		}

		//if the original element is no longer in the DOM don't bother to continue (see #8269)
		if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
			return false;
		}

		if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
				if(that._trigger("stop", event) !== false) {
					that._clear();
				}
			});
		} else {
			if(this._trigger("stop", event) !== false) {
				this._clear();
			}
		}

		return false;
	},

	_mouseUp: function(event) {
		//Remove frame helpers
		$("div.ui-draggable-iframeFix").each(function() {
			this.parentNode.removeChild(this);
		});

		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
		if( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStop(this, event);
		}

		return $.ui.mouse.prototype._mouseUp.call(this, event);
	},

	cancel: function() {

		if(this.helper.is(".ui-draggable-dragging")) {
			this._mouseUp({});
		} else {
			this._clear();
		}

		return this;

	},

	_getHandle: function(event) {
		return this.options.handle ?
			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
			true;
	},

	_createHelper: function(event) {

		var o = this.options,
			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);

		if(!helper.parents("body").length) {
			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
		}

		if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
			helper.css("position", "absolute");
		}

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj === "string") {
			obj = obj.split(" ");
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ("left" in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ("right" in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ("top" in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ("bottom" in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {

		//Get the offsetParent and cache its position
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		//This needs to be actually done for all browsers, since pageX/pageY includes this information
		//Ugly IE fix
		if((this.offsetParent[0] === document.body) ||
			(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
			po = { top: 0, left: 0 };
		}

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition === "relative") {
			var p = this.element.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.element.css("marginLeft"),10) || 0),
			top: (parseInt(this.element.css("marginTop"),10) || 0),
			right: (parseInt(this.element.css("marginRight"),10) || 0),
			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var over, c, ce,
			o = this.options;

		if ( !o.containment ) {
			this.containment = null;
			return;
		}

		if ( o.containment === "window" ) {
			this.containment = [
				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment === "document") {
			this.containment = [
				0,
				0,
				$( document ).width() - this.helperProportions.width - this.margins.left,
				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment.constructor === Array ) {
			this.containment = o.containment;
			return;
		}

		if ( o.containment === "parent" ) {
			o.containment = this.helper[ 0 ].parentNode;
		}

		c = $( o.containment );
		ce = c[ 0 ];

		if( !ce ) {
			return;
		}

		over = c.css( "overflow" ) !== "hidden";

		this.containment = [
			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
			( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
			( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
		];
		this.relative_container = c;
	},

	_convertPositionTo: function(d, pos) {

		if(!pos) {
			pos = this.position;
		}

		var mod = d === "absolute" ? 1 : -1,
			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;

		//Cache the scroll
		if (!this.offset.scroll) {
			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
		}

		return {
			top: (
				pos.top	+																// The absolute mouse position
				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod )
			),
			left: (
				pos.left +																// The absolute mouse position
				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
			)
		};

	},

	_generatePosition: function(event) {

		var containment, co, top, left,
			o = this.options,
			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
			pageX = event.pageX,
			pageY = event.pageY;

		//Cache the scroll
		if (!this.offset.scroll) {
			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
		}

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		// If we are not dragging yet, we won't check for options
		if ( this.originalPosition ) {
			if ( this.containment ) {
				if ( this.relative_container ){
					co = this.relative_container.offset();
					containment = [
						this.containment[ 0 ] + co.left,
						this.containment[ 1 ] + co.top,
						this.containment[ 2 ] + co.left,
						this.containment[ 3 ] + co.top
					];
				}
				else {
					containment = this.containment;
				}

				if(event.pageX - this.offset.click.left < containment[0]) {
					pageX = containment[0] + this.offset.click.left;
				}
				if(event.pageY - this.offset.click.top < containment[1]) {
					pageY = containment[1] + this.offset.click.top;
				}
				if(event.pageX - this.offset.click.left > containment[2]) {
					pageX = containment[2] + this.offset.click.left;
				}
				if(event.pageY - this.offset.click.top > containment[3]) {
					pageY = containment[3] + this.offset.click.top;
				}
			}

			if(o.grid) {
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY -																	// The absolute mouse position
				this.offset.click.top	-												// Click offset (relative to the element)
				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
			),
			left: (
				pageX -																	// The absolute mouse position
				this.offset.click.left -												// Click offset (relative to the element)
				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
			)
		};

	},

	_clear: function() {
		this.helper.removeClass("ui-draggable-dragging");
		if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
			this.helper.remove();
		}
		this.helper = null;
		this.cancelHelperRemoval = false;
	},

	// From now on bulk stuff - mainly helpers

	_trigger: function(type, event, ui) {
		ui = ui || this._uiHash();
		$.ui.plugin.call(this, type, [event, ui]);
		//The absolute position has to be recalculated after plugins
		if(type === "drag") {
			this.positionAbs = this._convertPositionTo("absolute");
		}
		return $.Widget.prototype._trigger.call(this, type, event, ui);
	},

	plugins: {},

	_uiHash: function() {
		return {
			helper: this.helper,
			position: this.position,
			originalPosition: this.originalPosition,
			offset: this.positionAbs
		};
	}

});

$.ui.plugin.add("draggable", "connectToSortable", {
	start: function(event, ui) {

		var inst = $(this).data("ui-draggable"), o = inst.options,
			uiSortable = $.extend({}, ui, { item: inst.element });
		inst.sortables = [];
		$(o.connectToSortable).each(function() {
			var sortable = $.data(this, "ui-sortable");
			if (sortable && !sortable.options.disabled) {
				inst.sortables.push({
					instance: sortable,
					shouldRevert: sortable.options.revert
				});
				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
				sortable._trigger("activate", event, uiSortable);
			}
		});

	},
	stop: function(event, ui) {

		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
		var inst = $(this).data("ui-draggable"),
			uiSortable = $.extend({}, ui, { item: inst.element });

		$.each(inst.sortables, function() {
			if(this.instance.isOver) {

				this.instance.isOver = 0;

				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)

				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
				if(this.shouldRevert) {
					this.instance.options.revert = this.shouldRevert;
				}

				//Trigger the stop of the sortable
				this.instance._mouseStop(event);

				this.instance.options.helper = this.instance.options._helper;

				//If the helper has been the original item, restore properties in the sortable
				if(inst.options.helper === "original") {
					this.instance.currentItem.css({ top: "auto", left: "auto" });
				}

			} else {
				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
				this.instance._trigger("deactivate", event, uiSortable);
			}

		});

	},
	drag: function(event, ui) {

		var inst = $(this).data("ui-draggable"), that = this;

		$.each(inst.sortables, function() {

			var innermostIntersecting = false,
				thisSortable = this;

			//Copy over some variables to allow calling the sortable's native _intersectsWith
			this.instance.positionAbs = inst.positionAbs;
			this.instance.helperProportions = inst.helperProportions;
			this.instance.offset.click = inst.offset.click;

			if(this.instance._intersectsWith(this.instance.containerCache)) {
				innermostIntersecting = true;
				$.each(inst.sortables, function () {
					this.instance.positionAbs = inst.positionAbs;
					this.instance.helperProportions = inst.helperProportions;
					this.instance.offset.click = inst.offset.click;
					if (this !== thisSortable &&
						this.instance._intersectsWith(this.instance.containerCache) &&
						$.contains(thisSortable.instance.element[0], this.instance.element[0])
					) {
						innermostIntersecting = false;
					}
					return innermostIntersecting;
				});
			}


			if(innermostIntersecting) {
				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
				if(!this.instance.isOver) {

					this.instance.isOver = 1;
					//Now we fake the start of dragging for the sortable instance,
					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
					this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
					this.instance.options.helper = function() { return ui.helper[0]; };

					event.target = this.instance.currentItem[0];
					this.instance._mouseCapture(event, true);
					this.instance._mouseStart(event, true, true);

					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
					this.instance.offset.click.top = inst.offset.click.top;
					this.instance.offset.click.left = inst.offset.click.left;
					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;

					inst._trigger("toSortable", event);
					inst.dropped = this.instance.element; //draggable revert needs that
					//hack so receive/update callbacks work (mostly)
					inst.currentItem = inst.element;
					this.instance.fromOutside = inst;

				}

				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
				if(this.instance.currentItem) {
					this.instance._mouseDrag(event);
				}

			} else {

				//If it doesn't intersect with the sortable, and it intersected before,
				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
				if(this.instance.isOver) {

					this.instance.isOver = 0;
					this.instance.cancelHelperRemoval = true;

					//Prevent reverting on this forced stop
					this.instance.options.revert = false;

					// The out event needs to be triggered independently
					this.instance._trigger("out", event, this.instance._uiHash(this.instance));

					this.instance._mouseStop(event, true);
					this.instance.options.helper = this.instance.options._helper;

					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
					this.instance.currentItem.remove();
					if(this.instance.placeholder) {
						this.instance.placeholder.remove();
					}

					inst._trigger("fromSortable", event);
					inst.dropped = false; //draggable revert needs that
				}

			}

		});

	}
});

$.ui.plugin.add("draggable", "cursor", {
	start: function() {
		var t = $("body"), o = $(this).data("ui-draggable").options;
		if (t.css("cursor")) {
			o._cursor = t.css("cursor");
		}
		t.css("cursor", o.cursor);
	},
	stop: function() {
		var o = $(this).data("ui-draggable").options;
		if (o._cursor) {
			$("body").css("cursor", o._cursor);
		}
	}
});

$.ui.plugin.add("draggable", "opacity", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
		if(t.css("opacity")) {
			o._opacity = t.css("opacity");
		}
		t.css("opacity", o.opacity);
	},
	stop: function(event, ui) {
		var o = $(this).data("ui-draggable").options;
		if(o._opacity) {
			$(ui.helper).css("opacity", o._opacity);
		}
	}
});

$.ui.plugin.add("draggable", "scroll", {
	start: function() {
		var i = $(this).data("ui-draggable");
		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
			i.overflowOffset = i.scrollParent.offset();
		}
	},
	drag: function( event ) {

		var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;

		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {

			if(!o.axis || o.axis !== "x") {
				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
				} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
				}
			}

			if(!o.axis || o.axis !== "y") {
				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
				} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
				}
			}

		} else {

			if(!o.axis || o.axis !== "x") {
				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
				}
			}

			if(!o.axis || o.axis !== "y") {
				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
				}
			}

		}

		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(i, event);
		}

	}
});

$.ui.plugin.add("draggable", "snap", {
	start: function() {

		var i = $(this).data("ui-draggable"),
			o = i.options;

		i.snapElements = [];

		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
			var $t = $(this),
				$o = $t.offset();
			if(this !== i.element[0]) {
				i.snapElements.push({
					item: this,
					width: $t.outerWidth(), height: $t.outerHeight(),
					top: $o.top, left: $o.left
				});
			}
		});

	},
	drag: function(event, ui) {

		var ts, bs, ls, rs, l, r, t, b, i, first,
			inst = $(this).data("ui-draggable"),
			o = inst.options,
			d = o.snapTolerance,
			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

		for (i = inst.snapElements.length - 1; i >= 0; i--){

			l = inst.snapElements[i].left;
			r = l + inst.snapElements[i].width;
			t = inst.snapElements[i].top;
			b = t + inst.snapElements[i].height;

			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
				if(inst.snapElements[i].snapping) {
					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				}
				inst.snapElements[i].snapping = false;
				continue;
			}

			if(o.snapMode !== "inner") {
				ts = Math.abs(t - y2) <= d;
				bs = Math.abs(b - y1) <= d;
				ls = Math.abs(l - x2) <= d;
				rs = Math.abs(r - x1) <= d;
				if(ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				}
				if(bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
				}
				if(ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
				}
				if(rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
				}
			}

			first = (ts || bs || ls || rs);

			if(o.snapMode !== "outer") {
				ts = Math.abs(t - y1) <= d;
				bs = Math.abs(b - y2) <= d;
				ls = Math.abs(l - x1) <= d;
				rs = Math.abs(r - x2) <= d;
				if(ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
				}
				if(bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				}
				if(ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
				}
				if(rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
				}
			}

			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
			}
			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

		}

	}
});

$.ui.plugin.add("draggable", "stack", {
	start: function() {
		var min,
			o = this.data("ui-draggable").options,
			group = $.makeArray($(o.stack)).sort(function(a,b) {
				return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
			});

		if (!group.length) { return; }

		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
		$(group).each(function(i) {
			$(this).css("zIndex", min + i);
		});
		this.css("zIndex", (min + group.length));
	}
});

$.ui.plugin.add("draggable", "zIndex", {
	start: function(event, ui) {
		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
		if(t.css("zIndex")) {
			o._zIndex = t.css("zIndex");
		}
		t.css("zIndex", o.zIndex);
	},
	stop: function(event, ui) {
		var o = $(this).data("ui-draggable").options;
		if(o._zIndex) {
			$(ui.helper).css("zIndex", o._zIndex);
		}
	}
});

})(jQuery);
!jquery.ui.resizable.js
/*!
 * jQuery UI Resizable 1.10.3
 * http://jqueryui.com
 *
 * Copyright 2013 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/resizable/
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */
(function( $, undefined ) {

function num(v) {
	return parseInt(v, 10) || 0;
}

function isNumber(value) {
	return !isNaN(parseInt(value, 10));
}

$.widget("ui.resizable", $.ui.mouse, {
	version: "1.10.3",
	widgetEventPrefix: "resize",
	options: {
		alsoResize: false,
		animate: false,
		animateDuration: "slow",
		animateEasing: "swing",
		aspectRatio: false,
		autoHide: false,
		containment: false,
		ghost: false,
		grid: false,
		handles: "e,s,se",
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		// See #7960
		zIndex: 90,

		// callbacks
		resize: null,
		start: null,
		stop: null
	},
	_create: function() {

		var n, i, handle, axis, hname,
			that = this,
			o = this.options;
		this.element.addClass("ui-resizable");

		$.extend(this, {
			_aspectRatio: !!(o.aspectRatio),
			aspectRatio: o.aspectRatio,
			originalElement: this.element,
			_proportionallyResizeElements: [],
			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
		});

		//Wrap the element if it cannot hold child nodes
		if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {

			//Create a wrapper element and set the wrapper to the new current internal element
			this.element.wrap(
				$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
					position: this.element.css("position"),
					width: this.element.outerWidth(),
					height: this.element.outerHeight(),
					top: this.element.css("top"),
					left: this.element.css("left")
				})
			);

			//Overwrite the original this.element
			this.element = this.element.parent().data(
				"ui-resizable", this.element.data("ui-resizable")
			);

			this.elementIsWrapper = true;

			//Move margins to the wrapper
			this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
			this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});

			//Prevent Safari textarea resize
			this.originalResizeStyle = this.originalElement.css("resize");
			this.originalElement.css("resize", "none");

			//Push the actual element to our proportionallyResize internal array
			this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" }));

			// avoid IE jump (hard set the margin)
			this.originalElement.css({ margin: this.originalElement.css("margin") });

			// fix handlers offset
			this._proportionallyResize();

		}

		this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" });
		if(this.handles.constructor === String) {

			if ( this.handles === "all") {
				this.handles = "n,e,s,w,se,sw,ne,nw";
			}

			n = this.handles.split(",");
			this.handles = {};

			for(i = 0; i < n.length; i++) {

				handle = $.trim(n[i]);
				hname = "ui-resizable-"+handle;
				axis = $("<div class='ui-resizable-handle " + hname + "'></div>");

				// Apply zIndex to all handles - see #7960
				axis.css({ zIndex: o.zIndex });

				//TODO : What's going on here?
				if ("se" === handle) {
					axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
				}

				//Insert into internal handles object and append to element
				this.handles[handle] = ".ui-resizable-"+handle;
				this.element.append(axis);
			}

		}

		this._renderAxis = function(target) {

			var i, axis, padPos, padWrapper;

			target = target || this.element;

			for(i in this.handles) {

				if(this.handles[i].constructor === String) {
					this.handles[i] = $(this.handles[i], this.element).show();
				}

				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {

					axis = $(this.handles[i], this.element);

					//Checking the correct pad and border
					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

					//The padding type i have to apply...
					padPos = [ "padding",
						/ne|nw|n/.test(i) ? "Top" :
						/se|sw|s/.test(i) ? "Bottom" :
						/^e$/.test(i) ? "Right" : "Left" ].join("");

					target.css(padPos, padWrapper);

					this._proportionallyResize();

				}

				//TODO: What's that good for? There's not anything to be executed left
				if(!$(this.handles[i]).length) {
					continue;
				}
			}
		};

		//TODO: make renderAxis a prototype function
		this._renderAxis(this.element);

		this._handles = $(".ui-resizable-handle", this.element)
			.disableSelection();

		//Matching axis name
		this._handles.mouseover(function() {
			if (!that.resizing) {
				if (this.className) {
					axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				}
				//Axis, default = se
				that.axis = axis && axis[1] ? axis[1] : "se";
			}
		});

		//If we want to auto hide the elements
		if (o.autoHide) {
			this._handles.hide();
			$(this.element)
				.addClass("ui-resizable-autohide")
				.mouseenter(function() {
					if (o.disabled) {
						return;
					}
					$(this).removeClass("ui-resizable-autohide");
					that._handles.show();
				})
				.mouseleave(function(){
					if (o.disabled) {
						return;
					}
					if (!that.resizing) {
						$(this).addClass("ui-resizable-autohide");
						that._handles.hide();
					}
				});
		}

		//Initialize the mouse interaction
		this._mouseInit();

	},

	_destroy: function() {

		this._mouseDestroy();

		var wrapper,
			_destroy = function(exp) {
				$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
					.removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove();
			};

		//TODO: Unwrap at same DOM position
		if (this.elementIsWrapper) {
			_destroy(this.element);
			wrapper = this.element;
			this.originalElement.css({
				position: wrapper.css("position"),
				width: wrapper.outerWidth(),
				height: wrapper.outerHeight(),
				top: wrapper.css("top"),
				left: wrapper.css("left")
			}).insertAfter( wrapper );
			wrapper.remove();
		}

		this.originalElement.css("resize", this.originalResizeStyle);
		_destroy(this.originalElement);

		return this;
	},

	_mouseCapture: function(event) {
		var i, handle,
			capture = false;

		for (i in this.handles) {
			handle = $(this.handles[i])[0];
			if (handle === event.target || $.contains(handle, event.target)) {
				capture = true;
			}
		}

		return !this.options.disabled && capture;
	},

	_mouseStart: function(event) {

		var curleft, curtop, cursor,
			o = this.options,
			iniPos = this.element.position(),
			el = this.element;

		this.resizing = true;

		// bugfix for http://dev.jquery.com/ticket/1749
		if ( (/absolute/).test( el.css("position") ) ) {
			el.css({ position: "absolute", top: el.css("top"), left: el.css("left") });
		} else if (el.is(".ui-draggable")) {
			el.css({ position: "absolute", top: iniPos.top, left: iniPos.left });
		}

		this._renderProxy();

		curleft = num(this.helper.css("left"));
		curtop = num(this.helper.css("top"));

		if (o.containment) {
			curleft += $(o.containment).scrollLeft() || 0;
			curtop += $(o.containment).scrollTop() || 0;
		}

		//Store needed variables
		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };
		this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
		this.originalPosition = { left: curleft, top: curtop };
		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
		this.originalMousePosition = { left: event.pageX, top: event.pageY };

		//Aspect Ratio
		this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);

		cursor = $(".ui-resizable-" + this.axis).css("cursor");
		$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

		el.addClass("ui-resizable-resizing");
		this._propagate("start", event);
		return true;
	},

	_mouseDrag: function(event) {

		//Increase performance, avoid regex
		var data,
			el = this.helper, props = {},
			smp = this.originalMousePosition,
			a = this.axis,
			prevTop = this.position.top,
			prevLeft = this.position.left,
			prevWidth = this.size.width,
			prevHeight = this.size.height,
			dx = (event.pageX-smp.left)||0,
			dy = (event.pageY-smp.top)||0,
			trigger = this._change[a];

		if (!trigger) {
			return false;
		}

		// Calculate the attrs that will be change
		data = trigger.apply(this, [event, dx, dy]);

		// Put this in the mouseDrag handler since the user can start pressing shift while resizing
		this._updateVirtualBoundaries(event.shiftKey);
		if (this._aspectRatio || event.shiftKey) {
			data = this._updateRatio(data, event);
		}

		data = this._respectSize(data, event);

		this._updateCache(data);

		// plugins callbacks need to be called first
		this._propagate("resize", event);

		if (this.position.top !== prevTop) {
			props.top = this.position.top + "px";
		}
		if (this.position.left !== prevLeft) {
			props.left = this.position.left + "px";
		}
		if (this.size.width !== prevWidth) {
			props.width = this.size.width + "px";
		}
		if (this.size.height !== prevHeight) {
			props.height = this.size.height + "px";
		}
		el.css(props);

		if (!this._helper && this._proportionallyResizeElements.length) {
			this._proportionallyResize();
		}

		// Call the user callback if the element was resized
		if ( ! $.isEmptyObject(props) ) {
			this._trigger("resize", event, this.ui());
		}

		return false;
	},

	_mouseStop: function(event) {

		this.resizing = false;
		var pr, ista, soffseth, soffsetw, s, left, top,
			o = this.options, that = this;

		if(this._helper) {

			pr = this._proportionallyResizeElements;
			ista = pr.length && (/textarea/i).test(pr[0].nodeName);
			soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height;
			soffsetw = ista ? 0 : that.sizeDiff.width;

			s = { width: (that.helper.width()  - soffsetw), height: (that.helper.height() - soffseth) };
			left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null;
			top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;

			if (!o.animate) {
				this.element.css($.extend(s, { top: top, left: left }));
			}

			that.helper.height(that.size.height);
			that.helper.width(that.size.width);

			if (this._helper && !o.animate) {
				this._proportionallyResize();
			}
		}

		$("body").css("cursor", "auto");

		this.element.removeClass("ui-resizable-resizing");

		this._propagate("stop", event);

		if (this._helper) {
			this.helper.remove();
		}

		return false;

	},

	_updateVirtualBoundaries: function(forceAspectRatio) {
		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
			o = this.options;

		b = {
			minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
			maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
			minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
			maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
		};

		if(this._aspectRatio || forceAspectRatio) {
			// We want to create an enclosing box whose aspect ration is the requested one
			// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
			pMinWidth = b.minHeight * this.aspectRatio;
			pMinHeight = b.minWidth / this.aspectRatio;
			pMaxWidth = b.maxHeight * this.aspectRatio;
			pMaxHeight = b.maxWidth / this.aspectRatio;

			if(pMinWidth > b.minWidth) {
				b.minWidth = pMinWidth;
			}
			if(pMinHeight > b.minHeight) {
				b.minHeight = pMinHeight;
			}
			if(pMaxWidth < b.maxWidth) {
				b.maxWidth = pMaxWidth;
			}
			if(pMaxHeight < b.maxHeight) {
				b.maxHeight = pMaxHeight;
			}
		}
		this._vBoundaries = b;
	},

	_updateCache: function(data) {
		this.offset = this.helper.offset();
		if (isNumber(data.left)) {
			this.position.left = data.left;
		}
		if (isNumber(data.top)) {
			this.position.top = data.top;
		}
		if (isNumber(data.height)) {
			this.size.height = data.height;
		}
		if (isNumber(data.width)) {
			this.size.width = data.width;
		}
	},

	_updateRatio: function( data ) {

		var cpos = this.position,
			csize = this.size,
			a = this.axis;

		if (isNumber(data.height)) {
			data.width = (data.height * this.aspectRatio);
		} else if (isNumber(data.width)) {
			data.height = (data.width / this.aspectRatio);
		}

		if (a === "sw") {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a === "nw") {
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}

		return data;
	},

	_respectSize: function( data ) {

		var o = this._vBoundaries,
			a = this.axis,
			ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
			isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
			dw = this.originalPosition.left + this.originalSize.width,
			dh = this.position.top + this.size.height,
			cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
		if (isminw) {
			data.width = o.minWidth;
		}
		if (isminh) {
			data.height = o.minHeight;
		}
		if (ismaxw) {
			data.width = o.maxWidth;
		}
		if (ismaxh) {
			data.height = o.maxHeight;
		}

		if (isminw && cw) {
			data.left = dw - o.minWidth;
		}
		if (ismaxw && cw) {
			data.left = dw - o.maxWidth;
		}
		if (isminh && ch) {
			data.top = dh - o.minHeight;
		}
		if (ismaxh && ch) {
			data.top = dh - o.maxHeight;
		}

		// fixing jump error on top/left - bug #2330
		if (!data.width && !data.height && !data.left && data.top) {
			data.top = null;
		} else if (!data.width && !data.height && !data.top && data.left) {
			data.left = null;
		}

		return data;
	},

	_proportionallyResize: function() {

		if (!this._proportionallyResizeElements.length) {
			return;
		}

		var i, j, borders, paddings, prel,
			element = this.helper || this.element;

		for ( i=0; i < this._proportionallyResizeElements.length; i++) {

			prel = this._proportionallyResizeElements[i];

			if (!this.borderDif) {
				this.borderDif = [];
				borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")];
				paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")];

				for ( j = 0; j < borders.length; j++ ) {
					this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 );
				}
			}

			prel.css({
				height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
				width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
			});

		}

	},

	_renderProxy: function() {

		var el = this.element, o = this.options;
		this.elementOffset = el.offset();

		if(this._helper) {

			this.helper = this.helper || $("<div style='overflow:hidden;'></div>");

			this.helper.addClass(this._helper).css({
				width: this.element.outerWidth() - 1,
				height: this.element.outerHeight() - 1,
				position: "absolute",
				left: this.elementOffset.left +"px",
				top: this.elementOffset.top +"px",
				zIndex: ++o.zIndex //TODO: Don't modify option
			});

			this.helper
				.appendTo("body")
				.disableSelection();

		} else {
			this.helper = this.element;
		}

	},

	_change: {
		e: function(event, dx) {
			return { width: this.originalSize.width + dx };
		},
		w: function(event, dx) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(event, dx, dy) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(event, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		sw: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		},
		ne: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
		},
		nw: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
		}
	},

	_propagate: function(n, event) {
		$.ui.plugin.call(this, n, [event, this.ui()]);
		(n !== "resize" && this._trigger(n, event, this.ui()));
	},

	plugins: {},

	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	}

});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "animate", {

	stop: function( event ) {
		var that = $(this).data("ui-resizable"),
			o = that.options,
			pr = that._proportionallyResizeElements,
			ista = pr.length && (/textarea/i).test(pr[0].nodeName),
			soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height,
			soffsetw = ista ? 0 : that.sizeDiff.width,
			style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
			left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null,
			top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;

		that.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), {
				duration: o.animateDuration,
				easing: o.animateEasing,
				step: function() {

					var data = {
						width: parseInt(that.element.css("width"), 10),
						height: parseInt(that.element.css("height"), 10),
						top: parseInt(that.element.css("top"), 10),
						left: parseInt(that.element.css("left"), 10)
					};

					if (pr && pr.length) {
						$(pr[0]).css({ width: data.width, height: data.height });
					}

					// propagating resize, and updating values for each animation step
					that._updateCache(data);
					that._propagate("resize", event);

				}
			}
		);
	}

});

$.ui.plugin.add("resizable", "containment", {

	start: function() {
		var element, p, co, ch, cw, width, height,
			that = $(this).data("ui-resizable"),
			o = that.options,
			el = that.element,
			oc = o.containment,
			ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;

		if (!ce) {
			return;
		}

		that.containerElement = $(ce);

		if (/document/.test(oc) || oc === document) {
			that.containerOffset = { left: 0, top: 0 };
			that.containerPosition = { left: 0, top: 0 };

			that.parentData = {
				element: $(document), left: 0, top: 0,
				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
			};
		}

		// i'm a node, so compute top, left, right, bottom
		else {
			element = $(ce);
			p = [];
			$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });

			that.containerOffset = element.offset();
			that.containerPosition = element.position();
			that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };

			co = that.containerOffset;
			ch = that.containerSize.height;
			cw = that.containerSize.width;
			width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw );
			height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);

			that.parentData = {
				element: ce, left: co.left, top: co.top, width: width, height: height
			};
		}
	},

	resize: function( event ) {
		var woset, hoset, isParent, isOffsetRelative,
			that = $(this).data("ui-resizable"),
			o = that.options,
			co = that.containerOffset, cp = that.position,
			pRatio = that._aspectRatio || event.shiftKey,
			cop = { top:0, left:0 }, ce = that.containerElement;

		if (ce[0] !== document && (/static/).test(ce.css("position"))) {
			cop = co;
		}

		if (cp.left < (that._helper ? co.left : 0)) {
			that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
			if (pRatio) {
				that.size.height = that.size.width / that.aspectRatio;
			}
			that.position.left = o.helper ? co.left : 0;
		}

		if (cp.top < (that._helper ? co.top : 0)) {
			that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
			if (pRatio) {
				that.size.width = that.size.height * that.aspectRatio;
			}
			that.position.top = that._helper ? co.top : 0;
		}

		that.offset.left = that.parentData.left+that.position.left;
		that.offset.top = that.parentData.top+that.position.top;

		woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width );
		hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );

		isParent = that.containerElement.get(0) === that.element.parent().get(0);
		isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position"));

		if(isParent && isOffsetRelative) {
			woset -= that.parentData.left;
		}

		if (woset + that.size.width >= that.parentData.width) {
			that.size.width = that.parentData.width - woset;
			if (pRatio) {
				that.size.height = that.size.width / that.aspectRatio;
			}
		}

		if (hoset + that.size.height >= that.parentData.height) {
			that.size.height = that.parentData.height - hoset;
			if (pRatio) {
				that.size.width = that.size.height * that.aspectRatio;
			}
		}
	},

	stop: function(){
		var that = $(this).data("ui-resizable"),
			o = that.options,
			co = that.containerOffset,
			cop = that.containerPosition,
			ce = that.containerElement,
			helper = $(that.helper),
			ho = helper.offset(),
			w = helper.outerWidth() - that.sizeDiff.width,
			h = helper.outerHeight() - that.sizeDiff.height;

		if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) {
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
		}

		if (that._helper && !o.animate && (/static/).test(ce.css("position"))) {
			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
		}

	}
});

$.ui.plugin.add("resizable", "alsoResize", {

	start: function () {
		var that = $(this).data("ui-resizable"),
			o = that.options,
			_store = function (exp) {
				$(exp).each(function() {
					var el = $(this);
					el.data("ui-resizable-alsoresize", {
						width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
						left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
					});
				});
			};

		if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
			else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
		}else{
			_store(o.alsoResize);
		}
	},

	resize: function (event, ui) {
		var that = $(this).data("ui-resizable"),
			o = that.options,
			os = that.originalSize,
			op = that.originalPosition,
			delta = {
				height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
				top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
			},

			_alsoResize = function (exp, c) {
				$(exp).each(function() {
					var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
						css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"];

					$.each(css, function (i, prop) {
						var sum = (start[prop]||0) + (delta[prop]||0);
						if (sum && sum >= 0) {
							style[prop] = sum || null;
						}
					});

					el.css(style);
				});
			};

		if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
			$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
		}else{
			_alsoResize(o.alsoResize);
		}
	},

	stop: function () {
		$(this).removeData("resizable-alsoresize");
	}
});

$.ui.plugin.add("resizable", "ghost", {

	start: function() {

		var that = $(this).data("ui-resizable"), o = that.options, cs = that.size;

		that.ghost = that.originalElement.clone();
		that.ghost
			.css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
			.addClass("ui-resizable-ghost")
			.addClass(typeof o.ghost === "string" ? o.ghost : "");

		that.ghost.appendTo(that.helper);

	},

	resize: function(){
		var that = $(this).data("ui-resizable");
		if (that.ghost) {
			that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width });
		}
	},

	stop: function() {
		var that = $(this).data("ui-resizable");
		if (that.ghost && that.helper) {
			that.helper.get(0).removeChild(that.ghost.get(0));
		}
	}

});

$.ui.plugin.add("resizable", "grid", {

	resize: function() {
		var that = $(this).data("ui-resizable"),
			o = that.options,
			cs = that.size,
			os = that.originalSize,
			op = that.originalPosition,
			a = that.axis,
			grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid,
			gridX = (grid[0]||1),
			gridY = (grid[1]||1),
			ox = Math.round((cs.width - os.width) / gridX) * gridX,
			oy = Math.round((cs.height - os.height) / gridY) * gridY,
			newWidth = os.width + ox,
			newHeight = os.height + oy,
			isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
			isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
			isMinWidth = o.minWidth && (o.minWidth > newWidth),
			isMinHeight = o.minHeight && (o.minHeight > newHeight);

		o.grid = grid;

		if (isMinWidth) {
			newWidth = newWidth + gridX;
		}
		if (isMinHeight) {
			newHeight = newHeight + gridY;
		}
		if (isMaxWidth) {
			newWidth = newWidth - gridX;
		}
		if (isMaxHeight) {
			newHeight = newHeight - gridY;
		}

		if (/^(se|s|e)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
		} else if (/^(ne)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.top = op.top - oy;
		} else if (/^(sw)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.left = op.left - ox;
		} else {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.top = op.top - oy;
			that.position.left = op.left - ox;
		}
	}

});

})(jQuery);
//}}}
!jquery.ui.touch-punch.js
http://touchpunch.furf.com
https://raw.github.com/furf/jquery-ui-touch-punch/master/jquery.ui.touch-punch.js
//{{{
/*!
 * jQuery UI Touch Punch 0.2.2
 *
 * Copyright 2011, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Depends:
 *  jquery.ui.widget.js
 *  jquery.ui.mouse.js
 */
(function ($) {

  // Detect touch support
  $.support.touch = 'ontouchend' in document;

  // Ignore browsers without touch support
  if (!$.support.touch) {
    return;
  }

  var mouseProto = $.ui.mouse.prototype,
      _mouseInit = mouseProto._mouseInit,
      touchHandled;

  /**
   * Simulate a mouse event based on a corresponding touch event
   * @param {Object} event A touch event
   * @param {String} simulatedType The corresponding mouse event
   */
  function simulateMouseEvent (event, simulatedType) {

    // Ignore multi-touch events
    if (event.originalEvent.touches.length > 1) {
      return;
    }

    event.preventDefault();

    var touch = event.originalEvent.changedTouches[0],
        simulatedEvent = document.createEvent('MouseEvents');
    
    // Initialize the simulated mouse event using the touch event's coordinates
    simulatedEvent.initMouseEvent(
      simulatedType,    // type
      true,             // bubbles                    
      true,             // cancelable                 
      window,           // view                       
      1,                // detail                     
      touch.screenX,    // screenX                    
      touch.screenY,    // screenY                    
      touch.clientX,    // clientX                    
      touch.clientY,    // clientY                    
      false,            // ctrlKey                    
      false,            // altKey                     
      false,            // shiftKey                   
      false,            // metaKey                    
      0,                // button                     
      null              // relatedTarget              
    );

    // Dispatch the simulated event to the target element
    event.target.dispatchEvent(simulatedEvent);
  }

  /**
   * Handle the jQuery UI widget's touchstart events
   * @param {Object} event The widget element's touchstart event
   */
  mouseProto._touchStart = function (event) {

    var self = this;

    // Ignore the event if another widget is already being handled
    if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
      return;
    }

    // Set the flag to prevent other widgets from inheriting the touch event
    touchHandled = true;

    // Track movement to determine if interaction was a click
    self._touchMoved = false;

    // Simulate the mouseover event
    simulateMouseEvent(event, 'mouseover');

    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');

    // Simulate the mousedown event
    simulateMouseEvent(event, 'mousedown');
  };

  /**
   * Handle the jQuery UI widget's touchmove events
   * @param {Object} event The document's touchmove event
   */
  mouseProto._touchMove = function (event) {

    // Ignore event if not handled
    if (!touchHandled) {
      return;
    }

    // Interaction was not a click
    this._touchMoved = true;

    // Simulate the mousemove event
    simulateMouseEvent(event, 'mousemove');
  };

  /**
   * Handle the jQuery UI widget's touchend events
   * @param {Object} event The document's touchend event
   */
  mouseProto._touchEnd = function (event) {

    // Ignore event if not handled
    if (!touchHandled) {
      return;
    }

    // Simulate the mouseup event
    simulateMouseEvent(event, 'mouseup');

    // Simulate the mouseout event
    simulateMouseEvent(event, 'mouseout');

    // If the touch interaction did not move, it should trigger a click
    if (!this._touchMoved) {

      // Simulate the click event
      simulateMouseEvent(event, 'click');
    }

    // Unset the flag to allow other widgets to inherit the touch event
    touchHandled = false;
  };

  /**
   * A duck punch of the $.ui.mouse _mouseInit method to support touch events.
   * This method extends the widget with bound touch event handlers that
   * translate touch events to mouse events and pass them to the widget's
   * original mouse event handling methods.
   */
  mouseProto._mouseInit = function () {
    
    var self = this;

    // Delegate the touch handlers to the widget's element
    self.element
      .bind('touchstart', $.proxy(self, '_touchStart'))
      .bind('touchmove', $.proxy(self, '_touchMove'))
      .bind('touchend', $.proxy(self, '_touchEnd'));

    // Call the original $.ui.mouse init method
    _mouseInit.call(self);
  };

})(jQuery);
//}}}
//{{{
if (!config.macros.guid) {
	config.macros.guid = {
		generate: function () {
			// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
			return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
				var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
				return v.toString(16);
			});
		},
		
		generateUniqueForTiddler: function (title, tiddlerList) {
			var originalGuid = null;
			var tiddler = null;
			if (title) {
				tiddler = store.getTiddler(title);
				if (tiddler && tiddler.fields) {
					originalGuid = tiddler.fields["guid"];
					if (originalGuid) {
						tiddler.fields["guid"] = "";
					}
				}
			}
			var guid = originalGuid ? originalGuid : this.generate();
			
			if (!tiddlerList) {
				tiddlerList = store.getTiddlers();
			}
			
			var collision = true;
			do {
				collision = false;
				for (var i = 0; i < tiddlerList.length; i++) {
					if (tiddlerList[i].fields && (tiddlerList[i].fields["guid"] == guid)) {
						collision = true;
						break;
					}
				}
				guid = collision ? this.generate() : guid;
			} while (collision);
			
			if (originalGuid) {
				tiddler.fields["guid"] = originalGuid;
			}
			return guid;
		}
	};
	
	TiddlyWiki.prototype.saveTiddlerOverride_base = TiddlyWiki.prototype.saveTiddler;
	TiddlyWiki.prototype.saveTiddler = function (title, newTitle, newBody, modifier, modified, tags, fields, clearChangeCount, created, creator) {
		var tiddler = TiddlyWiki.prototype.saveTiddlerOverride_base.apply(this, arguments);
		if (!tiddler.fields) {
			tiddler.fields = {};
		}
		tiddler.fields["guid"] = config.macros.guid.generateUniqueForTiddler(newTitle ? newTitle : title);
		return tiddler;
	}
}
//}}}
//{{{
config.macros.goToTiddler = {
	handler: function (place, macroName, params, wikifier, paramString) {
		var cmnt = config.macros.newTiddler;
		params = paramString.parseParams("anon", null, true, false, false);
		var title = params[1] && params[1].name == "anon" ? params[1].value : cmnt.title;
		title = getParam(params, "title", title);
		var tiddler = store.getTiddler(title);
		if (tiddler || readOnly) {
			var label = getParam(params, "label", cmnt.label);
			createTiddlyText(createTiddlyLink(place, title, false, "button", false, tiddler), label);
		} else {
			cmnt.createNewTiddlerButton(place, title, params, cmnt.label, cmnt.prompt, cmnt.accessKey, "title", false);
		}
	}
}
//}}}
http://www.htmlhelp.com/reference/html40/entities

Character entity references, or //entities// for short, provide a method of entering characters that cannot be expressed in the document's character encoding or that cannot easily be entered on a keyboard. Entities are case-sensitive and take the form ''&amp;//name//;''. Examples of entities include ''&amp;copy;'' for the copyright symbol and ''&amp;Alpha;'' for the Greek capital letter alpha.

In addition to entities, authors can use //numeric character references//. While entities are limited to a subset of [[Unicode characters|http://www.unicode.org/charts/]], numeric character references can specify any character. Numeric character references may be given in decimal or hexadecimal, though browser support is stronger for decimal references. Decimal references are of the form ''&amp;#//number//;'' while hexadecimal references take the case-insensitive form ''&amp;#x//number//;''. Examples of numeric character references include ''&amp;#169;'' or ''&amp;#xA9;'' for the copyright symbol, ''&amp;#913;'' or ''&amp;#x391;'' for the Greek capital letter alpha, and ''&amp;#1575;'' or ''&amp;#x627;'' for the Arabic letter alef.

The following documents feature tables of the character entity references in HTML 4, along with the numeric character reference in decimal and hexadecimal. A rendering of each character reference is provided so that users may check their browsers' compliance.

!Latin-1 Entities

The following table gives the character entity reference, decimal character reference, and hexadecimal character reference for 8-bit characters in the Latin-1 (ISO-8859-1) character set, as well as the rendering of each in your browser. [[Glyphs|http://www.unicode.org/charts/]] of the characters are available at the [[Unicode Consortium|http://www.unicode.org/]].

| !Character | !Entity | !Decimal | !Hex |>|>| !Rendering in Your Browser |
|~|~|~|~| !Entity | !Decimal | !Hex |
|no-break space = non-breaking space |&amp;nbsp; |&amp;#160; |&amp;#xA0; |&nbsp; |&#160; |&#xA0; |
|inverted exclamation mark |&amp;iexcl; |&amp;#161; |&amp;#xA1; |&iexcl; |&#161; |&#xA1; |
|cent sign |&amp;cent; |&amp;#162; |&amp;#xA2; |&cent; |&#162; |&#xA2; |
|pound sign |&amp;pound; |&amp;#163; |&amp;#xA3; |&pound; |&#163; |&#xA3; |
|currency sign |&amp;curren; |&amp;#164; |&amp;#xA4; |&curren; |&#164; |&#xA4; |
|yen sign = yuan sign |&amp;yen; |&amp;#165; |&amp;#xA5; |&yen; |&#165; |&#xA5; |
|broken bar = broken vertical bar |&amp;brvbar; |&amp;#166; |&amp;#xA6; |&brvbar; |&#166; |&#xA6; |
|section sign |&amp;sect; |&amp;#167; |&amp;#xA7; |&sect; |&#167; |&#xA7; |
|diaeresis = spacing diaeresis |&amp;uml; |&amp;#168; |&amp;#xA8; |&uml; |&#168; |&#xA8; |
|copyright sign |&amp;copy; |&amp;#169; |&amp;#xA9; |&copy; |&#169; |&#xA9; |
|feminine ordinal indicator |&amp;ordf; |&amp;#170; |&amp;#xAA; |&ordf; |&#170; |&#xAA; |
|left-pointing double angle quotation mark = left pointing guillemet |&amp;laquo; |&amp;#171; |&amp;#xAB; |&laquo; |&#171; |&#xAB; |
|not sign |&amp;not; |&amp;#172; |&amp;#xAC; |&not; |&#172; |&#xAC; |
|soft hyphen = discretionary hyphen |&amp;shy; |&amp;#173; |&amp;#xAD; |&shy; |&#173; |&#xAD; |
|registered sign = registered trade mark sign |&amp;reg; |&amp;#174; |&amp;#xAE; |&reg; |&#174; |&#xAE; |
|macron = spacing macron = overline = APL overbar |&amp;macr; |&amp;#175; |&amp;#xAF; |&macr; |&#175; |&#xAF; |
|degree sign |&amp;deg; |&amp;#176; |&amp;#xB0; |&deg; |&#176; |&#xB0; |
|plus-minus sign = plus-or-minus sign |&amp;plusmn; |&amp;#177; |&amp;#xB1; |&plusmn; |&#177; |&#xB1; |
|superscript two = superscript digit two = squared |&amp;sup2; |&amp;#178; |&amp;#xB2; |&sup2; |&#178; |&#xB2; |
|superscript three = superscript digit three = cubed |&amp;sup3; |&amp;#179; |&amp;#xB3; |&sup3; |&#179; |&#xB3; |
|acute accent = spacing acute |&amp;acute; |&amp;#180; |&amp;#xB4; |&acute; |&#180; |&#xB4; |
|micro sign |&amp;micro; |&amp;#181; |&amp;#xB5; |&micro; |&#181; |&#xB5; |
|pilcrow sign = paragraph sign |&amp;para; |&amp;#182; |&amp;#xB6; |&para; |&#182; |&#xB6; |
|middle dot = Georgian comma = Greek middle dot |&amp;middot; |&amp;#183; |&amp;#xB7; |&middot; |&#183; |&#xB7; |
|cedilla = spacing cedilla |&amp;cedil; |&amp;#184; |&amp;#xB8; |&cedil; |&#184; |&#xB8; |
|superscript one = superscript digit one |&amp;sup1; |&amp;#185; |&amp;#xB9; |&sup1; |&#185; |&#xB9; |
|masculine ordinal indicator |&amp;ordm; |&amp;#186; |&amp;#xBA; |&ordm; |&#186; |&#xBA; |
|right-pointing double angle quotation mark = right pointing guillemet |&amp;raquo; |&amp;#187; |&amp;#xBB; |&raquo; |&#187; |&#xBB; |
|vulgar fraction one quarter = fraction one quarter |&amp;frac14; |&amp;#188; |&amp;#xBC; |&frac14; |&#188; |&#xBC; |
|vulgar fraction one half = fraction one half |&amp;frac12; |&amp;#189; |&amp;#xBD; |&frac12; |&#189; |&#xBD; |
|vulgar fraction three quarters = fraction three quarters |&amp;frac34; |&amp;#190; |&amp;#xBE; |&frac34; |&#190; |&#xBE; |
|inverted question mark = turned question mark |&amp;iquest; |&amp;#191; |&amp;#xBF; |&iquest; |&#191; |&#xBF; |
|Latin capital letter A with grave = Latin capital letter A grave |&amp;Agrave; |&amp;#192; |&amp;#xC0; |&Agrave; |&#192; |&#xC0; |
|Latin capital letter A with acute |&amp;Aacute; |&amp;#193; |&amp;#xC1; |&Aacute; |&#193; |&#xC1; |
|Latin capital letter A with circumflex |&amp;Acirc; |&amp;#194; |&amp;#xC2; |&Acirc; |&#194; |&#xC2; |
|Latin capital letter A with tilde |&amp;Atilde; |&amp;#195; |&amp;#xC3; |&Atilde; |&#195; |&#xC3; |
|Latin capital letter A with diaeresis |&amp;Auml; |&amp;#196; |&amp;#xC4; |&Auml; |&#196; |&#xC4; |
|Latin capital letter A with ring above = Latin capital letter A ring |&amp;Aring; |&amp;#197; |&amp;#xC5; |&Aring; |&#197; |&#xC5; |
|Latin capital letter AE = Latin capital ligature AE |&amp;AElig; |&amp;#198; |&amp;#xC6; |&AElig; |&#198; |&#xC6; |
|Latin capital letter C with cedilla |&amp;Ccedil; |&amp;#199; |&amp;#xC7; |&Ccedil; |&#199; |&#xC7; |
|Latin capital letter E with grave |&amp;Egrave; |&amp;#200; |&amp;#xC8; |&Egrave; |&#200; |&#xC8; |
|Latin capital letter E with acute |&amp;Eacute; |&amp;#201; |&amp;#xC9; |&Eacute; |&#201; |&#xC9; |
|Latin capital letter E with circumflex |&amp;Ecirc; |&amp;#202; |&amp;#xCA; |&Ecirc; |&#202; |&#xCA; |
|Latin capital letter E with diaeresis |&amp;Euml; |&amp;#203; |&amp;#xCB; |&Euml; |&#203; |&#xCB; |
|Latin capital letter I with grave |&amp;Igrave; |&amp;#204; |&amp;#xCC; |&Igrave; |&#204; |&#xCC; |
|Latin capital letter I with acute |&amp;Iacute; |&amp;#205; |&amp;#xCD; |&Iacute; |&#205; |&#xCD; |
|Latin capital letter I with circumflex |&amp;Icirc; |&amp;#206; |&amp;#xCE; |&Icirc; |&#206; |&#xCE; |
|Latin capital letter I with diaeresis |&amp;Iuml; |&amp;#207; |&amp;#xCF; |&Iuml; |&#207; |&#xCF; |
|Latin capital letter ETH |&amp;ETH; |&amp;#208; |&amp;#xD0; |&ETH; |&#208; |&#xD0; |
|Latin capital letter N with tilde |&amp;Ntilde; |&amp;#209; |&amp;#xD1; |&Ntilde; |&#209; |&#xD1; |
|Latin capital letter O with grave |&amp;Ograve; |&amp;#210; |&amp;#xD2; |&Ograve; |&#210; |&#xD2; |
|Latin capital letter O with acute |&amp;Oacute; |&amp;#211; |&amp;#xD3; |&Oacute; |&#211; |&#xD3; |
|Latin capital letter O with circumflex |&amp;Ocirc; |&amp;#212; |&amp;#xD4; |&Ocirc; |&#212; |&#xD4; |
|Latin capital letter O with tilde |&amp;Otilde; |&amp;#213; |&amp;#xD5; |&Otilde; |&#213; |&#xD5; |
|Latin capital letter O with diaeresis |&amp;Ouml; |&amp;#214; |&amp;#xD6; |&Ouml; |&#214; |&#xD6; |
|multiplication sign |&amp;times; |&amp;#215; |&amp;#xD7; |&times; |&#215; |&#xD7; |
|Latin capital letter O with stroke = Latin capital letter O slash |&amp;Oslash; |&amp;#216; |&amp;#xD8; |&Oslash; |&#216; |&#xD8; |
|Latin capital letter U with grave |&amp;Ugrave; |&amp;#217; |&amp;#xD9; |&Ugrave; |&#217; |&#xD9; |
|Latin capital letter U with acute |&amp;Uacute; |&amp;#218; |&amp;#xDA; |&Uacute; |&#218; |&#xDA; |
|Latin capital letter U with circumflex |&amp;Ucirc; |&amp;#219; |&amp;#xDB; |&Ucirc; |&#219; |&#xDB; |
|Latin capital letter U with diaeresis |&amp;Uuml; |&amp;#220; |&amp;#xDC; |&Uuml; |&#220; |&#xDC; |
|Latin capital letter Y with acute |&amp;Yacute; |&amp;#221; |&amp;#xDD; |&Yacute; |&#221; |&#xDD; |
|Latin capital letter THORN |&amp;THORN; |&amp;#222; |&amp;#xDE; |&THORN; |&#222; |&#xDE; |
|Latin small letter sharp s = ess-zed |&amp;szlig; |&amp;#223; |&amp;#xDF; |&szlig; |&#223; |&#xDF; |
|Latin small letter a with grave = Latin small letter a grave |&amp;agrave; |&amp;#224; |&amp;#xE0; |&agrave; |&#224; |&#xE0; |
|Latin small letter a with acute |&amp;aacute; |&amp;#225; |&amp;#xE1; |&aacute; |&#225; |&#xE1; |
|Latin small letter a with circumflex |&amp;acirc; |&amp;#226; |&amp;#xE2; |&acirc; |&#226; |&#xE2; |
|Latin small letter a with tilde |&amp;atilde; |&amp;#227; |&amp;#xE3; |&atilde; |&#227; |&#xE3; |
|Latin small letter a with diaeresis |&amp;auml; |&amp;#228; |&amp;#xE4; |&auml; |&#228; |&#xE4; |
|Latin small letter a with ring above = Latin small letter a ring |&amp;aring; |&amp;#229; |&amp;#xE5; |&aring; |&#229; |&#xE5; |
|Latin small letter ae = Latin small ligature ae |&amp;aelig; |&amp;#230; |&amp;#xE6; |&aelig; |&#230; |&#xE6; |
|Latin small letter c with cedilla |&amp;ccedil; |&amp;#231; |&amp;#xE7; |&ccedil; |&#231; |&#xE7; |
|Latin small letter e with grave |&amp;egrave; |&amp;#232; |&amp;#xE8; |&egrave; |&#232; |&#xE8; |
|Latin small letter e with acute |&amp;eacute; |&amp;#233; |&amp;#xE9; |&eacute; |&#233; |&#xE9; |
|Latin small letter e with circumflex |&amp;ecirc; |&amp;#234; |&amp;#xEA; |&ecirc; |&#234; |&#xEA; |
|Latin small letter e with diaeresis |&amp;euml; |&amp;#235; |&amp;#xEB; |&euml; |&#235; |&#xEB; |
|Latin small letter i with grave |&amp;igrave; |&amp;#236; |&amp;#xEC; |&igrave; |&#236; |&#xEC; |
|Latin small letter i with acute |&amp;iacute; |&amp;#237; |&amp;#xED; |&iacute; |&#237; |&#xED; |
|Latin small letter i with circumflex |&amp;icirc; |&amp;#238; |&amp;#xEE; |&icirc; |&#238; |&#xEE; |
|Latin small letter i with diaeresis |&amp;iuml; |&amp;#239; |&amp;#xEF; |&iuml; |&#239; |&#xEF; |
|Latin small letter eth |&amp;eth; |&amp;#240; |&amp;#xF0; |&eth; |&#240; |&#xF0; |
|Latin small letter n with tilde |&amp;ntilde; |&amp;#241; |&amp;#xF1; |&ntilde; |&#241; |&#xF1; |
|Latin small letter o with grave |&amp;ograve; |&amp;#242; |&amp;#xF2; |&ograve; |&#242; |&#xF2; |
|Latin small letter o with acute |&amp;oacute; |&amp;#243; |&amp;#xF3; |&oacute; |&#243; |&#xF3; |
|Latin small letter o with circumflex |&amp;ocirc; |&amp;#244; |&amp;#xF4; |&ocirc; |&#244; |&#xF4; |
|Latin small letter o with tilde |&amp;otilde; |&amp;#245; |&amp;#xF5; |&otilde; |&#245; |&#xF5; |
|Latin small letter o with diaeresis |&amp;ouml; |&amp;#246; |&amp;#xF6; |&ouml; |&#246; |&#xF6; |
|division sign |&amp;divide; |&amp;#247; |&amp;#xF7; |&divide; |&#247; |&#xF7; |
|Latin small letter o with stroke = Latin small letter o slash |&amp;oslash; |&amp;#248; |&amp;#xF8; |&oslash; |&#248; |&#xF8; |
|Latin small letter u with grave |&amp;ugrave; |&amp;#249; |&amp;#xF9; |&ugrave; |&#249; |&#xF9; |
|Latin small letter u with acute |&amp;uacute; |&amp;#250; |&amp;#xFA; |&uacute; |&#250; |&#xFA; |
|Latin small letter u with circumflex |&amp;ucirc; |&amp;#251; |&amp;#xFB; |&ucirc; |&#251; |&#xFB; |
|Latin small letter u with diaeresis |&amp;uuml; |&amp;#252; |&amp;#xFC; |&uuml; |&#252; |&#xFC; |
|Latin small letter y with acute |&amp;yacute; |&amp;#253; |&amp;#xFD; |&yacute; |&#253; |&#xFD; |
|Latin small letter thorn |&amp;thorn; |&amp;#254; |&amp;#xFE; |&thorn; |&#254; |&#xFE; |
|Latin small letter y with diaeresis |&amp;yuml; |&amp;#255; |&amp;#xFF; |&yuml; |&#255; |&#xFF; |

!Entities for Symbols and Greek Letters

The following table gives the character entity reference, decimal character reference, and hexadecimal character reference for symbols and Greek letters, as well as the rendering of each in your browser. [[Glyphs|http://www.unicode.org/charts/]] of the characters are available at the [[Unicode Consortium|http://www.unicode.org/]].

These entities are all new in HTML 4.0 and may not be supported by old browsers. Support in recent browsers is good.

| !Character | !Entity | !Decimal | !Hex |>|>| !Rendering in Your Browser |
|~|~|~|~| !Entity | !Decimal | !Hex |
|Latin small f with hook = function = florin |&amp;fnof; |&amp;#402; |&amp;#x192; |&fnof; |&#402; |&#x192; |
|Greek capital letter alpha |&amp;Alpha; |&amp;#913; |&amp;#x391; |&Alpha; |&#913; |&#x391; |
|Greek capital letter beta |&amp;Beta; |&amp;#914; |&amp;#x392; |&Beta; |&#914; |&#x392; |
|Greek capital letter gamma |&amp;Gamma; |&amp;#915; |&amp;#x393; |&Gamma; |&#915; |&#x393; |
|Greek capital letter delta |&amp;Delta; |&amp;#916; |&amp;#x394; |&Delta; |&#916; |&#x394; |
|Greek capital letter epsilon |&amp;Epsilon; |&amp;#917; |&amp;#x395; |&Epsilon; |&#917; |&#x395; |
|Greek capital letter zeta |&amp;Zeta; |&amp;#918; |&amp;#x396; |&Zeta; |&#918; |&#x396; |
|Greek capital letter eta |&amp;Eta; |&amp;#919; |&amp;#x397; |&Eta; |&#919; |&#x397; |
|Greek capital letter theta |&amp;Theta; |&amp;#920; |&amp;#x398; |&Theta; |&#920; |&#x398; |
|Greek capital letter iota |&amp;Iota; |&amp;#921; |&amp;#x399; |&Iota; |&#921; |&#x399; |
|Greek capital letter kappa |&amp;Kappa; |&amp;#922; |&amp;#x39A; |&Kappa; |&#922; |&#x39A; |
|Greek capital letter lambda |&amp;Lambda; |&amp;#923; |&amp;#x39B; |&Lambda; |&#923; |&#x39B; |
|Greek capital letter mu |&amp;Mu; |&amp;#924; |&amp;#x39C; |&Mu; |&#924; |&#x39C; |
|Greek capital letter nu |&amp;Nu; |&amp;#925; |&amp;#x39D; |&Nu; |&#925; |&#x39D; |
|Greek capital letter xi |&amp;Xi; |&amp;#926; |&amp;#x39E; |&Xi; |&#926; |&#x39E; |
|Greek capital letter omicron |&amp;Omicron; |&amp;#927; |&amp;#x39F; |&Omicron; |&#927; |&#x39F; |
|Greek capital letter pi |&amp;Pi; |&amp;#928; |&amp;#x3A0; |&Pi; |&#928; |&#x3A0; |
|Greek capital letter rho |&amp;Rho; |&amp;#929; |&amp;#x3A1; |&Rho; |&#929; |&#x3A1; |
|Greek capital letter sigma |&amp;Sigma; |&amp;#931; |&amp;#x3A3; |&Sigma; |&#931; |&#x3A3; |
|Greek capital letter tau |&amp;Tau; |&amp;#932; |&amp;#x3A4; |&Tau; |&#932; |&#x3A4; |
|Greek capital letter upsilon |&amp;Upsilon; |&amp;#933; |&amp;#x3A5; |&Upsilon; |&#933; |&#x3A5; |
|Greek capital letter phi |&amp;Phi; |&amp;#934; |&amp;#x3A6; |&Phi; |&#934; |&#x3A6; |
|Greek capital letter chi |&amp;Chi; |&amp;#935; |&amp;#x3A7; |&Chi; |&#935; |&#x3A7; |
|Greek capital letter psi |&amp;Psi; |&amp;#936; |&amp;#x3A8; |&Psi; |&#936; |&#x3A8; |
|Greek capital letter omega |&amp;Omega; |&amp;#937; |&amp;#x3A9; |&Omega; |&#937; |&#x3A9; |
|Greek small letter alpha |&amp;alpha; |&amp;#945; |&amp;#x3B1; |&alpha; |&#945; |&#x3B1; |
|Greek small letter beta |&amp;beta; |&amp;#946; |&amp;#x3B2; |&beta; |&#946; |&#x3B2; |
|Greek small letter gamma |&amp;gamma; |&amp;#947; |&amp;#x3B3; |&gamma; |&#947; |&#x3B3; |
|Greek small letter delta |&amp;delta; |&amp;#948; |&amp;#x3B4; |&delta; |&#948; |&#x3B4; |
|Greek small letter epsilon |&amp;epsilon; |&amp;#949; |&amp;#x3B5; |&epsilon; |&#949; |&#x3B5; |
|Greek small letter zeta |&amp;zeta; |&amp;#950; |&amp;#x3B6; |&zeta; |&#950; |&#x3B6; |
|Greek small letter eta |&amp;eta; |&amp;#951; |&amp;#x3B7; |&eta; |&#951; |&#x3B7; |
|Greek small letter theta |&amp;theta; |&amp;#952; |&amp;#x3B8; |&theta; |&#952; |&#x3B8; |
|Greek small letter iota |&amp;iota; |&amp;#953; |&amp;#x3B9; |&iota; |&#953; |&#x3B9; |
|Greek small letter kappa |&amp;kappa; |&amp;#954; |&amp;#x3BA; |&kappa; |&#954; |&#x3BA; |
|Greek small letter lambda |&amp;lambda; |&amp;#955; |&amp;#x3BB; |&lambda; |&#955; |&#x3BB; |
|Greek small letter mu |&amp;mu; |&amp;#956; |&amp;#x3BC; |&mu; |&#956; |&#x3BC; |
|Greek small letter nu |&amp;nu; |&amp;#957; |&amp;#x3BD; |&nu; |&#957; |&#x3BD; |
|Greek small letter xi |&amp;xi; |&amp;#958; |&amp;#x3BE; |&xi; |&#958; |&#x3BE; |
|Greek small letter omicron |&amp;omicron; |&amp;#959; |&amp;#x3BF; |&omicron; |&#959; |&#x3BF; |
|Greek small letter pi |&amp;pi; |&amp;#960; |&amp;#x3C0; |&pi; |&#960; |&#x3C0; |
|Greek small letter rho |&amp;rho; |&amp;#961; |&amp;#x3C1; |&rho; |&#961; |&#x3C1; |
|Greek small letter final sigma |&amp;sigmaf; |&amp;#962; |&amp;#x3C2; |&sigmaf; |&#962; |&#x3C2; |
|Greek small letter sigma |&amp;sigma; |&amp;#963; |&amp;#x3C3; |&sigma; |&#963; |&#x3C3; |
|Greek small letter tau |&amp;tau; |&amp;#964; |&amp;#x3C4; |&tau; |&#964; |&#x3C4; |
|Greek small letter upsilon |&amp;upsilon; |&amp;#965; |&amp;#x3C5; |&upsilon; |&#965; |&#x3C5; |
|Greek small letter phi |&amp;phi; |&amp;#966; |&amp;#x3C6; |&phi; |&#966; |&#x3C6; |
|Greek small letter chi |&amp;chi; |&amp;#967; |&amp;#x3C7; |&chi; |&#967; |&#x3C7; |
|Greek small letter psi |&amp;psi; |&amp;#968; |&amp;#x3C8; |&psi; |&#968; |&#x3C8; |
|Greek small letter omega |&amp;omega; |&amp;#969; |&amp;#x3C9; |&omega; |&#969; |&#x3C9; |
|Greek small letter theta symbol |&amp;thetasym; |&amp;#977; |&amp;#x3D1; |&thetasym; |&#977; |&#x3D1; |
|Greek upsilon with hook symbol |&amp;upsih; |&amp;#978; |&amp;#x3D2; |&upsih; |&#978; |&#x3D2; |
|Greek pi symbol |&amp;piv; |&amp;#982; |&amp;#x3D6; |&piv; |&#982; |&#x3D6; |
|bullet = black small circle |&amp;bull; |&amp;#8226; |&amp;#x2022; |&bull; |&#8226; |&#x2022; |
|horizontal ellipsis = three dot leader |&amp;hellip; |&amp;#8230; |&amp;#x2026; |&hellip; |&#8230; |&#x2026; |
|prime = minutes = feet |&amp;prime; |&amp;#8242; |&amp;#x2032; |&prime; |&#8242; |&#x2032; |
|double prime = seconds = inches |&amp;Prime; |&amp;#8243; |&amp;#x2033; |&Prime; |&#8243; |&#x2033; |
|overline = spacing overscore |&amp;oline; |&amp;#8254; |&amp;#x203E; |&oline; |&#8254; |&#x203E; |
|fraction slash |&amp;frasl; |&amp;#8260; |&amp;#x2044; |&frasl; |&#8260; |&#x2044; |
|script capital P = power set = Weierstrass p |&amp;weierp; |&amp;#8472; |&amp;#x2118; |&weierp; |&#8472; |&#x2118; |
|blackletter capital I = imaginary part |&amp;image; |&amp;#8465; |&amp;#x2111; |&image; |&#8465; |&#x2111; |
|blackletter capital R = real part symbol |&amp;real; |&amp;#8476; |&amp;#x211C; |&real; |&#8476; |&#x211C; |
|trade mark sign |&amp;trade; |&amp;#8482; |&amp;#x2122; |&trade; |&#8482; |&#x2122; |
|alef symbol = first transfinite cardinal |&amp;alefsym; |&amp;#8501; |&amp;#x2135; |&alefsym; |&#8501; |&#x2135; |
|leftwards arrow |&amp;larr; |&amp;#8592; |&amp;#x2190; |&larr; |&#8592; |&#x2190; |
|upwards arrow |&amp;uarr; |&amp;#8593; |&amp;#x2191; |&uarr; |&#8593; |&#x2191; |
|rightwards arrow |&amp;rarr; |&amp;#8594; |&amp;#x2192; |&rarr; |&#8594; |&#x2192; |
|downwards arrow |&amp;darr; |&amp;#8595; |&amp;#x2193; |&darr; |&#8595; |&#x2193; |
|left right arrow |&amp;harr; |&amp;#8596; |&amp;#x2194; |&harr; |&#8596; |&#x2194; |
|downwards arrow with corner leftwards = carriage return |&amp;crarr; |&amp;#8629; |&amp;#x21B5; |&crarr; |&#8629; |&#x21B5; |
|leftwards double arrow |&amp;lArr; |&amp;#8656; |&amp;#x21D0; |&lArr; |&#8656; |&#x21D0; |
|upwards double arrow |&amp;uArr; |&amp;#8657; |&amp;#x21D1; |&uArr; |&#8657; |&#x21D1; |
|rightwards double arrow |&amp;rArr; |&amp;#8658; |&amp;#x21D2; |&rArr; |&#8658; |&#x21D2; |
|downwards double arrow |&amp;dArr; |&amp;#8659; |&amp;#x21D3; |&dArr; |&#8659; |&#x21D3; |
|left right double arrow |&amp;hArr; |&amp;#8660; |&amp;#x21D4; |&hArr; |&#8660; |&#x21D4; |
|for all |&amp;forall; |&amp;#8704; |&amp;#x2200; |&forall; |&#8704; |&#x2200; |
|partial differential |&amp;part; |&amp;#8706; |&amp;#x2202; |&part; |&#8706; |&#x2202; |
|there exists |&amp;exist; |&amp;#8707; |&amp;#x2203; |&exist; |&#8707; |&#x2203; |
|empty set = null set = diameter |&amp;empty; |&amp;#8709; |&amp;#x2205; |&empty; |&#8709; |&#x2205; |
|nabla = backward difference |&amp;nabla; |&amp;#8711; |&amp;#x2207; |&nabla; |&#8711; |&#x2207; |
|element of |&amp;isin; |&amp;#8712; |&amp;#x2208; |&isin; |&#8712; |&#x2208; |
|not an element of |&amp;notin; |&amp;#8713; |&amp;#x2209; |&notin; |&#8713; |&#x2209; |
|contains as member |&amp;ni; |&amp;#8715; |&amp;#x220B; |&ni; |&#8715; |&#x220B; |
|n-ary product = product sign |&amp;prod; |&amp;#8719; |&amp;#x220F; |&prod; |&#8719; |&#x220F; |
|n-ary sumation |&amp;sum; |&amp;#8721; |&amp;#x2211; |&sum; |&#8721; |&#x2211; |
|minus sign |&amp;minus; |&amp;#8722; |&amp;#x2212; |&minus; |&#8722; |&#x2212; |
|asterisk operator |&amp;lowast; |&amp;#8727; |&amp;#x2217; |&lowast; |&#8727; |&#x2217; |
|square root = radical sign |&amp;radic; |&amp;#8730; |&amp;#x221A; |&radic; |&#8730; |&#x221A; |
|proportional to |&amp;prop; |&amp;#8733; |&amp;#x221D; |&prop; |&#8733; |&#x221D; |
|infinity |&amp;infin; |&amp;#8734; |&amp;#x221E; |&infin; |&#8734; |&#x221E; |
|angle |&amp;ang; |&amp;#8736; |&amp;#x2220; |&ang; |&#8736; |&#x2220; |
|logical and = wedge |&amp;and; |&amp;#8743; |&amp;#x2227; |&and; |&#8743; |&#x2227; |
|logical or = vee |&amp;or; |&amp;#8744; |&amp;#x2228; |&or; |&#8744; |&#x2228; |
|intersection = cap |&amp;cap; |&amp;#8745; |&amp;#x2229; |&cap; |&#8745; |&#x2229; |
|union = cup |&amp;cup; |&amp;#8746; |&amp;#x222A; |&cup; |&#8746; |&#x222A; |
|integral |&amp;int; |&amp;#8747; |&amp;#x222B; |&int; |&#8747; |&#x222B; |
|therefore |&amp;there4; |&amp;#8756; |&amp;#x2234; |&there4; |&#8756; |&#x2234; |
|tilde operator = varies with = similar to |&amp;sim; |&amp;#8764; |&amp;#x223C; |&sim; |&#8764; |&#x223C; |
|approximately equal to |&amp;cong; |&amp;#8773; |&amp;#x2245; |&cong; |&#8773; |&#x2245; |
|almost equal to = asymptotic to |&amp;asymp; |&amp;#8776; |&amp;#x2248; |&asymp; |&#8776; |&#x2248; |
|not equal to |&amp;ne; |&amp;#8800; |&amp;#x2260; |&ne; |&#8800; |&#x2260; |
|identical to |&amp;equiv; |&amp;#8801; |&amp;#x2261; |&equiv; |&#8801; |&#x2261; |
|less-than or equal to |&amp;le; |&amp;#8804; |&amp;#x2264; |&le; |&#8804; |&#x2264; |
|greater-than or equal to |&amp;ge; |&amp;#8805; |&amp;#x2265; |&ge; |&#8805; |&#x2265; |
|subset of |&amp;sub; |&amp;#8834; |&amp;#x2282; |&sub; |&#8834; |&#x2282; |
|superset of |&amp;sup; |&amp;#8835; |&amp;#x2283; |&sup; |&#8835; |&#x2283; |
|not a subset of |&amp;nsub; |&amp;#8836; |&amp;#x2284; |&nsub; |&#8836; |&#x2284; |
|subset of or equal to |&amp;sube; |&amp;#8838; |&amp;#x2286; |&sube; |&#8838; |&#x2286; |
|superset of or equal to |&amp;supe; |&amp;#8839; |&amp;#x2287; |&supe; |&#8839; |&#x2287; |
|circled plus = direct sum |&amp;oplus; |&amp;#8853; |&amp;#x2295; |&oplus; |&#8853; |&#x2295; |
|circled times = vector product |&amp;otimes; |&amp;#8855; |&amp;#x2297; |&otimes; |&#8855; |&#x2297; |
|up tack = orthogonal to = perpendicular |&amp;perp; |&amp;#8869; |&amp;#x22A5; |&perp; |&#8869; |&#x22A5; |
|dot operator |&amp;sdot; |&amp;#8901; |&amp;#x22C5; |&sdot; |&#8901; |&#x22C5; |
|left ceiling = APL upstile |&amp;lceil; |&amp;#8968; |&amp;#x2308; |&lceil; |&#8968; |&#x2308; |
|right ceiling |&amp;rceil; |&amp;#8969; |&amp;#x2309; |&rceil; |&#8969; |&#x2309; |
|left floor = APL downstile |&amp;lfloor; |&amp;#8970; |&amp;#x230A; |&lfloor; |&#8970; |&#x230A; |
|right floor |&amp;rfloor; |&amp;#8971; |&amp;#x230B; |&rfloor; |&#8971; |&#x230B; |
|left-pointing angle bracket = bra |&amp;lang; |&amp;#9001; |&amp;#x2329; |&lang; |&#9001; |&#x2329; |
|right-pointing angle bracket = ket |&amp;rang; |&amp;#9002; |&amp;#x232A; |&rang; |&#9002; |&#x232A; |
|lozenge |&amp;loz; |&amp;#9674; |&amp;#x25CA; |&loz; |&#9674; |&#x25CA; |
|black spade suit |&amp;spades; |&amp;#9824; |&amp;#x2660; |&spades; |&#9824; |&#x2660; |
|black club suit = shamrock |&amp;clubs; |&amp;#9827; |&amp;#x2663; |&clubs; |&#9827; |&#x2663; |
|black heart suit = valentine |&amp;hearts; |&amp;#9829; |&amp;#x2665; |&hearts; |&#9829; |&#x2665; |
|black diamond suit |&amp;diams; |&amp;#9830; |&amp;#x2666; |&diams; |&#9830; |&#x2666; |

!Special Entities

The following table gives the character entity reference, decimal character reference, and hexadecimal character reference for markup-significant and internationalization characters, as well as the rendering of each in your browser. [[Glyphs|http://www.unicode.org/charts/]] of the characters are available at the [[Unicode Consortium|http://www.unicode.org/]].

With the exception of [[HTML 2.0|http://www.w3.org/MarkUp/html-spec/]]'s ''&amp;quot;'', ''&amp;amp;'', ''&amp;lt;'', and ''&amp;gt;'', these entities are all new in HTML 4.0 and may not be supported by old browsers. Support in recent browsers is good.

| !Character | !Entity | !Decimal | !Hex |>|>| !Rendering in Your Browser |
|~|~|~|~| !Entity | !Decimal | !Hex |
|quotation mark = APL quote |&amp;quot; |&amp;#34; |&amp;#x22; |&quot; |&#34; |&#x22; |
|ampersand |&amp;amp; |&amp;#38; |&amp;#x26; |&amp; |&#38; |&#x26; |
|less-than sign |&amp;lt; |&amp;#60; |&amp;#x3C; |&lt; |&#60; |&#x3C; |
|greater-than sign |&amp;gt; |&amp;#62; |&amp;#x3E; |&gt; |&#62; |&#x3E; |
|Latin capital ligature OE |&amp;OElig; |&amp;#338; |&amp;#x152; |&OElig; |&#338; |&#x152; |
|Latin small ligature oe |&amp;oelig; |&amp;#339; |&amp;#x153; |&oelig; |&#339; |&#x153; |
|Latin capital letter S with caron |&amp;Scaron; |&amp;#352; |&amp;#x160; |&Scaron; |&#352; |&#x160; |
|Latin small letter s with caron |&amp;scaron; |&amp;#353; |&amp;#x161; |&scaron; |&#353; |&#x161; |
|Latin capital letter Y with diaeresis |&amp;Yuml; |&amp;#376; |&amp;#x178; |&Yuml; |&#376; |&#x178; |
|modifier letter circumflex accent |&amp;circ; |&amp;#710; |&amp;#x2C6; |&circ; |&#710; |&#x2C6; |
|small tilde |&amp;tilde; |&amp;#732; |&amp;#x2DC; |&tilde; |&#732; |&#x2DC; |
|en space |&amp;ensp; |&amp;#8194; |&amp;#x2002; |&ensp; |&#8194; |&#x2002; |
|em space |&amp;emsp; |&amp;#8195; |&amp;#x2003; |&emsp; |&#8195; |&#x2003; |
|thin space |&amp;thinsp; |&amp;#8201; |&amp;#x2009; |&thinsp; |&#8201; |&#x2009; |
|zero width non-joiner |&amp;zwnj; |&amp;#8204; |&amp;#x200C; |&zwnj; |&#8204; |&#x200C; |
|zero width joiner |&amp;zwj; |&amp;#8205; |&amp;#x200D; |&zwj; |&#8205; |&#x200D; |
|left-to-right mark |&amp;lrm; |&amp;#8206; |&amp;#x200E; |&lrm; |&#8206; |&#x200E; |
|right-to-left mark |&amp;rlm; |&amp;#8207; |&amp;#x200F; |&rlm; |&#8207; |&#x200F; |
|en dash |&amp;ndash; |&amp;#8211; |&amp;#x2013; |&ndash; |&#8211; |&#x2013; |
|em dash |&amp;mdash; |&amp;#8212; |&amp;#x2014; |&mdash; |&#8212; |&#x2014; |
|left single quotation mark |&amp;lsquo; |&amp;#8216; |&amp;#x2018; |&lsquo; |&#8216; |&#x2018; |
|right single quotation mark |&amp;rsquo; |&amp;#8217; |&amp;#x2019; |&rsquo; |&#8217; |&#x2019; |
|single low-9 quotation mark |&amp;sbquo; |&amp;#8218; |&amp;#x201A; |&sbquo; |&#8218; |&#x201A; |
|left double quotation mark |&amp;ldquo; |&amp;#8220; |&amp;#x201C; |&ldquo; |&#8220; |&#x201C; |
|right double quotation mark |&amp;rdquo; |&amp;#8221; |&amp;#x201D; |&rdquo; |&#8221; |&#x201D; |
|double low-9 quotation mark |&amp;bdquo; |&amp;#8222; |&amp;#x201E; |&bdquo; |&#8222; |&#x201E; |
|dagger |&amp;dagger; |&amp;#8224; |&amp;#x2020; |&dagger; |&#8224; |&#x2020; |
|double dagger |&amp;Dagger; |&amp;#8225; |&amp;#x2021; |&Dagger; |&#8225; |&#x2021; |
|per mille sign |&amp;permil; |&amp;#8240; |&amp;#x2030; |&permil; |&#8240; |&#x2030; |
|single left-pointing angle quotation mark |&amp;lsaquo; |&amp;#8249; |&amp;#x2039; |&lsaquo; |&#8249; |&#x2039; |
|single right-pointing angle quotation mark |&amp;rsaquo; |&amp;#8250; |&amp;#x203A; |&rsaquo; |&#8250; |&#x203A; |
|euro sign |&amp;euro; |&amp;#8364; |&amp;#x20AC; |&euro; |&#8364; |&#x20AC; |
<<tiddler ContactsFormTemplate>><data>{"first.name":"Frank","last.name":"Herbert","birthday":"10-08"}</data>
/***
|Name:|HideWhenPlugin|
|Description:|Allows conditional inclusion/exclusion in templates|
|Version:|3.1 ($Rev: 3919 $)|
|Date:|$Date: 2013/07/07 09:04:17 $|
|Source:|http://mptw.tiddlyspot.com/#HideWhenPlugin|
|Author:|Simon Baird <simon.baird@gmail.com>|
|License:|http://mptw.tiddlyspot.com/#TheBSDLicense|
For use in ViewTemplate and EditTemplate. Example usage:
{{{<div macro="showWhenTagged Task">[[TaskToolbar]]</div>}}}
{{{<div macro="showWhen tiddler.modifier == 'BartSimpson'"><img src="bart.gif"/></div>}}}
***/
//{{{

window.hideWhenLastTest = false;

window.removeElementWhen = function(test,place) {
	window.hideWhenLastTest = test;
	if (test) {
		removeChildren(place);
		place.parentNode.removeChild(place);
	}
};


merge(config.macros,{

	hideWhen: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( eval(paramString), place);
	}},

	showWhen: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !eval(paramString), place);
	}},

	hideWhenTagged: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAll(params), place);
	}},

	showWhenTagged: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAll(params), place);
	}},

	hideWhenTaggedAny: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAny(params), place);
	}},

	showWhenTaggedAny: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAny(params), place);
	}},

	hideWhenTaggedAll: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.tags.containsAll(params), place);
	}},

	showWhenTaggedAll: { handler: function (place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !tiddler.tags.containsAll(params), place);
	}},

	hideWhenExists: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( store.tiddlerExists(params[0]) || store.isShadowTiddler(params[0]), place);
	}},

	showWhenExists: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !(store.tiddlerExists(params[0]) || store.isShadowTiddler(params[0])), place);
	}},

	hideWhenTitleIs: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.title == params[0], place);
	}},

	showWhenTitleIs: { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( tiddler.title != params[0], place);
	}},

	'else': { handler: function(place,macroName,params,wikifier,paramString,tiddler) {
		removeElementWhen( !window.hideWhenLastTest, place);
	}}

});

//}}}

/***
|''Name:''|HistoryPlugin|
|''Description:''|Limits to only one tiddler open. Manages an history stack and provides macro to navigate in this history (<<history>><<back>><<forward>>).|
|''Version:''|1.0.0|
|''Date:''|2008-03-23|
|''Source:''|http://tiddlywiki.bidix.info/#HistoryPlugin|
|''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.3.0|
***/
//{{{
	Story.prototype.tiddlerHistory = [];
	Story.prototype.historyCurrentPos = -1;
	Story.prototype.currentTiddler = null;
	Story.prototype.maxPos = 11;

	Story.prototype.old_history_displayTiddler = Story.prototype.displayTiddler;
	Story.prototype.displayTiddler = function(srcElement,title,template,animate,slowly)
	{
		title = ((typeof title === "string") ? title : title.title);
		//SinglePageMode
		if (this.currentTiddler) this.closeTiddler(this.currentTiddler);
		if (template == 2) {
			//switch to Edit mode : don't manage
			story.old_history_displayTiddler(null,title,template,animate,slowly);
			return; 
		}
		// if same tiddler no change
		if (this.tiddlerHistory[this.historyCurrentPos] == title) {
			this.currentTiddler = title;
			story.old_history_displayTiddler(null,title,template,animate,slowly);
			return;
		}
		if (this.historyCurrentPos == this.tiddlerHistory.length -1) {
			// bottom of stack
	    	this.tiddlerHistory.push(title);
		   	if (this.tiddlerHistory.length > 11) {
	                 this.tiddlerHistory.shift();
	       	} else {
		    this.historyCurrentPos += 1;
	            }

		} else {
			// middle of stack
		    this.historyCurrentPos += 1;
			if (this.tiddlerHistory[this.historyCurrentPos] != title) {
				// path change => cut history
				this.tiddlerHistory[this.historyCurrentPos] = title;
				var a = [];
				for(var i = 0; i <= this.historyCurrentPos;i++) {
					a[i] = this.tiddlerHistory[i];
				}
				this.tiddlerHistory = a;
			}
		}
		this.currentTiddler = title;
		story.old_history_displayTiddler(null,title,template,animate,true);
	        scrollTo(0, 1);
	}

	Story.prototype.old_history_closeTiddler = Story.prototype.closeTiddler;
	Story.prototype.closeTiddler = function(title,animate,slowly)
	{
		this.currentTiddler = null;
	    story.old_history_closeTiddler.apply(this,arguments);
	}

	config.macros.history = {};
	config.macros.history.action = function(event) {
	var popup = Popup.create(this);
		if(popup)
			{
	        if (!story.tiddlerHistory.length)
	            createTiddlyText(popup,"No history");
	        else
	           {
	           var c = story.tiddlerHistory.length;
			   for (i=0; i<c;i++ )
	               {
					var elmt = createTiddlyElement(popup,"li");
				   	var btn = createTiddlyButton(elmt,story.tiddlerHistory[i],story.tiddlerHistory[i],config.macros.history.onClick);
					btn.setAttribute("historyPos",i);
			       }
	           }
	        }
		Popup.show(popup,false);
		event.cancelBubble = true;
		if (event.stopPropagation) event.stopPropagation();
		return false;
	}
	config.macros.history.handler = function(place,macroName,params)
	{
		createTiddlyButton(place, 'history', 'history', config.macros.history.action);
	}

	config.macros.history.onClick = function(ev)
	{
		var e = ev ? ev : window.event;
		var historyPos = this.getAttribute("historyPos");
		story.historyCurrentPos = historyPos -1;
		story.displayTiddler(null,story.tiddlerHistory[historyPos]);
		return false;
	};

	config.macros.back = {};
	config.macros.back.action = function() {
	       if (story.historyCurrentPos > 0) {
				if (story.currentTiddler) story.closeTiddler(story.currentTiddler);
				story.historyCurrentPos = story.historyCurrentPos -2;
				story.displayTiddler(null,story.tiddlerHistory[story.historyCurrentPos+1]);
			} else {
				//if (story.currentTiddler) story.old_history_displayTiddler(null,story.currentTiddler);
				};
		return false;
	}
	config.macros.back.handler = function(place,macroName,params)
	{
		createTiddlyButton(place, '<', 'back', config.macros.back.action,"backButton");
	}

	config.macros.forward = {};
	config.macros.forward.action = function() {
	       if (story.historyCurrentPos < story.tiddlerHistory.length -1) {
				if (story.currentTiddler) story.closeTiddler(story.currentTiddler);
				//story.historyCurrentPos = story.historyCurrentPos;
				story.displayTiddler(null,story.tiddlerHistory[story.historyCurrentPos+1]);
			} else {
				//if (story.currentTiddler) story.old_history_displayTiddler(null,story.currentTiddler);
			}
		return false;
	}
	config.macros.forward.handler = function(place,macroName,params)
	{
		createTiddlyButton(place, '>', 'forward', config.macros.forward.action, "ibutton");
	}
//}}}
//{{{
config.macros.history.wrapMobileButton = function (buttonType, btn, place) {
	var textNode = btn.firstChild;
	btn.removeChild(textNode);
	var spanElement = document.createElement("span");
	jQuery(spanElement).addClass(buttonType + "Left");
	btn.appendChild(spanElement);
	spanElement = document.createElement("span");
	jQuery(spanElement).addClass(buttonType + "Middle");
	spanElement.appendChild(textNode);
	btn.appendChild(spanElement);
	spanElement = document.createElement("span");
	jQuery(spanElement).addClass(buttonType + "Right");
	btn.appendChild(spanElement);
	jQuery(btn).addClass(buttonType);
	if (place) {
		place.appendChild(btn);
	}
	return btn;
};

config.macros.history.handler = function(place,macroName,params) {
	config.macros.history.wrapMobileButton("mobileMenuButton", createTiddlyButton(null, 'history', 'history', config.macros.history.action), place);
}

config.macros.itwTag = {};
config.macros.itwTag.handler = function(place,macroName,params) {
	var btn = config.macros.history.wrapMobileButton("mobileMenuButton", createTagButton(null, params[0] ,null, params[1], params[2]), place);
	if (params[3]) {
		btn.setAttribute('sortby', params[3]);
	}
};

config.macros.back.handler = function(place,macroName,params) {
	config.macros.history.wrapMobileButton("mobileMenuBackButton", createTiddlyButton(null, '<', 'back', config.macros.back.action,"backButton"), place);
};

config.macros.forward.handler = function(place,macroName,params) {
	config.macros.history.wrapMobileButton("mobileMenuButton", createTiddlyButton(null, '>', 'forward', config.macros.forward.action, "ibutton"), place);
};

var code = eval("config.macros.toolbar.createCommand").toString();
var newCode = null;
var startPosition = code.indexOf('appendChild');
if (startPosition > -1) {
	var endPosition = code.indexOf(';', startPosition);
	if (endPosition > -1) {
		newCode = code.substring(0, startPosition) + 'appendChild(config.macros.history.wrapMobileButton("mobileButton", btn, null));' + code.substring(endPosition + 1);
		code = newCode;
	}
}
if (newCode != null) {
	eval("config.macros.toolbar.createCommand = function createCommand" + newCode.substring(newCode.indexOf("(")));
}
var code = eval("config.macros.toolbar.handler").toString();
var newCode = null;
var startPosition = 0;
for (var i = 0; (i < 2) && (startPosition > -1); i++)  {
	startPosition = code.indexOf('createTiddlyButton', startPosition);
	if (startPosition > -1) {
		var startParameterPosition = code.indexOf('place', startPosition);
		if (startParameterPosition > -1) {
			var endPosition = code.indexOf(';', startParameterPosition);
			if (endPosition > -1) {
				newCode = code.substring(0, startPosition) + 'config.macros.history.wrapMobileButton("mobileButton", createTiddlyButton(null' + code.substring(startParameterPosition + "place".length, endPosition) + ', place);' + code.substring(endPosition + 1);
				code = newCode;
				startPosition = code.indexOf('createTiddlyButton', startPosition) + 1;
			} else {
				startPosition = -1;
			}
		} else {
			startPosition = -1;
		}
	}
}
if (newCode != null) {
	eval("config.macros.toolbar.handler = function handler" + newCode.substring(newCode.indexOf("(")));
}
//}}}
//{{{
jQuery(window).bind('hashchange', function(e) {
	if (!jQuery.bbq.statePushed) {
		var state = jQuery.bbq.getState();
		var tiddlerTitle = null;
		for (var id in state) {
			if (id && (id != "") && (state[id] == "")) {
				tiddlerTitle = id;
				break;
			}
		}
		if (tiddlerTitle != null) {
			tiddlerTitle = tiddlerTitle.readBracketedList();
			if (tiddlerTitle.length > 0) {
				jQuery.bbq.isNavigating = true;
				story.displayTiddler(null, decodeURIComponent(tiddlerTitle[0]));
				jQuery.bbq.isNavigating = false;
			}
		}
	}
	jQuery.bbq.statePushed = false;
});

var code = eval("Story.prototype.displayTiddler").toString();
var newCode = null;
var startPosition = code.indexOf('this.tiddlerHistory.length');
if (startPosition > -1) {
	startPosition = code.lastIndexOf('if', startPosition);
	if (startPosition > -1) {
		newCode = code.substring(0, startPosition) + ' if (!jQuery.bbq.isNavigating) { jQuery.bbq.statePushed = true; jQuery.bbq.pushState("#" + encodeURIComponent(String.encodeTiddlyLink(title))); } ' + code.substring(startPosition);
		code = newCode;
	}
}
if (newCode != null) {
	eval("Story.prototype.displayTiddler = function displayTiddler" + newCode.substring(newCode.indexOf("(")));
}
//}}}
<<reminder month:1 day:1 leadtime:1 title:"@@background-color:#ffaace;priority:5;New Year's Day@@">>
<<reminder month:5 day:24 offsetdayofweek:-1 leadtime:1 title:"@@background-color:#ffaace;priority:5;Victoria Day@@">>
<<reminder month:6 day:24 leadtime:1 title:"@@background-color:#ffaace;priority:5;Fête nationale du Québec (Saint-Jean-Baptiste)@@">>
<<reminder month:7 day:1 leadtime:1 title:"@@background-color:#ffaace;priority:5;Canada Day@@">>
<<reminder month:9 day:1 offsetdayofweek:1 leadtime:1 title:"@@background-color:#ffaace;priority:5;Labour Day@@">>
<<reminder month:10 day:8 offsetdayofweek:1 leadtime:1 title:"@@background-color:#ffaace;priority:5;Thanksgiving Day@@">>
<<reminder month:12 day:25 leadtime:1 title:"@@background-color:#ffaace;priority:5;Christmas Day@@">>

<<reminder function:determineGoodFriday() leadtime:1 tag:"readOnlyReminder" title:"@@background-color:#ffaace;priority:5;Good Friday@@">>
<<reminder function:determineEasterSunday() leadtime:1 tag:"readOnlyReminder" title:"@@background-color:#ffaace;priority:5;Easter Sunday@@">>
<<reminder function:determineEasterMonday() leadtime:1 tag:"readOnlyReminder" title:"@@background-color:#ffaace;priority:5;Easter Monday@@">>
{{homeTitle{Empty iTW - a ~TiddlyWiki for iPhone}}}
http://www.tiddlywiki.com/#HtmlEntities

Entities in HTML documents allow characters to be entered that can't easily be typed on an ordinary keyboard. They take the form of an ampersand (&), an identifying string, and a terminating semi-colon (;). There's a complete reference [[here|HTML 4 Entities]]; some of the more common and useful ones are shown below.

|>|>|>|>|>|>| !HTML Entities |
| &amp;nbsp; | &nbsp; | no-break space | &nbsp;&nbsp; | &amp;apos; | &apos; | single quote, apostrophe |
| &amp;ndash; | &ndash; | en dash |~| &amp;quot; | " | quotation mark |
| &amp;mdash; | &mdash; | em dash |~| &amp;prime; | &prime; | prime; minutes; feet |
| &amp;hellip; | &hellip; |	horizontal ellipsis |~| &amp;Prime; | &Prime; | double prime; seconds; inches |
| &amp;copy; | &copy; | Copyright symbol |~| &amp;lsquo; | &lsquo; | left single quote |
| &amp;reg; | &reg; | Registered symbol |~| &amp;rsquo; | &rsquo; | right  single quote |
| &amp;trade; | &trade; | Trademark symbol |~| &amp;ldquo; | &ldquo; | left double quote |
| &amp;dagger; | &dagger; | dagger |~| &amp;rdquo; | &rdquo; | right double quote |
| &amp;Dagger; | &Dagger; | double dagger |~| &amp;laquo; | &laquo; | left angle quote |
| &amp;para; | &para; | paragraph sign |~| &amp;raquo; | &raquo; | right angle quote |
| &amp;sect; | &sect; | section sign |~| &amp;times; | &times; | multiplication symbol |
| &amp;uarr; | &uarr; | up arrow |~| &amp;darr; | &darr; | down arrow |
| &amp;larr; | &larr; | left arrow |~| &amp;rarr; | &rarr; | right arrow |
| &amp;lArr; | &lArr; | double left arrow |~| &amp;rArr; | &rArr; | double right arrow |
| &amp;harr; | &harr; | left right arrow |~| &amp;hArr; | &hArr; | double left right arrow |

The table below shows how accented characters can be built up by subsituting a base character into the various accent entities in place of the underscore ('_'):

|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>| !Accented Characters |
| grave accent | &amp;_grave; | &Agrave; | &agrave; | &Egrave; | &egrave; | &Igrave; | &igrave; | &Ograve; | &ograve; | &Ugrave; | &ugrave; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| acute accent | &amp;_acute; | &Aacute; | &aacute; | &Eacute; | &eacute; | &Iacute; | &iacute; | &Oacute; | &oacute; | &Uacute; | &uacute; | &nbsp; | &nbsp; | &Yacute; | &yacute; | &nbsp; | &nbsp; |
| circumflex accent | &amp;_circ; | &Acirc; | &acirc; | &Ecirc; | &ecirc; | &Icirc; | &icirc; | &Ocirc; | &ocirc; | &Ucirc; | &ucirc; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| umlaut mark | &amp;_uml; | &Auml; | &auml; |  &Euml; | &euml; | &Iuml; | &iuml; | &Ouml; | &ouml; | &Uuml; | &uuml; | &nbsp; | &nbsp; | &Yuml; | &yuml; | &nbsp; | &nbsp; |
| tilde | &amp;_tilde; | &Atilde; | &atilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Otilde; | &otilde; | &nbsp; | &nbsp; | &Ntilde; | &ntilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| ring | &amp;_ring; | &Aring; | &aring; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| slash | &amp;_slash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Oslash; | &oslash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |
| cedilla | &amp;_cedil; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Ccedil; | &ccedil; |
!Simple Images
{{{
[img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
}}}
Displays as:
[img[fractalveg.jpg]]
!Tooltips for Images
{{{
[img[tooltip|http://wikitext.tiddlyspace.com/fractalveg.jpg]]
}}}
Displays as:
[img[tooltip|fractalveg.jpg]]
!Image Links
{{{
[img[http://wikitext.tiddlyspace.com/fractalveg.jpg][http://www.flickr.com/photos/jermy/10134618/]]
}}}
Displays as:
[img[fractalveg.jpg][http://www.flickr.com/photos/jermy/10134618/]]
!Floating Images with Text
{{{
[<img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
}}}
[<img[fractalveg.jpg]]Displays as.
{{{
@@clear:both;display:block; @@
}}}
Will then clear the float.
@@clear:both;display:block;Like this@@
{{{
[>img[http://wikitext.tiddlyspace.com/fractalveg.jpg]]
}}}
[>img[fractalveg.jpg]]Displays as.@@clear:both;display:block; @@
!See Also
[[Image Macro]]
//{{{
if (config.options.chkPasteImages === undefined) {
	config.options.chkPasteImages = true;
}
merge(config.optionsDesc, {
	chkPasteImages: "Enable image pasting when editing tiddlers"
});
//}}}
//{{{
config.macros.imagePaste = {
	handler: function (place, macroName, params, wikifier, paramString, tiddler) {
		if (config.options.chkPasteImages) {
			var editTextarea = null;
			if (paramString) {
				try {
					editTextarea = eval(paramString);
				} catch (e) {}
			}
			if (!editTextarea) {
				editTextarea = jQuery(place).closest("[template='EditTemplate']").find("textarea").eq(0);
			}
			if (jQuery.isFunction(editTextarea.pastableTextarea)) {
				var insertAtCaret = function (txtarea, text) {
					//http://stackoverflow.com/questions/1064089/inserting-a-text-where-cursor-is-using-javascript-jquery
					var scrollPos = txtarea.scrollTop;
					var caretPos = txtarea.selectionStart;
					
					var front = (txtarea.value).substring(0, caretPos);
					var back = (txtarea.value).substring(txtarea.selectionEnd, txtarea.value.length);
					txtarea.value = front + text + back;
					caretPos = caretPos + text.length;
					txtarea.selectionStart = caretPos;
					txtarea.selectionEnd = caretPos;
					txtarea.focus();
					txtarea.scrollTop = scrollPos;
				};
				
				editTextarea.pastableTextarea();
				
				editTextarea.on("pasteImage", function(e, data) {
					insertAtCaret(this, "[img(" + data.width + "px+,)[" + data.dataURL + "]]");
				}).on("pasteText", function(e, data) {
					insertAtCaret(this, data.text);
				});
			}
		}
	}
}
//}}}
/***
|Name|ImageSizePlugin|
|Source|http://www.TiddlyTools.com/#ImageSizePlugin|
|Version|1.2.3|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|adds support for resizing images|
This plugin adds optional syntax to scale an image to a specified width and height and/or interactively resize the image with the mouse.
!!!!!Usage
<<<
The extended image syntax is:
{{{
[img(w+,h+)[...][...]]
}}}
where ''(w,h)'' indicates the desired width and height (in CSS units, e.g., px, em, cm, in, or %). Use ''auto'' (or a blank value) for either dimension to scale that dimension proportionally (i.e., maintain the aspect ratio). You can also calculate a CSS value 'on-the-fly' by using a //javascript expression// enclosed between """{{""" and """}}""". Appending a plus sign (+) to a dimension enables interactive resizing in that dimension (by dragging the mouse inside the image). Use ~SHIFT-click to show the full-sized (un-scaled) image. Use ~CTRL-click to restore the starting size (either scaled or full-sized).
<<<
!!!!!Examples
<<<
{{{
[img(100px+,75px+)[images/meow2.jpg]]
}}}
[img(100px+,75px+)[images/meow2.jpg]]
{{{
[<img(34%+,+)[images/meow.gif]]
[<img(21% ,+)[images/meow.gif]]
[<img(13%+, )[images/meow.gif]]
[<img( 8%+, )[images/meow.gif]]
[<img( 5% , )[images/meow.gif]]
[<img( 3% , )[images/meow.gif]]
[<img( 2% , )[images/meow.gif]]
[img(  1%+,+)[images/meow.gif]]
}}}
[<img(34%+,+)[images/meow.gif]]
[<img(21% ,+)[images/meow.gif]]
[<img(13%+, )[images/meow.gif]]
[<img( 8%+, )[images/meow.gif]]
[<img( 5% , )[images/meow.gif]]
[<img( 3% , )[images/meow.gif]]
[<img( 2% , )[images/meow.gif]]
[img(  1%+,+)[images/meow.gif]]
{{tagClear{
}}}
<<<
!!!!!Revisions
<<<
2011.09.03 [1.2.3] bypass addStretchHandlers() if no '+' suffix is used (i.e., not resizable)
2010.07.24 [1.2.2] moved tip/dragtip text to config.formatterHelpers.imageSize object to enable customization
2009.02.24 [1.2.1] cleanup width/height regexp, use '+' suffix for resizing
2009.02.22 [1.2.0] added stretchable images
2008.01.19 [1.1.0] added evaluated width/height values
2008.01.18 [1.0.1] regexp for "(width,height)" now passes all CSS values to browser for validation
2008.01.17 [1.0.0] initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.ImageSizePlugin= {major: 1, minor: 2, revision: 3, date: new Date(2011,9,3)};
//}}}
//{{{
var f=config.formatters[config.formatters.findByField("name","image")];
f.match="\\[[<>]?[Ii][Mm][Gg](?:\\([^,]*,[^\\)]*\\))?\\[";
f.lookaheadRegExp=/\[([<]?)(>?)[Ii][Mm][Gg](?:\(([^,]*),([^\)]*)\))?\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg;
f.handler=function(w) {
	this.lookaheadRegExp.lastIndex = w.matchStart;
	var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
	if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
		var floatLeft=lookaheadMatch[1];
		var floatRight=lookaheadMatch[2];
		var width=lookaheadMatch[3];
		var height=lookaheadMatch[4];
		var tooltip=lookaheadMatch[5];
		var src=lookaheadMatch[6];
		var link=lookaheadMatch[7];

		// Simple bracketted link
		var e = w.output;
		if(link) { // LINKED IMAGE
			if (config.formatterHelpers.isExternalLink(link)) {
				if (config.macros.attach && config.macros.attach.isAttachment(link)) {
					// see [[AttachFilePluginFormatters]]
					e = createExternalLink(w.output,link);
					e.href=config.macros.attach.getAttachment(link);
					e.title = config.macros.attach.linkTooltip + link;
				} else
					e = createExternalLink(w.output,link);
			} else 
				e = createTiddlyLink(w.output,link,false,null,w.isStatic);
			addClass(e,"imageLink");
		}

		var img = createTiddlyElement(e,"img");
		if(floatLeft) img.align="left"; else if(floatRight) img.align="right";
		if(width||height) {
			var x=width.trim(); var y=height.trim();
			var stretchW=(x.substr(x.length-1,1)=='+'); if (stretchW) x=x.substr(0,x.length-1);
			var stretchH=(y.substr(y.length-1,1)=='+'); if (stretchH) y=y.substr(0,y.length-1);
			if (x.substr(0,2)=="{{")
				{ try{x=eval(x.substr(2,x.length-4))} catch(e){displayMessage(e.description||e.toString())} }
			if (y.substr(0,2)=="{{")
				{ try{y=eval(y.substr(2,y.length-4))} catch(e){displayMessage(e.description||e.toString())} }
			img.style.width=x.trim(); img.style.height=y.trim();
			if (stretchW||stretchH) config.formatterHelpers.addStretchHandlers(img,stretchW,stretchH);
		}
		if(tooltip) img.title = tooltip;

		// GET IMAGE SOURCE
		if (config.macros.attach && config.macros.attach.isAttachment(src))
			src=config.macros.attach.getAttachment(src); // see [[AttachFilePluginFormatters]]
		else if (config.formatterHelpers.resolvePath) { // see [[ImagePathPlugin]]
			if (config.browser.isIE || config.browser.isSafari) {
				img.onerror=(function(){
					this.src=config.formatterHelpers.resolvePath(this.src,false);
					return false;
				});
			} else
				src=config.formatterHelpers.resolvePath(src,true);
		}
		img.src=src;
		w.nextMatch = this.lookaheadRegExp.lastIndex;
	}
}

config.formatterHelpers.imageSize={
	tip: 'SHIFT-CLICK=show full size, CTRL-CLICK=restore initial size',
	dragtip: 'DRAG=stretch/shrink, '
}

config.formatterHelpers.addStretchHandlers=function(e,stretchW,stretchH) {
	e.title=((stretchW||stretchH)?this.imageSize.dragtip:'')+this.imageSize.tip;
	e.statusMsg='width=%0, height=%1';
	e.style.cursor='move';
	e.originalW=e.style.width;
	e.originalH=e.style.height;
	e.minW=Math.max(e.offsetWidth/20,10);
	e.minH=Math.max(e.offsetHeight/20,10);
	e.stretchW=stretchW;
	e.stretchH=stretchH;
	e.onmousedown=function(ev) { var ev=ev||window.event;
		this.sizing=true;
		this.startX=!config.browser.isIE?ev.pageX:(ev.clientX+findScrollX());
		this.startY=!config.browser.isIE?ev.pageY:(ev.clientY+findScrollY());
		this.startW=this.offsetWidth;
		this.startH=this.offsetHeight;
		return false;
	};
	e.onmousemove=function(ev) { var ev=ev||window.event;
		if (this.sizing) {
			var s=this.style;
			var currX=!config.browser.isIE?ev.pageX:(ev.clientX+findScrollX());
			var currY=!config.browser.isIE?ev.pageY:(ev.clientY+findScrollY());
			var newW=(currX-this.offsetLeft)/(this.startX-this.offsetLeft)*this.startW;
			var newH=(currY-this.offsetTop )/(this.startY-this.offsetTop )*this.startH;
			if (this.stretchW) s.width =Math.floor(Math.max(newW,this.minW))+'px';
			if (this.stretchH) s.height=Math.floor(Math.max(newH,this.minH))+'px';
			clearMessage(); displayMessage(this.statusMsg.format([s.width,s.height]));
		}
		return false;
	};
	e.onmouseup=function(ev) { var ev=ev||window.event;
		if (ev.shiftKey) { this.style.width=this.style.height=''; }
		if (ev.ctrlKey)  { this.style.width=this.originalW; this.style.height=this.originalH; }
		this.sizing=false;
		clearMessage();
		return false;
	};
	e.onmouseout=function(ev) { var ev=ev||window.event;
		this.sizing=false;
		clearMessage();
		return false;
	};
}
//}}}
/***
|Name|ImportTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#ImportTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#ImportTiddlersPluginInfo|
|Version|4.6.2|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|interactive controls for import/export with filtering.|
Combine tiddlers from any two TiddlyWiki documents.  Interactively select and copy tiddlers from another TiddlyWiki source document.  Includes prompting for skip, rename, merge or replace actions when importing tiddlers that match existing titles.  When done, a list of all imported tiddlers is written into [[ImportedTiddlers]].
!!!!!Documentation
<<<
see [[ImportTiddlersPluginInfo]] for details
<<<
!!!!!interactive control panel
<<<
<<importTiddlers inline>>
{{clear{
^^(see also: [[ImportTiddlers]] shadow tiddler)^^}}}
<<<
!!!!!Revisions
<<<
2011.02.14 4.6.2 fix OSX error: use picker.file.path
2009.10.10 4.6.1 in createImportPanel, Use {{{window.Components}}} instead of {{{config.browser.isGecko}}} to avoid applying FF3 'file browse' fixup in Chrome.
2009.10.06 4.6.0 added createTiddlerFromFile (import text files)
|please see [[ImportTiddlersPluginInfo]] for additional revision details|
2005.07.20 1.0.0 Initial Release
<<<
!!!!!Code
***/
//{{{
version.extensions.ImportTiddlersPlugin= {major: 4, minor: 6, revision: 2, date: new Date(2011,2,14)};

// IE needs explicit global scoping for functions/vars called from browser events
window.onClickImportButton=onClickImportButton;
window.refreshImportList=refreshImportList;

// default cookie/option values
if (!config.options.chkImportReport) config.options.chkImportReport=true;

// default shadow definition
config.shadowTiddlers.ImportTiddlers='<<importTiddlers inline>>';

// use shadow tiddler content in backstage panel
if (config.tasks) config.tasks.importTask.content='<<tiddler ImportTiddlers>>' // TW2.2 or above
//}}}
//{{{
// backward-compatiblity for TW2.0.x and TW1.2.x
if (config.macros.importTiddlers==undefined) config.macros.importTiddlers={};
if (typeof merge=='undefined') {
	function merge(dst,src,preserveExisting) {
		for(var i in src) { if(!preserveExisting || dst[i] === undefined) dst[i] = src[i]; }
		return dst;
	}
}
if (config.browser.isGecko===undefined)
	config.browser.isGecko=(config.userAgent.indexOf('gecko')!=-1);
//}}}
//{{{
merge(config.macros.importTiddlers,{
	$: function(id) { return document.getElementById(id); }, // abbreviation
	label: 'import tiddlers',
	prompt: 'Copy tiddlers from another document',
	openMsg: 'Opening %0',
	openErrMsg: 'Could not open %0 - error=%1',
	readMsg: 'Read %0 bytes from %1',
	foundMsg: 'Found %0 tiddlers in %1',
	filterMsg: "Filtered %0 tiddlers matching '%1'",
	summaryMsg: '%0 tiddler%1 in the list',
	summaryFilteredMsg: '%0 of %1 tiddler%2 in the list',
	plural: 's are',
	single: ' is',
	countMsg: '%0 tiddlers selected for import',
	processedMsg: 'Processed %0 tiddlers',
	importedMsg: 'Imported %0 of %1 tiddlers from %2',
	loadText: 'please load a document...',
	closeText: 'close',
	doneText: 'done',
	startText: 'import',
	stopText: 'stop',
	local: true,		// default to import from local file
	src: '',		// path/filename or URL of document to import (retrieved from SiteUrl)
	proxy: '',		// URL for remote proxy script (retrieved from SiteProxy)
	useProxy: false,	// use specific proxy script in front of remote URL
	inbound: null,		// hash-indexed array of tiddlers from other document
	newTags: '',		// text of tags added to imported tiddlers
	addTags: true,		// add new tags to imported tiddlers
	listsize: 10,		// # of lines to show in imported tiddler list
	importTags: true,	// include tags from remote source document when importing a tiddler
	keepTags: true,		// retain existing tags when replacing a tiddler
	sync: false,		// add 'server' fields to imported tiddlers (for sync function)
	lastFilter: '',		// most recent filter (URL hash) applied
	lastAction: null,	// most recent collision button performed
	index: 0,		// current processing index in import list
	sort: ''		// sort order for imported tiddler listbox
});
//}}}
//{{{
// hijack core macro handler
if (config.macros.importTiddlers.coreHandler==undefined)
	config.macros.importTiddlers.coreHandler=config.macros.importTiddlers.handler;

config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if (!params[0] || params[0].toLowerCase()=='core') { // default to built in
		if (config.macros.importTiddlers.coreHandler)
			config.macros.importTiddlers.coreHandler.apply(this,arguments);
		else 
			createTiddlyButton(place,this.label,this.prompt,onClickImportMenu);
	} else if (params[0]=='link') { // show link to floating panel
		createTiddlyButton(place,params[1]||this.label,params[2]||this.prompt,onClickImportMenu);
	} else if (params[0]=='inline') {// show panel as INLINE tiddler content
		createImportPanel(place);
		this.$('importPanel').style.position='static';
		this.$('importPanel').style.display='block';
	} else if (config.macros.loadTiddlers)
		config.macros.loadTiddlers.handler(place,macroName,params); // any other params: loadtiddlers
}
//}}}
//{{{
// Handle link click to create/show/hide control panel
function onClickImportMenu(e) { var e=e||window.event;
	var parent=resolveTarget(e).parentNode;
	var panel=document.getElementById('importPanel');
	if (panel==undefined || panel.parentNode!=parent) panel=createImportPanel(parent);
	var isOpen=panel.style.display=='block';
	if(config.options.chkAnimate)
		anim.startAnimating(new Slider(panel,!isOpen,false,'none'));
	else
		panel.style.display=isOpen?'none':'block';
	e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); return(false);
}
//}}}
//{{{
// Create control panel: HTML, CSS
function createImportPanel(place) {
	var cmi=config.macros.importTiddlers; // abbrev
	var panel=cmi.$('importPanel');
	if (panel) { panel.parentNode.removeChild(panel); }
	setStylesheet(store.getTiddlerText('ImportTiddlersPlugin##css'),'importTiddlers');
	panel=createTiddlyElement(place,'span','importPanel',null,null)
	panel.innerHTML=store.getTiddlerText('ImportTiddlersPlugin##html');
	refreshImportList();
	if (!cmi.src.length) cmi.src=store.getTiddlerText('SiteUrl')||'';
	cmi.$('importSourceURL').value=cmi.src;
	if (!cmi.proxy.length) cmi.proxy=store.getTiddlerText('SiteProxy')||'SiteProxy';
	cmi.$('importSiteProxy').value=cmi.proxy;
	if (window.Components) { // FF3 FIXUP
		cmi.$('fileImportSource').style.display='none';
		cmi.$('importLocalPanelFix').style.display='block';
	}
	cmi.$('chkSync').checked=cmi.sync;
	cmi.$('chkImportTags').checked=cmi.importTags;
	cmi.$('chkKeepTags').checked=cmi.keepTags;
	cmi.$('chkAddTags').checked=cmi.addTags;
	cmi.$('txtNewTags').value=cmi.newTags;
	cmi.$('txtNewTags').style.display=cmi.addTags?'block':'none';
	cmi.$('chkSync').checked=cmi.sync;
	cmi.$('chkImportReport').checked=config.options.chkImportReport;
	return panel;
}
//}}}
//{{{
// process control interactions
function onClickImportButton(which,event) {
	var cmi=config.macros.importTiddlers; // abbreviation
	var list=cmi.$('importList'); if (!list) return false;
	var thePanel=cmi.$('importPanel');
	var theCollisionPanel=cmi.$('importCollisionPanel');
	var theNewTitle=cmi.$('importNewTitle');
	var count=0;
	switch (which.id)
		{
		case 'importFromFile':	// show local panel
		case 'importFromWeb':	// show HTTP panel
			cmi.local=(which.id=='importFromFile');
			cmi.showPanel('importLocalPanel',cmi.local);
			cmi.showPanel('importHTTPPanel',!cmi.local);
			break;
		case 'importOptions':	// show/hide options panel
			cmi.showPanel('importOptionsPanel',cmi.$('importOptionsPanel').style.display=='none');
			break;
		case 'fileImportSource':
		case 'importLoad':		// load import source into hidden frame
			importReport();		// if an import was in progress, generate a report
			cmi.inbound=null;	// clear the imported tiddler buffer
			refreshImportList();	// reset/resize the listbox
			if (cmi.src=='') break;
			// Load document, read it's DOM and fill the list
			cmi.loadRemoteFile(cmi.src,cmi.filterTiddlerList);
			break;
		case 'importSelectFeed':	// select a pre-defined systemServer feed URL
			var p=Popup.create(which); if (!p) return false;
			var tids=store.getTaggedTiddlers('systemServer');
			if (!tids.length)
				createTiddlyText(createTiddlyElement(p,'li'),'no pre-defined server feeds');
			for (var t=0; t<tids.length; t++) {
				var u=store.getTiddlerSlice(tids[t].title,'URL');
				var d=store.getTiddlerSlice(tids[t].title,'Description');
				if (!d||!d.length) d=store.getTiddlerSlice(tids[t].title,'description');
				if (!d||!d.length) d=u;
				createTiddlyButton(createTiddlyElement(p,'li'),tids[t].title,d,
					function(){
						var u=this.getAttribute('url');
						document.getElementById('importSourceURL').value=u;
						config.macros.importTiddlers.src=u;
						document.getElementById('importLoad').onclick();
					},
					null,null,null,{url:u});
			}
			Popup.show();
			event.cancelBubble = true;
			if (event.stopPropagation) event.stopPropagation();
			return false;
			// create popup with feed list
			// onselect, insert feed URL into input field.
			break;
		case 'importSelectAll':		// select all tiddler list items (i.e., not headings)
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				if (list.options[t].value=='') continue;
				list.options[t].selected=true;
				count++;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectNew':		// select tiddlers not in current document
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value=='') continue;
				list.options[t].selected=!store.tiddlerExists(list.options[t].value);
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectChanges':		// select tiddlers that are updated from existing tiddlers
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value==''||!store.tiddlerExists(list.options[t].value)) continue;
				for (var i=0; i<cmi.inbound.length; i++) // find matching inbound tiddler
					{ var inbound=cmi.inbound[i]; if (inbound.title==list.options[t].value) break; }
				list.options[t].selected=(inbound.modified-store.getTiddler(list.options[t].value).modified>0); // updated tiddler
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importSelectDifferences':		// select tiddlers that are new or different from existing tiddlers
			importReport();		// if an import was in progress, generate a report
			for (var t=0,count=0; t < list.options.length; t++) {
				list.options[t].selected=false;
				if (list.options[t].value=='') continue;
				if (!store.tiddlerExists(list.options[t].value)) { list.options[t].selected=true; count++; continue; }
				for (var i=0; i<cmi.inbound.length; i++) // find matching inbound tiddler
					{ var inbound=cmi.inbound[i]; if (inbound.title==list.options[t].value) break; }
				list.options[t].selected=(inbound.modified-store.getTiddler(list.options[t].value).modified!=0); // changed tiddler
				count+=list.options[t].selected?1:0;
			}
			clearMessage(); displayMessage(cmi.countMsg.format([count]));
			cmi.$('importStart').disabled=!count;
			break;
		case 'importApplyFilter':	// filter list to include only matching tiddlers
			importReport();		// if an import was in progress, generate a report
			clearMessage();
			if (!cmi.all) // no tiddlers loaded = '0 selected'
				{ displayMessage(cmi.countMsg.format([0])); return false; }
			var hash=cmi.$('importLastFilter').value;
			cmi.inbound=cmi.filterByHash('#'+hash,cmi.all);
			refreshImportList();	// reset/resize the listbox
			break;
		case 'importStart':		// initiate the import processing
			importReport();		// if an import was in progress, generate a report
			cmi.$('importApplyToAll').checked=false;
			cmi.$('importStart').value=cmi.stopText;
			if (cmi.index>0) cmi.index=-1; // stop processing
			else cmi.index=importTiddlers(0); // or begin processing
			importStopped();
			break;
		case 'importClose':		// unload imported tiddlers or hide the import control panel
			// if imported tiddlers not loaded, close the import control panel
			if (!cmi.inbound) { thePanel.style.display='none'; break; }
			importReport();		// if an import was in progress, generate a report
			cmi.inbound=null;	// clear the imported tiddler buffer
			refreshImportList();	// reset/resize the listbox
			break;
		case 'importSkip':	// don't import the tiddler
			cmi.lastAction=which;
			var theItem	= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported = cmi.inbound[j];
			theImported.status='skipped after asking';			// mark item as skipped
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index+1);	// resume with NEXT item
			importStopped();
			break;
		case 'importRename':		// change name of imported tiddler
			cmi.lastAction=which;
			var theItem		= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported		= cmi.inbound[j];
			theImported.status	= 'renamed from '+theImported.title;	// mark item as renamed
			theImported.set(theNewTitle.value,null,null,null,null);		// change the tiddler title
			theItem.value		= theNewTitle.value;			// change the listbox item text
			theItem.text		= theNewTitle.value;			// change the listbox item text
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with THIS item
			importStopped();
			break;
		case 'importMerge':	// join existing and imported tiddler content
			cmi.lastAction=which;
			var theItem	= list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported	= cmi.inbound[j];
			var theExisting	= store.getTiddler(theItem.value);
			var theText	= theExisting.text+'\n----\n^^merged from: ';
			theText		+='[['+cmi.src+'#'+theItem.value+'|'+cmi.src+'#'+theItem.value+']]^^\n';
			theText		+='^^'+theImported.modified.toLocaleString()+' by '+theImported.modifier+'^^\n'+theImported.text;
			var theDate	= new Date();
			var theTags	= theExisting.getTags()+' '+theImported.getTags();
			theImported.set(null,theText,null,theDate,theTags);
			theImported.status   = 'merged with '+theExisting.title;	// mark item as merged
			theImported.status  += ' - '+theExisting.modified.formatString('MM/DD/YYYY 0hh:0mm:0ss');
			theImported.status  += ' by '+theExisting.modifier;
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with this item
			importStopped();
			break;
		case 'importReplace':		// substitute imported tiddler for existing tiddler
			cmi.lastAction=which;
			var theItem		  = list.options[cmi.index];
			for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==theItem.value) break;
			var theImported     = cmi.inbound[j];
			var theExisting	  = store.getTiddler(theItem.value);
			theImported.status  = 'replaces '+theExisting.title;		// mark item for replace
			theImported.status += ' - '+theExisting.modified.formatString('MM/DD/YYYY 0hh:0mm:0ss');
			theImported.status += ' by '+theExisting.modifier;
			theCollisionPanel.style.display='none';
			cmi.index=importTiddlers(cmi.index);	// resume with THIS item
			importStopped();
			break;
		case 'importListSmaller':		// decrease current listbox size, minimum=5
			if (list.options.length==1) break;
			list.size-=(list.size>5)?1:0;
			cmi.listsize=list.size;
			break;
		case 'importListLarger':		// increase current listbox size, maximum=number of items in list
			if (list.options.length==1) break;
			list.size+=(list.size<list.options.length)?1:0;
			cmi.listsize=list.size;
			break;
		case 'importListMaximize':	// toggle listbox size between current and maximum
			if (list.options.length==1) break;
			list.size=(list.size==list.options.length)?cmi.listsize:list.options.length;
			break;
		}
}
//}}}
//{{{
config.macros.importTiddlers.showPanel=function(place,show,skipAnim) {
	if (typeof place=='string') var place=document.getElementById(place);
	if (!place||!place.style) return;
	if(!skipAnim && anim && config.options.chkAnimate) anim.startAnimating(new Slider(place,show,false,'none'));
	else place.style.display=show?'block':'none';
}
//}}}
//{{{
function refreshImportList(selectedIndex) {
	var cmi=config.macros.importTiddlers; // abbrev
	var list=cmi.$('importList'); if (!list) return;
	// if nothing to show, reset list content and size
	if (!cmi.inbound) {
		while (list.length > 0) { list.options[0] = null; }
		list.options[0]=new Option(cmi.loadText,'',false,false);
		list.size=cmi.listsize;
		cmi.$('importLoad').disabled=false;
		cmi.$('importLoad').style.display='inline';
		cmi.$('importStart').disabled=true;
		cmi.$('importOptions').disabled=true;
		cmi.$('importOptions').style.display='none';
		cmi.$('fileImportSource').disabled=false;
		cmi.$('importFromFile').disabled=false;
		cmi.$('importFromWeb').disabled=false;
		cmi.$('importStart').value=cmi.startText;
		cmi.$('importClose').value=cmi.doneText;
		cmi.$('importSelectPanel').style.display='none';
		cmi.$('importOptionsPanel').style.display='none';
		return;
	}
	// there are inbound tiddlers loaded...
	cmi.$('importLoad').disabled=true;
	cmi.$('importLoad').style.display='none';
	cmi.$('importOptions').style.display='inline';
	cmi.$('importOptions').disabled=false;
	cmi.$('fileImportSource').disabled=true;
	cmi.$('importFromFile').disabled=true;
	cmi.$('importFromWeb').disabled=true;
	cmi.$('importClose').value=cmi.closeText;
	if (cmi.$('importSelectPanel').style.display=='none')
		cmi.showPanel('importSelectPanel',true);

	// get the sort order
	if (!selectedIndex)   selectedIndex=0;
	if (selectedIndex==0) cmi.sort='title';		// heading
	if (selectedIndex==1) cmi.sort='title';
	if (selectedIndex==2) cmi.sort='modified';
	if (selectedIndex==3) cmi.sort='tags';
	if (selectedIndex>3) {
		// display selected tiddler count
		for (var t=0,count=0; t < list.options.length; t++) {
			if (!list.options[t].selected) continue;
			if (list.options[t].value!='')
				count+=1;
			else { // if heading is selected, deselect it, and then select and count all in section
				list.options[t].selected=false;
				for ( t++; t<list.options.length && list.options[t].value!=''; t++) {
					list.options[t].selected=true;
					count++;
				}
			}
		}
		clearMessage(); displayMessage(cmi.countMsg.format([count]));
	}
	cmi.$('importStart').disabled=!count;
	if (selectedIndex>3) return; // no refresh needed

	// get the alphasorted list of tiddlers
	var tiddlers=cmi.inbound;
	tiddlers.sort(function (a,b) {if(a['title'] == b['title']) return(0); else return (a['title'] < b['title']) ? -1 : +1; });
	// clear current list contents
	while (list.length > 0) { list.options[0] = null; }
	// add heading and control items to list
	var i=0;
	var indent=String.fromCharCode(160)+String.fromCharCode(160);
	if (cmi.all.length==tiddlers.length)
		var summary=cmi.summaryMsg.format([tiddlers.length,(tiddlers.length!=1)?cmi.plural:cmi.single]);
	else
		var summary=cmi.summaryFilteredMsg.format([tiddlers.length,cmi.all.length,(cmi.all.length!=1)?cmi.plural:cmi.single]);
	list.options[i++]=new Option(summary,'',false,false);
	list.options[i++]=new Option(((cmi.sort=='title'   )?'>':indent)+' [by title]','',false,false);
	list.options[i++]=new Option(((cmi.sort=='modified')?'>':indent)+' [by date]','',false,false);
	list.options[i++]=new Option(((cmi.sort=='tags')?'>':indent)+' [by tags]','',false,false);
	// output the tiddler list
	switch(cmi.sort) {
		case 'title':
			for(var t = 0; t < tiddlers.length; t++)
				list.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);
			break;
		case 'modified':
			// sort descending for newest date first
			tiddlers.sort(function (a,b) {if(a['modified'] == b['modified']) return(0); else return (a['modified'] > b['modified']) ? -1 : +1; });
			var lastSection = '';
			for(var t = 0; t < tiddlers.length; t++) {
				var tiddler = tiddlers[t];
				var theSection = tiddler.modified.toLocaleDateString();
				if (theSection != lastSection) {
					list.options[i++] = new Option(theSection,'',false,false);
					lastSection = theSection;
				}
				list.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);
			}
			break;
		case 'tags':
			var theTitles = {}; // all tiddler titles, hash indexed by tag value
			var theTags = new Array();
			for(var t=0; t<tiddlers.length; t++) {
				var title=tiddlers[t].title;
				var tags=tiddlers[t].tags;
				if (!tags || !tags.length) {
					if (theTitles['untagged']==undefined) { theTags.push('untagged'); theTitles['untagged']=new Array(); }
					theTitles['untagged'].push(title);
				}
				else for(var s=0; s<tags.length; s++) {
					if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }
					theTitles[tags[s]].push(title);
				}
			}
			theTags.sort();
			for(var tagindex=0; tagindex<theTags.length; tagindex++) {
				var theTag=theTags[tagindex];
				list.options[i++]=new Option(theTag,'',false,false);
				for(var t=0; t<theTitles[theTag].length; t++)
					list.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);
			}
			break;
		}
	list.selectedIndex=selectedIndex;		  // select current control item
	if (list.size<cmi.listsize) list.size=cmi.listsize;
	if (list.size>list.options.length) list.size=list.options.length;
}
//}}}
//{{{
// re-entrant processing for handling import with interactive collision prompting
function importTiddlers(startIndex) {
	var cmi=config.macros.importTiddlers; // abbrev
	if (!cmi.inbound) return -1;
	var list=cmi.$('importList'); if (!list) return;
	var t;
	// if starting new import, reset import status flags
	if (startIndex==0)
		for (var t=0;t<cmi.inbound.length;t++)
			cmi.inbound[t].status='';
	for (var i=startIndex; i<list.options.length; i++) {
		// if list item is not selected or is a heading (i.e., has no value), skip it
		if ((!list.options[i].selected) || ((t=list.options[i].value)==''))
			continue;
		for (var j=0;j<cmi.inbound.length;j++)
			if (cmi.inbound[j].title==t) break;
		var inbound = cmi.inbound[j];
		var theExisting = store.getTiddler(inbound.title);
		// avoid redundant import for tiddlers that are listed multiple times (when 'by tags')
		if (inbound.status=='added')
			continue;
		// don't import the 'ImportedTiddlers' history from the other document...
		if (inbound.title=='ImportedTiddlers')
			continue;
		// if tiddler exists and import not marked for replace or merge, stop importing
		if (theExisting && (inbound.status.substr(0,7)!='replace') && (inbound.status.substr(0,5)!='merge'))
			return i;
		// assemble tags (remote + existing + added)
		var newTags = '';
		if (cmi.importTags)
			newTags+=inbound.getTags()	// import remote tags
		if (cmi.keepTags && theExisting)
			newTags+=' '+theExisting.getTags(); // keep existing tags
		if (cmi.addTags && cmi.newTags.trim().length)
			newTags+=' '+cmi.newTags; // add new tags
		inbound.set(null,null,null,null,newTags.trim());
		// set the status to 'added' (if not already set by the 'ask the user' UI)
		inbound.status=(inbound.status=='')?'added':inbound.status;
		// set sync fields
		if (cmi.sync) {
			if (!inbound.fields) inbound.fields={}; // for TW2.1.x backward-compatibility
			inbound.fields['server.page.revision']=inbound.modified.convertToYYYYMMDDHHMM();
			inbound.fields['server.type']='file';
			inbound.fields['server.host']=(cmi.local&&!cmi.src.startsWith('file:')?'file:///':'')+cmi.src;
		}
		// do the import!
		store.suspendNotifications();
		store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, inbound.tags, inbound.fields, true, inbound.created);
                store.fetchTiddler(inbound.title).created = inbound.created; // force creation date to imported value (needed for TW2.1.x and earlier)
		store.resumeNotifications();
		}
	return(-1);	// signals that we really finished the entire list
}
function importStopped() {
	var cmi=config.macros.importTiddlers; // abbrev
	var list=cmi.$('importList'); if (!list) return;
	var theNewTitle=cmi.$('importNewTitle');
	if (cmi.index==-1){ 
		cmi.$('importStart').value=cmi.startText;
		importReport();	// import finished... generate the report
	} else {
		// import collision...
		// show the collision panel and set the title edit field
		cmi.$('importStart').value=cmi.stopText;
		cmi.showPanel('importCollisionPanel',true);
		theNewTitle.value=list.options[cmi.index].value;
		if (cmi.$('importApplyToAll').checked && cmi.lastAction && cmi.lastAction.id!='importRename')
			onClickImportButton(cmi.lastAction);
	}
}
//}}}
//{{{
function importReport() {
	var cmi=config.macros.importTiddlers; // abbrev
	if (!cmi.inbound) return;
	// if import was not completed, the collision panel will still be open... close it now.
	var panel=cmi.$('importCollisionPanel'); if (panel) panel.style.display='none';
	// get the alphasorted list of tiddlers
	var tiddlers = cmi.inbound;
	// gather the statistics
	var count=0; var total=0;
	for (var t=0; t<tiddlers.length; t++) {
		if (!tiddlers[t].status || !tiddlers[t].status.trim().length) continue;
		if (tiddlers[t].status.substr(0,7)!='skipped') count++;
		total++;
	}
	// generate a report
	if (total) displayMessage(cmi.processedMsg.format([total]));
	if (count && config.options.chkImportReport) {
		// get/create the report tiddler
		var theReport = store.getTiddler('ImportedTiddlers');
		if (!theReport) { theReport=new Tiddler(); theReport.title='ImportedTiddlers'; theReport.text=''; }
		// format the report content
		var now = new Date();
		var newText = 'On '+now.toLocaleString()+', '+config.options.txtUserName
		newText +=' imported '+count+' tiddler'+(count==1?'':'s')+' from\n[['+cmi.src+'|'+cmi.src+']]:\n';
		if (cmi.addTags && cmi.newTags.trim().length)
			newText += 'imported tiddlers were tagged with: "'+cmi.newTags+'"\n';
		newText += '<<<\n';
		for (var t=0; t<tiddlers.length; t++) if (tiddlers[t].status)
			newText += '#[['+tiddlers[t].title+']] - '+tiddlers[t].status+'\n';
		newText += '<<<\n';
		// update the ImportedTiddlers content and show the tiddler
		theReport.text	 = newText+((theReport.text!='')?'\n----\n':'')+theReport.text;
		theReport.modifier = config.options.txtUserName;
		theReport.modified = new Date();
                store.saveTiddler(theReport.title, theReport.title, theReport.text, theReport.modifier, theReport.modified, theReport.tags, theReport.fields);
		story.displayTiddler(null,theReport.title,1,null,null,false);
		story.refreshTiddler(theReport.title,1,true);
	}
	// reset status flags
	for (var t=0; t<cmi.inbound.length; t++) cmi.inbound[t].status='';
	// mark document as dirty and let display update as needed
	if (count) { store.setDirty(true); store.notifyAll(); }
	// always show final message when tiddlers were actually loaded
	if (count) displayMessage(cmi.importedMsg.format([count,tiddlers.length,cmi.src.replace(/%20/g,' ')]));
}
//}}}
//{{{
// // File and XMLHttpRequest I/O
config.macros.importTiddlers.askForFilename=function(here) {
	var msg=here.title; // use tooltip as dialog box message
	var path=getLocalPath(document.location.href);
	var slashpos=path.lastIndexOf('/'); if (slashpos==-1) slashpos=path.lastIndexOf('\\'); 
	if (slashpos!=-1) path = path.substr(0,slashpos+1); // remove filename from path, leave the trailing slash
	var file='';
	var result='';
	if(window.Components) { // moz
		try {
			netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');

			var nsIFilePicker = window.Components.interfaces.nsIFilePicker;
			var picker = Components.classes['@mozilla.org/filepicker;1'].createInstance(nsIFilePicker);
			picker.init(window, msg, nsIFilePicker.modeOpen);
			var thispath = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
			thispath.initWithPath(path);
			picker.displayDirectory=thispath;
			picker.defaultExtension='html';
			picker.defaultString=file;
			picker.appendFilters(nsIFilePicker.filterAll|nsIFilePicker.filterText|nsIFilePicker.filterHTML);
			if (picker.show()!=nsIFilePicker.returnCancel) var result=picker.file.path;
		}
		catch(e) { alert('error during local file access: '+e.toString()) }
	}
	else { // IE
		try { // XPSP2 IE only
			var s = new ActiveXObject('UserAccounts.CommonDialog');
			s.Filter='All files|*.*|Text files|*.txt|HTML files|*.htm;*.html|';
			s.FilterIndex=3; // default to HTML files;
			s.InitialDir=path;
			s.FileName=file;
			if (s.showOpen()) var result=s.FileName;
		}
		catch(e) {  // fallback
			var result=prompt(msg,path+file);
		}
	}
	return result;
}

config.macros.importTiddlers.loadRemoteFile = function(src,callback) {
	if (src==undefined || !src.length) return null; // filename is required
	var original=src; // URL as specified
	var hashpos=src.indexOf('#'); if (hashpos!=-1) src=src.substr(0,hashpos); // URL with #... suffix removed (needed for IE)
	clearMessage();
	displayMessage(this.openMsg.format([src.replace(/%20/g,' ')]));
	if (src.substr(0,5)!='http:' && src.substr(0,5)!='file:') { // if not a URL, read from local filesystem
		var txt=loadFile(src);
		if (!txt) { // file didn't load, might be relative path.. try fixup
			var pathPrefix=document.location.href;  // get current document path and trim off filename
			var slashpos=pathPrefix.lastIndexOf('/'); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf('\\'); 
			if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
			src=pathPrefix+src;
			if (pathPrefix.substr(0,5)!='http:') src=getLocalPath(src);
			var txt=loadFile(src);
		}
		if (!txt) { // file still didn't load, report error
			displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g,' '),'(filesystem error)']));
		} else {
			displayMessage(config.macros.importTiddlers.readMsg.format([txt.length,src.replace(/%20/g,' ')]));
			if (version.major+version.minor*.1+version.revision*.01!=2.52) txt=convertUTF8ToUnicode(txt);
			if (callback) callback(true,original,txt,src,null);
		}
	} else {
		doHttp('GET',src,null,null,config.options.txtRemoteUsername,config.options.txtRemotePassword,callback,original,null);
	}
}

config.macros.importTiddlers.readTiddlersFromHTML=function(html){
	var remoteStore=new TiddlyWiki();
	remoteStore.importTiddlyWiki(html);
	return remoteStore.getTiddlers('title');	
}

config.macros.importTiddlers.readTiddlersFromCSV=function(CSV){
	var remoteStore=new TiddlyWiki();
	// GET NAMES
	var lines=CSV.replace(/\r/g,'').split('\n');
	var names=lines.shift().replace(/"/g,'').split(',');
	CSV=lines.join('\n');
	// ENCODE commas and newlines within quoted values
	var comma='!~comma~!'; var commaRE=new RegExp(comma,'g');
	var newline='!~newline~!'; var newlineRE=new RegExp(newline,'g');
	CSV=CSV.replace(/"([^"]*?)"/g,
		function(x){ return x.replace(/\,/g,comma).replace(/\n/g,newline); });
	// PARSE lines
	var lines=CSV.split('\n');
	for (var i=0; i<lines.length; i++) { if (!lines[i].length) continue;
		var values=lines[i].split(',');
		// DECODE commas, newlines, and doubled-quotes, and remove enclosing quotes (if any)
		for (var v=0; v<values.length; v++)
			values[v]=values[v].replace(commaRE,',').replace(newlineRE,'\n')
				.replace(/^"|"$/g,'').replace(/""/g,'"');
		// EXTRACT tiddler values
		var title=''; var text=''; var tags=[]; var fields={};
		var created=null; var when=new Date(); var who=config.options.txtUserName;
		for (var v=0; v<values.length; v++) { var val=values[v];
			if (names[v]) switch(names[v].toLowerCase()) {
				case 'title':	title=val.replace(/\[\]\|/g,'_'); break;
				case 'created': created=new Date(val); break;
				case 'modified':when=new Date(val); break;
				case 'modifier':who=val; break;
				case 'text':	text=val; break;
				case 'tags':	tags=val.readBracketedList(); break;
				default:	fields[names[v].toLowerCase()]=val; break;
			}
		}
		// CREATE tiddler in temporary store
		if (title.length)
			remoteStore.saveTiddler(title,title,text,who,when,tags,fields,true,created||when);
	}
	return remoteStore.getTiddlers('title');
}

config.macros.importTiddlers.createTiddlerFromFile=function(src,txt) {
	var t=new Tiddler();
	var pos=src.lastIndexOf("/"); if (pos==-1) pos=src.lastIndexOf("\\");
	t.title=pos==-1?src:src.substr(pos+1);
	t.text=txt; 
	t.created=t.modified=new Date();
	t.modifier=config.options.txtUserName;
	if (src.substr(src.length-3,3)=='.js') t.tags=['systemConfig'];
	return [t];
}

config.macros.importTiddlers.filterTiddlerList=function(success,params,txt,src,xhr){
	var cmi=config.macros.importTiddlers; // abbreviation
	var src=src.replace(/%20/g,' ');
	if (!success) { displayMessage(cmi.openErrMsg.format([src,xhr.status])); return; }
	cmi.all=cmi.readTiddlersFromHTML(txt);
	if (!cmi.all||!cmi.all.length) cmi.all=cmi.readTiddlersFromCSV(txt)
	if (!cmi.all||!cmi.all.length) cmi.all=cmi.createTiddlerFromFile(src,txt)
	var count=cmi.all?cmi.all.length:0;
	var querypos=src.lastIndexOf('?'); if (querypos!=-1) src=src.substr(0,querypos);
	displayMessage(cmi.foundMsg.format([count,src]));
	cmi.inbound=cmi.filterByHash(params,cmi.all); // use full URL including hash (if any)
	cmi.$('importLastFilter').value=cmi.lastFilter;
	window.refreshImportList(0);
}

config.macros.importTiddlers.filterByHash=function(src,tiddlers){
	var hashpos=src.lastIndexOf('#'); if (hashpos==-1) return tiddlers;
	var hash=src.substr(hashpos+1); if (!hash.length) return tiddlers;
	var tids=[];
	var params=hash.parseParams('anon',null,true,false,false);
	for (var p=1; p<params.length; p++) {
		switch (params[p].name) {
			case 'anon':
			case 'open':
				tids.pushUnique(params[p].value);
				break;
			case 'tag':
				if (store.getMatchingTiddlers) { // for boolean expressions - see MatchTagsPlugin
					var r=store.getMatchingTiddlers(params[p].value,null,tiddlers);
					for (var t=0; t<r.length; t++) tids.pushUnique(r[t].title);
				} else for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].isTagged(params[p].value))
						tids.pushUnique(tiddlers[t].title);
				break;
			case 'story':
				for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].title==params[p].value) {
						tiddlers[t].changed();
						for (var s=0; s<tiddlers[t].links.length; s++)
							tids.pushUnique(tiddlers[t].links[s]);
						break;
					}
				break;
			case 'search':
				for (var t=0; t<tiddlers.length; t++)
					if (tiddlers[t].text.indexOf(params[p].value)!=-1)
						tids.pushUnique(tiddlers[t].title);
				break;
		}
	}
	var matches=[];
	for (var t=0; t<tiddlers.length; t++)
		if (tids.contains(tiddlers[t].title))
			matches.push(tiddlers[t]);
	displayMessage(config.macros.importTiddlers.filterMsg.format([matches.length,hash]));
	config.macros.importTiddlers.lastFilter=hash;
	return matches;
}
//}}}
/***
!!!Control panel CSS
//{{{
!css
#importPanel {
	display: none; position:absolute; z-index:11; width:35em; right:105%; top:3em;
	background-color: #eee; color:#000; font-size: 8pt; line-height:110%;
	border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;
	padding: 0.5em; margin:0em; -moz-border-radius:1em;-webkit-border-radius:1em;
}
#importPanel a, #importPanel td a { color:#009; display:inline; margin:0px; padding:1px; }
#importPanel table { width:100%; border:0px; padding:0px; margin:0px; font-size:8pt; line-height:110%; background:transparent; }
#importPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }
#importPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }
#importPanel select { width:100%;margin:0px;font-size:8pt;line-height:110%;}
#importPanel input  { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%}
#importPanel .box { border:1px solid #000; background-color:#eee; padding:3px 5px; margin-bottom:5px; -moz-border-radius:5px;-webkit-border-radius:5px;}
#importPanel .topline { border-top:1px solid #999; padding-top:2px; margin-top:2px; }
#importPanel .rad { width:auto; }
#importPanel .chk { width:auto; margin:1px;border:0; }
#importPanel .btn { width:auto; }
#importPanel .btn1 { width:98%; }
#importPanel .btn2 { width:48%; }
#importPanel .btn3 { width:32%; }
#importPanel .btn4 { width:23%; }
#importPanel .btn5 { width:19%; }
#importPanel .importButton { padding: 0em; margin: 0px; font-size:8pt; }
#importPanel .importListButton { padding:0em 0.25em 0em 0.25em; color: #000000; display:inline }
#backstagePanel #importPanel { left:10%; right:auto; }
!end
//}}}
!!!Control panel HTML
//{{{
!html
<!-- source and report -->
<table><tr><td align=left>
	import from
	<input type="radio" class="rad" name="importFrom" id="importFromFile" value="file" CHECKED
		onclick="onClickImportButton(this,event)" title="show file controls"> local file
	<input type="radio" class="rad" name="importFrom" id="importFromWeb"  value="http"
		onclick="onClickImportButton(this,event)" title="show web controls"> web server
</td><td align=right>
	<input type=checkbox class="chk" id="chkImportReport"
		onClick="config.options['chkImportReport']=this.checked;"> create report
</td></tr></table>

<div class="box" id="importSourcePanel" style="margin:.5em">
<div id="importLocalPanel" style="display:block;margin-bottom:2px;"><!-- import from local file  -->
enter or browse for source path/filename<br>
<input type="file" id="fileImportSource" size=57 style="width:100%"
	onKeyUp="config.macros.importTiddlers.src=this.value"
	onChange="config.macros.importTiddlers.src=this.value;document.getElementById('importLoad').onclick()">
<div id="importLocalPanelFix" style="display:none"><!-- FF3 FIXUP -->
	<input type="text" id="fileImportSourceFix" style="width:90%"
		title="Enter a path/file to import"
		onKeyUp="config.macros.importTiddlers.src=this.value"
		onChange="config.macros.importTiddlers.src=this.value;document.getElementById('importLoad').onclick()">
	<input type="button" id="fileImportSourceFixButton" style="width:7%" value="..."
		title="Select a path/file to import"
		onClick="var r=config.macros.importTiddlers.askForFilename(this); if (!r||!r.length) return;
			document.getElementById('fileImportSourceFix').value=r;
			config.macros.importTiddlers.src=r;
			document.getElementById('importLoad').onclick()">
</div><!--end FF3 FIXUP-->
</div><!--end local-->
<div id="importHTTPPanel" style="display:none;margin-bottom:2px;"><!-- import from http server -->
<table><tr><td align=left>
	enter a URL or <a href="javascript:;" id="importSelectFeed"
		onclick="return onClickImportButton(this,event)" title="select a pre-defined 'systemServer' URL">
		select a server</a><br>
</td><td align=right>
	<input type="checkbox" class="chk" id="importUsePassword"
		onClick="config.macros.importTiddlers.usePassword=this.checked;
			config.macros.importTiddlers.showPanel('importIDPWPanel',this.checked,true);">password
	<input type="checkbox" class="chk" id="importUseProxy"
		onClick="config.macros.importTiddlers.useProxy=this.checked;
			config.macros.importTiddlers.showPanel('importSiteProxy',this.checked,true);">proxy
</td></tr></table>
<input type="text" id="importSiteProxy" style="display:none;margin-bottom:1px" onfocus="this.select()" value="SiteProxy"
	onKeyUp="config.macros.importTiddlers.proxy=this.value"
	onChange="config.macros.importTiddlers.proxy=this.value;">
<input type="text" id="importSourceURL" onfocus="this.select()" value="SiteUrl"
	onKeyUp="config.macros.importTiddlers.src=this.value"
	onChange="config.macros.importTiddlers.src=this.value;">
<div id="importIDPWPanel" style="text-align:center;margin-top:2px;display:none";>
username: <input type=text id="txtImportID" style="width:25%" 
	onChange="config.options.txtRemoteUsername=this.value;">
 password: <input type=password id="txtImportPW" style="width:25%" 
	onChange="config.options.txtRemotePassword=this.value;">
</div><!--end idpw-->
</div><!--end http-->
</div><!--end source-->

<div class="box" id="importSelectPanel" style="display:none;margin:.5em;">
<table><tr><td align=left>
select:
<a href="javascript:;" id="importSelectAll"
	onclick="return onClickImportButton(this)" title="SELECT all tiddlers">
	all</a>
&nbsp;<a href="javascript:;" id="importSelectNew"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers not already in destination document">
	added</a>
&nbsp;<a href="javascript:;" id="importSelectChanges"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers that have been updated in source document">
	changes</a>
&nbsp;<a href="javascript:;" id="importSelectDifferences"
	onclick="return onClickImportButton(this)" title="SELECT tiddlers that have been added or are different from existing tiddlers">
	differences</a>
</td><td align=right>
<a href="javascript:;" id="importListSmaller"
	onclick="return onClickImportButton(this)" title="SHRINK list size">
	&nbsp;&#150;&nbsp;</a>
<a href="javascript:;" id="importListLarger"
	onclick="return onClickImportButton(this)" title="GROW list size">
	&nbsp;+&nbsp;</a>
<a href="javascript:;" id="importListMaximize"
	onclick="return onClickImportButton(this)" title="MAXIMIZE/RESTORE list size">
	&nbsp;=&nbsp;</a>
</td></tr></table>
<select id="importList" size=8 multiple
	onchange="setTimeout('refreshImportList('+this.selectedIndex+')',1)">
	<!-- NOTE: delay refresh so list is updated AFTER onchange event is handled -->
</select>
<div style="text-align:center">
	<a href="javascript:;"
		title="click for help using filters..."
		onclick="alert('A filter consists of one or more space-separated combinations of: tiddlertitle, tag:[[tagvalue]], tag:[[tag expression]] (requires MatchTagsPlugin), story:[[TiddlerName]], and/or search:[[searchtext]]. Use a blank filter to restore the list of all tiddlers.'); return false;"
	>filter</a>
	<input type="text" id="importLastFilter" style="margin-bottom:1px; width:65%"
		title="Enter a combination of one or more filters. Use a blank filter for all tiddlers."
		onfocus="this.select()" value=""
		onKeyUp="config.macros.importTiddlers.lastFilter=this.value"
		onChange="config.macros.importTiddlers.lastFilter=this.value;">
	<input type="button" id="importApplyFilter" style="width:20%" value="apply"
		title="filter list of tiddlers to include only those that match certain criteria"
		onclick="return onClickImportButton(this)">
	</div>
</div><!--end select-->

<div class="box" id="importOptionsPanel" style="text-align:center;margin:.5em;display:none;">
	apply tags: <input type=checkbox class="chk" id="chkImportTags" checked
		onClick="config.macros.importTiddlers.importTags=this.checked;">from source&nbsp;
	<input type=checkbox class="chk" id="chkKeepTags" checked
		onClick="config.macros.importTiddlers.keepTags=this.checked;">keep existing&nbsp;
	<input type=checkbox class="chk" id="chkAddTags" 
		onClick="config.macros.importTiddlers.addTags=this.checked;
			config.macros.importTiddlers.showPanel('txtNewTags',this.checked,false);
			if (this.checked) document.getElementById('txtNewTags').focus();">add tags<br>
	<input type=text id="txtNewTags" style="margin-top:4px;display:none;" size=15 onfocus="this.select()" 
		title="enter tags to be added to imported tiddlers" 
		onKeyUp="config.macros.importTiddlers.newTags=this.value;
		document.getElementById('chkAddTags').checked=this.value.length>0;" autocomplete=off>
	<nobr><input type=checkbox class="chk" id="chkSync" 
		onClick="config.macros.importTiddlers.sync=this.checked;">
		link tiddlers to source document (for sync later)</nobr>
</div><!--end options-->

<div id="importButtonPanel" style="text-align:center">
	<input type=button id="importLoad"	class="importButton btn3" value="open"
		title="load listbox with tiddlers from source document"
		onclick="onClickImportButton(this)">
	<input type=button id="importOptions"	class="importButton btn3" value="options..."
		title="set options for tags, sync, etc."
		onclick="onClickImportButton(this)">
	<input type=button id="importStart"	class="importButton btn3" value="import"
		title="start/stop import of selected source tiddlers into current document"
		onclick="onClickImportButton(this)">
	<input type=button id="importClose"	class="importButton btn3" value="done"
		title="clear listbox or hide control panel"
		onclick="onClickImportButton(this)">
</div>

<div class="none" id="importCollisionPanel" style="display:none;margin:.5em 0 .5em .5em;">
	<table><tr><td style="width:65%" align="left">
		<table><tr><td align=left>
			tiddler already exists:
		</td><td align=right>
			<input type=checkbox class="chk" id="importApplyToAll" 
			onclick="document.getElementById('importRename').disabled=this.checked;"
			checked>apply to all
		</td></tr></table>
		<input type=text id="importNewTitle" size=15 autocomplete=off">
	</td><td style="width:34%" align="center">
		<input type=button id="importMerge"
			class="importButton" style="width:47%" value="merge"
			title="append the incoming tiddler to the existing tiddler"
			onclick="onClickImportButton(this)"><!--
		--><input type=button id="importSkip"
			class="importButton" style="width:47%" value="skip"
			title="do not import this tiddler"
			onclick="onClickImportButton(this)"><!--
		--><br><input type=button id="importRename"
			class="importButton" style="width:47%" value="rename"
			title="rename the incoming tiddler"
			onclick="onClickImportButton(this)"><!--
		--><input type=button id="importReplace"
			class="importButton" style="width:47%" value="replace"
			title="discard the existing tiddler"
			onclick="onClickImportButton(this)">
	</td></tr></table>
</div><!--end collision-->
!end
//}}}
***/
 
//{{{
config.macros.importTiddlers.addTags = true;
config.macros.importTiddlers.importTags = true;
config.macros.importTiddlers.keepTags = false;
config.macros.importTiddlers.sync = true;
//}}}
//{{{
var code = eval("httpReq").toString();
var newCode = null;
var startPosition = code.indexOf('xhr.setRequestHeader("X-Requested-With"');
if (startPosition > -1) {
	var endPosition = code.indexOf(';', startPosition);
	if (endPosition > -1) {
		newCode = code.substring(0, startPosition) + code.substring(endPosition + 1);
		code = newCode;
	}
}
var startPosition = code.indexOf("window.netscape.security.PrivilegeManager.enablePrivilege");
if (startPosition > -1) {
	var endPosition = code.indexOf(';', startPosition);
	if (endPosition > -1) {
		newCode = code.substring(0, startPosition) + " try { " + code.substring(startPosition, endPosition + 1) + " } catch (e) {} " + code.substring(endPosition + 1);
		code = newCode;
	}
}
if (newCode != null) {
	eval("httpReq = function httpReq" + newCode.substring(newCode.indexOf("(")));
}
//}}}
//{{{
if (typeof netscape != "undefined") {
	if (!netscape.security || !netscape.security.PrivilegeManager || !netscape.security.PrivilegeManager.enablePrivilege) {
		config.macros.importTiddlers.loadRemoteFile = function(src,callback) {
			if (src==undefined || !src.length) return null; // filename is required
			var original=src; // URL as specified
			var hashpos=src.indexOf('#'); if (hashpos!=-1) src=src.substr(0,hashpos); // URL with #... suffix removed (needed for IE)
			clearMessage();
			displayMessage(this.openMsg.format([src.replace(/%20/g,' ')]));
			if (src.substr(0,5)!='http:' && src.substr(0,5)!='file:') { // if not a URL, read from local filesystem
				var reader = new FileReader;
				reader.onload = function (e) {
					var txt = e.target.result;
					if (!txt) { // file still didn't load, report error
						displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g,' '),'(filesystem error)']));
					} else {
						displayMessage(config.macros.importTiddlers.readMsg.format([txt.length,src.replace(/%20/g,' ')]));
						if (version.major+version.minor*.1+version.revision*.01!=2.52) {
							try {
								var textTemp = '';
								while (txt.length > 0) {
									var index = txt.indexOf(' ', 10000);
									index = (index > -1) ? index : txt.length;
									textTemp = textTemp + convertUTF8ToUnicode(txt.substring(0, index));
									txt = txt.substring(index);
								}
								txt = textTemp;
							} catch (err) {
								displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g,' '),'(' + err + ')']));
								return false;
							}
						}
						if (callback) callback(true,original,txt,src,null);
					}
				};
				reader.readAsBinaryString(config.macros.importTiddlers.$('fileImportSource').files[0]);
			} else {
				doHttp('GET',src,null,null,config.options.txtRemoteUsername,config.options.txtRemotePassword,callback,original,null);
			}
		}
		
		var code = eval("createImportPanel").toString();
		var newCode = null;
		var startPosition = code.indexOf("window.Components");
		if (startPosition > -1) {
			newCode = code.substring(0, startPosition) + "false" + code.substring(startPosition + "window.Components".length);
			code = newCode;
		}
		var startPosition = code.indexOf("ImportTiddlersPlugin##html");
		if (startPosition > -1) {
			var startPosition = code.indexOf(";", startPosition);
			if (startPosition > -1) {
				newCode = code.substring(0, startPosition) + ".replace('57', '52')" + code.substring(startPosition);
				code = newCode;
			}
		}
		if (newCode != null) {
			eval("createImportPanel = function createImportPanel" + newCode.substring(newCode.indexOf("(")));
		}
	}
}
//}}}
//{{{
config.macros.importTiddlers.filterByHash_contains = function (tids, title) {
	if (tids) {
		var lowerCaseTitle = null;
		for (var i = 0; i < tids.length; i++) {
			if ((typeof tids[i] == "string") || (tids[i] instanceof String)) {
				if (tids[i] == title) {
					return true;
				}
			} else {
				lowerCaseTitle = (lowerCaseTitle === null) ? title.toLowerCase() : lowerCaseTitle;
				if (lowerCaseTitle.indexOf(tids[i][0].toLowerCase()) > -1) {
					return true;
				}
			}
		}
	}
	return false;
}

var code = eval("config.macros.importTiddlers.filterByHash").toString();
var newCode = null;
var startPosition = code.indexOf("pushUnique");
if (startPosition > -1) {
	newCode = code.substring(0, code.lastIndexOf("t", startPosition)) + "tids.pushUnique([params[p].value])" + code.substring(code.indexOf(")", startPosition) + 1);
	code = newCode;
}
var startPosition = code.indexOf("contains");
if (startPosition > -1) {
	newCode = code.substring(0, code.lastIndexOf("t", startPosition)) + "config.macros.importTiddlers.filterByHash_contains(tids, " + code.substring(code.indexOf("(", startPosition) + 1);
	code = newCode;
}
if (newCode != null) {
	eval("config.macros.importTiddlers.filterByHash = function filterByHash" + newCode.substring(newCode.indexOf("(")));
}
//}}}
//{{{
var code = eval("importTiddlers").toString();
var newCode = null;
var startPosition = code.indexOf("store.fetchTiddler");
if (startPosition > -1) {
	newCode = code.substring(0, startPosition) + 'if (!cmi.sync && inbound.fields && inbound.fields["changecount"]) { var importedTiddler = store.fetchTiddler(inbound.title); if (!importedTiddler.fields) importedTiddler.fields = {}; importedTiddler.fields["changecount"] = inbound.fields["changecount"]; }\n' + code.substring(startPosition);
	code = newCode;
}
if (newCode != null) {
	eval("importTiddlers = function importTiddlers" + newCode.substring(newCode.indexOf("(")));
}
//}}}
/***
|Name|ImportTiddlersPluginPatch|
|Source|http://www.TiddlyTools.com/#ImportTiddlersPluginPatch|
|Version|4.4.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Requires|ImportTiddlersPlugin|
|Description|backward-compatible function patches for use with ImportTiddlersPlugin and TW2.1.x or earlier|
!!!!!Usage
<<<
The current version ImportTiddlersPlugin is compatible with the TW2.2.x core functions.  This "patch" plugin provides additional functions needed to enable the current version of ImportTiddlersPlugin to operate correctly under TW2.1.x or earlier.

{{medium{You do not need to install this plugin if you are using TW2.2.0 or above}}}
(though it won't hurt anything if you do... it will just take up more space).
<<<
!!!!!Revisions
<<<
2008.09.30 [4.4.0] added safety check for TW21Loader object and forward-compatible loadFromDiv() prototype to permit use with TW2.0.x and TW1.2.x.
2008.08.05 [4.3.2] rewrote loadRemoteFile to eliminate use of platform-specific fileExists() function
2008.01.03 [3.6.0] added support for passing txtRemoteUsername and txtRemotePassword for accessing password-protected remote servers
2007.06.27 [3.5.5] compatibility functions split from ImportTiddlersPlugin
|please see [[ImportTiddlersPlugin]] for additional revision details|
2005.07.20 [1.0.0] Initial Release
<<<
!!!!!Code
***/
//{{{
// these functions are only defined when installed in TW2.1.x and earlier... 
if (version.major+version.minor/10 <= 2.1) {

// Version
version.extensions.ImportTiddlersPluginPatch= {major: 4, minor: 4, revision: 0, date: new Date(2008,9,30)};

// fixups for TW2.0.x and earlier
if (window.merge==undefined) window.merge=function(dst,src,preserveExisting)
	{ for (p in src) if (!preserveExisting||dst[p]===undefined) dst[p]=src[p]; return dst; }
if (config.macros.importTiddlers==undefined) config.macros.importTiddlers={ };

config.macros.importTiddlers.loadRemoteFile = function(src,callback,quiet) {
	if (src==undefined || !src.length) return null; // filename is required
	if (!quiet) clearMessage();
	if (!quiet) displayMessage(this.openMsg.format([src]));

	if (src.substr(0,5)!="http:" && src.substr(0,5)!="file:") { // if not a URL, read from local filesystem
		var txt=loadFile(src);
		if (!txt) { // file didn't load, might be relative path.. try fixup
			var pathPrefix=document.location.href;  // get current document path and trim off filename
			var slashpos=pathPrefix.lastIndexOf("/"); if (slashpos==-1) slashpos=pathPrefix.lastIndexOf("\\"); 
			if (slashpos!=-1 && slashpos!=pathPrefix.length-1) pathPrefix=pathPrefix.substr(0,slashpos+1);
			src=pathPrefix+src;
			if (pathPrefix.substr(0,5)!="http:") src=getLocalPath(src);
			var txt=loadFile(src);
		}
		if (!txt) { // file still didn't load, report error
			if (!quiet) displayMessage(config.macros.importTiddlers.openErrMsg.format([src.replace(/%20/g," "),"(filesystem error)"]));
		} else {
			if (!quiet) displayMessage(config.macros.importTiddlers.readMsg.format([txt.length,src.replace(/%20/g," ")]));
			if (callback) callback(true,src,convertUTF8ToUnicode(txt),src,null);
		}
	} else {
		var x; // get an request object
		try {x = new XMLHttpRequest()} // moz
		catch(e) {
			try {x = new ActiveXObject("Msxml2.XMLHTTP")} // IE 6
			catch (e) {
				try {x = new ActiveXObject("Microsoft.XMLHTTP")} // IE 5
				catch (e) { return }
			}
		}
		// setup callback function to handle server response(s)
		x.onreadystatechange = function() {
			if (x.readyState == 4) {
				if (x.status==0 || x.status == 200) {
					if (!quiet) displayMessage(config.macros.importTiddlers.readMsg.format([x.responseText.length,src]));
					if (callback) callback(true,src,x.responseText,src,x);
				}
				else {
					if (!quiet) displayMessage(config.macros.importTiddlers.openErrMsg.format([src,x.status]));
				}
			}
		}
		// get privileges to read another document's DOM via http:// or file:// (moz-only)
		if (typeof(netscape)!="undefined") {
			try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); }
			catch (e) { if (!quiet) displayMessage(e.description?e.description:e.toString()); }
		}
		// send the HTTP request
		try {
			var url=src+(src.indexOf('?')<0?'?':'&')+'nocache='+Math.random();
			x.open("GET",src,true,config.options.txtRemoteUsername,config.options.txtRemotePassword);
			if (x.overrideMimeType) x.overrideMimeType('text/html');
			x.send(null);
		}
		catch (e) {
			if (!quiet) {
				displayMessage(config.macros.importTiddlers.openErrMsg.format([src,"(unknown)"]));
				displayMessage(e.description?e.description:e.toString());
			}
		}
	}
}

config.macros.importTiddlers.readTiddlersFromHTML=function(html) {
	// for TW2.1 and earlier
	// extract store area from html 
	var start=html.indexOf('<div id="storeArea">');
	var end=html.indexOf("<!--POST-BODY-START--"+">",start);
	if (end==-1) var end=html.indexOf("</body"+">",start); // backward-compatibility for older documents
	var sa="<html><body>"+html.substring(start,end)+"</body></html>";

	// load html into iframe document
	var f=document.getElementById("loaderFrame"); if (f) document.body.removeChild(f);
	f=document.createElement("iframe"); f.id="loaderFrame";
	f.style.width="0px"; f.style.height="0px"; f.style.border="0px";
	document.body.appendChild(f);
	var d=f.document;
	if (f.contentDocument) d=f.contentDocument; // For NS6
	else if (f.contentWindow) d=f.contentWindow.document; // For IE5.5 and IE6
	d.open(); d.writeln(sa); d.close();

	// read tiddler DIVs from storeArea DOM element	
	var sa = d.getElementById("storeArea");
	if (!sa) return null;
	sa.normalize();
	var nodes = sa.childNodes;
	if (!nodes || !nodes.length) return null;
	var tiddlers = [];
	for(var t = 0; t < nodes.length; t++) {
		var title = null;
		if(nodes[t].getAttribute)
			title = nodes[t].getAttribute("title"); // TW 2.2+
		if(!title && nodes[t].getAttribute)
			title = nodes[t].getAttribute("tiddler"); // TW 2.1.x
		if(!title && nodes[t].id && (nodes[t].id.substr(0,5) == "store"))
			title = nodes[t].id.substr(5); // TW 1.2.x
		if(title && title != "")
			tiddlers.push((new Tiddler()).loadFromDiv(nodes[t],title));
	}
	return tiddlers;
}

// // FORWARD-COMPATIBLE SUPPORT FOR TW2.1.x
// // enables reading tiddler definitions using TW2.2+ storeArea format, even when plugin is running under TW2.1.x
if (typeof TW21Loader!="undefined") {
TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node) {
	var e = node.firstChild;
	var text = null;
	if(node.getAttribute("tiddler"))
		text = getNodeText(e).unescapeLineBreaks();
	else {
		while(e.nodeName!="PRE" && e.nodeName!="pre") e = e.nextSibling;
		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
	}
	var modifier = node.getAttribute("modifier");
	var c = node.getAttribute("created");
	var m = node.getAttribute("modified");
	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
	var tags = node.getAttribute("tags");
	var fields = {};
	var attrs = node.attributes;
	for(var i = attrs.length-1; i >= 0; i--) {
		var name = attrs[i].name;
		if (attrs[i].specified && !TiddlyWiki.isStandardField(name))
			fields[name] = attrs[i].value.unescapeLineBreaks();
		
	}
	tiddler.assign(title,text,modifier,modified,tags,created,fields);
	return tiddler;
};
}

// FORWARD-COMPATIBLE SUPPORT FOR TW2.0.x and TW1.2.x
// enables reading tiddler definitions using TW2.2+ storeArea format, even when plugin is running under TW2.0.x or TW1.2.x
if (typeof Tiddler.prototype.loadFromDiv!="undefined") {
Tiddler.prototype.loadFromDiv = function(node,title) { // Load a tiddler from an HTML DIV
	var e = node.firstChild;
	var text = null;
	if(node.getAttribute("tiddler")) {
		// get merged text from adjacent text nodes
		var t=""; while(e&&e.nodeName=="#text") { t+=e.nodeValue; e=e.nextSibling; }
		text = Tiddler.unescapeLineBreaks(t);
	} else {
		while(e.nodeName!="PRE" && e.nodeName!="pre") e = e.nextSibling;
		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
	}
	var modifier = node.getAttribute("modifier");
	var c = node.getAttribute("created");
	var m = node.getAttribute("modified");
	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
	var tags = node.getAttribute("tags");
	this.set(title,text,modifier,modified,tags,created);
	return this;
}
}

} // END OF pre-TW2.2 backward-compatibility functions
//}}}
On 2017-08-05, 10:35:17 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[AttachFilePluginFormattersOverride]] - replaces AttachFilePluginFormattersOverride - 5/6/2017 10:05:00 by Galasoft
<<<

----
On 2017-06-24, 10:55:00 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[tagChooserMacroOverride]] - replaces tagChooserMacroOverride - 5/7/2013 07:17:00 by Galasoft
<<<

----
On 2017-05-07, 7:54:39 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[AttachFilePluginFormattersOverride]] - added
<<<

----
On 2017-03-18, 9:14:50 AM, YourName imported 5 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[AttachFileMIMETypesOverride]] - replaces AttachFileMIMETypesOverride - 3/20/2016 08:30:00 by Galasoft
#[[Cleanup]] - replaces Cleanup - 1/11/2014 06:10:00 by Galasoft
#[[Free/Busy]] - replaces Free/Busy - 3/2/2014 05:57:00 by Galasoft
#[[MatchTagsPluginOverride]] - replaces MatchTagsPluginOverride - 4/14/2016 05:29:00 by Galasoft
#[[ReminderMacrosOverride]] - replaces ReminderMacrosOverride - 2/5/2015 06:44:00 by Galasoft
<<<

----
On 2016-10-21, 7:12:24 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - replaces TimesheetPlugin - 10/6/2016 06:26:00 by Galasoft
<<<

----
On 2016-10-06, 6:38:47 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - replaces TimesheetPlugin - 10/3/2016 05:20:00 by Galasoft
<<<

----
On 2016-10-03, 5:34:07 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - replaces TimesheetPlugin - 7/29/2016 16:08:00 by Galasoft
<<<

----
On 2016-09-05, 12:10:03 PM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 2/9/2015 18:11:00 by Galasoft
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 4/18/2015 06:02:00 by Galasoft
<<<

----
On 2016-07-29, 4:36:50 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - replaces TimesheetPlugin - 7/2/2016 15:32:00 by Galasoft
<<<

----
On 2016-07-02, 3:50:21 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - replaces TimesheetPlugin - 7/1/2016 19:52:00 by Galasoft
<<<

----
On 2016-07-01, 9:02:29 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TimesheetPlugin]] - added
<<<

----
On 2016-07-01, 8:45:20 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TiddlyWiki Markup]] - replaces TiddlyWiki Markup - 5/25/2013 05:44:00 by Galasoft
<<<

----
On 2016-04-14, 5:46:17 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CollatorPlugin]] - added
#[[MatchTagsPluginOverride]] - replaces MatchTagsPluginOverride - 1/30/2013 05:35:00 by Galasoft
#[[SearchOptionsPluginOverride]] - added
<<<

----
On 2016-03-20, 9:03:19 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[AttachFileMIMETypesOverride]] - replaces AttachFileMIMETypesOverride - 2/6/2016 10:24:00 by Galasoft
#[[FileDropPluginOverrideICS]] - replaces FileDropPluginOverrideICS - 12/6/2012 05:35:00 by Galasoft
<<<

----
On 2016-02-06, 10:34:07 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[AttachFileMIMETypesOverride]] - added
#[[AttachFilePluginOverride]] - replaces AttachFilePluginOverride - 2/15/2013 04:09:00 by Galasoft
#[[PDFPlugin]] - added
<<<

----
On 11/3/2015, 6:06:53 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 10/26/2015 18:47:00 by Galasoft
<<<

----
On 10/26/2015, 7:21:23 PM, YourName imported 6 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EditSectionPluginOverride]] - replaces EditSectionPluginOverride - 10/26/2015 18:33:00 by YourName
#[[ImagePastePlugin]] - replaces ImagePastePlugin - 10/26/2015 18:28:00 by YourName
#[[TWNotesEditSectionTemplate]] - replaces TWNotesEditSectionTemplate - 10/26/2015 18:31:00 by YourName
#[[TWNotesEditTemplate]] - replaces TWNotesEditTemplate - 10/26/2015 18:29:00 by YourName
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 10/26/2015 18:35:00 by YourName
#[[paste.js]] - replaces paste.js - 10/26/2015 18:25:00 by YourName
<<<

----
On 4/18/2015, 7:44:36 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TWNotesToolbarCommands]] - replaces TWNotesToolbarCommands - 1/7/2014 06:16:00 by Galasoft
<<<

----
On 4/18/2015, 6:35:11 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalculatorPlugin]] - replaces CalculatorPlugin - 8/20/2014 05:05:00 by Galasoft
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 2/9/2015 18:09:00 by Galasoft
<<<

----
On 2/9/2015, 6:45:44 PM, YourName imported 4 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 2/14/2014 04:53:00 by Galasoft
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 2/6/2015 07:15:00 by Galasoft
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 1/17/2014 05:23:00 by Galasoft
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 2/5/2015 06:45:00 by Galasoft
<<<

----
On 2/8/2015, 7:09:55 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/30/2014 04:49:00 by Galasoft
<<<

----
On 2/6/2015, 7:29:42 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 2/14/2014 04:54:00 by Galasoft
#[[jquery.icalendar.js]] - replaces jquery.icalendar.js - 3/9/2012 05:29:00 by Galasoft
#[[jquery.json.js]] - replaces jquery.json.js - 5/8/2012 04:57:00 by Galasoft
<<<

----
On 2/5/2015, 7:34:50 AM, YourName imported 4 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[ReminderMacrosOverride]] - replaces ReminderMacrosOverride - 2/5/2014 04:32:00 by Galasoft
#[[TWNotesMarkupPostHead]] - replaces TWNotesMarkupPostHead - 1/11/2014 07:41:00 by Galasoft
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 2/5/2014 04:31:00 by Galasoft
#[[rrule.js]] - added
<<<

----
On 8/20/2014, 6:06:18 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalculatorPlugin]] - replaces CalculatorPlugin - 1/19/2014 14:12:00 by Galasoft
#[[jQueryCalculator]] - replaces jQueryCalculator - 8/15/2014 04:29:00 by Galasoft
<<<

----
On 8/15/2014, 5:26:43 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[jQueryCalculator]] - replaces jQueryCalculator - 8/14/2014 04:43:00 by Galasoft
<<<

----
On 8/14/2014, 5:31:13 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[d3.js]] - replaces d3.js - 1/20/2014 05:34:00 by Galasoft
<<<

----
On 8/14/2014, 5:00:54 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[jQueryCalculator]] - replaces jQueryCalculator - 1/19/2014 14:13:00 by Galasoft
<<<

----
On 6/13/2014, 8:00:46 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[iTWNotesMarkupPreHead]] - replaces iTWNotesMarkupPreHead - 1/11/2014 21:20:00 by Galasoft
<<<

----
On March 2, 2014 8:22:51 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Free/Busy]] - replaces Free/Busy - 2/20/2014 06:03:00 by Galasoft
<<<

----
On February 21, 2014 8:02:14 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TaskTimerPluginOverride]] - added
<<<

----
On February 20, 2014 6:32:32 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Free/Busy]] - replaces Free/Busy - 2/5/2014 04:33:00 by Galasoft
<<<

----
On February 14, 2014 5:22:02 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 2/5/2014 05:00:00 by Galasoft
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 2/10/2014 07:21:00 by Galasoft
#[[DatePluginOverride]] - replaces DatePluginOverride - 1/31/2014 05:30:00 by Galasoft
<<<

----
On February 12, 2014 3:54:27 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[RelatedTiddlersPlugin]] - replaces RelatedTiddlersPlugin - 1/19/2014 14:26:00 by Galasoft
<<<

----
On February 10, 2014 7:33:40 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 1/31/2014 05:29:00 by Galasoft
<<<

----
On February 8, 2014 4:52:35 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TaskTimerPlugin500override]] - replaces TaskTimerPlugin500override - 4/5/2012 12:41:00 by Galasoft
<<<

----
On February 5, 2014 5:30:53 AM, YourName imported 4 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 1/31/2014 05:31:00 by Galasoft
#[[Free/Busy]] - replaces Free/Busy - 1/31/2014 04:36:00 by Galasoft
#[[ReminderMacrosOverride]] - replaces ReminderMacrosOverride - 11/8/2013 06:17:00 by Galasoft
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 2/2/2014 17:32:00 by Galasoft
<<<

----
On February 2, 2014 6:00:30 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[iCalendarPlugin]] - replaces iCalendarPlugin - 9/3/2013 03:48:00 by Galasoft
<<<

----
On February 2, 2014 9:27:49 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[DcTableOfContentsPluginOverride]] - replaces DcTableOfContentsPluginOverride - 3/2/2013 05:59:00 by Galasoft
<<<

----
On January 31, 2014 6:05:35 AM, YourName imported 4 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 1/23/2014 05:56:00 by Galasoft
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 1/29/2014 05:19:00 by Galasoft
#[[DatePluginOverride]] - replaces DatePluginOverride - 1/23/2014 05:57:00 by Galasoft
#[[Free/Busy]] - replaces Free/Busy - 10/19/2013 05:53:00 by Galasoft
<<<

----
On January 30, 2014 5:13:23 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/29/2014 06:00:00 by Galasoft
<<<

----
On January 29, 2014 6:08:17 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 1/29/2014 04:45:00 by Galasoft
#[[EmailPlugin]] - replaces EmailPlugin - 1/28/2014 04:35:00 by Galasoft
<<<

----
On January 29, 2014 4:57:55 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 1/28/2014 04:07:00 by Galasoft
<<<

----
On January 28, 2014 4:55:01 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 1/23/2014 05:57:00 by Galasoft
#[[EmailPlugin]] - replaces EmailPlugin - 1/22/2014 06:49:00 by Galasoft
#[[RegexLinkPlugin]] - replaces RegexLinkPlugin - 11/30/2013 07:46:00 by Galasoft
<<<

----
On January 23, 2014 7:00:15 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Agenda]] - replaces Agenda - 10/29/2013 06:27:00 by Galasoft
#[[CalendarPluginOverride]] - replaces CalendarPluginOverride - 10/29/2013 06:28:00 by Galasoft
#[[DatePluginOverride]] - replaces DatePluginOverride - 10/29/2013 06:29:00 by Galasoft
<<<

----
On January 22, 2014 6:58:04 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/21/2014 03:11:00 by Galasoft
<<<

----
On January 21, 2014 4:36:18 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/16/2014 04:14:00 by Galasoft
<<<

----
On January 20, 2014 5:43:40 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[d3.js]] - replaces d3.js - 1/12/2014 07:15:00 by Galasoft
<<<

----
On January 19, 2014 2:32:25 PM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalculatorPlugin]] - replaces CalculatorPlugin - 1/14/2014 04:31:00 by Galasoft
#[[RelatedTiddlersPlugin]] - replaces RelatedTiddlersPlugin - 1/16/2014 05:22:00 by Galasoft
#[[jQueryCalculator]] - replaces jQueryCalculator - 1/18/2014 19:17:00 by Galasoft
<<<

----
On January 18, 2014 7:26:57 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[jQueryCalculator]] - replaces jQueryCalculator - 12/27/2013 17:59:00 by Galasoft
<<<

----
On January 17, 2014 5:50:03 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itw.html|http://www.galasoft.net/tiddlywiki/itw.html]]:
<<<
#[[iTWTweaks]] - replaces iTWTweaks - 1/3/2014 06:19:00 by Galasoft
<<<

----
On January 17, 2014 5:49:36 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/11/2014 21:51:00 by Galasoft
<<<

----
On January 17, 2014 5:49:02 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 1/15/2014 04:12:00 by Galasoft
<<<

----
On January 16, 2014 5:31:34 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/10/2014 07:13:00 by Galasoft
#[[RelatedTiddlersPlugin]] - replaces RelatedTiddlersPlugin - 1/15/2014 04:35:00 by Galasoft
<<<

----
On January 15, 2014 5:03:17 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[RelatedTiddlersPlugin]] - replaces RelatedTiddlersPlugin - 11/8/2013 06:11:00 by Galasoft
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 1/11/2014 09:01:00 by Galasoft
#[[TWNotesViewTemplate]] - replaces TWNotesViewTemplate - 1/7/2014 06:10:00 by Galasoft
<<<

----
On January 14, 2014 4:44:14 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalculatorPlugin]] - replaces CalculatorPlugin - 1/3/2014 04:06:00 by Galasoft
#[[ImportTiddlersPluginOverride]] - replaces ImportTiddlersPluginOverride - 11/20/2013 05:54:00 by Galasoft
<<<

----
On January 12, 2014 7:36:44 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[d3.js]] - replaces d3.js - 12/18/2013 04:47:00 by Galasoft
<<<

----
On January 11, 2014 9:56:12 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/11/2014 21:21:00 by Galasoft
<<<

----
On January 11, 2014 9:37:38 PM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/9/2014 07:43:00 by Galasoft
#[[iTWNotesMarkupPreHead]] - added
<<<

----
On January 11, 2014 6:28:37 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[Tools]] - replaces Tools - 1/11/2014 09:09:00 by Galasoft
<<<

----
On January 11, 2014 9:14:22 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[Tools]] - replaces Tools - 3/7/2012 07:03:00 by Galasoft
<<<

----
On January 11, 2014 9:13:57 AM, YourName imported 5 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[Cleanup]] - added
#[[DateTime]] - added
#[[TWNotesInitPlugin]] - replaces TWNotesInitPlugin - 1/7/2014 06:47:00 by Galasoft
#[[TWNotesMarkupPostHead]] - replaces TWNotesMarkupPostHead - 1/7/2014 06:12:00 by Galasoft
#[[TrashPluginOverride]] - added
<<<

----
On January 10, 2014 7:27:53 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 1/10/2014 06:22:00 by Galasoft
<<<

----
On January 10, 2014 6:37:33 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[EmailPlugin]] - replaces EmailPlugin - 12/11/2013 07:18:00 by Galasoft
<<<

----
On January 9, 2014 7:51:43 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/8/2014 06:31:00 by Galasoft
<<<

----
On January 8, 2014 6:39:59 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/7/2014 07:17:00 by Galasoft
#[[iTWNotesPageTemplate]] - added
#[[iTWNotesStyleSheet]] - added
<<<

----
On January 7, 2014 7:29:57 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - replaces TWNotesInitPluginOverrideITW - 1/7/2014 06:46:00 by Galasoft
<<<

----
On January 7, 2014 7:10:46 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[TWNotesInitPluginOverrideITW]] - added
#[[iTWNotesViewTemplate]] - added
<<<

----
On January 7, 2014 7:10:19 AM, YourName imported 9 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[FileDropPluginOverrideTiddler]] - replaces FileDropPluginOverrideTiddler - 11/20/2013 05:52:00 by Galasoft
#[[TWNotesDefaultTiddlers]] - added
#[[TWNotesInitPlugin]] - added
#[[TWNotesMainMenu]] - added
#[[TWNotesMarkupPostHead]] - added
#[[TWNotesSiteSubtitle]] - added
#[[TWNotesSiteTitle]] - added
#[[TWNotesToolbarCommands]] - added
#[[TWNotesViewTemplate]] - added
<<<

----
On January 3, 2014 6:39:55 AM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[iTWTweaks]] - replaces iTWTweaks - 3/14/2011 16:05:00 by Galasoft
<<<

----
On January 3, 2014 6:38:19 AM, YourName imported 2 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[CalculatorPlugin]] - replaces CalculatorPlugin - 12/27/2013 17:46:00 by Galasoft
#[[DefaultTiddlers]] - replaces DefaultTiddlers - 1/18/2011 14:27:00 by Galasoft
<<<

----
On January 3, 2014 6:29:50 AM, YourName imported 3 tiddlers from
[[http://www.galasoft.net/tiddlywiki/notes.html|http://www.galasoft.net/tiddlywiki/notes.html]]:
<<<
#[[MainMenu]] - added
#[[TW Notes License]] - added
#[[ToolbarCommands]] - added
<<<

----
On January 2, 2014 8:21:20 PM, YourName imported 21 tiddlers from
[[http://www.galasoft.net/tiddlywiki/itwnotes.html|http://www.galasoft.net/tiddlywiki/itwnotes.html]]:
<<<
#[[AlwaysConfirmExitOnAndTidWiki]] - added
#[[Commands]] - replaces Commands - 8/17/2008 17:24:00 by BidiX
#[[DefaultTiddlers]] - replaces DefaultTiddlers - 1/19/2008 04:50:00 by BidiX
#[[HistoryPluginOverride]] - added
#[[Home]] - replaces Home - 8/17/2008 16:06:00 by BidiX
#[[Indexes]] - replaces Indexes - 3/26/2008 10:45:00 by BidiX
#[[ListByTagOverride]] - added
#[[Options]] - replaces Options - 9/13/2008 17:11:00 by YourName
#[[Other commands]] - replaces Other commands - 8/16/2008 04:39:00 by BidiX
#[[Plugins]] - added
#[[SearchOptionsPluginOverrideITW]] - added
#[[StyleSheet]] - replaces StyleSheet - 9/3/2008 05:25:00 by BidiX
#[[StyleSheetCustom]] - added
#[[Tools]] - added
#[[TopMenu]] - replaces TopMenu - 8/15/2008 06:23:00 by BidiX
#[[Upgrade]] - added
#[[ViewTemplate]] - replaces ViewTemplate - 4/8/2008 00:58:00 by BidiX
#[[displayMessageOverride]] - added
#[[iTWTweaks]] - replaces iTWTweaks - 9/13/2008 17:11:00 by YourName
#[[jquery.ba-bbq.js]] - added
#[[saveAllChangesCommand]] - added
<<<

----
On January 2, 2014 8:20:57 PM, YourName imported 30 tiddlers from
[[http://www.galasoft.net/tiddlywiki/itw.html|http://www.galasoft.net/tiddlywiki/itw.html]]:
<<<
#[[<<newTiddler>>]] - added
#[[BSD Open Source License]] - added
#[[BidiX]] - added
#[[Commands]] - added
#[[DefaultTiddlers]] - replaces DefaultTiddlers - 1/2/2014 17:45:00 by Galasoft
#[[GettingStarted]] - added
#[[HideWhenPlugin]] - added
#[[HistoryPlugin]] - added
#[[Home]] - added
#[[Indexes]] - added
#[[ListByTag]] - added
#[[LoadRemoteFileThroughProxy]] - added
#[[MarkupPreHead]] - added
#[[Menu]] - added
#[[Options]] - added
#[[Other commands]] - added
#[[PageTemplate]] - added
#[[PasswordOptionPlugin]] - added
#[[SiteProxy]] - added
#[[StyleSheet]] - added
#[[Sync]] - added
#[[TagMenu]] - added
#[[TiddlyWiki]] - added
#[[TopMenu]] - added
#[[UploadOptions]] - added
#[[UploadPlugin]] - added
#[[UploadTiddlerPlugin]] - added
#[[ViewTemplate]] - replaces ViewTemplate - 1/2/2014 18:17:00 by Galasoft
#[[iTWRepository]] - added
#[[iTWTweaks]] - added
<<<

----
On January 2, 2014 8:09:46 PM, YourName imported 7 tiddlers from
[[http://www.galasoft.net/tiddlywiki/games.html|http://www.galasoft.net/tiddlywiki/games.html]]:
<<<
#[[Minesweeper]] - added
#[[Minesweeper Beginner]] - added
#[[Minesweeper Expert]] - added
#[[Minesweeper Intermediate]] - added
#[[MinesweeperOverride]] - added
#[[invaders.svg]] - added
#[[svgtetris.svg]] - added
<<<

----
On January 2, 2014 8:04:27 PM, YourName imported 21 tiddlers from
[[notesold.html|notesold.html]]:
<<<
#[[2013-05-21 12:00 - 13:00 : Meeting]] - added
#[[2013-05-28 : Meeting]] - added
#[[2013-05-31 : Renew subscription]] - added
#[[2013-06-01 : Errands]] - added
#[[2013-06-01 : Log on to https://account.dyn.com/entrance]] - added
#[[2013-07-01 : Log on to https://account.dyn.com/entrance]] - added
#[[2013-08-01 : Log on to https://account.dyn.com/entrance]] - added
#[[2013-09-01 : Log on to https://account.dyn.com/entrance]] - added
#[[2013-11-01 : Log on to https://account.dyn.com/entrance]] - added
#[[Adams, Douglas]] - added
#[[Agenda français]] - added
#[[Agenda français simple]] - added
#[[Calculator]] - added
#[[CalculatorPluginQuebec]] - added
#[[Examples]] - added
#[[Herbert, Frank]] - added
#[[Log on to https://account.dyn.com/entrance]] - added
#[[Meeting.ics]] - added
#[[Sample e-mail message]] - added
#[[iCalendarPlugin Reminders]] - added
#[[tweaks]] - added
<<<

----
On January 2, 2014 6:50:40 PM, YourName imported 1 tiddler from
[[http://www.galasoft.net/tiddlywiki/notes-ext.html|http://www.galasoft.net/tiddlywiki/notes-ext.html]]:
<<<
#[[tiddlyfox.xpi]] - added
<<<
<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>
<<list filter "[tag[information AND NOT Trash]]">>
<<EncryptionDecryptAll "Decrypt" "Decrypt all 'information' Tiddlers." "information">>
/***
|Name|InlineJavascriptPlugin|
|Source|http://www.TiddlyTools.com/#InlineJavascriptPlugin|
|Documentation|http://www.TiddlyTools.com/#InlineJavascriptPluginInfo|
|Version|1.9.6|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|Insert Javascript executable code directly into your tiddler content.|
''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.
!!!!!Documentation
>see [[InlineJavascriptPluginInfo]]
!!!!!Revisions
<<<
2010.12.15 1.9.6 allow (but ignore) type="..." syntax
|please see [[InlineJavascriptPluginInfo]] for additional revision details|
2005.11.08 1.0.0 initial release
<<<
!!!!!Code
***/
//{{{
version.extensions.InlineJavascriptPlugin= {major: 1, minor: 9, revision: 6, date: new Date(2010,12,15)};

config.formatters.push( {
	name: "inlineJavascript",
	match: "\\<script",
	lookahead: "\\<script(?: type=\\\"[^\\\"]*\\\")?(?: src=\\\"([^\\\"]*)\\\")?(?: label=\\\"([^\\\"]*)\\\")?(?: title=\\\"([^\\\"]*)\\\")?(?: key=\\\"([^\\\"]*)\\\")?( show)?\\>((?:.|\\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) {
			var src=lookaheadMatch[1];
			var label=lookaheadMatch[2];
			var tip=lookaheadMatch[3];
			var key=lookaheadMatch[4];
			var show=lookaheadMatch[5];
			var code=lookaheadMatch[6];
			if (src) { // external script library
				var script = document.createElement("script"); script.src = src;
				document.body.appendChild(script); document.body.removeChild(script);
			}
			if (code) { // inline code
				if (show) // display source in tiddler
					wikify("{{{\n"+lookaheadMatch[0]+"\n}}}\n",w.output);
				if (label) { // create 'onclick' command link
					var link=createTiddlyElement(w.output,"a",null,"tiddlyLinkExisting",wikifyPlainText(label));
					var fixup=code.replace(/document.write\s*\(/gi,'place.bufferedHTML+=(');
					link.code="function _out(place,tiddler){"+fixup+"\n};_out(this,this.tiddler);"
					link.tiddler=w.tiddler;
					link.onclick=function(){
						this.bufferedHTML="";
						try{ var r=eval(this.code);
							if(this.bufferedHTML.length || (typeof(r)==="string")&&r.length)
								var s=this.parentNode.insertBefore(document.createElement("span"),this.nextSibling);
							if(this.bufferedHTML.length)
								s.innerHTML=this.bufferedHTML;
							if((typeof(r)==="string")&&r.length) {
								wikify(r,s,null,this.tiddler);
								return false;
							} else return r!==undefined?r:false;
						} catch(e){alert(e.description||e.toString());return false;}
					};
					link.setAttribute("title",tip||"");
					var URIcode='javascript:void(eval(decodeURIComponent(%22(function(){try{';
					URIcode+=encodeURIComponent(encodeURIComponent(code.replace(/\n/g,' ')));
					URIcode+='}catch(e){alert(e.description||e.toString())}})()%22)))';
					link.setAttribute("href",URIcode);
					link.style.cursor="pointer";
					if (key) link.accessKey=key.substr(0,1); // single character only
				}
				else { // run script immediately
					var fixup=code.replace(/document.write\s*\(/gi,'place.innerHTML+=(');
					var c="function _out(place,tiddler){"+fixup+"\n};_out(w.output,w.tiddler);";
					try	 { var out=eval(c); }
					catch(e) { out=e.description?e.description:e.toString(); }
					if (out && out.length) wikify(out,w.output,w.highlightRegExp,w.tiddler);
				}
			}
			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
		}
	}
} )
//}}}

// // Backward-compatibility for TW2.1.x and earlier
//{{{
if (typeof(wikifyPlainText)=="undefined") window.wikifyPlainText=function(text,limit,tiddler) {
	if(limit > 0) text = text.substr(0,limit);
	var wikifier = new Wikifier(text,formatter,null,tiddler);
	return wikifier.wikifyPlain();
}
//}}}

// // GLOBAL FUNCTION: $(...) -- 'shorthand' convenience syntax for document.getElementById()
//{{{
if (typeof($)=='undefined') { function $(id) { return document.getElementById(id.replace(/^#/,'')); } }
//}}}
//{{{
/*
 * ListByTag
 * 2008-09-08
 */

Array.prototype.tiddlerList = function(listFormat,max) {
		var output = "";
		if (!listFormat) 
			listFormat = "'\\n{{tiddlerListItem{{{tiddlerListItemTitle{[[' + tiddler.title + ']]}}} - ' + tiddler.created.formatString('0DD/0MM/YY') + ' - ' + tiddler.modifier + '\\n{{tiddlerExcerpt{' + text + ' ... \\n}}}}}}'";        
		if (!max) 
			max = this.length;
		if (this.length > 0 && this[0] instanceof Tiddler) {
			for (var i=0;i<max;i++) {
				var tiddler = this[i];
				var text = "{{{"+wikifyPlain(tiddler.title, store).substr(0,100)+"}}}";
				output += eval(listFormat);
			}
		}
		output += "\n----\n";
		return output;
	};

// tag, sorted, listformat, max(0), noReverse(true)
config.macros.listByTag = {};
config.macros.listByTag.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
    	params[0]  = (params[0] ? params[0] : tiddler.title);
    	var tiddlers = store.getTaggedTiddlers(params[0],params[1]);
    	if (params[3] == 0) params[3] = null;
    	if (! params[4])
    	    	tiddlers = tiddlers.reverse();
 	wikify(tiddlers.tiddlerList(params[2],params[3]),place,null,tiddler.tiddler);

	var tiddlerElem = document.getElementById(story.idPrefix + tiddler.title);
	var e = null;
	if(tiddlerElem != null) {
		var children = tiddlerElem.getElementsByTagName("span");
		for(var t=0; t<children.length; t++) {
			var c = children[t];
			if(c.className == 'tiddlerListItem') {
				c.addEventListener('mousedown', function(event) {
					var tiddlyLink = event.currentTarget.firstChild.firstChild;
					var tiddlerTitle = tiddlyLink.getAttribute("tiddlyLink");
					story.displayTiddler(null, tiddlerTitle);
					event.preventDefault();
				}, true);
			}
		}
	}
	
};
//}}}

//{{{
Array.prototype.tiddlerList = function(listFormat,max) {
		var output = "";
		if (!listFormat) 
			listFormat = "'\\n{{tiddlerListItem{{{tiddlerListItemTitle{[[' + tiddler.title + ']]}}}\\n}}}'";        
		if (!max) 
			max = this.length;
		if (this.length > 0 && this[0] instanceof Tiddler) {
			for (var i=0;i<max;i++) {
				var tiddler = this[i];
				output += eval(listFormat);
			}
		}
		output += "\n----\n";
		return output;
	};

config.macros.listByTag.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
    	params[0]  = (params[0] ? params[0] : tiddler.title);
    	var tiddlers = store.getTaggedTiddlers(params[0],params[1]);
    	if (params[3] == 0) params[3] = null;
    	if (! params[4])
    	    	tiddlers = tiddlers.reverse();
 	wikify(tiddlers.tiddlerList(params[2],params[3]),place,null,tiddler.tiddler);

	var tiddlerElem = document.getElementById(story.idPrefix + tiddler.title);
	var e = null;
	if(tiddlerElem != null) {
		var children = tiddlerElem.getElementsByTagName("span");
		for(var t=0; t<children.length; t++) {
			var c = children[t];
			if(c.className == 'tiddlerListItem') {
				addEvent(c, 'mousedown', function(event) {
					event = !event ? window.event : event;
					var target = event.currentTarget ? event.currentTarget : event.srcElement;
					while (target.nodeType != 1) {
						target = target.parentNode;
					}
					var tiddlyLink = target.firstChild.firstChild;
					var tiddlerTitle = tiddlyLink.getAttribute("tiddlyLink");
					story.displayTiddler(null, tiddlerTitle);
					if (event.preventDefault) {
						event.preventDefault();
					}
				});
			}
		}
	}
	
};
//}}}
/***
|''Name:''|LoadRemoteFileThroughProxy (previous LoadRemoteFileHijack)|
|''Description:''|When the TiddlyWiki file is located on the web (view over http) the content of [[SiteProxy]] tiddler is added in front of the file url. If [[SiteProxy]] does not exist "/proxy/" is added. |
|''Version:''|1.1.0|
|''Date:''|mar 17, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#LoadRemoteFileHijack|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
***/
//{{{
version.extensions.LoadRemoteFileThroughProxy = {
 major: 1, minor: 1, revision: 0, 
 date: new Date("mar 17, 2007"), 
 source: "http://tiddlywiki.bidix.info/#LoadRemoteFileThroughProxy"};

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

bidix.core.loadRemoteFile = loadRemoteFile;
loadRemoteFile = function(url,callback,params)
{
 if ((document.location.toString().substr(0,4) == "http") && (url.substr(0,4) == "http")){ 
 url = store.getTiddlerText("SiteProxy", "/proxy/") + url;
 }
 return bidix.core.loadRemoteFile(url,callback,params);
}
//}}}
/***
|Name|LoadTiddlersPlugin|
|Source|http://www.TiddlyTools.com/#LoadTiddlersPlugin|
|Documentation|http://www.TiddlyTools.com/#LoadTiddlersPluginInfo|
|Version|3.9.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|macro for automated updates or one-click installations of tiddlers from remote sources|
!!!!!Documentation
>see [[LoadTidd