app/taggable-mixin/taggable.html
changeset 2368 e07c425c7135
equal deleted inserted replaced
2367:19ca23d78701 2368:e07c425c7135
       
     1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
       
     2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
       
     3 <head>
       
     4 <script id="versionArea" type="text/javascript">
       
     5 //<![CDATA[
       
     6 var version = {title: "TiddlyWiki", major: 2, minor: 4, revision: 0, date: new Date("May 9, 2008"), extensions: {}};
       
     7 //]]>
       
     8 </script>
       
     9 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
       
    10 <meta name="copyright" content="
       
    11 TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
       
    12 
       
    13 Copyright (c) UnaMesa Association 2004-2008
       
    14 
       
    15 Redistribution and use in source and binary forms, with or without modification,
       
    16 are permitted provided that the following conditions are met:
       
    17 
       
    18 Redistributions of source code must retain the above copyright notice, this
       
    19 list of conditions and the following disclaimer.
       
    20 
       
    21 Redistributions in binary form must reproduce the above copyright notice, this
       
    22 list of conditions and the following disclaimer in the documentation and/or other
       
    23 materials provided with the distribution.
       
    24 
       
    25 Neither the name of the UnaMesa Association nor the names of its contributors may be
       
    26 used to endorse or promote products derived from this software without specific
       
    27 prior written permission.
       
    28 
       
    29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY
       
    30 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
       
    31 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
       
    32 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
       
    33 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
       
    34 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
       
    35 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
       
    36 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
       
    37 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
       
    38 DAMAGE.
       
    39 " />
       
    40 <!--PRE-HEAD-START-->
       
    41 <!--{{{-->
       
    42 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
       
    43 <!--}}}-->
       
    44 <!--PRE-HEAD-END-->
       
    45 <title> Taggable - a portable mixin class for adding Tags to Google AppEngine Models </title>
       
    46 <style id="styleArea" type="text/css">
       
    47 #saveTest {display:none;}
       
    48 #messageArea {display:none;}
       
    49 #copyright {display:none;}
       
    50 #storeArea {display:none;}
       
    51 #storeArea div {padding:0.5em; margin:1em 0em 0em 0em; border-color:#fff #666 #444 #ddd; border-style:solid; border-width:2px; overflow:auto;}
       
    52 #shadowArea {display:none;}
       
    53 #javascriptWarning {width:100%; text-align:center; font-weight:bold; background-color:#dd1100; color:#fff; padding:1em 0em;}
       
    54 </style>
       
    55 <!--POST-HEAD-START-->
       
    56 
       
    57 <!--POST-HEAD-END-->
       
    58 </head>
       
    59 <body onload="main();" onunload="if(window.checkUnsavedChanges) checkUnsavedChanges(); if(window.scrubNodes) scrubNodes(document.body);">
       
    60 <!--PRE-BODY-START-->
       
    61 
       
    62 <!--PRE-BODY-END-->
       
    63 <div id="copyright">
       
    64 Welcome to TiddlyWiki created by Jeremy Ruston, Copyright &copy; 2007 UnaMesa Association
       
    65 </div>
       
    66 <noscript>
       
    67 	<div id="javascriptWarning">This page requires JavaScript to function properly.<br /><br />If you are using Microsoft Internet Explorer you may need to click on the yellow bar above and select 'Allow Blocked Content'. You must then click 'Yes' on the following security warning.</div>
       
    68 </noscript>
       
    69 <div id="saveTest"></div>
       
    70 <div id="backstageCloak"></div>
       
    71 <div id="backstageButton"></div>
       
    72 <div id="backstageArea"><div id="backstageToolbar"></div></div>
       
    73 <div id="backstage">
       
    74 	<div id="backstagePanel"></div>
       
    75 </div>
       
    76 <div id="contentWrapper"></div>
       
    77 <div id="contentStash"></div>
       
    78 <div id="shadowArea">
       
    79 <div title="MarkupPreHead">
       
    80 <pre>&lt;!--{{{--&gt;
       
    81 &lt;link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/&gt;
       
    82 &lt;!--}}}--&gt;</pre>
       
    83 </div>
       
    84 <div title="ColorPalette">
       
    85 <pre>Background: #fff
       
    86 Foreground: #000
       
    87 PrimaryPale: #8cf
       
    88 PrimaryLight: #18f
       
    89 PrimaryMid: #04b
       
    90 PrimaryDark: #014
       
    91 SecondaryPale: #ffc
       
    92 SecondaryLight: #fe8
       
    93 SecondaryMid: #db4
       
    94 SecondaryDark: #841
       
    95 TertiaryPale: #eee
       
    96 TertiaryLight: #ccc
       
    97 TertiaryMid: #999
       
    98 TertiaryDark: #666
       
    99 Error: #f88</pre>
       
   100 </div>
       
   101 <div title="StyleSheetColors">
       
   102 <pre>/*{{{*/
       
   103 body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
       
   104 
       
   105 a {color:[[ColorPalette::PrimaryMid]];}
       
   106 a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
       
   107 a img {border:0;}
       
   108 
       
   109 h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
       
   110 h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
       
   111 h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}
       
   112 
       
   113 .button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
       
   114 .button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
       
   115 .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}
       
   116 
       
   117 .header {background:[[ColorPalette::PrimaryMid]];}
       
   118 .headerShadow {color:[[ColorPalette::Foreground]];}
       
   119 .headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
       
   120 .headerForeground {color:[[ColorPalette::Background]];}
       
   121 .headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}
       
   122 
       
   123 .tabSelected{color:[[ColorPalette::PrimaryDark]];
       
   124 	background:[[ColorPalette::TertiaryPale]];
       
   125 	border-left:1px solid [[ColorPalette::TertiaryLight]];
       
   126 	border-top:1px solid [[ColorPalette::TertiaryLight]];
       
   127 	border-right:1px solid [[ColorPalette::TertiaryLight]];
       
   128 }
       
   129 .tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
       
   130 .tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
       
   131 .tabContents .button {border:0;}
       
   132 
       
   133 #sidebar {}
       
   134 #sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
       
   135 #sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
       
   136 #sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
       
   137 #sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
       
   138 #sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}
       
   139 
       
   140 .wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
       
   141 .wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
       
   142 .wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
       
   143 .wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
       
   144 	border:1px solid [[ColorPalette::PrimaryMid]];}
       
   145 .wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
       
   146 .wizardFooter {background:[[ColorPalette::PrimaryPale]];}
       
   147 .wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
       
   148 .wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
       
   149 	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
       
   150 .wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
       
   151 .wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
       
   152 	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
       
   153 
       
   154 #messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
       
   155 #messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
       
   156 
       
   157 .popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
       
   158 
       
   159 .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]];}
       
   160 .popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
       
   161 .popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
       
   162 .popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
       
   163 .popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
       
   164 .popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
       
   165 .popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
       
   166 .listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
       
   167 
       
   168 .tiddler .defaultCommand {font-weight:bold;}
       
   169 
       
   170 .shadow .title {color:[[ColorPalette::TertiaryDark]];}
       
   171 
       
   172 .title {color:[[ColorPalette::SecondaryDark]];}
       
   173 .subtitle {color:[[ColorPalette::TertiaryDark]];}
       
   174 
       
   175 .toolbar {color:[[ColorPalette::PrimaryMid]];}
       
   176 .toolbar a {color:[[ColorPalette::TertiaryLight]];}
       
   177 .selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
       
   178 .selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
       
   179 
       
   180 .tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
       
   181 .selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
       
   182 .tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
       
   183 .tagging .button, .tagged .button {border:none;}
       
   184 
       
   185 .footer {color:[[ColorPalette::TertiaryLight]];}
       
   186 .selected .footer {color:[[ColorPalette::TertiaryMid]];}
       
   187 
       
   188 .sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
       
   189 .sparktick {background:[[ColorPalette::PrimaryDark]];}
       
   190 
       
   191 .error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
       
   192 .warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
       
   193 .lowlight {background:[[ColorPalette::TertiaryLight]];}
       
   194 
       
   195 .zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
       
   196 
       
   197 .imageLink, #displayArea .imageLink {background:transparent;}
       
   198 
       
   199 .annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
       
   200 
       
   201 .viewer .listTitle {list-style-type:none; margin-left:-2em;}
       
   202 .viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
       
   203 .viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
       
   204 
       
   205 .viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
       
   206 .viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
       
   207 .viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
       
   208 
       
   209 .viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
       
   210 .viewer code {color:[[ColorPalette::SecondaryDark]];}
       
   211 .viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
       
   212 
       
   213 .highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
       
   214 
       
   215 .editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
       
   216 .editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
       
   217 .editorFooter {color:[[ColorPalette::TertiaryMid]];}
       
   218 
       
   219 #backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
       
   220 #backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
       
   221 #backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
       
   222 #backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
       
   223 #backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
       
   224 #backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
       
   225 #backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
       
   226 .backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
       
   227 .backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
       
   228 #backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
       
   229 /*}}}*/</pre>
       
   230 </div>
       
   231 <div title="StyleSheetLayout">
       
   232 <pre>/*{{{*/
       
   233 * html .tiddler {height:1%;}
       
   234 
       
   235 body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
       
   236 
       
   237 h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
       
   238 h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
       
   239 h4,h5,h6 {margin-top:1em;}
       
   240 h1 {font-size:1.35em;}
       
   241 h2 {font-size:1.25em;}
       
   242 h3 {font-size:1.1em;}
       
   243 h4 {font-size:1em;}
       
   244 h5 {font-size:.9em;}
       
   245 
       
   246 hr {height:1px;}
       
   247 
       
   248 a {text-decoration:none;}
       
   249 
       
   250 dt {font-weight:bold;}
       
   251 
       
   252 ol {list-style-type:decimal;}
       
   253 ol ol {list-style-type:lower-alpha;}
       
   254 ol ol ol {list-style-type:lower-roman;}
       
   255 ol ol ol ol {list-style-type:decimal;}
       
   256 ol ol ol ol ol {list-style-type:lower-alpha;}
       
   257 ol ol ol ol ol ol {list-style-type:lower-roman;}
       
   258 ol ol ol ol ol ol ol {list-style-type:decimal;}
       
   259 
       
   260 .txtOptionInput {width:11em;}
       
   261 
       
   262 #contentWrapper .chkOptionInput {border:0;}
       
   263 
       
   264 .externalLink {text-decoration:underline;}
       
   265 
       
   266 .indent {margin-left:3em;}
       
   267 .outdent {margin-left:3em; text-indent:-3em;}
       
   268 code.escaped {white-space:nowrap;}
       
   269 
       
   270 .tiddlyLinkExisting {font-weight:bold;}
       
   271 .tiddlyLinkNonExisting {font-style:italic;}
       
   272 
       
   273 /* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
       
   274 a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
       
   275 
       
   276 #mainMenu .tiddlyLinkExisting,
       
   277 	#mainMenu .tiddlyLinkNonExisting,
       
   278 	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
       
   279 #sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
       
   280 
       
   281 .header {position:relative;}
       
   282 .header a:hover {background:transparent;}
       
   283 .headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
       
   284 .headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
       
   285 
       
   286 .siteTitle {font-size:3em;}
       
   287 .siteSubtitle {font-size:1.2em;}
       
   288 
       
   289 #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;}
       
   290 
       
   291 #sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
       
   292 #sidebarOptions {padding-top:0.3em;}
       
   293 #sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
       
   294 #sidebarOptions input {margin:0.4em 0.5em;}
       
   295 #sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
       
   296 #sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
       
   297 #sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
       
   298 #sidebarTabs .tabContents {width:15em; overflow:hidden;}
       
   299 
       
   300 .wizard {padding:0.1em 1em 0em 2em;}
       
   301 .wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
       
   302 .wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
       
   303 .wizardStep {padding:1em 1em 1em 1em;}
       
   304 .wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
       
   305 .wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
       
   306 .wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
       
   307 .wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
       
   308 
       
   309 #messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
       
   310 .messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
       
   311 #messageArea a {text-decoration:underline;}
       
   312 
       
   313 .tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
       
   314 .popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
       
   315 
       
   316 .popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
       
   317 .popup .popupMessage {padding:0.4em;}
       
   318 .popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
       
   319 .popup li.disabled {padding:0.4em;}
       
   320 .popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
       
   321 .listBreak {font-size:1px; line-height:1px;}
       
   322 .listBreak div {margin:2px 0;}
       
   323 
       
   324 .tabset {padding:1em 0em 0em 0.5em;}
       
   325 .tab {margin:0em 0em 0em 0.25em; padding:2px;}
       
   326 .tabContents {padding:0.5em;}
       
   327 .tabContents ul, .tabContents ol {margin:0; padding:0;}
       
   328 .txtMainTab .tabContents li {list-style:none;}
       
   329 .tabContents li.listLink { margin-left:.75em;}
       
   330 
       
   331 #contentWrapper {display:block;}
       
   332 #splashScreen {display:none;}
       
   333 
       
   334 #displayArea {margin:1em 17em 0em 14em;}
       
   335 
       
   336 .toolbar {text-align:right; font-size:.9em;}
       
   337 
       
   338 .tiddler {padding:1em 1em 0em 1em;}
       
   339 
       
   340 .missing .viewer,.missing .title {font-style:italic;}
       
   341 
       
   342 .title {font-size:1.6em; font-weight:bold;}
       
   343 
       
   344 .missing .subtitle {display:none;}
       
   345 .subtitle {font-size:1.1em;}
       
   346 
       
   347 .tiddler .button {padding:0.2em 0.4em;}
       
   348 
       
   349 .tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
       
   350 .isTag .tagging {display:block;}
       
   351 .tagged {margin:0.5em; float:right;}
       
   352 .tagging, .tagged {font-size:0.9em; padding:0.25em;}
       
   353 .tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
       
   354 .tagClear {clear:both;}
       
   355 
       
   356 .footer {font-size:.9em;}
       
   357 .footer li {display:inline;}
       
   358 
       
   359 .annotation {padding:0.5em; margin:0.5em;}
       
   360 
       
   361 * html .viewer pre {width:99%; padding:0 0 1em 0;}
       
   362 .viewer {line-height:1.4em; padding-top:0.5em;}
       
   363 .viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
       
   364 .viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
       
   365 .viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
       
   366 
       
   367 .viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
       
   368 .viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
       
   369 table.listView {font-size:0.85em; margin:0.8em 1.0em;}
       
   370 table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
       
   371 
       
   372 .viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
       
   373 .viewer code {font-size:1.2em; line-height:1.4em;}
       
   374 
       
   375 .editor {font-size:1.1em;}
       
   376 .editor input, .editor textarea {display:block; width:100%; font:inherit;}
       
   377 .editorFooter {padding:0.25em 0em; font-size:.9em;}
       
   378 .editorFooter .button {padding-top:0px; padding-bottom:0px;}
       
   379 
       
   380 .fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
       
   381 
       
   382 .sparkline {line-height:1em;}
       
   383 .sparktick {outline:0;}
       
   384 
       
   385 .zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
       
   386 .zoomer div {padding:1em;}
       
   387 
       
   388 * html #backstage {width:99%;}
       
   389 * html #backstageArea {width:99%;}
       
   390 #backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
       
   391 #backstageToolbar {position:relative;}
       
   392 #backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
       
   393 #backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
       
   394 #backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
       
   395 #backstage {position:relative; width:100%; z-index:50;}
       
   396 #backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
       
   397 .backstagePanelFooter {padding-top:0.2em; float:right;}
       
   398 .backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
       
   399 #backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
       
   400 
       
   401 .whenBackstage {display:none;}
       
   402 .backstageVisible .whenBackstage {display:block;}
       
   403 /*}}}*/</pre>
       
   404 </div>
       
   405 <div title="StyleSheetLocale">
       
   406 <pre>/***
       
   407 StyleSheet for use when a translation requires any css style changes.
       
   408 This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
       
   409 ***/
       
   410 /*{{{*/
       
   411 body {font-size:0.8em;}
       
   412 #sidebarOptions {font-size:1.05em;}
       
   413 #sidebarOptions a {font-style:normal;}
       
   414 #sidebarOptions .sliderPanel {font-size:0.95em;}
       
   415 .subtitle {font-size:0.8em;}
       
   416 .viewer table.listView {font-size:0.95em;}
       
   417 /*}}}*/</pre>
       
   418 </div>
       
   419 <div title="StyleSheetPrint">
       
   420 <pre>/*{{{*/
       
   421 @media print {
       
   422 #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
       
   423 #displayArea {margin: 1em 1em 0em 1em;}
       
   424 /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
       
   425 noscript {display:none;}
       
   426 }
       
   427 /*}}}*/</pre>
       
   428 </div>
       
   429 <div title="PageTemplate">
       
   430 <pre>&lt;!--{{{--&gt;
       
   431 &lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
       
   432 &lt;div class='headerShadow'&gt;
       
   433 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
       
   434 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
       
   435 &lt;/div&gt;
       
   436 &lt;div class='headerForeground'&gt;
       
   437 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
       
   438 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
       
   439 &lt;/div&gt;
       
   440 &lt;/div&gt;
       
   441 &lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
       
   442 &lt;div id='sidebar'&gt;
       
   443 &lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
       
   444 &lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
       
   445 &lt;/div&gt;
       
   446 &lt;div id='displayArea'&gt;
       
   447 &lt;div id='messageArea'&gt;&lt;/div&gt;
       
   448 &lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
       
   449 &lt;/div&gt;
       
   450 &lt;!--}}}--&gt;</pre>
       
   451 </div>
       
   452 <div title="ViewTemplate">
       
   453 <pre>&lt;!--{{{--&gt;
       
   454 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
       
   455 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
       
   456 &lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
       
   457 &lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
       
   458 &lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
       
   459 &lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
       
   460 &lt;div class='tagClear'&gt;&lt;/div&gt;
       
   461 &lt;!--}}}--&gt;</pre>
       
   462 </div>
       
   463 <div title="EditTemplate">
       
   464 <pre>&lt;!--{{{--&gt;
       
   465 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
       
   466 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
       
   467 &lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
       
   468 &lt;div macro='annotations'&gt;&lt;/div&gt;
       
   469 &lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
       
   470 &lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
       
   471 &lt;!--}}}--&gt;</pre>
       
   472 </div>
       
   473 <div title="GettingStarted">
       
   474 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
       
   475 * SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
       
   476 * MainMenu: The menu (usually on the left)
       
   477 * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
       
   478 You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
       
   479 </div>
       
   480 <div title="OptionsPanel">
       
   481 <pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser
       
   482 
       
   483 Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)
       
   484 
       
   485 &lt;&lt;option txtUserName&gt;&gt;
       
   486 &lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
       
   487 &lt;&lt;option chkAutoSave&gt;&gt; AutoSave
       
   488 &lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
       
   489 &lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
       
   490 &lt;&lt;option chkAnimate&gt;&gt; EnableAnimations
       
   491 
       
   492 ----
       
   493 Also see AdvancedOptions</pre>
       
   494 </div>
       
   495 <div title="ImportTiddlers">
       
   496 <pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
       
   497 </div>
       
   498 </div>
       
   499 <!--POST-SHADOWAREA-->
       
   500 <div id="storeArea">
       
   501 <div title="AdamCrossland" modifier="AdamCrossland" created="200805201949" modified="200805201951" changecount="2">
       
   502 <pre>Adam is the author of ''Taggable.''  To find out more about him and other software that he has created, visit http://www.adamcrossland.net.  You can also visit his blog, http://blog.adamcrossland.net, to see the code in action; he created ''Taggable'' as part of the blogging software project the he wrote as a vehicle to learn about [[Google AppEngine]].</pre>
       
   503 </div>
       
   504 <div title="Apache 2.0 Open Source license" modifier="AdamCrossland" created="200805202001" modified="200805211430" changecount="5">
       
   505 <pre>{{{
       
   506 Copyright 2008 Adam A. Crossland
       
   507 
       
   508 Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
       
   509 you may not use this file except in compliance with the License.
       
   510 You may obtain a copy of the License at
       
   511 
       
   512 http://www.apache.org/licenses/LICENSE-2.0
       
   513 
       
   514 Unless required by applicable law or agreed to in writing, software
       
   515 distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
       
   516 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
   517 See the License for the specific language governing permissions and
       
   518 limitations under the License.
       
   519 }}}</pre>
       
   520 </div>
       
   521 <div title="ChangeLog" modifier="AdamCrossland" created="200812041728" modified="200812042045" changecount="9">
       
   522 <pre>''1.0''
       
   523 Initial release
       
   524 ----
       
   525 ''2.0''
       
   526 This release has major changes to both [[Tag|Tag Class]] and [[Taggable|Taggable Class]], reflecting significant and important lessons that [[the author|AdamCrossland]] learned about [[Google AppEngine]].  Specifically, this release's changes should substantially improve the performance of any code that uses tags.
       
   527 *[[Tag|Tag Class]] entities are now stored with a custom key_name that allows them to be much-more-quickly retrieved from the data store.
       
   528 *The [[Tag|Tag Class]] has been rewritten to breakdown Tag/Taggable interactions in to a series of simple, atomic transactions.
       
   529 *[[Tag|Tag Class]] has a new property, [[tagged_count|Tag tagged_count Property]], that records the number of Taggable entities in its [[tagged|Tag tagged Property]] property.
       
   530 *Two new methods, [[get_tags_by_frequency|Tag get_tags_by_frequency Method]] and [[popular_tags|Tag popular_tags Method]], provide access to lists of [[Tags|Tag Class]] based on the number of [[Taggable|Taggable Class]] entities to which they refer.
       
   531 *A new method, [[get_tags_by_name|Tag get_tags_by_name Method]], provides lists of tags in alphabetical order.
       
   532 *The [[Taggable|Taggable Class]] class has been greatly simplified.  Multiple methods for getting and setting tags have been replaced with a single property, [[tags|Taggable tags Property]], that handles both getting and setting operations.
       
   533 *A full suite of automated unit tests for both classes has been created.</pre>
       
   534 </div>
       
   535 <div title="Contact" modifier="AdamCrossland" created="200805211444" changecount="1">
       
   536 <pre>If you have any questions or suggestions about ''Taggable'', please feel free to contact the author, AdamCrossland, at adam@adamcrossland.net.  Alternatively, you could post the question in &lt;html&gt;&lt;a href=&quot;http://groups.google.com/group/google-appengine/topics&quot;&gt;the Google AppEngine Group.&lt;/a&gt;&lt;/html&gt;</pre>
       
   537 </div>
       
   538 <div title="Contribute" modifier="AdamCrossland" created="200805211441" changecount="1">
       
   539 <pre>If you are using ''Taggable'' and you have some changes, improvements or bug fixes that you'd like to contribute, please contact AdamCrossland at adam@adamcrossland.net.</pre>
       
   540 </div>
       
   541 <div title="Converting from 1.0 to 2.0" modifier="AdamCrossland" created="200812051535" modified="200812051536" changecount="2">
       
   542 <pre>Because the Tag records are stored and queried slightly differently (yet much-more efficiently) in //Taggable-mixin 2.0//, an existing application will have to go through a process of converting all of its existing tags.
       
   543 
       
   544 For AdamCBlog, the application from which //Taggable-mixin// is extracted, I added a new RequestHandler called UpdateTag:
       
   545 {{{
       
   546 class UpdateTag(RequestHandler):
       
   547     def get(self):
       
   548         from taggable import Tag
       
   549         tag_name = self.request.get('tag')
       
   550         tag = db.Query(Tag).filter('tag =', tag_name).fetch(1)[0]
       
   551         if tag is not None:
       
   552             new_tag = Tag.get_or_create(tag_name)
       
   553             for each_tagged in tag.tagged:
       
   554                 new_tag.add_tagged(each_tagged)
       
   555         tag.delete()
       
   556     
       
   557         self.redirect('/')
       
   558 }}}
       
   559 
       
   560 Then, I made the request /~UpdateTag?tag=//inserttaghere// for each Tag in the datastore.  Yes, it is laborious.
       
   561 
       
   562 Why not just create a RequestHandler that cycled through all of the tags automatically, converting them as it went?  I tried that, but there is too much processing involved; the request uses up its time quota and is killed before it can finish.</pre>
       
   563 </div>
       
   564 <div title="DateTimeProperty" modifier="AdamCrossland" created="200812042027" tags="appengine" changecount="1">
       
   565 <pre>A [[Google AppEngine]] datastore property that holds a Python //datetime//.
       
   566 
       
   567 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#DateTimeProperty</pre>
       
   568 </div>
       
   569 <div title="DefaultTiddlers" modifier="AdamCrossland" created="200805202104" changecount="1">
       
   570 <pre>[[Introduction]]</pre>
       
   571 </div>
       
   572 <div title="Google AppEngine" modifier="AdamCrossland" created="200805201811" modified="200805202010" changecount="3">
       
   573 <pre>Google ~AppEngine is Google's platform for developing web applications that run inside Google's computing cloud.
       
   574 
       
   575 Find out more about it at &lt;html&gt;&lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;The Google AppEngine website&lt;/a&gt;&lt;/html&gt;.
       
   576 
       
   577 </pre>
       
   578 </div>
       
   579 <div title="IntegerProperty" modifier="AdamCrossland" created="200812041949" tags="appengine" changecount="1">
       
   580 <pre>A [[Google AppEngine]] datastore property that holds a Python //long//.
       
   581 
       
   582 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#IntegerProperty</pre>
       
   583 </div>
       
   584 <div title="Introduction" modifier="AdamCrossland" created="200805201810" modified="200812051536" changecount="8">
       
   585 <pre>''Taggable'' is a &lt;html&gt;&lt;a href=&quot;http://www.linuxjournal.com/node/4540/print&quot;&gt;mixin&lt;/a&gt;&lt;/html&gt; class that AdamCrossland created in order to add [[tags|Tag Definition]] to his personal blog (http://blog.adamcrossland.net) which is built on [[Google AppEngine]].  The design seemed clean and portable, so he decided to share it with the greater [[AppEngine|Google AppEngine]] community.  It is available under [[Apache 2.0 Open Source license]].
       
   586 
       
   587 Users of taggable-mixin 1.0 should note that taggable-mixin 2.0 is not directly compatible with 1.0.  While it is possible to upgrade, existing Tag entities will have to be [[converted|Converting from 1.0 to 2.0]].</pre>
       
   588 </div>
       
   589 <div title="Key Class" modifier="AdamCrossland" created="200812042055" tags="appengine" changecount="1">
       
   590 <pre>A [[Google AppEngine]] class that represents a unique key for a datastore entity.
       
   591 
       
   592 http://code.google.com/appengine/docs/datastore/keyclass.html</pre>
       
   593 </div>
       
   594 <div title="ListProperty" modifier="AdamCrossland" created="200812042030" tags="appengine" changecount="1">
       
   595 <pre>A [[Google AppEngine]] datastore property that represents a Python //list//.
       
   596 
       
   597 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#ListProperty</pre>
       
   598 </div>
       
   599 <div title="MainMenu" modifier="AdamCrossland" created="200805201814" modified="200812041913" changecount="10">
       
   600 <pre>[[Introduction|Introduction]]
       
   601 ChangeLog
       
   602 StepByStep
       
   603 &lt;&lt;tag api&gt;&gt;
       
   604 ----
       
   605 [[License|Apache 2.0 Open Source license]]
       
   606 [[Contribute]]
       
   607 [[Contact]]</pre>
       
   608 </div>
       
   609 <div title="RequestHandler" modifier="AdamCrossland" created="200805211437" modified="200805211438" changecount="4">
       
   610 <pre>A //~RequestHandler// is a Python class that inherits from //webapp.~RequestHandler//.  It is used to answer HTTP requests that are received by your [[Google AppEngine]] application.  For more information on this subject, please consult the &lt;html&gt;&lt;a href=&quot;http://code.google.com/appengine/docs/webapp/requesthandlers.html&quot;&gt;documentation.&lt;/a&gt;&lt;/html&gt;
       
   611 
       
   612 </pre>
       
   613 </div>
       
   614 <div title="SideBarOptions" modifier="AdamCrossland" created="200805211439" changecount="1">
       
   615 <pre>&lt;&lt;search&gt;&gt;&lt;&lt;closeAll&gt;&gt;&lt;&lt;permaview&gt;&gt;&lt;&lt;newTiddler&gt;&gt;&lt;&lt;saveChanges&gt;&gt;&lt;&lt;slider chkSliderOptionsPanel OptionsPanel &quot;options »&quot; &quot;Change TiddlyWiki advanced options&quot;&gt;&gt;</pre>
       
   616 </div>
       
   617 <div title="SiteSubtitle" modifier="AdamCrossland" created="200805201806" modified="200805201807" changecount="2">
       
   618 <pre>a portable mixin class for adding Tags to Google ~AppEngine Models</pre>
       
   619 </div>
       
   620 <div title="SiteTitle" modifier="AdamCrossland" created="200805201805" changecount="1">
       
   621 <pre>Taggable</pre>
       
   622 </div>
       
   623 <div title="StepByStep" modifier="AdamCrossland" created="200805201831" modified="200812041926" tags="help examples code" changecount="14">
       
   624 <pre>Here's a step-by-step guide to adding ''Taggable'' to your [[Google AppEngine]] application.  This presents the simplest, most straightforward path to integrating ''Taggable'' as as such, it does not cover all of the options that are available.  
       
   625 
       
   626 All of the code examples are modified extracts from the blogging software for which I originally created ''Taggable.''  //''These examples do not represent what the author considers to be complete and secure code; please make sure that you are familiar with best practices for building secure web applications before creating a web application.  The author of this document and the accompanying code bears no responsibility whatsoever for any losses or damages that you incur as a result of failing to take the appropriate steps to create a stable, secure, well-written web application.''//
       
   627 
       
   628 *Copy [[taggable.py]] to your application directory.
       
   629 *Import [[taggable.py]] into the python file that defines the Model that you want to make taggable:
       
   630 {{{
       
   631 from taggable import Taggable
       
   632 }}}
       
   633 *Add //Taggable// to the list of classes from which your Model class inherits.  Taggable -- and any other mixin classes -- should come before db.Model:
       
   634 {{{
       
   635 class Post(Taggable, db.Model):
       
   636 }}}
       
   637 *Add code to your Model's //init// method to call Taggable's //init// method.  If your Model class does not already override //init//, it will have to, and you will have to explicitly call the //init// method of any other superclass -- such as db.Model:
       
   638 {{{
       
   639     def __init__(self, parent=None, key_name=None, app=None, **entity_values):
       
   640         db.Model.__init__(self, parent, key_name, app, **entity_values)
       
   641         Taggable.__init__(self)
       
   642 }}}
       
   643 *Add code to your template to express the tagging information:
       
   644 {{{
       
   645 {% if post.tags %}
       
   646     &lt;div class=&quot;posttags&quot;&gt;tags:&amp;nbsp;
       
   647         {% for each_tag in post.tags %}
       
   648             &lt;a href=&quot;/searchbytag?tag={{ each_tag.tag|escape }}&quot;&gt;{{ each_tag.tag }}&lt;/a&gt;{% if forloop.last %}{% else %}, {% endif %}
       
   649         {% endfor %}
       
   650     &lt;/div&gt;
       
   651 {% endif %}
       
   652 }}}
       
   653 or
       
   654 {{{
       
   655 &lt;tr&gt;
       
   656     &lt;td&gt;Tags:&lt;/td&gt;
       
   657     &lt;td&gt;
       
   658         &lt;input type=&quot;TEXT&quot; name=&quot;tags&quot; size=&quot;106&quot; value=&quot;{% if post %}{{ post.tags_string }}{% endif %}&quot; /&gt;
       
   659     &lt;/td&gt;
       
   660 &lt;/tr&gt;
       
   661 }}}
       
   662 *In any RequestHandler method that updates a Model object that has tags associated with it,  assign any new value to the ''Taggable'' object's [[tags|TaggableTagsProperty]]:
       
   663 {{{
       
   664 class EditPost(SmartHandler.SmartHandler):
       
   665     def post(self):
       
   666         postid = self.request.get('id')
       
   667         post = Post.get(postid)
       
   668         post.title = self.request.get('title')
       
   669         post.body = self.request.get('body')
       
   670         post.edited = datetime.datetime.now()
       
   671         post.tags = self.request.get('tags')
       
   672         post.put()
       
   673         .
       
   674         . do whatever else you need to do in your handler...
       
   675         .
       
   676 }}}</pre>
       
   677 </div>
       
   678 <div title="StringProperty" modifier="AdamCrossland" created="200812042024" tags="appengine" changecount="1">
       
   679 <pre>A [[Google AppEngine]] datastore property that holds a Python //string// of 500 characters or less.
       
   680 
       
   681 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#StringProperty</pre>
       
   682 </div>
       
   683 <div title="Tag Class" modifier="AdamCrossland" created="200812041942" modified="200812050051" tags="api" changecount="6">
       
   684 <pre>The //Tag// class is the Model class that holds the data for an individual [[tag|Tag Definition]].
       
   685 
       
   686 It has four properties:
       
   687 *[[tag|Tag tag Property]]
       
   688 *[[added|Tag tag_added Property]]
       
   689 *[[tagged|Tag tagged Property]]
       
   690 *[[tagged_count|Tag tagged_count Property]]
       
   691 
       
   692 and ten methods:
       
   693 *[[remove_tagged|Tag remove_tagged Method]]
       
   694 *[[add_tagged|Tag add_tagged Method]]
       
   695 *[[clear_tagged|Tag clear_tagged Method]]
       
   696 *[[get_by_name|Tag get_by_name Method]]
       
   697 *[[get_tags_for_key|Tag get_tags_for_key Method]]
       
   698 *[[get_or_create|Tag get_or_create Method]]
       
   699 *[[get_tags_by_frequency|Tag get_tags_by_frequency Method]]
       
   700 *[[get_tags_by_name|Tag get_tags_by_name Method]]
       
   701 *[[popular_tags|Tag popular_tags Method]]
       
   702 *[[expire_cached_tags|Tag expire_cached_tags Method]]</pre>
       
   703 </div>
       
   704 <div title="Tag Definition" modifier="AdamCrossland" created="200812050049" changecount="1">
       
   705 <pre>A //Tag// is a word or short phrase that acts as metadata; it describes and increases the searchability and findability of data.
       
   706 
       
   707 http://en.wikipedia.org/wiki/Tag_(metadata)</pre>
       
   708 </div>
       
   709 <div title="Tag add_tagged Method" modifier="AdamCrossland" created="200812042059" modified="200812042332" tags="api" changecount="3">
       
   710 <pre>//tag_instance.add_tagged(key)//
       
   711 * key - a [[Key|Key Class]] for a //Taggable// Model to mark with the [[Tag|Tag Class]]
       
   712 Returns: nothing
       
   713 
       
   714 The //add_tagged// method adds the [[Key|Key Class]] passed in to the Tag's [[tagged|Tag tagged Property]] collection and increments the [[tagged_count|Tag tagged_count Property]].  The operations are performed inside a transaction to ensure data intergity.
       
   715 
       
   716 Under most circumstances, the programmer will not need to call //add_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
       
   717 </div>
       
   718 <div title="Tag clear_tagged Method" modifier="AdamCrossland" created="200812042103" modified="200812042332" tags="api" changecount="2">
       
   719 <pre>//tag_instance.clear_tagged(key)//
       
   720 * key - a [[Key|Key Class]] for a //Taggable// object.
       
   721 Returns: nothing
       
   722 
       
   723 The //clear_tagged// method empties the Tag's [[tagged|Tag tagged Property]] collection and sets the [[tagged_count|Tag tagged_count Property]] to zero.  The operations are performed inside a transaction to ensure data intergity.
       
   724 
       
   725 Under most circumstances, the programmer will not need to call //clear_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
       
   726 </div>
       
   727 <div title="Tag expire_cached_tags Method" modifier="AdamCrossland" created="200812050015" tags="api" changecount="1">
       
   728 <pre>//Tag.expire_cached_tags()//
       
   729 Returns: nothing
       
   730 
       
   731 Comments:
       
   732 This method removes from memcache any objects that have been cached by other [[Tag|Tag Class]] methods.  Usually, the programmer will not call this method directly, as it is used behind-the-scenes by code that affects the validity of the cached items.  It is available, however, in case the programmer wishes to customize existing behavior.</pre>
       
   733 </div>
       
   734 <div title="Tag get_by_name Method" modifier="AdamCrossland" created="200812042151" modified="200812042227" tags="api example" changecount="5">
       
   735 <pre>//Tag.get_by_name(tag_name)//
       
   736 *tag_name - the tag, in text form
       
   737 Returns: a [[Tag|Tag Class]] or //None//
       
   738 
       
   739 Example:
       
   740 {{{
       
   741 class SearchByTag(RequestHandler):
       
   742     def get(self):
       
   743         from post import Post
       
   744         from taggable import Tag
       
   745         
       
   746         requested_tag = self.request.get('tag')
       
   747         if requested_tag is not None and len(requested_tag) &gt; 0:
       
   748             tag = Tag.get_by_name(requested_tag)
       
   749             posts = None
       
   750             if tag is not None:
       
   751                 posts = Post.get(tag.tagged)
       
   752 
       
   753             self.template_values['posts'] = posts
       
   754 }}}
       
   755 </pre>
       
   756 </div>
       
   757 <div title="Tag get_or_create Method" modifier="AdamCrossland" created="200812042332" modified="200812042334" tags="api" changecount="2">
       
   758 <pre>//Tag.get_or_create(tag_name)//
       
   759 *tag_name - the name of the tag that is to be retrieved from or created in the datastore
       
   760 Returns: a [[Tag|Tag Class]]
       
   761 
       
   762 Example:
       
   763 {{{
       
   764 for each_tag in tags:
       
   765     each_tag = string.strip(each_tag)
       
   766     if len(each_tag) &gt; 0 and each_tag not in self.__tags:
       
   767         # A tag that was not previously assigned to this entity
       
   768         # is present in the list that is being assigned, so we
       
   769         # associate this entity with the tag.
       
   770         tag = Tag.get_or_create(each_tag)
       
   771         tag.add_tagged(self.key())
       
   772         self.__tags.append(tag)
       
   773 }}}</pre>
       
   774 </div>
       
   775 <div title="Tag get_tags_by_frequency Method" modifier="AdamCrossland" created="200812042356" tags="api" changecount="1">
       
   776 <pre>//Tag.get_tags_by_frequency(limit=1000)//
       
   777 *limit - the number of records to return; the maximum is 1000
       
   778 Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of //Taggable// objects assigned to it.
       
   779 
       
   780 Example:
       
   781 {{{
       
   782 @classmethod
       
   783 def popular_tags(cls, limit=5):
       
   784     from google.appengine.api import memcache
       
   785         
       
   786     tags = memcache.get('popular_tags')
       
   787     if tags is None:
       
   788         tags = Tag.get_tags_by_frequency(limit)
       
   789         memcache.add('popular_tags', tags, 3600)
       
   790         
       
   791     return tags
       
   792 }}}</pre>
       
   793 </div>
       
   794 <div title="Tag get_tags_by_name Method" modifier="AdamCrossland" created="200812050018" tags="api" changecount="1">
       
   795 <pre>//Tag.get_tags_by_name(tag_name)//
       
   796 *tag_name - the string value of a [[Tag|Tag Class]] to be retrieved from the datastore
       
   797 Returns: a [[Tag|Tag Class]] object or None if the given //tag_name// does not exist
       
   798 
       
   799 Example:
       
   800 {{{
       
   801 requested_tag = self.request.get('tag')
       
   802 if requested_tag is not None and len(requested_tag) &gt; 0:
       
   803     tag = Tag.get_by_name(requested_tag)
       
   804     posts = None
       
   805     if tag is not None:
       
   806         posts = Post.get(tag.tagged)
       
   807 
       
   808     self.template_values['posts'] = posts
       
   809 }}}</pre>
       
   810 </div>
       
   811 <div title="Tag get_tags_for_key Method" modifier="AdamCrossland" created="200812042318" modified="200812042355" tags="api example" changecount="2">
       
   812 <pre>//Tag.get_tags_for_key(key)//
       
   813 *key - a [[Key|Key Class]] for a //Taggable// object.
       
   814 Returns: a //list// of [[Tag|Tag Class]] objects
       
   815 
       
   816 Example:
       
   817 {{{
       
   818 def __get_tags(self):
       
   819     &quot;Get a List of Tag objects for all Tags that apply to this object.&quot;
       
   820     if self.__tags is None or len(self.__tags) == 0:
       
   821         self.__tags = Tag.get_tags_for_key(self.key())
       
   822     return self.__tags
       
   823 }}}</pre>
       
   824 </div>
       
   825 <div title="Tag popular_tags Method" modifier="AdamCrossland" created="200812050000" tags="api" changecount="1">
       
   826 <pre>//Tag.popular_tags(limit=5)//
       
   827 *limit - the number of records to return; the default is 5 and the maximum is 1000
       
   828 Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of [[Taggable|Taggable Class]] objects assigned to it.
       
   829 
       
   830 Comments:
       
   831 This method is a thin wrapper around [[get_tags_by_frequency|Tag get_tags_by_frequency]] that caches the returned values.
       
   832 
       
   833 Example:
       
   834 {{{
       
   835 self.template_values['popular_tags'] = Tag.popular_tags()
       
   836 }}}</pre>
       
   837 </div>
       
   838 <div title="Tag remove_tagged Method" modifier="AdamCrossland" created="200812042054" modified="200812042332" tags="api" changecount="5">
       
   839 <pre>//tag_instance.remove_tagged(key)//
       
   840 * key - a [[Key|Key Class]] for a //Taggable// object.
       
   841 Returns: nothing
       
   842 
       
   843 The //remove_tagged// method removes the [[Key|Key Class]] passed in from the Tag's [[tagged|Tag tagged Property]] collection and decrements the [[tagged_count|Tag tagged_count Property]].  The operations are performed inside a transaction to ensure data intergity.
       
   844 
       
   845 Under most circumstances, the programmer will not need to call //remove_tagged// directly; it is meant to be used internally by the [[Tagged Class]].  However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
       
   846 </div>
       
   847 <div title="Tag tag Property" modifier="AdamCrossland" created="200812042022" tags="api" changecount="1">
       
   848 <pre>A StringProperty that holds the name or value of the tag.</pre>
       
   849 </div>
       
   850 <div title="Tag tag_added Property" modifier="AdamCrossland" created="200812042025" modified="200812042026" tags="api" changecount="2">
       
   851 <pre>The DateTimeProperty that records that date and time that the [[Tag|TagClass]] was first added.</pre>
       
   852 </div>
       
   853 <div title="Tag tagged Property" modifier="AdamCrossland" created="200812042028" tags="api" changecount="1">
       
   854 <pre>A ListProperty that holds the Keys of all of the entities that have the given [[Tag|TagClass]] assigned to them.</pre>
       
   855 </div>
       
   856 <div title="Tag tagged_count Property" modifier="AdamCrossland" created="200812041947" modified="200812042018" tags="api" changecount="3">
       
   857 <pre>An IntegerProperty that holds the number of items that have the Tag assigned to them.</pre>
       
   858 </div>
       
   859 <div title="Taggable Class" modifier="AdamCrossland" created="200812050054" modified="200812051431" tags="api" changecount="8">
       
   860 <pre>The //Taggable// class can be mixed-in to any other Python class that inherits from //db.Model// in order to associate [[Tags|Tag Definition]] with that model.  For instance, if you were creating blogging software, one of your fundamental objects would be a //Post// that would comprise all of the information about an individual entry in your blog.  Usually, a Post can have [[Tags|Tag Definition]] added to it in order to provide easily-digested information to the reader about the content.
       
   861 
       
   862 It has two properties:
       
   863 *[[tags|Taggable tags Property]]
       
   864 *[[tag_separator|Taggable tag_separator Property]]
       
   865 
       
   866 and one method:
       
   867 *[[tags_string|Taggable tags_string Method]]
       
   868 
       
   869 Example:
       
   870 {{{
       
   871 class Post(Taggable, db.Model):
       
   872     index = db.IntegerProperty(required=True, default=0)
       
   873     body = db.TextProperty(required = True)
       
   874     title = db.StringProperty()
       
   875     added = db.DateTimeProperty(auto_now_add=True)
       
   876     added_month = db.IntegerProperty()
       
   877     added_year = db.IntegerProperty()
       
   878     edited = db.DateTimeProperty()
       
   879         
       
   880     def __init__(self, parent=None, key_name=None, app=None, **entity_values):
       
   881         db.Model.__init__(self, parent, key_name, app, **entity_values)
       
   882         Taggable.__init__(self)
       
   883 }}}
       
   884 Notes:
       
   885 *Notice that [[Taggable|Taggable Class]] is placed before db.Model in the inheritance list.  While this is not strictly required for //Taggable-mixin// to function correctly, it //is// required for other mixin classes, and it is generally a good practice.
       
   886 *It //is// required that the [[Taggable|Taggable Class]] //init// method is explicitly called, so it should be placed in the inherting class's //init// method.  If the class would not otherwise have one, it should be created, and the db.Model //init// must be called as well, as shown.</pre>
       
   887 </div>
       
   888 <div title="Taggable tag_separator Property" modifier="AdamCrossland" created="200805201936" modified="200812042033" tags="api" changecount="6">
       
   889 <pre>The //tag_separator// property is the string that is used to separate individual tags in a string representation of a list of tags.  It is used by the [[tags property|TaggableTagsProperty]] to parse a string that may contain one or more tags to be applied to a [[Taggable|TaggableClass]] object.  It is also used by the [[tags_string|TaggableTagsStringMethod]] method to construct a string representation of the tags for a [[Taggable|TaggableClass]] object.
       
   890 
       
   891 *By default, it is set to a comma (','), but it can be programatically-set to whatever value the developer desires
       
   892 *It is probably best to avoid using whitespace characters, as that would prevent users from entering multi-word tags
       
   893 *Custom separator values can be set at different levels.
       
   894 **To set one value for all [[Taggable|TaggableClass]] objects, modify the //init// method in the //Taggable// class in the taggable.py file:
       
   895 {{{
       
   896 class Taggable:
       
   897      def __init__(self):
       
   898         self.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma
       
   899 }}}
       
   900 **To set one value for all instances of a particular [[Taggable|TaggableClass]] class, modify the //init// method of that class:
       
   901 {{{
       
   902 class Post(Taggable, db.Model):
       
   903     body = db.TextProperty(required = True)
       
   904     title = db.StringProperty()
       
   905     added = db.DateTimeProperty(auto_now_add=True)
       
   906     edited = db.DateTimeProperty()
       
   907             
       
   908     def __init__(self, parent=None, key_name=None, app=None, **entity_values):
       
   909         db.Model.__init__(self, parent, key_name, app, **entity_values)
       
   910         Taggable.__init__(self)
       
   911         self.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma        
       
   912 }}}
       
   913 **To set a value for one particular instance of a [[Taggable|TaggableClass]] class, set the value after creating the instance:
       
   914 {{{
       
   915 newpost = Post(title = self.request.get('title'), body = self.request.get('body'))
       
   916 newpost.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma
       
   917 newpost.set_tags_from_string(&quot;foo; bar; bletch; this is a tag; tags rule&quot;     
       
   918 }}}</pre>
       
   919 </div>
       
   920 <div title="Taggable tags Property" modifier="AdamCrossland" created="200812051447" modified="200812051516" tags="api" changecount="2">
       
   921 <pre>The a [[Taggable|Taggable Class]] class's //tags// property is used to assign [[Tags|Tag Class]] to the [[Taggable|Taggable Class]] instance or retrieve a //list// of those already assigned.
       
   922 
       
   923 Assignment Example:
       
   924 {{{
       
   925 class Post(Taggable, db.Model):
       
   926     index = db.IntegerProperty(required=True, default=0)
       
   927     body = db.TextProperty(required = True)
       
   928     title = db.StringProperty()
       
   929     added = db.DateTimeProperty(auto_now_add=True)
       
   930     added_month = db.IntegerProperty()
       
   931     added_year = db.IntegerProperty()
       
   932     edited = db.DateTimeProperty()
       
   933         
       
   934     def __init__(self, parent=None, key_name=None, app=None, **entity_values):
       
   935         db.Model.__init__(self, parent, key_name, app, **entity_values)
       
   936         Taggable.__init__(self)
       
   937 
       
   938     @classmethod
       
   939     def new_post(cls, new_title=None, new_body=None, new_tags=[]):
       
   940         if new_title is not None and new_body is not None:
       
   941             
       
   942             new_post = Post(title = new_title, body = new_body)
       
   943             new_post.put()
       
   944         
       
   945             new_post.tags = new_tags
       
   946             new_post.save()
       
   947         else:
       
   948             raise Exception(&quot;Must supply both new_title and new_body when creating a new Post.&quot;)
       
   949 }}}
       
   950 
       
   951 Retrieval Example:
       
   952 {{{
       
   953 {% if post.tags %}
       
   954     &lt;div class=&quot;posttags&quot;&gt;tags:&amp;nbsp;
       
   955         {% for each_tag in post.tags %}
       
   956             &lt;a href=&quot;/searchbytag?tag={{ each_tag.tag|escape }}&quot;&gt;{{ each_tag.tag }}&lt;/a&gt;{% if forloop.last %}{% else %}, {% endif %}
       
   957         {% endfor %}
       
   958     &lt;/div&gt;
       
   959 {% endif %}
       
   960 }}}</pre>
       
   961 </div>
       
   962 <div title="Taggable tags_string Method" modifier="AdamCrossland" created="200812051441" modified="200812051441" tags="api" changecount="3">
       
   963 <pre>//taggable_instance.tags_string()//
       
   964 Returns: a string representation of the tags assigned to the [[Taggable|Taggable Class]] instance.
       
   965 
       
   966 This method simply joins the string versions of each [[Tag|Tag Class]] in the [[Taggable|Taggable Class]] class's //list//, placing the value stored in [[tag_separator|Taggable tag_separator Property]] inbetween each element.</pre>
       
   967 </div>
       
   968 <div title="TaggableAPI" modifier="AdamCrossland" created="200805201953" modified="200805201956" changecount="6">
       
   969 <pre>&lt;&lt;tagging api&gt;&gt;</pre>
       
   970 </div>
       
   971 <div title="get_tags_as_string" modifier="AdamCrossland" created="200805201952" modified="200812041913" tags="obsolete" changecount="2">
       
   972 <pre>The //get_tags_as_string// method creates a string representation of all of the tags that apply to a ''Taggable'' object.</pre>
       
   973 </div>
       
   974 <div title="set_tags" modifier="AdamCrossland" created="200805201919" modified="200812041848" tags="obsolete" changecount="2">
       
   975 <pre>The //set_tags// method sets the tags for a ''Taggable'' object from a list of strings:
       
   976 {{{
       
   977 tag_list = [&quot;foo&quot;, &quot;bar&quot;, &quot;bletch&quot;,&quot;this is a tag&quot;, &quot;tags rule&quot;]
       
   978 my_taggable_object.set_tags(tag_list)
       
   979 }}}</pre>
       
   980 </div>
       
   981 <div title="set_tags_from_string" modifier="AdamCrossland" created="200805201926" modified="200812041848" tags="obsolete" changecount="3">
       
   982 <pre>The //set_tags_from_string method// sets the tags for a Taggable object from a string that contains one or more tags separated by the character or characters in //self.tag_seperator//:
       
   983 {{{
       
   984 tag_list = &quot;foo, bar, bletch, this is a tag, tags rule&quot;
       
   985 my_taggable_object.set_tags_from_string(tag_list)
       
   986 }}}
       
   987 
       
   988 By default, [[tag_separator|tag_separator]] is set to a comma (','), but it can be anything that the programmer wants.</pre>
       
   989 </div>
       
   990 <div title="taggable.py" modifier="AdamCrossland" created="200805201904" modified="200805201946" changecount="2">
       
   991 <pre>All of the code for the ''Taggable'' mixin class lives in the file taggable.py.  Simply copy this file in to your Google ~AppEngine main directory, and it should be available to your code.</pre>
       
   992 </div>
       
   993 </div>
       
   994 <!--POST-STOREAREA-->
       
   995 <!--POST-BODY-START-->
       
   996 <!--POST-BODY-END-->
       
   997 <script id="jsArea" type="text/javascript">
       
   998 //<![CDATA[
       
   999 //
       
  1000 // Please note:
       
  1001 //
       
  1002 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
       
  1003 //   in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
       
  1004 //
       
  1005 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
       
  1006 //   without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
       
  1007 //
       
  1008 
       
  1009 //--
       
  1010 //-- Configuration repository
       
  1011 //--
       
  1012 
       
  1013 // Miscellaneous options
       
  1014 var config = {
       
  1015 	numRssItems: 20, // Number of items in the RSS feed
       
  1016 	animDuration: 400, // Duration of UI animations in milliseconds
       
  1017 	cascadeFast: 20, // Speed for cascade animations (higher == slower)
       
  1018 	cascadeSlow: 60, // Speed for EasterEgg cascade animations
       
  1019 	cascadeDepth: 5, // Depth of cascade animation
       
  1020 	locale: "en" // W3C language tag
       
  1021 };
       
  1022 
       
  1023 // Hashmap of alternative parsers for the wikifier
       
  1024 config.parsers = {};
       
  1025 
       
  1026 // Adaptors
       
  1027 config.adaptors = {};
       
  1028 config.defaultAdaptor = null;
       
  1029 
       
  1030 // Backstage tasks
       
  1031 config.tasks = {};
       
  1032 
       
  1033 // Annotations
       
  1034 config.annotations = {};
       
  1035 
       
  1036 // Custom fields to be automatically added to new tiddlers
       
  1037 config.defaultCustomFields = {};
       
  1038 
       
  1039 // Messages
       
  1040 config.messages = {
       
  1041 	messageClose: {},
       
  1042 	dates: {},
       
  1043 	tiddlerPopup: {}
       
  1044 };
       
  1045 
       
  1046 // Options that can be set in the options panel and/or cookies
       
  1047 config.options = {
       
  1048 	chkRegExpSearch: false,
       
  1049 	chkCaseSensitiveSearch: false,
       
  1050 	chkIncrementalSearch: true,
       
  1051 	chkAnimate: true,
       
  1052 	chkSaveBackups: true,
       
  1053 	chkAutoSave: false,
       
  1054 	chkGenerateAnRssFeed: false,
       
  1055 	chkSaveEmptyTemplate: false,
       
  1056 	chkOpenInNewWindow: true,
       
  1057 	chkToggleLinks: false,
       
  1058 	chkHttpReadOnly: true,
       
  1059 	chkForceMinorUpdate: false,
       
  1060 	chkConfirmDelete: true,
       
  1061 	chkInsertTabs: false,
       
  1062 	chkUsePreForStorage: true, // Whether to use <pre> format for storage
       
  1063 	chkDisplayInstrumentation: false,
       
  1064 	txtBackupFolder: "",
       
  1065 	txtEditorFocus: "text",
       
  1066 	txtMainTab: "tabTimeline",
       
  1067 	txtMoreTab: "moreTabAll",
       
  1068 	txtMaxEditRows: "30",
       
  1069 	txtFileSystemCharSet: "UTF-8",
       
  1070 	txtTheme: ""
       
  1071 	};
       
  1072 config.optionsDesc = {};
       
  1073 
       
  1074 // List of notification functions to be called when certain tiddlers are changed or deleted
       
  1075 config.notifyTiddlers = [
       
  1076 	{name: "StyleSheetLayout", notify: refreshStyles},
       
  1077 	{name: "StyleSheetColors", notify: refreshStyles},
       
  1078 	{name: "StyleSheet", notify: refreshStyles},
       
  1079 	{name: "StyleSheetPrint", notify: refreshStyles},
       
  1080 	{name: "PageTemplate", notify: refreshPageTemplate},
       
  1081 	{name: "SiteTitle", notify: refreshPageTitle},
       
  1082 	{name: "SiteSubtitle", notify: refreshPageTitle},
       
  1083 	{name: "ColorPalette", notify: refreshColorPalette},
       
  1084 	{name: null, notify: refreshDisplay}
       
  1085 ];
       
  1086 
       
  1087 // Default tiddler templates
       
  1088 var DEFAULT_VIEW_TEMPLATE = 1;
       
  1089 var DEFAULT_EDIT_TEMPLATE = 2;
       
  1090 config.tiddlerTemplates = {
       
  1091 	1: "ViewTemplate",
       
  1092 	2: "EditTemplate"
       
  1093 };
       
  1094 
       
  1095 // More messages (rather a legacy layout that shouldn't really be like this)
       
  1096 config.views = {
       
  1097 	wikified: {
       
  1098 		tag: {}
       
  1099 	},
       
  1100 	editor: {
       
  1101 		tagChooser: {}
       
  1102 	}
       
  1103 };
       
  1104 
       
  1105 // Backstage tasks
       
  1106 config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];
       
  1107 
       
  1108 // Macros; each has a 'handler' member that is inserted later
       
  1109 config.macros = {
       
  1110 	today: {},
       
  1111 	version: {},
       
  1112 	search: {sizeTextbox: 15},
       
  1113 	tiddler: {},
       
  1114 	tag: {},
       
  1115 	tags: {},
       
  1116 	tagging: {},
       
  1117 	timeline: {},
       
  1118 	allTags: {},
       
  1119 	list: {
       
  1120 		all: {},
       
  1121 		missing: {},
       
  1122 		orphans: {},
       
  1123 		shadowed: {},
       
  1124 		touched: {},
       
  1125 		filter: {}
       
  1126 	},
       
  1127 	closeAll: {},
       
  1128 	permaview: {},
       
  1129 	saveChanges: {},
       
  1130 	slider: {},
       
  1131 	option: {},
       
  1132 	options: {},
       
  1133 	newTiddler: {},
       
  1134 	newJournal: {},
       
  1135 	tabs: {},
       
  1136 	gradient: {},
       
  1137 	message: {},
       
  1138 	view: {defaultView: "text"},
       
  1139 	edit: {},
       
  1140 	tagChooser: {},
       
  1141 	toolbar: {},
       
  1142 	plugins: {},
       
  1143 	refreshDisplay: {},
       
  1144 	importTiddlers: {},
       
  1145 	upgrade: {
       
  1146 		source: "http://www.tiddlywiki.com/upgrade/",
       
  1147 		backupExtension: "pre.core.upgrade"
       
  1148 	},
       
  1149 	sync: {},
       
  1150 	annotations: {}
       
  1151 };
       
  1152 
       
  1153 // Commands supported by the toolbar macro
       
  1154 config.commands = {
       
  1155 	closeTiddler: {},
       
  1156 	closeOthers: {},
       
  1157 	editTiddler: {},
       
  1158 	saveTiddler: {hideReadOnly: true},
       
  1159 	cancelTiddler: {},
       
  1160 	deleteTiddler: {hideReadOnly: true},
       
  1161 	permalink: {},
       
  1162 	references: {type: "popup"},
       
  1163 	jump: {type: "popup"},
       
  1164 	syncing: {type: "popup"},
       
  1165 	fields: {type: "popup"}
       
  1166 };
       
  1167 
       
  1168 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
       
  1169 config.userAgent = navigator.userAgent.toLowerCase();
       
  1170 config.browser = {
       
  1171 	isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
       
  1172 	isGecko: config.userAgent.indexOf("gecko") != -1,
       
  1173 	ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
       
  1174 	isSafari: config.userAgent.indexOf("applewebkit") != -1,
       
  1175 	isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
       
  1176 	firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
       
  1177 	isOpera: config.userAgent.indexOf("opera") != -1,
       
  1178 	isLinux: config.userAgent.indexOf("linux") != -1,
       
  1179 	isUnix: config.userAgent.indexOf("x11") != -1,
       
  1180 	isMac: config.userAgent.indexOf("mac") != -1,
       
  1181 	isWindows: config.userAgent.indexOf("win") != -1
       
  1182 };
       
  1183 
       
  1184 // Basic regular expressions
       
  1185 config.textPrimitives = {
       
  1186 	upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
       
  1187 	lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
       
  1188 	anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
       
  1189 	anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
       
  1190 };
       
  1191 if(config.browser.isBadSafari) {
       
  1192 	config.textPrimitives = {
       
  1193 		upperLetter: "[A-Z\u00c0-\u00de]",
       
  1194 		lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
       
  1195 		anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
       
  1196 		anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
       
  1197 	};
       
  1198 }
       
  1199 config.textPrimitives.sliceSeparator = "::";
       
  1200 config.textPrimitives.sectionSeparator = "##";
       
  1201 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
       
  1202 config.textPrimitives.unWikiLink = "~";
       
  1203 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
       
  1204 	config.textPrimitives.lowerLetter + "+" +
       
  1205 	config.textPrimitives.upperLetter +
       
  1206 	config.textPrimitives.anyLetter + "*)|(?:" +
       
  1207 	config.textPrimitives.upperLetter + "{2,}" +
       
  1208 	config.textPrimitives.lowerLetter + "+))";
       
  1209 
       
  1210 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
       
  1211 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
       
  1212 
       
  1213 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
       
  1214 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
       
  1215 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
       
  1216 	config.textPrimitives.brackettedLink + ")|(?:" +
       
  1217 	config.textPrimitives.urlPattern + ")","mg");
       
  1218 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
       
  1219 	config.textPrimitives.titledBrackettedLink + ")|(?:" +
       
  1220 	config.textPrimitives.brackettedLink + ")|(?:" +
       
  1221 	config.textPrimitives.urlPattern + ")","mg");
       
  1222 
       
  1223 config.glyphs = {
       
  1224 	browsers: [
       
  1225 		function() {return config.browser.isIE;},
       
  1226 		function() {return true;}
       
  1227 	],
       
  1228 	currBrowser: null,
       
  1229 	codes: {
       
  1230 		downTriangle: ["\u25BC","\u25BE"],
       
  1231 		downArrow: ["\u2193","\u2193"],
       
  1232 		bentArrowLeft: ["\u2190","\u21A9"],
       
  1233 		bentArrowRight: ["\u2192","\u21AA"]
       
  1234 	}
       
  1235 };
       
  1236 
       
  1237 //--
       
  1238 //-- Shadow tiddlers
       
  1239 //--
       
  1240 
       
  1241 config.shadowTiddlers = {
       
  1242 	StyleSheet: "",
       
  1243 	MarkupPreHead: "",
       
  1244 	MarkupPostHead: "",
       
  1245 	MarkupPreBody: "",
       
  1246 	MarkupPostBody: "",
       
  1247 	TabTimeline: '<<timeline>>',
       
  1248 	TabAll: '<<list all>>',
       
  1249 	TabTags: '<<allTags excludeLists>>',
       
  1250 	TabMoreMissing: '<<list missing>>',
       
  1251 	TabMoreOrphans: '<<list orphans>>',
       
  1252 	TabMoreShadowed: '<<list shadowed>>',
       
  1253 	AdvancedOptions: '<<options>>',
       
  1254 	PluginManager: '<<plugins>>'
       
  1255 };
       
  1256 
       
  1257 //--
       
  1258 //-- Translateable strings
       
  1259 //--
       
  1260 
       
  1261 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
       
  1262 
       
  1263 merge(config.options,{
       
  1264 	txtUserName: "YourName"});
       
  1265 
       
  1266 merge(config.tasks,{
       
  1267 	save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
       
  1268 	sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
       
  1269 	importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
       
  1270 	tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
       
  1271 	upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
       
  1272 	plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
       
  1273 });
       
  1274 
       
  1275 // Options that can be set in the options panel and/or cookies
       
  1276 merge(config.optionsDesc,{
       
  1277 	txtUserName: "Username for signing your edits",
       
  1278 	chkRegExpSearch: "Enable regular expressions for searches",
       
  1279 	chkCaseSensitiveSearch: "Case-sensitive searching",
       
  1280 	chkIncrementalSearch: "Incremental key-by-key searching",
       
  1281 	chkAnimate: "Enable animations",
       
  1282 	chkSaveBackups: "Keep backup file when saving changes",
       
  1283 	chkAutoSave: "Automatically save changes",
       
  1284 	chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
       
  1285 	chkSaveEmptyTemplate: "Generate an empty template when saving changes",
       
  1286 	chkOpenInNewWindow: "Open external links in a new window",
       
  1287 	chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
       
  1288 	chkHttpReadOnly: "Hide editing features when viewed over HTTP",
       
  1289 	chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
       
  1290 	chkConfirmDelete: "Require confirmation before deleting tiddlers",
       
  1291 	chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
       
  1292 	txtBackupFolder: "Name of folder to use for backups",
       
  1293 	txtMaxEditRows: "Maximum number of rows in edit boxes",
       
  1294 	txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
       
  1295 
       
  1296 merge(config.messages,{
       
  1297 	customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
       
  1298 	pluginError: "Error: %0",
       
  1299 	pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
       
  1300 	pluginForced: "Executed because forced via 'systemConfigForce' tag",
       
  1301 	pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
       
  1302 	nothingSelected: "Nothing is selected. You must select one or more items first",
       
  1303 	savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
       
  1304 	subtitleUnknown: "(unknown)",
       
  1305 	undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
       
  1306 	shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
       
  1307 	tiddlerLinkTooltip: "%0 - %1, %2",
       
  1308 	externalLinkTooltip: "External link to %0",
       
  1309 	noTags: "There are no tagged tiddlers",
       
  1310 	notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
       
  1311 	cantSaveError: "It's not possible to save changes. Possible reasons include:\n- your browser doesn't support saving (Firefox, Internet Explorer, Safari and Opera all work if properly configured)\n- the pathname to your TiddlyWiki file contains illegal characters\n- the TiddlyWiki HTML file has been moved or renamed",
       
  1312 	invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
       
  1313 	backupSaved: "Backup saved",
       
  1314 	backupFailed: "Failed to save backup file",
       
  1315 	rssSaved: "RSS feed saved",
       
  1316 	rssFailed: "Failed to save RSS feed file",
       
  1317 	emptySaved: "Empty template saved",
       
  1318 	emptyFailed: "Failed to save empty template file",
       
  1319 	mainSaved: "Main TiddlyWiki file saved",
       
  1320 	mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
       
  1321 	macroError: "Error in macro <<\%0>>",
       
  1322 	macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
       
  1323 	missingMacro: "No such macro",
       
  1324 	overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
       
  1325 	unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
       
  1326 	confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
       
  1327 	saveInstructions: "SaveChanges",
       
  1328 	unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
       
  1329 	tiddlerSaveError: "Error when saving tiddler '%0'",
       
  1330 	tiddlerLoadError: "Error when loading tiddler '%0'",
       
  1331 	wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
       
  1332 	invalidFieldName: "Invalid field name %0",
       
  1333 	fieldCannotBeChanged: "Field '%0' cannot be changed",
       
  1334 	loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
       
  1335 	upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});
       
  1336 
       
  1337 merge(config.messages.messageClose,{
       
  1338 	text: "close",
       
  1339 	tooltip: "close this message area"});
       
  1340 
       
  1341 config.messages.backstage = {
       
  1342 	open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
       
  1343 	close: {text: "close", tooltip: "Close the backstage area"},
       
  1344 	prompt: "backstage: ",
       
  1345 	decal: {
       
  1346 		edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
       
  1347 	}
       
  1348 };
       
  1349 
       
  1350 config.messages.listView = {
       
  1351 	tiddlerTooltip: "Click for the full text of this tiddler",
       
  1352 	previewUnavailable: "(preview not available)"
       
  1353 };
       
  1354 
       
  1355 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
       
  1356 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
       
  1357 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
       
  1358 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
       
  1359 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
       
  1360 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
       
  1361 		"th","th","th","th","th","th","th","th","th","th",
       
  1362 		"st","nd","rd","th","th","th","th","th","th","th",
       
  1363 		"st"];
       
  1364 config.messages.dates.am = "am";
       
  1365 config.messages.dates.pm = "pm";
       
  1366 
       
  1367 merge(config.messages.tiddlerPopup,{
       
  1368 	});
       
  1369 
       
  1370 merge(config.views.wikified.tag,{
       
  1371 	labelNoTags: "no tags",
       
  1372 	labelTags: "tags: ",
       
  1373 	openTag: "Open tag '%0'",
       
  1374 	tooltip: "Show tiddlers tagged with '%0'",
       
  1375 	openAllText: "Open all",
       
  1376 	openAllTooltip: "Open all of these tiddlers",
       
  1377 	popupNone: "No other tiddlers tagged with '%0'"});
       
  1378 
       
  1379 merge(config.views.wikified,{
       
  1380 	defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
       
  1381 	defaultModifier: "(missing)",
       
  1382 	shadowModifier: "(built-in shadow tiddler)",
       
  1383 	dateFormat: "DD MMM YYYY",
       
  1384 	createdPrompt: "created"});
       
  1385 
       
  1386 merge(config.views.editor,{
       
  1387 	tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
       
  1388 	defaultText: "Type the text for '%0'"});
       
  1389 
       
  1390 merge(config.views.editor.tagChooser,{
       
  1391 	text: "tags",
       
  1392 	tooltip: "Choose existing tags to add to this tiddler",
       
  1393 	popupNone: "There are no tags defined",
       
  1394 	tagTooltip: "Add the tag '%0'"});
       
  1395 
       
  1396 merge(config.messages,{
       
  1397 	sizeTemplates:
       
  1398 		[
       
  1399 		{unit: 1024*1024*1024, template: "%0\u00a0GB"},
       
  1400 		{unit: 1024*1024, template: "%0\u00a0MB"},
       
  1401 		{unit: 1024, template: "%0\u00a0KB"},
       
  1402 		{unit: 1, template: "%0\u00a0B"}
       
  1403 		]});
       
  1404 
       
  1405 merge(config.macros.search,{
       
  1406 	label: "search",
       
  1407 	prompt: "Search this TiddlyWiki",
       
  1408 	accessKey: "F",
       
  1409 	successMsg: "%0 tiddlers found matching %1",
       
  1410 	failureMsg: "No tiddlers found matching %0"});
       
  1411 
       
  1412 merge(config.macros.tagging,{
       
  1413 	label: "tagging: ",
       
  1414 	labelNotTag: "not tagging",
       
  1415 	tooltip: "List of tiddlers tagged with '%0'"});
       
  1416 
       
  1417 merge(config.macros.timeline,{
       
  1418 	dateFormat: "DD MMM YYYY"});
       
  1419 
       
  1420 merge(config.macros.allTags,{
       
  1421 	tooltip: "Show tiddlers tagged with '%0'",
       
  1422 	noTags: "There are no tagged tiddlers"});
       
  1423 
       
  1424 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
       
  1425 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
       
  1426 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
       
  1427 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
       
  1428 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
       
  1429 
       
  1430 merge(config.macros.closeAll,{
       
  1431 	label: "close all",
       
  1432 	prompt: "Close all displayed tiddlers (except any that are being edited)"});
       
  1433 
       
  1434 merge(config.macros.permaview,{
       
  1435 	label: "permaview",
       
  1436 	prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
       
  1437 
       
  1438 merge(config.macros.saveChanges,{
       
  1439 	label: "save changes",
       
  1440 	prompt: "Save all tiddlers to create a new TiddlyWiki",
       
  1441 	accessKey: "S"});
       
  1442 
       
  1443 merge(config.macros.newTiddler,{
       
  1444 	label: "new tiddler",
       
  1445 	prompt: "Create a new tiddler",
       
  1446 	title: "New Tiddler",
       
  1447 	accessKey: "N"});
       
  1448 
       
  1449 merge(config.macros.newJournal,{
       
  1450 	label: "new journal",
       
  1451 	prompt: "Create a new tiddler from the current date and time",
       
  1452 	accessKey: "J"});
       
  1453 
       
  1454 merge(config.macros.options,{
       
  1455 	wizardTitle: "Tweak advanced options",
       
  1456 	step1Title: "These options are saved in cookies in your browser",
       
  1457 	step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
       
  1458 	unknownDescription: "//(unknown)//",
       
  1459 	listViewTemplate: {
       
  1460 		columns: [
       
  1461 			{name: 'Option', field: 'option', title: "Option", type: 'String'},
       
  1462 			{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
       
  1463 			{name: 'Name', field: 'name', title: "Name", type: 'String'}
       
  1464 			],
       
  1465 		rowClasses: [
       
  1466 			{className: 'lowlight', field: 'lowlight'}
       
  1467 			]}
       
  1468 	});
       
  1469 
       
  1470 merge(config.macros.plugins,{
       
  1471 	wizardTitle: "Manage plugins",
       
  1472 	step1Title: "Currently loaded plugins",
       
  1473 	step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
       
  1474 	skippedText: "(This plugin has not been executed because it was added since startup)",
       
  1475 	noPluginText: "There are no plugins installed",
       
  1476 	confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
       
  1477 	removeLabel: "remove systemConfig tag",
       
  1478 	removePrompt: "Remove systemConfig tag",
       
  1479 	deleteLabel: "delete",
       
  1480 	deletePrompt: "Delete these tiddlers forever",
       
  1481 	listViewTemplate: {
       
  1482 		columns: [
       
  1483 			{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
       
  1484 			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
       
  1485 			{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
       
  1486 			{name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
       
  1487 			{name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
       
  1488 			{name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
       
  1489 			{name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
       
  1490 			{name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
       
  1491 			{name: 'Log', field: 'log', title: "Log", type: 'StringList'}
       
  1492 			],
       
  1493 		rowClasses: [
       
  1494 			{className: 'error', field: 'error'},
       
  1495 			{className: 'warning', field: 'warning'}
       
  1496 			]}
       
  1497 	});
       
  1498 
       
  1499 merge(config.macros.toolbar,{
       
  1500 	moreLabel: "more",
       
  1501 	morePrompt: "Reveal further commands"
       
  1502 	});
       
  1503 
       
  1504 merge(config.macros.refreshDisplay,{
       
  1505 	label: "refresh",
       
  1506 	prompt: "Redraw the entire TiddlyWiki display"
       
  1507 	});
       
  1508 
       
  1509 merge(config.macros.importTiddlers,{
       
  1510 	readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
       
  1511 	wizardTitle: "Import tiddlers from another file or server",
       
  1512 	step1Title: "Step 1: Locate the server or TiddlyWiki file",
       
  1513 	step1Html: "Specify the type of the server: <select name='selTypes'><option value=''>Choose...</option></select><br>Enter the URL or pathname here: <input type='text' size=50 name='txtPath'><br>...or browse for a file: <input type='file' size=50 name='txtBrowse'><br><hr>...or select a pre-defined feed: <select name='selFeeds'><option value=''>Choose...</option></select>",
       
  1514 	openLabel: "open",
       
  1515 	openPrompt: "Open the connection to this file or server",
       
  1516 	openError: "There were problems fetching the tiddlywiki file",
       
  1517 	statusOpenHost: "Opening the host",
       
  1518 	statusGetWorkspaceList: "Getting the list of available workspaces",
       
  1519 	step2Title: "Step 2: Choose the workspace",
       
  1520 	step2Html: "Enter a workspace name: <input type='text' size=50 name='txtWorkspace'><br>...or select a workspace: <select name='selWorkspace'><option value=''>Choose...</option></select>",
       
  1521 	cancelLabel: "cancel",
       
  1522 	cancelPrompt: "Cancel this import",
       
  1523 	statusOpenWorkspace: "Opening the workspace",
       
  1524 	statusGetTiddlerList: "Getting the list of available tiddlers",
       
  1525 	errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
       
  1526 	step3Title: "Step 3: Choose the tiddlers to import",
       
  1527 	step3Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='true' name='chkSync'>Keep these tiddlers linked to this server so that you can synchronise subsequent changes</input><br><input type='checkbox' name='chkSave'>Save the details of this server in a 'systemServer' tiddler called:</input> <input type='text' size=25 name='txtSaveTiddler'>",
       
  1528 	importLabel: "import",
       
  1529 	importPrompt: "Import these tiddlers",
       
  1530 	confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
       
  1531 	step4Title: "Step 4: Importing %0 tiddler(s)",
       
  1532 	step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
       
  1533 	doneLabel: "done",
       
  1534 	donePrompt: "Close this wizard",
       
  1535 	statusDoingImport: "Importing tiddlers",
       
  1536 	statusDoneImport: "All tiddlers imported",
       
  1537 	systemServerNamePattern: "%2 on %1",
       
  1538 	systemServerNamePatternNoWorkspace: "%1",
       
  1539 	confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
       
  1540 	serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
       
  1541 	serverSaveModifier: "(System)",
       
  1542 	listViewTemplate: {
       
  1543 		columns: [
       
  1544 			{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
       
  1545 			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
       
  1546 			{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
       
  1547 			{name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
       
  1548 			],
       
  1549 		rowClasses: [
       
  1550 			]}
       
  1551 	});
       
  1552 
       
  1553 merge(config.macros.upgrade,{
       
  1554 	wizardTitle: "Upgrade TiddlyWiki core code",
       
  1555 	step1Title: "Update or repair this TiddlyWiki to the latest release",
       
  1556 	step1Html: "You are about to upgrade to the latest release of the TiddlyWiki core code (from <a href='%0' class='externalLink' target='_blank'>%1</a>). Your content will be preserved across the upgrade.<br><br>Note that core upgrades have been known to interfere with older plugins. If you run into problems with the upgraded file, see <a href='http://www.tiddlywiki.org/wiki/CoreUpgrades' class='externalLink' target='_blank'>http://www.tiddlywiki.org/wiki/CoreUpgrades</a>",
       
  1557 	errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
       
  1558 	errorNotSaved: "You must save changes before you can perform an upgrade",
       
  1559 	step2Title: "Confirm the upgrade details",
       
  1560 	step2Html_downgrade: "You are about to downgrade to TiddlyWiki version %0 from %1.<br><br>Downgrading to an earlier version of the core code is not recommended",
       
  1561 	step2Html_restore: "This TiddlyWiki appears to be already using the latest version of the core code (%0).<br><br>You can continue to upgrade anyway to ensure that the core code hasn't been corrupted or damaged",
       
  1562 	step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
       
  1563 	upgradeLabel: "upgrade",
       
  1564 	upgradePrompt: "Prepare for the upgrade process",
       
  1565 	statusPreparingBackup: "Preparing backup",
       
  1566 	statusSavingBackup: "Saving backup file",
       
  1567 	errorSavingBackup: "There was a problem saving the backup file",
       
  1568 	statusLoadingCore: "Loading core code",
       
  1569 	errorLoadingCore: "Error loading the core code",
       
  1570 	errorCoreFormat: "Error with the new core code",
       
  1571 	statusSavingCore: "Saving the new core code",
       
  1572 	statusReloadingCore: "Reloading the new core code",
       
  1573 	startLabel: "start",
       
  1574 	startPrompt: "Start the upgrade process",
       
  1575 	cancelLabel: "cancel",
       
  1576 	cancelPrompt: "Cancel the upgrade process",
       
  1577 	step3Title: "Upgrade cancelled",
       
  1578 	step3Html: "You have cancelled the upgrade process"
       
  1579 	});
       
  1580 
       
  1581 merge(config.macros.sync,{
       
  1582 	listViewTemplate: {
       
  1583 		columns: [
       
  1584 			{name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
       
  1585 			{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
       
  1586 			{name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
       
  1587 			{name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
       
  1588 			{name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
       
  1589 			{name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
       
  1590 			{name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
       
  1591 			],
       
  1592 		rowClasses: [
       
  1593 			],
       
  1594 		buttons: [
       
  1595 			{caption: "Sync these tiddlers", name: 'sync'}
       
  1596 			]},
       
  1597 	wizardTitle: "Synchronize with external servers and files",
       
  1598 	step1Title: "Choose the tiddlers you want to synchronize",
       
  1599 	step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
       
  1600 	syncLabel: "sync",
       
  1601 	syncPrompt: "Sync these tiddlers",
       
  1602 	hasChanged: "Changed while unplugged",
       
  1603 	hasNotChanged: "Unchanged while unplugged",
       
  1604 	syncStatusList: {
       
  1605 		none: {text: "...", color: "transparent"},
       
  1606 		changedServer: {text: "Changed on server", color: '#80ff80'},
       
  1607 		changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
       
  1608 		changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
       
  1609 		notFound: {text: "Not found on server", color: '#ffff80'},
       
  1610 		putToServer: {text: "Saved update on server", color: '#ff80ff'},
       
  1611 		gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
       
  1612 		}
       
  1613 	});
       
  1614 
       
  1615 merge(config.macros.annotations,{
       
  1616 	});
       
  1617 
       
  1618 merge(config.commands.closeTiddler,{
       
  1619 	text: "close",
       
  1620 	tooltip: "Close this tiddler"});
       
  1621 
       
  1622 merge(config.commands.closeOthers,{
       
  1623 	text: "close others",
       
  1624 	tooltip: "Close all other tiddlers"});
       
  1625 
       
  1626 merge(config.commands.editTiddler,{
       
  1627 	text: "edit",
       
  1628 	tooltip: "Edit this tiddler",
       
  1629 	readOnlyText: "view",
       
  1630 	readOnlyTooltip: "View the source of this tiddler"});
       
  1631 
       
  1632 merge(config.commands.saveTiddler,{
       
  1633 	text: "done",
       
  1634 	tooltip: "Save changes to this tiddler"});
       
  1635 
       
  1636 merge(config.commands.cancelTiddler,{
       
  1637 	text: "cancel",
       
  1638 	tooltip: "Undo changes to this tiddler",
       
  1639 	warning: "Are you sure you want to abandon your changes to '%0'?",
       
  1640 	readOnlyText: "done",
       
  1641 	readOnlyTooltip: "View this tiddler normally"});
       
  1642 
       
  1643 merge(config.commands.deleteTiddler,{
       
  1644 	text: "delete",
       
  1645 	tooltip: "Delete this tiddler",
       
  1646 	warning: "Are you sure you want to delete '%0'?"});
       
  1647 
       
  1648 merge(config.commands.permalink,{
       
  1649 	text: "permalink",
       
  1650 	tooltip: "Permalink for this tiddler"});
       
  1651 
       
  1652 merge(config.commands.references,{
       
  1653 	text: "references",
       
  1654 	tooltip: "Show tiddlers that link to this one",
       
  1655 	popupNone: "No references"});
       
  1656 
       
  1657 merge(config.commands.jump,{
       
  1658 	text: "jump",
       
  1659 	tooltip: "Jump to another open tiddler"});
       
  1660 
       
  1661 merge(config.commands.syncing,{
       
  1662 	text: "syncing",
       
  1663 	tooltip: "Control synchronisation of this tiddler with a server or external file",
       
  1664 	currentlySyncing: "<div>Currently syncing via <span class='popupHighlight'>'%0'</span> to:</"+"div><div>host: <span class='popupHighlight'>%1</span></"+"div><div>workspace: <span class='popupHighlight'>%2</span></"+"div>", // Note escaping of closing <div> tag
       
  1665 	notCurrentlySyncing: "Not currently syncing",
       
  1666 	captionUnSync: "Stop synchronising this tiddler",
       
  1667 	chooseServer: "Synchronise this tiddler with another server:",
       
  1668 	currServerMarker: "\u25cf ",
       
  1669 	notCurrServerMarker: "  "});
       
  1670 
       
  1671 merge(config.commands.fields,{
       
  1672 	text: "fields",
       
  1673 	tooltip: "Show the extended fields of this tiddler",
       
  1674 	emptyText: "There are no extended fields for this tiddler",
       
  1675 	listViewTemplate: {
       
  1676 		columns: [
       
  1677 			{name: 'Field', field: 'field', title: "Field", type: 'String'},
       
  1678 			{name: 'Value', field: 'value', title: "Value", type: 'String'}
       
  1679 			],
       
  1680 		rowClasses: [
       
  1681 			],
       
  1682 		buttons: [
       
  1683 			]}});
       
  1684 
       
  1685 merge(config.shadowTiddlers,{
       
  1686 	DefaultTiddlers: "GettingStarted",
       
  1687 	MainMenu: "GettingStarted",
       
  1688 	SiteTitle: "My TiddlyWiki",
       
  1689 	SiteSubtitle: "a reusable non-linear personal web notebook",
       
  1690 	SiteUrl: "http://www.tiddlywiki.com/",
       
  1691 	SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
       
  1692 	SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
       
  1693 	TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>',
       
  1694 	ToolbarCommands: "|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|"});
       
  1695 
       
  1696 merge(config.annotations,{
       
  1697 	AdvancedOptions: "This shadow tiddler provides access to several advanced options",
       
  1698 	ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
       
  1699 	DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
       
  1700 	EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
       
  1701 	GettingStarted: "This shadow tiddler provides basic usage instructions",
       
  1702 	ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
       
  1703 	MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
       
  1704 	MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
       
  1705 	MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
       
  1706 	MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
       
  1707 	MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
       
  1708 	OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
       
  1709 	PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
       
  1710 	PluginManager: "This shadow tiddler provides access to the plugin manager",
       
  1711 	SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
       
  1712 	SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
       
  1713 	SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
       
  1714 	SiteTitle: "This shadow tiddler is used as the first part of the page title",
       
  1715 	SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
       
  1716 	StyleSheetColors: "This shadow tiddler contains CSS definitions related to the color of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
       
  1717 	StyleSheet: "This tiddler can contain custom CSS definitions",
       
  1718 	StyleSheetLayout: "This shadow tiddler contains CSS definitions related to the layout of page elements. ''DO NOT EDIT THIS TIDDLER'', instead make your changes in the StyleSheet shadow tiddler",
       
  1719 	StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
       
  1720 	StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
       
  1721 	TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
       
  1722 	TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
       
  1723 	TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
       
  1724 	TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
       
  1725 	TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
       
  1726 	TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
       
  1727 	TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
       
  1728 	ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
       
  1729 	ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
       
  1730 	});
       
  1731 
       
  1732 //--
       
  1733 //-- Main
       
  1734 //--
       
  1735 
       
  1736 var params = null; // Command line parameters
       
  1737 var store = null; // TiddlyWiki storage
       
  1738 var story = null; // Main story
       
  1739 var formatter = null; // Default formatters for the wikifier
       
  1740 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
       
  1741 var readOnly = false; // Whether we're in readonly mode
       
  1742 var highlightHack = null; // Embarrassing hack department...
       
  1743 var hadConfirmExit = false; // Don't warn more than once
       
  1744 var safeMode = false; // Disable all plugins and cookies
       
  1745 var showBackstage; // Whether to include the backstage area
       
  1746 var installedPlugins = []; // Information filled in when plugins are executed
       
  1747 var startingUp = false; // Whether we're in the process of starting up
       
  1748 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
       
  1749 
       
  1750 // Whether to use the JavaSaver applet
       
  1751 var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0,4) != "http");
       
  1752 
       
  1753 // Starting up
       
  1754 function main()
       
  1755 {
       
  1756 	var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
       
  1757 	startingUp = true;
       
  1758 	window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
       
  1759 	params = getParameters();
       
  1760 	if(params)
       
  1761 		params = params.parseParams("open",null,false);
       
  1762 	store = new TiddlyWiki();
       
  1763 	invokeParamifier(params,"oninit");
       
  1764 	story = new Story("tiddlerDisplay","tiddler");
       
  1765 	addEvent(document,"click",Popup.onDocumentClick);
       
  1766 	saveTest();
       
  1767 	loadOptionsCookie();
       
  1768 	for(var s=0; s<config.notifyTiddlers.length; s++)
       
  1769 		store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
       
  1770 	t1 = new Date();
       
  1771 	loadShadowTiddlers();
       
  1772 	t2 = new Date();
       
  1773 	store.loadFromDiv("storeArea","store",true);
       
  1774 	t3 = new Date();
       
  1775 	invokeParamifier(params,"onload");
       
  1776 	t4 = new Date();
       
  1777 	readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
       
  1778 	showBackstage = !readOnly;
       
  1779 	var pluginProblem = loadPlugins();
       
  1780 	t5 = new Date();
       
  1781 	formatter = new Formatter(config.formatters);
       
  1782 	story.switchTheme(config.options.txtTheme);
       
  1783 	invokeParamifier(params,"onconfig");
       
  1784 	t6 = new Date();
       
  1785 	store.notifyAll();
       
  1786 	t7 = new Date();
       
  1787 	restart();
       
  1788 	refreshDisplay();
       
  1789 	t8 = new Date();
       
  1790 	if(pluginProblem) {
       
  1791 		story.displayTiddler(null,"PluginManager");
       
  1792 		displayMessage(config.messages.customConfigError);
       
  1793 	}
       
  1794 	for(var m in config.macros) {
       
  1795 		if(config.macros[m].init)
       
  1796 			config.macros[m].init();
       
  1797 	}
       
  1798 	t9 = new Date();
       
  1799 	if(showBackstage)
       
  1800 		backstage.init();
       
  1801 	t10 = new Date();
       
  1802 	if(config.options.chkDisplayInstrumentation) {
       
  1803 		displayMessage("LoadShadows " + (t2-t1) + " ms");
       
  1804 		displayMessage("LoadFromDiv " + (t3-t2) + " ms");
       
  1805 		displayMessage("LoadPlugins " + (t5-t4) + " ms");
       
  1806 		displayMessage("Notify " + (t7-t6) + " ms");
       
  1807 		displayMessage("Restart " + (t8-t7) + " ms");
       
  1808 		displayMessage("Macro init " + (t9-t8) + " ms");
       
  1809 		displayMessage("Total: " + (t10-t0) + " ms");
       
  1810 	}
       
  1811 	startingUp = false;
       
  1812 }
       
  1813 
       
  1814 // Restarting
       
  1815 function restart()
       
  1816 {
       
  1817 	invokeParamifier(params,"onstart");
       
  1818 	if(story.isEmpty()) {
       
  1819 		var tiddlers = store.filterTiddlers(store.getTiddlerText("DefaultTiddlers"));
       
  1820 		story.displayTiddlers(null,tiddlers);
       
  1821 	}
       
  1822 	window.scrollTo(0,0);
       
  1823 }
       
  1824 
       
  1825 function saveTest()
       
  1826 {
       
  1827 	var s = document.getElementById("saveTest");
       
  1828 	if(s.hasChildNodes())
       
  1829 		alert(config.messages.savedSnapshotError);
       
  1830 	s.appendChild(document.createTextNode("savetest"));
       
  1831 }
       
  1832 
       
  1833 function loadShadowTiddlers()
       
  1834 {
       
  1835 	var shadows = new TiddlyWiki();
       
  1836 	shadows.loadFromDiv("shadowArea","shadows",true);
       
  1837 	shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
       
  1838 	delete shadows;
       
  1839 }
       
  1840 
       
  1841 function loadPlugins()
       
  1842 {
       
  1843 	if(safeMode)
       
  1844 		return false;
       
  1845 	var tiddlers = store.getTaggedTiddlers("systemConfig");
       
  1846 	var toLoad = [];
       
  1847 	var nLoaded = 0;
       
  1848 	var map = {};
       
  1849 	var nPlugins = tiddlers.length;
       
  1850 	installedPlugins = [];
       
  1851 	for(var i=0; i<nPlugins; i++) {
       
  1852 		var p = getPluginInfo(tiddlers[i]);
       
  1853 		installedPlugins[i] = p;
       
  1854 		var n = p.Name;
       
  1855 		if(n)
       
  1856 			map[n] = p;
       
  1857 		n = p.Source;
       
  1858 		if(n)
       
  1859 			map[n] = p;
       
  1860 	}
       
  1861 	var visit = function(p) {
       
  1862 		if(!p || p.done)
       
  1863 			return;
       
  1864 		p.done = 1;
       
  1865 		var reqs = p.Requires;
       
  1866 		if(reqs) {
       
  1867 			reqs = reqs.readBracketedList();
       
  1868 			for(var i=0; i<reqs.length; i++)
       
  1869 				visit(map[reqs[i]]);
       
  1870 		}
       
  1871 		toLoad.push(p);
       
  1872 	};
       
  1873 	for(i=0; i<nPlugins; i++)
       
  1874 		visit(installedPlugins[i]);
       
  1875 	for(i=0; i<toLoad.length; i++) {
       
  1876 		p = toLoad[i];
       
  1877 		pluginInfo = p;
       
  1878 		tiddler = p.tiddler;
       
  1879 		if(isPluginExecutable(p)) {
       
  1880 			if(isPluginEnabled(p)) {
       
  1881 				p.executed = true;
       
  1882 				var startTime = new Date();
       
  1883 				try {
       
  1884 					if(tiddler.text)
       
  1885 						window.eval(tiddler.text);
       
  1886 					nLoaded++;
       
  1887 				} catch(ex) {
       
  1888 					p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
       
  1889 					p.error = true;
       
  1890 				}
       
  1891 				pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
       
  1892 			} else {
       
  1893 				nPlugins--;
       
  1894 			}
       
  1895 		} else {
       
  1896 			p.warning = true;
       
  1897 		}
       
  1898 	}
       
  1899 	return nLoaded != nPlugins;
       
  1900 }
       
  1901 
       
  1902 function getPluginInfo(tiddler)
       
  1903 {
       
  1904 	var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
       
  1905 	p.tiddler = tiddler;
       
  1906 	p.title = tiddler.title;
       
  1907 	p.log = [];
       
  1908 	return p;
       
  1909 }
       
  1910 
       
  1911 // Check that a particular plugin is valid for execution
       
  1912 function isPluginExecutable(plugin)
       
  1913 {
       
  1914 	if(plugin.tiddler.isTagged("systemConfigForce"))
       
  1915 		return verifyTail(plugin,true,config.messages.pluginForced);
       
  1916 	if(plugin["CoreVersion"]) {
       
  1917 		var coreVersion = plugin["CoreVersion"].split(".");
       
  1918 		var w = parseInt(coreVersion[0]) - version.major;
       
  1919 		if(w == 0 && coreVersion[1])
       
  1920 			w = parseInt(coreVersion[1]) - version.minor;
       
  1921 		if(w == 0 && coreVersion[2])
       
  1922 			w = parseInt(coreVersion[2]) - version.revision;
       
  1923 		if(w > 0)
       
  1924 			return verifyTail(plugin,false,config.messages.pluginVersionError);
       
  1925 		}
       
  1926 	return true;
       
  1927 }
       
  1928 
       
  1929 function isPluginEnabled(plugin)
       
  1930 {
       
  1931 	if(plugin.tiddler.isTagged("systemConfigDisable"))
       
  1932 		return verifyTail(plugin,false,config.messages.pluginDisabled);
       
  1933 	return true;
       
  1934 }
       
  1935 
       
  1936 function verifyTail(plugin,result,message)
       
  1937 {
       
  1938 	plugin.log.push(message);
       
  1939 	return result;
       
  1940 }
       
  1941 
       
  1942 function invokeMacro(place,macro,params,wikifier,tiddler)
       
  1943 {
       
  1944 	try {
       
  1945 		var m = config.macros[macro];
       
  1946 		if(m && m.handler)
       
  1947 			m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
       
  1948 		else
       
  1949 			createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
       
  1950 	} catch(ex) {
       
  1951 		createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
       
  1952 	}
       
  1953 }
       
  1954 
       
  1955 //--
       
  1956 //-- Paramifiers
       
  1957 //--
       
  1958 
       
  1959 function getParameters()
       
  1960 {
       
  1961 	var p = null;
       
  1962 	if(window.location.hash) {
       
  1963 		p = decodeURIComponent(window.location.hash.substr(1));
       
  1964 		if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
       
  1965 			p = convertUTF8ToUnicode(p);
       
  1966 	}
       
  1967 	return p;
       
  1968 }
       
  1969 
       
  1970 function invokeParamifier(params,handler)
       
  1971 {
       
  1972 	if(!params || params.length == undefined || params.length <= 1)
       
  1973 		return;
       
  1974 	for(var t=1; t<params.length; t++) {
       
  1975 		var p = config.paramifiers[params[t].name];
       
  1976 		if(p && p[handler] instanceof Function)
       
  1977 			p[handler](params[t].value);
       
  1978 	}
       
  1979 }
       
  1980 
       
  1981 config.paramifiers = {};
       
  1982 
       
  1983 config.paramifiers.start = {
       
  1984 	oninit: function(v) {
       
  1985 		safeMode = v.toLowerCase() == "safe";
       
  1986 	}
       
  1987 };
       
  1988 
       
  1989 config.paramifiers.open = {
       
  1990 	onstart: function(v) {
       
  1991 		if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
       
  1992 			story.displayTiddler("bottom",v,null,false,null);
       
  1993 	}
       
  1994 };
       
  1995 
       
  1996 config.paramifiers.story = {
       
  1997 	onstart: function(v) {
       
  1998 		var list = store.getTiddlerText(v,"").parseParams("open",null,false);
       
  1999 		invokeParamifier(list,"onstart");
       
  2000 	}
       
  2001 };
       
  2002 
       
  2003 config.paramifiers.search = {
       
  2004 	onstart: function(v) {
       
  2005 		story.search(v,false,false);
       
  2006 	}
       
  2007 };
       
  2008 
       
  2009 config.paramifiers.searchRegExp = {
       
  2010 	onstart: function(v) {
       
  2011 		story.prototype.search(v,false,true);
       
  2012 	}
       
  2013 };
       
  2014 
       
  2015 config.paramifiers.tag = {
       
  2016 	onstart: function(v) {
       
  2017 		var tagged = store.getTaggedTiddlers(v,"title");
       
  2018 		story.displayTiddlers(null,tagged,null,false,null);
       
  2019 	}
       
  2020 };
       
  2021 
       
  2022 config.paramifiers.newTiddler = {
       
  2023 	onstart: function(v) {
       
  2024 		if(!readOnly) {
       
  2025 			story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
       
  2026 			story.focusTiddler(v,"text");
       
  2027 		}
       
  2028 	}
       
  2029 };
       
  2030 
       
  2031 config.paramifiers.newJournal = {
       
  2032 	onstart: function(v) {
       
  2033 		if(!readOnly) {
       
  2034 			var now = new Date();
       
  2035 			var title = now.formatString(v.trim());
       
  2036 			story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
       
  2037 			story.focusTiddler(title,"text");
       
  2038 		}
       
  2039 	}
       
  2040 };
       
  2041 
       
  2042 config.paramifiers.readOnly = {
       
  2043 	onconfig: function(v) {
       
  2044 		var p = v.toLowerCase();
       
  2045 		readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
       
  2046 	}
       
  2047 };
       
  2048 
       
  2049 config.paramifiers.theme = {
       
  2050 	onconfig: function(v) {
       
  2051 		story.switchTheme(v);
       
  2052 	}
       
  2053 };
       
  2054 
       
  2055 config.paramifiers.upgrade = {
       
  2056 	onstart: function(v) {
       
  2057 		upgradeFrom(v);
       
  2058 	}
       
  2059 };
       
  2060 
       
  2061 //--
       
  2062 //-- Formatter helpers
       
  2063 //--
       
  2064 
       
  2065 function Formatter(formatters)
       
  2066 {
       
  2067 	this.formatters = [];
       
  2068 	var pattern = [];
       
  2069 	for(var n=0; n<formatters.length; n++) {
       
  2070 		pattern.push("(" + formatters[n].match + ")");
       
  2071 		this.formatters.push(formatters[n]);
       
  2072 	}
       
  2073 	this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
       
  2074 }
       
  2075 
       
  2076 config.formatterHelpers = {
       
  2077 
       
  2078 	createElementAndWikify: function(w)
       
  2079 	{
       
  2080 		w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
       
  2081 	},
       
  2082 
       
  2083 	inlineCssHelper: function(w)
       
  2084 	{
       
  2085 		var styles = [];
       
  2086 		config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
       
  2087 		var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
       
  2088 		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
       
  2089 			var s,v;
       
  2090 			if(lookaheadMatch[1]) {
       
  2091 				s = lookaheadMatch[1].unDash();
       
  2092 				v = lookaheadMatch[2];
       
  2093 			} else {
       
  2094 				s = lookaheadMatch[3].unDash();
       
  2095 				v = lookaheadMatch[4];
       
  2096 			}
       
  2097 			if(s=="bgcolor")
       
  2098 				s = "backgroundColor";
       
  2099 			styles.push({style: s, value: v});
       
  2100 			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
       
  2101 			config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
       
  2102 			lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
       
  2103 		}
       
  2104 		return styles;
       
  2105 	},
       
  2106 
       
  2107 	applyCssHelper: function(e,styles)
       
  2108 	{
       
  2109 		for(var t=0; t< styles.length; t++) {
       
  2110 			try {
       
  2111 				e.style[styles[t].style] = styles[t].value;
       
  2112 			} catch (ex) {
       
  2113 			}
       
  2114 		}
       
  2115 	},
       
  2116 
       
  2117 	enclosedTextHelper: function(w)
       
  2118 	{
       
  2119 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2120 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2121 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2122 			var text = lookaheadMatch[1];
       
  2123 			if(config.browser.isIE)
       
  2124 				text = text.replace(/\n/g,"\r");
       
  2125 			createTiddlyElement(w.output,this.element,null,null,text);
       
  2126 			w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
       
  2127 		}
       
  2128 	},
       
  2129 
       
  2130 	isExternalLink: function(link)
       
  2131 	{
       
  2132 		if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
       
  2133 			return false;
       
  2134 		}
       
  2135 		var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
       
  2136 		if(urlRegExp.exec(link)) {
       
  2137 			return true;
       
  2138 		}
       
  2139 		if(link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
       
  2140 			return true;
       
  2141 		}
       
  2142 		return false;
       
  2143 	}
       
  2144 
       
  2145 };
       
  2146 
       
  2147 //--
       
  2148 //-- Standard formatters
       
  2149 //--
       
  2150 
       
  2151 config.formatters = [
       
  2152 {
       
  2153 	name: "table",
       
  2154 	match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
       
  2155 	lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
       
  2156 	rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
       
  2157 	cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
       
  2158 	cellTermRegExp: /((?:\x20*)\|)/mg,
       
  2159 	rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
       
  2160 	handler: function(w)
       
  2161 	{
       
  2162 		var table = createTiddlyElement(w.output,"table",null,"twtable");
       
  2163 		var prevColumns = [];
       
  2164 		var currRowType = null;
       
  2165 		var rowContainer;
       
  2166 		var rowCount = 0;
       
  2167 		w.nextMatch = w.matchStart;
       
  2168 		this.lookaheadRegExp.lastIndex = w.nextMatch;
       
  2169 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2170 		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
       
  2171 			var nextRowType = lookaheadMatch[2];
       
  2172 			if(nextRowType == "k") {
       
  2173 				table.className = lookaheadMatch[1];
       
  2174 				w.nextMatch += lookaheadMatch[0].length+1;
       
  2175 			} else {
       
  2176 				if(nextRowType != currRowType) {
       
  2177 					rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
       
  2178 					currRowType = nextRowType;
       
  2179 				}
       
  2180 				if(currRowType == "c") {
       
  2181 					// Caption
       
  2182 					w.nextMatch++;
       
  2183 					if(rowContainer != table.firstChild)
       
  2184 						table.insertBefore(rowContainer,table.firstChild);
       
  2185 					rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
       
  2186 					w.subWikifyTerm(rowContainer,this.rowTermRegExp);
       
  2187 				} else {
       
  2188 					var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
       
  2189 					theRow.onmouseover = function() {addClass(this,"hoverRow");};
       
  2190 					theRow.onmouseout = function() {removeClass(this,"hoverRow");};
       
  2191 					this.rowHandler(w,theRow,prevColumns);
       
  2192 					rowCount++;
       
  2193 				}
       
  2194 			}
       
  2195 			this.lookaheadRegExp.lastIndex = w.nextMatch;
       
  2196 			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2197 		}
       
  2198 	},
       
  2199 	rowHandler: function(w,e,prevColumns)
       
  2200 	{
       
  2201 		var col = 0;
       
  2202 		var colSpanCount = 1;
       
  2203 		var prevCell = null;
       
  2204 		this.cellRegExp.lastIndex = w.nextMatch;
       
  2205 		var cellMatch = this.cellRegExp.exec(w.source);
       
  2206 		while(cellMatch && cellMatch.index == w.nextMatch) {
       
  2207 			if(cellMatch[1] == "~") {
       
  2208 				// Rowspan
       
  2209 				var last = prevColumns[col];
       
  2210 				if(last) {
       
  2211 					last.rowSpanCount++;
       
  2212 					last.element.setAttribute("rowspan",last.rowSpanCount);
       
  2213 					last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
       
  2214 					last.element.valign = "center";
       
  2215 				}
       
  2216 				w.nextMatch = this.cellRegExp.lastIndex-1;
       
  2217 			} else if(cellMatch[1] == ">") {
       
  2218 				// Colspan
       
  2219 				colSpanCount++;
       
  2220 				w.nextMatch = this.cellRegExp.lastIndex-1;
       
  2221 			} else if(cellMatch[2]) {
       
  2222 				// End of row
       
  2223 				if(prevCell && colSpanCount > 1) {
       
  2224 					prevCell.setAttribute("colspan",colSpanCount);
       
  2225 					prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
       
  2226 				}
       
  2227 				w.nextMatch = this.cellRegExp.lastIndex;
       
  2228 				break;
       
  2229 			} else {
       
  2230 				// Cell
       
  2231 				w.nextMatch++;
       
  2232 				var styles = config.formatterHelpers.inlineCssHelper(w);
       
  2233 				var spaceLeft = false;
       
  2234 				var chr = w.source.substr(w.nextMatch,1);
       
  2235 				while(chr == " ") {
       
  2236 					spaceLeft = true;
       
  2237 					w.nextMatch++;
       
  2238 					chr = w.source.substr(w.nextMatch,1);
       
  2239 				}
       
  2240 				var cell;
       
  2241 				if(chr == "!") {
       
  2242 					cell = createTiddlyElement(e,"th");
       
  2243 					w.nextMatch++;
       
  2244 				} else {
       
  2245 					cell = createTiddlyElement(e,"td");
       
  2246 				}
       
  2247 				prevCell = cell;
       
  2248 				prevColumns[col] = {rowSpanCount:1,element:cell};
       
  2249 				if(colSpanCount > 1) {
       
  2250 					cell.setAttribute("colspan",colSpanCount);
       
  2251 					cell.setAttribute("colSpan",colSpanCount); // Needed for IE
       
  2252 					colSpanCount = 1;
       
  2253 				}
       
  2254 				config.formatterHelpers.applyCssHelper(cell,styles);
       
  2255 				w.subWikifyTerm(cell,this.cellTermRegExp);
       
  2256 				if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
       
  2257 					cell.align = spaceLeft ? "center" : "left";
       
  2258 				else if(spaceLeft)
       
  2259 					cell.align = "right";
       
  2260 				w.nextMatch--;
       
  2261 			}
       
  2262 			col++;
       
  2263 			this.cellRegExp.lastIndex = w.nextMatch;
       
  2264 			cellMatch = this.cellRegExp.exec(w.source);
       
  2265 		}
       
  2266 	}
       
  2267 },
       
  2268 
       
  2269 {
       
  2270 	name: "heading",
       
  2271 	match: "^!{1,6}",
       
  2272 	termRegExp: /(\n)/mg,
       
  2273 	handler: function(w)
       
  2274 	{
       
  2275 		w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
       
  2276 	}
       
  2277 },
       
  2278 
       
  2279 {
       
  2280 	name: "list",
       
  2281 	match: "^(?:[\\*#;:]+)",
       
  2282 	lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
       
  2283 	termRegExp: /(\n)/mg,
       
  2284 	handler: function(w)
       
  2285 	{
       
  2286 		var stack = [w.output];
       
  2287 		var currLevel = 0, currType = null;
       
  2288 		var listLevel, listType, itemType, baseType;
       
  2289 		w.nextMatch = w.matchStart;
       
  2290 		this.lookaheadRegExp.lastIndex = w.nextMatch;
       
  2291 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2292 		while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
       
  2293 			if(lookaheadMatch[1]) {
       
  2294 				listType = "ul";
       
  2295 				itemType = "li";
       
  2296 			} else if(lookaheadMatch[2]) {
       
  2297 				listType = "ol";
       
  2298 				itemType = "li";
       
  2299 			} else if(lookaheadMatch[3]) {
       
  2300 				listType = "dl";
       
  2301 				itemType = "dt";
       
  2302 			} else if(lookaheadMatch[4]) {
       
  2303 				listType = "dl";
       
  2304 				itemType = "dd";
       
  2305 			}
       
  2306 			if(!baseType)
       
  2307 				baseType = listType;
       
  2308 			listLevel = lookaheadMatch[0].length;
       
  2309 			w.nextMatch += lookaheadMatch[0].length;
       
  2310 			var t;
       
  2311 			if(listLevel > currLevel) {
       
  2312 				for(t=currLevel; t<listLevel; t++) {
       
  2313 					var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
       
  2314 					stack.push(createTiddlyElement(target,listType));
       
  2315 				}
       
  2316 			} else if(listType!=baseType && listLevel==1) {
       
  2317 				w.nextMatch -= lookaheadMatch[0].length;
       
  2318 				return;
       
  2319 			} else if(listLevel < currLevel) {
       
  2320 				for(t=currLevel; t>listLevel; t--)
       
  2321 					stack.pop();
       
  2322 			} else if(listLevel == currLevel && listType != currType) {
       
  2323 				stack.pop();
       
  2324 				stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
       
  2325 			}
       
  2326 			currLevel = listLevel;
       
  2327 			currType = listType;
       
  2328 			var e = createTiddlyElement(stack[stack.length-1],itemType);
       
  2329 			w.subWikifyTerm(e,this.termRegExp);
       
  2330 			this.lookaheadRegExp.lastIndex = w.nextMatch;
       
  2331 			lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2332 		}
       
  2333 	}
       
  2334 },
       
  2335 
       
  2336 {
       
  2337 	name: "quoteByBlock",
       
  2338 	match: "^<<<\\n",
       
  2339 	termRegExp: /(^<<<(\n|$))/mg,
       
  2340 	element: "blockquote",
       
  2341 	handler: config.formatterHelpers.createElementAndWikify
       
  2342 },
       
  2343 
       
  2344 {
       
  2345 	name: "quoteByLine",
       
  2346 	match: "^>+",
       
  2347 	lookaheadRegExp: /^>+/mg,
       
  2348 	termRegExp: /(\n)/mg,
       
  2349 	element: "blockquote",
       
  2350 	handler: function(w)
       
  2351 	{
       
  2352 		var stack = [w.output];
       
  2353 		var currLevel = 0;
       
  2354 		var newLevel = w.matchLength;
       
  2355 		var t;
       
  2356 		do {
       
  2357 			if(newLevel > currLevel) {
       
  2358 				for(t=currLevel; t<newLevel; t++)
       
  2359 					stack.push(createTiddlyElement(stack[stack.length-1],this.element));
       
  2360 			} else if(newLevel < currLevel) {
       
  2361 				for(t=currLevel; t>newLevel; t--)
       
  2362 					stack.pop();
       
  2363 			}
       
  2364 			currLevel = newLevel;
       
  2365 			w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
       
  2366 			createTiddlyElement(stack[stack.length-1],"br");
       
  2367 			this.lookaheadRegExp.lastIndex = w.nextMatch;
       
  2368 			var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2369 			var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
       
  2370 			if(matched) {
       
  2371 				newLevel = lookaheadMatch[0].length;
       
  2372 				w.nextMatch += lookaheadMatch[0].length;
       
  2373 			}
       
  2374 		} while(matched);
       
  2375 	}
       
  2376 },
       
  2377 
       
  2378 {
       
  2379 	name: "rule",
       
  2380 	match: "^----+$\\n?",
       
  2381 	handler: function(w)
       
  2382 	{
       
  2383 		createTiddlyElement(w.output,"hr");
       
  2384 	}
       
  2385 },
       
  2386 
       
  2387 {
       
  2388 	name: "monospacedByLine",
       
  2389 	match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
       
  2390 	element: "pre",
       
  2391 	handler: function(w)
       
  2392 	{
       
  2393 		switch(w.matchText) {
       
  2394 		case "/*{{{*/\n": // CSS
       
  2395 			this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
       
  2396 			break;
       
  2397 		case "{{{\n": // monospaced block
       
  2398 			this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
       
  2399 			break;
       
  2400 		case "//{{{\n": // plugin
       
  2401 			this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
       
  2402 			break;
       
  2403 		case "<!--{{{-->\n": //template
       
  2404 			this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
       
  2405 			break;
       
  2406 		default:
       
  2407 			break;
       
  2408 		}
       
  2409 		config.formatterHelpers.enclosedTextHelper.call(this,w);
       
  2410 	}
       
  2411 },
       
  2412 
       
  2413 {
       
  2414 	name: "wikifyComment",
       
  2415 	match: "^(?:/\\*\\*\\*|<!---)\\n",
       
  2416 	handler: function(w)
       
  2417 	{
       
  2418 		var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
       
  2419 		w.subWikifyTerm(w.output,termRegExp);
       
  2420 	}
       
  2421 },
       
  2422 
       
  2423 {
       
  2424 	name: "macro",
       
  2425 	match: "<<",
       
  2426 	lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
       
  2427 	handler: function(w)
       
  2428 	{
       
  2429 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2430 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2431 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
       
  2432 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2433 			invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
       
  2434 		}
       
  2435 	}
       
  2436 },
       
  2437 
       
  2438 {
       
  2439 	name: "prettyLink",
       
  2440 	match: "\\[\\[",
       
  2441 	lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
       
  2442 	handler: function(w)
       
  2443 	{
       
  2444 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2445 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2446 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2447 			var e;
       
  2448 			var text = lookaheadMatch[1];
       
  2449 			if(lookaheadMatch[3]) {
       
  2450 				// Pretty bracketted link
       
  2451 				var link = lookaheadMatch[3];
       
  2452 				e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
       
  2453 						createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
       
  2454 			} else {
       
  2455 				// Simple bracketted link
       
  2456 				e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
       
  2457 			}
       
  2458 			createTiddlyText(e,text);
       
  2459 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2460 		}
       
  2461 	}
       
  2462 },
       
  2463 
       
  2464 {
       
  2465 	name: "wikiLink",
       
  2466 	match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
       
  2467 	handler: function(w)
       
  2468 	{
       
  2469 		if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
       
  2470 			w.outputText(w.output,w.matchStart+1,w.nextMatch);
       
  2471 			return;
       
  2472 		}
       
  2473 		if(w.matchStart > 0) {
       
  2474 			var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
       
  2475 			preRegExp.lastIndex = w.matchStart-1;
       
  2476 			var preMatch = preRegExp.exec(w.source);
       
  2477 			if(preMatch.index == w.matchStart-1) {
       
  2478 				w.outputText(w.output,w.matchStart,w.nextMatch);
       
  2479 				return;
       
  2480 			}
       
  2481 		}
       
  2482 		if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
       
  2483 			var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
       
  2484 			w.outputText(link,w.matchStart,w.nextMatch);
       
  2485 		} else {
       
  2486 			w.outputText(w.output,w.matchStart,w.nextMatch);
       
  2487 		}
       
  2488 	}
       
  2489 },
       
  2490 
       
  2491 {
       
  2492 	name: "urlLink",
       
  2493 	match: config.textPrimitives.urlPattern,
       
  2494 	handler: function(w)
       
  2495 	{
       
  2496 		w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
       
  2497 	}
       
  2498 },
       
  2499 
       
  2500 {
       
  2501 	name: "image",
       
  2502 	match: "\\[[<>]?[Ii][Mm][Gg]\\[",
       
  2503 	lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
       
  2504 	handler: function(w)
       
  2505 	{
       
  2506 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2507 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2508 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2509 			var e = w.output;
       
  2510 			if(lookaheadMatch[5]) {
       
  2511 				var link = lookaheadMatch[5];
       
  2512 				e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
       
  2513 				addClass(e,"imageLink");
       
  2514 			}
       
  2515 			var img = createTiddlyElement(e,"img");
       
  2516 			if(lookaheadMatch[1])
       
  2517 				img.align = "left";
       
  2518 			else if(lookaheadMatch[2])
       
  2519 				img.align = "right";
       
  2520 			if(lookaheadMatch[3]) {
       
  2521 				img.title = lookaheadMatch[3];
       
  2522 				img.setAttribute("alt",lookaheadMatch[3]);
       
  2523 			}
       
  2524 			img.src = lookaheadMatch[4];
       
  2525 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2526 		}
       
  2527 	}
       
  2528 },
       
  2529 
       
  2530 {
       
  2531 	name: "html",
       
  2532 	match: "<[Hh][Tt][Mm][Ll]>",
       
  2533 	lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
       
  2534 	handler: function(w)
       
  2535 	{
       
  2536 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2537 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2538 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2539 			createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
       
  2540 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2541 		}
       
  2542 	}
       
  2543 },
       
  2544 
       
  2545 {
       
  2546 	name: "commentByBlock",
       
  2547 	match: "/%",
       
  2548 	lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
       
  2549 	handler: function(w)
       
  2550 	{
       
  2551 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2552 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2553 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
       
  2554 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2555 	}
       
  2556 },
       
  2557 
       
  2558 {
       
  2559 	name: "characterFormat",
       
  2560 	match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
       
  2561 	handler: function(w)
       
  2562 	{
       
  2563 		switch(w.matchText) {
       
  2564 		case "''":
       
  2565 			w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
       
  2566 			break;
       
  2567 		case "//":
       
  2568 			w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
       
  2569 			break;
       
  2570 		case "__":
       
  2571 			w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
       
  2572 			break;
       
  2573 		case "^^":
       
  2574 			w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
       
  2575 			break;
       
  2576 		case "~~":
       
  2577 			w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
       
  2578 			break;
       
  2579 		case "--":
       
  2580 			w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
       
  2581 			break;
       
  2582 		case "{{{":
       
  2583 			var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
       
  2584 			lookaheadRegExp.lastIndex = w.matchStart;
       
  2585 			var lookaheadMatch = lookaheadRegExp.exec(w.source);
       
  2586 			if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2587 				createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
       
  2588 				w.nextMatch = lookaheadRegExp.lastIndex;
       
  2589 			}
       
  2590 			break;
       
  2591 		}
       
  2592 	}
       
  2593 },
       
  2594 
       
  2595 {
       
  2596 	name: "customFormat",
       
  2597 	match: "@@|\\{\\{",
       
  2598 	handler: function(w)
       
  2599 	{
       
  2600 		switch(w.matchText) {
       
  2601 		case "@@":
       
  2602 			var e = createTiddlyElement(w.output,"span");
       
  2603 			var styles = config.formatterHelpers.inlineCssHelper(w);
       
  2604 			if(styles.length == 0)
       
  2605 				e.className = "marked";
       
  2606 			else
       
  2607 				config.formatterHelpers.applyCssHelper(e,styles);
       
  2608 			w.subWikifyTerm(e,/(@@)/mg);
       
  2609 			break;
       
  2610 		case "{{":
       
  2611 			lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
       
  2612 			lookaheadRegExp.lastIndex = w.matchStart;
       
  2613 			lookaheadMatch = lookaheadRegExp.exec(w.source);
       
  2614 			if(lookaheadMatch) {
       
  2615 				w.nextMatch = lookaheadRegExp.lastIndex;
       
  2616 				e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
       
  2617 				w.subWikifyTerm(e,/(\}\}\})/mg);
       
  2618 			}
       
  2619 			break;
       
  2620 		}
       
  2621 	}
       
  2622 },
       
  2623 
       
  2624 {
       
  2625 	name: "mdash",
       
  2626 	match: "--",
       
  2627 	handler: function(w)
       
  2628 	{
       
  2629 		createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
       
  2630 	}
       
  2631 },
       
  2632 
       
  2633 {
       
  2634 	name: "lineBreak",
       
  2635 	match: "\\n|<br ?/?>",
       
  2636 	handler: function(w)
       
  2637 	{
       
  2638 		createTiddlyElement(w.output,"br");
       
  2639 	}
       
  2640 },
       
  2641 
       
  2642 {
       
  2643 	name: "rawText",
       
  2644 	match: "\\\"{3}|<nowiki>",
       
  2645 	lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
       
  2646 	handler: function(w)
       
  2647 	{
       
  2648 		this.lookaheadRegExp.lastIndex = w.matchStart;
       
  2649 		var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
       
  2650 		if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
       
  2651 			createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
       
  2652 			w.nextMatch = this.lookaheadRegExp.lastIndex;
       
  2653 		}
       
  2654 	}
       
  2655 },
       
  2656 
       
  2657 {
       
  2658 	name: "htmlEntitiesEncoding",
       
  2659 	match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)",
       
  2660 	handler: function(w)
       
  2661 	{
       
  2662 		createTiddlyElement(w.output,"span").innerHTML = w.matchText;
       
  2663 	}
       
  2664 }
       
  2665 
       
  2666 ];
       
  2667 
       
  2668 //--
       
  2669 //-- Wikifier
       
  2670 //--
       
  2671 
       
  2672 function getParser(tiddler,format)
       
  2673 {
       
  2674 	if(tiddler) {
       
  2675 		if(!format)
       
  2676 			format = tiddler.fields["wikiformat"];
       
  2677 		var i;
       
  2678 		if(format) {
       
  2679 			for(i in config.parsers) {
       
  2680 				if(format == config.parsers[i].format)
       
  2681 					return config.parsers[i];
       
  2682 			}
       
  2683 		} else {
       
  2684 			for(i in config.parsers) {
       
  2685 				if(tiddler.isTagged(config.parsers[i].formatTag))
       
  2686 					return config.parsers[i];
       
  2687 			}
       
  2688 		}
       
  2689 	}
       
  2690 	return formatter;
       
  2691 }
       
  2692 
       
  2693 function wikify(source,output,highlightRegExp,tiddler)
       
  2694 {
       
  2695 	if(source && source != "") {
       
  2696 		var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
       
  2697 		wikifier.subWikify(output);
       
  2698 	}
       
  2699 }
       
  2700 
       
  2701 function wikifyStatic(source,highlightRegExp,tiddler,format)
       
  2702 {
       
  2703 	var e = createTiddlyElement(document.body,"pre");
       
  2704 	e.style.display = "none";
       
  2705 	var html = "";
       
  2706 	if(source && source != "") {
       
  2707 		if(!tiddler)
       
  2708 			tiddler = new Tiddler("temp");
       
  2709 		var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
       
  2710 		wikifier.isStatic = true;
       
  2711 		wikifier.subWikify(e);
       
  2712 		html = e.innerHTML;
       
  2713 		removeNode(e);
       
  2714 	}
       
  2715 	return html;
       
  2716 }
       
  2717 
       
  2718 function wikifyPlain(title,theStore,limit)
       
  2719 {
       
  2720 	if(!theStore)
       
  2721 		theStore = store;
       
  2722 	if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
       
  2723 		return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
       
  2724 	} else {
       
  2725 		return "";
       
  2726 	}
       
  2727 }
       
  2728 
       
  2729 function wikifyPlainText(text,limit,tiddler)
       
  2730 {
       
  2731 	if(limit > 0)
       
  2732 		text = text.substr(0,limit);
       
  2733 	var wikifier = new Wikifier(text,formatter,null,tiddler);
       
  2734 	return wikifier.wikifyPlain();
       
  2735 }
       
  2736 
       
  2737 function highlightify(source,output,highlightRegExp,tiddler)
       
  2738 {
       
  2739 	if(source && source != "") {
       
  2740 		var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
       
  2741 		wikifier.outputText(output,0,source.length);
       
  2742 	}
       
  2743 }
       
  2744 
       
  2745 function Wikifier(source,formatter,highlightRegExp,tiddler)
       
  2746 {
       
  2747 	this.source = source;
       
  2748 	this.output = null;
       
  2749 	this.formatter = formatter;
       
  2750 	this.nextMatch = 0;
       
  2751 	this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
       
  2752 	this.highlightRegExp = highlightRegExp;
       
  2753 	this.highlightMatch = null;
       
  2754 	this.isStatic = false;
       
  2755 	if(highlightRegExp) {
       
  2756 		highlightRegExp.lastIndex = 0;
       
  2757 		this.highlightMatch = highlightRegExp.exec(source);
       
  2758 	}
       
  2759 	this.tiddler = tiddler;
       
  2760 }
       
  2761 
       
  2762 Wikifier.prototype.wikifyPlain = function()
       
  2763 {
       
  2764 	var e = createTiddlyElement(document.body,"div");
       
  2765 	e.style.display = "none";
       
  2766 	this.subWikify(e);
       
  2767 	var text = getPlainText(e);
       
  2768 	removeNode(e);
       
  2769 	return text;
       
  2770 };
       
  2771 
       
  2772 Wikifier.prototype.subWikify = function(output,terminator)
       
  2773 {
       
  2774 	try {
       
  2775 		if(terminator)
       
  2776 			this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
       
  2777 		else
       
  2778 			this.subWikifyUnterm(output);
       
  2779 	} catch(ex) {
       
  2780 		showException(ex);
       
  2781 	}
       
  2782 };
       
  2783 
       
  2784 Wikifier.prototype.subWikifyUnterm = function(output)
       
  2785 {
       
  2786 	var oldOutput = this.output;
       
  2787 	this.output = output;
       
  2788 	this.formatter.formatterRegExp.lastIndex = this.nextMatch;
       
  2789 	var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
       
  2790 	while(formatterMatch) {
       
  2791 		// Output any text before the match
       
  2792 		if(formatterMatch.index > this.nextMatch)
       
  2793 			this.outputText(this.output,this.nextMatch,formatterMatch.index);
       
  2794 		// Set the match parameters for the handler
       
  2795 		this.matchStart = formatterMatch.index;
       
  2796 		this.matchLength = formatterMatch[0].length;
       
  2797 		this.matchText = formatterMatch[0];
       
  2798 		this.nextMatch = this.formatter.formatterRegExp.lastIndex;
       
  2799 		for(var t=1; t<formatterMatch.length; t++) {
       
  2800 			if(formatterMatch[t]) {
       
  2801 				this.formatter.formatters[t-1].handler(this);
       
  2802 				this.formatter.formatterRegExp.lastIndex = this.nextMatch;
       
  2803 				break;
       
  2804 			}
       
  2805 		}
       
  2806 		formatterMatch = this.formatter.formatterRegExp.exec(this.source);
       
  2807 	}
       
  2808 	if(this.nextMatch < this.source.length) {
       
  2809 		this.outputText(this.output,this.nextMatch,this.source.length);
       
  2810 		this.nextMatch = this.source.length;
       
  2811 	}
       
  2812 	this.output = oldOutput;
       
  2813 };
       
  2814 
       
  2815 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
       
  2816 {
       
  2817 	var oldOutput = this.output;
       
  2818 	this.output = output;
       
  2819 	terminatorRegExp.lastIndex = this.nextMatch;
       
  2820 	var terminatorMatch = terminatorRegExp.exec(this.source);
       
  2821 	this.formatter.formatterRegExp.lastIndex = this.nextMatch;
       
  2822 	var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
       
  2823 	while(terminatorMatch || formatterMatch) {
       
  2824 		if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
       
  2825 			if(terminatorMatch.index > this.nextMatch)
       
  2826 				this.outputText(this.output,this.nextMatch,terminatorMatch.index);
       
  2827 			this.matchText = terminatorMatch[1];
       
  2828 			this.matchLength = terminatorMatch[1].length;
       
  2829 			this.matchStart = terminatorMatch.index;
       
  2830 			this.nextMatch = this.matchStart + this.matchLength;
       
  2831 			this.output = oldOutput;
       
  2832 			return;
       
  2833 		}
       
  2834 		if(formatterMatch.index > this.nextMatch)
       
  2835 			this.outputText(this.output,this.nextMatch,formatterMatch.index);
       
  2836 		this.matchStart = formatterMatch.index;
       
  2837 		this.matchLength = formatterMatch[0].length;
       
  2838 		this.matchText = formatterMatch[0];
       
  2839 		this.nextMatch = this.formatter.formatterRegExp.lastIndex;
       
  2840 		for(var t=1; t<formatterMatch.length; t++) {
       
  2841 			if(formatterMatch[t]) {
       
  2842 				this.formatter.formatters[t-1].handler(this);
       
  2843 				this.formatter.formatterRegExp.lastIndex = this.nextMatch;
       
  2844 				break;
       
  2845 			}
       
  2846 		}
       
  2847 		terminatorRegExp.lastIndex = this.nextMatch;
       
  2848 		terminatorMatch = terminatorRegExp.exec(this.source);
       
  2849 		formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
       
  2850 	}
       
  2851 	if(this.nextMatch < this.source.length) {
       
  2852 		this.outputText(this.output,this.nextMatch,this.source.length);
       
  2853 		this.nextMatch = this.source.length;
       
  2854 	}
       
  2855 	this.output = oldOutput;
       
  2856 };
       
  2857 
       
  2858 Wikifier.prototype.outputText = function(place,startPos,endPos)
       
  2859 {
       
  2860 	while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
       
  2861 		if(this.highlightMatch.index > startPos) {
       
  2862 			createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
       
  2863 			startPos = this.highlightMatch.index;
       
  2864 		}
       
  2865 		var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
       
  2866 		var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
       
  2867 		startPos = highlightEnd;
       
  2868 		if(startPos >= this.highlightRegExp.lastIndex)
       
  2869 			this.highlightMatch = this.highlightRegExp.exec(this.source);
       
  2870 	}
       
  2871 	if(startPos < endPos) {
       
  2872 		createTiddlyText(place,this.source.substring(startPos,endPos));
       
  2873 	}
       
  2874 };
       
  2875 
       
  2876 //--
       
  2877 //-- Macro definitions
       
  2878 //--
       
  2879 
       
  2880 config.macros.today.handler = function(place,macroName,params)
       
  2881 {
       
  2882 	var now = new Date();
       
  2883 	var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
       
  2884 	createTiddlyElement(place,"span",null,null,text);
       
  2885 };
       
  2886 
       
  2887 config.macros.version.handler = function(place)
       
  2888 {
       
  2889 	createTiddlyElement(place,"span",null,null,formatVersion());
       
  2890 };
       
  2891 
       
  2892 config.macros.list.handler = function(place,macroName,params)
       
  2893 {
       
  2894 	var type = params[0] ? params[0] : "all";
       
  2895 	var list = document.createElement("ul");
       
  2896 	place.appendChild(list);
       
  2897 	if(this[type].prompt)
       
  2898 		createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
       
  2899 	var results;
       
  2900 	if(this[type].handler)
       
  2901 		results = this[type].handler(params);
       
  2902 	for(var t = 0; t < results.length; t++) {
       
  2903 		var li = document.createElement("li");
       
  2904 		list.appendChild(li);
       
  2905 		createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
       
  2906 	}
       
  2907 };
       
  2908 
       
  2909 config.macros.list.all.handler = function(params)
       
  2910 {
       
  2911 	return store.reverseLookup("tags","excludeLists",false,"title");
       
  2912 };
       
  2913 
       
  2914 config.macros.list.missing.handler = function(params)
       
  2915 {
       
  2916 	return store.getMissingLinks();
       
  2917 };
       
  2918 
       
  2919 config.macros.list.orphans.handler = function(params)
       
  2920 {
       
  2921 	return store.getOrphans();
       
  2922 };
       
  2923 
       
  2924 config.macros.list.shadowed.handler = function(params)
       
  2925 {
       
  2926 	return store.getShadowed();
       
  2927 };
       
  2928 
       
  2929 config.macros.list.touched.handler = function(params)
       
  2930 {
       
  2931 	return store.getTouched();
       
  2932 };
       
  2933 
       
  2934 config.macros.list.filter.handler = function(params)
       
  2935 {
       
  2936 	var filter = params[1];
       
  2937 	var results = [];
       
  2938 	if(filter) {
       
  2939 		var tiddlers = store.filterTiddlers(filter);
       
  2940 		for(var t=0; t<tiddlers.length; t++)
       
  2941 			results.push(tiddlers[t].title);
       
  2942 	}
       
  2943 	return results;
       
  2944 };
       
  2945 
       
  2946 config.macros.allTags.handler = function(place,macroName,params)
       
  2947 {
       
  2948 	var tags = store.getTags(params[0]);
       
  2949 	var ul = createTiddlyElement(place,"ul");
       
  2950 	if(tags.length == 0)
       
  2951 		createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
       
  2952 	for(var t=0; t<tags.length; t++) {
       
  2953 		var title = tags[t][0];
       
  2954 		var info = getTiddlyLinkInfo(title);
       
  2955 		var li = createTiddlyElement(ul,"li");
       
  2956 		var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
       
  2957 		btn.setAttribute("tag",title);
       
  2958 		btn.setAttribute("refresh","link");
       
  2959 		btn.setAttribute("tiddlyLink",title);
       
  2960 	}
       
  2961 };
       
  2962 
       
  2963 config.macros.timeline.handler = function(place,macroName,params)
       
  2964 {
       
  2965 	var field = params[0] ? params[0] : "modified";
       
  2966 	var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
       
  2967 	var lastDay = "";
       
  2968 	var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
       
  2969 	var dateFormat = params[2] ? params[2] : this.dateFormat;
       
  2970 	for(var t=tiddlers.length-1; t>=last; t--) {
       
  2971 		var tiddler = tiddlers[t];
       
  2972 		var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
       
  2973 		if(theDay != lastDay) {
       
  2974 			var ul = document.createElement("ul");
       
  2975 			place.appendChild(ul);
       
  2976 			createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
       
  2977 			lastDay = theDay;
       
  2978 		}
       
  2979 		createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
       
  2980 	}
       
  2981 };
       
  2982 
       
  2983 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  2984 {
       
  2985 	params = paramString.parseParams("name",null,true,false,true);
       
  2986 	var names = params[0]["name"];
       
  2987 	var tiddlerName = names[0];
       
  2988 	var className = names[1] ? names[1] : null;
       
  2989 	var args = params[0]["with"];
       
  2990 	var wrapper = createTiddlyElement(place,"span",null,className);
       
  2991 	if(!args) {
       
  2992 		wrapper.setAttribute("refresh","content");
       
  2993 		wrapper.setAttribute("tiddler",tiddlerName);
       
  2994 	}
       
  2995 	var text = store.getTiddlerText(tiddlerName);
       
  2996 	if(text) {
       
  2997 		var stack = config.macros.tiddler.tiddlerStack;
       
  2998 		if(stack.indexOf(tiddlerName) !== -1)
       
  2999 			return;
       
  3000 		stack.push(tiddlerName);
       
  3001 		try {
       
  3002 			var n = args ? Math.min(args.length,9) : 0;
       
  3003 			for(var i=0; i<n; i++) {
       
  3004 				var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
       
  3005 				text = text.replace(placeholderRE,args[i]);
       
  3006 			}
       
  3007 			config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
       
  3008 		} finally {
       
  3009 			stack.pop();
       
  3010 		}
       
  3011 	}
       
  3012 };
       
  3013 
       
  3014 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
       
  3015 {
       
  3016 	wikify(text,place,null,store.getTiddler(tiddlerName));
       
  3017 };
       
  3018 
       
  3019 config.macros.tiddler.tiddlerStack = [];
       
  3020 
       
  3021 config.macros.tag.handler = function(place,macroName,params)
       
  3022 {
       
  3023 	createTagButton(place,params[0],null,params[1],params[2]);
       
  3024 };
       
  3025 
       
  3026 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3027 {
       
  3028 	params = paramString.parseParams("anon",null,true,false,false);
       
  3029 	var ul = createTiddlyElement(place,"ul");
       
  3030 	var title = getParam(params,"anon","");
       
  3031 	if(title && store.tiddlerExists(title))
       
  3032 		tiddler = store.getTiddler(title);
       
  3033 	var sep = getParam(params,"sep"," ");
       
  3034 	var lingo = config.views.wikified.tag;
       
  3035 	var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
       
  3036 	createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
       
  3037 	for(var t=0; t<tiddler.tags.length; t++) {
       
  3038 		createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
       
  3039 		if(t<tiddler.tags.length-1)
       
  3040 			createTiddlyText(ul,sep);
       
  3041 	}
       
  3042 };
       
  3043 
       
  3044 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3045 {
       
  3046 	params = paramString.parseParams("anon",null,true,false,false);
       
  3047 	var ul = createTiddlyElement(place,"ul");
       
  3048 	var title = getParam(params,"anon","");
       
  3049 	if(title == "" && tiddler instanceof Tiddler)
       
  3050 		title = tiddler.title;
       
  3051 	var sep = getParam(params,"sep"," ");
       
  3052 	ul.setAttribute("title",this.tooltip.format([title]));
       
  3053 	var tagged = store.getTaggedTiddlers(title);
       
  3054 	var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
       
  3055 	createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
       
  3056 	for(var t=0; t<tagged.length; t++) {
       
  3057 		createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
       
  3058 		if(t<tagged.length-1)
       
  3059 			createTiddlyText(ul,sep);
       
  3060 	}
       
  3061 };
       
  3062 
       
  3063 config.macros.closeAll.handler = function(place)
       
  3064 {
       
  3065 	createTiddlyButton(place,this.label,this.prompt,this.onClick);
       
  3066 };
       
  3067 
       
  3068 config.macros.closeAll.onClick = function(e)
       
  3069 {
       
  3070 	story.closeAllTiddlers();
       
  3071 	return false;
       
  3072 };
       
  3073 
       
  3074 config.macros.permaview.handler = function(place)
       
  3075 {
       
  3076 	createTiddlyButton(place,this.label,this.prompt,this.onClick);
       
  3077 };
       
  3078 
       
  3079 config.macros.permaview.onClick = function(e)
       
  3080 {
       
  3081 	story.permaView();
       
  3082 	return false;
       
  3083 };
       
  3084 
       
  3085 config.macros.saveChanges.handler = function(place,macroName,params)
       
  3086 {
       
  3087 	if(!readOnly)
       
  3088 		createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
       
  3089 };
       
  3090 
       
  3091 config.macros.saveChanges.onClick = function(e)
       
  3092 {
       
  3093 	saveChanges();
       
  3094 	return false;
       
  3095 };
       
  3096 
       
  3097 config.macros.slider.onClickSlider = function(ev)
       
  3098 {
       
  3099 	var e = ev ? ev : window.event;
       
  3100 	var n = this.nextSibling;
       
  3101 	var cookie = n.getAttribute("cookie");
       
  3102 	var isOpen = n.style.display != "none";
       
  3103 	if(config.options.chkAnimate && anim && typeof Slider == "function")
       
  3104 		anim.startAnimating(new Slider(n,!isOpen,null,"none"));
       
  3105 	else
       
  3106 		n.style.display = isOpen ? "none" : "block";
       
  3107 	config.options[cookie] = !isOpen;
       
  3108 	saveOptionCookie(cookie);
       
  3109 	return false;
       
  3110 };
       
  3111 
       
  3112 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
       
  3113 {
       
  3114 	var c = cookie ? cookie : "";
       
  3115 	var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
       
  3116 	var panel = createTiddlyElement(null,"div",null,"sliderPanel");
       
  3117 	panel.setAttribute("cookie",c);
       
  3118 	panel.style.display = config.options[c] ? "block" : "none";
       
  3119 	place.appendChild(panel);
       
  3120 	return panel;
       
  3121 };
       
  3122 
       
  3123 config.macros.slider.handler = function(place,macroName,params)
       
  3124 {
       
  3125 	var panel = this.createSlider(place,params[0],params[2],params[3]);
       
  3126 	var text = store.getTiddlerText(params[1]);
       
  3127 	panel.setAttribute("refresh","content");
       
  3128 	panel.setAttribute("tiddler",params[1]);
       
  3129 	if(text)
       
  3130 		wikify(text,panel,null,store.getTiddler(params[1]));
       
  3131 };
       
  3132 
       
  3133 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
       
  3134 config.macros.gradient.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3135 {
       
  3136 	var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
       
  3137 	panel.style.position = "relative";
       
  3138 	panel.style.overflow = "hidden";
       
  3139 	panel.style.zIndex = "0";
       
  3140 	if(wikifier) {
       
  3141 		var styles = config.formatterHelpers.inlineCssHelper(wikifier);
       
  3142 		config.formatterHelpers.applyCssHelper(panel,styles);
       
  3143 	}
       
  3144 	params = paramString.parseParams("color");
       
  3145 	var locolors = [], hicolors = [];
       
  3146 	for(var t=2; t<params.length; t++) {
       
  3147 		var c = new RGB(params[t].value);
       
  3148 		if(params[t].name == "snap") {
       
  3149 			hicolors[hicolors.length-1] = c;
       
  3150 		} else {
       
  3151 			locolors.push(c);
       
  3152 			hicolors.push(c);
       
  3153 		}
       
  3154 	}
       
  3155 	drawGradient(panel,params[1].value != "vert",locolors,hicolors);
       
  3156 	if(wikifier)
       
  3157 		wikifier.subWikify(panel,">>");
       
  3158 	if(document.all) {
       
  3159 		panel.style.height = "100%";
       
  3160 		panel.style.width = "100%";
       
  3161 	}
       
  3162 };
       
  3163 
       
  3164 config.macros.message.handler = function(place,macroName,params)
       
  3165 {
       
  3166 	if(params[0]) {
       
  3167 		var names = params[0].split(".");
       
  3168 		var lookupMessage = function(root,nameIndex) {
       
  3169 				if(names[nameIndex] in root) {
       
  3170 					if(nameIndex < names.length-1)
       
  3171 						return (lookupMessage(root[names[nameIndex]],nameIndex+1));
       
  3172 					else
       
  3173 						return root[names[nameIndex]];
       
  3174 				} else
       
  3175 					return null;
       
  3176 			};
       
  3177 		var m = lookupMessage(config,0);
       
  3178 		if(m == null)
       
  3179 			m = lookupMessage(window,0);
       
  3180 		createTiddlyText(place,m.toString().format(params.splice(1)));
       
  3181 	}
       
  3182 };
       
  3183 
       
  3184 
       
  3185 config.macros.view.views = {
       
  3186 	text: function(value,place,params,wikifier,paramString,tiddler) {
       
  3187 		highlightify(value,place,highlightHack,tiddler);
       
  3188 	},
       
  3189 	link: function(value,place,params,wikifier,paramString,tiddler) {
       
  3190 		createTiddlyLink(place,value,true);
       
  3191 	},
       
  3192 	wikified: function(value,place,params,wikifier,paramString,tiddler) {
       
  3193 		if(params[2])
       
  3194 			value=params[2].unescapeLineBreaks().format([value]);
       
  3195 		wikify(value,place,highlightHack,tiddler);
       
  3196 	},
       
  3197 	date: function(value,place,params,wikifier,paramString,tiddler) {
       
  3198 		value = Date.convertFromYYYYMMDDHHMM(value);
       
  3199 		createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
       
  3200 	}
       
  3201 };
       
  3202 
       
  3203 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3204 {
       
  3205 	if((tiddler instanceof Tiddler) && params[0]) {
       
  3206 		var value = store.getValue(tiddler,params[0]);
       
  3207 		if(value) {
       
  3208 			var type = params[1] ? params[1] : config.macros.view.defaultView;
       
  3209 			var handler = config.macros.view.views[type];
       
  3210 			if(handler)
       
  3211 				handler(value,place,params,wikifier,paramString,tiddler);
       
  3212 		}
       
  3213 	}
       
  3214 };
       
  3215 
       
  3216 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3217 {
       
  3218 	var field = params[0];
       
  3219 	var rows = params[1] || 0;
       
  3220 	var defVal = params[2] || '';
       
  3221 	if((tiddler instanceof Tiddler) && field) {
       
  3222 		story.setDirty(tiddler.title,true);
       
  3223 		var e,v;
       
  3224 		if(field != "text" && !rows) {
       
  3225 			e = createTiddlyElement(null,"input");
       
  3226 			if(tiddler.isReadOnly())
       
  3227 				e.setAttribute("readOnly","readOnly");
       
  3228 			e.setAttribute("edit",field);
       
  3229 			e.setAttribute("type","text");
       
  3230 			e.value = store.getValue(tiddler,field) || defVal;
       
  3231 			e.setAttribute("size","40");
       
  3232 			e.setAttribute("autocomplete","off");
       
  3233 			place.appendChild(e);
       
  3234 		} else {
       
  3235 			var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
       
  3236 			var wrapper2 = createTiddlyElement(wrapper1,"div");
       
  3237 			e = createTiddlyElement(wrapper2,"textarea");
       
  3238 			if(tiddler.isReadOnly())
       
  3239 				e.setAttribute("readOnly","readOnly");
       
  3240 			e.value = v = store.getValue(tiddler,field) || defVal;
       
  3241 			rows = rows ? rows : 10;
       
  3242 			var lines = v.match(/\n/mg);
       
  3243 			var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
       
  3244 			if(lines != null && lines.length > rows)
       
  3245 				rows = lines.length + 5;
       
  3246 			rows = Math.min(rows,maxLines);
       
  3247 			e.setAttribute("rows",rows);
       
  3248 			e.setAttribute("edit",field);
       
  3249 			place.appendChild(wrapper1);
       
  3250 		}
       
  3251 		return e;
       
  3252 	}
       
  3253 };
       
  3254 
       
  3255 config.macros.tagChooser.onClick = function(ev)
       
  3256 {
       
  3257 	var e = ev ? ev : window.event;
       
  3258 	if(e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
       
  3259 	var lingo = config.views.editor.tagChooser;
       
  3260 	var popup = Popup.create(this);
       
  3261 	var tags = store.getTags();
       
  3262 	if(tags.length == 0)
       
  3263 		createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
       
  3264 	for(var t=0; t<tags.length; t++) {
       
  3265 		var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
       
  3266 		tag.setAttribute("tag",tags[t][0]);
       
  3267 		tag.setAttribute("tiddler",this.getAttribute("tiddler"));
       
  3268 	}
       
  3269 	Popup.show();
       
  3270 	e.cancelBubble = true;
       
  3271 	if(e.stopPropagation) e.stopPropagation();
       
  3272 	return false;
       
  3273 };
       
  3274 
       
  3275 config.macros.tagChooser.onTagClick = function(ev)
       
  3276 {
       
  3277 	var e = ev ? ev : window.event;
       
  3278 	var tag = this.getAttribute("tag");
       
  3279 	var title = this.getAttribute("tiddler");
       
  3280 	if(!readOnly)
       
  3281 		story.setTiddlerTag(title,tag,0);
       
  3282 	return false;
       
  3283 };
       
  3284 
       
  3285 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3286 {
       
  3287 	if(tiddler instanceof Tiddler) {
       
  3288 		var lingo = config.views.editor.tagChooser;
       
  3289 		var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
       
  3290 		btn.setAttribute("tiddler",tiddler.title);
       
  3291 	}
       
  3292 };
       
  3293 
       
  3294 config.macros.refreshDisplay.handler = function(place)
       
  3295 {
       
  3296 	createTiddlyButton(place,this.label,this.prompt,this.onClick);
       
  3297 };
       
  3298 
       
  3299 config.macros.refreshDisplay.onClick = function(e)
       
  3300 {
       
  3301 	refreshAll();
       
  3302 	return false;
       
  3303 };
       
  3304 
       
  3305 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3306 {
       
  3307 	var title = tiddler ? tiddler.title : null;
       
  3308 	var a = title ? config.annotations[title] : null;
       
  3309 	if(!tiddler || !title || !a)
       
  3310 		return;
       
  3311 	var text = a.format([title]);
       
  3312 	wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
       
  3313 };
       
  3314 
       
  3315 //--
       
  3316 //-- NewTiddler and NewJournal macros
       
  3317 //--
       
  3318 
       
  3319 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
       
  3320 {
       
  3321 	var tags = [];
       
  3322 	for(var t=1; t<params.length; t++) {
       
  3323 		if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
       
  3324 			tags.push(params[t].value);
       
  3325 	}
       
  3326 	label = getParam(params,"label",label);
       
  3327 	prompt = getParam(params,"prompt",prompt);
       
  3328 	accessKey = getParam(params,"accessKey",accessKey);
       
  3329 	newFocus = getParam(params,"focus",newFocus);
       
  3330 	var customFields = getParam(params,"fields","");
       
  3331 	if(!customFields && !store.isShadowTiddler(title))
       
  3332 		customFields = String.encodeHashMap(config.defaultCustomFields);
       
  3333 	var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
       
  3334 	btn.setAttribute("newTitle",title);
       
  3335 	btn.setAttribute("isJournal",isJournal ? "true" : "false");
       
  3336 	if(tags.length > 0)
       
  3337 		btn.setAttribute("params",tags.join("|"));
       
  3338 	btn.setAttribute("newFocus",newFocus);
       
  3339 	btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
       
  3340 	if(customFields !== "")
       
  3341 		btn.setAttribute("customFields",customFields);
       
  3342 	var text = getParam(params,"text");
       
  3343 	if(text !== undefined)
       
  3344 		btn.setAttribute("newText",text);
       
  3345 	return btn;
       
  3346 };
       
  3347 
       
  3348 config.macros.newTiddler.onClickNewTiddler = function()
       
  3349 {
       
  3350 	var title = this.getAttribute("newTitle");
       
  3351 	if(this.getAttribute("isJournal") == "true") {
       
  3352 		var now = new Date();
       
  3353 		title = now.formatString(title.trim());
       
  3354 	}
       
  3355 	var params = this.getAttribute("params");
       
  3356 	var tags = params ? params.split("|") : [];
       
  3357 	var focus = this.getAttribute("newFocus");
       
  3358 	var template = this.getAttribute("newTemplate");
       
  3359 	var customFields = this.getAttribute("customFields");
       
  3360 	story.displayTiddler(null,title,template,false,null,null);
       
  3361 	var tiddlerElem = story.getTiddler(title);
       
  3362 	if(customFields)
       
  3363 		story.addCustomFields(tiddlerElem,customFields);
       
  3364 	var text = this.getAttribute("newText");
       
  3365 	if(typeof text == "string")
       
  3366 		story.getTiddlerField(title,"text").value = text.format([title]);
       
  3367 	for(var t=0;t<tags.length;t++)
       
  3368 		story.setTiddlerTag(title,tags[t],+1);
       
  3369 	story.focusTiddler(title,focus);
       
  3370 	return false;
       
  3371 };
       
  3372 
       
  3373 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3374 {
       
  3375 	if(!readOnly) {
       
  3376 		params = paramString.parseParams("anon",null,true,false,false);
       
  3377 		var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
       
  3378 		title = getParam(params,"title",title);
       
  3379 		this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
       
  3380 	}
       
  3381 };
       
  3382 
       
  3383 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3384 {
       
  3385 	if(!readOnly) {
       
  3386 		params = paramString.parseParams("anon",null,true,false,false);
       
  3387 		var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
       
  3388 		title = getParam(params,"title",title);
       
  3389 		config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
       
  3390 	}
       
  3391 };
       
  3392 
       
  3393 //--
       
  3394 //-- Search macro
       
  3395 //--
       
  3396 
       
  3397 config.macros.search.handler = function(place,macroName,params)
       
  3398 {
       
  3399 	var searchTimeout = null;
       
  3400 	var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick,"searchButton");
       
  3401 	var txt = createTiddlyElement(place,"input",null,"txtOptionInput searchField");
       
  3402 	if(params[0])
       
  3403 		txt.value = params[0];
       
  3404 	txt.onkeyup = this.onKeyPress;
       
  3405 	txt.onfocus = this.onFocus;
       
  3406 	txt.setAttribute("size",this.sizeTextbox);
       
  3407 	txt.setAttribute("accessKey",this.accessKey);
       
  3408 	txt.setAttribute("autocomplete","off");
       
  3409 	txt.setAttribute("lastSearchText","");
       
  3410 	if(config.browser.isSafari) {
       
  3411 		txt.setAttribute("type","search");
       
  3412 		txt.setAttribute("results","5");
       
  3413 	} else {
       
  3414 		txt.setAttribute("type","text");
       
  3415 	}
       
  3416 };
       
  3417 
       
  3418 // Global because there's only ever one outstanding incremental search timer
       
  3419 config.macros.search.timeout = null;
       
  3420 
       
  3421 config.macros.search.doSearch = function(txt)
       
  3422 {
       
  3423 	if(txt.value.length > 0) {
       
  3424 		story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
       
  3425 		txt.setAttribute("lastSearchText",txt.value);
       
  3426 	}
       
  3427 };
       
  3428 
       
  3429 config.macros.search.onClick = function(e)
       
  3430 {
       
  3431 	config.macros.search.doSearch(this.nextSibling);
       
  3432 	return false;
       
  3433 };
       
  3434 
       
  3435 config.macros.search.onKeyPress = function(ev)
       
  3436 {
       
  3437 	var e = ev ? ev : window.event;
       
  3438 	switch(e.keyCode) {
       
  3439 		case 13: // Ctrl-Enter
       
  3440 		case 10: // Ctrl-Enter on IE PC
       
  3441 			config.macros.search.doSearch(this);
       
  3442 			break;
       
  3443 		case 27: // Escape
       
  3444 			this.value = "";
       
  3445 			clearMessage();
       
  3446 			break;
       
  3447 	}
       
  3448 	if(config.options.chkIncrementalSearch) {
       
  3449 		if(this.value.length > 2) {
       
  3450 			if(this.value != this.getAttribute("lastSearchText")) {
       
  3451 				if(config.macros.search.timeout)
       
  3452 					clearTimeout(config.macros.search.timeout);
       
  3453 				var txt = this;
       
  3454 				config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
       
  3455 			}
       
  3456 		} else {
       
  3457 			if(config.macros.search.timeout)
       
  3458 				clearTimeout(config.macros.search.timeout);
       
  3459 		}
       
  3460 	}
       
  3461 };
       
  3462 
       
  3463 config.macros.search.onFocus = function(e)
       
  3464 {
       
  3465 	this.select();
       
  3466 };
       
  3467 
       
  3468 //--
       
  3469 //-- Tabs macro
       
  3470 //--
       
  3471 
       
  3472 config.macros.tabs.handler = function(place,macroName,params)
       
  3473 {
       
  3474 	var cookie = params[0];
       
  3475 	var numTabs = (params.length-1)/3;
       
  3476 	var wrapper = createTiddlyElement(null,"div",null,"tabsetWrapper " + cookie);
       
  3477 	var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
       
  3478 	tabset.setAttribute("cookie",cookie);
       
  3479 	var validTab = false;
       
  3480 	for(var t=0; t<numTabs; t++) {
       
  3481 		var label = params[t*3+1];
       
  3482 		var prompt = params[t*3+2];
       
  3483 		var content = params[t*3+3];
       
  3484 		var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
       
  3485 		tab.setAttribute("tab",label);
       
  3486 		tab.setAttribute("content",content);
       
  3487 		tab.title = prompt;
       
  3488 		if(config.options[cookie] == label)
       
  3489 			validTab = true;
       
  3490 	}
       
  3491 	if(!validTab)
       
  3492 		config.options[cookie] = params[1];
       
  3493 	place.appendChild(wrapper);
       
  3494 	this.switchTab(tabset,config.options[cookie]);
       
  3495 };
       
  3496 
       
  3497 config.macros.tabs.onClickTab = function(e)
       
  3498 {
       
  3499 	config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
       
  3500 	return false;
       
  3501 };
       
  3502 
       
  3503 config.macros.tabs.switchTab = function(tabset,tab)
       
  3504 {
       
  3505 	var cookie = tabset.getAttribute("cookie");
       
  3506 	var theTab = null;
       
  3507 	var nodes = tabset.childNodes;
       
  3508 	for(var t=0; t<nodes.length; t++) {
       
  3509 		if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
       
  3510 			theTab = nodes[t];
       
  3511 			theTab.className = "tab tabSelected";
       
  3512 		} else {
       
  3513 			nodes[t].className = "tab tabUnselected";
       
  3514 		}
       
  3515 	}
       
  3516 	if(theTab) {
       
  3517 		if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
       
  3518 			removeNode(tabset.nextSibling);
       
  3519 		var tabContent = createTiddlyElement(null,"div",null,"tabContents");
       
  3520 		tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
       
  3521 		var contentTitle = theTab.getAttribute("content");
       
  3522 		wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
       
  3523 		if(cookie) {
       
  3524 			config.options[cookie] = tab;
       
  3525 			saveOptionCookie(cookie);
       
  3526 		}
       
  3527 	}
       
  3528 };
       
  3529 
       
  3530 //--
       
  3531 //-- Tiddler toolbar
       
  3532 //--
       
  3533 
       
  3534 // Create a toolbar command button
       
  3535 config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
       
  3536 {
       
  3537 	if(typeof commandName != "string") {
       
  3538 		var c = null;
       
  3539 		for(var t in config.commands) {
       
  3540 			if(config.commands[t] == commandName)
       
  3541 				c = t;
       
  3542 		}
       
  3543 		commandName = c;
       
  3544 	}
       
  3545 	if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
       
  3546 		var command = config.commands[commandName];
       
  3547 		if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
       
  3548 			var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
       
  3549 			var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
       
  3550 			var cmd;
       
  3551 			switch(command.type) {
       
  3552 				case "popup":
       
  3553 					cmd = this.onClickPopup;
       
  3554 					break;
       
  3555 				case "command":
       
  3556 				default:
       
  3557 					cmd = this.onClickCommand;
       
  3558 					break;
       
  3559 			}
       
  3560 			var btn = createTiddlyButton(null,text,tooltip,cmd);
       
  3561 			btn.setAttribute("commandName",commandName);
       
  3562 			btn.setAttribute("tiddler",tiddler.title);
       
  3563 			if(className)
       
  3564 				addClass(btn,className);
       
  3565 			place.appendChild(btn);
       
  3566 		}
       
  3567 	}
       
  3568 };
       
  3569 
       
  3570 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
       
  3571 {
       
  3572 	var title = tiddler.title;
       
  3573 	var ro = tiddler.isReadOnly();
       
  3574 	var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
       
  3575 	return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
       
  3576 };
       
  3577 
       
  3578 config.macros.toolbar.getCommandText = function(command,tiddler)
       
  3579 {
       
  3580 	return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
       
  3581 };
       
  3582 
       
  3583 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
       
  3584 {
       
  3585 	return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
       
  3586 };
       
  3587 
       
  3588 config.macros.toolbar.onClickCommand = function(ev)
       
  3589 {
       
  3590 	var e = ev ? ev : window.event;
       
  3591 	e.cancelBubble = true;
       
  3592 	if(e.stopPropagation) e.stopPropagation();
       
  3593 	var command = config.commands[this.getAttribute("commandName")];
       
  3594 	return command.handler(e,this,this.getAttribute("tiddler"));
       
  3595 };
       
  3596 
       
  3597 config.macros.toolbar.onClickPopup = function(ev)
       
  3598 {
       
  3599 	var e = ev ? ev : window.event;
       
  3600 	e.cancelBubble = true;
       
  3601 	if(e.stopPropagation) e.stopPropagation();
       
  3602 	var popup = Popup.create(this);
       
  3603 	var command = config.commands[this.getAttribute("commandName")];
       
  3604 	var title = this.getAttribute("tiddler");
       
  3605 	var tiddler = store.fetchTiddler(title);
       
  3606 	popup.setAttribute("tiddler",title);
       
  3607 	command.handlePopup(popup,title);
       
  3608 	Popup.show();
       
  3609 	return false;
       
  3610 };
       
  3611 
       
  3612 // Invoke the first command encountered from a given place that is tagged with a specified class
       
  3613 config.macros.toolbar.invokeCommand = function(place,className,event)
       
  3614 {
       
  3615 	var children = place.getElementsByTagName("a");
       
  3616 	for(var t=0; t<children.length; t++) {
       
  3617 		var c = children[t];
       
  3618 		if(hasClass(c,className) && c.getAttribute && c.getAttribute("commandName")) {
       
  3619 			if(c.onclick instanceof Function)
       
  3620 				c.onclick.call(c,event);
       
  3621 			break;
       
  3622 		}
       
  3623 	}
       
  3624 };
       
  3625 
       
  3626 config.macros.toolbar.onClickMore = function(ev)
       
  3627 {
       
  3628 	var e = this.nextSibling;
       
  3629 	e.style.display = "inline";
       
  3630 	removeNode(this);
       
  3631 	return false;
       
  3632 };
       
  3633 
       
  3634 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  3635 {
       
  3636 	for(var t=0; t<params.length; t++) {
       
  3637 		var c = params[t];
       
  3638 		switch(c) {
       
  3639 			case '>':
       
  3640 				var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
       
  3641 				addClass(btn,"moreCommand");
       
  3642 				var e = createTiddlyElement(place,"span",null,"moreCommand");
       
  3643 				e.style.display = "none";
       
  3644 				place = e;
       
  3645 				break;
       
  3646 			default:
       
  3647 				var className = "";
       
  3648 				switch(c.substr(0,1)) {
       
  3649 					case "+":
       
  3650 						className = "defaultCommand";
       
  3651 						c = c.substr(1);
       
  3652 						break;
       
  3653 					case "-":
       
  3654 						className = "cancelCommand";
       
  3655 						c = c.substr(1);
       
  3656 						break;
       
  3657 				}
       
  3658 				if(c in config.commands)
       
  3659 					this.createCommand(place,c,tiddler,className);
       
  3660 				break;
       
  3661 		}
       
  3662 	}
       
  3663 };
       
  3664 
       
  3665 //--
       
  3666 //-- Menu and toolbar commands
       
  3667 //--
       
  3668 
       
  3669 config.commands.closeTiddler.handler = function(event,src,title)
       
  3670 {
       
  3671 	if(story.isDirty(title) && !readOnly) {
       
  3672 		if(!confirm(config.commands.cancelTiddler.warning.format([title])))
       
  3673 			return false;
       
  3674 	}
       
  3675 	story.setDirty(title,false);
       
  3676 	story.closeTiddler(title,true);
       
  3677 	return false;
       
  3678 };
       
  3679 
       
  3680 config.commands.closeOthers.handler = function(event,src,title)
       
  3681 {
       
  3682 	story.closeAllTiddlers(title);
       
  3683 	return false;
       
  3684 };
       
  3685 
       
  3686 config.commands.editTiddler.handler = function(event,src,title)
       
  3687 {
       
  3688 	clearMessage();
       
  3689 	var tiddlerElem = story.getTiddler(title);
       
  3690 	var fields = tiddlerElem.getAttribute("tiddlyFields");
       
  3691 	story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
       
  3692 	story.focusTiddler(title,config.options.txtEditorFocus||"text");
       
  3693 	return false;
       
  3694 };
       
  3695 
       
  3696 config.commands.saveTiddler.handler = function(event,src,title)
       
  3697 {
       
  3698 	var newTitle = story.saveTiddler(title,event.shiftKey);
       
  3699 	if(newTitle)
       
  3700 		story.displayTiddler(null,newTitle);
       
  3701 	return false;
       
  3702 };
       
  3703 
       
  3704 config.commands.cancelTiddler.handler = function(event,src,title)
       
  3705 {
       
  3706 	if(story.hasChanges(title) && !readOnly) {
       
  3707 		if(!confirm(this.warning.format([title])))
       
  3708 			return false;
       
  3709 	}
       
  3710 	story.setDirty(title,false);
       
  3711 	story.displayTiddler(null,title);
       
  3712 	return false;
       
  3713 };
       
  3714 
       
  3715 config.commands.deleteTiddler.handler = function(event,src,title)
       
  3716 {
       
  3717 	var deleteIt = true;
       
  3718 	if(config.options.chkConfirmDelete)
       
  3719 		deleteIt = confirm(this.warning.format([title]));
       
  3720 	if(deleteIt) {
       
  3721 		store.removeTiddler(title);
       
  3722 		story.closeTiddler(title,true);
       
  3723 		autoSaveChanges();
       
  3724 	}
       
  3725 	return false;
       
  3726 };
       
  3727 
       
  3728 config.commands.permalink.handler = function(event,src,title)
       
  3729 {
       
  3730 	var t = encodeURIComponent(String.encodeTiddlyLink(title));
       
  3731 	if(window.location.hash != t)
       
  3732 		window.location.hash = t;
       
  3733 	return false;
       
  3734 };
       
  3735 
       
  3736 config.commands.references.handlePopup = function(popup,title)
       
  3737 {
       
  3738 	var references = store.getReferringTiddlers(title);
       
  3739 	var c = false;
       
  3740 	for(var r=0; r<references.length; r++) {
       
  3741 		if(references[r].title != title && !references[r].isTagged("excludeLists")) {
       
  3742 			createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
       
  3743 			c = true;
       
  3744 		}
       
  3745 	}
       
  3746 	if(!c)
       
  3747 		createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
       
  3748 };
       
  3749 
       
  3750 config.commands.jump.handlePopup = function(popup,title)
       
  3751 {
       
  3752 	story.forEachTiddler(function(title,element) {
       
  3753 		createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
       
  3754 		});
       
  3755 };
       
  3756 
       
  3757 config.commands.syncing.handlePopup = function(popup,title)
       
  3758 {
       
  3759 	var tiddler = store.fetchTiddler(title);
       
  3760 	if(!tiddler)
       
  3761 		return;
       
  3762 	var serverType = tiddler.getServerType();
       
  3763 	var serverHost = tiddler.fields['server.host'];
       
  3764 	var serverWorkspace = tiddler.fields['server.workspace'];
       
  3765 	if(!serverWorkspace)
       
  3766 		serverWorkspace = "";
       
  3767 	if(serverType) {
       
  3768 		var e = createTiddlyElement(popup,"li",null,"popupMessage");
       
  3769 		e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
       
  3770 	} else {
       
  3771 		createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
       
  3772 	}
       
  3773 	if(serverType) {
       
  3774 		createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
       
  3775 		var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
       
  3776 		btn.setAttribute("tiddler",title);
       
  3777 		btn.setAttribute("server.type","");
       
  3778 	}
       
  3779 	createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
       
  3780 	createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
       
  3781 	var feeds = store.getTaggedTiddlers("systemServer","title");
       
  3782 	for(var t=0; t<feeds.length; t++) {
       
  3783 		var f = feeds[t];
       
  3784 		var feedServerType = store.getTiddlerSlice(f.title,"Type");
       
  3785 		if(!feedServerType)
       
  3786 			feedServerType = "file";
       
  3787 		var feedServerHost = store.getTiddlerSlice(f.title,"URL");
       
  3788 		if(!feedServerHost)
       
  3789 			feedServerHost = "";
       
  3790 		var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
       
  3791 		if(!feedServerWorkspace)
       
  3792 			feedServerWorkspace = "";
       
  3793 		var caption = f.title;
       
  3794 		if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
       
  3795 			caption = config.commands.syncing.currServerMarker + caption;
       
  3796 		} else {
       
  3797 			caption = config.commands.syncing.notCurrServerMarker + caption;
       
  3798 		}
       
  3799 		btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
       
  3800 		btn.setAttribute("tiddler",title);
       
  3801 		btn.setAttribute("server.type",feedServerType);
       
  3802 		btn.setAttribute("server.host",feedServerHost);
       
  3803 		btn.setAttribute("server.workspace",feedServerWorkspace);
       
  3804 	}
       
  3805 };
       
  3806 
       
  3807 config.commands.syncing.onChooseServer = function(e)
       
  3808 {
       
  3809 	var tiddler = this.getAttribute("tiddler");
       
  3810 	var serverType = this.getAttribute("server.type");
       
  3811 	if(serverType) {
       
  3812 		store.addTiddlerFields(tiddler,{
       
  3813 			'server.type': serverType,
       
  3814 			'server.host': this.getAttribute("server.host"),
       
  3815 			'server.workspace': this.getAttribute("server.workspace")
       
  3816 			});
       
  3817 	} else {
       
  3818 		store.setValue(tiddler,'server',null);
       
  3819 	}
       
  3820 	return false;
       
  3821 };
       
  3822 
       
  3823 config.commands.fields.handlePopup = function(popup,title)
       
  3824 {
       
  3825 	var tiddler = store.fetchTiddler(title);
       
  3826 	if(!tiddler)
       
  3827 		return;
       
  3828 	var fields = {};
       
  3829 	store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
       
  3830 	var items = [];
       
  3831 	for(var t in fields) {
       
  3832 		items.push({field: t,value: fields[t]});
       
  3833 	}
       
  3834 	items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
       
  3835 	if(items.length > 0)
       
  3836 		ListView.create(popup,items,this.listViewTemplate);
       
  3837 	else
       
  3838 		createTiddlyElement(popup,"div",null,null,this.emptyText);
       
  3839 };
       
  3840 
       
  3841 //--
       
  3842 //-- Tiddler() object
       
  3843 //--
       
  3844 
       
  3845 function Tiddler(title)
       
  3846 {
       
  3847 	this.title = title;
       
  3848 	this.text = "";
       
  3849 	this.modifier = null;
       
  3850 	this.created = new Date();
       
  3851 	this.modified = this.created;
       
  3852 	this.links = [];
       
  3853 	this.linksUpdated = false;
       
  3854 	this.tags = [];
       
  3855 	this.fields = {};
       
  3856 	return this;
       
  3857 }
       
  3858 
       
  3859 Tiddler.prototype.getLinks = function()
       
  3860 {
       
  3861 	if(this.linksUpdated==false)
       
  3862 		this.changed();
       
  3863 	return this.links;
       
  3864 };
       
  3865 
       
  3866 // Returns the fields that are inherited in string field:"value" field2:"value2" format
       
  3867 Tiddler.prototype.getInheritedFields = function()
       
  3868 {
       
  3869 	var f = {};
       
  3870 	for(i in this.fields) {
       
  3871 		if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
       
  3872 			f[i] = this.fields[i];
       
  3873 		}
       
  3874 	}
       
  3875 	return String.encodeHashMap(f);
       
  3876 };
       
  3877 
       
  3878 // Increment the changeCount of a tiddler
       
  3879 Tiddler.prototype.incChangeCount = function()
       
  3880 {
       
  3881 	var c = this.fields['changecount'];
       
  3882 	c = c ? parseInt(c) : 0;
       
  3883 	this.fields['changecount'] = String(c+1);
       
  3884 };
       
  3885 
       
  3886 // Clear the changeCount of a tiddler
       
  3887 Tiddler.prototype.clearChangeCount = function()
       
  3888 {
       
  3889 	if(this.fields['changecount']) {
       
  3890 		delete this.fields['changecount'];
       
  3891 	}
       
  3892 };
       
  3893 
       
  3894 Tiddler.prototype.doNotSave = function()
       
  3895 {
       
  3896 	return this.fields['doNotSave'];
       
  3897 };
       
  3898 
       
  3899 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
       
  3900 Tiddler.prototype.isTouched = function()
       
  3901 {
       
  3902 	var changeCount = this.fields['changecount'];
       
  3903 	if(changeCount === undefined)
       
  3904 		changeCount = 0;
       
  3905 	return changeCount > 0;
       
  3906 };
       
  3907 
       
  3908 // Return the tiddler as an RSS item
       
  3909 Tiddler.prototype.toRssItem = function(uri)
       
  3910 {
       
  3911 	var s = [];
       
  3912 	s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
       
  3913 	s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
       
  3914 	for(var t=0; t<this.tags.length; t++)
       
  3915 		s.push("<category>" + this.tags[t] + "</category>");
       
  3916 	s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
       
  3917 	s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
       
  3918 	return s.join("\n");
       
  3919 };
       
  3920 
       
  3921 // Format the text for storage in an RSS item
       
  3922 Tiddler.prototype.saveToRss = function(uri)
       
  3923 {
       
  3924 	return "<item>\n" + this.toRssItem(uri) + "\n</item>";
       
  3925 };
       
  3926 
       
  3927 // Change the text and other attributes of a tiddler
       
  3928 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
       
  3929 {
       
  3930 	this.assign(title,text,modifier,modified,tags,created,fields);
       
  3931 	this.changed();
       
  3932 	return this;
       
  3933 };
       
  3934 
       
  3935 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
       
  3936 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
       
  3937 {
       
  3938 	if(title != undefined)
       
  3939 		this.title = title;
       
  3940 	if(text != undefined)
       
  3941 		this.text = text;
       
  3942 	if(modifier != undefined)
       
  3943 		this.modifier = modifier;
       
  3944 	if(modified != undefined)
       
  3945 		this.modified = modified;
       
  3946 	if(created != undefined)
       
  3947 		this.created = created;
       
  3948 	if(fields != undefined)
       
  3949 		this.fields = fields;
       
  3950 	if(tags != undefined)
       
  3951 		this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
       
  3952 	else if(this.tags == undefined)
       
  3953 		this.tags = [];
       
  3954 	return this;
       
  3955 };
       
  3956 
       
  3957 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
       
  3958 Tiddler.prototype.getTags = function()
       
  3959 {
       
  3960 	return String.encodeTiddlyLinkList(this.tags);
       
  3961 };
       
  3962 
       
  3963 // Test if a tiddler carries a tag
       
  3964 Tiddler.prototype.isTagged = function(tag)
       
  3965 {
       
  3966 	return this.tags.indexOf(tag) != -1;
       
  3967 };
       
  3968 
       
  3969 // Static method to convert "\n" to newlines, "\s" to "\"
       
  3970 Tiddler.unescapeLineBreaks = function(text)
       
  3971 {
       
  3972 	return text ? text.unescapeLineBreaks() : "";
       
  3973 };
       
  3974 
       
  3975 // Convert newlines to "\n", "\" to "\s"
       
  3976 Tiddler.prototype.escapeLineBreaks = function()
       
  3977 {
       
  3978 	return this.text.escapeLineBreaks();
       
  3979 };
       
  3980 
       
  3981 // Updates the secondary information (like links[] array) after a change to a tiddler
       
  3982 Tiddler.prototype.changed = function()
       
  3983 {
       
  3984 	this.links = [];
       
  3985 	var t = this.autoLinkWikiWords() ? 0 : 1;
       
  3986 	var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
       
  3987 	tiddlerLinkRegExp.lastIndex = 0;
       
  3988 	var formatMatch = tiddlerLinkRegExp.exec(this.text);
       
  3989 	while(formatMatch) {
       
  3990 		var lastIndex = tiddlerLinkRegExp.lastIndex;
       
  3991 		if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
       
  3992 			// wikiWordLink
       
  3993 			if(formatMatch.index > 0) {
       
  3994 				var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
       
  3995 				preRegExp.lastIndex = formatMatch.index-1;
       
  3996 				var preMatch = preRegExp.exec(this.text);
       
  3997 				if(preMatch.index != formatMatch.index-1)
       
  3998 					this.links.pushUnique(formatMatch[1]);
       
  3999 			} else {
       
  4000 				this.links.pushUnique(formatMatch[1]);
       
  4001 			}
       
  4002 		}
       
  4003 		else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
       
  4004 			this.links.pushUnique(formatMatch[3-t]);
       
  4005 		else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
       
  4006 			this.links.pushUnique(formatMatch[4-t]);
       
  4007 		tiddlerLinkRegExp.lastIndex = lastIndex;
       
  4008 		formatMatch = tiddlerLinkRegExp.exec(this.text);
       
  4009 	}
       
  4010 	this.linksUpdated = true;
       
  4011 };
       
  4012 
       
  4013 Tiddler.prototype.getSubtitle = function()
       
  4014 {
       
  4015 	var modifier = this.modifier;
       
  4016 	if(!modifier)
       
  4017 		modifier = config.messages.subtitleUnknown;
       
  4018 	var modified = this.modified;
       
  4019 	if(modified)
       
  4020 		modified = modified.toLocaleString();
       
  4021 	else
       
  4022 		modified = config.messages.subtitleUnknown;
       
  4023 	return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
       
  4024 };
       
  4025 
       
  4026 Tiddler.prototype.isReadOnly = function()
       
  4027 {
       
  4028 	return readOnly;
       
  4029 };
       
  4030 
       
  4031 Tiddler.prototype.autoLinkWikiWords = function()
       
  4032 {
       
  4033 	return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
       
  4034 };
       
  4035 
       
  4036 Tiddler.prototype.generateFingerprint = function()
       
  4037 {
       
  4038 	return "0x" + Crypto.hexSha1Str(this.text);
       
  4039 };
       
  4040 
       
  4041 Tiddler.prototype.getServerType = function()
       
  4042 {
       
  4043 	var serverType = null;
       
  4044 	if(this.fields && this.fields['server.type'])
       
  4045 		serverType = this.fields['server.type'];
       
  4046 	if(!serverType)
       
  4047 		serverType = this.fields['wikiformat'];
       
  4048 	if(serverType && !config.adaptors[serverType])
       
  4049 		serverType = null;
       
  4050 	return serverType;
       
  4051 };
       
  4052 
       
  4053 Tiddler.prototype.getAdaptor = function()
       
  4054 {
       
  4055 	var serverType = this.getServerType();
       
  4056 	return serverType ? new config.adaptors[serverType] : null;
       
  4057 };
       
  4058 
       
  4059 //--
       
  4060 //-- TiddlyWiki() object contains Tiddler()s
       
  4061 //--
       
  4062 
       
  4063 function TiddlyWiki()
       
  4064 {
       
  4065 	var tiddlers = {}; // Hashmap by name of tiddlers
       
  4066 	this.tiddlersUpdated = false;
       
  4067 	this.namedNotifications = []; // Array of {name:,notify:} of notification functions
       
  4068 	this.notificationLevel = 0;
       
  4069 	this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
       
  4070 	this.clear = function() {
       
  4071 		tiddlers = {};
       
  4072 		this.setDirty(false);
       
  4073 	};
       
  4074 	this.fetchTiddler = function(title) {
       
  4075 		var t = tiddlers[title];
       
  4076 		return t instanceof Tiddler ? t : null;
       
  4077 	};
       
  4078 	this.deleteTiddler = function(title) {
       
  4079 		delete this.slices[title];
       
  4080 		delete tiddlers[title];
       
  4081 	};
       
  4082 	this.addTiddler = function(tiddler) {
       
  4083 		delete this.slices[tiddler.title];
       
  4084 		tiddlers[tiddler.title] = tiddler;
       
  4085 	};
       
  4086 	this.forEachTiddler = function(callback) {
       
  4087 		for(var t in tiddlers) {
       
  4088 			var tiddler = tiddlers[t];
       
  4089 			if(tiddler instanceof Tiddler)
       
  4090 				callback.call(this,t,tiddler);
       
  4091 		}
       
  4092 	};
       
  4093 }
       
  4094 
       
  4095 TiddlyWiki.prototype.setDirty = function(dirty)
       
  4096 {
       
  4097 	this.dirty = dirty;
       
  4098 };
       
  4099 
       
  4100 TiddlyWiki.prototype.isDirty = function()
       
  4101 {
       
  4102 	return this.dirty;
       
  4103 };
       
  4104 
       
  4105 TiddlyWiki.prototype.suspendNotifications = function()
       
  4106 {
       
  4107 	this.notificationLevel--;
       
  4108 };
       
  4109 
       
  4110 TiddlyWiki.prototype.resumeNotifications = function()
       
  4111 {
       
  4112 	this.notificationLevel++;
       
  4113 };
       
  4114 
       
  4115 // Invoke the notification handlers for a particular tiddler
       
  4116 TiddlyWiki.prototype.notify = function(title,doBlanket)
       
  4117 {
       
  4118 	if(!this.notificationLevel) {
       
  4119 		for(var t=0; t<this.namedNotifications.length; t++) {
       
  4120 			var n = this.namedNotifications[t];
       
  4121 			if((n.name == null && doBlanket) || (n.name == title))
       
  4122 				n.notify(title);
       
  4123 		}
       
  4124 	}
       
  4125 };
       
  4126 
       
  4127 // Invoke the notification handlers for all tiddlers
       
  4128 TiddlyWiki.prototype.notifyAll = function()
       
  4129 {
       
  4130 	if(!this.notificationLevel) {
       
  4131 		for(var t=0; t<this.namedNotifications.length; t++) {
       
  4132 			var n = this.namedNotifications[t];
       
  4133 			if(n.name)
       
  4134 				n.notify(n.name);
       
  4135 		}
       
  4136 	}
       
  4137 };
       
  4138 
       
  4139 // Add a notification handler to a tiddler
       
  4140 TiddlyWiki.prototype.addNotification = function(title,fn)
       
  4141 {
       
  4142 	for(var i=0; i<this.namedNotifications.length; i++) {
       
  4143 		if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
       
  4144 			return this;
       
  4145 	}
       
  4146 	this.namedNotifications.push({name: title, notify: fn});
       
  4147 	return this;
       
  4148 };
       
  4149 
       
  4150 TiddlyWiki.prototype.removeTiddler = function(title)
       
  4151 {
       
  4152 	var tiddler = this.fetchTiddler(title);
       
  4153 	if(tiddler) {
       
  4154 		this.deleteTiddler(title);
       
  4155 		this.notify(title,true);
       
  4156 		this.setDirty(true);
       
  4157 	}
       
  4158 };
       
  4159 
       
  4160 TiddlyWiki.prototype.tiddlerExists = function(title)
       
  4161 {
       
  4162 	var t = this.fetchTiddler(title);
       
  4163 	return t != undefined;
       
  4164 };
       
  4165 
       
  4166 TiddlyWiki.prototype.isShadowTiddler = function(title)
       
  4167 {
       
  4168 	return typeof config.shadowTiddlers[title] == "string";
       
  4169 };
       
  4170 
       
  4171 TiddlyWiki.prototype.getTiddler = function(title)
       
  4172 {
       
  4173 	var t = this.fetchTiddler(title);
       
  4174 	if(t != undefined)
       
  4175 		return t;
       
  4176 	else
       
  4177 		return null;
       
  4178 };
       
  4179 
       
  4180 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
       
  4181 {
       
  4182 	if(!title)
       
  4183 		return defaultText;
       
  4184 	var pos = title.indexOf(config.textPrimitives.sectionSeparator);
       
  4185 	var section = null;
       
  4186 	if(pos != -1) {
       
  4187 		section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
       
  4188 		title = title.substr(0,pos);
       
  4189 	}
       
  4190 	pos = title.indexOf(config.textPrimitives.sliceSeparator);
       
  4191 	if(pos != -1) {
       
  4192 		var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
       
  4193 		if(slice)
       
  4194 			return slice;
       
  4195 	}
       
  4196 	var tiddler = this.fetchTiddler(title);
       
  4197 	if(tiddler) {
       
  4198 		if(!section)
       
  4199 			return tiddler.text;
       
  4200 		var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)","mg");
       
  4201 		re.lastIndex = 0;
       
  4202 		var match =  re.exec(tiddler.text);
       
  4203 		if(match) {
       
  4204 			var t = tiddler.text.substr(match.index+match[1].length);
       
  4205 			var re2 = /^!/mg;
       
  4206 			re2.lastIndex = 0;
       
  4207 			match = re2.exec(t); //# search for the next heading
       
  4208 			if(match)
       
  4209 				t = t.substr(0,match.index-1);//# don't include final \n
       
  4210 			return t;
       
  4211 		}
       
  4212 		return defaultText;
       
  4213 	}
       
  4214 	if(this.isShadowTiddler(title))
       
  4215 		return config.shadowTiddlers[title];
       
  4216 	if(defaultText != undefined)
       
  4217 		return defaultText;
       
  4218 	return null;
       
  4219 };
       
  4220 
       
  4221 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
       
  4222 
       
  4223 // @internal
       
  4224 TiddlyWiki.prototype.calcAllSlices = function(title)
       
  4225 {
       
  4226 	var slices = {};
       
  4227 	var text = this.getTiddlerText(title,"");
       
  4228 	this.slicesRE.lastIndex = 0;
       
  4229 	do {
       
  4230 		var m = this.slicesRE.exec(text);
       
  4231 		if(m) {
       
  4232 			if(m[1])
       
  4233 				slices[m[1]] = m[2];
       
  4234 			else
       
  4235 				slices[m[3]] = m[4];
       
  4236 		}
       
  4237 	} while(m);
       
  4238 	return slices;
       
  4239 };
       
  4240 
       
  4241 // Returns the slice of text of the given name
       
  4242 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
       
  4243 {
       
  4244 	var slices = this.slices[title];
       
  4245 	if(!slices) {
       
  4246 		slices = this.calcAllSlices(title);
       
  4247 		this.slices[title] = slices;
       
  4248 	}
       
  4249 	return slices[sliceName];
       
  4250 };
       
  4251 
       
  4252 // Build an hashmap of the specified named slices of a tiddler
       
  4253 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
       
  4254 {
       
  4255 	var r = {};
       
  4256 	for(var t=0; t<sliceNames.length; t++) {
       
  4257 		var slice = this.getTiddlerSlice(title,sliceNames[t]);
       
  4258 		if(slice)
       
  4259 			r[sliceNames[t]] = slice;
       
  4260 	}
       
  4261 	return r;
       
  4262 };
       
  4263 
       
  4264 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
       
  4265 {
       
  4266 	var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
       
  4267 	var text = this.getTiddlerText(title,null);
       
  4268 	if(text == null)
       
  4269 		return defaultText;
       
  4270 	var textOut = [];
       
  4271 	var lastPos = 0;
       
  4272 	do {
       
  4273 		var match = bracketRegExp.exec(text);
       
  4274 		if(match) {
       
  4275 			textOut.push(text.substr(lastPos,match.index-lastPos));
       
  4276 			if(match[1]) {
       
  4277 				if(depth <= 0)
       
  4278 					textOut.push(match[1]);
       
  4279 				else
       
  4280 					textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
       
  4281 			}
       
  4282 			lastPos = match.index + match[0].length;
       
  4283 		} else {
       
  4284 			textOut.push(text.substr(lastPos));
       
  4285 		}
       
  4286 	} while(match);
       
  4287 	return textOut.join("");
       
  4288 };
       
  4289 
       
  4290 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
       
  4291 {
       
  4292 	var tiddler = this.fetchTiddler(title);
       
  4293 	if(tiddler) {
       
  4294 		var t = tiddler.tags.indexOf(tag);
       
  4295 		if(t != -1)
       
  4296 			tiddler.tags.splice(t,1);
       
  4297 		if(status)
       
  4298 			tiddler.tags.push(tag);
       
  4299 		tiddler.changed();
       
  4300 		this.incChangeCount(title);
       
  4301 		this.notify(title,true);
       
  4302 		this.setDirty(true);
       
  4303 	}
       
  4304 };
       
  4305 
       
  4306 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
       
  4307 {
       
  4308 	var tiddler = this.fetchTiddler(title);
       
  4309 	if(!tiddler)
       
  4310 		return;
       
  4311 	merge(tiddler.fields,fields);
       
  4312 	tiddler.changed();
       
  4313 	this.incChangeCount(title);
       
  4314 	this.notify(title,true);
       
  4315 	this.setDirty(true);
       
  4316 };
       
  4317 
       
  4318 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
       
  4319 {
       
  4320 	var tiddler = this.fetchTiddler(title);
       
  4321 	if(tiddler) {
       
  4322 		created = created ? created : tiddler.created; // Preserve created date
       
  4323 		this.deleteTiddler(title);
       
  4324 	} else {
       
  4325 		created = created ? created : modified;
       
  4326 		tiddler = new Tiddler();
       
  4327 	}
       
  4328 	tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
       
  4329 	this.addTiddler(tiddler);
       
  4330 	if(clearChangeCount)
       
  4331 		tiddler.clearChangeCount();
       
  4332 	else
       
  4333 		tiddler.incChangeCount();
       
  4334 	if(title != newTitle)
       
  4335 		this.notify(title,true);
       
  4336 	this.notify(newTitle,true);
       
  4337 	this.setDirty(true);
       
  4338 	return tiddler;
       
  4339 };
       
  4340 
       
  4341 // Reset the sync status of a freshly synced tiddler
       
  4342 TiddlyWiki.prototype.resetTiddler = function(title)
       
  4343 {
       
  4344 	var tiddler = this.fetchTiddler(title);
       
  4345 	if(tiddler) {
       
  4346 		tiddler.clearChangeCount();
       
  4347 		this.notify(title,true);
       
  4348 		this.setDirty(true);
       
  4349 	}
       
  4350 };
       
  4351 
       
  4352 TiddlyWiki.prototype.incChangeCount = function(title)
       
  4353 {
       
  4354 	var tiddler = this.fetchTiddler(title);
       
  4355 	if(tiddler)
       
  4356 		tiddler.incChangeCount();
       
  4357 };
       
  4358 
       
  4359 TiddlyWiki.prototype.createTiddler = function(title)
       
  4360 {
       
  4361 	var tiddler = this.fetchTiddler(title);
       
  4362 	if(!tiddler) {
       
  4363 		tiddler = new Tiddler(title);
       
  4364 		this.addTiddler(tiddler);
       
  4365 		this.setDirty(true);
       
  4366 	}
       
  4367 	return tiddler;
       
  4368 };
       
  4369 
       
  4370 // Load contents of a TiddlyWiki from an HTML DIV
       
  4371 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
       
  4372 {
       
  4373 	this.idPrefix = idPrefix;
       
  4374 	var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
       
  4375 	if(!storeElem)
       
  4376 		return;
       
  4377 	var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
       
  4378 	this.setDirty(false);
       
  4379 	if(!noUpdate) {
       
  4380 		for(var i = 0;i<tiddlers.length; i++)
       
  4381 			tiddlers[i].changed();
       
  4382 	}
       
  4383 };
       
  4384 
       
  4385 // Load contents of a TiddlyWiki from a string
       
  4386 // Returns null if there's an error
       
  4387 TiddlyWiki.prototype.importTiddlyWiki = function(text)
       
  4388 {
       
  4389 	var posDiv = locateStoreArea(text);
       
  4390 	if(!posDiv)
       
  4391 		return null;
       
  4392 	var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
       
  4393 	// Create the iframe
       
  4394 	var iframe = document.createElement("iframe");
       
  4395 	iframe.style.display = "none";
       
  4396 	document.body.appendChild(iframe);
       
  4397 	var doc = iframe.document;
       
  4398 	if(iframe.contentDocument)
       
  4399 		doc = iframe.contentDocument; // For NS6
       
  4400 	else if(iframe.contentWindow)
       
  4401 		doc = iframe.contentWindow.document; // For IE5.5 and IE6
       
  4402 	// Put the content in the iframe
       
  4403 	doc.open();
       
  4404 	doc.writeln(content);
       
  4405 	doc.close();
       
  4406 	// Load the content into a TiddlyWiki() object
       
  4407 	var storeArea = doc.getElementById("storeArea");
       
  4408 	this.loadFromDiv(storeArea,"store");
       
  4409 	// Get rid of the iframe
       
  4410 	iframe.parentNode.removeChild(iframe);
       
  4411 	return this;
       
  4412 };
       
  4413 
       
  4414 TiddlyWiki.prototype.updateTiddlers = function()
       
  4415 {
       
  4416 	this.tiddlersUpdated = true;
       
  4417 	this.forEachTiddler(function(title,tiddler) {
       
  4418 		tiddler.changed();
       
  4419 	});
       
  4420 };
       
  4421 
       
  4422 // Return all tiddlers formatted as an HTML string
       
  4423 TiddlyWiki.prototype.allTiddlersAsHtml = function()
       
  4424 {
       
  4425 	return store.getSaver().externalize(store);
       
  4426 };
       
  4427 
       
  4428 // Return an array of tiddlers matching a search regular expression
       
  4429 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
       
  4430 {
       
  4431 	var candidates = this.reverseLookup("tags",excludeTag,!!match);
       
  4432 	var results = [];
       
  4433 	for(var t=0; t<candidates.length; t++) {
       
  4434 		if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
       
  4435 			results.push(candidates[t]);
       
  4436 	}
       
  4437 	if(!sortField)
       
  4438 		sortField = "title";
       
  4439 	results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
       
  4440 	return results;
       
  4441 };
       
  4442 
       
  4443 // Returns a list of all tags in use
       
  4444 //   excludeTag - if present, excludes tags that are themselves tagged with excludeTag
       
  4445 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
       
  4446 TiddlyWiki.prototype.getTags = function(excludeTag)
       
  4447 {
       
  4448 	var results = [];
       
  4449 	this.forEachTiddler(function(title,tiddler) {
       
  4450 		for(var g=0; g<tiddler.tags.length; g++) {
       
  4451 			var tag = tiddler.tags[g];
       
  4452 			var n = true;
       
  4453 			for(var c=0; c<results.length; c++) {
       
  4454 				if(results[c][0] == tag) {
       
  4455 					n = false;
       
  4456 					results[c][1]++;
       
  4457 				}
       
  4458 			}
       
  4459 			if(n && excludeTag) {
       
  4460 				var t = store.fetchTiddler(tag);
       
  4461 				if(t && t.isTagged(excludeTag))
       
  4462 					n = false;
       
  4463 			}
       
  4464 			if(n)
       
  4465 				results.push([tag,1]);
       
  4466 		}
       
  4467 	});
       
  4468 	results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
       
  4469 	return results;
       
  4470 };
       
  4471 
       
  4472 // Return an array of the tiddlers that are tagged with a given tag
       
  4473 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
       
  4474 {
       
  4475 	return this.reverseLookup("tags",tag,true,sortField);
       
  4476 };
       
  4477 
       
  4478 // Return an array of the tiddlers that link to a given tiddler
       
  4479 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
       
  4480 {
       
  4481 	if(!this.tiddlersUpdated)
       
  4482 		this.updateTiddlers();
       
  4483 	return this.reverseLookup("links",title,true,sortField);
       
  4484 };
       
  4485 
       
  4486 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
       
  4487 // lookupMatch == true to match tiddlers, false to exclude tiddlers
       
  4488 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
       
  4489 {
       
  4490 	var results = [];
       
  4491 	this.forEachTiddler(function(title,tiddler) {
       
  4492 		var f = !lookupMatch;
       
  4493 		for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
       
  4494 			if(tiddler[lookupField][lookup] == lookupValue)
       
  4495 				f = lookupMatch;
       
  4496 		}
       
  4497 		if(f)
       
  4498 			results.push(tiddler);
       
  4499 	});
       
  4500 	if(!sortField)
       
  4501 		sortField = "title";
       
  4502 	results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
       
  4503 	return results;
       
  4504 };
       
  4505 
       
  4506 // Return the tiddlers as a sorted array
       
  4507 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
       
  4508 {
       
  4509 	var results = [];
       
  4510 	this.forEachTiddler(function(title,tiddler) {
       
  4511 		if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
       
  4512 			results.push(tiddler);
       
  4513 	});
       
  4514 	if(field)
       
  4515 		results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
       
  4516 	return results;
       
  4517 };
       
  4518 
       
  4519 // Return array of names of tiddlers that are referred to but not defined
       
  4520 TiddlyWiki.prototype.getMissingLinks = function(sortField)
       
  4521 {
       
  4522 	if(!this.tiddlersUpdated)
       
  4523 		this.updateTiddlers();
       
  4524 	var results = [];
       
  4525 	this.forEachTiddler(function (title,tiddler) {
       
  4526 		if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
       
  4527 			return;
       
  4528 		for(var n=0; n<tiddler.links.length;n++) {
       
  4529 			var link = tiddler.links[n];
       
  4530 			if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
       
  4531 				results.pushUnique(link);
       
  4532 		}
       
  4533 	});
       
  4534 	results.sort();
       
  4535 	return results;
       
  4536 };
       
  4537 
       
  4538 // Return an array of names of tiddlers that are defined but not referred to
       
  4539 TiddlyWiki.prototype.getOrphans = function()
       
  4540 {
       
  4541 	var results = [];
       
  4542 	this.forEachTiddler(function (title,tiddler) {
       
  4543 		if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
       
  4544 			results.push(title);
       
  4545 	});
       
  4546 	results.sort();
       
  4547 	return results;
       
  4548 };
       
  4549 
       
  4550 // Return an array of names of all the shadow tiddlers
       
  4551 TiddlyWiki.prototype.getShadowed = function()
       
  4552 {
       
  4553 	var results = [];
       
  4554 	for(var t in config.shadowTiddlers) {
       
  4555 		if(typeof config.shadowTiddlers[t] == "string")
       
  4556 			results.push(t);
       
  4557 	}
       
  4558 	results.sort();
       
  4559 	return results;
       
  4560 };
       
  4561 
       
  4562 // Return an array of tiddlers that have been touched since they were downloaded or created
       
  4563 TiddlyWiki.prototype.getTouched = function()
       
  4564 {
       
  4565 	var results = [];
       
  4566 	this.forEachTiddler(function(title,tiddler) {
       
  4567 		if(tiddler.isTouched())
       
  4568 			results.push(tiddler);
       
  4569 		});
       
  4570 	results.sort();
       
  4571 	return results;
       
  4572 };
       
  4573 
       
  4574 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
       
  4575 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
       
  4576 {
       
  4577 	var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
       
  4578 	return t instanceof Tiddler ? t : null;
       
  4579 };
       
  4580 
       
  4581 TiddlyWiki.prototype.getLoader = function()
       
  4582 {
       
  4583 	if(!this.loader)
       
  4584 		this.loader = new TW21Loader();
       
  4585 	return this.loader;
       
  4586 };
       
  4587 
       
  4588 TiddlyWiki.prototype.getSaver = function()
       
  4589 {
       
  4590 	if(!this.saver)
       
  4591 		this.saver = new TW21Saver();
       
  4592 	return this.saver;
       
  4593 };
       
  4594 
       
  4595 // Filter a list of tiddlers
       
  4596 TiddlyWiki.prototype.filterTiddlers = function(filter)
       
  4597 {
       
  4598 	var results = [];
       
  4599 	if(filter) {
       
  4600 		var tiddler;
       
  4601 		var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
       
  4602 		var match = re.exec(filter);
       
  4603 		while(match) {
       
  4604 			if(match[1] || match[4]) {
       
  4605 				var title = match[1] ? match[1] : match[4];
       
  4606 				tiddler = this.fetchTiddler(title);
       
  4607 				if(tiddler) {
       
  4608 					results.pushUnique(tiddler);
       
  4609 				} else if(store.isShadowTiddler(title)) {
       
  4610 					tiddler = new Tiddler();
       
  4611 					tiddler.set(title,store.getTiddlerText(title));
       
  4612 					results.pushUnique(tiddler);
       
  4613 				}
       
  4614 			} else if(match[2]) {
       
  4615 				switch(match[2]) {
       
  4616 					case "tag":
       
  4617 						var matched = this.getTaggedTiddlers(match[3]);
       
  4618 						for(var m = 0; m < matched.length; m++)
       
  4619 							results.pushUnique(matched[m]);
       
  4620 						break;
       
  4621 					case "sort":
       
  4622 						results = this.sortTiddlers(results,match[3]);
       
  4623 						break;
       
  4624 				}
       
  4625 			}
       
  4626 			match = re.exec(filter);
       
  4627 		}
       
  4628 	}
       
  4629 	return results;
       
  4630 };
       
  4631 
       
  4632 // Sort a list of tiddlers
       
  4633 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
       
  4634 {
       
  4635 	var asc = +1;
       
  4636 	switch(field.substr(0,1)) {
       
  4637 		case "-":
       
  4638 			asc = -1;
       
  4639 			// Note: this fall-through is intentional
       
  4640 		case "+":
       
  4641 			field = field.substr(1);
       
  4642 			break;
       
  4643 	}
       
  4644 	if(TiddlyWiki.standardFieldAccess[field])
       
  4645 		tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
       
  4646 	else
       
  4647 		tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
       
  4648 	return tiddlers;
       
  4649 };
       
  4650 // Returns true if path is a valid field name (path),
       
  4651 // i.e. a sequence of identifiers, separated by '.'
       
  4652 TiddlyWiki.isValidFieldName = function(name)
       
  4653 {
       
  4654 	var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
       
  4655 	return match && (match[0] == name);
       
  4656 };
       
  4657 
       
  4658 // Throws an exception when name is not a valid field name.
       
  4659 TiddlyWiki.checkFieldName = function(name)
       
  4660 {
       
  4661 	if(!TiddlyWiki.isValidFieldName(name))
       
  4662 		throw config.messages.invalidFieldName.format([name]);
       
  4663 };
       
  4664 
       
  4665 function StringFieldAccess(n,readOnly)
       
  4666 {
       
  4667 	this.set = readOnly ?
       
  4668 			function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
       
  4669 			function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
       
  4670 	this.get = function(t) {return t[n];};
       
  4671 }
       
  4672 
       
  4673 function DateFieldAccess(n)
       
  4674 {
       
  4675 	this.set = function(t,v) {
       
  4676 		var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
       
  4677 		if(d != t[n]) {
       
  4678 			t[n] = d; return true;
       
  4679 		}
       
  4680 	};
       
  4681 	this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
       
  4682 }
       
  4683 
       
  4684 function LinksFieldAccess(n)
       
  4685 {
       
  4686 	this.set = function(t,v) {
       
  4687 		var s = (typeof v == "string") ? v.readBracketedList() : v;
       
  4688 		if(s.toString() != t[n].toString()) {
       
  4689 			t[n] = s; return true;
       
  4690 		}
       
  4691 	};
       
  4692 	this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
       
  4693 }
       
  4694 
       
  4695 TiddlyWiki.standardFieldAccess = {
       
  4696 	// The set functions return true when setting the data has changed the value.
       
  4697 	"title":    new StringFieldAccess("title",true),
       
  4698 	// Handle the "tiddler" field name as the title
       
  4699 	"tiddler":  new StringFieldAccess("title",true),
       
  4700 	"text":     new StringFieldAccess("text"),
       
  4701 	"modifier": new StringFieldAccess("modifier"),
       
  4702 	"modified": new DateFieldAccess("modified"),
       
  4703 	"created":  new DateFieldAccess("created"),
       
  4704 	"tags":     new LinksFieldAccess("tags")
       
  4705 };
       
  4706 
       
  4707 TiddlyWiki.isStandardField = function(name)
       
  4708 {
       
  4709 	return TiddlyWiki.standardFieldAccess[name] != undefined;
       
  4710 };
       
  4711 
       
  4712 // Sets the value of the given field of the tiddler to the value.
       
  4713 // Setting an ExtendedField's value to null or undefined removes the field.
       
  4714 // Setting a namespace to undefined removes all fields of that namespace.
       
  4715 // The fieldName is case-insensitive.
       
  4716 // All values will be converted to a string value.
       
  4717 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
       
  4718 {
       
  4719 	TiddlyWiki.checkFieldName(fieldName);
       
  4720 	var t = this.resolveTiddler(tiddler);
       
  4721 	if(!t)
       
  4722 		return;
       
  4723 	fieldName = fieldName.toLowerCase();
       
  4724 	var isRemove = (value === undefined) || (value === null);
       
  4725 	var accessor = TiddlyWiki.standardFieldAccess[fieldName];
       
  4726 	if(accessor) {
       
  4727 		if(isRemove)
       
  4728 			// don't remove StandardFields
       
  4729 			return;
       
  4730 		var h = TiddlyWiki.standardFieldAccess[fieldName];
       
  4731 		if(!h.set(t,value))
       
  4732 			return;
       
  4733 	} else {
       
  4734 		var oldValue = t.fields[fieldName];
       
  4735 		if(isRemove) {
       
  4736 			if(oldValue !== undefined) {
       
  4737 				// deletes a single field
       
  4738 				delete t.fields[fieldName];
       
  4739 			} else {
       
  4740 				// no concrete value is defined for the fieldName
       
  4741 				// so we guess this is a namespace path.
       
  4742 				// delete all fields in a namespace
       
  4743 				var re = new RegExp('^'+fieldName+'\\.');
       
  4744 				var dirty = false;
       
  4745 				for(var n in t.fields) {
       
  4746 					if(n.match(re)) {
       
  4747 						delete t.fields[n];
       
  4748 						dirty = true;
       
  4749 					}
       
  4750 				}
       
  4751 				if(!dirty)
       
  4752 					return;
       
  4753 			}
       
  4754 		} else {
       
  4755 			// the "normal" set case. value is defined (not null/undefined)
       
  4756 			// For convenience provide a nicer conversion Date->String
       
  4757 			value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
       
  4758 			if(oldValue == value)
       
  4759 				return;
       
  4760 			t.fields[fieldName] = value;
       
  4761 		}
       
  4762 	}
       
  4763 	// When we are here the tiddler/store really was changed.
       
  4764 	this.notify(t.title,true);
       
  4765 	if(!fieldName.match(/^temp\./))
       
  4766 		this.setDirty(true);
       
  4767 };
       
  4768 
       
  4769 // Returns the value of the given field of the tiddler.
       
  4770 // The fieldName is case-insensitive.
       
  4771 // Will only return String values (or undefined).
       
  4772 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
       
  4773 {
       
  4774 	var t = this.resolveTiddler(tiddler);
       
  4775 	if(!t)
       
  4776 		return undefined;
       
  4777 	fieldName = fieldName.toLowerCase();
       
  4778 	var accessor = TiddlyWiki.standardFieldAccess[fieldName];
       
  4779 	if(accessor) {
       
  4780 		return accessor.get(t);
       
  4781 	}
       
  4782 	return t.fields[fieldName];
       
  4783 };
       
  4784 
       
  4785 // Calls the callback function for every field in the tiddler.
       
  4786 // When callback function returns a non-false value the iteration stops
       
  4787 // and that value is returned.
       
  4788 // The order of the fields is not defined.
       
  4789 // @param callback a function(tiddler,fieldName,value).
       
  4790 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
       
  4791 {
       
  4792 	var t = this.resolveTiddler(tiddler);
       
  4793 	if(!t)
       
  4794 		return undefined;
       
  4795 	var n,result;
       
  4796 	for(n in t.fields) {
       
  4797 		result = callback(t,n,t.fields[n]);
       
  4798 		if(result)
       
  4799 			return result;
       
  4800 		}
       
  4801 	if(onlyExtendedFields)
       
  4802 		return undefined;
       
  4803 	for(n in TiddlyWiki.standardFieldAccess) {
       
  4804 		if(n == "tiddler")
       
  4805 			// even though the "title" field can also be referenced through the name "tiddler"
       
  4806 			// we only visit this field once.
       
  4807 			continue;
       
  4808 		result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
       
  4809 		if(result)
       
  4810 			return result;
       
  4811 	}
       
  4812 	return undefined;
       
  4813 };
       
  4814 
       
  4815 //--
       
  4816 //-- Story functions
       
  4817 //--
       
  4818 
       
  4819 function Story(containerId,idPrefix)
       
  4820 {
       
  4821 	this.container = containerId;
       
  4822 	this.idPrefix = idPrefix;
       
  4823 	this.highlightRegExp = null;
       
  4824 	this.tiddlerId = function(title) {
       
  4825 		return this.idPrefix + title;
       
  4826 	};
       
  4827 	this.containerId = function() {
       
  4828 		return this.container;
       
  4829 	};
       
  4830 }
       
  4831 
       
  4832 Story.prototype.forEachTiddler = function(fn)
       
  4833 {
       
  4834 	var place = this.getContainer();
       
  4835 	if(!place)
       
  4836 		return;
       
  4837 	var e = place.firstChild;
       
  4838 	while(e) {
       
  4839 		var n = e.nextSibling;
       
  4840 		var title = e.getAttribute("tiddler");
       
  4841 		fn.call(this,title,e);
       
  4842 		e = n;
       
  4843 	}
       
  4844 };
       
  4845 
       
  4846 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
       
  4847 {
       
  4848 	for(var t = titles.length-1;t>=0;t--)
       
  4849 		this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
       
  4850 };
       
  4851 
       
  4852 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle)
       
  4853 {
       
  4854 	var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
       
  4855 	var tiddlerElem = this.getTiddler(title);
       
  4856 	if(tiddlerElem) {
       
  4857 		if(toggle)
       
  4858 			this.closeTiddler(title,true);
       
  4859 		else
       
  4860 			this.refreshTiddler(title,template,false,customFields);
       
  4861 	} else {
       
  4862 		var place = this.getContainer();
       
  4863 		var before = this.positionTiddler(srcElement);
       
  4864 		tiddlerElem = this.createTiddler(place,before,title,template,customFields);
       
  4865 	}
       
  4866 	if(srcElement && typeof srcElement !== "string") {
       
  4867 		if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
       
  4868 			anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
       
  4869 		else
       
  4870 			window.scrollTo(0,ensureVisible(tiddlerElem));
       
  4871 	}
       
  4872 };
       
  4873 
       
  4874 Story.prototype.positionTiddler = function(srcElement)
       
  4875 {
       
  4876 	var place = this.getContainer();
       
  4877 	var before = null;
       
  4878 	if(typeof srcElement == "string") {
       
  4879 		switch(srcElement) {
       
  4880 			case "top":
       
  4881 				before = place.firstChild;
       
  4882 				break;
       
  4883 			case "bottom":
       
  4884 				before = null;
       
  4885 				break;
       
  4886 		}
       
  4887 	} else {
       
  4888 		var after = this.findContainingTiddler(srcElement);
       
  4889 		if(after == null) {
       
  4890 			before = place.firstChild;
       
  4891 		} else if(after.nextSibling) {
       
  4892 			before = after.nextSibling;
       
  4893 			if(before.nodeType != 1)
       
  4894 				before = null;
       
  4895 		}
       
  4896 	}
       
  4897 	return before;
       
  4898 };
       
  4899 
       
  4900 Story.prototype.createTiddler = function(place,before,title,template,customFields)
       
  4901 {
       
  4902 	var tiddlerElem = createTiddlyElement(null,"div",this.tiddlerId(title),"tiddler");
       
  4903 	tiddlerElem.setAttribute("refresh","tiddler");
       
  4904 	if(customFields)
       
  4905 		tiddlerElem.setAttribute("tiddlyFields",customFields);
       
  4906 	place.insertBefore(tiddlerElem,before);
       
  4907 	var defaultText = null;
       
  4908 	if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
       
  4909 		defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
       
  4910 	this.refreshTiddler(title,template,false,customFields,defaultText);
       
  4911 	return tiddlerElem;
       
  4912 };
       
  4913 
       
  4914 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
       
  4915 {
       
  4916 	var tiddler = new Tiddler(title);
       
  4917 	tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
       
  4918 	var serverType = tiddler.getServerType();
       
  4919 	var host = tiddler.fields['server.host'];
       
  4920 	var workspace = tiddler.fields['server.workspace'];
       
  4921 	if(!serverType || !host)
       
  4922 		return null;
       
  4923 	var sm = new SyncMachine(serverType,{
       
  4924 			start: function() {
       
  4925 				return this.openHost(host,"openWorkspace");
       
  4926 			},
       
  4927 			openWorkspace: function() {
       
  4928 				return this.openWorkspace(workspace,"getTiddler");
       
  4929 			},
       
  4930 			getTiddler: function() {
       
  4931 				return this.getTiddler(title,"onGetTiddler");
       
  4932 			},
       
  4933 			onGetTiddler: function(context) {
       
  4934 				var tiddler = context.tiddler;
       
  4935 				if(tiddler && tiddler.text) {
       
  4936 					var downloaded = new Date();
       
  4937 					if(!tiddler.created)
       
  4938 						tiddler.created = downloaded;
       
  4939 					if(!tiddler.modified)
       
  4940 						tiddler.modified = tiddler.created;
       
  4941 					store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
       
  4942 					autoSaveChanges();
       
  4943 				}
       
  4944 				delete this;
       
  4945 				return true;
       
  4946 			},
       
  4947 			error: function(message) {
       
  4948 				displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
       
  4949 			}
       
  4950 		});
       
  4951 	sm.go();
       
  4952 	return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
       
  4953 };
       
  4954 
       
  4955 Story.prototype.chooseTemplateForTiddler = function(title,template)
       
  4956 {
       
  4957 	if(!template)
       
  4958 		template = DEFAULT_VIEW_TEMPLATE;
       
  4959 	if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
       
  4960 		template = config.tiddlerTemplates[template];
       
  4961 	return template;
       
  4962 };
       
  4963 
       
  4964 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
       
  4965 {
       
  4966 	return store.getRecursiveTiddlerText(template,null,10);
       
  4967 };
       
  4968 
       
  4969 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
       
  4970 {
       
  4971 	var tiddlerElem = this.getTiddler(title);
       
  4972 	if(tiddlerElem) {
       
  4973 		if(tiddlerElem.getAttribute("dirty") == "true" && !force)
       
  4974 			return tiddlerElem;
       
  4975 		template = this.chooseTemplateForTiddler(title,template);
       
  4976 		var currTemplate = tiddlerElem.getAttribute("template");
       
  4977 		if((template != currTemplate) || force) {
       
  4978 			var tiddler = store.getTiddler(title);
       
  4979 			if(!tiddler) {
       
  4980 				tiddler = new Tiddler();
       
  4981 				if(store.isShadowTiddler(title)) {
       
  4982 					tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
       
  4983 				} else {
       
  4984 					var text = template=="EditTemplate" ?
       
  4985 								config.views.editor.defaultText.format([title]) :
       
  4986 								config.views.wikified.defaultText.format([title]);
       
  4987 					text = defaultText ? defaultText : text;
       
  4988 					var fields = customFields ? customFields.decodeHashMap() : null;
       
  4989 					tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
       
  4990 				}
       
  4991 			}
       
  4992 			tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
       
  4993 			tiddlerElem.setAttribute("tiddler",title);
       
  4994 			tiddlerElem.setAttribute("template",template);
       
  4995 			tiddlerElem.onmouseover = this.onTiddlerMouseOver;
       
  4996 			tiddlerElem.onmouseout = this.onTiddlerMouseOut;
       
  4997 			tiddlerElem.ondblclick = this.onTiddlerDblClick;
       
  4998 			tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
       
  4999 			tiddlerElem.innerHTML = this.getTemplateForTiddler(title,template,tiddler);
       
  5000 			applyHtmlMacros(tiddlerElem,tiddler);
       
  5001 			if(store.getTaggedTiddlers(title).length > 0)
       
  5002 				addClass(tiddlerElem,"isTag");
       
  5003 			else
       
  5004 				removeClass(tiddlerElem,"isTag");
       
  5005 			if(store.tiddlerExists(title)) {
       
  5006 				removeClass(tiddlerElem,"shadow");
       
  5007 				removeClass(tiddlerElem,"missing");
       
  5008 			} else {
       
  5009 				addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow" : "missing");
       
  5010 			}
       
  5011 			if(customFields)
       
  5012 				this.addCustomFields(tiddlerElem,customFields);
       
  5013 			forceReflow();
       
  5014 		}
       
  5015 	}
       
  5016 	return tiddlerElem;
       
  5017 };
       
  5018 
       
  5019 Story.prototype.addCustomFields = function(place,customFields)
       
  5020 {
       
  5021 	var fields = customFields.decodeHashMap();
       
  5022 	var w = document.createElement("div");
       
  5023 	w.style.display = "none";
       
  5024 	place.appendChild(w);
       
  5025 	for(var t in fields) {
       
  5026 		var e = document.createElement("input");
       
  5027 		e.setAttribute("type","text");
       
  5028 		e.setAttribute("value",fields[t]);
       
  5029 		w.appendChild(e);
       
  5030 		e.setAttribute("edit",t);
       
  5031 	}
       
  5032 };
       
  5033 
       
  5034 Story.prototype.refreshAllTiddlers = function(force)
       
  5035 {
       
  5036 	var place = this.getContainer();
       
  5037 	var e = place.firstChild;
       
  5038 	if(!e)
       
  5039 		return;
       
  5040 	this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
       
  5041 	while((e = e.nextSibling) != null)
       
  5042 		this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
       
  5043 };
       
  5044 
       
  5045 Story.prototype.onTiddlerMouseOver = function(e)
       
  5046 {
       
  5047 	if(window.addClass instanceof Function)
       
  5048 		addClass(this,"selected");
       
  5049 };
       
  5050 
       
  5051 Story.prototype.onTiddlerMouseOut = function(e)
       
  5052 {
       
  5053 	if(window.removeClass instanceof Function)
       
  5054 		removeClass(this,"selected");
       
  5055 };
       
  5056 
       
  5057 Story.prototype.onTiddlerDblClick = function(ev)
       
  5058 {
       
  5059 	var e = ev ? ev : window.event;
       
  5060 	var theTarget = resolveTarget(e);
       
  5061 	if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
       
  5062 		if(document.selection && document.selection.empty)
       
  5063 			document.selection.empty();
       
  5064 		config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
       
  5065 		e.cancelBubble = true;
       
  5066 		if(e.stopPropagation) e.stopPropagation();
       
  5067 		return true;
       
  5068 	} else {
       
  5069 		return false;
       
  5070 	}
       
  5071 };
       
  5072 
       
  5073 Story.prototype.onTiddlerKeyPress = function(ev)
       
  5074 {
       
  5075 	var e = ev ? ev : window.event;
       
  5076 	clearMessage();
       
  5077 	var consume = false;
       
  5078 	var title = this.getAttribute("tiddler");
       
  5079 	var target = resolveTarget(e);
       
  5080 	switch(e.keyCode) {
       
  5081 		case 9: // Tab
       
  5082 			if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
       
  5083 				replaceSelection(target,String.fromCharCode(9));
       
  5084 				consume = true;
       
  5085 			}
       
  5086 			if(config.isOpera) {
       
  5087 				target.onblur = function() {
       
  5088 					this.focus();
       
  5089 					this.onblur = null;
       
  5090 				};
       
  5091 			}
       
  5092 			break;
       
  5093 		case 13: // Ctrl-Enter
       
  5094 		case 10: // Ctrl-Enter on IE PC
       
  5095 		case 77: // Ctrl-Enter is "M" on some platforms
       
  5096 			if(e.ctrlKey) {
       
  5097 				blurElement(this);
       
  5098 				config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
       
  5099 				consume = true;
       
  5100 			}
       
  5101 			break;
       
  5102 		case 27: // Escape
       
  5103 			blurElement(this);
       
  5104 			config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
       
  5105 			consume = true;
       
  5106 			break;
       
  5107 	}
       
  5108 	e.cancelBubble = consume;
       
  5109 	if(consume) {
       
  5110 		if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
       
  5111 		e.returnValue = true; // Cancel The Event in IE
       
  5112 		if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
       
  5113 	}
       
  5114 	return !consume;
       
  5115 };
       
  5116 
       
  5117 Story.prototype.getTiddlerField = function(title,field)
       
  5118 {
       
  5119 	var tiddlerElem = this.getTiddler(title);
       
  5120 	var e = null;
       
  5121 	if(tiddlerElem != null) {
       
  5122 		var children = tiddlerElem.getElementsByTagName("*");
       
  5123 		for(var t=0; t<children.length; t++) {
       
  5124 			var c = children[t];
       
  5125 			if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
       
  5126 				if(!e)
       
  5127 					e = c;
       
  5128 				if(c.getAttribute("edit") == field)
       
  5129 					e = c;
       
  5130 			}
       
  5131 		}
       
  5132 	}
       
  5133 	return e;
       
  5134 };
       
  5135 
       
  5136 Story.prototype.focusTiddler = function(title,field)
       
  5137 {
       
  5138 	var e = this.getTiddlerField(title,field);
       
  5139 	if(e) {
       
  5140 		e.focus();
       
  5141 		e.select();
       
  5142 	}
       
  5143 };
       
  5144 
       
  5145 Story.prototype.blurTiddler = function(title)
       
  5146 {
       
  5147 	var tiddlerElem = this.getTiddler(title);
       
  5148 	if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
       
  5149 		tiddlerElem.focus();
       
  5150 		tiddlerElem.blur();
       
  5151 	}
       
  5152 };
       
  5153 
       
  5154 Story.prototype.setTiddlerField = function(title,tag,mode,field)
       
  5155 {
       
  5156 	var c = story.getTiddlerField(title,field);
       
  5157 
       
  5158 	var tags = c.value.readBracketedList();
       
  5159 	tags.setItem(tag,mode);
       
  5160 	c.value = String.encodeTiddlyLinkList(tags);
       
  5161 };
       
  5162 
       
  5163 Story.prototype.setTiddlerTag = function(title,tag,mode)
       
  5164 {
       
  5165 	Story.prototype.setTiddlerField(title,tag,mode,"tags");
       
  5166 };
       
  5167 
       
  5168 Story.prototype.closeTiddler = function(title,animate,unused)
       
  5169 {
       
  5170 	var tiddlerElem = this.getTiddler(title);
       
  5171 	if(tiddlerElem != null) {
       
  5172 		clearMessage();
       
  5173 		this.scrubTiddler(tiddlerElem);
       
  5174 		if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
       
  5175 			anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
       
  5176 		else {
       
  5177 			removeNode(tiddlerElem);
       
  5178 			forceReflow();
       
  5179 		}
       
  5180 	}
       
  5181 };
       
  5182 
       
  5183 Story.prototype.scrubTiddler = function(tiddlerElem)
       
  5184 {
       
  5185 	tiddlerElem.id = null;
       
  5186 };
       
  5187 
       
  5188 Story.prototype.setDirty = function(title,dirty)
       
  5189 {
       
  5190 	var tiddlerElem = this.getTiddler(title);
       
  5191 	if(tiddlerElem != null)
       
  5192 		tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
       
  5193 };
       
  5194 
       
  5195 Story.prototype.isDirty = function(title)
       
  5196 {
       
  5197 	var tiddlerElem = this.getTiddler(title);
       
  5198 	if(tiddlerElem != null)
       
  5199 		return tiddlerElem.getAttribute("dirty") == "true";
       
  5200 	return null;
       
  5201 };
       
  5202 
       
  5203 Story.prototype.areAnyDirty = function()
       
  5204 {
       
  5205 	var r = false;
       
  5206 	this.forEachTiddler(function(title,element) {
       
  5207 		if(this.isDirty(title))
       
  5208 			r = true;
       
  5209 	});
       
  5210 	return r;
       
  5211 };
       
  5212 
       
  5213 Story.prototype.closeAllTiddlers = function(exclude)
       
  5214 {
       
  5215 	clearMessage();
       
  5216 	this.forEachTiddler(function(title,element) {
       
  5217 		if((title != exclude) && element.getAttribute("dirty") != "true")
       
  5218 			this.closeTiddler(title);
       
  5219 	});
       
  5220 	window.scrollTo(0,ensureVisible(this.container));
       
  5221 };
       
  5222 
       
  5223 Story.prototype.isEmpty = function()
       
  5224 {
       
  5225 	var place = this.getContainer();
       
  5226 	return place && place.firstChild == null;
       
  5227 };
       
  5228 
       
  5229 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
       
  5230 {
       
  5231 	this.closeAllTiddlers();
       
  5232 	highlightHack = new RegExp(useRegExp ?	 text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
       
  5233 	var matches = store.search(highlightHack,"title","excludeSearch");
       
  5234 	this.displayTiddlers(null,matches);
       
  5235 	highlightHack = null;
       
  5236 	var q = useRegExp ? "/" : "'";
       
  5237 	if(matches.length > 0)
       
  5238 		displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
       
  5239 	else
       
  5240 		displayMessage(config.macros.search.failureMsg.format([q + text + q]));
       
  5241 };
       
  5242 
       
  5243 Story.prototype.findContainingTiddler = function(e)
       
  5244 {
       
  5245 	while(e && !hasClass(e,"tiddler"))
       
  5246 		e = e.parentNode;
       
  5247 	return e;
       
  5248 };
       
  5249 
       
  5250 Story.prototype.gatherSaveFields = function(e,fields)
       
  5251 {
       
  5252 	if(e && e.getAttribute) {
       
  5253 		var f = e.getAttribute("edit");
       
  5254 		if(f)
       
  5255 			fields[f] = e.value.replace(/\r/mg,"");
       
  5256 		if(e.hasChildNodes()) {
       
  5257 			var c = e.childNodes;
       
  5258 			for(var t=0; t<c.length; t++)
       
  5259 				this.gatherSaveFields(c[t],fields);
       
  5260 		}
       
  5261 	}
       
  5262 };
       
  5263 
       
  5264 Story.prototype.hasChanges = function(title)
       
  5265 {
       
  5266 	var e = this.getTiddler(title);
       
  5267 	if(e != null) {
       
  5268 		var fields = {};
       
  5269 		this.gatherSaveFields(e,fields);
       
  5270 		var tiddler = store.fetchTiddler(title);
       
  5271 		if(!tiddler)
       
  5272 			return false;
       
  5273 		for(var n in fields) {
       
  5274 			if(store.getValue(title,n) != fields[n])
       
  5275 				return true;
       
  5276 		}
       
  5277 	}
       
  5278 	return false;
       
  5279 };
       
  5280 
       
  5281 Story.prototype.saveTiddler = function(title,minorUpdate)
       
  5282 {
       
  5283 	var tiddlerElem = this.getTiddler(title);
       
  5284 	if(tiddlerElem != null) {
       
  5285 		var fields = {};
       
  5286 		this.gatherSaveFields(tiddlerElem,fields);
       
  5287 		var newTitle = fields.title ? fields.title : title;
       
  5288 		if(!store.tiddlerExists(newTitle))
       
  5289 			newTitle = newTitle.trim();
       
  5290 		if(store.tiddlerExists(newTitle) && newTitle != title) {
       
  5291 			if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
       
  5292 				return null;
       
  5293 		}
       
  5294 		if(newTitle != title)
       
  5295 			this.closeTiddler(newTitle,false);
       
  5296 		tiddlerElem.id = this.tiddlerId(newTitle);
       
  5297 		tiddlerElem.setAttribute("tiddler",newTitle);
       
  5298 		tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
       
  5299 		tiddlerElem.setAttribute("dirty","false");
       
  5300 		if(config.options.chkForceMinorUpdate)
       
  5301 			minorUpdate = !minorUpdate;
       
  5302 		if(!store.tiddlerExists(newTitle))
       
  5303 			minorUpdate = false;
       
  5304 		var newDate = new Date();
       
  5305 		var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : {});
       
  5306 		for(var n in fields) {
       
  5307 			if(!TiddlyWiki.isStandardField(n))
       
  5308 				extendedFields[n] = fields[n];
       
  5309 		}
       
  5310 		var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
       
  5311 		autoSaveChanges(null,[tiddler]);
       
  5312 		return newTitle;
       
  5313 	}
       
  5314 	return null;
       
  5315 };
       
  5316 
       
  5317 Story.prototype.permaView = function()
       
  5318 {
       
  5319 	var links = [];
       
  5320 	this.forEachTiddler(function(title,element) {
       
  5321 		links.push(String.encodeTiddlyLink(title));
       
  5322 	});
       
  5323 	var t = encodeURIComponent(links.join(" "));
       
  5324 	if(t == "")
       
  5325 		t = "#";
       
  5326 	if(window.location.hash != t)
       
  5327 		window.location.hash = t;
       
  5328 };
       
  5329 
       
  5330 Story.prototype.switchTheme = function(theme)
       
  5331 {
       
  5332 	if(safeMode)
       
  5333 		return;
       
  5334 
       
  5335 	isAvailable = function(title) {
       
  5336 		var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
       
  5337 		if(s!=-1)
       
  5338 			title = title.substr(0,s);
       
  5339 		return store.tiddlerExists(title) || store.isShadowTiddler(title);
       
  5340 	};
       
  5341 
       
  5342 	getSlice = function(theme,slice) {
       
  5343 		var r;
       
  5344 		if(readOnly)
       
  5345 			r = store.getTiddlerSlice(theme,slice+"ReadOnly") || store.getTiddlerSlice(theme,"Web"+slice);
       
  5346 		r = r || store.getTiddlerSlice(theme,slice);
       
  5347 		if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
       
  5348 			r = theme + r;
       
  5349 		return isAvailable(r) ? r : slice;
       
  5350 	};
       
  5351 
       
  5352 	replaceNotification = function(i,name,theme,slice) {
       
  5353 		var newName = getSlice(theme,slice);
       
  5354 		if(name!=newName && store.namedNotifications[i].name==name) {
       
  5355 			store.namedNotifications[i].name = newName;
       
  5356 			return newName;
       
  5357 		}
       
  5358 		return name;
       
  5359 	};
       
  5360 
       
  5361 	var pt = config.refresherData.pageTemplate;
       
  5362 	var vi = DEFAULT_VIEW_TEMPLATE;
       
  5363 	var vt = config.tiddlerTemplates[vi];
       
  5364 	var ei = DEFAULT_EDIT_TEMPLATE;
       
  5365 	var et = config.tiddlerTemplates[ei];
       
  5366 
       
  5367 	for(var i=0; i<config.notifyTiddlers.length; i++) {
       
  5368 		var name = config.notifyTiddlers[i].name;
       
  5369 		switch(name) {
       
  5370 		case "PageTemplate":
       
  5371 			config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
       
  5372 			break;
       
  5373 		case "StyleSheet":
       
  5374 			removeStyleSheet(config.refresherData.styleSheet);
       
  5375 			config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
       
  5376 			break;
       
  5377 		case "ColorPalette":
       
  5378 			config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
       
  5379 			break;
       
  5380 		default:
       
  5381 			break;
       
  5382 		}
       
  5383 	}
       
  5384 	config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate");
       
  5385 	config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate");
       
  5386 	if(!startingUp) {
       
  5387 		if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
       
  5388 			refreshAll();
       
  5389 			story.refreshAllTiddlers(true);
       
  5390 		} else {
       
  5391 			setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet,"",10),config.refreshers.styleSheet);
       
  5392 		}
       
  5393 		config.options.txtTheme = theme;
       
  5394 		saveOptionCookie("txtTheme");
       
  5395 	}
       
  5396 };
       
  5397 
       
  5398 Story.prototype.getTiddler = function(title)
       
  5399 {
       
  5400 	return document.getElementById(this.tiddlerId(title));
       
  5401 };
       
  5402 
       
  5403 Story.prototype.getContainer = function()
       
  5404 {
       
  5405 	return document.getElementById(this.containerId());
       
  5406 };
       
  5407 
       
  5408 //--
       
  5409 //-- Backstage
       
  5410 //--
       
  5411 
       
  5412 var backstage = {
       
  5413 	area: null,
       
  5414 	toolbar: null,
       
  5415 	button: null,
       
  5416 	showButton: null,
       
  5417 	hideButton: null,
       
  5418 	cloak: null,
       
  5419 	panel: null,
       
  5420 	panelBody: null,
       
  5421 	panelFooter: null,
       
  5422 	currTabName: null,
       
  5423 	currTabElem: null,
       
  5424 	content: null,
       
  5425 
       
  5426 	init: function() {
       
  5427 		var cmb = config.messages.backstage;
       
  5428 		this.area = document.getElementById("backstageArea");
       
  5429 		this.toolbar = document.getElementById("backstageToolbar");
       
  5430 		this.button = document.getElementById("backstageButton");
       
  5431 		this.button.style.display = "block";
       
  5432 		var t = cmb.open.text + " " + glyph("bentArrowLeft");
       
  5433 		this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
       
  5434 						function (e) {backstage.show(); return false;},null,"backstageShow");
       
  5435 		t = glyph("bentArrowRight") + " " + cmb.close.text;
       
  5436 		this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
       
  5437 						function (e) {backstage.hide(); return false;},null,"backstageHide");
       
  5438 		this.cloak = document.getElementById("backstageCloak");
       
  5439 		this.panel = document.getElementById("backstagePanel");
       
  5440 		this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
       
  5441 		this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
       
  5442 		this.cloak.onmousedown = function(e) {
       
  5443 			backstage.switchTab(null);
       
  5444 		};
       
  5445 		createTiddlyText(this.toolbar,cmb.prompt);
       
  5446 		for(t=0; t<config.backstageTasks.length; t++) {
       
  5447 			var taskName = config.backstageTasks[t];
       
  5448 			var task = config.tasks[taskName];
       
  5449 			var handler = task.action ? this.onClickCommand : this.onClickTab;
       
  5450 			var text = task.text + (task.action ? "" : glyph("downTriangle"));
       
  5451 			var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
       
  5452 			btn.setAttribute("task",taskName);
       
  5453 			addClass(btn,task.action ? "backstageAction" : "backstageTask");
       
  5454 			}
       
  5455 		this.content = document.getElementById("contentWrapper");
       
  5456 		if(config.options.chkBackstage)
       
  5457 			this.show();
       
  5458 		else
       
  5459 			this.hide();
       
  5460 	},
       
  5461 
       
  5462 	isVisible: function() {
       
  5463 		return this.area ? this.area.style.display == "block" : false;
       
  5464 	},
       
  5465 
       
  5466 	show: function() {
       
  5467 		this.area.style.display = "block";
       
  5468 		if(anim && config.options.chkAnimate) {
       
  5469 			backstage.toolbar.style.left = findWindowWidth() + "px";
       
  5470 			var p = [
       
  5471 				{style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
       
  5472 			];
       
  5473 			anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
       
  5474 		} else {
       
  5475 			backstage.area.style.left = "0px";
       
  5476 		}
       
  5477 		this.showButton.style.display = "none";
       
  5478 		this.hideButton.style.display = "block";
       
  5479 		config.options.chkBackstage = true;
       
  5480 		saveOptionCookie("chkBackstage");
       
  5481 		addClass(this.content,"backstageVisible");
       
  5482 	},
       
  5483 
       
  5484 	hide: function() {
       
  5485 		if(this.currTabElem) {
       
  5486 			this.switchTab(null);
       
  5487 		} else {
       
  5488 			backstage.toolbar.style.left = "0px";
       
  5489 			if(anim && config.options.chkAnimate) {
       
  5490 				var p = [
       
  5491 					{style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
       
  5492 				];
       
  5493 				var c = function(element,properties) {backstage.area.style.display = "none";};
       
  5494 				anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
       
  5495 			} else {
       
  5496 				this.area.style.display = "none";
       
  5497 			}
       
  5498 			this.showButton.style.display = "block";
       
  5499 			this.hideButton.style.display = "none";
       
  5500 			config.options.chkBackstage = false;
       
  5501 			saveOptionCookie("chkBackstage");
       
  5502 			removeClass(this.content,"backstageVisible");
       
  5503 		}
       
  5504 	},
       
  5505 
       
  5506 	onClickCommand: function(e) {
       
  5507 		var task = config.tasks[this.getAttribute("task")];
       
  5508 		displayMessage(task);
       
  5509 		if(task.action) {
       
  5510 			backstage.switchTab(null);
       
  5511 			task.action();
       
  5512 		}
       
  5513 		return false;
       
  5514 	},
       
  5515 
       
  5516 	onClickTab: function(e) {
       
  5517 		backstage.switchTab(this.getAttribute("task"));
       
  5518 		return false;
       
  5519 	},
       
  5520 
       
  5521 	// Switch to a given tab, or none if null is passed
       
  5522 	switchTab: function(tabName) {
       
  5523 		var tabElem = null;
       
  5524 		var e = this.toolbar.firstChild;
       
  5525 		while(e)
       
  5526 			{
       
  5527 			if(e.getAttribute && e.getAttribute("task") == tabName)
       
  5528 				tabElem = e;
       
  5529 			e = e.nextSibling;
       
  5530 			}
       
  5531 		if(tabName == backstage.currTabName)
       
  5532 			return;
       
  5533 		if(backstage.currTabElem) {
       
  5534 			removeClass(this.currTabElem,"backstageSelTab");
       
  5535 		}
       
  5536 		if(tabElem && tabName) {
       
  5537 			backstage.preparePanel();
       
  5538 			addClass(tabElem,"backstageSelTab");
       
  5539 			var task = config.tasks[tabName];
       
  5540 			wikify(task.content,backstage.panelBody,null,null);
       
  5541 			backstage.showPanel();
       
  5542 		} else if(backstage.currTabElem) {
       
  5543 			backstage.hidePanel();
       
  5544 		}
       
  5545 		backstage.currTabName = tabName;
       
  5546 		backstage.currTabElem = tabElem;
       
  5547 	},
       
  5548 
       
  5549 	isPanelVisible: function() {
       
  5550 		return backstage.panel ? backstage.panel.style.display == "block" : false;
       
  5551 	},
       
  5552 
       
  5553 	preparePanel: function() {
       
  5554 		backstage.cloak.style.height = findWindowHeight() + "px";
       
  5555 		backstage.cloak.style.display = "block";
       
  5556 		removeChildren(backstage.panelBody);
       
  5557 		return backstage.panelBody;
       
  5558 	},
       
  5559 
       
  5560 	showPanel: function() {
       
  5561 		backstage.panel.style.display = "block";
       
  5562 		if(anim && config.options.chkAnimate) {
       
  5563 			backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
       
  5564 			var p = [
       
  5565 				{style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
       
  5566 			];
       
  5567 			anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
       
  5568 		} else {
       
  5569 			backstage.panel.style.top = "0px";
       
  5570 		}
       
  5571 		return backstage.panelBody;
       
  5572 	},
       
  5573 
       
  5574 	hidePanel: function() {
       
  5575 		backstage.currTabName = null;
       
  5576 		backstage.currTabElem = null;
       
  5577 		if(anim && config.options.chkAnimate) {
       
  5578 			var p = [
       
  5579 				{style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
       
  5580 				{style: "display", atEnd: "none"}
       
  5581 			];
       
  5582 			var c = function(element,properties) {backstage.cloak.style.display = "none";};
       
  5583 			anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
       
  5584 		 } else {
       
  5585 			backstage.panel.style.display = "none";
       
  5586 			backstage.cloak.style.display = "none";
       
  5587 		}
       
  5588 	}
       
  5589 };
       
  5590 
       
  5591 config.macros.backstage = {};
       
  5592 
       
  5593 config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  5594 {
       
  5595 	var backstageTask = config.tasks[params[0]];
       
  5596 	if(backstageTask)
       
  5597 		createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
       
  5598 };
       
  5599 
       
  5600 //--
       
  5601 //-- ImportTiddlers macro
       
  5602 //--
       
  5603 
       
  5604 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  5605 {
       
  5606 	if(readOnly) {
       
  5607 		createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
       
  5608 		return;
       
  5609 	}
       
  5610 	var w = new Wizard();
       
  5611 	w.createWizard(place,this.wizardTitle);
       
  5612 	this.restart(w);
       
  5613 };
       
  5614 
       
  5615 config.macros.importTiddlers.onCancel = function(e)
       
  5616 {
       
  5617 	var wizard = new Wizard(this);
       
  5618 	var place = wizard.clear();
       
  5619 	config.macros.importTiddlers.restart(wizard);
       
  5620 	return false;
       
  5621 };
       
  5622 
       
  5623 config.macros.importTiddlers.onClose = function(e)
       
  5624 {
       
  5625 	backstage.hidePanel();
       
  5626 	return false;
       
  5627 };
       
  5628 
       
  5629 config.macros.importTiddlers.restart = function(wizard)
       
  5630 {
       
  5631 	wizard.addStep(this.step1Title,this.step1Html);
       
  5632 	var s = wizard.getElement("selTypes");
       
  5633 	for(var t in config.adaptors) {
       
  5634 		var e = createTiddlyElement(s,"option",null,null,config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
       
  5635 		e.value = t;
       
  5636 	}
       
  5637 	if(config.defaultAdaptor)
       
  5638 		s.value = config.defaultAdaptor;
       
  5639 	s = wizard.getElement("selFeeds");
       
  5640 	var feeds = this.getFeeds();
       
  5641 	for(t in feeds) {
       
  5642 		e = createTiddlyElement(s,"option",null,null,t);
       
  5643 		e.value = t;
       
  5644 	}
       
  5645 	wizard.setValue("feeds",feeds);
       
  5646 	s.onchange = config.macros.importTiddlers.onFeedChange;
       
  5647 	var fileInput = wizard.getElement("txtBrowse");
       
  5648 	fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
       
  5649 	fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
       
  5650 	wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
       
  5651 };
       
  5652 
       
  5653 config.macros.importTiddlers.getFeeds = function()
       
  5654 {
       
  5655 	var feeds = {};
       
  5656 	var tagged = store.getTaggedTiddlers("systemServer","title");
       
  5657 	for(var t=0; t<tagged.length; t++) {
       
  5658 		var title = tagged[t].title;
       
  5659 		var serverType = store.getTiddlerSlice(title,"Type");
       
  5660 		if(!serverType)
       
  5661 			serverType = "file";
       
  5662 		feeds[title] = {title: title,
       
  5663 						url: store.getTiddlerSlice(title,"URL"),
       
  5664 						workspace: store.getTiddlerSlice(title,"Workspace"),
       
  5665 						workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
       
  5666 						tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
       
  5667 						serverType: serverType,
       
  5668 						description: store.getTiddlerSlice(title,"Description")};
       
  5669 	}
       
  5670 	return feeds;
       
  5671 };
       
  5672 
       
  5673 config.macros.importTiddlers.onFeedChange = function(e)
       
  5674 {
       
  5675 	var wizard = new Wizard(this);
       
  5676 	var selTypes = wizard.getElement("selTypes");
       
  5677 	var fileInput = wizard.getElement("txtPath");
       
  5678 	var feeds = wizard.getValue("feeds");
       
  5679 	var f = feeds[this.value];
       
  5680 	if(f) {
       
  5681 		selTypes.value = f.serverType;
       
  5682 		fileInput.value = f.url;
       
  5683 		wizard.setValue("feedName",f.serverType);
       
  5684 		wizard.setValue("feedHost",f.url);
       
  5685 		wizard.setValue("feedWorkspace",f.workspace);
       
  5686 		wizard.setValue("feedWorkspaceList",f.workspaceList);
       
  5687 		wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
       
  5688 	}
       
  5689 	return false;
       
  5690 };
       
  5691 
       
  5692 config.macros.importTiddlers.onBrowseChange = function(e)
       
  5693 {
       
  5694 	var wizard = new Wizard(this);
       
  5695 	var fileInput = wizard.getElement("txtPath");
       
  5696 	fileInput.value = "file://" + this.value;
       
  5697 	var serverType = wizard.getElement("selTypes");
       
  5698 	serverType.value = "file";
       
  5699 	return false;
       
  5700 };
       
  5701 
       
  5702 config.macros.importTiddlers.onOpen = function(e)
       
  5703 {
       
  5704 	var wizard = new Wizard(this);
       
  5705 	var fileInput = wizard.getElement("txtPath");
       
  5706 	var url = fileInput.value;
       
  5707 	var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
       
  5708 	var adaptor = new config.adaptors[serverType];
       
  5709 	wizard.setValue("adaptor",adaptor);
       
  5710 	wizard.setValue("serverType",serverType);
       
  5711 	wizard.setValue("host",url);
       
  5712 	var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
       
  5713 	if(ret !== true)
       
  5714 		displayMessage(ret);
       
  5715 	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
       
  5716 	return false;
       
  5717 };
       
  5718 
       
  5719 config.macros.importTiddlers.onOpenHost = function(context,wizard)
       
  5720 {
       
  5721 	var adaptor = wizard.getValue("adaptor");
       
  5722 	if(context.status !== true)
       
  5723 		displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
       
  5724 	var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
       
  5725 	if(ret !== true)
       
  5726 		displayMessage(ret);
       
  5727 	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
       
  5728 };
       
  5729 
       
  5730 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
       
  5731 {
       
  5732 	if(context.status !== true)
       
  5733 		displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
       
  5734 	wizard.setValue("context",context);
       
  5735 	var workspace = wizard.getValue("feedWorkspace");
       
  5736 	if(!workspace && context.workspaces.length==1)
       
  5737 		workspace = context.workspaces[0].title;
       
  5738 	if(workspace) {
       
  5739 		var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
       
  5740 		if(ret !== true)
       
  5741 			displayMessage(ret);
       
  5742 		wizard.setValue("workspace",workspace);
       
  5743 		wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
       
  5744 		return;
       
  5745 	}
       
  5746 	wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
       
  5747 	var s = wizard.getElement("selWorkspace");
       
  5748 	s.onchange = config.macros.importTiddlers.onWorkspaceChange;
       
  5749 	for(var t=0; t<context.workspaces.length; t++) {
       
  5750 		var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
       
  5751 		e.value = context.workspaces[t].title;
       
  5752 	}
       
  5753 	var workspaceList = wizard.getValue("feedWorkspaceList");
       
  5754 	if(workspaceList) {
       
  5755 		var list = workspaceList.parseParams("workspace",null,false,true);
       
  5756 		for(var n=1; n<list.length; n++) {
       
  5757 			if(context.workspaces.findByField("title",list[n].value) == null) {
       
  5758 				e = createTiddlyElement(s,"option",null,null,list[n].value);
       
  5759 				e.value = list[n].value;
       
  5760 			}
       
  5761 		}
       
  5762 	}
       
  5763 	if(workspace) {
       
  5764 		t = wizard.getElement("txtWorkspace");
       
  5765 		t.value = workspace;
       
  5766 	}
       
  5767 	wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
       
  5768 };
       
  5769 
       
  5770 config.macros.importTiddlers.onWorkspaceChange = function(e)
       
  5771 {
       
  5772 	var wizard = new Wizard(this);
       
  5773 	var t = wizard.getElement("txtWorkspace");
       
  5774 	t.value  = this.value;
       
  5775 	this.selectedIndex = 0;
       
  5776 	return false;
       
  5777 };
       
  5778 
       
  5779 config.macros.importTiddlers.onChooseWorkspace = function(e)
       
  5780 {
       
  5781 	var wizard = new Wizard(this);
       
  5782 	var adaptor = wizard.getValue("adaptor");
       
  5783 	var workspace = wizard.getElement("txtWorkspace").value;
       
  5784 	wizard.setValue("workspace",workspace);
       
  5785 	var context = wizard.getValue("context");
       
  5786 	var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
       
  5787 	if(ret !== true)
       
  5788 		displayMessage(ret);
       
  5789 	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
       
  5790 	return false;
       
  5791 };
       
  5792 
       
  5793 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
       
  5794 {
       
  5795 	if(context.status !== true)
       
  5796 		displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
       
  5797 	var adaptor = wizard.getValue("adaptor");
       
  5798 	var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
       
  5799 	if(ret !== true)
       
  5800 		displayMessage(ret);
       
  5801 	wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
       
  5802 };
       
  5803 
       
  5804 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
       
  5805 {
       
  5806 	if(context.status !== true) {
       
  5807 		wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.errorGettingTiddlerList);
       
  5808 		return;
       
  5809 	}
       
  5810 	// Extract data for the listview
       
  5811 	var listedTiddlers = [];
       
  5812 	if(context.tiddlers) {
       
  5813 		for(var n=0; n<context.tiddlers.length; n++) {
       
  5814 			var tiddler = context.tiddlers[n];
       
  5815 			listedTiddlers.push({
       
  5816 				title: tiddler.title,
       
  5817 				modified: tiddler.modified,
       
  5818 				modifier: tiddler.modifier,
       
  5819 				text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
       
  5820 				tags: tiddler.tags,
       
  5821 				size: tiddler.text ? tiddler.text.length : 0,
       
  5822 				tiddler: tiddler
       
  5823 			});
       
  5824 		}
       
  5825 	}
       
  5826 	listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
       
  5827 	// Display the listview
       
  5828 	wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
       
  5829 	var markList = wizard.getElement("markList");
       
  5830 	var listWrapper = document.createElement("div");
       
  5831 	markList.parentNode.insertBefore(listWrapper,markList);
       
  5832 	var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
       
  5833 	wizard.setValue("listView",listView);
       
  5834 	var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
       
  5835 	txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
       
  5836 	wizard.setButtons([
       
  5837 			{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
       
  5838 			{caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick:  config.macros.importTiddlers.doImport}
       
  5839 		]);
       
  5840 };
       
  5841 
       
  5842 config.macros.importTiddlers.generateSystemServerName = function(wizard)
       
  5843 {
       
  5844 	var serverType = wizard.getValue("serverType");
       
  5845 	var host = wizard.getValue("host");
       
  5846 	var workspace = wizard.getValue("workspace");
       
  5847 	var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
       
  5848 	return pattern.format([serverType,host,workspace]);
       
  5849 };
       
  5850 
       
  5851 config.macros.importTiddlers.saveServerTiddler = function(wizard)
       
  5852 {
       
  5853 	var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
       
  5854 	if(store.tiddlerExists(txtSaveTiddler)) {
       
  5855 		if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
       
  5856 			return;
       
  5857 		store.suspendNotifications();
       
  5858 		store.removeTiddler(txtSaveTiddler);
       
  5859 		store.resumeNotifications();
       
  5860 	}
       
  5861 	var serverType = wizard.getValue("serverType");
       
  5862 	var host = wizard.getValue("host");
       
  5863 	var workspace = wizard.getValue("workspace");
       
  5864 	var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
       
  5865 	store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
       
  5866 };
       
  5867 
       
  5868 config.macros.importTiddlers.doImport = function(e)
       
  5869 {
       
  5870 	var wizard = new Wizard(this);
       
  5871 	if(wizard.getElement("chkSave").checked)
       
  5872 		config.macros.importTiddlers.saveServerTiddler(wizard);
       
  5873 	var chkSync = wizard.getElement("chkSync").checked;
       
  5874 	wizard.setValue("sync",chkSync);
       
  5875 	var listView = wizard.getValue("listView");
       
  5876 	var rowNames = ListView.getSelectedRows(listView);
       
  5877 	var adaptor = wizard.getValue("adaptor");
       
  5878 	var overwrite = new Array();
       
  5879 	var t;
       
  5880 	for(t=0; t<rowNames.length; t++) {
       
  5881 		if(store.tiddlerExists(rowNames[t]))
       
  5882 			overwrite.push(rowNames[t]);
       
  5883 	}
       
  5884 	if(overwrite.length > 0) {
       
  5885 		if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
       
  5886 			return false;
       
  5887 	}
       
  5888 	wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
       
  5889 	for(t=0; t<rowNames.length; t++) {
       
  5890 		var link = document.createElement("div");
       
  5891 		createTiddlyLink(link,rowNames[t],true);
       
  5892 		var place = wizard.getElement("markReport");
       
  5893 		place.parentNode.insertBefore(link,place);
       
  5894 	}
       
  5895 	wizard.setValue("remainingImports",rowNames.length);
       
  5896 	wizard.setButtons([
       
  5897 			{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
       
  5898 		],config.macros.importTiddlers.statusDoingImport);
       
  5899 	for(t=0; t<rowNames.length; t++) {
       
  5900 		var context = {};
       
  5901 		context.allowSynchronous = true;
       
  5902 		var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
       
  5903 	}
       
  5904 	return false;
       
  5905 };
       
  5906 
       
  5907 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
       
  5908 {
       
  5909 	if(!context.status)
       
  5910 		displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
       
  5911 	var tiddler = context.tiddler;
       
  5912 	store.suspendNotifications();
       
  5913 	store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
       
  5914 	if(!wizard.getValue("sync")) {
       
  5915 		store.setValue(tiddler.title,'server',null);
       
  5916 	}
       
  5917 	store.resumeNotifications();
       
  5918 	if(!context.isSynchronous)
       
  5919 		store.notify(tiddler.title,true);
       
  5920 	var remainingImports = wizard.getValue("remainingImports")-1;
       
  5921 	wizard.setValue("remainingImports",remainingImports);
       
  5922 	if(remainingImports == 0) {
       
  5923 		if(context.isSynchronous) {
       
  5924 			store.notifyAll();
       
  5925 			refreshDisplay();
       
  5926 		}
       
  5927 		wizard.setButtons([
       
  5928 				{caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
       
  5929 			],config.macros.importTiddlers.statusDoneImport);
       
  5930 		autoSaveChanges();
       
  5931 	}
       
  5932 };
       
  5933 
       
  5934 //--
       
  5935 //-- Upgrade macro
       
  5936 //--
       
  5937 
       
  5938 config.macros.upgrade.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  5939 {
       
  5940 	var w = new Wizard();
       
  5941 	w.createWizard(place,this.wizardTitle);
       
  5942 	w.addStep(this.step1Title,this.step1Html.format([this.source,this.source]));
       
  5943 	w.setButtons([{caption: this.upgradeLabel, tooltip: this.upgradePrompt, onClick: this.onClickUpgrade}]);
       
  5944 };
       
  5945 
       
  5946 config.macros.upgrade.onClickUpgrade = function(e)
       
  5947 {
       
  5948 	var me = config.macros.upgrade;
       
  5949 	var w = new Wizard(this);
       
  5950 	if(window.location.protocol != "file:") {
       
  5951 		alert(me.errorCantUpgrade);
       
  5952 		return false;
       
  5953 	}	
       
  5954 	if(story.areAnyDirty() || store.isDirty()) {
       
  5955 		alert(me.errorNotSaved);
       
  5956 		return false;
       
  5957 	}
       
  5958 	var localPath = getLocalPath(document.location.toString());
       
  5959 	var backupPath = getBackupPath(localPath,me.backupExtension);
       
  5960 	w.setValue("backupPath",backupPath);
       
  5961 	w.setButtons([],me.statusPreparingBackup);
       
  5962 	var original = loadOriginal(localPath);
       
  5963 	w.setButtons([],me.statusSavingBackup);
       
  5964 	var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
       
  5965 	if(backup != true) {
       
  5966 		w.setButtons([],me.errorSavingBackup);
       
  5967 		alert(me.errorSavingBackup);
       
  5968 		return false;
       
  5969 	}
       
  5970 	w.setButtons([],me.statusLoadingCore);
       
  5971 	var load = loadRemoteFile(me.source,me.onLoadCore,w);
       
  5972 	if(typeof load == "string") {
       
  5973 		w.setButtons([],me.errorLoadingCore);
       
  5974 		alert(me.errorLoadingCore);
       
  5975 		return false;
       
  5976 	}
       
  5977 	return false;
       
  5978 };
       
  5979 
       
  5980 config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
       
  5981 {
       
  5982 	var me = config.macros.upgrade;
       
  5983 	var w = params;
       
  5984 	var errMsg;
       
  5985 	if(!status)
       
  5986 		errMsg = me.errorLoadingCore;
       
  5987 	var newVer = me.extractVersion(responseText);
       
  5988 	if(!newVer)
       
  5989 		errMsg = me.errorCoreFormat;
       
  5990 	if(errMsg) {
       
  5991 		w.setButtons([],errMsg);
       
  5992 		alert(errMsg);
       
  5993 		return;
       
  5994 	}
       
  5995 	var onStartUpgrade = function(e) {
       
  5996 		w.setButtons([],me.statusSavingCore);
       
  5997 		var localPath = getLocalPath(document.location.toString());
       
  5998 		saveFile(localPath,responseText);
       
  5999 		w.setButtons([],me.statusReloadingCore);
       
  6000 		var backupPath = w.getValue("backupPath");
       
  6001 		var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM()  + '#upgrade:[[' + encodeURI(backupPath) + ']]';
       
  6002 		window.setTimeout(function () {window.location = newLoc;},10)
       
  6003 	};
       
  6004 	var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version,newVer) + 1];
       
  6005 	w.addStep(me.step2Title,step2.format([formatVersion(newVer),formatVersion(version)]));
       
  6006 	w.setButtons([{caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade},{caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel}]);
       
  6007 };
       
  6008 
       
  6009 config.macros.upgrade.onCancel = function(e)
       
  6010 {
       
  6011 	var me = config.macros.upgrade;
       
  6012 	var w = new Wizard(this);
       
  6013 	w.addStep(me.step3Title,me.step3Html);
       
  6014 	w.setButtons([]);
       
  6015 	return false;
       
  6016 }
       
  6017 
       
  6018 config.macros.upgrade.extractVersion = function(upgradeFile)
       
  6019 {
       
  6020 	var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
       
  6021 	var m = re.exec(upgradeFile);
       
  6022 	return  m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
       
  6023 };
       
  6024 
       
  6025 function upgradeFrom(path)
       
  6026 {
       
  6027 	var importStore = new TiddlyWiki();
       
  6028 	var tw = loadFile(path);
       
  6029 	if(window.netscape !== undefined)
       
  6030 		tw = convertUTF8ToUnicode(tw);
       
  6031 	importStore.importTiddlyWiki(tw);
       
  6032 	importStore.forEachTiddler(function(title,tiddler) {
       
  6033 		if(!store.getTiddler(title)) {
       
  6034 			store.addTiddler(tiddler);
       
  6035 		}
       
  6036 	});
       
  6037 	refreshDisplay();
       
  6038 	saveChanges(); //# To create appropriate Markup* sections
       
  6039 	alert(config.messages.upgradeDone.format([formatVersion()]));
       
  6040 	window.location = window.location.toString().substr(0,window.location.toString().lastIndexOf('?'));
       
  6041 }
       
  6042 
       
  6043 //--
       
  6044 //-- Sync macro
       
  6045 //--
       
  6046 
       
  6047 // Synchronisation handlers
       
  6048 config.syncers = {};
       
  6049 
       
  6050 // Sync state.
       
  6051 var currSync = null;
       
  6052 
       
  6053 // sync macro
       
  6054 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  6055 {
       
  6056 	if(!wikifier.isStatic)
       
  6057 		this.startSync(place);
       
  6058 };
       
  6059 
       
  6060 config.macros.sync.startSync = function(place)
       
  6061 {
       
  6062 	if(currSync)
       
  6063 		config.macros.sync.cancelSync();
       
  6064 	currSync = {};
       
  6065 	currSync.syncList = this.getSyncableTiddlers();
       
  6066 	this.createSyncTasks();
       
  6067 	this.preProcessSyncableTiddlers();
       
  6068 	var wizard = new Wizard();
       
  6069 	currSync.wizard = wizard;
       
  6070 	wizard.createWizard(place,this.wizardTitle);
       
  6071 	wizard.addStep(this.step1Title,this.step1Html);
       
  6072 	var markList = wizard.getElement("markList");
       
  6073 	var listWrapper = document.createElement("div");
       
  6074 	markList.parentNode.insertBefore(listWrapper,markList);
       
  6075 	currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
       
  6076 	this.processSyncableTiddlers();
       
  6077 	wizard.setButtons([
       
  6078 			{caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
       
  6079 		]);
       
  6080 };
       
  6081 
       
  6082 config.macros.sync.getSyncableTiddlers = function()
       
  6083 {
       
  6084 	var list = [];
       
  6085 	store.forEachTiddler(function(title,tiddler) {
       
  6086 		var syncItem = {};
       
  6087 		syncItem.serverType = tiddler.getServerType();
       
  6088 		syncItem.serverHost = tiddler.fields['server.host'];
       
  6089 		syncItem.serverWorkspace = tiddler.fields['server.workspace'];
       
  6090 		syncItem.tiddler = tiddler;
       
  6091 		syncItem.title = tiddler.title;
       
  6092 		syncItem.isTouched = tiddler.isTouched();
       
  6093 		syncItem.selected = syncItem.isTouched;
       
  6094 		syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
       
  6095 		syncItem.status = syncItem.syncStatus.text;
       
  6096 		if(syncItem.serverType && syncItem.serverHost)
       
  6097 			list.push(syncItem);
       
  6098 		});
       
  6099 	list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
       
  6100 	return list;
       
  6101 };
       
  6102 
       
  6103 config.macros.sync.preProcessSyncableTiddlers = function()
       
  6104 {
       
  6105 	for(var t=0; t<currSync.syncList.length; t++) {
       
  6106 		si = currSync.syncList[t];
       
  6107 		var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
       
  6108 		si.serverUrl = ti.uri;
       
  6109 	}
       
  6110 };
       
  6111 
       
  6112 config.macros.sync.processSyncableTiddlers = function()
       
  6113 {
       
  6114 	for(var t=0; t<currSync.syncList.length; t++) {
       
  6115 		si = currSync.syncList[t];
       
  6116 		si.rowElement.style.backgroundColor = si.syncStatus.color;
       
  6117 	}
       
  6118 };
       
  6119 
       
  6120 config.macros.sync.createSyncTasks = function()
       
  6121 {
       
  6122 	currSync.syncTasks = [];
       
  6123 	for(var t=0; t<currSync.syncList.length; t++) {
       
  6124 		var si = currSync.syncList[t];
       
  6125 		var r = null;
       
  6126 		for(var st=0; st<currSync.syncTasks.length; st++) {
       
  6127 			var cst = currSync.syncTasks[st];
       
  6128 			if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
       
  6129 				r = cst;
       
  6130 		}
       
  6131 		if(r == null) {
       
  6132 			si.syncTask = this.createSyncTask(si);
       
  6133 			currSync.syncTasks.push(si.syncTask);
       
  6134 		} else {
       
  6135 			si.syncTask = r;
       
  6136 			r.syncItems.push(si);
       
  6137 		}
       
  6138 	}
       
  6139 };
       
  6140 
       
  6141 config.macros.sync.createSyncTask = function(syncItem)
       
  6142 {
       
  6143 	var st = {};
       
  6144 	st.serverType = syncItem.serverType;
       
  6145 	st.serverHost = syncItem.serverHost;
       
  6146 	st.serverWorkspace = syncItem.serverWorkspace;
       
  6147 	st.syncItems = [syncItem];
       
  6148 	st.syncMachine = new SyncMachine(st.serverType,{
       
  6149 		start: function() {
       
  6150 			return this.openHost(st.serverHost,"openWorkspace");
       
  6151 		},
       
  6152 		openWorkspace: function() {
       
  6153 			return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
       
  6154 		},
       
  6155 		getTiddlerList: function() {
       
  6156 			return this.getTiddlerList("onGetTiddlerList");
       
  6157 		},
       
  6158 		onGetTiddlerList: function(context) {
       
  6159 			var tiddlers = context.tiddlers;
       
  6160 			for(var t=0; t<st.syncItems.length; t++) {
       
  6161 				var si = st.syncItems[t];
       
  6162 				var f = tiddlers.findByField("title",si.title);
       
  6163 				if(f !== null) {
       
  6164 					if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
       
  6165 						si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
       
  6166 					}
       
  6167 				} else {
       
  6168 					si.syncStatus = config.macros.sync.syncStatusList.notFound;
       
  6169 				}
       
  6170 				config.macros.sync.updateSyncStatus(si);
       
  6171 			}
       
  6172 		},
       
  6173 		getTiddler: function(title) {
       
  6174 			return this.getTiddler(title,"onGetTiddler");
       
  6175 		},
       
  6176 		onGetTiddler: function(context) {
       
  6177 			var tiddler = context.tiddler;
       
  6178 			var syncItem = st.syncItems.findByField("title",tiddler.title);
       
  6179 			if(syncItem !== null) {
       
  6180 				syncItem = st.syncItems[syncItem];
       
  6181 				store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
       
  6182 				syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
       
  6183 				config.macros.sync.updateSyncStatus(syncItem);
       
  6184 			}
       
  6185 		},
       
  6186 		putTiddler: function(tiddler) {
       
  6187 			return this.putTiddler(tiddler,"onPutTiddler");
       
  6188 		},
       
  6189 		onPutTiddler: function(context) {
       
  6190 			var title = context.title;
       
  6191 			var syncItem = st.syncItems.findByField("title",title);
       
  6192 			if(syncItem !== null) {
       
  6193 				syncItem = st.syncItems[syncItem];
       
  6194 				store.resetTiddler(title);
       
  6195 				syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
       
  6196 				config.macros.sync.updateSyncStatus(syncItem);
       
  6197 			}
       
  6198 		}
       
  6199 	});
       
  6200 	st.syncMachine.go();
       
  6201 	return st;
       
  6202 };
       
  6203 
       
  6204 config.macros.sync.updateSyncStatus = function(syncItem)
       
  6205 {
       
  6206 	var e = syncItem.colElements["status"];
       
  6207 	removeChildren(e);
       
  6208 	createTiddlyText(e,syncItem.syncStatus.text);
       
  6209 	syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
       
  6210 };
       
  6211 
       
  6212 config.macros.sync.doSync = function(e)
       
  6213 {
       
  6214 	var rowNames = ListView.getSelectedRows(currSync.listView);
       
  6215 	for(var t=0; t<currSync.syncList.length; t++) {
       
  6216 		var si = currSync.syncList[t];
       
  6217 		if(rowNames.indexOf(si.title) != -1) {
       
  6218 			config.macros.sync.doSyncItem(si);
       
  6219 		}
       
  6220 	}
       
  6221 	return false;
       
  6222 };
       
  6223 
       
  6224 config.macros.sync.doSyncItem = function(syncItem)
       
  6225 {
       
  6226 	var r = true;
       
  6227 	var sl = config.macros.sync.syncStatusList;
       
  6228 	switch(syncItem.syncStatus) {
       
  6229 		case sl.changedServer:
       
  6230 			r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
       
  6231 			break;
       
  6232 		case sl.notFound:
       
  6233 		case sl.changedLocally:
       
  6234 		case sl.changedBoth:
       
  6235 			r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
       
  6236 			break;
       
  6237 		default:
       
  6238 			break;
       
  6239 	}
       
  6240 	if(r !== true)
       
  6241 		displayMessage("Error in doSyncItem: " + r);
       
  6242 };
       
  6243 
       
  6244 config.macros.sync.cancelSync = function()
       
  6245 {
       
  6246 	currSync = null;
       
  6247 };
       
  6248 
       
  6249 function SyncMachine(serverType,steps)
       
  6250 {
       
  6251 	this.serverType = serverType;
       
  6252 	this.adaptor = new config.adaptors[serverType];
       
  6253 	this.steps = steps;
       
  6254 }
       
  6255 
       
  6256 SyncMachine.prototype.go = function(step,context)
       
  6257 {
       
  6258 	var r = context ? context.status : null;
       
  6259 	if(typeof r == "string") {
       
  6260 		this.invokeError(r);
       
  6261 		return r;
       
  6262 	}
       
  6263 	var h = this.steps[step ? step : "start"];
       
  6264 	if(!h)
       
  6265 		return null;
       
  6266 	r = h.call(this,context);
       
  6267 	if(typeof r == "string")
       
  6268 		this.invokeError(r);
       
  6269 	return r;
       
  6270 };
       
  6271 
       
  6272 SyncMachine.prototype.invokeError = function(message)
       
  6273 {
       
  6274 	if(this.steps.error)
       
  6275 		this.steps.error(message);
       
  6276 };
       
  6277 
       
  6278 SyncMachine.prototype.openHost = function(host,nextStep)
       
  6279 {
       
  6280 	var me = this;
       
  6281 	return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
       
  6282 };
       
  6283 
       
  6284 SyncMachine.prototype.getWorkspaceList = function(nextStep)
       
  6285 {
       
  6286 	var me = this;
       
  6287 	return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
       
  6288 };
       
  6289 
       
  6290 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
       
  6291 {
       
  6292 	var me = this;
       
  6293 	return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
       
  6294 };
       
  6295 
       
  6296 SyncMachine.prototype.getTiddlerList = function(nextStep)
       
  6297 {
       
  6298 	var me = this;
       
  6299 	return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
       
  6300 };
       
  6301 
       
  6302 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
       
  6303 {
       
  6304 	return this.adaptor.generateTiddlerInfo(tiddler);
       
  6305 };
       
  6306 
       
  6307 SyncMachine.prototype.getTiddler = function(title,nextStep)
       
  6308 {
       
  6309 	var me = this;
       
  6310 	return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
       
  6311 };
       
  6312 
       
  6313 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
       
  6314 {
       
  6315 	var me = this;
       
  6316 	return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
       
  6317 };
       
  6318 
       
  6319 //--
       
  6320 //-- Manager UI for groups of tiddlers
       
  6321 //--
       
  6322 
       
  6323 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  6324 {
       
  6325 	var wizard = new Wizard();
       
  6326 	wizard.createWizard(place,this.wizardTitle);
       
  6327 	wizard.addStep(this.step1Title,this.step1Html);
       
  6328 	var markList = wizard.getElement("markList");
       
  6329 	var listWrapper = document.createElement("div");
       
  6330 	markList.parentNode.insertBefore(listWrapper,markList);
       
  6331 	listWrapper.setAttribute("refresh","macro");
       
  6332 	listWrapper.setAttribute("macroName","plugins");
       
  6333 	listWrapper.setAttribute("params",paramString);
       
  6334 	this.refresh(listWrapper,paramString);
       
  6335 };
       
  6336 
       
  6337 config.macros.plugins.refresh = function(listWrapper,params)
       
  6338 {
       
  6339 	var wizard = new Wizard(listWrapper);
       
  6340 	var selectedRows = [];
       
  6341 	ListView.forEachSelector(listWrapper,function(e,rowName) {
       
  6342 			if(e.checked)
       
  6343 				selectedRows.push(e.getAttribute("rowName"));
       
  6344 		});
       
  6345 	removeChildren(listWrapper);
       
  6346 	params = params.parseParams("anon");
       
  6347 	var plugins = installedPlugins.slice(0);
       
  6348 	var t,tiddler,p;
       
  6349 	var configTiddlers = store.getTaggedTiddlers("systemConfig");
       
  6350 	for(t=0; t<configTiddlers.length; t++) {
       
  6351 		tiddler = configTiddlers[t];
       
  6352 		if(plugins.findByField("title",tiddler.title) == null) {
       
  6353 			p = getPluginInfo(tiddler);
       
  6354 			p.executed = false;
       
  6355 			p.log.splice(0,0,this.skippedText);
       
  6356 			plugins.push(p);
       
  6357 		}
       
  6358 	}
       
  6359 	for(t=0; t<plugins.length; t++) {
       
  6360 		p = plugins[t];
       
  6361 		p.size = p.tiddler.text ? p.tiddler.text.length : 0;
       
  6362 		p.forced = p.tiddler.isTagged("systemConfigForce");
       
  6363 		p.disabled = p.tiddler.isTagged("systemConfigDisable");
       
  6364 		p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
       
  6365 	}
       
  6366 	if(plugins.length == 0) {
       
  6367 		createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
       
  6368 		wizard.setButtons([]);
       
  6369 	} else {
       
  6370 		var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
       
  6371 		wizard.setValue("listView",listView);
       
  6372 		wizard.setButtons([
       
  6373 				{caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick:  config.macros.plugins.doRemoveTag},
       
  6374 				{caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick:  config.macros.plugins.doDelete}
       
  6375 			]);
       
  6376 	}
       
  6377 };
       
  6378 
       
  6379 config.macros.plugins.doRemoveTag = function(e)
       
  6380 {
       
  6381 	var wizard = new Wizard(this);
       
  6382 	var listView = wizard.getValue("listView");
       
  6383 	var rowNames = ListView.getSelectedRows(listView);
       
  6384 	if(rowNames.length == 0) {
       
  6385 		alert(config.messages.nothingSelected);
       
  6386 	} else {
       
  6387 		for(var t=0; t<rowNames.length; t++)
       
  6388 			store.setTiddlerTag(rowNames[t],false,"systemConfig");
       
  6389 	}
       
  6390 };
       
  6391 
       
  6392 config.macros.plugins.doDelete = function(e)
       
  6393 {
       
  6394 	var wizard = new Wizard(this);
       
  6395 	var listView = wizard.getValue("listView");
       
  6396 	var rowNames = ListView.getSelectedRows(listView);
       
  6397 	if(rowNames.length == 0) {
       
  6398 		alert(config.messages.nothingSelected);
       
  6399 	} else {
       
  6400 		if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
       
  6401 			for(t=0; t<rowNames.length; t++) {
       
  6402 				store.removeTiddler(rowNames[t]);
       
  6403 				story.closeTiddler(rowNames[t],true);
       
  6404 			}
       
  6405 		}
       
  6406 	}
       
  6407 };
       
  6408 
       
  6409 //--
       
  6410 //-- Message area
       
  6411 //--
       
  6412 
       
  6413 function getMessageDiv()
       
  6414 {
       
  6415 	var msgArea = document.getElementById("messageArea");
       
  6416 	if(!msgArea)
       
  6417 		return null;
       
  6418 	if(!msgArea.hasChildNodes())
       
  6419 		createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
       
  6420 			config.messages.messageClose.text,
       
  6421 			config.messages.messageClose.tooltip,
       
  6422 			clearMessage);
       
  6423 	msgArea.style.display = "block";
       
  6424 	return createTiddlyElement(msgArea,"div");
       
  6425 }
       
  6426 
       
  6427 function displayMessage(text,linkText)
       
  6428 {
       
  6429 	var e = getMessageDiv();
       
  6430 	if(!e) {
       
  6431 		alert(text);
       
  6432 		return;
       
  6433 	}
       
  6434 	if(linkText) {
       
  6435 		var link = createTiddlyElement(e,"a",null,null,text);
       
  6436 		link.href = linkText;
       
  6437 		link.target = "_blank";
       
  6438 	} else {
       
  6439 		e.appendChild(document.createTextNode(text));
       
  6440 	}
       
  6441 }
       
  6442 
       
  6443 function clearMessage()
       
  6444 {
       
  6445 	var msgArea = document.getElementById("messageArea");
       
  6446 	if(msgArea) {
       
  6447 		removeChildren(msgArea);
       
  6448 		msgArea.style.display = "none";
       
  6449 	}
       
  6450 	return false;
       
  6451 }
       
  6452 
       
  6453 //--
       
  6454 //-- Refresh mechanism
       
  6455 //--
       
  6456 
       
  6457 config.refreshers = {
       
  6458 	link: function(e,changeList)
       
  6459 		{
       
  6460 		var title = e.getAttribute("tiddlyLink");
       
  6461 		refreshTiddlyLink(e,title);
       
  6462 		return true;
       
  6463 		},
       
  6464 
       
  6465 	tiddler: function(e,changeList)
       
  6466 		{
       
  6467 		var title = e.getAttribute("tiddler");
       
  6468 		var template = e.getAttribute("template");
       
  6469 		if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
       
  6470 			story.refreshTiddler(title,template,true);
       
  6471 		else
       
  6472 			refreshElements(e,changeList);
       
  6473 		return true;
       
  6474 		},
       
  6475 
       
  6476 	content: function(e,changeList)
       
  6477 		{
       
  6478 		var title = e.getAttribute("tiddler");
       
  6479 		var force = e.getAttribute("force");
       
  6480 		if(force != null || changeList == null || changeList.indexOf(title) != -1) {
       
  6481 			removeChildren(e);
       
  6482 			wikify(store.getTiddlerText(title,title),e,null,store.fetchTiddler(title));
       
  6483 			return true;
       
  6484 		} else
       
  6485 			return false;
       
  6486 		},
       
  6487 
       
  6488 	macro: function(e,changeList)
       
  6489 		{
       
  6490 		var macro = e.getAttribute("macroName");
       
  6491 		var params = e.getAttribute("params");
       
  6492 		if(macro)
       
  6493 			macro = config.macros[macro];
       
  6494 		if(macro && macro.refresh)
       
  6495 			macro.refresh(e,params);
       
  6496 		return true;
       
  6497 		}
       
  6498 };
       
  6499 
       
  6500 config.refresherData = {
       
  6501 	styleSheet: "StyleSheet",
       
  6502 	defaultStyleSheet: "StyleSheet",
       
  6503 	pageTemplate: "PageTemplate",
       
  6504 	defaultPageTemplate: "PageTemplate",
       
  6505 	colorPalette: "ColorPalette",
       
  6506 	defaultColorPalette: "ColorPalette"
       
  6507 };
       
  6508 
       
  6509 function refreshElements(root,changeList)
       
  6510 {
       
  6511 	var nodes = root.childNodes;
       
  6512 	for(var c=0; c<nodes.length; c++) {
       
  6513 		var e = nodes[c], type = null;
       
  6514 		if(e.getAttribute  && (e.tagName ? e.tagName != "IFRAME" : true))
       
  6515 			type = e.getAttribute("refresh");
       
  6516 		var refresher = config.refreshers[type];
       
  6517 		var refreshed = false;
       
  6518 		if(refresher != undefined)
       
  6519 			refreshed = refresher(e,changeList);
       
  6520 		if(e.hasChildNodes() && !refreshed)
       
  6521 			refreshElements(e,changeList);
       
  6522 	}
       
  6523 }
       
  6524 
       
  6525 function applyHtmlMacros(root,tiddler)
       
  6526 {
       
  6527 	var e = root.firstChild;
       
  6528 	while(e) {
       
  6529 		var nextChild = e.nextSibling;
       
  6530 		if(e.getAttribute) {
       
  6531 			var macro = e.getAttribute("macro");
       
  6532 			if(macro) {
       
  6533 				var params = "";
       
  6534 				var p = macro.indexOf(" ");
       
  6535 				if(p != -1) {
       
  6536 					params = macro.substr(p+1);
       
  6537 					macro = macro.substr(0,p);
       
  6538 				}
       
  6539 				invokeMacro(e,macro,params,null,tiddler);
       
  6540 			}
       
  6541 		}
       
  6542 		if(e.hasChildNodes())
       
  6543 			applyHtmlMacros(e,tiddler);
       
  6544 		e = nextChild;
       
  6545 	}
       
  6546 }
       
  6547 
       
  6548 function refreshPageTemplate(title)
       
  6549 {
       
  6550 	var stash = createTiddlyElement(document.body,"div");
       
  6551 	stash.style.display = "none";
       
  6552 	var display = story.getContainer();
       
  6553 	var nodes,t;
       
  6554 	if(display) {
       
  6555 		nodes = display.childNodes;
       
  6556 		for(t=nodes.length-1; t>=0; t--)
       
  6557 			stash.appendChild(nodes[t]);
       
  6558 	}
       
  6559 	var wrapper = document.getElementById("contentWrapper");
       
  6560 
       
  6561 	isAvailable = function(title) {
       
  6562 		var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
       
  6563 		if(s!=-1)
       
  6564 			title = title.substr(0,s);
       
  6565 		return store.tiddlerExists(title) || store.isShadowTiddler(title);
       
  6566 	};
       
  6567 	if(!title || !isAvailable(title))
       
  6568 		title = config.refresherData.pageTemplate;
       
  6569 	if(!isAvailable(title))
       
  6570 		title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
       
  6571 	html = store.getRecursiveTiddlerText(title,null,10);
       
  6572 	wrapper.innerHTML = html;
       
  6573 	applyHtmlMacros(wrapper);
       
  6574 	refreshElements(wrapper);
       
  6575 	display = story.getContainer();
       
  6576 	removeChildren(display);
       
  6577 	if(!display)
       
  6578 		display = createTiddlyElement(wrapper,"div",story.containerId());
       
  6579 	nodes = stash.childNodes;
       
  6580 	for(t=nodes.length-1; t>=0; t--)
       
  6581 		display.appendChild(nodes[t]);
       
  6582 	removeNode(stash);
       
  6583 }
       
  6584 
       
  6585 function refreshDisplay(hint)
       
  6586 {
       
  6587 	if(typeof hint == "string")
       
  6588 		hint = [hint];
       
  6589 	var e = document.getElementById("contentWrapper");
       
  6590 	refreshElements(e,hint);
       
  6591 	if(backstage.isPanelVisible()) {
       
  6592 		e = document.getElementById("backstage");
       
  6593 		refreshElements(e,hint);
       
  6594 	}
       
  6595 }
       
  6596 
       
  6597 function refreshPageTitle()
       
  6598 {
       
  6599 	document.title = getPageTitle();
       
  6600 }
       
  6601 
       
  6602 function getPageTitle()
       
  6603 {
       
  6604 	var st = wikifyPlain("SiteTitle");
       
  6605 	var ss = wikifyPlain("SiteSubtitle");
       
  6606 	return st + ((st == "" || ss == "") ? "" : " - ") + ss;
       
  6607 }
       
  6608 
       
  6609 function refreshStyles(title,doc)
       
  6610 {
       
  6611 	setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc ? doc : document);
       
  6612 }
       
  6613 
       
  6614 function refreshColorPalette(title)
       
  6615 {
       
  6616 	if(!startingUp)
       
  6617 		refreshAll();
       
  6618 }
       
  6619 
       
  6620 function refreshAll()
       
  6621 {
       
  6622 	refreshPageTemplate();
       
  6623 	refreshDisplay();
       
  6624 	refreshStyles("StyleSheetLayout");
       
  6625 	refreshStyles("StyleSheetColors");
       
  6626 	refreshStyles(config.refresherData.styleSheet);
       
  6627 	refreshStyles("StyleSheetPrint");
       
  6628 }
       
  6629 
       
  6630 //--
       
  6631 //-- Options stuff
       
  6632 //--
       
  6633 
       
  6634 config.optionHandlers = {
       
  6635 	'txt': {
       
  6636 		get: function(name) {return encodeCookie(config.options[name].toString());},
       
  6637 		set: function(name,value) {config.options[name] = decodeCookie(value);}
       
  6638 	},
       
  6639 	'chk': {
       
  6640 		get: function(name) {return config.options[name] ? "true" : "false";},
       
  6641 		set: function(name,value) {config.options[name] = value == "true";}
       
  6642 	}
       
  6643 };
       
  6644 
       
  6645 function loadOptionsCookie()
       
  6646 {
       
  6647 	if(safeMode)
       
  6648 		return;
       
  6649 	var cookies = document.cookie.split(";");
       
  6650 	for(var c=0; c<cookies.length; c++) {
       
  6651 		var p = cookies[c].indexOf("=");
       
  6652 		if(p != -1) {
       
  6653 			var name = cookies[c].substr(0,p).trim();
       
  6654 			var value = cookies[c].substr(p+1).trim();
       
  6655 			var optType = name.substr(0,3);
       
  6656 			if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
       
  6657 				config.optionHandlers[optType].set(name,value);
       
  6658 		}
       
  6659 	}
       
  6660 }
       
  6661 
       
  6662 function saveOptionCookie(name)
       
  6663 {
       
  6664 	if(safeMode)
       
  6665 		return;
       
  6666 	var c = name + "=";
       
  6667 	var optType = name.substr(0,3);
       
  6668 	if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
       
  6669 		c += config.optionHandlers[optType].get(name);
       
  6670 	c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
       
  6671 	document.cookie = c;
       
  6672 }
       
  6673 
       
  6674 function encodeCookie(s)
       
  6675 {
       
  6676 	return escape(manualConvertUnicodeToUTF8(s));
       
  6677 }
       
  6678 
       
  6679 function decodeCookie(s)
       
  6680 {
       
  6681 	s = unescape(s);
       
  6682 	var re = /&#[0-9]{1,5};/g;
       
  6683 	return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
       
  6684 }
       
  6685 
       
  6686 
       
  6687 config.macros.option.genericCreate = function(place,type,opt,className,desc)
       
  6688 {
       
  6689 	var typeInfo = config.macros.option.types[type];
       
  6690 	var c = document.createElement(typeInfo.elementType);
       
  6691 	if(typeInfo.typeValue)
       
  6692 		c.setAttribute("type",typeInfo.typeValue);
       
  6693 	c[typeInfo.eventName] = typeInfo.onChange;
       
  6694 	c.setAttribute("option",opt);
       
  6695 	if(className)
       
  6696 		c.className = className;
       
  6697 	else
       
  6698 		c.className = typeInfo.className;
       
  6699 	if(config.optionsDesc[opt])
       
  6700 		c.setAttribute("title",config.optionsDesc[opt]);
       
  6701 	place.appendChild(c);
       
  6702 	if(desc != "no")
       
  6703 		createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
       
  6704 	c[typeInfo.valueField] = config.options[opt];
       
  6705 	return c;
       
  6706 };
       
  6707 
       
  6708 config.macros.option.genericOnChange = function(e)
       
  6709 {
       
  6710 	var opt = this.getAttribute("option");
       
  6711 	if(opt) {
       
  6712 		var optType = opt.substr(0,3);
       
  6713 		var handler = config.macros.option.types[optType];
       
  6714 		if(handler.elementType && handler.valueField)
       
  6715 			config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
       
  6716 		}
       
  6717 	return true;
       
  6718 };
       
  6719 
       
  6720 config.macros.option.types = {
       
  6721 	'txt': {
       
  6722 		elementType: "input",
       
  6723 		valueField: "value",
       
  6724 		eventName: "onkeyup",
       
  6725 		className: "txtOptionInput",
       
  6726 		create: config.macros.option.genericCreate,
       
  6727 		onChange: config.macros.option.genericOnChange
       
  6728 	},
       
  6729 	'chk': {
       
  6730 		elementType: "input",
       
  6731 		valueField: "checked",
       
  6732 		eventName: "onclick",
       
  6733 		className: "chkOptionInput",
       
  6734 		typeValue: "checkbox",
       
  6735 		create: config.macros.option.genericCreate,
       
  6736 		onChange: config.macros.option.genericOnChange
       
  6737 	}
       
  6738 };
       
  6739 
       
  6740 config.macros.option.propagateOption = function(opt,valueField,value,elementType)
       
  6741 {
       
  6742 	config.options[opt] = value;
       
  6743 	saveOptionCookie(opt);
       
  6744 	var nodes = document.getElementsByTagName(elementType);
       
  6745 	for(var t=0; t<nodes.length; t++) {
       
  6746 		var optNode = nodes[t].getAttribute("option");
       
  6747 		if(opt == optNode)
       
  6748 			nodes[t][valueField] = value;
       
  6749 		}
       
  6750 };
       
  6751 
       
  6752 config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  6753 {
       
  6754 	params = paramString.parseParams("anon",null,true,false,false);
       
  6755 	var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
       
  6756 	var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
       
  6757 	var desc = getParam(params,"desc","no");
       
  6758 	var type = opt.substr(0,3);
       
  6759 	var h = config.macros.option.types[type];
       
  6760 	if(h && h.create)
       
  6761 		h.create(place,type,opt,className,desc);
       
  6762 };
       
  6763 
       
  6764 config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
       
  6765 {
       
  6766 	params = paramString.parseParams("anon",null,true,false,false);
       
  6767 	var showUnknown = getParam(params,"showUnknown","no");
       
  6768 	var wizard = new Wizard();
       
  6769 	wizard.createWizard(place,this.wizardTitle);
       
  6770 	wizard.addStep(this.step1Title,this.step1Html);
       
  6771 	var markList = wizard.getElement("markList");
       
  6772 	var chkUnknown = wizard.getElement("chkUnknown");
       
  6773 	chkUnknown.checked = showUnknown == "yes";
       
  6774 	chkUnknown.onchange = this.onChangeUnknown;
       
  6775 	var listWrapper = document.createElement("div");
       
  6776 	markList.parentNode.insertBefore(listWrapper,markList);
       
  6777 	wizard.setValue("listWrapper",listWrapper);
       
  6778 	this.refreshOptions(listWrapper,showUnknown == "yes");
       
  6779 };
       
  6780 
       
  6781 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
       
  6782 {
       
  6783 	var opts = [];
       
  6784 	for(var n in config.options) {
       
  6785 		var opt = {};
       
  6786 		opt.option = "";
       
  6787 		opt.name = n;
       
  6788 		opt.lowlight = !config.optionsDesc[n];
       
  6789 		opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
       
  6790 		if(!opt.lowlight || showUnknown)
       
  6791 			opts.push(opt);
       
  6792 	}
       
  6793 	opts.sort(function(a,b) {return a.name.substr(3) < b.name.substr(3) ? -1 : (a.name.substr(3) == b.name.substr(3) ? 0 : +1);});
       
  6794 	var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
       
  6795 	for(n=0; n<opts.length; n++) {
       
  6796 		var type = opts[n].name.substr(0,3);
       
  6797 		var h = config.macros.option.types[type];
       
  6798 		if(h && h.create) {
       
  6799 			h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
       
  6800 		}
       
  6801 	}
       
  6802 };
       
  6803 
       
  6804 config.macros.options.onChangeUnknown = function(e)
       
  6805 {
       
  6806 	var wizard = new Wizard(this);
       
  6807 	var listWrapper = wizard.getValue("listWrapper");
       
  6808 	removeChildren(listWrapper);
       
  6809 	config.macros.options.refreshOptions(listWrapper,this.checked);
       
  6810 	return false;
       
  6811 };
       
  6812 
       
  6813 //--
       
  6814 //-- Saving
       
  6815 //--
       
  6816 
       
  6817 var saveUsingSafari = false;
       
  6818 
       
  6819 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
       
  6820 var endSaveArea = '</d' + 'iv>';
       
  6821 
       
  6822 // If there are unsaved changes, force the user to confirm before exitting
       
  6823 function confirmExit()
       
  6824 {
       
  6825 	hadConfirmExit = true;
       
  6826 	if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
       
  6827 		return config.messages.confirmExit;
       
  6828 }
       
  6829 
       
  6830 // Give the user a chance to save changes before exitting
       
  6831 function checkUnsavedChanges()
       
  6832 {
       
  6833 	if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
       
  6834 		if(confirm(config.messages.unsavedChangesWarning))
       
  6835 			saveChanges();
       
  6836 	}
       
  6837 }
       
  6838 
       
  6839 function updateLanguageAttribute(s)
       
  6840 {
       
  6841 	if(config.locale) {
       
  6842 		var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
       
  6843 		var m = mRE.exec(s);
       
  6844 		if(m) {
       
  6845 			var t = m[1];
       
  6846 			if(m[2])
       
  6847 				t += ' xml:lang="' + config.locale + '"';
       
  6848 			if(m[3])
       
  6849 				t += ' lang="' + config.locale + '"';
       
  6850 			t += ">";
       
  6851 			s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
       
  6852 		}
       
  6853 	}
       
  6854 	return s;
       
  6855 }
       
  6856 
       
  6857 function updateMarkupBlock(s,blockName,tiddlerName)
       
  6858 {
       
  6859 	return s.replaceChunk(
       
  6860 			"<!--%0-START-->".format([blockName]),
       
  6861 			"<!--%0-END-->".format([blockName]),
       
  6862 			"\n" + convertUnicodeToUTF8(store.getRecursiveTiddlerText(tiddlerName,"")) + "\n");
       
  6863 }
       
  6864 
       
  6865 function updateOriginal(original,posDiv)
       
  6866 {
       
  6867 	if(!posDiv)
       
  6868 		posDiv = locateStoreArea(original);
       
  6869 	if(!posDiv) {
       
  6870 		alert(config.messages.invalidFileError.format([localPath]));
       
  6871 		return null;
       
  6872 	}
       
  6873 	var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
       
  6874 				convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
       
  6875 				original.substr(posDiv[1]);
       
  6876 	var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
       
  6877 	revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
       
  6878 	revised = updateLanguageAttribute(revised);
       
  6879 	revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
       
  6880 	revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
       
  6881 	revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
       
  6882 	revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
       
  6883 	return revised;
       
  6884 }
       
  6885 
       
  6886 function locateStoreArea(original)
       
  6887 {
       
  6888 	// Locate the storeArea div's
       
  6889 	var posOpeningDiv = original.indexOf(startSaveArea);
       
  6890 	var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
       
  6891 	if(limitClosingDiv == -1)
       
  6892 		limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
       
  6893 	var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
       
  6894 	return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
       
  6895 }
       
  6896 
       
  6897 function autoSaveChanges(onlyIfDirty,tiddlers)
       
  6898 {
       
  6899 	if(config.options.chkAutoSave)
       
  6900 		saveChanges(onlyIfDirty,tiddlers);
       
  6901 }
       
  6902 
       
  6903 function loadOriginal(localPath)
       
  6904 {
       
  6905 	return loadFile(localPath);
       
  6906 }
       
  6907 
       
  6908 // Save this tiddlywiki with the pending changes
       
  6909 function saveChanges(onlyIfDirty,tiddlers)
       
  6910 {
       
  6911 	if(onlyIfDirty && !store.isDirty())
       
  6912 		return;
       
  6913 	clearMessage();
       
  6914 	var t0 = new Date();
       
  6915 	var originalPath = document.location.toString();
       
  6916 	if(originalPath.substr(0,5) != "file:") {
       
  6917 		alert(config.messages.notFileUrlError);
       
  6918 		if(store.tiddlerExists(config.messages.saveInstructions))
       
  6919 			story.displayTiddler(null,config.messages.saveInstructions);
       
  6920 		return;
       
  6921 	}
       
  6922 	var localPath = getLocalPath(originalPath);
       
  6923 	var original = loadOriginal(localPath);
       
  6924 	if(original == null) {
       
  6925 		alert(config.messages.cantSaveError);
       
  6926 		if(store.tiddlerExists(config.messages.saveInstructions))
       
  6927 			story.displayTiddler(null,config.messages.saveInstructions);
       
  6928 		return;
       
  6929 	}
       
  6930 	var posDiv = locateStoreArea(original);
       
  6931 	if(!posDiv) {
       
  6932 		alert(config.messages.invalidFileError.format([localPath]));
       
  6933 		return;
       
  6934 	}
       
  6935 	saveMain(localPath,original,posDiv);
       
  6936 	if(config.options.chkSaveBackups)
       
  6937 		saveBackup(localPath,original);
       
  6938 	if(config.options.chkSaveEmptyTemplate)
       
  6939 		saveEmpty(localPath,original,posDiv);
       
  6940 	if(config.options.chkGenerateAnRssFeed)
       
  6941 		saveRss(localPath);
       
  6942 	if(config.options.chkDisplayInstrumentation)
       
  6943 		displayMessage("saveChanges " + (new Date()-t0) + " ms");
       
  6944 }
       
  6945 
       
  6946 function saveMain(localPath,original,posDiv)
       
  6947 {
       
  6948 	var save;
       
  6949 	try {
       
  6950 		var revised = updateOriginal(original,posDiv);
       
  6951 		save = saveFile(localPath,revised);
       
  6952 	} catch (ex) {
       
  6953 		showException(ex);
       
  6954 	}
       
  6955 	if(save) {
       
  6956 		displayMessage(config.messages.mainSaved,"file://" + localPath);
       
  6957 		store.setDirty(false);
       
  6958 	} else {
       
  6959 		alert(config.messages.mainFailed);
       
  6960 	}
       
  6961 }
       
  6962 
       
  6963 function saveBackup(localPath,original)
       
  6964 {
       
  6965 	var backupPath = getBackupPath(localPath);
       
  6966 	var backup = copyFile(backupPath,localPath);
       
  6967 	if(!backup)
       
  6968 		backup = saveFile(backupPath,original);
       
  6969 	if(backup)
       
  6970 		displayMessage(config.messages.backupSaved,"file://" + backupPath);
       
  6971 	else
       
  6972 		alert(config.messages.backupFailed);
       
  6973 }
       
  6974 
       
  6975 function saveEmpty(localPath,original,posDiv)
       
  6976 {
       
  6977 	var emptyPath,p;
       
  6978 	if((p = localPath.lastIndexOf("/")) != -1)
       
  6979 		emptyPath = localPath.substr(0,p) + "/";
       
  6980 	else if((p = localPath.lastIndexOf("\\")) != -1)
       
  6981 		emptyPath = localPath.substr(0,p) + "\\";
       
  6982 	else
       
  6983 		emptyPath = localPath + ".";
       
  6984 	emptyPath += "empty.html";
       
  6985 	var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
       
  6986 	var emptySave = saveFile(emptyPath,empty);
       
  6987 	if(emptySave)
       
  6988 		displayMessage(config.messages.emptySaved,"file://" + emptyPath);
       
  6989 	else
       
  6990 		alert(config.messages.emptyFailed);
       
  6991 }
       
  6992 
       
  6993 function saveRss(localPath)
       
  6994 {
       
  6995 	var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
       
  6996 	if(saveFile(rssPath,convertUnicodeToUTF8(generateRss())))
       
  6997 		displayMessage(config.messages.rssSaved,"file://" + rssPath);
       
  6998 	else
       
  6999 		alert(config.messages.rssFailed);
       
  7000 }
       
  7001 
       
  7002 function getLocalPath(origPath)
       
  7003 {
       
  7004 	var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
       
  7005 	// Remove any location or query part of the URL
       
  7006 	var argPos = originalPath.indexOf("?");
       
  7007 	if(argPos != -1)
       
  7008 		originalPath = originalPath.substr(0,argPos);
       
  7009 	var hashPos = originalPath.indexOf("#");
       
  7010 	if(hashPos != -1)
       
  7011 		originalPath = originalPath.substr(0,hashPos);
       
  7012 	// Convert file://localhost/ to file:///
       
  7013 	if(originalPath.indexOf("file://localhost/") == 0)
       
  7014 		originalPath = "file://" + originalPath.substr(16);
       
  7015 	// Convert to a native file format
       
  7016 	var localPath;
       
  7017 	if(originalPath.charAt(9) == ":") // pc local file
       
  7018 		localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
       
  7019 	else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
       
  7020 		localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
       
  7021 	else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
       
  7022 		localPath = unescape(originalPath.substr(7));
       
  7023 	else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
       
  7024 		localPath = unescape(originalPath.substr(5));
       
  7025 	else // pc network file
       
  7026 		localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
       
  7027 	return localPath;
       
  7028 }
       
  7029 
       
  7030 function getBackupPath(localPath,title,extension)
       
  7031 {
       
  7032 	var slash = "\\";
       
  7033 	var dirPathPos = localPath.lastIndexOf("\\");
       
  7034 	if(dirPathPos == -1) {
       
  7035 		dirPathPos = localPath.lastIndexOf("/");
       
  7036 		slash = "/";
       
  7037 	}
       
  7038 	var backupFolder = config.options.txtBackupFolder;
       
  7039 	if(!backupFolder || backupFolder == "")
       
  7040 		backupFolder = ".";
       
  7041 	var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
       
  7042 	backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
       
  7043 	if(title)
       
  7044 		backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
       
  7045 	backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension ? extension : "html");
       
  7046 	return backupPath;
       
  7047 }
       
  7048 
       
  7049 function generateRss()
       
  7050 {
       
  7051 	var s = [];
       
  7052 	var d = new Date();
       
  7053 	var u = store.getTiddlerText("SiteUrl");
       
  7054 	// Assemble the header
       
  7055 	s.push("<" + "?xml version=\"1.0\"?" + ">");
       
  7056 	s.push("<rss version=\"2.0\">");
       
  7057 	s.push("<channel>");
       
  7058 	s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
       
  7059 	if(u)
       
  7060 		s.push("<link>" + u.htmlEncode() + "</link>");
       
  7061 	s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
       
  7062 	s.push("<language>en-us</language>");
       
  7063 	s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
       
  7064 	s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
       
  7065 	s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
       
  7066 	s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
       
  7067 	s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
       
  7068 	// The body
       
  7069 	var tiddlers = store.getTiddlers("modified","excludeLists");
       
  7070 	var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
       
  7071 	for(var t=tiddlers.length-1; t>=n; t--) {
       
  7072 		s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
       
  7073 	}
       
  7074 	// And footer
       
  7075 	s.push("</channel>");
       
  7076 	s.push("</rss>");
       
  7077 	// Save it all
       
  7078 	return s.join("\n");
       
  7079 }
       
  7080 
       
  7081 
       
  7082 //--
       
  7083 //-- Filesystem code
       
  7084 //--
       
  7085 
       
  7086 function convertUTF8ToUnicode(u)
       
  7087 {
       
  7088 	return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
       
  7089 }
       
  7090 
       
  7091 function manualConvertUTF8ToUnicode(utf)
       
  7092 {
       
  7093 	var uni = utf;
       
  7094 	var src = 0;
       
  7095 	var dst = 0;
       
  7096 	var b1, b2, b3;
       
  7097 	var c;
       
  7098 	while(src < utf.length) {
       
  7099 		b1 = utf.charCodeAt(src++);
       
  7100 		if(b1 < 0x80) {
       
  7101 			dst++;
       
  7102 		} else if(b1 < 0xE0) {
       
  7103 			b2 = utf.charCodeAt(src++);
       
  7104 			c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
       
  7105 			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
       
  7106 		} else {
       
  7107 			b2 = utf.charCodeAt(src++);
       
  7108 			b3 = utf.charCodeAt(src++);
       
  7109 			c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
       
  7110 			uni = uni.substring(0,dst++).concat(c,utf.substr(src));
       
  7111 		}
       
  7112 	}
       
  7113 	return uni;
       
  7114 }
       
  7115 
       
  7116 function mozConvertUTF8ToUnicode(u)
       
  7117 {
       
  7118 	try {
       
  7119 		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       
  7120 		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
       
  7121 		converter.charset = "UTF-8";
       
  7122 	} catch(ex) {
       
  7123 		return manualConvertUTF8ToUnicode(u);
       
  7124 	} // fallback
       
  7125 	var s = converter.ConvertToUnicode(u);
       
  7126 	var fin = converter.Finish();
       
  7127 	return (fin.length > 0) ? s+fin : s;
       
  7128 }
       
  7129 
       
  7130 function convertUnicodeToUTF8(s)
       
  7131 {
       
  7132 	if(window.netscape == undefined)
       
  7133 		return manualConvertUnicodeToUTF8(s);
       
  7134 	else
       
  7135 		return mozConvertUnicodeToUTF8(s);
       
  7136 }
       
  7137 
       
  7138 function manualConvertUnicodeToUTF8(s)
       
  7139 {
       
  7140 	var re = /[^\u0000-\u007F]/g ;
       
  7141 	return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
       
  7142 }
       
  7143 
       
  7144 function mozConvertUnicodeToUTF8(s)
       
  7145 {
       
  7146 	try {
       
  7147 		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       
  7148 		var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
       
  7149 		converter.charset = "UTF-8";
       
  7150 	} catch(ex) {
       
  7151 		return manualConvertUnicodeToUTF8(s);
       
  7152 	} // fallback
       
  7153 	var u = converter.ConvertFromUnicode(s);
       
  7154 	var fin = converter.Finish();
       
  7155 	return fin.length > 0 ? u + fin : u;
       
  7156 }
       
  7157 
       
  7158 function convertUriToUTF8(uri,charSet)
       
  7159 {
       
  7160 	if(window.netscape == undefined || charSet == undefined || charSet == "")
       
  7161 		return uri;
       
  7162 	try {
       
  7163 		netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       
  7164 		var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
       
  7165 	} catch(ex) {
       
  7166 		return uri;
       
  7167 	}
       
  7168 	return converter.convertURISpecToUTF8(uri,charSet);
       
  7169 }
       
  7170 
       
  7171 function copyFile(dest,source)
       
  7172 {
       
  7173 	return config.browser.isIE ? ieCopyFile(dest,source) : false;
       
  7174 }
       
  7175 
       
  7176 function saveFile(fileUrl,content)
       
  7177 {
       
  7178 	var r = mozillaSaveFile(fileUrl,content);
       
  7179 	if(!r)
       
  7180 		r = ieSaveFile(fileUrl,content);
       
  7181 	if(!r)
       
  7182 		r = javaSaveFile(fileUrl,content);
       
  7183 	return r;
       
  7184 }
       
  7185 
       
  7186 function loadFile(fileUrl)
       
  7187 {
       
  7188 	var r = mozillaLoadFile(fileUrl);
       
  7189 	if((r == null) || (r == false))
       
  7190 		r = ieLoadFile(fileUrl);
       
  7191 	if((r == null) || (r == false))
       
  7192 		r = javaLoadFile(fileUrl);
       
  7193 	return r;
       
  7194 }
       
  7195 
       
  7196 function ieCreatePath(path)
       
  7197 {
       
  7198 	try {
       
  7199 		var fso = new ActiveXObject("Scripting.FileSystemObject");
       
  7200 	} catch(ex) {
       
  7201 		return null;
       
  7202 	}
       
  7203 
       
  7204 	var pos = path.lastIndexOf("\\");
       
  7205 	if(pos!=-1)
       
  7206 		path = path.substring(0, pos+1);
       
  7207 
       
  7208 	var scan = [];
       
  7209 	scan.push(path);
       
  7210 	var i = 0;
       
  7211 	do {
       
  7212 		var parent = fso.GetParentFolderName(scan[i++]);
       
  7213 		if(fso.FolderExists(parent))
       
  7214 			break;
       
  7215 		scan.push(parent);
       
  7216 	} while(true);
       
  7217 
       
  7218 	for(i=scan.length-1;i>=0;i--) {
       
  7219 		if(!fso.FolderExists(scan[i]))
       
  7220 			fso.CreateFolder(scan[i]);
       
  7221 	}
       
  7222 	return true;
       
  7223 }
       
  7224 
       
  7225 // Returns null if it can't do it, false if there's an error, true if it saved OK
       
  7226 function ieSaveFile(filePath,content)
       
  7227 {
       
  7228 	ieCreatePath(filePath);
       
  7229 	try {
       
  7230 		var fso = new ActiveXObject("Scripting.FileSystemObject");
       
  7231 	} catch(ex) {
       
  7232 		return null;
       
  7233 	}
       
  7234 	var file = fso.OpenTextFile(filePath,2,-1,0);
       
  7235 	file.Write(content);
       
  7236 	file.Close();
       
  7237 	return true;
       
  7238 }
       
  7239 
       
  7240 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
       
  7241 function ieLoadFile(filePath)
       
  7242 {
       
  7243 	try {
       
  7244 		var fso = new ActiveXObject("Scripting.FileSystemObject");
       
  7245 		var file = fso.OpenTextFile(filePath,1);
       
  7246 		var content = file.ReadAll();
       
  7247 		file.Close();
       
  7248 	} catch(ex) {
       
  7249 		return null;
       
  7250 	}
       
  7251 	return content;
       
  7252 }
       
  7253 
       
  7254 function ieCopyFile(dest,source)
       
  7255 {
       
  7256 	ieCreatePath(dest);
       
  7257 	try {
       
  7258 		var fso = new ActiveXObject("Scripting.FileSystemObject");
       
  7259 		fso.GetFile(source).Copy(dest);
       
  7260 	} catch(ex) {
       
  7261 		return false;
       
  7262 	}
       
  7263 	return true;
       
  7264 }
       
  7265 
       
  7266 // Returns null if it can't do it, false if there's an error, true if it saved OK
       
  7267 function mozillaSaveFile(filePath,content)
       
  7268 {
       
  7269 	if(window.Components) {
       
  7270 		try {
       
  7271 			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       
  7272 			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
       
  7273 			file.initWithPath(filePath);
       
  7274 			if(!file.exists())
       
  7275 				file.create(0,0664);
       
  7276 			var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
       
  7277 			out.init(file,0x20|0x02,00004,null);
       
  7278 			out.write(content,content.length);
       
  7279 			out.flush();
       
  7280 			out.close();
       
  7281 			return true;
       
  7282 		} catch(ex) {
       
  7283 			return false;
       
  7284 		}
       
  7285 	}
       
  7286 	return null;
       
  7287 }
       
  7288 
       
  7289 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
       
  7290 function mozillaLoadFile(filePath)
       
  7291 {
       
  7292 	if(window.Components) {
       
  7293 		try {
       
  7294 			netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
       
  7295 			var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
       
  7296 			file.initWithPath(filePath);
       
  7297 			if(!file.exists())
       
  7298 				return null;
       
  7299 			var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
       
  7300 			inputStream.init(file,0x01,00004,null);
       
  7301 			var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
       
  7302 			sInputStream.init(inputStream);
       
  7303 			var contents = sInputStream.read(sInputStream.available());
       
  7304 			sInputStream.close();
       
  7305 			inputStream.close();
       
  7306 			return contents;
       
  7307 		} catch(ex) {
       
  7308 			return false;
       
  7309 		}
       
  7310 	}
       
  7311 	return null;
       
  7312 }
       
  7313 
       
  7314 function javaUrlToFilename(url)
       
  7315 {
       
  7316 	var f = "//localhost";
       
  7317 	if(url.indexOf(f) == 0)
       
  7318 		return url.substring(f.length);
       
  7319 	var i = url.indexOf(":");
       
  7320 	if(i > 0)
       
  7321 		return url.substring(i-1);
       
  7322 	return url;
       
  7323 }
       
  7324 
       
  7325 function javaSaveFile(filePath,content)
       
  7326 {
       
  7327 	try {
       
  7328 		if(document.applets["TiddlySaver"])
       
  7329 			return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
       
  7330 	} catch(ex) {
       
  7331 	}
       
  7332 	try {
       
  7333 		var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
       
  7334 		s.print(content);
       
  7335 		s.close();
       
  7336 	} catch(ex) {
       
  7337 		return null;
       
  7338 	}
       
  7339 	return true;
       
  7340 }
       
  7341 
       
  7342 function javaLoadFile(filePath)
       
  7343 {
       
  7344 	try {
       
  7345 		if(document.applets["TiddlySaver"])
       
  7346 			return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
       
  7347 	} catch(ex) {
       
  7348 	}
       
  7349 	var content = [];
       
  7350 	try {
       
  7351 		var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
       
  7352 		var line;
       
  7353 		while((line = r.readLine()) != null)
       
  7354 			content.push(new String(line));
       
  7355 		r.close();
       
  7356 	} catch(ex) {
       
  7357 		return null;
       
  7358 	}
       
  7359 	return content.join("\n");
       
  7360 }
       
  7361 
       
  7362 //--
       
  7363 //-- Server adaptor for talking to static TiddlyWiki files
       
  7364 //--
       
  7365 
       
  7366 function FileAdaptor()
       
  7367 {
       
  7368 	this.host = null;
       
  7369 	this.store = null;
       
  7370 	return this;
       
  7371 }
       
  7372 
       
  7373 FileAdaptor.serverType = 'file';
       
  7374 FileAdaptor.serverLabel = 'TiddlyWiki';
       
  7375 
       
  7376 FileAdaptor.prototype.setContext = function(context,userParams,callback)
       
  7377 {
       
  7378 	if(!context) context = {};
       
  7379 	context.userParams = userParams;
       
  7380 	if(callback) context.callback = callback;
       
  7381 	context.adaptor = this;
       
  7382 	if(!context.host)
       
  7383 		context.host = this.host;
       
  7384 	context.host = FileAdaptor.fullHostName(context.host);
       
  7385 	if(!context.workspace)
       
  7386 		context.workspace = this.workspace;
       
  7387 	return context;
       
  7388 };
       
  7389 
       
  7390 FileAdaptor.fullHostName = function(host)
       
  7391 {
       
  7392 	if(!host)
       
  7393 		return '';
       
  7394 	if(!host.match(/:\/\//))
       
  7395 		host = 'http://' + host;
       
  7396 	return host;
       
  7397 };
       
  7398 
       
  7399 FileAdaptor.minHostName = function(host)
       
  7400 {
       
  7401 	return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
       
  7402 };
       
  7403 
       
  7404 // Open the specified host
       
  7405 FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
       
  7406 {
       
  7407 	this.host = host;
       
  7408 	context = this.setContext(context,userParams,callback);
       
  7409 	context.status = true;
       
  7410 	if(callback)
       
  7411 		window.setTimeout(function() {context.callback(context,userParams);},10);
       
  7412 	return true;
       
  7413 };
       
  7414 
       
  7415 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
       
  7416 {
       
  7417 	context.status = status;
       
  7418 	if(!status) {
       
  7419 		context.statusText = "Error reading file";
       
  7420 	} else {
       
  7421 		context.adaptor.store = new TiddlyWiki();
       
  7422 		if(!context.adaptor.store.importTiddlyWiki(responseText))
       
  7423 			context.statusText = config.messages.invalidFileError.format([url]);
       
  7424 	}
       
  7425 	context.complete(context,context.userParams);
       
  7426 };
       
  7427 
       
  7428 // Get the list of workspaces on a given server
       
  7429 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
       
  7430 {
       
  7431 	context = this.setContext(context,userParams,callback);
       
  7432 	context.workspaces = [{title:"(default)"}];
       
  7433 	context.status = true;
       
  7434 	if(callback)
       
  7435 		window.setTimeout(function() {callback(context,userParams);},10);
       
  7436 	return true;
       
  7437 };
       
  7438 
       
  7439 // Open the specified workspace
       
  7440 FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
       
  7441 {
       
  7442 	this.workspace = workspace;
       
  7443 	context = this.setContext(context,userParams,callback);
       
  7444 	context.status = true;
       
  7445 	if(callback)
       
  7446 		window.setTimeout(function() {callback(context,userParams);},10);
       
  7447 	return true;
       
  7448 };
       
  7449 
       
  7450 // Gets the list of tiddlers within a given workspace
       
  7451 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
       
  7452 {
       
  7453 	context = this.setContext(context,userParams,callback);
       
  7454 	if(!context.filter)
       
  7455 		context.filter = filter;
       
  7456 	context.complete = FileAdaptor.getTiddlerListComplete;
       
  7457 	if(this.store) {
       
  7458 		var ret = context.complete(context,context.userParams);
       
  7459 	} else {
       
  7460 		ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
       
  7461 		if(typeof ret != "string")
       
  7462 			ret = true;
       
  7463 	}
       
  7464 	return ret;
       
  7465 };
       
  7466 
       
  7467 FileAdaptor.getTiddlerListComplete = function(context,userParams)
       
  7468 {
       
  7469 	if(context.status) {
       
  7470 		if(context.filter) {
       
  7471 			context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
       
  7472 		} else {
       
  7473 			context.tiddlers = [];
       
  7474 			context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
       
  7475 		}
       
  7476 		for(var i=0; i<context.tiddlers.length; i++) {
       
  7477 			context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
       
  7478 			context.tiddlers[i].fields['server.host'] = FileAdaptor.minHostName(context.host);
       
  7479 			context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
       
  7480 		}
       
  7481 		context.status = true;
       
  7482 	}
       
  7483 	if(context.callback) {
       
  7484 		window.setTimeout(function() {context.callback(context,userParams);},10);
       
  7485 	}
       
  7486 	return true;
       
  7487 };
       
  7488 
       
  7489 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
       
  7490 {
       
  7491 	var info = {};
       
  7492 	info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
       
  7493 	return info;
       
  7494 };
       
  7495 
       
  7496 // Retrieve a tiddler from a given workspace on a given server
       
  7497 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
       
  7498 {
       
  7499 	context = this.setContext(context,userParams,callback);
       
  7500 	context.title = title;
       
  7501 	context.complete = FileAdaptor.getTiddlerComplete;
       
  7502 	return context.adaptor.store ?
       
  7503 		context.complete(context,context.userParams) :
       
  7504 		loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
       
  7505 };
       
  7506 
       
  7507 FileAdaptor.getTiddlerComplete = function(context,userParams)
       
  7508 {
       
  7509 	var t = context.adaptor.store.fetchTiddler(context.title);
       
  7510 	t.fields['server.type'] = FileAdaptor.serverType;
       
  7511 	t.fields['server.host'] = FileAdaptor.minHostName(context.host);
       
  7512 	t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
       
  7513 	context.tiddler = t;
       
  7514 	context.status = true;
       
  7515 	if(context.allowSynchronous) {
       
  7516 		context.isSynchronous = true;
       
  7517 		context.callback(context,userParams);
       
  7518 	} else {
       
  7519 		window.setTimeout(function() {context.callback(context,userParams);},10);
       
  7520 	}
       
  7521 	return true;
       
  7522 };
       
  7523 
       
  7524 FileAdaptor.prototype.close = function()
       
  7525 {
       
  7526 	delete this.store;
       
  7527 	this.store = null;
       
  7528 };
       
  7529 
       
  7530 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
       
  7531 
       
  7532 config.defaultAdaptor = FileAdaptor.serverType;
       
  7533 
       
  7534 //--
       
  7535 //-- Remote HTTP requests
       
  7536 //--
       
  7537 
       
  7538 function loadRemoteFile(url,callback,params)
       
  7539 {
       
  7540 	return doHttp("GET",url,null,null,null,null,callback,params,null);
       
  7541 }
       
  7542 
       
  7543 // HTTP status codes
       
  7544 var httpStatus = {
       
  7545 	OK: 200,
       
  7546 	ContentCreated: 201,
       
  7547 	NoContent: 204,
       
  7548 	MultiStatus: 207,
       
  7549 	Unauthorized: 401,
       
  7550 	Forbidden: 403,
       
  7551 	NotFound: 404,
       
  7552 	MethodNotAllowed: 405
       
  7553 };
       
  7554 
       
  7555 function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
       
  7556 {
       
  7557 	var x = getXMLHttpRequest();
       
  7558 	if(!x)
       
  7559 		return "Can't create XMLHttpRequest object";
       
  7560 	x.onreadystatechange = function() {
       
  7561 		try {
       
  7562 			var status = x.status;
       
  7563 		} catch(ex) {
       
  7564 			status = false;
       
  7565 		}
       
  7566 		if(x.readyState == 4 && callback && (status !== undefined)) {
       
  7567 			if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent, httpStatus.MultiStatus].contains(status))
       
  7568 				callback(true,params,x.responseText,url,x);
       
  7569 			else
       
  7570 				callback(false,params,null,url,x);
       
  7571 			x.onreadystatechange = function(){};
       
  7572 			x = null;
       
  7573 		}
       
  7574 	};
       
  7575 	if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
       
  7576 		window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
       
  7577 	try {
       
  7578 		if(!allowCache)
       
  7579 			url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
       
  7580 		x.open(type,url,true,username,password);
       
  7581 		if(data)
       
  7582 			x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
       
  7583 		if(x.overrideMimeType)
       
  7584 			x.setRequestHeader("Connection", "close");
       
  7585 		if(headers) {
       
  7586 			for(var n in headers)
       
  7587 				x.setRequestHeader(n,headers[n]);
       
  7588 		}
       
  7589 		x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
       
  7590 		x.send(data);
       
  7591 	} catch(ex) {
       
  7592 		return exceptionText(ex);
       
  7593 	}
       
  7594 	return x;
       
  7595 }
       
  7596 
       
  7597 function getXMLHttpRequest()
       
  7598 {
       
  7599 	try {
       
  7600 		var x = new XMLHttpRequest(); // Modern
       
  7601 	} catch(ex) {
       
  7602 		try {
       
  7603 			x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
       
  7604 		} catch (ex2) {
       
  7605 			return null;
       
  7606 		}
       
  7607 	}
       
  7608 	return x;
       
  7609 }
       
  7610 
       
  7611 //--
       
  7612 //-- TiddlyWiki-specific utility functions
       
  7613 //--
       
  7614 
       
  7615 formatVersion = function(v)
       
  7616 {
       
  7617 	v = v || version;
       
  7618 	return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
       
  7619 };
       
  7620 
       
  7621 compareVersions = function(v1,v2)
       
  7622 {
       
  7623 	var a = ["major","minor","revision"];
       
  7624 	for(var i = 0; i<a.length; i++) {
       
  7625 		var x1 = v1[a[i]] || 0;
       
  7626 		var x2 = v2[a[i]] || 0;
       
  7627 		if(x1<x2)
       
  7628 			return 1;
       
  7629 		if(x1>x2)
       
  7630 			return -1;
       
  7631 	}
       
  7632 	x1 = v1.beta || 9999;
       
  7633 	x2 = v2.beta || 9999;
       
  7634 	if(x1<x2)
       
  7635 		return 1;
       
  7636 	return x1 > x2 ? -1 : 0;
       
  7637 };
       
  7638 
       
  7639 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
       
  7640 {
       
  7641 	var btn = document.createElement("a");
       
  7642 	if(action) {
       
  7643 		btn.onclick = action;
       
  7644 		btn.setAttribute("href","javascript:;");
       
  7645 	}
       
  7646 	if(tooltip)
       
  7647 		btn.setAttribute("title",tooltip);
       
  7648 	if(text)
       
  7649 		btn.appendChild(document.createTextNode(text));
       
  7650 	btn.className = className ? className : "button";
       
  7651 	if(id)
       
  7652 		btn.id = id;
       
  7653 	if(attribs) {
       
  7654 		for(var n in attribs) {
       
  7655 			btn.setAttribute(n,attribs[n]);
       
  7656 		}
       
  7657 	}
       
  7658 	if(parent)
       
  7659 		parent.appendChild(btn);
       
  7660 	if(accessKey)
       
  7661 		btn.setAttribute("accessKey",accessKey);
       
  7662 	return btn;
       
  7663 }
       
  7664 
       
  7665 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
       
  7666 {
       
  7667 	var text = includeText ? title : null;
       
  7668 	var i = getTiddlyLinkInfo(title,className);
       
  7669 	var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
       
  7670 	btn.setAttribute("refresh","link");
       
  7671 	btn.setAttribute("tiddlyLink",title);
       
  7672 	if(noToggle)
       
  7673 		btn.setAttribute("noToggle","true");
       
  7674 	if(linkedFromTiddler) {
       
  7675 		var fields = linkedFromTiddler.getInheritedFields();
       
  7676 		if(fields)
       
  7677 			btn.setAttribute("tiddlyFields",fields);
       
  7678 	}
       
  7679 	return btn;
       
  7680 }
       
  7681 
       
  7682 function refreshTiddlyLink(e,title)
       
  7683 {
       
  7684 	var i = getTiddlyLinkInfo(title,e.className);
       
  7685 	e.className = i.classes;
       
  7686 	e.title = i.subTitle;
       
  7687 }
       
  7688 
       
  7689 function getTiddlyLinkInfo(title,currClasses)
       
  7690 {
       
  7691 	var classes = currClasses ? currClasses.split(" ") : [];
       
  7692 	classes.pushUnique("tiddlyLink");
       
  7693 	var tiddler = store.fetchTiddler(title);
       
  7694 	var subTitle;
       
  7695 	if(tiddler) {
       
  7696 		subTitle = tiddler.getSubtitle();
       
  7697 		classes.pushUnique("tiddlyLinkExisting");
       
  7698 		classes.remove("tiddlyLinkNonExisting");
       
  7699 		classes.remove("shadow");
       
  7700 	} else {
       
  7701 		classes.remove("tiddlyLinkExisting");
       
  7702 		classes.pushUnique("tiddlyLinkNonExisting");
       
  7703 		if(store.isShadowTiddler(title)) {
       
  7704 			subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
       
  7705 			classes.pushUnique("shadow");
       
  7706 		} else {
       
  7707 			subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
       
  7708 			classes.remove("shadow");
       
  7709 		}
       
  7710 	}
       
  7711 	if(typeof config.annotations[title]=="string")
       
  7712 		subTitle = config.annotations[title];
       
  7713 	return {classes: classes.join(" "),subTitle: subTitle};
       
  7714 }
       
  7715 
       
  7716 function createExternalLink(place,url)
       
  7717 {
       
  7718 	var link = document.createElement("a");
       
  7719 	link.className = "externalLink";
       
  7720 	link.href = url;
       
  7721 	link.title = config.messages.externalLinkTooltip.format([url]);
       
  7722 	if(config.options.chkOpenInNewWindow)
       
  7723 		link.target = "_blank";
       
  7724 	place.appendChild(link);
       
  7725 	return link;
       
  7726 }
       
  7727 
       
  7728 // Event handler for clicking on a tiddly link
       
  7729 function onClickTiddlerLink(ev)
       
  7730 {
       
  7731 	var e = ev ? ev : window.event;
       
  7732 	var target = resolveTarget(e);
       
  7733 	var link = target;
       
  7734 	var title = null;
       
  7735 	var fields = null;
       
  7736 	var noToggle = null;
       
  7737 	do {
       
  7738 		title = link.getAttribute("tiddlyLink");
       
  7739 		fields = link.getAttribute("tiddlyFields");
       
  7740 		noToggle = link.getAttribute("noToggle");
       
  7741 		link = link.parentNode;
       
  7742 	} while(title == null && link != null);
       
  7743 	if(!store.isShadowTiddler(title)) {
       
  7744 		var f = fields ? fields.decodeHashMap() : {};
       
  7745 		fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
       
  7746 	}
       
  7747 	if(title) {
       
  7748 		var toggling = e.metaKey || e.ctrlKey;
       
  7749 		if(config.options.chkToggleLinks)
       
  7750 			toggling = !toggling;
       
  7751 		if(noToggle)
       
  7752 			toggling = false;
       
  7753 		if(store.getTiddler(title))
       
  7754 			fields = null;
       
  7755 		story.displayTiddler(target,title,null,true,null,fields,toggling);
       
  7756 	}
       
  7757 	clearMessage();
       
  7758 	return false;
       
  7759 }
       
  7760 
       
  7761 // Create a button for a tag with a popup listing all the tiddlers that it tags
       
  7762 function createTagButton(place,tag,excludeTiddler,title,tooltip)
       
  7763 {
       
  7764 	var btn = createTiddlyButton(place,title||tag,(tooltip||config.views.wikified.tag.tooltip).format([tag]),onClickTag);
       
  7765 	btn.setAttribute("tag",tag);
       
  7766 	if(excludeTiddler)
       
  7767 		btn.setAttribute("tiddler",excludeTiddler);
       
  7768 	return btn;
       
  7769 }
       
  7770 
       
  7771 // Event handler for clicking on a tiddler tag
       
  7772 function onClickTag(ev)
       
  7773 {
       
  7774 	var e = ev ? ev : window.event;
       
  7775 	var popup = Popup.create(this);
       
  7776 	var tag = this.getAttribute("tag");
       
  7777 	var title = this.getAttribute("tiddler");
       
  7778 	if(popup && tag) {
       
  7779 		var tagged = store.getTaggedTiddlers(tag);
       
  7780 		var titles = [];
       
  7781 		var li,r;
       
  7782 		for(r=0;r<tagged.length;r++) {
       
  7783 			if(tagged[r].title != title)
       
  7784 				titles.push(tagged[r].title);
       
  7785 		}
       
  7786 		var lingo = config.views.wikified.tag;
       
  7787 		if(titles.length > 0) {
       
  7788 			var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
       
  7789 			openAll.setAttribute("tag",tag);
       
  7790 			createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
       
  7791 			for(r=0; r<titles.length; r++) {
       
  7792 				createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
       
  7793 			}
       
  7794 		} else {
       
  7795 			createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
       
  7796 		}
       
  7797 		createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
       
  7798 		var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
       
  7799 		createTiddlyText(h,lingo.openTag.format([tag]));
       
  7800 	}
       
  7801 	Popup.show();
       
  7802 	e.cancelBubble = true;
       
  7803 	if(e.stopPropagation) e.stopPropagation();
       
  7804 	return false;
       
  7805 }
       
  7806 
       
  7807 // Event handler for 'open all' on a tiddler popup
       
  7808 function onClickTagOpenAll(ev)
       
  7809 {
       
  7810 	var e = ev ? ev : window.event;
       
  7811 	var tag = this.getAttribute("tag");
       
  7812 	var tagged = store.getTaggedTiddlers(tag);
       
  7813 	story.displayTiddlers(this,tagged);
       
  7814 	return false;
       
  7815 }
       
  7816 
       
  7817 function onClickError(ev)
       
  7818 {
       
  7819 	var e = ev ? ev : window.event;
       
  7820 	var popup = Popup.create(this);
       
  7821 	var lines = this.getAttribute("errorText").split("\n");
       
  7822 	for(var t=0; t<lines.length; t++)
       
  7823 		createTiddlyElement(popup,"li",null,null,lines[t]);
       
  7824 	Popup.show();
       
  7825 	e.cancelBubble = true;
       
  7826 	if(e.stopPropagation) e.stopPropagation();
       
  7827 	return false;
       
  7828 }
       
  7829 
       
  7830 function createTiddlyDropDown(place,onchange,options,defaultValue)
       
  7831 {
       
  7832 	var sel = createTiddlyElement(place,"select");
       
  7833 	sel.onchange = onchange;
       
  7834 	for(var t=0; t<options.length; t++) {
       
  7835 		var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
       
  7836 		e.value = options[t].name;
       
  7837 		if(options[t].name == defaultValue)
       
  7838 			e.selected = true;
       
  7839 	}
       
  7840 	return sel;
       
  7841 }
       
  7842 
       
  7843 function createTiddlyPopup(place,caption,tooltip,tiddler)
       
  7844 {
       
  7845 	if(tiddler.text) {
       
  7846 		createTiddlyLink(place,caption,true);
       
  7847 		var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
       
  7848 		btn.tiddler = tiddler;
       
  7849 	} else {
       
  7850 		createTiddlyText(place,caption);
       
  7851 	}
       
  7852 }
       
  7853 
       
  7854 function onClickTiddlyPopup(ev)
       
  7855 {
       
  7856 	var e = ev ? ev : window.event;
       
  7857 	var tiddler = this.tiddler;
       
  7858 	if(tiddler.text) {
       
  7859 		var popup = Popup.create(this,"div","popupTiddler");
       
  7860 		wikify(tiddler.text,popup,null,tiddler);
       
  7861 		Popup.show();
       
  7862 	}
       
  7863 	if(e) e.cancelBubble = true;
       
  7864 	if(e && e.stopPropagation) e.stopPropagation();
       
  7865 	return false;
       
  7866 }
       
  7867 
       
  7868 function createTiddlyError(place,title,text)
       
  7869 {
       
  7870 	var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
       
  7871 	if(text) btn.setAttribute("errorText",text);
       
  7872 }
       
  7873 
       
  7874 function merge(dst,src,preserveExisting)
       
  7875 {
       
  7876 	for(p in src) {
       
  7877 		if(!preserveExisting || dst[p] === undefined)
       
  7878 			dst[p] = src[p];
       
  7879 	}
       
  7880 	return dst;
       
  7881 }
       
  7882 
       
  7883 // Returns a string containing the description of an exception, optionally prepended by a message
       
  7884 function exceptionText(e,message)
       
  7885 {
       
  7886 	var s = e.description ? e.description : e.toString();
       
  7887 	return message ? "%0:\n%1".format([message,s]) : s;
       
  7888 }
       
  7889 
       
  7890 // Displays an alert of an exception description with optional message
       
  7891 function showException(e,message)
       
  7892 {
       
  7893 	alert(exceptionText(e,message));
       
  7894 }
       
  7895 
       
  7896 function alertAndThrow(m)
       
  7897 {
       
  7898 	alert(m);
       
  7899 	throw(m);
       
  7900 }
       
  7901 
       
  7902 function glyph(name)
       
  7903 {
       
  7904 	var g = config.glyphs;
       
  7905 	var b = g.currBrowser;
       
  7906 	if(b == null) {
       
  7907 		b = 0;
       
  7908 		while(!g.browsers[b]() && b < g.browsers.length-1)
       
  7909 			b++;
       
  7910 		g.currBrowser = b;
       
  7911 	}
       
  7912 	if(!g.codes[name])
       
  7913 		return "";
       
  7914 	return g.codes[name][b];
       
  7915 }
       
  7916 
       
  7917 
       
  7918 //-
       
  7919 //- Animation engine
       
  7920 //-
       
  7921 
       
  7922 function Animator()
       
  7923 {
       
  7924 	this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
       
  7925 	this.timerID = 0; // ID of the timer used for animating
       
  7926 	this.animations = []; // List of animations in progress
       
  7927 	return this;
       
  7928 }
       
  7929 
       
  7930 // Start animation engine
       
  7931 Animator.prototype.startAnimating = function() //# Variable number of arguments
       
  7932 {
       
  7933 	for(var t=0; t<arguments.length; t++)
       
  7934 		this.animations.push(arguments[t]);
       
  7935 	if(this.running == 0) {
       
  7936 		var me = this;
       
  7937 		this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
       
  7938 	}
       
  7939 	this.running += arguments.length;
       
  7940 };
       
  7941 
       
  7942 // Perform an animation engine tick, calling each of the known animation modules
       
  7943 Animator.prototype.doAnimate = function(me)
       
  7944 {
       
  7945 	var a = 0;
       
  7946 	while(a < me.animations.length) {
       
  7947 		var animation = me.animations[a];
       
  7948 		if(animation.tick()) {
       
  7949 			a++;
       
  7950 		} else {
       
  7951 			me.animations.splice(a,1);
       
  7952 			if(--me.running == 0)
       
  7953 				window.clearInterval(me.timerID);
       
  7954 		}
       
  7955 	}
       
  7956 };
       
  7957 
       
  7958 Animator.slowInSlowOut = function(progress)
       
  7959 {
       
  7960 	return(1-((Math.cos(progress * Math.PI)+1)/2));
       
  7961 };
       
  7962 
       
  7963 //--
       
  7964 //-- Morpher animation
       
  7965 //--
       
  7966 
       
  7967 // Animate a set of properties of an element
       
  7968 function Morpher(element,duration,properties,callback)
       
  7969 {
       
  7970 	this.element = element;
       
  7971 	this.duration = duration;
       
  7972 	this.properties = properties;
       
  7973 	this.startTime = new Date();
       
  7974 	this.endTime = Number(this.startTime) + duration;
       
  7975 	this.callback = callback;
       
  7976 	this.tick();
       
  7977 	return this;
       
  7978 }
       
  7979 
       
  7980 Morpher.prototype.assignStyle = function(element,style,value)
       
  7981 {
       
  7982 	switch(style) {
       
  7983 		case "-tw-vertScroll":
       
  7984 			window.scrollTo(findScrollX(),value);
       
  7985 			break;
       
  7986 		case "-tw-horizScroll":
       
  7987 			window.scrollTo(value,findScrollY());
       
  7988 			break;
       
  7989 		default:
       
  7990 			element.style[style] = value;
       
  7991 			break;
       
  7992 	}
       
  7993 };
       
  7994 
       
  7995 Morpher.prototype.stop = function()
       
  7996 {
       
  7997 	for(var t=0; t<this.properties.length; t++) {
       
  7998 		var p = this.properties[t];
       
  7999 		if(p.atEnd !== undefined) {
       
  8000 			this.assignStyle(this.element,p.style,p.atEnd);
       
  8001 		}
       
  8002 	}
       
  8003 	if(this.callback)
       
  8004 		this.callback(this.element,this.properties);
       
  8005 };
       
  8006 
       
  8007 Morpher.prototype.tick = function()
       
  8008 {
       
  8009 	var currTime = Number(new Date());
       
  8010 	progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
       
  8011 	for(var t=0; t<this.properties.length; t++) {
       
  8012 		var p = this.properties[t];
       
  8013 		if(p.start !== undefined && p.end !== undefined) {
       
  8014 			var template = p.template ? p.template : "%0";
       
  8015 			switch(p.format) {
       
  8016 				case undefined:
       
  8017 				case "style":
       
  8018 					var v = p.start + (p.end-p.start) * progress;
       
  8019 					this.assignStyle(this.element,p.style,template.format([v]));
       
  8020 					break;
       
  8021 				case "color":
       
  8022 					break;
       
  8023 			}
       
  8024 		}
       
  8025 	}
       
  8026 	if(currTime >= this.endTime) {
       
  8027 		this.stop();
       
  8028 		return false;
       
  8029 	}
       
  8030 	return true;
       
  8031 };
       
  8032 
       
  8033 //--
       
  8034 //-- Zoomer animation
       
  8035 //--
       
  8036 
       
  8037 function Zoomer(text,startElement,targetElement,unused)
       
  8038 {
       
  8039 	var e = createTiddlyElement(document.body,"div",null,"zoomer");
       
  8040 	createTiddlyElement(e,"div",null,null,text);
       
  8041 	var winWidth = findWindowWidth();
       
  8042 	var winHeight = findWindowHeight();
       
  8043 	var p = [
       
  8044 		{style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
       
  8045 		{style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
       
  8046 		{style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
       
  8047 		{style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
       
  8048 		{style: 'fontSize', start: 8, end: 24, template: '%0pt'}
       
  8049 	];
       
  8050 	var c = function(element,properties) {removeNode(element);};
       
  8051 	return new Morpher(e,config.animDuration,p,c);
       
  8052 }
       
  8053 
       
  8054 //--
       
  8055 //-- Scroller animation
       
  8056 //--
       
  8057 
       
  8058 function Scroller(targetElement,unused)
       
  8059 {
       
  8060 	var p = [
       
  8061 		{style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
       
  8062 	];
       
  8063 	return new Morpher(targetElement,config.animDuration,p);
       
  8064 }
       
  8065 
       
  8066 //--
       
  8067 //-- Slider animation
       
  8068 //--
       
  8069 
       
  8070 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
       
  8071 function Slider(element,opening,unused,deleteMode)
       
  8072 {
       
  8073 	element.style.overflow = 'hidden';
       
  8074 	if(opening)
       
  8075 		element.style.height = '0px'; // Resolves a Firefox flashing bug
       
  8076 	element.style.display = 'block';
       
  8077 	var left = findPosX(element);
       
  8078 	var width = element.scrollWidth;
       
  8079 	var height = element.scrollHeight;
       
  8080 	var winWidth = findWindowWidth();
       
  8081 	var p = [];
       
  8082 	var c = null;
       
  8083 	if(opening) {
       
  8084 		p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
       
  8085 		p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
       
  8086 		p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
       
  8087 	} else {
       
  8088 		p.push({style: 'height', start: height, end: 0, template: '%0px'});
       
  8089 		p.push({style: 'display', atEnd: 'none'});
       
  8090 		p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
       
  8091 		p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
       
  8092 		switch(deleteMode) {
       
  8093 			case "all":
       
  8094 				c = function(element,properties) {removeNode(element);};
       
  8095 				break;
       
  8096 			case "children":
       
  8097 				c = function(element,properties) {removeChildren(element);};
       
  8098 				break;
       
  8099 		}
       
  8100 	}
       
  8101 	return new Morpher(element,config.animDuration,p,c);
       
  8102 }
       
  8103 
       
  8104 //--
       
  8105 //-- Popup menu
       
  8106 //--
       
  8107 
       
  8108 var Popup = {
       
  8109 	stack: [] // Array of objects with members root: and popup:
       
  8110 	};
       
  8111 
       
  8112 Popup.create = function(root,elem,theClass)
       
  8113 {
       
  8114 	var stackPosition = this.find(root,"popup");
       
  8115 	Popup.remove(stackPosition+1);
       
  8116 	var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
       
  8117 	popup.stackPosition = stackPosition;
       
  8118 	Popup.stack.push({root: root, popup: popup});
       
  8119 	return popup;
       
  8120 };
       
  8121 
       
  8122 Popup.onDocumentClick = function(ev)
       
  8123 {
       
  8124 	var e = ev ? ev : window.event;
       
  8125 	if(e.eventPhase == undefined)
       
  8126 		Popup.remove();
       
  8127 	else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
       
  8128 		Popup.remove();
       
  8129 	return true;
       
  8130 };
       
  8131 
       
  8132 Popup.show = function(valign,halign,offset)
       
  8133 {
       
  8134 	var curr = Popup.stack[Popup.stack.length-1];
       
  8135 	this.place(curr.root,curr.popup,valign,halign,offset);
       
  8136 	addClass(curr.root,"highlight");
       
  8137 	if(config.options.chkAnimate && anim && typeof Scroller == "function")
       
  8138 		anim.startAnimating(new Scroller(curr.popup));
       
  8139 	else
       
  8140 		window.scrollTo(0,ensureVisible(curr.popup));
       
  8141 };
       
  8142 
       
  8143 Popup.place = function(root,popup,valign,halign,offset)
       
  8144 {
       
  8145 	if(!offset)
       
  8146 		var offset = {x:0,y:0};
       
  8147 	if(popup.stackPosition >= 0 && !valign && !halign) {
       
  8148 		offset.x = offset.x + root.offsetWidth;
       
  8149 	} else {
       
  8150 		offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
       
  8151 		offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
       
  8152 	}
       
  8153 	var rootLeft = findPosX(root);
       
  8154 	var rootTop = findPosY(root);
       
  8155 	var popupLeft = rootLeft + offset.x;
       
  8156 	var popupTop = rootTop + offset.y;
       
  8157 	var winWidth = findWindowWidth();
       
  8158 	if(popup.offsetWidth > winWidth*0.75)
       
  8159 		popup.style.width = winWidth*0.75 + "px";
       
  8160 	var popupWidth = popup.offsetWidth;
       
  8161 	var scrollWidth = winWidth - document.body.offsetWidth;
       
  8162 	if(popupLeft + popupWidth > winWidth - scrollWidth - 1) {
       
  8163 		if(halign == 'right')
       
  8164 			popupLeft = popupLeft - root.offsetWidth - popupWidth;
       
  8165 		else
       
  8166 			popupLeft = winWidth - popupWidth - scrollWidth - 1;
       
  8167 	}
       
  8168 	popup.style.left = popupLeft + "px";
       
  8169 	popup.style.top = popupTop + "px";
       
  8170 	popup.style.display = "block";
       
  8171 };
       
  8172 
       
  8173 Popup.find = function(e)
       
  8174 {
       
  8175 	var pos = -1;
       
  8176 	for (var t=this.stack.length-1; t>=0; t--) {
       
  8177 		if(isDescendant(e,this.stack[t].popup))
       
  8178 			pos = i;
       
  8179 	}
       
  8180 	return pos;
       
  8181 };
       
  8182 
       
  8183 Popup.remove = function(pos)
       
  8184 {
       
  8185 	if(!pos) var pos = 0;
       
  8186 	if(Popup.stack.length > pos) {
       
  8187 		Popup.removeFrom(pos);
       
  8188 	}
       
  8189 };
       
  8190 
       
  8191 Popup.removeFrom = function(from)
       
  8192 {
       
  8193 	for(var t=Popup.stack.length-1; t>=from; t--) {
       
  8194 		var p = Popup.stack[t];
       
  8195 		removeClass(p.root,"highlight");
       
  8196 		removeNode(p.popup);
       
  8197 	}
       
  8198 	Popup.stack = Popup.stack.slice(0,from);
       
  8199 };
       
  8200 
       
  8201 //--
       
  8202 //-- Wizard support
       
  8203 //--
       
  8204 
       
  8205 function Wizard(elem)
       
  8206 {
       
  8207 	if(elem) {
       
  8208 		this.formElem = findRelated(elem,"wizard","className");
       
  8209 		this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
       
  8210 		this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
       
  8211 	} else {
       
  8212 		this.formElem = null;
       
  8213 		this.bodyElem = null;
       
  8214 		this.footElem = null;
       
  8215 	}
       
  8216 }
       
  8217 
       
  8218 Wizard.prototype.setValue = function(name,value)
       
  8219 {
       
  8220 	if(this.formElem)
       
  8221 		this.formElem[name] = value;
       
  8222 };
       
  8223 
       
  8224 Wizard.prototype.getValue = function(name)
       
  8225 {
       
  8226 	return this.formElem ? this.formElem[name] : null;
       
  8227 };
       
  8228 
       
  8229 Wizard.prototype.createWizard = function(place,title)
       
  8230 {
       
  8231 	this.formElem = createTiddlyElement(place,"form",null,"wizard");
       
  8232 	createTiddlyElement(this.formElem,"h1",null,null,title);
       
  8233 	this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
       
  8234 	this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
       
  8235 };
       
  8236 
       
  8237 Wizard.prototype.clear = function()
       
  8238 {
       
  8239 	removeChildren(this.bodyElem);
       
  8240 };
       
  8241 
       
  8242 Wizard.prototype.setButtons = function(buttonInfo,status)
       
  8243 {
       
  8244 	removeChildren(this.footElem);
       
  8245 	for(var t=0; t<buttonInfo.length; t++) {
       
  8246 		createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
       
  8247 		insertSpacer(this.footElem);
       
  8248 		}
       
  8249 	if(typeof status == "string") {
       
  8250 		createTiddlyElement(this.footElem,"span",null,"status",status);
       
  8251 	}
       
  8252 };
       
  8253 
       
  8254 Wizard.prototype.addStep = function(stepTitle,html)
       
  8255 {
       
  8256 	removeChildren(this.bodyElem);
       
  8257 	var w = createTiddlyElement(this.bodyElem,"div");
       
  8258 	createTiddlyElement(w,"h2",null,null,stepTitle);
       
  8259 	var step = createTiddlyElement(w,"div",null,"wizardStep");
       
  8260 	step.innerHTML = html;
       
  8261 	applyHtmlMacros(step,tiddler);
       
  8262 };
       
  8263 
       
  8264 Wizard.prototype.getElement = function(name)
       
  8265 {
       
  8266 	return this.formElem.elements[name];
       
  8267 };
       
  8268 
       
  8269 //--
       
  8270 //-- ListView gadget
       
  8271 //--
       
  8272 
       
  8273 var ListView = {};
       
  8274 
       
  8275 // Create a listview
       
  8276 ListView.create = function(place,listObject,listTemplate,callback,className)
       
  8277 {
       
  8278 	var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
       
  8279 	var thead = createTiddlyElement(table,"thead");
       
  8280 	var r = createTiddlyElement(thead,"tr");
       
  8281 	for(var t=0; t<listTemplate.columns.length; t++) {
       
  8282 		var columnTemplate = listTemplate.columns[t];
       
  8283 		var c = createTiddlyElement(r,"th");
       
  8284 		var colType = ListView.columnTypes[columnTemplate.type];
       
  8285 		if(colType && colType.createHeader)
       
  8286 			colType.createHeader(c,columnTemplate,t);
       
  8287 	}
       
  8288 	var tbody = createTiddlyElement(table,"tbody");
       
  8289 	for(var rc=0; rc<listObject.length; rc++) {
       
  8290 		rowObject = listObject[rc];
       
  8291 		r = createTiddlyElement(tbody,"tr");
       
  8292 		for(c=0; c<listTemplate.rowClasses.length; c++) {
       
  8293 			if(rowObject[listTemplate.rowClasses[c].field])
       
  8294 				addClass(r,listTemplate.rowClasses[c].className);
       
  8295 		}
       
  8296 		rowObject.rowElement = r;
       
  8297 		rowObject.colElements = {};
       
  8298 		for(var cc=0; cc<listTemplate.columns.length; cc++) {
       
  8299 			c = createTiddlyElement(r,"td");
       
  8300 			columnTemplate = listTemplate.columns[cc];
       
  8301 			var field = columnTemplate.field;
       
  8302 			colType = ListView.columnTypes[columnTemplate.type];
       
  8303 			if(colType && colType.createItem)
       
  8304 				colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
       
  8305 			rowObject.colElements[field] = c;
       
  8306 		}
       
  8307 	}
       
  8308 	if(callback && listTemplate.actions)
       
  8309 		createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
       
  8310 	if(callback && listTemplate.buttons) {
       
  8311 		for(t=0; t<listTemplate.buttons.length; t++) {
       
  8312 			var a = listTemplate.buttons[t];
       
  8313 			if(a && a.name != "")
       
  8314 				createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
       
  8315 		}
       
  8316 	}
       
  8317 	return table;
       
  8318 };
       
  8319 
       
  8320 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
       
  8321 {
       
  8322 	return function(e) {
       
  8323 		var view = findRelated(this,"TABLE",null,"previousSibling");
       
  8324 		var tiddlers = [];
       
  8325 		ListView.forEachSelector(view,function(e,rowName) {
       
  8326 					if(e.checked)
       
  8327 						tiddlers.push(rowName);
       
  8328 					});
       
  8329 		if(tiddlers.length == 0 && !allowEmptySelection) {
       
  8330 			alert(config.messages.nothingSelected);
       
  8331 		} else {
       
  8332 			if(this.nodeName.toLowerCase() == "select") {
       
  8333 				callback(view,this.value,tiddlers);
       
  8334 				this.selectedIndex = 0;
       
  8335 			} else {
       
  8336 				callback(view,name,tiddlers);
       
  8337 			}
       
  8338 		}
       
  8339 	};
       
  8340 };
       
  8341 
       
  8342 // Invoke a callback for each selector checkbox in the listview
       
  8343 ListView.forEachSelector = function(view,callback)
       
  8344 {
       
  8345 	var checkboxes = view.getElementsByTagName("input");
       
  8346 	var hadOne = false;
       
  8347 	for(var t=0; t<checkboxes.length; t++) {
       
  8348 		var cb = checkboxes[t];
       
  8349 		if(cb.getAttribute("type") == "checkbox") {
       
  8350 			var rn = cb.getAttribute("rowName");
       
  8351 			if(rn) {
       
  8352 				callback(cb,rn);
       
  8353 				hadOne = true;
       
  8354 			}
       
  8355 		}
       
  8356 	}
       
  8357 	return hadOne;
       
  8358 };
       
  8359 
       
  8360 ListView.getSelectedRows = function(view)
       
  8361 {
       
  8362 	var rowNames = [];
       
  8363 	ListView.forEachSelector(view,function(e,rowName) {
       
  8364 				if(e.checked)
       
  8365 					rowNames.push(rowName);
       
  8366 				});
       
  8367 	return rowNames;
       
  8368 };
       
  8369 
       
  8370 ListView.columnTypes = {};
       
  8371 
       
  8372 ListView.columnTypes.String = {
       
  8373 	createHeader: function(place,columnTemplate,col)
       
  8374 		{
       
  8375 			createTiddlyText(place,columnTemplate.title);
       
  8376 		},
       
  8377 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8378 		{
       
  8379 			var v = listObject[field];
       
  8380 			if(v != undefined)
       
  8381 				createTiddlyText(place,v);
       
  8382 		}
       
  8383 };
       
  8384 
       
  8385 ListView.columnTypes.WikiText = {
       
  8386 	createHeader: ListView.columnTypes.String.createHeader,
       
  8387 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8388 		{
       
  8389 			var v = listObject[field];
       
  8390 			if(v != undefined)
       
  8391 				wikify(v,place,null,null);
       
  8392 		}
       
  8393 };
       
  8394 
       
  8395 ListView.columnTypes.Tiddler = {
       
  8396 	createHeader: ListView.columnTypes.String.createHeader,
       
  8397 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8398 		{
       
  8399 			var v = listObject[field];
       
  8400 			if(v != undefined && v.title)
       
  8401 				createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
       
  8402 		}
       
  8403 };
       
  8404 
       
  8405 ListView.columnTypes.Size = {
       
  8406 	createHeader: ListView.columnTypes.String.createHeader,
       
  8407 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8408 		{
       
  8409 			var v = listObject[field];
       
  8410 			if(v != undefined) {
       
  8411 				var t = 0;
       
  8412 				while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
       
  8413 					t++;
       
  8414 				createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
       
  8415 			}
       
  8416 		}
       
  8417 };
       
  8418 
       
  8419 ListView.columnTypes.Link = {
       
  8420 	createHeader: ListView.columnTypes.String.createHeader,
       
  8421 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8422 		{
       
  8423 			var v = listObject[field];
       
  8424 			var c = columnTemplate.text;
       
  8425 			if(v != undefined)
       
  8426 				createTiddlyText(createExternalLink(place,v),c ? c : v);
       
  8427 		}
       
  8428 };
       
  8429 
       
  8430 ListView.columnTypes.Date = {
       
  8431 	createHeader: ListView.columnTypes.String.createHeader,
       
  8432 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8433 		{
       
  8434 			var v = listObject[field];
       
  8435 			if(v != undefined)
       
  8436 				createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
       
  8437 		}
       
  8438 };
       
  8439 
       
  8440 ListView.columnTypes.StringList = {
       
  8441 	createHeader: ListView.columnTypes.String.createHeader,
       
  8442 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8443 		{
       
  8444 			var v = listObject[field];
       
  8445 			if(v != undefined) {
       
  8446 				for(var t=0; t<v.length; t++) {
       
  8447 					createTiddlyText(place,v[t]);
       
  8448 					createTiddlyElement(place,"br");
       
  8449 				}
       
  8450 			}
       
  8451 		}
       
  8452 };
       
  8453 
       
  8454 ListView.columnTypes.Selector = {
       
  8455 	createHeader: function(place,columnTemplate,col)
       
  8456 		{
       
  8457 			createTiddlyCheckbox(place,null,false,this.onHeaderChange);
       
  8458 		},
       
  8459 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8460 		{
       
  8461 			var e = createTiddlyCheckbox(place,null,listObject[field],null);
       
  8462 			e.setAttribute("rowName",listObject[columnTemplate.rowName]);
       
  8463 		},
       
  8464 	onHeaderChange: function(e)
       
  8465 		{
       
  8466 			var state = this.checked;
       
  8467 			var view = findRelated(this,"TABLE");
       
  8468 			if(!view)
       
  8469 				return;
       
  8470 			ListView.forEachSelector(view,function(e,rowName) {
       
  8471 								e.checked = state;
       
  8472 							});
       
  8473 		}
       
  8474 };
       
  8475 
       
  8476 ListView.columnTypes.Tags = {
       
  8477 	createHeader: ListView.columnTypes.String.createHeader,
       
  8478 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8479 		{
       
  8480 			var tags = listObject[field];
       
  8481 			createTiddlyText(place,String.encodeTiddlyLinkList(tags));
       
  8482 		}
       
  8483 };
       
  8484 
       
  8485 ListView.columnTypes.Boolean = {
       
  8486 	createHeader: ListView.columnTypes.String.createHeader,
       
  8487 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8488 		{
       
  8489 			if(listObject[field] == true)
       
  8490 				createTiddlyText(place,columnTemplate.trueText);
       
  8491 			if(listObject[field] == false)
       
  8492 				createTiddlyText(place,columnTemplate.falseText);
       
  8493 		}
       
  8494 };
       
  8495 
       
  8496 ListView.columnTypes.TagCheckbox = {
       
  8497 	createHeader: ListView.columnTypes.String.createHeader,
       
  8498 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8499 		{
       
  8500 			var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
       
  8501 			e.setAttribute("tiddler",listObject.title);
       
  8502 			e.setAttribute("tag",columnTemplate.tag);
       
  8503 		},
       
  8504 	onChange : function(e)
       
  8505 		{
       
  8506 			var tag = this.getAttribute("tag");
       
  8507 			var tiddler = this.getAttribute("tiddler");
       
  8508 			store.setTiddlerTag(tiddler,this.checked,tag);
       
  8509 		}
       
  8510 };
       
  8511 
       
  8512 ListView.columnTypes.TiddlerLink = {
       
  8513 	createHeader: ListView.columnTypes.String.createHeader,
       
  8514 	createItem: function(place,listObject,field,columnTemplate,col,row)
       
  8515 		{
       
  8516 			var v = listObject[field];
       
  8517 			if(v != undefined) {
       
  8518 				var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
       
  8519 				createTiddlyText(link,listObject[field]);
       
  8520 			}
       
  8521 		}
       
  8522 };
       
  8523 
       
  8524 //--
       
  8525 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
       
  8526 //--
       
  8527 
       
  8528 // Clamp a number to a range
       
  8529 Number.prototype.clamp = function(min,max)
       
  8530 {
       
  8531 	var c = this;
       
  8532 	if(c < min)
       
  8533 		c = min;
       
  8534 	if(c > max)
       
  8535 		c = max;
       
  8536 	return c;
       
  8537 };
       
  8538 
       
  8539 // Add indexOf function if browser does not support it
       
  8540 if(!Array.indexOf) {
       
  8541 Array.prototype.indexOf = function(item,from)
       
  8542 {
       
  8543 	if(!from)
       
  8544 		from = 0;
       
  8545 	for(var i=from; i<this.length; i++) {
       
  8546 		if(this[i] === item)
       
  8547 			return i;
       
  8548 	}
       
  8549 	return -1;
       
  8550 };}
       
  8551 
       
  8552 // Find an entry in a given field of the members of an array
       
  8553 Array.prototype.findByField = function(field,value)
       
  8554 {
       
  8555 	for(var t=0; t<this.length; t++) {
       
  8556 		if(this[t][field] == value)
       
  8557 			return t;
       
  8558 	}
       
  8559 	return null;
       
  8560 };
       
  8561 
       
  8562 // Return whether an entry exists in an array
       
  8563 Array.prototype.contains = function(item)
       
  8564 {
       
  8565 	return this.indexOf(item) != -1;
       
  8566 };
       
  8567 
       
  8568 // Adds, removes or toggles a particular value within an array
       
  8569 //  value - value to add
       
  8570 //  mode - +1 to add value, -1 to remove value, 0 to toggle it
       
  8571 Array.prototype.setItem = function(value,mode)
       
  8572 {
       
  8573 	var p = this.indexOf(value);
       
  8574 	if(mode == 0)
       
  8575 		mode = (p == -1) ? +1 : -1;
       
  8576 	if(mode == +1) {
       
  8577 		if(p == -1)
       
  8578 			this.push(value);
       
  8579 	} else if(mode == -1) {
       
  8580 		if(p != -1)
       
  8581 			this.splice(p,1);
       
  8582 	}
       
  8583 };
       
  8584 
       
  8585 // Return whether one of a list of values exists in an array
       
  8586 Array.prototype.containsAny = function(items)
       
  8587 {
       
  8588 	for(var i=0; i<items.length; i++) {
       
  8589 		if(this.indexOf(items[i]) != -1)
       
  8590 			return true;
       
  8591 	}
       
  8592 	return false;
       
  8593 };
       
  8594 
       
  8595 // Return whether all of a list of values exists in an array
       
  8596 Array.prototype.containsAll = function(items)
       
  8597 {
       
  8598 	for(var i = 0; i<items.length; i++) {
       
  8599 		if(this.indexOf(items[i]) == -1)
       
  8600 			return false;
       
  8601 	}
       
  8602 	return true;
       
  8603 };
       
  8604 
       
  8605 // Push a new value into an array only if it is not already present in the array. If the optional unique parameter is false, it reverts to a normal push
       
  8606 Array.prototype.pushUnique = function(item,unique)
       
  8607 {
       
  8608 	if(unique === false) {
       
  8609 		this.push(item);
       
  8610 	} else {
       
  8611 		if(this.indexOf(item) == -1)
       
  8612 			this.push(item);
       
  8613 	}
       
  8614 };
       
  8615 
       
  8616 Array.prototype.remove = function(item)
       
  8617 {
       
  8618 	var p = this.indexOf(item);
       
  8619 	if(p != -1)
       
  8620 		this.splice(p,1);
       
  8621 };
       
  8622 
       
  8623 if(!Array.prototype.map) {
       
  8624 Array.prototype.map = function(fn,thisObj)
       
  8625 {
       
  8626 	var scope = thisObj || window;
       
  8627 	var a = [];
       
  8628 	for(var i=0, j=this.length; i < j; ++i) {
       
  8629 		a.push(fn.call(scope,this[i],i,this));
       
  8630 	}
       
  8631 	return a;
       
  8632 };}
       
  8633 
       
  8634 // Get characters from the right end of a string
       
  8635 String.prototype.right = function(n)
       
  8636 {
       
  8637 	return n < this.length ? this.slice(this.length-n) : this;
       
  8638 };
       
  8639 
       
  8640 // Trim whitespace from both ends of a string
       
  8641 String.prototype.trim = function()
       
  8642 {
       
  8643 	return this.replace(/^\s*|\s*$/g,"");
       
  8644 };
       
  8645 
       
  8646 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
       
  8647 String.prototype.unDash = function()
       
  8648 {
       
  8649 	var s = this.split("-");
       
  8650 	if(s.length > 1) {
       
  8651 		for(var t=1; t<s.length; t++)
       
  8652 			s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
       
  8653 	}
       
  8654 	return s.join("");
       
  8655 };
       
  8656 
       
  8657 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
       
  8658 String.prototype.format = function(substrings)
       
  8659 {
       
  8660 	var subRegExp = /(?:%(\d+))/mg;
       
  8661 	var currPos = 0;
       
  8662 	var r = [];
       
  8663 	do {
       
  8664 		var match = subRegExp.exec(this);
       
  8665 		if(match && match[1]) {
       
  8666 			if(match.index > currPos)
       
  8667 				r.push(this.substring(currPos,match.index));
       
  8668 			r.push(substrings[parseInt(match[1])]);
       
  8669 			currPos = subRegExp.lastIndex;
       
  8670 		}
       
  8671 	} while(match);
       
  8672 	if(currPos < this.length)
       
  8673 		r.push(this.substring(currPos,this.length));
       
  8674 	return r.join("");
       
  8675 };
       
  8676 
       
  8677 // Escape any special RegExp characters with that character preceded by a backslash
       
  8678 String.prototype.escapeRegExp = function()
       
  8679 {
       
  8680 	var s = "\\^$*+?()=!|,{}[].";
       
  8681 	var c = this;
       
  8682 	for(var t=0; t<s.length; t++)
       
  8683 		c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
       
  8684 	return c;
       
  8685 };
       
  8686 
       
  8687 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
       
  8688 String.prototype.escapeLineBreaks = function()
       
  8689 {
       
  8690 	return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
       
  8691 };
       
  8692 
       
  8693 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
       
  8694 String.prototype.unescapeLineBreaks = function()
       
  8695 {
       
  8696 	return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
       
  8697 };
       
  8698 
       
  8699 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
       
  8700 String.prototype.htmlEncode = function()
       
  8701 {
       
  8702 	return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
       
  8703 };
       
  8704 
       
  8705 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
       
  8706 String.prototype.htmlDecode = function()
       
  8707 {
       
  8708 	return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
       
  8709 };
       
  8710 
       
  8711 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
       
  8712 String.prototype.toJSONString = function()
       
  8713 {
       
  8714 	var m = {
       
  8715 		'\b': '\\b',
       
  8716 		'\f': '\\f',
       
  8717 		'\n': '\\n',
       
  8718 		'\r': '\\r',
       
  8719 		'\t': '\\t',
       
  8720 		'"' : '\\"',
       
  8721 		'\\': '\\\\'
       
  8722 		};
       
  8723 	var replaceFn = function(a,b) {
       
  8724 		var c = m[b];
       
  8725 		if(c)
       
  8726 			return c;
       
  8727 		c = b.charCodeAt();
       
  8728 		return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
       
  8729 		};
       
  8730 	if(/["\\\x00-\x1f]/.test(this))
       
  8731 		return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
       
  8732 	return '"' + this + '"';
       
  8733 };
       
  8734 
       
  8735 // Parse a space-separated string of name:value parameters
       
  8736 // The result is an array of objects:
       
  8737 //   result[0] = object with a member for each parameter name, value of that member being an array of values
       
  8738 //   result[1..n] = one object for each parameter, with 'name' and 'value' members
       
  8739 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
       
  8740 {
       
  8741 	var parseToken = function(match,p) {
       
  8742 		var n;
       
  8743 		if(match[p]) // Double quoted
       
  8744 			n = match[p];
       
  8745 		else if(match[p+1]) // Single quoted
       
  8746 			n = match[p+1];
       
  8747 		else if(match[p+2]) // Double-square-bracket quoted
       
  8748 			n = match[p+2];
       
  8749 		else if(match[p+3]) // Double-brace quoted
       
  8750 			try {
       
  8751 				n = match[p+3];
       
  8752 				if(allowEval)
       
  8753 					n = window.eval(n);
       
  8754 			} catch(ex) {
       
  8755 				throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
       
  8756 			}
       
  8757 		else if(match[p+4]) // Unquoted
       
  8758 			n = match[p+4];
       
  8759 		else if(match[p+5]) // empty quote
       
  8760 			n = "";
       
  8761 		return n;
       
  8762 	};
       
  8763 	var r = [{}];
       
  8764 	var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
       
  8765 	var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
       
  8766 	var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
       
  8767 	var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
       
  8768 	var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
       
  8769 	var emptyQuote = "((?:\"\")|(?:''))";
       
  8770 	var skipSpace = "(?:\\s*)";
       
  8771 	var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
       
  8772 	var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
       
  8773 	var params = [];
       
  8774 	do {
       
  8775 		var match = re.exec(this);
       
  8776 		if(match) {
       
  8777 			var n = parseToken(match,1);
       
  8778 			if(noNames) {
       
  8779 				r.push({name:"",value:n});
       
  8780 			} else {
       
  8781 				var v = parseToken(match,8);
       
  8782 				if(v == null && defaultName) {
       
  8783 					v = n;
       
  8784 					n = defaultName;
       
  8785 				} else if(v == null && defaultValue) {
       
  8786 					v = defaultValue;
       
  8787 				}
       
  8788 				r.push({name:n,value:v});
       
  8789 				if(cascadeDefaults) {
       
  8790 					defaultName = n;
       
  8791 					defaultValue = v;
       
  8792 				}
       
  8793 			}
       
  8794 		}
       
  8795 	} while(match);
       
  8796 	// Summarise parameters into first element
       
  8797 	for(var t=1; t<r.length; t++) {
       
  8798 		if(r[0][r[t].name])
       
  8799 			r[0][r[t].name].push(r[t].value);
       
  8800 		else
       
  8801 			r[0][r[t].name] = [r[t].value];
       
  8802 	}
       
  8803 	return r;
       
  8804 };
       
  8805 
       
  8806 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
       
  8807 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
       
  8808 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
       
  8809 String.prototype.readMacroParams = function()
       
  8810 {
       
  8811 	var p = this.parseParams("list",null,true,true);
       
  8812 	var n = [];
       
  8813 	for(var t=1; t<p.length; t++)
       
  8814 		n.push(p[t].value);
       
  8815 	return n;
       
  8816 };
       
  8817 
       
  8818 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
       
  8819 String.prototype.readBracketedList = function(unique)
       
  8820 {
       
  8821 	var p = this.parseParams("list",null,false,true);
       
  8822 	var n = [];
       
  8823 	for(var t=1; t<p.length; t++) {
       
  8824 		if(p[t].value)
       
  8825 			n.pushUnique(p[t].value,unique);
       
  8826 	}
       
  8827 	return n;
       
  8828 };
       
  8829 
       
  8830 // Returns array with start and end index of chunk between given start and end marker, or undefined.
       
  8831 String.prototype.getChunkRange = function(start,end)
       
  8832 {
       
  8833 	var s = this.indexOf(start);
       
  8834 	if(s != -1) {
       
  8835 		s += start.length;
       
  8836 		var e = this.indexOf(end,s);
       
  8837 		if(e != -1)
       
  8838 			return [s,e];
       
  8839 	}
       
  8840 };
       
  8841 
       
  8842 // Replace a chunk of a string given start and end markers
       
  8843 String.prototype.replaceChunk = function(start,end,sub)
       
  8844 {
       
  8845 	var r = this.getChunkRange(start,end);
       
  8846 	return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
       
  8847 };
       
  8848 
       
  8849 // Returns a chunk of a string between start and end markers, or undefined
       
  8850 String.prototype.getChunk = function(start,end)
       
  8851 {
       
  8852 	var r = this.getChunkRange(start,end);
       
  8853 	if(r)
       
  8854 		return this.substring(r[0],r[1]);
       
  8855 };
       
  8856 
       
  8857 
       
  8858 // Static method to bracket a string with double square brackets if it contains a space
       
  8859 String.encodeTiddlyLink = function(title)
       
  8860 {
       
  8861 	return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
       
  8862 };
       
  8863 
       
  8864 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
       
  8865 String.encodeTiddlyLinkList = function(list)
       
  8866 {
       
  8867 	if(list) {
       
  8868 		var results = [];
       
  8869 		for(var t=0; t<list.length; t++)
       
  8870 			results.push(String.encodeTiddlyLink(list[t]));
       
  8871 		return results.join(" ");
       
  8872 	} else {
       
  8873 		return "";
       
  8874 	}
       
  8875 };
       
  8876 
       
  8877 // Convert a string as a sequence of name:"value" pairs into a hashmap
       
  8878 String.prototype.decodeHashMap = function()
       
  8879 {
       
  8880 	var fields = this.parseParams("anon","",false);
       
  8881 	var r = {};
       
  8882 	for(var t=1; t<fields.length; t++)
       
  8883 		r[fields[t].name] = fields[t].value;
       
  8884 	return r;
       
  8885 };
       
  8886 
       
  8887 // Static method to encode a hashmap into a name:"value"... string
       
  8888 String.encodeHashMap = function(hashmap)
       
  8889 {
       
  8890 	var r = [];
       
  8891 	for(var t in hashmap)
       
  8892 		r.push(t + ':"' + hashmap[t] + '"');
       
  8893 	return r.join(" ");
       
  8894 };
       
  8895 
       
  8896 // Static method to left-pad a string with 0s to a certain width
       
  8897 String.zeroPad = function(n,d)
       
  8898 {
       
  8899 	var s = n.toString();
       
  8900 	if(s.length < d)
       
  8901 		s = "000000000000000000000000000".substr(0,d-s.length) + s;
       
  8902 	return s;
       
  8903 };
       
  8904 
       
  8905 String.prototype.startsWith = function(prefix)
       
  8906 {
       
  8907 	return !prefix || this.substring(0,prefix.length) == prefix;
       
  8908 };
       
  8909 
       
  8910 // Returns the first value of the given named parameter.
       
  8911 function getParam(params,name,defaultValue)
       
  8912 {
       
  8913 	if(!params)
       
  8914 		return defaultValue;
       
  8915 	var p = params[0][name];
       
  8916 	return p ? p[0] : defaultValue;
       
  8917 }
       
  8918 
       
  8919 // Returns the first value of the given boolean named parameter.
       
  8920 function getFlag(params,name,defaultValue)
       
  8921 {
       
  8922 	return !!getParam(params,name,defaultValue);
       
  8923 }
       
  8924 
       
  8925 // Substitute date components into a string
       
  8926 Date.prototype.formatString = function(template)
       
  8927 {
       
  8928 	var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
       
  8929 	t = t.replace(/hh12/g,this.getHours12());
       
  8930 	t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
       
  8931 	t = t.replace(/hh/g,this.getHours());
       
  8932 	t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
       
  8933 	t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
       
  8934 	t = t.replace(/mm/g,this.getMinutes());
       
  8935 	t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
       
  8936 	t = t.replace(/ss/g,this.getSeconds());
       
  8937 	t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
       
  8938 	t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
       
  8939 	t = t.replace(/wYYYY/g,this.getYearForWeekNo());
       
  8940 	t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
       
  8941 	t = t.replace(/YYYY/g,this.getFullYear());
       
  8942 	t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
       
  8943 	t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
       
  8944 	t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
       
  8945 	t = t.replace(/MM/g,this.getMonth()+1);
       
  8946 	t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
       
  8947 	t = t.replace(/WW/g,this.getWeek());
       
  8948 	t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
       
  8949 	t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
       
  8950 	t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
       
  8951 	t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
       
  8952 	t = t.replace(/DD/g,this.getDate());
       
  8953 	var tz = this.getTimezoneOffset();
       
  8954 	var atz = Math.abs(tz);
       
  8955 	t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60),2) + ':' + String.zeroPad(atz % 60,2));
       
  8956 	return t;
       
  8957 };
       
  8958 
       
  8959 Date.prototype.getWeek = function()
       
  8960 {
       
  8961 	var dt = new Date(this.getTime());
       
  8962 	var d = dt.getDay();
       
  8963 	if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
       
  8964 	dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
       
  8965 	var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
       
  8966 	return Math.floor(n/7)+1;
       
  8967 };
       
  8968 
       
  8969 Date.prototype.getYearForWeekNo = function()
       
  8970 {
       
  8971 	var dt = new Date(this.getTime());
       
  8972 	var d = dt.getDay();
       
  8973 	if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
       
  8974 	dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
       
  8975 	return dt.getFullYear();
       
  8976 };
       
  8977 
       
  8978 Date.prototype.getHours12 = function()
       
  8979 {
       
  8980 	var h = this.getHours();
       
  8981 	return h > 12 ? h-12 : ( h > 0 ? h : 12 );
       
  8982 };
       
  8983 
       
  8984 Date.prototype.getAmPm = function()
       
  8985 {
       
  8986 	return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
       
  8987 };
       
  8988 
       
  8989 Date.prototype.daySuffix = function()
       
  8990 {
       
  8991 	return config.messages.dates.daySuffixes[this.getDate()-1];
       
  8992 };
       
  8993 
       
  8994 // Convert a date to local YYYYMMDDHHMM string format
       
  8995 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
       
  8996 {
       
  8997 	return this.getFullYear() + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
       
  8998 };
       
  8999 
       
  9000 // Convert a date to UTC YYYYMMDDHHMM string format
       
  9001 Date.prototype.convertToYYYYMMDDHHMM = function()
       
  9002 {
       
  9003 	return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
       
  9004 };
       
  9005 
       
  9006 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
       
  9007 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
       
  9008 {
       
  9009 	return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + "." + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2) + String.zeroPad(this.getUTCSeconds(),2) + String.zeroPad(this.getUTCMilliseconds(),4);
       
  9010 };
       
  9011 
       
  9012 // Static method to create a date from a UTC YYYYMMDDHHMM format string
       
  9013 Date.convertFromYYYYMMDDHHMM = function(d)
       
  9014 {
       
  9015 	var hh = d.substr(8,2) || "00";
       
  9016 	var mm = d.substr(10,2) || "00";
       
  9017 	return new Date(Date.UTC(parseInt(d.substr(0,4),10),
       
  9018 			parseInt(d.substr(4,2),10)-1,
       
  9019 			parseInt(d.substr(6,2),10),
       
  9020 			parseInt(hh,10),
       
  9021 			parseInt(mm,10),0,0));
       
  9022 };
       
  9023 
       
  9024 
       
  9025 //--
       
  9026 //-- Crypto functions and associated conversion routines
       
  9027 //--
       
  9028 
       
  9029 // Crypto 'namespace'
       
  9030 function Crypto() {}
       
  9031 
       
  9032 // Convert a string to an array of big-endian 32-bit words
       
  9033 Crypto.strToBe32s = function(str)
       
  9034 {
       
  9035 	var be=Array();
       
  9036 	var len=Math.floor(str.length/4);
       
  9037 	var i, j;
       
  9038 	for(i=0, j=0; i<len; i++, j+=4) {
       
  9039 		be[i]=((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
       
  9040 	}
       
  9041 	while(j<str.length) {
       
  9042 		be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
       
  9043 		j++;
       
  9044 	}
       
  9045 	return be;
       
  9046 };
       
  9047 
       
  9048 // Convert an array of big-endian 32-bit words to a string
       
  9049 Crypto.be32sToStr = function(be)
       
  9050 {
       
  9051 	var str='';
       
  9052 	for(var i=0;i<be.length*32;i+=8)
       
  9053 		str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
       
  9054 	return str;
       
  9055 };
       
  9056 
       
  9057 // Convert an array of big-endian 32-bit words to a hex string
       
  9058 Crypto.be32sToHex = function(be)
       
  9059 {
       
  9060 	var hex='0123456789ABCDEF';
       
  9061 	var str='';
       
  9062 	for(var i=0;i<be.length*4;i++)
       
  9063 		str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
       
  9064 	return str;
       
  9065 };
       
  9066 
       
  9067 // Return, in hex, the SHA-1 hash of a string
       
  9068 Crypto.hexSha1Str = function(str)
       
  9069 {
       
  9070 	return Crypto.be32sToHex(Crypto.sha1Str(str));
       
  9071 };
       
  9072 
       
  9073 // Return the SHA-1 hash of a string
       
  9074 Crypto.sha1Str = function(str)
       
  9075 {
       
  9076 	return Crypto.sha1(Crypto.strToBe32s(str),str.length);
       
  9077 };
       
  9078 
       
  9079 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
       
  9080 Crypto.sha1 = function(x,blen)
       
  9081 {
       
  9082 	// Add 32-bit integers, wrapping at 32 bits
       
  9083 	function add32(a,b)
       
  9084 	{
       
  9085 		var lsw=(a&0xFFFF)+(b&0xFFFF);
       
  9086 		var msw=(a>>16)+(b>>16)+(lsw>>16);
       
  9087 		return (msw<<16)|(lsw&0xFFFF);
       
  9088 	}
       
  9089 	function AA(a,b,c,d,e)
       
  9090 	{
       
  9091 		b=(b>>>27)|(b<<5);
       
  9092 		var lsw=(a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
       
  9093 		var msw=(a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
       
  9094 		return (msw<<16)|(lsw&0xFFFF);
       
  9095 	}
       
  9096 	function RR(w,j)
       
  9097 	{
       
  9098 		var n=w[j-3]^w[j-8]^w[j-14]^w[j-16];
       
  9099 		return (n>>>31)|(n<<1);
       
  9100 	}
       
  9101 
       
  9102 	var len=blen*8;
       
  9103 	x[len>>5] |= 0x80 << (24-len%32);
       
  9104 	x[((len+64>>9)<<4)+15]=len;
       
  9105 	var w=Array(80);
       
  9106 
       
  9107 	var k1=0x5A827999;
       
  9108 	var k2=0x6ED9EBA1;
       
  9109 	var k3=0x8F1BBCDC;
       
  9110 	var k4=0xCA62C1D6;
       
  9111 
       
  9112 	var h0=0x67452301;
       
  9113 	var h1=0xEFCDAB89;
       
  9114 	var h2=0x98BADCFE;
       
  9115 	var h3=0x10325476;
       
  9116 	var h4=0xC3D2E1F0;
       
  9117 
       
  9118 	for(var i=0;i<x.length;i+=16) {
       
  9119 		var j=0;
       
  9120 		var t;
       
  9121 		var a=h0;
       
  9122 		var b=h1;
       
  9123 		var c=h2;
       
  9124 		var d=h3;
       
  9125 		var e=h4;
       
  9126 		while(j<16) {
       
  9127 			w[j]=x[i+j];
       
  9128 			t=AA(e,a,d^(b&(c^d)),w[j],k1);
       
  9129 			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
       
  9130 		}
       
  9131 		while(j<20) {
       
  9132 			w[j]=RR(w,j);
       
  9133 			t=AA(e,a,d^(b&(c^d)),w[j],k1);
       
  9134 			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
       
  9135 		}
       
  9136 		while(j<40) {
       
  9137 			w[j]=RR(w,j);
       
  9138 			t=AA(e,a,b^c^d,w[j],k2);
       
  9139 			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
       
  9140 		}
       
  9141 		while(j<60) {
       
  9142 			w[j]=RR(w,j);
       
  9143 			t=AA(e,a,(b&c)|(d&(b|c)),w[j],k3);
       
  9144 			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
       
  9145 		}
       
  9146 		while(j<80) {
       
  9147 			w[j]=RR(w,j);
       
  9148 			t=AA(e,a,b^c^d,w[j],k4);
       
  9149 			e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
       
  9150 		}
       
  9151 		h0=add32(h0,a);
       
  9152 		h1=add32(h1,b);
       
  9153 		h2=add32(h2,c);
       
  9154 		h3=add32(h3,d);
       
  9155 		h4=add32(h4,e);
       
  9156 	}
       
  9157 	return [h0,h1,h2,h3,h4];
       
  9158 };
       
  9159 
       
  9160 //--
       
  9161 //-- RGB colour object
       
  9162 //--
       
  9163 
       
  9164 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
       
  9165 function RGB(r,g,b)
       
  9166 {
       
  9167 	this.r = 0;
       
  9168 	this.g = 0;
       
  9169 	this.b = 0;
       
  9170 	if(typeof r == "string") {
       
  9171 		if(r.substr(0,1) == "#") {
       
  9172 			if(r.length == 7) {
       
  9173 				this.r = parseInt(r.substr(1,2),16)/255;
       
  9174 				this.g = parseInt(r.substr(3,2),16)/255;
       
  9175 				this.b = parseInt(r.substr(5,2),16)/255;
       
  9176 			} else {
       
  9177 				this.r = parseInt(r.substr(1,1),16)/15;
       
  9178 				this.g = parseInt(r.substr(2,1),16)/15;
       
  9179 				this.b = parseInt(r.substr(3,1),16)/15;
       
  9180 			}
       
  9181 		} else {
       
  9182 			var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
       
  9183 			var c = r.match(rgbPattern);
       
  9184 			if(c) {
       
  9185 				this.r = parseInt(c[1],10)/255;
       
  9186 				this.g = parseInt(c[2],10)/255;
       
  9187 				this.b = parseInt(c[3],10)/255;
       
  9188 			}
       
  9189 		}
       
  9190 	} else {
       
  9191 		this.r = r;
       
  9192 		this.g = g;
       
  9193 		this.b = b;
       
  9194 	}
       
  9195 	return this;
       
  9196 }
       
  9197 
       
  9198 // Mixes this colour with another in a specified proportion
       
  9199 // c = other colour to mix
       
  9200 // f = 0..1 where 0 is this colour and 1 is the new colour
       
  9201 // Returns an RGB object
       
  9202 RGB.prototype.mix = function(c,f)
       
  9203 {
       
  9204 	return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
       
  9205 };
       
  9206 
       
  9207 // Return an rgb colour as a #rrggbb format hex string
       
  9208 RGB.prototype.toString = function()
       
  9209 {
       
  9210 	return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
       
  9211 				 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
       
  9212 				 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
       
  9213 };
       
  9214 
       
  9215 //--
       
  9216 //-- DOM utilities - many derived from www.quirksmode.org
       
  9217 //--
       
  9218 
       
  9219 function drawGradient(place,horiz,locolors,hicolors)
       
  9220 {
       
  9221 	if(!hicolors)
       
  9222 		hicolors = locolors;
       
  9223 	for(var t=0; t<= 100; t+=2) {
       
  9224 		var bar = document.createElement("div");
       
  9225 		place.appendChild(bar);
       
  9226 		bar.style.position = "absolute";
       
  9227 		bar.style.left = horiz ? t + "%" : 0;
       
  9228 		bar.style.top = horiz ? 0 : t + "%";
       
  9229 		bar.style.width = horiz ? (101-t) + "%" : "100%";
       
  9230 		bar.style.height = horiz ? "100%" : (101-t) + "%";
       
  9231 		bar.style.zIndex = -1;
       
  9232 		var p = t/100*(locolors.length-1);
       
  9233 		bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)],p-Math.floor(p)).toString();
       
  9234 	}
       
  9235 }
       
  9236 
       
  9237 function createTiddlyText(parent,text)
       
  9238 {
       
  9239 	return parent.appendChild(document.createTextNode(text));
       
  9240 }
       
  9241 
       
  9242 function createTiddlyCheckbox(parent,caption,checked,onChange)
       
  9243 {
       
  9244 	var cb = document.createElement("input");
       
  9245 	cb.setAttribute("type","checkbox");
       
  9246 	cb.onclick = onChange;
       
  9247 	parent.appendChild(cb);
       
  9248 	cb.checked = checked;
       
  9249 	cb.className = "chkOptionInput";
       
  9250 	if(caption)
       
  9251 		wikify(caption,parent);
       
  9252 	return cb;
       
  9253 }
       
  9254 
       
  9255 function createTiddlyElement(parent,element,id,className,text,attribs)
       
  9256 {
       
  9257 	var e = document.createElement(element);
       
  9258 	if(className != null)
       
  9259 		e.className = className;
       
  9260 	if(id != null)
       
  9261 		e.setAttribute("id",id);
       
  9262 	if(text != null)
       
  9263 		e.appendChild(document.createTextNode(text));
       
  9264 	if(attribs) {
       
  9265 		for(var n in attribs) {
       
  9266 			e.setAttribute(n,attribs[n]);
       
  9267 		}
       
  9268 	}
       
  9269 	if(parent != null)
       
  9270 		parent.appendChild(e);
       
  9271 	return e;
       
  9272 }
       
  9273 
       
  9274 function addEvent(obj,type,fn)
       
  9275 {
       
  9276 	if(obj.attachEvent) {
       
  9277 		obj['e'+type+fn] = fn;
       
  9278 		obj[type+fn] = function(){obj['e'+type+fn](window.event);};
       
  9279 		obj.attachEvent('on'+type,obj[type+fn]);
       
  9280 	} else {
       
  9281 		obj.addEventListener(type,fn,false);
       
  9282 	}
       
  9283 }
       
  9284 
       
  9285 function removeEvent(obj,type,fn)
       
  9286 {
       
  9287 	if(obj.detachEvent) {
       
  9288 		obj.detachEvent('on'+type,obj[type+fn]);
       
  9289 		obj[type+fn] = null;
       
  9290 	} else {
       
  9291 		obj.removeEventListener(type,fn,false);
       
  9292 	}
       
  9293 }
       
  9294 
       
  9295 function addClass(e,className)
       
  9296 {
       
  9297 	var currClass = e.className.split(" ");
       
  9298 	if(currClass.indexOf(className) == -1)
       
  9299 		e.className += " " + className;
       
  9300 }
       
  9301 
       
  9302 function removeClass(e,className)
       
  9303 {
       
  9304 	var currClass = e.className.split(" ");
       
  9305 	var i = currClass.indexOf(className);
       
  9306 	while(i != -1) {
       
  9307 		currClass.splice(i,1);
       
  9308 		i = currClass.indexOf(className);
       
  9309 	}
       
  9310 	e.className = currClass.join(" ");
       
  9311 }
       
  9312 
       
  9313 function hasClass(e,className)
       
  9314 {
       
  9315 	if(e.className) {
       
  9316 		if(e.className.split(" ").indexOf(className) != -1)
       
  9317 			return true;
       
  9318 	}
       
  9319 	return false;
       
  9320 }
       
  9321 
       
  9322 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
       
  9323 function findRelated(e,value,name,relative)
       
  9324 {
       
  9325 	name = name ? name : "tagName";
       
  9326 	relative = relative ? relative : "parentNode";
       
  9327 	if(name == "className") {
       
  9328 		while(e && !hasClass(e,value)) {
       
  9329 			e = e[relative];
       
  9330 		}
       
  9331 	} else {
       
  9332 		while(e && e[name] != value) {
       
  9333 			e = e[relative];
       
  9334 		}
       
  9335 	}
       
  9336 	return e;
       
  9337 }
       
  9338 
       
  9339 // Resolve the target object of an event
       
  9340 function resolveTarget(e)
       
  9341 {
       
  9342 	var obj;
       
  9343 	if(e.target)
       
  9344 		obj = e.target;
       
  9345 	else if(e.srcElement)
       
  9346 		obj = e.srcElement;
       
  9347 	if(obj.nodeType == 3) // defeat Safari bug
       
  9348 		obj = obj.parentNode;
       
  9349 	return obj;
       
  9350 }
       
  9351 
       
  9352 // Prevent an event from bubbling
       
  9353 function stopEvent(e)
       
  9354 {
       
  9355 	var ev = e ? e : window.event;
       
  9356 	ev.cancelBubble = true;
       
  9357 	if(ev.stopPropagation) ev.stopPropagation();
       
  9358 	return false;
       
  9359 }
       
  9360 
       
  9361 // Return the content of an element as plain text with no formatting
       
  9362 function getPlainText(e)
       
  9363 {
       
  9364 	var text = "";
       
  9365 	if(e.innerText)
       
  9366 		text = e.innerText;
       
  9367 	else if(e.textContent)
       
  9368 		text = e.textContent;
       
  9369 	return text;
       
  9370 }
       
  9371 
       
  9372 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
       
  9373 function ensureVisible(e)
       
  9374 {
       
  9375 	var posTop = findPosY(e);
       
  9376 	var posBot = posTop + e.offsetHeight;
       
  9377 	var winTop = findScrollY();
       
  9378 	var winHeight = findWindowHeight();
       
  9379 	var winBot = winTop + winHeight;
       
  9380 	if(posTop < winTop) {
       
  9381 		return posTop;
       
  9382 	} else if(posBot > winBot) {
       
  9383 		if(e.offsetHeight < winHeight)
       
  9384 			return posTop - (winHeight - e.offsetHeight);
       
  9385 		else
       
  9386 			return posTop;
       
  9387 	} else {
       
  9388 		return winTop;
       
  9389 	}
       
  9390 }
       
  9391 
       
  9392 // Get the current width of the display window
       
  9393 function findWindowWidth()
       
  9394 {
       
  9395 	return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
       
  9396 }
       
  9397 
       
  9398 // Get the current height of the display window
       
  9399 function findWindowHeight()
       
  9400 {
       
  9401 	return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
       
  9402 }
       
  9403 
       
  9404 // Get the current horizontal page scroll position
       
  9405 function findScrollX()
       
  9406 {
       
  9407 	return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
       
  9408 }
       
  9409 
       
  9410 // Get the current vertical page scroll position
       
  9411 function findScrollY()
       
  9412 {
       
  9413 	return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
       
  9414 }
       
  9415 
       
  9416 function findPosX(obj)
       
  9417 {
       
  9418 	var curleft = 0;
       
  9419 	while(obj.offsetParent) {
       
  9420 		curleft += obj.offsetLeft;
       
  9421 		obj = obj.offsetParent;
       
  9422 	}
       
  9423 	return curleft;
       
  9424 }
       
  9425 
       
  9426 function findPosY(obj)
       
  9427 {
       
  9428 	var curtop = 0;
       
  9429 	while(obj.offsetParent) {
       
  9430 		curtop += obj.offsetTop;
       
  9431 		obj = obj.offsetParent;
       
  9432 	}
       
  9433 	return curtop;
       
  9434 }
       
  9435 
       
  9436 // Blur a particular element
       
  9437 function blurElement(e)
       
  9438 {
       
  9439 	if(e != null && e.focus && e.blur) {
       
  9440 		e.focus();
       
  9441 		e.blur();
       
  9442 	}
       
  9443 }
       
  9444 
       
  9445 // Create a non-breaking space
       
  9446 function insertSpacer(place)
       
  9447 {
       
  9448 	var e = document.createTextNode(String.fromCharCode(160));
       
  9449 	if(place)
       
  9450 		place.appendChild(e);
       
  9451 	return e;
       
  9452 }
       
  9453 
       
  9454 // Remove all children of a node
       
  9455 function removeChildren(e)
       
  9456 {
       
  9457 	while(e && e.hasChildNodes())
       
  9458 		removeNode(e.firstChild);
       
  9459 }
       
  9460 
       
  9461 // Remove a node and all it's children
       
  9462 function removeNode(e)
       
  9463 {
       
  9464 	scrubNode(e);
       
  9465 	e.parentNode.removeChild(e);
       
  9466 }
       
  9467 
       
  9468 // Remove any event handlers or non-primitve custom attributes
       
  9469 function scrubNode(e)
       
  9470 {
       
  9471 	if(!config.browser.isIE)
       
  9472 		return;
       
  9473 	var att = e.attributes;
       
  9474 	if(att) {
       
  9475 		for(var t=0; t<att.length; t++) {
       
  9476 			var n = att[t].name;
       
  9477 			if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
       
  9478 				try {
       
  9479 					e[n] = null;
       
  9480 				} catch(ex) {
       
  9481 				}
       
  9482 			}
       
  9483 		}
       
  9484 	}
       
  9485 	var c = e.firstChild;
       
  9486 	while(c) {
       
  9487 		scrubNode(c);
       
  9488 		c = c.nextSibling;
       
  9489 	}
       
  9490 }
       
  9491 
       
  9492 // Add a stylesheet, replacing any previous custom stylesheet
       
  9493 function setStylesheet(s,id,doc)
       
  9494 {
       
  9495 	if(!id)
       
  9496 		id = "customStyleSheet";
       
  9497 	if(!doc)
       
  9498 		doc = document;
       
  9499 	var n = doc.getElementById(id);
       
  9500 	if(doc.createStyleSheet) {
       
  9501 		// Test for IE's non-standard createStyleSheet method
       
  9502 		if(n)
       
  9503 			n.parentNode.removeChild(n);
       
  9504 		// This failed without the &nbsp;
       
  9505 		doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
       
  9506 	} else {
       
  9507 		if(n) {
       
  9508 			n.replaceChild(doc.createTextNode(s),n.firstChild);
       
  9509 		} else {
       
  9510 			n = doc.createElement("style");
       
  9511 			n.type = "text/css";
       
  9512 			n.id = id;
       
  9513 			n.appendChild(doc.createTextNode(s));
       
  9514 			doc.getElementsByTagName("head")[0].appendChild(n);
       
  9515 		}
       
  9516 	}
       
  9517 }
       
  9518 
       
  9519 function removeStyleSheet(id)
       
  9520 {
       
  9521 	var e = document.getElementById(id);
       
  9522 	if(e)
       
  9523 		e.parentNode.removeChild(e);
       
  9524 }
       
  9525 
       
  9526 // Force the browser to do a document reflow when needed to workaround browser bugs
       
  9527 function forceReflow()
       
  9528 {
       
  9529 	if(config.browser.isGecko) {
       
  9530 		setStylesheet("body {top:0px;margin-top:0px;}","forceReflow");
       
  9531 		setTimeout(function() {setStylesheet("","forceReflow");},1);
       
  9532 	}
       
  9533 }
       
  9534 
       
  9535 // Replace the current selection of a textarea or text input and scroll it into view
       
  9536 function replaceSelection(e,text)
       
  9537 {
       
  9538 	if(e.setSelectionRange) {
       
  9539 		var oldpos = e.selectionStart;
       
  9540 		var isRange = e.selectionEnd > e.selectionStart;
       
  9541 		e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
       
  9542 		e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
       
  9543 		var linecount = e.value.split('\n').length;
       
  9544 		var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
       
  9545 		e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
       
  9546 	} else if(document.selection) {
       
  9547 		var range = document.selection.createRange();
       
  9548 		if(range.parentElement() == e) {
       
  9549 			var isCollapsed = range.text == "";
       
  9550 			range.text = text;
       
  9551 			if(!isCollapsed) {
       
  9552 				range.moveStart('character', -text.length);
       
  9553 				range.select();
       
  9554 			}
       
  9555 		}
       
  9556 	}
       
  9557 }
       
  9558 
       
  9559 // Returns the text of the given (text) node, possibly merging subsequent text nodes
       
  9560 function getNodeText(e)
       
  9561 {
       
  9562 	var t = "";
       
  9563 	while(e && e.nodeName == "#text") {
       
  9564 		t += e.nodeValue;
       
  9565 		e = e.nextSibling;
       
  9566 	}
       
  9567 	return t;
       
  9568 }
       
  9569 
       
  9570 // Returns true if the element e has a given ancestor element
       
  9571 function isDescendant(e,ancestor)
       
  9572 {
       
  9573 	while(e) {
       
  9574 		if(e === ancestor)
       
  9575 			return true;
       
  9576 		e = e.parentNode;
       
  9577 	}
       
  9578 	return false;
       
  9579 }
       
  9580 
       
  9581 //--
       
  9582 //-- LoaderBase and SaverBase
       
  9583 //--
       
  9584 
       
  9585 function LoaderBase() {}
       
  9586 
       
  9587 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
       
  9588 {
       
  9589 	var title = this.getTitle(store,node);
       
  9590 	if(safeMode && store.isShadowTiddler(title))
       
  9591 		return;
       
  9592 	if(title) {
       
  9593 		var tiddler = store.createTiddler(title);
       
  9594 		this.internalizeTiddler(store,tiddler,title,node);
       
  9595 		tiddlers.push(tiddler);
       
  9596 	}
       
  9597 };
       
  9598 
       
  9599 LoaderBase.prototype.loadTiddlers = function(store,nodes)
       
  9600 {
       
  9601 	var tiddlers = [];
       
  9602 	for(var t = 0; t < nodes.length; t++) {
       
  9603 		try {
       
  9604 			this.loadTiddler(store,nodes[t],tiddlers);
       
  9605 		} catch(ex) {
       
  9606 			showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
       
  9607 		}
       
  9608 	}
       
  9609 	return tiddlers;
       
  9610 };
       
  9611 
       
  9612 function SaverBase() {}
       
  9613 
       
  9614 SaverBase.prototype.externalize = function(store)
       
  9615 {
       
  9616 	var results = [];
       
  9617 	var tiddlers = store.getTiddlers("title");
       
  9618 	for(var t = 0; t < tiddlers.length; t++) {
       
  9619 		if(!tiddlers[t].doNotSave())
       
  9620 			results.push(this.externalizeTiddler(store, tiddlers[t]));
       
  9621 	}
       
  9622 	return results.join("\n");
       
  9623 };
       
  9624 
       
  9625 //--
       
  9626 //-- TW21Loader (inherits from LoaderBase)
       
  9627 //--
       
  9628 
       
  9629 function TW21Loader() {}
       
  9630 
       
  9631 TW21Loader.prototype = new LoaderBase();
       
  9632 
       
  9633 TW21Loader.prototype.getTitle = function(store,node)
       
  9634 {
       
  9635 	var title = null;
       
  9636 	if(node.getAttribute) {
       
  9637 		title = node.getAttribute("title");
       
  9638 		if(!title)
       
  9639 			title = node.getAttribute("tiddler");
       
  9640 	}
       
  9641 	if(!title && node.id) {
       
  9642 		var lenPrefix = store.idPrefix.length;
       
  9643 		if(node.id.substr(0,lenPrefix) == store.idPrefix)
       
  9644 			title = node.id.substr(lenPrefix);
       
  9645 	}
       
  9646 	return title;
       
  9647 };
       
  9648 
       
  9649 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
       
  9650 {
       
  9651 	var e = node.firstChild;
       
  9652 	var text = null;
       
  9653 	if(node.getAttribute("tiddler")) {
       
  9654 		text = getNodeText(e).unescapeLineBreaks();
       
  9655 	} else {
       
  9656 		while(e.nodeName!="PRE" && e.nodeName!="pre") {
       
  9657 			e = e.nextSibling;
       
  9658 		}
       
  9659 		text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
       
  9660 	}
       
  9661 	var modifier = node.getAttribute("modifier");
       
  9662 	var c = node.getAttribute("created");
       
  9663 	var m = node.getAttribute("modified");
       
  9664 	var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
       
  9665 	var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
       
  9666 	var tags = node.getAttribute("tags");
       
  9667 	var fields = {};
       
  9668 	var attrs = node.attributes;
       
  9669 	for(var i = attrs.length-1; i >= 0; i--) {
       
  9670 		var name = attrs[i].name;
       
  9671 		if(attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
       
  9672 			fields[name] = attrs[i].value.unescapeLineBreaks();
       
  9673 		}
       
  9674 	}
       
  9675 	tiddler.assign(title,text,modifier,modified,tags,created,fields);
       
  9676 	return tiddler;
       
  9677 };
       
  9678 
       
  9679 //--
       
  9680 //-- TW21Saver (inherits from SaverBase)
       
  9681 //--
       
  9682 
       
  9683 function TW21Saver() {}
       
  9684 
       
  9685 TW21Saver.prototype = new SaverBase();
       
  9686 
       
  9687 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
       
  9688 {
       
  9689 	try {
       
  9690 		var extendedAttributes = "";
       
  9691 		var usePre = config.options.chkUsePreForStorage;
       
  9692 		store.forEachField(tiddler,
       
  9693 			function(tiddler,fieldName,value) {
       
  9694 				// don't store stuff from the temp namespace
       
  9695 				if(typeof value != "string")
       
  9696 					value = "";
       
  9697 				if(!fieldName.match(/^temp\./))
       
  9698 					extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
       
  9699 			},true);
       
  9700 		var created = tiddler.created;
       
  9701 		var modified = tiddler.modified;
       
  9702 		var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
       
  9703 		attributes += (usePre && created == version.date) ? "" :' created="' + created.convertToYYYYMMDDHHMM() + '"';
       
  9704 		attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() +'"';
       
  9705 		var tags = tiddler.getTags();
       
  9706 		if(!usePre || tags)
       
  9707 			attributes += ' tags="' + tags.htmlEncode() + '"';
       
  9708 		return ('<div %0="%1"%2%3>%4</'+'div>').format([
       
  9709 				usePre ? "title" : "tiddler",
       
  9710 				tiddler.title.htmlEncode(),
       
  9711 				attributes,
       
  9712 				extendedAttributes,
       
  9713 				usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
       
  9714 			]);
       
  9715 	} catch (ex) {
       
  9716 		throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
       
  9717 	}
       
  9718 };
       
  9719 
       
  9720 //]]>
       
  9721 </script>
       
  9722 <script type="text/javascript">
       
  9723 //<![CDATA[
       
  9724 if(useJavaSaver)
       
  9725 	document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
       
  9726 //]]>
       
  9727 </script>
       
  9728 <!--POST-SCRIPT-START-->
       
  9729 
       
  9730 <!--POST-SCRIPT-END-->
       
  9731 </body>
       
  9732 </html>