Ticket #1327: empty.html

File empty.html, 283.8 KB (added by arch_o_median, at 2011-01-16T01:56:32Z)

native tiddlywiki

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