Ticket #1327: zookosmod.htm

File zookosmod.htm, 304.5 KB (added by arch_o_median, at 2011-01-16T01:58:23Z)

tiddlywiki with tahoe specific patches

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