lots of new ideas in the wiki
[openlease.git] / wiki.html
blob8645b97028265dc1ebb2921e146f5a662b9e8b20
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 type="text/javascript">
5 //<![CDATA[
6 var version = {title: "TiddlyWiki", major: 2, minor: 3, revision: 0, date: new Date("Dec 4, 2007"), extensions: {}};
7 //]]>
8 </script>
9 <!--
10 TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
12 Copyright (c) UnaMesa Association 2004-2007
14 Redistribution and use in source and binary forms, with or without modification,
15 are permitted provided that the following conditions are met:
17 Redistributions of source code must retain the above copyright notice, this
18 list of conditions and the following disclaimer.
20 Redistributions in binary form must reproduce the above copyright notice, this
21 list of conditions and the following disclaimer in the documentation and/or other
22 materials provided with the distribution.
24 Neither the name of the UnaMesa Association nor the names of its contributors may be
25 used to endorse or promote products derived from this software without specific
26 prior written permission.
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
29 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
31 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
33 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
37 DAMAGE.
38 -->
39 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
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> Open Lease - open source rental property management software </title>
46 <style 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-->
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-->
62 <!--PRE-BODY-END-->
63 <div id="copyright">
64 Welcome to TiddlyWiki created by Jeremy Ruston, Copyright &copy; 2007 UnaMesa Association
65 </div>
66 <noscript>
67 <div id="javascriptWarning">This page requires JavaScript to function properly.<br /><br />If you are using Microsoft Internet Explorer you may need to click on the yellow bar above and select 'Allow Blocked Content'. You must then click 'Yes' on the following security warning.</div>
68 </noscript>
69 <div id="saveTest"></div>
70 <div id="backstageCloak"></div>
71 <div id="backstageButton"></div>
72 <div id="backstageArea"><div id="backstageToolbar"></div></div>
73 <div id="backstage">
74 <div id="backstagePanel"></div>
75 </div>
76 <div id="contentWrapper"></div>
77 <div id="contentStash"></div>
78 <div id="shadowArea">
79 <div title="MarkupPreHead">
80 <pre>&lt;!--{{{--&gt;
81 &lt;link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/&gt;
82 &lt;!--}}}--&gt;</pre>
83 </div>
84 <div title="ColorPalette">
85 <pre>Background: #fff
86 Foreground: #000
87 PrimaryPale: #8cf
88 PrimaryLight: #18f
89 PrimaryMid: #04b
90 PrimaryDark: #014
91 SecondaryPale: #ffc
92 SecondaryLight: #fe8
93 SecondaryMid: #db4
94 SecondaryDark: #841
95 TertiaryPale: #eee
96 TertiaryLight: #ccc
97 TertiaryMid: #999
98 TertiaryDark: #666
99 Error: #f88</pre>
100 </div>
101 <div title="StyleSheetColors">
102 <pre>/*{{{*/
103 body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
105 a {color:[[ColorPalette::PrimaryMid]];}
106 a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
107 a img {border:0;}
109 h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
110 h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
111 h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}
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]];}
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]];}
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]];
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;}
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]];}
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]];}
154 #messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
155 #messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
157 .popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
159 .popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
160 .popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
161 .popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
162 .popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
163 .popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
164 .popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
165 .popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
166 .listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
168 .tiddler .defaultCommand {font-weight:bold;}
170 .shadow .title {color:[[ColorPalette::TertiaryDark]];}
172 .title {color:[[ColorPalette::SecondaryDark]];}
173 .subtitle {color:[[ColorPalette::TertiaryDark]];}
175 .toolbar {color:[[ColorPalette::PrimaryMid]];}
176 .toolbar a {color:[[ColorPalette::TertiaryLight]];}
177 .selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
178 .selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
180 .tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
181 .selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
182 .tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
183 .tagging .button, .tagged .button {border:none;}
185 .footer {color:[[ColorPalette::TertiaryLight]];}
186 .selected .footer {color:[[ColorPalette::TertiaryMid]];}
188 .sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
189 .sparktick {background:[[ColorPalette::PrimaryDark]];}
191 .error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
192 .warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
193 .lowlight {background:[[ColorPalette::TertiaryLight]];}
195 .zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
197 .imageLink, #displayArea .imageLink {background:transparent;}
199 .annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
201 .viewer .listTitle {list-style-type:none; margin-left:-2em;}
202 .viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
203 .viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
205 .viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
206 .viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
207 .viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
209 .viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
210 .viewer code {color:[[ColorPalette::SecondaryDark]];}
211 .viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
213 .highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
215 .editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
216 .editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
217 .editorFooter {color:[[ColorPalette::TertiaryMid]];}
219 #backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
220 #backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
221 #backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
222 #backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
223 #backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
224 #backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
225 #backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
226 .backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
227 .backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
228 #backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
229 /*}}}*/</pre>
230 </div>
231 <div title="StyleSheetLayout">
232 <pre>/*{{{*/
233 * html .tiddler {height:1%;}
235 body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
237 h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
238 h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
239 h4,h5,h6 {margin-top:1em;}
240 h1 {font-size:1.35em;}
241 h2 {font-size:1.25em;}
242 h3 {font-size:1.1em;}
243 h4 {font-size:1em;}
244 h5 {font-size:.9em;}
246 hr {height:1px;}
248 a {text-decoration:none;}
250 dt {font-weight:bold;}
252 ol {list-style-type:decimal;}
253 ol ol {list-style-type:lower-alpha;}
254 ol ol ol {list-style-type:lower-roman;}
255 ol ol ol ol {list-style-type:decimal;}
256 ol ol ol ol ol {list-style-type:lower-alpha;}
257 ol ol ol ol ol ol {list-style-type:lower-roman;}
258 ol ol ol ol ol ol ol {list-style-type:decimal;}
260 .txtOptionInput {width:11em;}
262 #contentWrapper .chkOptionInput {border:0;}
264 .externalLink {text-decoration:underline;}
266 .indent {margin-left:3em;}
267 .outdent {margin-left:3em; text-indent:-3em;}
268 code.escaped {white-space:nowrap;}
270 .tiddlyLinkExisting {font-weight:bold;}
271 .tiddlyLinkNonExisting {font-style:italic;}
273 /* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
274 a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
276 #mainMenu .tiddlyLinkExisting,
277 #mainMenu .tiddlyLinkNonExisting,
278 #sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
279 #sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
281 .header {position:relative;}
282 .header a:hover {background:transparent;}
283 .headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
284 .headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
286 .siteTitle {font-size:3em;}
287 .siteSubtitle {font-size:1.2em;}
289 #mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
291 #sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
292 #sidebarOptions {padding-top:0.3em;}
293 #sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
294 #sidebarOptions input {margin:0.4em 0.5em;}
295 #sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
296 #sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
297 #sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
298 #sidebarTabs .tabContents {width:15em; overflow:hidden;}
300 .wizard {padding:0.1em 1em 0em 2em;}
301 .wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
302 .wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
303 .wizardStep {padding:1em 1em 1em 1em;}
304 .wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
305 .wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
306 .wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
307 .wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
309 #messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
310 .messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
311 #messageArea a {text-decoration:underline;}
313 .tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
314 .popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
316 .popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
317 .popup .popupMessage {padding:0.4em;}
318 .popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
319 .popup li.disabled {padding:0.4em;}
320 .popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
321 .listBreak {font-size:1px; line-height:1px;}
322 .listBreak div {margin:2px 0;}
324 .tabset {padding:1em 0em 0em 0.5em;}
325 .tab {margin:0em 0em 0em 0.25em; padding:2px;}
326 .tabContents {padding:0.5em;}
327 .tabContents ul, .tabContents ol {margin:0; padding:0;}
328 .txtMainTab .tabContents li {list-style:none;}
329 .tabContents li.listLink { margin-left:.75em;}
331 #contentWrapper {display:block;}
332 #splashScreen {display:none;}
334 #displayArea {margin:1em 17em 0em 14em;}
336 .toolbar {text-align:right; font-size:.9em;}
338 .tiddler {padding:1em 1em 0em 1em;}
340 .missing .viewer,.missing .title {font-style:italic;}
342 .title {font-size:1.6em; font-weight:bold;}
344 .missing .subtitle {display:none;}
345 .subtitle {font-size:1.1em;}
347 .tiddler .button {padding:0.2em 0.4em;}
349 .tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
350 .isTag .tagging {display:block;}
351 .tagged {margin:0.5em; float:right;}
352 .tagging, .tagged {font-size:0.9em; padding:0.25em;}
353 .tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
354 .tagClear {clear:both;}
356 .footer {font-size:.9em;}
357 .footer li {display:inline;}
359 .annotation {padding:0.5em; margin:0.5em;}
361 * html .viewer pre {width:99%; padding:0 0 1em 0;}
362 .viewer {line-height:1.4em; padding-top:0.5em;}
363 .viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
364 .viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
365 .viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
367 .viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
368 .viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
369 table.listView {font-size:0.85em; margin:0.8em 1.0em;}
370 table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
372 .viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
373 .viewer code {font-size:1.2em; line-height:1.4em;}
375 .editor {font-size:1.1em;}
376 .editor input, .editor textarea {display:block; width:100%; font:inherit;}
377 .editorFooter {padding:0.25em 0em; font-size:.9em;}
378 .editorFooter .button {padding-top:0px; padding-bottom:0px;}
380 .fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
382 .sparkline {line-height:1em;}
383 .sparktick {outline:0;}
385 .zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
386 .zoomer div {padding:1em;}
388 * html #backstage {width:99%;}
389 * html #backstageArea {width:99%;}
390 #backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
391 #backstageToolbar {position:relative;}
392 #backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
393 #backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
394 #backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
395 #backstage {position:relative; width:100%; z-index:50;}
396 #backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
397 .backstagePanelFooter {padding-top:0.2em; float:right;}
398 .backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
399 #backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
401 .whenBackstage {display:none;}
402 .backstageVisible .whenBackstage {display:block;}
403 /*}}}*/</pre>
404 </div>
405 <div title="StyleSheetLocale">
406 <pre>/***
407 StyleSheet for use when a translation requires any css style changes.
408 This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
409 ***/
410 /*{{{*/
411 body {font-size:0.8em;}
412 #sidebarOptions {font-size:1.05em;}
413 #sidebarOptions a {font-style:normal;}
414 #sidebarOptions .sliderPanel {font-size:0.95em;}
415 .subtitle {font-size:0.8em;}
416 .viewer table.listView {font-size:0.95em;}
417 /*}}}*/</pre>
418 </div>
419 <div title="StyleSheetPrint">
420 <pre>/*{{{*/
421 @media print {
422 #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
423 #displayArea {margin: 1em 1em 0em 1em;}
424 /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
425 noscript {display:none;}
427 /*}}}*/</pre>
428 </div>
429 <div title="PageTemplate">
430 <pre>&lt;!--{{{--&gt;
431 &lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
432 &lt;div class='headerShadow'&gt;
433 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
434 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
435 &lt;/div&gt;
436 &lt;div class='headerForeground'&gt;
437 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
438 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
439 &lt;/div&gt;
440 &lt;/div&gt;
441 &lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
442 &lt;div id='sidebar'&gt;
443 &lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
444 &lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
445 &lt;/div&gt;
446 &lt;div id='displayArea'&gt;
447 &lt;div id='messageArea'&gt;&lt;/div&gt;
448 &lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
449 &lt;/div&gt;
450 &lt;!--}}}--&gt;</pre>
451 </div>
452 <div title="ViewTemplate">
453 <pre>&lt;!--{{{--&gt;
454 &lt;div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler &gt; fields syncing permalink references jump'&gt;&lt;/div&gt;
455 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
456 &lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
457 &lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
458 &lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
459 &lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
460 &lt;div class='tagClear'&gt;&lt;/div&gt;
461 &lt;!--}}}--&gt;</pre>
462 </div>
463 <div title="EditTemplate">
464 <pre>&lt;!--{{{--&gt;
465 &lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'&gt;&lt;/div&gt;
466 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
467 &lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
468 &lt;div macro='annotations'&gt;&lt;/div&gt;
469 &lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
470 &lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
471 &lt;!--}}}--&gt;</pre>
472 </div>
473 <div title="GettingStarted">
474 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
475 * SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
476 * MainMenu: The menu (usually on the left)
477 * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
478 You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
479 </div>
480 <div title="OptionsPanel">
481 <pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser
483 Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)
485 &lt;&lt;option txtUserName&gt;&gt;
486 &lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
487 &lt;&lt;option chkAutoSave&gt;&gt; AutoSave
488 &lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
489 &lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
490 &lt;&lt;option chkAnimate&gt;&gt; EnableAnimations
492 ----
493 Also see AdvancedOptions</pre>
494 </div>
495 <div title="ImportTiddlers">
496 <pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
497 </div>
498 </div>
499 <!--POST-SHADOWAREA-->
500 <div id="storeArea">
501 <div title="Boilerplate Lease Contracts" modifier="Jason McVetta" modified="200803112242" created="200803112239" changecount="2">
502 <pre>What kind of CreativeCommons license is appropriate?</pre>
503 </div>
504 <div title="CryptoFunctionsPlugin" created="200710131134" tags="systemConfig excludeLists excludeSearch">
505 <pre>/***
506 |''Name:''|CryptoFunctionsPlugin|
507 |''Description:''|Support for cryptographic functions|
508 ***/
509 //{{{
510 if(!version.extensions.CryptoFunctionsPlugin) {
511 version.extensions.CryptoFunctionsPlugin = {installed:true};
513 //--
514 //-- Crypto functions and associated conversion routines
515 //--
517 // Crypto &quot;namespace&quot;
518 function Crypto() {}
520 // Convert a string to an array of big-endian 32-bit words
521 Crypto.strToBe32s = function(str)
523 var be = Array();
524 var len = Math.floor(str.length/4);
525 var i, j;
526 for(i=0, j=0; i&lt;len; i++, j+=4) {
527 be[i] = ((str.charCodeAt(j)&amp;0xff) &lt;&lt; 24)|((str.charCodeAt(j+1)&amp;0xff) &lt;&lt; 16)|((str.charCodeAt(j+2)&amp;0xff) &lt;&lt; 8)|(str.charCodeAt(j+3)&amp;0xff);
529 while (j&lt;str.length) {
530 be[j&gt;&gt;2] |= (str.charCodeAt(j)&amp;0xff)&lt;&lt;(24-(j*8)%32);
531 j++;
533 return be;
536 // Convert an array of big-endian 32-bit words to a string
537 Crypto.be32sToStr = function(be)
539 var str = &quot;&quot;;
540 for(var i=0;i&lt;be.length*32;i+=8)
541 str += String.fromCharCode((be[i&gt;&gt;5]&gt;&gt;&gt;(24-i%32)) &amp; 0xff);
542 return str;
545 // Convert an array of big-endian 32-bit words to a hex string
546 Crypto.be32sToHex = function(be)
548 var hex = &quot;0123456789ABCDEF&quot;;
549 var str = &quot;&quot;;
550 for(var i=0;i&lt;be.length*4;i++)
551 str += hex.charAt((be[i&gt;&gt;2]&gt;&gt;((3-i%4)*8+4))&amp;0xF) + hex.charAt((be[i&gt;&gt;2]&gt;&gt;((3-i%4)*8))&amp;0xF);
552 return str;
555 // Return, in hex, the SHA-1 hash of a string
556 Crypto.hexSha1Str = function(str)
558 return Crypto.be32sToHex(Crypto.sha1Str(str));
561 // Return the SHA-1 hash of a string
562 Crypto.sha1Str = function(str)
564 return Crypto.sha1(Crypto.strToBe32s(str),str.length);
567 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
568 Crypto.sha1 = function(x,blen)
570 // Add 32-bit integers, wrapping at 32 bits
571 add32 = function(a,b)
573 var lsw = (a&amp;0xFFFF)+(b&amp;0xFFFF);
574 var msw = (a&gt;&gt;16)+(b&gt;&gt;16)+(lsw&gt;&gt;16);
575 return (msw&lt;&lt;16)|(lsw&amp;0xFFFF);
577 // Add five 32-bit integers, wrapping at 32 bits
578 add32x5 = function(a,b,c,d,e)
580 var lsw = (a&amp;0xFFFF)+(b&amp;0xFFFF)+(c&amp;0xFFFF)+(d&amp;0xFFFF)+(e&amp;0xFFFF);
581 var msw = (a&gt;&gt;16)+(b&gt;&gt;16)+(c&gt;&gt;16)+(d&gt;&gt;16)+(e&gt;&gt;16)+(lsw&gt;&gt;16);
582 return (msw&lt;&lt;16)|(lsw&amp;0xFFFF);
584 // Bitwise rotate left a 32-bit integer by 1 bit
585 rol32 = function(n)
587 return (n&gt;&gt;&gt;31)|(n&lt;&lt;1);
590 var len = blen*8;
591 // Append padding so length in bits is 448 mod 512
592 x[len&gt;&gt;5] |= 0x80 &lt;&lt; (24-len%32);
593 // Append length
594 x[((len+64&gt;&gt;9)&lt;&lt;4)+15] = len;
595 var w = Array(80);
597 var k1 = 0x5A827999;
598 var k2 = 0x6ED9EBA1;
599 var k3 = 0x8F1BBCDC;
600 var k4 = 0xCA62C1D6;
602 var h0 = 0x67452301;
603 var h1 = 0xEFCDAB89;
604 var h2 = 0x98BADCFE;
605 var h3 = 0x10325476;
606 var h4 = 0xC3D2E1F0;
608 for(var i=0;i&lt;x.length;i+=16) {
609 var j,t;
610 var a = h0;
611 var b = h1;
612 var c = h2;
613 var d = h3;
614 var e = h4;
615 for(j = 0;j&lt;16;j++) {
616 w[j] = x[i+j];
617 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),d^(b&amp;(c^d)),w[j],k1);
618 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
620 for(j=16;j&lt;20;j++) {
621 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
622 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),d^(b&amp;(c^d)),w[j],k1);
623 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
625 for(j=20;j&lt;40;j++) {
626 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
627 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),b^c^d,w[j],k2);
628 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
630 for(j=40;j&lt;60;j++) {
631 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
632 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),(b&amp;c)|(d&amp;(b|c)),w[j],k3);
633 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
635 for(j=60;j&lt;80;j++) {
636 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
637 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),b^c^d,w[j],k4);
638 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
641 h0 = add32(h0,a);
642 h1 = add32(h1,b);
643 h2 = add32(h2,c);
644 h3 = add32(h3,d);
645 h4 = add32(h4,e);
647 return Array(h0,h1,h2,h3,h4);
652 //}}}</pre>
653 </div>
654 <div title="DeprecatedFunctionsPlugin" created="200710140259" tags="systemConfig excludeLists excludeSearch">
655 <pre>/***
656 |''Name:''|DeprecatedFunctionsPlugin|
657 |''Description:''|Support for deprecated functions removed from core|
658 ***/
659 //{{{
660 if(!version.extensions.DeprecatedFunctionsPlugin) {
661 version.extensions.DeprecatedFunctionsPlugin = {installed:true};
663 //--
664 //-- Deprecated code
665 //--
667 // @Deprecated: Use createElementAndWikify and this.termRegExp instead
668 config.formatterHelpers.charFormatHelper = function(w)
670 w.subWikify(createTiddlyElement(w.output,this.element),this.terminator);
673 // @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
674 config.formatterHelpers.monospacedByLineHelper = function(w)
676 var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
677 lookaheadRegExp.lastIndex = w.matchStart;
678 var lookaheadMatch = lookaheadRegExp.exec(w.source);
679 if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
680 var text = lookaheadMatch[1];
681 if(config.browser.isIE)
682 text = text.replace(/\n/g,&quot;\r&quot;);
683 createTiddlyElement(w.output,&quot;pre&quot;,null,null,text);
684 w.nextMatch = lookaheadRegExp.lastIndex;
688 // @Deprecated: Use &lt;br&gt; or &lt;br /&gt; instead of &lt;&lt;br&gt;&gt;
689 config.macros.br = {};
690 config.macros.br.handler = function(place)
692 createTiddlyElement(place,&quot;br&quot;);
695 // Find an entry in an array. Returns the array index or null
696 // @Deprecated: Use indexOf instead
697 Array.prototype.find = function(item)
699 var i = this.indexOf(item);
700 return i == -1 ? null : i;
703 // Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
704 // @Deprecated: Use store.getLoader().internalizeTiddler instead
705 Tiddler.prototype.loadFromDiv = function(divRef,title)
707 return store.getLoader().internalizeTiddler(store,this,title,divRef);
710 // Format the text for storage in an HTML DIV
711 // @Deprecated Use store.getSaver().externalizeTiddler instead.
712 Tiddler.prototype.saveToDiv = function()
714 return store.getSaver().externalizeTiddler(store,this);
717 // @Deprecated: Use store.allTiddlersAsHtml() instead
718 function allTiddlersAsHtml()
720 return store.allTiddlersAsHtml();
723 // @Deprecated: Use refreshPageTemplate instead
724 function applyPageTemplate(title)
726 refreshPageTemplate(title);
729 // @Deprecated: Use story.displayTiddlers instead
730 function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,unused3)
732 story.displayTiddlers(srcElement,titles,template,animate);
735 // @Deprecated: Use story.displayTiddler instead
736 function displayTiddler(srcElement,title,template,unused1,unused2,animate,unused3)
738 story.displayTiddler(srcElement,title,template,animate);
741 // @Deprecated: Use functions on right hand side directly instead
742 var createTiddlerPopup = Popup.create;
743 var scrollToTiddlerPopup = Popup.show;
744 var hideTiddlerPopup = Popup.remove;
746 // @Deprecated: Use right hand side directly instead
747 var regexpBackSlashEn = new RegExp(&quot;\\\\n&quot;,&quot;mg&quot;);
748 var regexpBackSlash = new RegExp(&quot;\\\\&quot;,&quot;mg&quot;);
749 var regexpBackSlashEss = new RegExp(&quot;\\\\s&quot;,&quot;mg&quot;);
750 var regexpNewLine = new RegExp(&quot;\n&quot;,&quot;mg&quot;);
751 var regexpCarriageReturn = new RegExp(&quot;\r&quot;,&quot;mg&quot;);
754 //}}}</pre>
755 </div>
756 <div title="Jason McVetta" modifier="Jason McVetta" modified="200803112257" created="200803112255" changecount="7">
757 <pre>[[jason.mcvetta@gmail.com|mailto:jason.mcvetta@gmail.com]]</pre>
758 </div>
759 <div title="LegacyStrikeThroughPlugin" modifier="MartinBudden" created="200607210000" tags="systemConfig">
760 <pre>/***
761 |''Name:''|LegacyStrikeThroughPlugin|
762 |''Description:''|Support for legacy (pre 2.1) strike through formatting|
763 |''Version:''|1.0.2|
764 |''Date:''|Jul 21, 2006|
765 |''Source:''|http://www.tiddlywiki.com/#LegacyStrikeThroughPlugin|
766 |''Author:''|MartinBudden (mjbudden (at) gmail (dot) com)|
767 |''License:''|[[BSD open source license]]|
768 |''CoreVersion:''|2.1.0|
769 ***/
771 //{{{
772 // Ensure that the LegacyStrikeThrough Plugin is only installed once.
773 if(!version.extensions.LegacyStrikeThroughPlugin) {
774 version.extensions.LegacyStrikeThroughPlugin = {installed:true};
776 config.formatters.push(
778 name: &quot;legacyStrikeByChar&quot;,
779 match: &quot;==&quot;,
780 termRegExp: /(==)/mg,
781 element: &quot;strike&quot;,
782 handler: config.formatterHelpers.createElementAndWikify
785 } //# end of &quot;install only once&quot;
786 //}}}</pre>
787 </div>
788 <div title="MainMenu" modifier="Jason McVetta" created="200803112251" changecount="1">
789 <pre>[[New Lease Processing]]
790 [[Boilerplate Lease Contracts]]
791 [[Tenant Protection]]
792 [[Landlord Protection]]
793 GettingStarted</pre>
794 </div>
795 <div title="New Lease Processing" modifier="Jason McVetta" modified="200803112253" created="200803112201" changecount="9">
796 <pre>* Boilerplate legal contracts that match the default functionality of the app
797 ** Need to be written on a per-state, or per-city, basis?
798 * Incomplete leases can be entered
799 ** need {{{lease.is_complete()}}} instance method
800 ** A lease cannot take effect until it {{{is_complete()}}}
801 ** System can tell user (property manager //and// tenant) what steps/documents must yet be completed
802 * A sublease is just like a lease, except it has a master tenant and a chain of liability
803 ** Sublease must have its own contract, which wholly supercedes that of the master lease -- clarity
804 *** always?
805 *** 'cascading' contract from master lease? </pre>
806 </div>
807 <div title="Self-Service" modifier="Jason McVetta" created="200803112212" changecount="1">
808 <pre>* Rental payment via credit card
809 ** Easy for tenants &amp; subtenants to pay individually
810 ** Tenants &amp; subtenants can be assigned explicit proportional responsibility for rent payment</pre>
811 </div>
812 <div title="SiteSubtitle" modifier="Jason McVetta" modified="200803112200" created="200803112159" changecount="2">
813 <pre>open source rental property management software</pre>
814 </div>
815 <div title="SiteTitle" modifier="Jason McVetta" modified="200803112200" created="200803112159" changecount="2">
816 <pre>Open Lease</pre>
817 </div>
818 <div title="SparklinePlugin" created="200710140259" tags="systemConfig excludeLists excludeSearch">
819 <pre>/***
820 |''Name:''|SparklinePlugin|
821 |''Description:''|Sparklines macro|
822 ***/
823 //{{{
824 if(!version.extensions.SparklinePlugin) {
825 version.extensions.SparklinePlugin = {installed:true};
827 //--
828 //-- Sparklines
829 //--
831 config.macros.sparkline = {};
832 config.macros.sparkline.handler = function(place,macroName,params)
834 var data = [];
835 var min = 0;
836 var max = 0;
837 var v;
838 for(var t=0; t&lt;params.length; t++) {
839 v = parseInt(params[t]);
840 if(v &lt; min)
841 min = v;
842 if(v &gt; max)
843 max = v;
844 data.push(v);
846 if(data.length &lt; 1)
847 return;
848 var box = createTiddlyElement(place,&quot;span&quot;,null,&quot;sparkline&quot;,String.fromCharCode(160));
849 box.title = data.join(&quot;,&quot;);
850 var w = box.offsetWidth;
851 var h = box.offsetHeight;
852 box.style.paddingRight = (data.length * 2 - w) + &quot;px&quot;;
853 box.style.position = &quot;relative&quot;;
854 for(var d=0; d&lt;data.length; d++) {
855 var tick = document.createElement(&quot;img&quot;);
856 tick.border = 0;
857 tick.className = &quot;sparktick&quot;;
858 tick.style.position = &quot;absolute&quot;;
859 tick.src = &quot;data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B&quot;;
860 tick.style.left = d*2 + &quot;px&quot;;
861 tick.style.width = &quot;2px&quot;;
862 v = Math.floor(((data[d] - min)/(max-min)) * h);
863 tick.style.top = (h-v) + &quot;px&quot;;
864 tick.style.height = v + &quot;px&quot;;
865 box.appendChild(tick);
871 //}}}</pre>
872 </div>
873 <div title="Tenant Protection" modifier="Jason McVetta" modified="200803112247" created="200803112238" changecount="5">
874 <pre>The proportional rent responsibility built-in to the system and its associated [[ready-made leases|Boilerplate Lease Contracts]] affords tenants added protection from their fellow tenants. Landlords will often continue to demand joint &amp; several liability in the contract language, but the proportion for which each party to the lease is responsible will be explicity fixed at signing. Should one of tenant among several default, this explicit contractual relationship may make it easier for the other tenants to recover damages in court.</pre>
875 </div>
876 </div>
877 <!--POST-STOREAREA-->
878 <!--POST-BODY-START-->
879 <!--POST-BODY-END-->
880 <script type="text/javascript">
881 //<![CDATA[
883 // Please note:
885 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
886 // in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
888 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
889 // without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
892 //--
893 //-- Configuration repository
894 //--
896 // Miscellaneous options
897 var config = {
898 numRssItems: 20, // Number of items in the RSS feed
899 animDuration: 400, // Duration of UI animations in milliseconds
900 cascadeFast: 20, // Speed for cascade animations (higher == slower)
901 cascadeSlow: 60, // Speed for EasterEgg cascade animations
902 cascadeDepth: 5 // Depth of cascade animation
905 // Adaptors
906 config.adaptors = {};
908 // Backstage tasks
909 config.tasks = {};
911 // Annotations
912 config.annotations = {};
914 // Custom fields to be automatically added to new tiddlers
915 config.defaultCustomFields = {};
917 // Messages
918 config.messages = {
919 messageClose: {},
920 dates: {},
921 tiddlerPopup: {}
924 // Options that can be set in the options panel and/or cookies
925 config.options = {
926 chkRegExpSearch: false,
927 chkCaseSensitiveSearch: false,
928 chkAnimate: true,
929 chkSaveBackups: true,
930 chkAutoSave: false,
931 chkGenerateAnRssFeed: false,
932 chkSaveEmptyTemplate: false,
933 chkOpenInNewWindow: true,
934 chkToggleLinks: false,
935 chkHttpReadOnly: true,
936 chkForceMinorUpdate: false,
937 chkConfirmDelete: true,
938 chkInsertTabs: false,
939 chkUsePreForStorage: true, // Whether to use <pre> format for storage
940 chkDisplayStartupTime: false,
941 txtBackupFolder: "",
942 txtMainTab: "tabTimeline",
943 txtMoreTab: "moreTabAll",
944 txtMaxEditRows: "30",
945 txtFileSystemCharSet: "UTF-8",
946 txtTheme: ""
948 config.optionsDesc = {};
950 // List of notification functions to be called when certain tiddlers are changed or deleted
951 config.notifyTiddlers = [
952 {name: "StyleSheetLayout", notify: refreshStyles},
953 {name: "StyleSheetColors", notify: refreshStyles},
954 {name: "StyleSheet", notify: refreshStyles},
955 {name: "StyleSheetPrint", notify: refreshStyles},
956 {name: "PageTemplate", notify: refreshPageTemplate},
957 {name: "SiteTitle", notify: refreshPageTitle},
958 {name: "SiteSubtitle", notify: refreshPageTitle},
959 {name: "ColorPalette", notify: refreshColorPalette},
960 {name: null, notify: refreshDisplay}
963 // Default tiddler templates
964 var DEFAULT_VIEW_TEMPLATE = 1;
965 var DEFAULT_EDIT_TEMPLATE = 2;
966 config.tiddlerTemplates = {
967 1: "ViewTemplate",
968 2: "EditTemplate"
971 // More messages (rather a legacy layout that shouldn't really be like this)
972 config.views = {
973 wikified: {
974 tag: {}
976 editor: {
977 tagChooser: {}
981 // Backstage tasks
982 config.backstageTasks = ["save","sync","importTask","tweak","plugins"];
984 // Macros; each has a 'handler' member that is inserted later
985 config.macros = {
986 today: {},
987 version: {},
988 search: {sizeTextbox: 15},
989 tiddler: {},
990 tag: {},
991 tags: {},
992 tagging: {},
993 timeline: {},
994 allTags: {},
995 list: {
996 all: {},
997 missing: {},
998 orphans: {},
999 shadowed: {},
1000 touched: {},
1001 filter: {}
1003 closeAll: {},
1004 permaview: {},
1005 saveChanges: {},
1006 slider: {},
1007 option: {},
1008 options: {},
1009 newTiddler: {},
1010 newJournal: {},
1011 tabs: {},
1012 gradient: {},
1013 message: {},
1014 view: {},
1015 edit: {},
1016 tagChooser: {},
1017 toolbar: {},
1018 plugins: {},
1019 refreshDisplay: {},
1020 importTiddlers: {},
1021 sync: {},
1022 annotations: {}
1025 // Commands supported by the toolbar macro
1026 config.commands = {
1027 closeTiddler: {},
1028 closeOthers: {},
1029 editTiddler: {},
1030 saveTiddler: {hideReadOnly: true},
1031 cancelTiddler: {},
1032 deleteTiddler: {hideReadOnly: true},
1033 permalink: {},
1034 references: {type: "popup"},
1035 jump: {type: "popup"},
1036 syncing: {type: "popup"},
1037 fields: {type: "popup"}
1040 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
1041 config.userAgent = navigator.userAgent.toLowerCase();
1042 config.browser = {
1043 isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
1044 isGecko: config.userAgent.indexOf("gecko") != -1,
1045 ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
1046 isSafari: config.userAgent.indexOf("applewebkit") != -1,
1047 isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
1048 firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
1049 isOpera: config.userAgent.indexOf("opera") != -1,
1050 isLinux: config.userAgent.indexOf("linux") != -1,
1051 isUnix: config.userAgent.indexOf("x11") != -1,
1052 isMac: config.userAgent.indexOf("mac") != -1,
1053 isWindows: config.userAgent.indexOf("win") != -1
1056 // Basic regular expressions
1057 config.textPrimitives = {
1058 upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
1059 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
1060 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
1061 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
1063 if(config.browser.isBadSafari) {
1064 config.textPrimitives = {
1065 upperLetter: "[A-Z\u00c0-\u00de]",
1066 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
1067 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
1068 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
1071 config.textPrimitives.sliceSeparator = "::";
1072 config.textPrimitives.sectionSeparator = "##";
1073 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
1074 config.textPrimitives.unWikiLink = "~";
1075 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
1076 config.textPrimitives.lowerLetter + "+" +
1077 config.textPrimitives.upperLetter +
1078 config.textPrimitives.anyLetter + "*)|(?:" +
1079 config.textPrimitives.upperLetter + "{2,}" +
1080 config.textPrimitives.lowerLetter + "+))";
1082 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
1083 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
1085 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
1086 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
1087 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
1088 config.textPrimitives.brackettedLink + ")|(?:" +
1089 config.textPrimitives.urlPattern + ")","mg");
1090 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
1091 config.textPrimitives.titledBrackettedLink + ")|(?:" +
1092 config.textPrimitives.brackettedLink + ")|(?:" +
1093 config.textPrimitives.urlPattern + ")","mg");
1095 config.glyphs = {
1096 browsers: [
1097 function() {return config.browser.isIE;},
1098 function() {return true;}
1100 currBrowser: null,
1101 codes: {
1102 downTriangle: ["\u25BC","\u25BE"],
1103 downArrow: ["\u2193","\u2193"],
1104 bentArrowLeft: ["\u2190","\u21A9"],
1105 bentArrowRight: ["\u2192","\u21AA"]
1109 //--
1110 //-- Shadow tiddlers
1111 //--
1113 config.shadowTiddlers = {
1114 StyleSheet: "",
1115 MarkupPreHead: "",
1116 MarkupPostHead: "",
1117 MarkupPreBody: "",
1118 MarkupPostBody: "",
1119 TabTimeline: '<<timeline>>',
1120 TabAll: '<<list all>>',
1121 TabTags: '<<allTags excludeLists>>',
1122 TabMoreMissing: '<<list missing>>',
1123 TabMoreOrphans: '<<list orphans>>',
1124 TabMoreShadowed: '<<list shadowed>>',
1125 AdvancedOptions: '<<options>>',
1126 PluginManager: '<<plugins>>'
1129 //--
1130 //-- Translateable strings
1131 //--
1133 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
1135 merge(config.options,{
1136 txtUserName: "YourName"});
1138 merge(config.tasks,{
1139 save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
1140 sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
1141 importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
1142 tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
1143 plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
1146 // Options that can be set in the options panel and/or cookies
1147 merge(config.optionsDesc,{
1148 txtUserName: "Username for signing your edits",
1149 chkRegExpSearch: "Enable regular expressions for searches",
1150 chkCaseSensitiveSearch: "Case-sensitive searching",
1151 chkAnimate: "Enable animations",
1152 chkSaveBackups: "Keep backup file when saving changes",
1153 chkAutoSave: "Automatically save changes",
1154 chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
1155 chkSaveEmptyTemplate: "Generate an empty template when saving changes",
1156 chkOpenInNewWindow: "Open external links in a new window",
1157 chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
1158 chkHttpReadOnly: "Hide editing features when viewed over HTTP",
1159 chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
1160 chkConfirmDelete: "Require confirmation before deleting tiddlers",
1161 chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
1162 txtBackupFolder: "Name of folder to use for backups",
1163 txtMaxEditRows: "Maximum number of rows in edit boxes",
1164 txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
1166 merge(config.messages,{
1167 customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
1168 pluginError: "Error: %0",
1169 pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
1170 pluginForced: "Executed because forced via 'systemConfigForce' tag",
1171 pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
1172 nothingSelected: "Nothing is selected. You must select one or more items first",
1173 savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
1174 subtitleUnknown: "(unknown)",
1175 undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
1176 shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
1177 tiddlerLinkTooltip: "%0 - %1, %2",
1178 externalLinkTooltip: "External link to %0",
1179 noTags: "There are no tagged tiddlers",
1180 notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
1181 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",
1182 invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
1183 backupSaved: "Backup saved",
1184 backupFailed: "Failed to save backup file",
1185 rssSaved: "RSS feed saved",
1186 rssFailed: "Failed to save RSS feed file",
1187 emptySaved: "Empty template saved",
1188 emptyFailed: "Failed to save empty template file",
1189 mainSaved: "Main TiddlyWiki file saved",
1190 mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
1191 macroError: "Error in macro <<\%0>>",
1192 macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
1193 missingMacro: "No such macro",
1194 overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
1195 unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
1196 confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
1197 saveInstructions: "SaveChanges",
1198 unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
1199 tiddlerSaveError: "Error when saving tiddler '%0'",
1200 tiddlerLoadError: "Error when loading tiddler '%0'",
1201 wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
1202 invalidFieldName: "Invalid field name %0",
1203 fieldCannotBeChanged: "Field '%0' cannot be changed",
1204 loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'"});
1206 merge(config.messages.messageClose,{
1207 text: "close",
1208 tooltip: "close this message area"});
1210 config.messages.backstage = {
1211 open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
1212 close: {text: "close", tooltip: "Close the backstage area"},
1213 prompt: "backstage: ",
1214 decal: {
1215 edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
1219 config.messages.listView = {
1220 tiddlerTooltip: "Click for the full text of this tiddler",
1221 previewUnavailable: "(preview not available)"
1224 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
1225 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1226 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1227 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1228 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
1229 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
1230 "th","th","th","th","th","th","th","th","th","th",
1231 "st","nd","rd","th","th","th","th","th","th","th",
1232 "st"];
1233 config.messages.dates.am = "am";
1234 config.messages.dates.pm = "pm";
1236 merge(config.messages.tiddlerPopup,{
1239 merge(config.views.wikified.tag,{
1240 labelNoTags: "no tags",
1241 labelTags: "tags: ",
1242 openTag: "Open tag '%0'",
1243 tooltip: "Show tiddlers tagged with '%0'",
1244 openAllText: "Open all",
1245 openAllTooltip: "Open all of these tiddlers",
1246 popupNone: "No other tiddlers tagged with '%0'"});
1248 merge(config.views.wikified,{
1249 defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
1250 defaultModifier: "(missing)",
1251 shadowModifier: "(built-in shadow tiddler)",
1252 dateFormat: "DD MMM YYYY",
1253 createdPrompt: "created"});
1255 merge(config.views.editor,{
1256 tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
1257 defaultText: "Type the text for '%0'"});
1259 merge(config.views.editor.tagChooser,{
1260 text: "tags",
1261 tooltip: "Choose existing tags to add to this tiddler",
1262 popupNone: "There are no tags defined",
1263 tagTooltip: "Add the tag '%0'"});
1265 merge(config.messages,{
1266 sizeTemplates:
1268 {unit: 1024*1024*1024, template: "%0\u00a0GB"},
1269 {unit: 1024*1024, template: "%0\u00a0MB"},
1270 {unit: 1024, template: "%0\u00a0KB"},
1271 {unit: 1, template: "%0\u00a0B"}
1272 ]});
1274 merge(config.macros.search,{
1275 label: "search",
1276 prompt: "Search this TiddlyWiki",
1277 accessKey: "F",
1278 successMsg: "%0 tiddlers found matching %1",
1279 failureMsg: "No tiddlers found matching %0"});
1281 merge(config.macros.tagging,{
1282 label: "tagging: ",
1283 labelNotTag: "not tagging",
1284 tooltip: "List of tiddlers tagged with '%0'"});
1286 merge(config.macros.timeline,{
1287 dateFormat: "DD MMM YYYY"});
1289 merge(config.macros.allTags,{
1290 tooltip: "Show tiddlers tagged with '%0'",
1291 noTags: "There are no tagged tiddlers"});
1293 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
1294 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
1295 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
1296 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
1297 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
1299 merge(config.macros.closeAll,{
1300 label: "close all",
1301 prompt: "Close all displayed tiddlers (except any that are being edited)"});
1303 merge(config.macros.permaview,{
1304 label: "permaview",
1305 prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
1307 merge(config.macros.saveChanges,{
1308 label: "save changes",
1309 prompt: "Save all tiddlers to create a new TiddlyWiki",
1310 accessKey: "S"});
1312 merge(config.macros.newTiddler,{
1313 label: "new tiddler",
1314 prompt: "Create a new tiddler",
1315 title: "New Tiddler",
1316 accessKey: "N"});
1318 merge(config.macros.newJournal,{
1319 label: "new journal",
1320 prompt: "Create a new tiddler from the current date and time",
1321 accessKey: "J"});
1323 merge(config.macros.options,{
1324 wizardTitle: "Tweak advanced options",
1325 step1Title: "These options are saved in cookies in your browser",
1326 step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
1327 unknownDescription: "//(unknown)//",
1328 listViewTemplate: {
1329 columns: [
1330 {name: 'Option', field: 'option', title: "Option", type: 'String'},
1331 {name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
1332 {name: 'Name', field: 'name', title: "Name", type: 'String'}
1334 rowClasses: [
1335 {className: 'lowlight', field: 'lowlight'}
1339 merge(config.macros.plugins,{
1340 wizardTitle: "Manage plugins",
1341 step1Title: "Currently loaded plugins",
1342 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1343 skippedText: "(This plugin has not been executed because it was added since startup)",
1344 noPluginText: "There are no plugins installed",
1345 confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
1346 removeLabel: "remove systemConfig tag",
1347 removePrompt: "Remove systemConfig tag",
1348 deleteLabel: "delete",
1349 deletePrompt: "Delete these tiddlers forever",
1350 listViewTemplate: {
1351 columns: [
1352 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1353 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1354 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1355 {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
1356 {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
1357 {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
1358 {name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
1359 {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
1360 {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
1362 rowClasses: [
1363 {className: 'error', field: 'error'},
1364 {className: 'warning', field: 'warning'}
1368 merge(config.macros.toolbar,{
1369 moreLabel: "more",
1370 morePrompt: "Reveal further commands"
1373 merge(config.macros.refreshDisplay,{
1374 label: "refresh",
1375 prompt: "Redraw the entire TiddlyWiki display"
1378 merge(config.macros.importTiddlers,{
1379 readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
1380 wizardTitle: "Import tiddlers from another file or server",
1381 step1Title: "Step 1: Locate the server or TiddlyWiki file",
1382 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>",
1383 openLabel: "open",
1384 openPrompt: "Open the connection to this file or server",
1385 openError: "There were problems fetching the tiddlywiki file",
1386 statusOpenHost: "Opening the host",
1387 statusGetWorkspaceList: "Getting the list of available workspaces",
1388 step2Title: "Step 2: Choose the workspace",
1389 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>",
1390 cancelLabel: "cancel",
1391 cancelPrompt: "Cancel this import",
1392 statusOpenWorkspace: "Opening the workspace",
1393 statusGetTiddlerList: "Getting the list of available tiddlers",
1394 step3Title: "Step 3: Choose the tiddlers to import",
1395 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'>",
1396 importLabel: "import",
1397 importPrompt: "Import these tiddlers",
1398 confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
1399 step4Title: "Step 4: Importing %0 tiddler(s)",
1400 step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
1401 doneLabel: "done",
1402 donePrompt: "Close this wizard",
1403 statusDoingImport: "Importing tiddlers",
1404 statusDoneImport: "All tiddlers imported",
1405 systemServerNamePattern: "%2 on %1",
1406 systemServerNamePatternNoWorkspace: "%1",
1407 confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
1408 serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
1409 serverSaveModifier: "(System)",
1410 listViewTemplate: {
1411 columns: [
1412 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1413 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1414 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1415 {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
1417 rowClasses: [
1421 merge(config.macros.sync,{
1422 listViewTemplate: {
1423 columns: [
1424 {name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
1425 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1426 {name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
1427 {name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
1428 {name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
1429 {name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
1430 {name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
1432 rowClasses: [
1434 buttons: [
1435 {caption: "Sync these tiddlers", name: 'sync'}
1437 wizardTitle: "Synchronize with external servers and files",
1438 step1Title: "Choose the tiddlers you want to synchronize",
1439 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1440 syncLabel: "sync",
1441 syncPrompt: "Sync these tiddlers",
1442 hasChanged: "Changed while unplugged",
1443 hasNotChanged: "Unchanged while unplugged",
1444 syncStatusList: {
1445 none: {text: "...", color: "transparent"},
1446 changedServer: {text: "Changed on server", color: '#80ff80'},
1447 changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
1448 changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
1449 notFound: {text: "Not found on server", color: '#ffff80'},
1450 putToServer: {text: "Saved update on server", color: '#ff80ff'},
1451 gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
1455 merge(config.macros.annotations,{
1458 merge(config.commands.closeTiddler,{
1459 text: "close",
1460 tooltip: "Close this tiddler"});
1462 merge(config.commands.closeOthers,{
1463 text: "close others",
1464 tooltip: "Close all other tiddlers"});
1466 merge(config.commands.editTiddler,{
1467 text: "edit",
1468 tooltip: "Edit this tiddler",
1469 readOnlyText: "view",
1470 readOnlyTooltip: "View the source of this tiddler"});
1472 merge(config.commands.saveTiddler,{
1473 text: "done",
1474 tooltip: "Save changes to this tiddler"});
1476 merge(config.commands.cancelTiddler,{
1477 text: "cancel",
1478 tooltip: "Undo changes to this tiddler",
1479 warning: "Are you sure you want to abandon your changes to '%0'?",
1480 readOnlyText: "done",
1481 readOnlyTooltip: "View this tiddler normally"});
1483 merge(config.commands.deleteTiddler,{
1484 text: "delete",
1485 tooltip: "Delete this tiddler",
1486 warning: "Are you sure you want to delete '%0'?"});
1488 merge(config.commands.permalink,{
1489 text: "permalink",
1490 tooltip: "Permalink for this tiddler"});
1492 merge(config.commands.references,{
1493 text: "references",
1494 tooltip: "Show tiddlers that link to this one",
1495 popupNone: "No references"});
1497 merge(config.commands.jump,{
1498 text: "jump",
1499 tooltip: "Jump to another open tiddler"});
1501 merge(config.commands.syncing,{
1502 text: "syncing",
1503 tooltip: "Control synchronisation of this tiddler with a server or external file",
1504 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
1505 notCurrentlySyncing: "Not currently syncing",
1506 captionUnSync: "Stop synchronising this tiddler",
1507 chooseServer: "Synchronise this tiddler with another server:",
1508 currServerMarker: "\u25cf ",
1509 notCurrServerMarker: " "});
1511 merge(config.commands.fields,{
1512 text: "fields",
1513 tooltip: "Show the extended fields of this tiddler",
1514 emptyText: "There are no extended fields for this tiddler",
1515 listViewTemplate: {
1516 columns: [
1517 {name: 'Field', field: 'field', title: "Field", type: 'String'},
1518 {name: 'Value', field: 'value', title: "Value", type: 'String'}
1520 rowClasses: [
1522 buttons: [
1523 ]}});
1525 merge(config.shadowTiddlers,{
1526 DefaultTiddlers: "GettingStarted",
1527 MainMenu: "GettingStarted",
1528 SiteTitle: "My TiddlyWiki",
1529 SiteSubtitle: "a reusable non-linear personal web notebook",
1530 SiteUrl: "http://www.tiddlywiki.com/",
1531 SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>',
1532 SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
1533 TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'});
1535 merge(config.annotations,{
1536 AdvancedOptions: "This shadow tiddler provides access to several advanced options",
1537 ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
1538 DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
1539 EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
1540 GettingStarted: "This shadow tiddler provides basic usage instructions",
1541 ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
1542 MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
1543 MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
1544 MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
1545 MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
1546 MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately before the script block",
1547 OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
1548 PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
1549 PluginManager: "This shadow tiddler provides access to the plugin manager",
1550 SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
1551 SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
1552 SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
1553 SiteTitle: "This shadow tiddler is used as the first part of the page title",
1554 SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
1555 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.",
1556 StyleSheet: "This tiddler can contain custom CSS definitions",
1557 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.",
1558 StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
1559 StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
1560 TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
1561 TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
1562 TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
1563 TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
1564 TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
1565 TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
1566 TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
1567 ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
1570 //--
1571 //-- Main
1572 //--
1574 var params = null; // Command line parameters
1575 var store = null; // TiddlyWiki storage
1576 var story = null; // Main story
1577 var formatter = null; // Default formatters for the wikifier
1578 config.parsers = {}; // Hashmap of alternative parsers for the wikifier
1579 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
1580 var readOnly = false; // Whether we're in readonly mode
1581 var highlightHack = null; // Embarrassing hack department...
1582 var hadConfirmExit = false; // Don't warn more than once
1583 var safeMode = false; // Disable all plugins and cookies
1584 var showBackstage; // Whether to include the backstage area
1585 var installedPlugins = []; // Information filled in when plugins are executed
1586 var startingUp = false; // Whether we're in the process of starting up
1587 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
1589 // Whether to use the JavaSaver applet
1590 var useJavaSaver = config.browser.isSafari || config.browser.isOpera;
1592 // Starting up
1593 function main()
1595 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1596 startingUp = true;
1597 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1598 params = getParameters();
1599 if(params)
1600 params = params.parseParams("open",null,false);
1601 store = new TiddlyWiki();
1602 invokeParamifier(params,"oninit");
1603 story = new Story("tiddlerDisplay","tiddler");
1604 addEvent(document,"click",Popup.onDocumentClick);
1605 saveTest();
1606 loadOptionsCookie();
1607 for(var s=0; s<config.notifyTiddlers.length; s++)
1608 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1609 t1 = new Date();
1610 store.loadFromDiv("storeArea","store",true);
1611 t2 = new Date();
1612 loadShadowTiddlers();
1613 t3 = new Date();
1614 invokeParamifier(params,"onload");
1615 t4 = new Date();
1616 readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
1617 showBackstage = !readOnly;
1618 var pluginProblem = loadPlugins();
1619 t5 = new Date();
1620 formatter = new Formatter(config.formatters);
1621 story.switchTheme(config.options.txtTheme);
1622 invokeParamifier(params,"onconfig");
1623 t6 = new Date();
1624 store.notifyAll();
1625 t7 = new Date();
1626 restart();
1627 t8 = new Date();
1628 if(pluginProblem) {
1629 story.displayTiddler(null,"PluginManager");
1630 displayMessage(config.messages.customConfigError);
1632 for(var m in config.macros) {
1633 if(config.macros[m].init)
1634 config.macros[m].init();
1636 t9 = new Date();
1637 if(showBackstage)
1638 backstage.init();
1639 t10 = new Date();
1640 if(config.options.chkDisplayStartupTime) {
1641 displayMessage("LoadFromDiv " + (t2-t1) + " ms");
1642 displayMessage("LoadShadows " + (t3-t2) + " ms");
1643 displayMessage("LoadPlugins " + (t5-t4) + " ms");
1644 displayMessage("Notify " + (t7-t6) + " ms");
1645 displayMessage("Restart " + (t8-t7) + " ms");
1646 displayMessage("Macro init " + (t9-t8) + " ms");
1647 displayMessage("Total: " + (t10-t0) + " ms");
1649 startingUp = false;
1652 // Restarting
1653 function restart()
1655 invokeParamifier(params,"onstart");
1656 if(story.isEmpty()) {
1657 var tiddlers = store.filterTiddlers(store.getTiddlerText("DefaultTiddlers"));
1658 story.displayTiddlers(null,tiddlers);
1660 window.scrollTo(0,0);
1663 function saveTest()
1665 var s = document.getElementById("saveTest");
1666 if(s.hasChildNodes())
1667 alert(config.messages.savedSnapshotError);
1668 s.appendChild(document.createTextNode("savetest"));
1671 function loadShadowTiddlers()
1673 var shadows = new TiddlyWiki();
1674 shadows.loadFromDiv("shadowArea","shadows",true);
1675 shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
1676 delete shadows;
1679 function loadPlugins()
1681 if(safeMode)
1682 return false;
1683 var tiddlers = store.getTaggedTiddlers("systemConfig");
1684 var toLoad = [];
1685 var nLoaded = 0;
1686 var map = {};
1687 var nPlugins = tiddlers.length;
1688 installedPlugins = [];
1689 for(var i=0; i<nPlugins; i++) {
1690 var p = getPluginInfo(tiddlers[i]);
1691 installedPlugins[i] = p;
1692 var n = p.Name;
1693 if(n)
1694 map[n] = p;
1695 n = p.Source;
1696 if(n)
1697 map[n] = p;
1699 var visit = function(p) {
1700 if(!p || p.done)
1701 return;
1702 p.done = 1;
1703 var reqs = p.Requires;
1704 if(reqs) {
1705 reqs = reqs.readBracketedList();
1706 for(var i=0; i<reqs.length; i++)
1707 visit(map[reqs[i]]);
1709 toLoad.push(p);
1711 for(i=0; i<nPlugins; i++)
1712 visit(installedPlugins[i]);
1713 for(i=0; i<toLoad.length; i++) {
1714 p = toLoad[i];
1715 pluginInfo = p;
1716 tiddler = p.tiddler;
1717 if(isPluginExecutable(p)) {
1718 if(isPluginEnabled(p)) {
1719 p.executed = true;
1720 var startTime = new Date();
1721 try {
1722 if(tiddler.text)
1723 window.eval(tiddler.text);
1724 nLoaded++;
1725 } catch(ex) {
1726 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
1727 p.error = true;
1729 pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
1730 } else {
1731 nPlugins--;
1733 } else {
1734 p.warning = true;
1737 return nLoaded != nPlugins;
1740 function getPluginInfo(tiddler)
1742 var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
1743 p.tiddler = tiddler;
1744 p.title = tiddler.title;
1745 p.log = [];
1746 return p;
1749 // Check that a particular plugin is valid for execution
1750 function isPluginExecutable(plugin)
1752 if(plugin.tiddler.isTagged("systemConfigForce"))
1753 return verifyTail(plugin,true,config.messages.pluginForced);
1754 if(plugin["CoreVersion"]) {
1755 var coreVersion = plugin["CoreVersion"].split(".");
1756 var w = parseInt(coreVersion[0]) - version.major;
1757 if(w == 0 && coreVersion[1])
1758 w = parseInt(coreVersion[1]) - version.minor;
1759 if(w == 0 && coreVersion[2])
1760 w = parseInt(coreVersion[2]) - version.revision;
1761 if(w > 0)
1762 return verifyTail(plugin,false,config.messages.pluginVersionError);
1764 return true;
1767 function isPluginEnabled(plugin)
1769 if(plugin.tiddler.isTagged("systemConfigDisable"))
1770 return verifyTail(plugin,false,config.messages.pluginDisabled);
1771 return true;
1774 function verifyTail(plugin,result,message)
1776 plugin.log.push(message);
1777 return result;
1780 function invokeMacro(place,macro,params,wikifier,tiddler)
1782 try {
1783 var m = config.macros[macro];
1784 if(m && m.handler)
1785 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
1786 else
1787 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
1788 } catch(ex) {
1789 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
1793 //--
1794 //-- Paramifiers
1795 //--
1797 function getParameters()
1799 var p = null;
1800 if(window.location.hash) {
1801 p = decodeURI(window.location.hash.substr(1));
1802 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
1803 p = convertUTF8ToUnicode(p);
1805 return p;
1808 function invokeParamifier(params,handler)
1810 if(!params || params.length == undefined || params.length <= 1)
1811 return;
1812 for(var t=1; t<params.length; t++) {
1813 var p = config.paramifiers[params[t].name];
1814 if(p && p[handler] instanceof Function)
1815 p[handler](params[t].value);
1819 config.paramifiers = {};
1821 config.paramifiers.start = {
1822 oninit: function(v) {
1823 safeMode = v.toLowerCase() == "safe";
1827 config.paramifiers.open = {
1828 onstart: function(v) {
1829 story.displayTiddler("bottom",v,null,false,null);
1833 config.paramifiers.story = {
1834 onstart: function(v) {
1835 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
1836 invokeParamifier(list,"onstart");
1840 config.paramifiers.search = {
1841 onstart: function(v) {
1842 story.search(v,false,false);
1846 config.paramifiers.searchRegExp = {
1847 onstart: function(v) {
1848 story.prototype.search(v,false,true);
1852 config.paramifiers.tag = {
1853 onstart: function(v) {
1854 var tagged = store.getTaggedTiddlers(v,"title");
1855 story.displayTiddlers(null,tagged,null,false,null);
1859 config.paramifiers.newTiddler = {
1860 onstart: function(v) {
1861 if(!readOnly) {
1862 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
1863 story.focusTiddler(v,"text");
1868 config.paramifiers.newJournal = {
1869 onstart: function(v) {
1870 if(!readOnly) {
1871 var now = new Date();
1872 var title = now.formatString(v.trim());
1873 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
1874 story.focusTiddler(title,"text");
1879 config.paramifiers.readOnly = {
1880 onconfig: function(v) {
1881 var p = v.toLowerCase();
1882 readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
1887 config.paramifiers.theme = {
1888 onconfig: function(v) {
1889 story.switchTheme(v);
1893 //--
1894 //-- Formatter helpers
1895 //--
1897 function Formatter(formatters)
1899 this.formatters = [];
1900 var pattern = [];
1901 for(var n=0; n<formatters.length; n++) {
1902 pattern.push("(" + formatters[n].match + ")");
1903 this.formatters.push(formatters[n]);
1905 this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
1908 config.formatterHelpers = {
1910 createElementAndWikify: function(w)
1912 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
1915 inlineCssHelper: function(w)
1917 var styles = [];
1918 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
1919 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
1920 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
1921 var s,v;
1922 if(lookaheadMatch[1]) {
1923 s = lookaheadMatch[1].unDash();
1924 v = lookaheadMatch[2];
1925 } else {
1926 s = lookaheadMatch[3].unDash();
1927 v = lookaheadMatch[4];
1929 if (s=="bgcolor")
1930 s = "backgroundColor";
1931 styles.push({style: s, value: v});
1932 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
1933 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
1934 lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
1936 return styles;
1939 applyCssHelper: function(e,styles)
1941 for(var t=0; t< styles.length; t++) {
1942 try {
1943 e.style[styles[t].style] = styles[t].value;
1944 } catch (ex) {
1949 enclosedTextHelper: function(w)
1951 this.lookaheadRegExp.lastIndex = w.matchStart;
1952 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1953 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
1954 var text = lookaheadMatch[1];
1955 if(config.browser.isIE)
1956 text = text.replace(/\n/g,"\r");
1957 createTiddlyElement(w.output,this.element,null,null,text);
1958 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
1962 isExternalLink: function(link)
1964 if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
1965 return false;
1967 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
1968 if(urlRegExp.exec(link)) {
1969 return true;
1971 if (link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
1972 return true;
1974 return false;
1979 //--
1980 //-- Standard formatters
1981 //--
1983 config.formatters = [
1985 name: "table",
1986 match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
1987 lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
1988 rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
1989 cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
1990 cellTermRegExp: /((?:\x20*)\|)/mg,
1991 rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
1992 handler: function(w)
1994 var table = createTiddlyElement(w.output,"table",null,"twtable");
1995 var prevColumns = [];
1996 var currRowType = null;
1997 var rowContainer;
1998 var rowCount = 0;
1999 w.nextMatch = w.matchStart;
2000 this.lookaheadRegExp.lastIndex = w.nextMatch;
2001 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2002 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2003 var nextRowType = lookaheadMatch[2];
2004 if(nextRowType == "k") {
2005 table.className = lookaheadMatch[1];
2006 w.nextMatch += lookaheadMatch[0].length+1;
2007 } else {
2008 if(nextRowType != currRowType) {
2009 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2010 currRowType = nextRowType;
2012 if(currRowType == "c") {
2013 // Caption
2014 w.nextMatch++;
2015 if(rowContainer != table.firstChild)
2016 table.insertBefore(rowContainer,table.firstChild);
2017 rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
2018 w.subWikifyTerm(rowContainer,this.rowTermRegExp);
2019 } else {
2020 var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
2021 theRow.onmouseover = function() {addClass(this,"hoverRow");};
2022 theRow.onmouseout = function() {removeClass(this,"hoverRow");};
2023 this.rowHandler(w,theRow,prevColumns);
2024 rowCount++;
2027 this.lookaheadRegExp.lastIndex = w.nextMatch;
2028 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2031 rowHandler: function(w,e,prevColumns)
2033 var col = 0;
2034 var colSpanCount = 1;
2035 var prevCell = null;
2036 this.cellRegExp.lastIndex = w.nextMatch;
2037 var cellMatch = this.cellRegExp.exec(w.source);
2038 while(cellMatch && cellMatch.index == w.nextMatch) {
2039 if(cellMatch[1] == "~") {
2040 // Rowspan
2041 var last = prevColumns[col];
2042 if(last) {
2043 last.rowSpanCount++;
2044 last.element.setAttribute("rowspan",last.rowSpanCount);
2045 last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
2046 last.element.valign = "center";
2048 w.nextMatch = this.cellRegExp.lastIndex-1;
2049 } else if(cellMatch[1] == ">") {
2050 // Colspan
2051 colSpanCount++;
2052 w.nextMatch = this.cellRegExp.lastIndex-1;
2053 } else if(cellMatch[2]) {
2054 // End of row
2055 if(prevCell && colSpanCount > 1) {
2056 prevCell.setAttribute("colspan",colSpanCount);
2057 prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
2059 w.nextMatch = this.cellRegExp.lastIndex;
2060 break;
2061 } else {
2062 // Cell
2063 w.nextMatch++;
2064 var styles = config.formatterHelpers.inlineCssHelper(w);
2065 var spaceLeft = false;
2066 var chr = w.source.substr(w.nextMatch,1);
2067 while(chr == " ") {
2068 spaceLeft = true;
2069 w.nextMatch++;
2070 chr = w.source.substr(w.nextMatch,1);
2072 var cell;
2073 if(chr == "!") {
2074 cell = createTiddlyElement(e,"th");
2075 w.nextMatch++;
2076 } else {
2077 cell = createTiddlyElement(e,"td");
2079 prevCell = cell;
2080 prevColumns[col] = {rowSpanCount:1,element:cell};
2081 if(colSpanCount > 1) {
2082 cell.setAttribute("colspan",colSpanCount);
2083 cell.setAttribute("colSpan",colSpanCount); // Needed for IE
2084 colSpanCount = 1;
2086 config.formatterHelpers.applyCssHelper(cell,styles);
2087 w.subWikifyTerm(cell,this.cellTermRegExp);
2088 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
2089 cell.align = spaceLeft ? "center" : "left";
2090 else if(spaceLeft)
2091 cell.align = "right";
2092 w.nextMatch--;
2094 col++;
2095 this.cellRegExp.lastIndex = w.nextMatch;
2096 cellMatch = this.cellRegExp.exec(w.source);
2102 name: "heading",
2103 match: "^!{1,6}",
2104 termRegExp: /(\n)/mg,
2105 handler: function(w)
2107 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
2112 name: "list",
2113 match: "^(?:[\\*#;:]+)",
2114 lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
2115 termRegExp: /(\n)/mg,
2116 handler: function(w)
2118 var stack = [w.output];
2119 var currLevel = 0, currType = null;
2120 var listLevel, listType, itemType, baseType;
2121 w.nextMatch = w.matchStart;
2122 this.lookaheadRegExp.lastIndex = w.nextMatch;
2123 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2124 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2125 if(lookaheadMatch[1]) {
2126 listType = "ul";
2127 itemType = "li";
2128 } else if(lookaheadMatch[2]) {
2129 listType = "ol";
2130 itemType = "li";
2131 } else if(lookaheadMatch[3]) {
2132 listType = "dl";
2133 itemType = "dt";
2134 } else if(lookaheadMatch[4]) {
2135 listType = "dl";
2136 itemType = "dd";
2138 if(!baseType)
2139 baseType = listType;
2140 listLevel = lookaheadMatch[0].length;
2141 w.nextMatch += lookaheadMatch[0].length;
2142 var t;
2143 if(listLevel > currLevel) {
2144 for(t=currLevel; t<listLevel; t++) {
2145 var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
2146 stack.push(createTiddlyElement(target,listType));
2148 } else if(listType!=baseType && listLevel==1) {
2149 w.nextMatch -= lookaheadMatch[0].length;
2150 return;
2151 } else if(listLevel < currLevel) {
2152 for(t=currLevel; t>listLevel; t--)
2153 stack.pop();
2154 } else if(listLevel == currLevel && listType != currType) {
2155 stack.pop();
2156 stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
2158 currLevel = listLevel;
2159 currType = listType;
2160 var e = createTiddlyElement(stack[stack.length-1],itemType);
2161 w.subWikifyTerm(e,this.termRegExp);
2162 this.lookaheadRegExp.lastIndex = w.nextMatch;
2163 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2169 name: "quoteByBlock",
2170 match: "^<<<\\n",
2171 termRegExp: /(^<<<(\n|$))/mg,
2172 element: "blockquote",
2173 handler: config.formatterHelpers.createElementAndWikify
2177 name: "quoteByLine",
2178 match: "^>+",
2179 lookaheadRegExp: /^>+/mg,
2180 termRegExp: /(\n)/mg,
2181 element: "blockquote",
2182 handler: function(w)
2184 var stack = [w.output];
2185 var currLevel = 0;
2186 var newLevel = w.matchLength;
2187 var t;
2188 do {
2189 if(newLevel > currLevel) {
2190 for(t=currLevel; t<newLevel; t++)
2191 stack.push(createTiddlyElement(stack[stack.length-1],this.element));
2192 } else if(newLevel < currLevel) {
2193 for(t=currLevel; t>newLevel; t--)
2194 stack.pop();
2196 currLevel = newLevel;
2197 w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
2198 createTiddlyElement(stack[stack.length-1],"br");
2199 this.lookaheadRegExp.lastIndex = w.nextMatch;
2200 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2201 var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
2202 if(matched) {
2203 newLevel = lookaheadMatch[0].length;
2204 w.nextMatch += lookaheadMatch[0].length;
2206 } while(matched);
2211 name: "rule",
2212 match: "^----+$\\n?",
2213 handler: function(w)
2215 createTiddlyElement(w.output,"hr");
2220 name: "monospacedByLine",
2221 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
2222 element: "pre",
2223 handler: function(w)
2225 switch(w.matchText) {
2226 case "/*{{{*/\n": // CSS
2227 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2228 break;
2229 case "{{{\n": // monospaced block
2230 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2231 break;
2232 case "//{{{\n": // plugin
2233 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2234 break;
2235 case "<!--{{{-->\n": //template
2236 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
2237 break;
2238 default:
2239 break;
2241 config.formatterHelpers.enclosedTextHelper.call(this,w);
2246 name: "wikifyComment",
2247 match: "^(?:/\\*\\*\\*|<!---)\\n",
2248 handler: function(w)
2250 var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
2251 w.subWikifyTerm(w.output,termRegExp);
2256 name: "macro",
2257 match: "<<",
2258 lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
2259 handler: function(w)
2261 this.lookaheadRegExp.lastIndex = w.matchStart;
2262 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2263 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
2264 w.nextMatch = this.lookaheadRegExp.lastIndex;
2265 invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
2271 name: "prettyLink",
2272 match: "\\[\\[",
2273 lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
2274 handler: function(w)
2276 this.lookaheadRegExp.lastIndex = w.matchStart;
2277 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2278 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2279 var e;
2280 var text = lookaheadMatch[1];
2281 if(lookaheadMatch[3]) {
2282 // Pretty bracketted link
2283 var link = lookaheadMatch[3];
2284 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
2285 createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2286 } else {
2287 // Simple bracketted link
2288 e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
2290 createTiddlyText(e,text);
2291 w.nextMatch = this.lookaheadRegExp.lastIndex;
2297 name: "wikiLink",
2298 match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
2299 handler: function(w)
2301 if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
2302 w.outputText(w.output,w.matchStart+1,w.nextMatch);
2303 return;
2305 if(w.matchStart > 0) {
2306 var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
2307 preRegExp.lastIndex = w.matchStart-1;
2308 var preMatch = preRegExp.exec(w.source);
2309 if(preMatch.index == w.matchStart-1) {
2310 w.outputText(w.output,w.matchStart,w.nextMatch);
2311 return;
2314 if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
2315 var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
2316 w.outputText(link,w.matchStart,w.nextMatch);
2317 } else {
2318 w.outputText(w.output,w.matchStart,w.nextMatch);
2324 name: "urlLink",
2325 match: config.textPrimitives.urlPattern,
2326 handler: function(w)
2328 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
2333 name: "image",
2334 match: "\\[[<>]?[Ii][Mm][Gg]\\[",
2335 lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
2336 handler: function(w)
2338 this.lookaheadRegExp.lastIndex = w.matchStart;
2339 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2340 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2341 var e = w.output;
2342 if(lookaheadMatch[5]) {
2343 var link = lookaheadMatch[5];
2344 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2345 addClass(e,"imageLink");
2347 var img = createTiddlyElement(e,"img");
2348 if(lookaheadMatch[1])
2349 img.align = "left";
2350 else if(lookaheadMatch[2])
2351 img.align = "right";
2352 if(lookaheadMatch[3])
2353 img.title = lookaheadMatch[3];
2354 img.src = lookaheadMatch[4];
2355 w.nextMatch = this.lookaheadRegExp.lastIndex;
2361 name: "html",
2362 match: "<[Hh][Tt][Mm][Ll]>",
2363 lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
2364 handler: function(w)
2366 this.lookaheadRegExp.lastIndex = w.matchStart;
2367 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2368 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2369 createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
2370 w.nextMatch = this.lookaheadRegExp.lastIndex;
2376 name: "commentByBlock",
2377 match: "/%",
2378 lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
2379 handler: function(w)
2381 this.lookaheadRegExp.lastIndex = w.matchStart;
2382 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2383 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
2384 w.nextMatch = this.lookaheadRegExp.lastIndex;
2389 name: "characterFormat",
2390 match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
2391 handler: function(w)
2393 switch(w.matchText) {
2394 case "''":
2395 w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
2396 break;
2397 case "//":
2398 w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
2399 break;
2400 case "__":
2401 w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
2402 break;
2403 case "^^":
2404 w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
2405 break;
2406 case "~~":
2407 w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
2408 break;
2409 case "--":
2410 w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
2411 break;
2412 case "{{{":
2413 var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
2414 lookaheadRegExp.lastIndex = w.matchStart;
2415 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2416 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2417 createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
2418 w.nextMatch = lookaheadRegExp.lastIndex;
2420 break;
2426 name: "customFormat",
2427 match: "@@|\\{\\{",
2428 handler: function(w)
2430 switch(w.matchText) {
2431 case "@@":
2432 var e = createTiddlyElement(w.output,"span");
2433 var styles = config.formatterHelpers.inlineCssHelper(w);
2434 if(styles.length == 0)
2435 e.className = "marked";
2436 else
2437 config.formatterHelpers.applyCssHelper(e,styles);
2438 w.subWikifyTerm(e,/(@@)/mg);
2439 break;
2440 case "{{":
2441 lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
2442 lookaheadRegExp.lastIndex = w.matchStart;
2443 lookaheadMatch = lookaheadRegExp.exec(w.source);
2444 if(lookaheadMatch) {
2445 w.nextMatch = lookaheadRegExp.lastIndex;
2446 e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
2447 w.subWikifyTerm(e,/(\}\}\})/mg);
2449 break;
2455 name: "mdash",
2456 match: "--",
2457 handler: function(w)
2459 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
2464 name: "lineBreak",
2465 match: "\\n|<br ?/?>",
2466 handler: function(w)
2468 createTiddlyElement(w.output,"br");
2473 name: "rawText",
2474 match: "\\\"{3}|<nowiki>",
2475 lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
2476 handler: function(w)
2478 this.lookaheadRegExp.lastIndex = w.matchStart;
2479 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2480 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2481 createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
2482 w.nextMatch = this.lookaheadRegExp.lastIndex;
2488 name: "htmlEntitiesEncoding",
2489 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};)",
2490 handler: function(w)
2492 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
2498 //--
2499 //-- Wikifier
2500 //--
2502 function getParser(tiddler,format)
2504 if(tiddler) {
2505 if(!format)
2506 format = tiddler.fields["wikiformat"];
2507 var i;
2508 if(format) {
2509 for(i in config.parsers) {
2510 if(format == config.parsers[i].format)
2511 return config.parsers[i];
2513 } else {
2514 for(i in config.parsers) {
2515 if(tiddler.isTagged(config.parsers[i].formatTag))
2516 return config.parsers[i];
2520 return formatter;
2523 function wikify(source,output,highlightRegExp,tiddler)
2525 if(source && source != "") {
2526 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
2527 wikifier.subWikifyUnterm(output);
2531 function wikifyStatic(source,highlightRegExp,tiddler,format)
2533 var e = createTiddlyElement(document.body,"div");
2534 e.style.display = "none";
2535 var html = "";
2536 if(source && source != "") {
2537 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2538 wikifier.isStatic = true;
2539 wikifier.subWikifyUnterm(e);
2540 html = e.innerHTML;
2541 removeNode(e);
2543 return html;
2546 function wikifyPlain(title,theStore,limit)
2548 if(!theStore)
2549 theStore = store;
2550 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2551 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2552 } else {
2553 return "";
2557 function wikifyPlainText(text,limit,tiddler)
2559 if(limit > 0)
2560 text = text.substr(0,limit);
2561 var wikifier = new Wikifier(text,formatter,null,tiddler);
2562 return wikifier.wikifyPlain();
2565 function highlightify(source,output,highlightRegExp,tiddler)
2567 if(source && source != "") {
2568 var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
2569 wikifier.outputText(output,0,source.length);
2573 function Wikifier(source,formatter,highlightRegExp,tiddler)
2575 this.source = source;
2576 this.output = null;
2577 this.formatter = formatter;
2578 this.nextMatch = 0;
2579 this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
2580 this.highlightRegExp = highlightRegExp;
2581 this.highlightMatch = null;
2582 this.isStatic = false;
2583 if(highlightRegExp) {
2584 highlightRegExp.lastIndex = 0;
2585 this.highlightMatch = highlightRegExp.exec(source);
2587 this.tiddler = tiddler;
2590 Wikifier.prototype.wikifyPlain = function()
2592 var e = createTiddlyElement(document.body,"div");
2593 this.subWikify(e);
2594 var text = getPlainText(e);
2595 removeNode(e);
2596 return text;
2599 Wikifier.prototype.subWikify = function(output,terminator)
2601 if(terminator)
2602 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
2603 else
2604 this.subWikifyUnterm(output);
2607 Wikifier.prototype.subWikifyUnterm = function(output)
2609 var oldOutput = this.output;
2610 this.output = output;
2611 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2612 var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2613 while(formatterMatch) {
2614 // Output any text before the match
2615 if(formatterMatch.index > this.nextMatch)
2616 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2617 // Set the match parameters for the handler
2618 this.matchStart = formatterMatch.index;
2619 this.matchLength = formatterMatch[0].length;
2620 this.matchText = formatterMatch[0];
2621 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2622 for(var t=1; t<formatterMatch.length; t++) {
2623 if(formatterMatch[t]) {
2624 this.formatter.formatters[t-1].handler(this);
2625 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2626 break;
2629 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2631 if(this.nextMatch < this.source.length) {
2632 this.outputText(this.output,this.nextMatch,this.source.length);
2633 this.nextMatch = this.source.length;
2635 this.output = oldOutput;
2638 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
2640 var oldOutput = this.output;
2641 this.output = output;
2642 terminatorRegExp.lastIndex = this.nextMatch;
2643 var terminatorMatch = terminatorRegExp.exec(this.source);
2644 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2645 var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2646 while(terminatorMatch || formatterMatch) {
2647 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
2648 if(terminatorMatch.index > this.nextMatch)
2649 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
2650 this.matchText = terminatorMatch[1];
2651 this.matchLength = terminatorMatch[1].length;
2652 this.matchStart = terminatorMatch.index;
2653 this.nextMatch = this.matchStart + this.matchLength;
2654 this.output = oldOutput;
2655 return;
2657 if(formatterMatch.index > this.nextMatch)
2658 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2659 this.matchStart = formatterMatch.index;
2660 this.matchLength = formatterMatch[0].length;
2661 this.matchText = formatterMatch[0];
2662 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2663 for(var t=1; t<formatterMatch.length; t++) {
2664 if(formatterMatch[t]) {
2665 this.formatter.formatters[t-1].handler(this);
2666 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2667 break;
2670 terminatorRegExp.lastIndex = this.nextMatch;
2671 terminatorMatch = terminatorRegExp.exec(this.source);
2672 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2674 if(this.nextMatch < this.source.length) {
2675 this.outputText(this.output,this.nextMatch,this.source.length);
2676 this.nextMatch = this.source.length;
2678 this.output = oldOutput;
2681 Wikifier.prototype.outputText = function(place,startPos,endPos)
2683 while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
2684 if(this.highlightMatch.index > startPos) {
2685 createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
2686 startPos = this.highlightMatch.index;
2688 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
2689 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
2690 startPos = highlightEnd;
2691 if(startPos >= this.highlightRegExp.lastIndex)
2692 this.highlightMatch = this.highlightRegExp.exec(this.source);
2694 if(startPos < endPos) {
2695 createTiddlyText(place,this.source.substring(startPos,endPos));
2699 //--
2700 //-- Macro definitions
2701 //--
2703 config.macros.today.handler = function(place,macroName,params)
2705 var now = new Date();
2706 var text = params[0] ? now.formatString(params[0].trim()) : text = now.toLocaleString();
2707 createTiddlyElement(place,"span",null,null,text);
2710 config.macros.version.handler = function(place)
2712 createTiddlyElement(place,"span",null,null,version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
2715 config.macros.list.handler = function(place,macroName,params)
2717 var type = params[0] ? params[0] : "all";
2718 var list = document.createElement("ul");
2719 place.appendChild(list);
2720 if(this[type].prompt)
2721 createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
2722 var results;
2723 if(this[type].handler)
2724 results = this[type].handler(params);
2725 for(var t = 0; t < results.length; t++) {
2726 var li = document.createElement("li");
2727 list.appendChild(li);
2728 createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
2732 config.macros.list.all.handler = function(params)
2734 return store.reverseLookup("tags","excludeLists",false,"title");
2737 config.macros.list.missing.handler = function(params)
2739 return store.getMissingLinks();
2742 config.macros.list.orphans.handler = function(params)
2744 return store.getOrphans();
2747 config.macros.list.shadowed.handler = function(params)
2749 return store.getShadowed();
2752 config.macros.list.touched.handler = function(params)
2754 return store.getTouched();
2757 config.macros.list.filter.handler = function(params)
2759 var filter = params[1];
2760 var results = [];
2761 if(filter) {
2762 var tiddlers = store.filterTiddlers(filter);
2763 for(var t=0; t<tiddlers.length; t++)
2764 results.push(tiddlers[t].title);
2766 return results;
2769 config.macros.allTags.handler = function(place,macroName,params)
2771 var tags = store.getTags(params[0]);
2772 var ul = createTiddlyElement(place,"ul");
2773 if(tags.length == 0)
2774 createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
2775 for(var t=0; t<tags.length; t++) {
2776 var title = tags[t][0];
2777 var info = getTiddlyLinkInfo(title);
2778 var li =createTiddlyElement(ul,"li");
2779 var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
2780 btn.setAttribute("tag",title);
2781 btn.setAttribute("refresh","link");
2782 btn.setAttribute("tiddlyLink",title);
2786 config.macros.timeline.handler = function(place,macroName,params)
2788 var field = params[0] ? params[0] : "modified";
2789 var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
2790 var lastDay = "";
2791 var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
2792 var dateFormat = params[2] ? params[2] : this.dateFormat;
2793 for(var t=tiddlers.length-1; t>=last; t--) {
2794 var tiddler = tiddlers[t];
2795 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
2796 if(theDay != lastDay) {
2797 var ul = document.createElement("ul");
2798 place.appendChild(ul);
2799 createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
2800 lastDay = theDay;
2802 createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
2806 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2808 params = paramString.parseParams("name",null,true,false,true);
2809 var names = params[0]["name"];
2810 var tiddlerName = names[0];
2811 var className = names[1] ? names[1] : null;
2812 var args = params[0]["with"];
2813 var wrapper = createTiddlyElement(place,"span",null,className);
2814 if(!args) {
2815 wrapper.setAttribute("refresh","content");
2816 wrapper.setAttribute("tiddler",tiddlerName);
2818 var text = store.getTiddlerText(tiddlerName);
2819 if(text) {
2820 var stack = config.macros.tiddler.tiddlerStack;
2821 if(stack.indexOf(tiddlerName) !== -1)
2822 return;
2823 stack.push(tiddlerName);
2824 try {
2825 var n = args ? Math.min(args.length,9) : 0;
2826 for(var i=0; i<n; i++) {
2827 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
2828 text = text.replace(placeholderRE,args[i]);
2830 config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
2831 } finally {
2832 stack.pop();
2837 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
2839 wikify(text,place,null,store.getTiddler(tiddlerName));
2842 config.macros.tiddler.tiddlerStack = [];
2844 config.macros.tag.handler = function(place,macroName,params)
2846 createTagButton(place,params[0]);
2849 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2851 params = paramString.parseParams("anon",null,true,false,false);
2852 var ul = createTiddlyElement(place,"ul");
2853 var title = getParam(params,"anon","");
2854 if(title && store.tiddlerExists(title))
2855 tiddler = store.getTiddler(title);
2856 var sep = getParam(params,"sep"," ");
2857 var lingo = config.views.wikified.tag;
2858 var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
2859 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
2860 for(var t=0; t<tiddler.tags.length; t++) {
2861 createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
2862 if(t<tiddler.tags.length-1)
2863 createTiddlyText(ul,sep);
2867 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2869 params = paramString.parseParams("anon",null,true,false,false);
2870 var ul = createTiddlyElement(place,"ul");
2871 var title = getParam(params,"anon","");
2872 if(title == "" && tiddler instanceof Tiddler)
2873 title = tiddler.title;
2874 var sep = getParam(params,"sep"," ");
2875 ul.setAttribute("title",this.tooltip.format([title]));
2876 var tagged = store.getTaggedTiddlers(title);
2877 var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
2878 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
2879 for(var t=0; t<tagged.length; t++) {
2880 createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
2881 if(t<tagged.length-1)
2882 createTiddlyText(ul,sep);
2886 config.macros.closeAll.handler = function(place)
2888 createTiddlyButton(place,this.label,this.prompt,this.onClick);
2891 config.macros.closeAll.onClick = function(e)
2893 story.closeAllTiddlers();
2894 return false;
2897 config.macros.permaview.handler = function(place)
2899 createTiddlyButton(place,this.label,this.prompt,this.onClick);
2902 config.macros.permaview.onClick = function(e)
2904 story.permaView();
2905 return false;
2908 config.macros.saveChanges.handler = function(place)
2910 if(!readOnly)
2911 createTiddlyButton(place,this.label,this.prompt,this.onClick,null,null,this.accessKey);
2914 config.macros.saveChanges.onClick = function(e)
2916 saveChanges();
2917 return false;
2920 config.macros.slider.onClickSlider = function(ev)
2922 var e = ev ? ev : window.event;
2923 var n = this.nextSibling;
2924 var cookie = n.getAttribute("cookie");
2925 var isOpen = n.style.display != "none";
2926 if(config.options.chkAnimate && anim && typeof Slider == "function")
2927 anim.startAnimating(new Slider(n,!isOpen,null,"none"));
2928 else
2929 n.style.display = isOpen ? "none" : "block";
2930 config.options[cookie] = !isOpen;
2931 saveOptionCookie(cookie);
2932 return false;
2935 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
2937 var c = cookie ? cookie : "";
2938 var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
2939 var panel = createTiddlyElement(null,"div",null,"sliderPanel");
2940 panel.setAttribute("cookie",c);
2941 panel.style.display = config.options[c] ? "block" : "none";
2942 place.appendChild(panel);
2943 return panel;
2946 config.macros.slider.handler = function(place,macroName,params)
2948 var panel = this.createSlider(place,params[0],params[2],params[3]);
2949 var text = store.getTiddlerText(params[1]);
2950 panel.setAttribute("refresh","content");
2951 panel.setAttribute("tiddler",params[1]);
2952 if(text)
2953 wikify(text,panel,null,store.getTiddler(params[1]));
2956 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
2957 config.macros.gradient.handler = function(place,macroName,params,wikifier)
2959 var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
2960 panel.style.position = "relative";
2961 panel.style.overflow = "hidden";
2962 panel.style.zIndex = "0";
2963 if(wikifier) {
2964 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
2965 config.formatterHelpers.applyCssHelper(panel,styles);
2967 var colours = [];
2968 for(var t=1; t<params.length; t++) {
2969 var c = new RGB(params[t]);
2970 if(c)
2971 colours.push(c);
2973 drawGradient(panel,params[0] != "vert",colours);
2974 if(wikifier)
2975 wikifier.subWikify(panel,">>");
2976 if(document.all) {
2977 panel.style.height = "100%";
2978 panel.style.width = "100%";
2982 config.macros.message.handler = function(place,macroName,params)
2984 if(params[0]) {
2985 var m = config;
2986 var p = params[0].split(".");
2987 for(var t=0; t<p.length; t++) {
2988 if(p[t] in m)
2989 m = m[p[t]];
2990 else
2991 break;
2993 createTiddlyText(place,m.toString().format(params.splice(1)));
2997 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2999 if((tiddler instanceof Tiddler) && params[0]) {
3000 var value = store.getValue(tiddler,params[0]);
3001 if(value != undefined) {
3002 switch(params[1]) {
3003 case undefined:
3004 highlightify(value,place,highlightHack,tiddler);
3005 break;
3006 case "link":
3007 createTiddlyLink(place,value,true);
3008 break;
3009 case "wikified":
3010 wikify(value,place,highlightHack,tiddler);
3011 break;
3012 case "date":
3013 value = Date.convertFromYYYYMMDDHHMM(value);
3014 createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
3015 break;
3021 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3023 var field = params[0];
3024 var rows = params[1] || 0;
3025 var defVal = params[2] || '';
3026 if((tiddler instanceof Tiddler) && field) {
3027 story.setDirty(tiddler.title,true);
3028 var e,v;
3029 if(field != "text" && !rows) {
3030 e = createTiddlyElement(null,"input");
3031 if(tiddler.isReadOnly())
3032 e.setAttribute("readOnly","readOnly");
3033 e.setAttribute("edit",field);
3034 e.setAttribute("type","text");
3035 e.value = store.getValue(tiddler,field) || defVal;
3036 e.setAttribute("size","40");
3037 e.setAttribute("autocomplete","off");
3038 place.appendChild(e);
3039 } else {
3040 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
3041 var wrapper2 = createTiddlyElement(wrapper1,"div");
3042 e = createTiddlyElement(wrapper2,"textarea");
3043 if(tiddler.isReadOnly())
3044 e.setAttribute("readOnly","readOnly");
3045 e.value = v = store.getValue(tiddler,field) || defVal;
3046 rows = rows ? rows : 10;
3047 var lines = v.match(/\n/mg);
3048 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
3049 if(lines != null && lines.length > rows)
3050 rows = lines.length + 5;
3051 rows = Math.min(rows,maxLines);
3052 e.setAttribute("rows",rows);
3053 e.setAttribute("edit",field);
3054 place.appendChild(wrapper1);
3056 return e;
3060 config.macros.tagChooser.onClick = function(ev)
3062 var e = ev ? ev : window.event;
3063 var lingo = config.views.editor.tagChooser;
3064 var popup = Popup.create(this);
3065 var tags = store.getTags();
3066 if(tags.length == 0)
3067 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
3068 for(var t=0; t<tags.length; t++) {
3069 var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
3070 tag.setAttribute("tag",tags[t][0]);
3071 tag.setAttribute("tiddler",this.getAttribute("tiddler"));
3073 Popup.show();
3074 e.cancelBubble = true;
3075 if(e.stopPropagation) e.stopPropagation();
3076 return false;
3079 config.macros.tagChooser.onTagClick = function(ev)
3081 var e = ev ? ev : window.event;
3082 var tag = this.getAttribute("tag");
3083 var title = this.getAttribute("tiddler");
3084 if(!readOnly)
3085 story.setTiddlerTag(title,tag,0);
3086 return false;
3089 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3091 if(tiddler instanceof Tiddler) {
3092 var lingo = config.views.editor.tagChooser;
3093 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
3094 btn.setAttribute("tiddler",tiddler.title);
3098 config.macros.refreshDisplay.handler = function(place)
3100 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3103 config.macros.refreshDisplay.onClick = function(e)
3105 refreshAll();
3106 return false;
3109 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3111 var title = tiddler ? tiddler.title : null;
3112 var a = title ? config.annotations[title] : null;
3113 if(!tiddler || !title || !a)
3114 return;
3115 var text = a.format([title]);
3116 wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
3119 //--
3120 //-- NewTiddler and NewJournal macros
3121 //--
3123 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
3125 var tags = [];
3126 for(var t=1; t<params.length; t++) {
3127 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
3128 tags.push(params[t].value);
3130 label = getParam(params,"label",label);
3131 prompt = getParam(params,"prompt",prompt);
3132 accessKey = getParam(params,"accessKey",accessKey);
3133 newFocus = getParam(params,"focus",newFocus);
3134 var customFields = getParam(params,"fields","");
3135 if(!customFields && !store.isShadowTiddler(title))
3136 customFields = String.encodeHashMap(config.defaultCustomFields);
3137 var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
3138 btn.setAttribute("newTitle",title);
3139 btn.setAttribute("isJournal",isJournal ? "true" : "false");
3140 if(tags.length > 0)
3141 btn.setAttribute("params",tags.join("|"));
3142 btn.setAttribute("newFocus",newFocus);
3143 btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
3144 if(customFields !== "")
3145 btn.setAttribute("customFields",customFields);
3146 var text = getParam(params,"text");
3147 if(text !== undefined)
3148 btn.setAttribute("newText",text);
3149 return btn;
3152 config.macros.newTiddler.onClickNewTiddler = function()
3154 var title = this.getAttribute("newTitle");
3155 if(this.getAttribute("isJournal") == "true") {
3156 var now = new Date();
3157 title = now.formatString(title.trim());
3159 var params = this.getAttribute("params");
3160 var tags = params ? params.split("|") : [];
3161 var focus = this.getAttribute("newFocus");
3162 var template = this.getAttribute("newTemplate");
3163 var customFields = this.getAttribute("customFields");
3164 story.displayTiddler(null,title,template,false,null,null);
3165 var tiddlerElem = document.getElementById(story.idPrefix + title);
3166 if(customFields)
3167 story.addCustomFields(tiddlerElem,customFields);
3168 var text = this.getAttribute("newText");
3169 if(typeof text == "string")
3170 story.getTiddlerField(title,"text").value = text.format([title]);
3171 for(var t=0;t<tags.length;t++)
3172 story.setTiddlerTag(title,tags[t],+1);
3173 story.focusTiddler(title,focus);
3174 return false;
3177 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3179 if(!readOnly) {
3180 params = paramString.parseParams("anon",null,true,false,false);
3181 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
3182 title = getParam(params,"title",title);
3183 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
3187 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3189 if(!readOnly) {
3190 params = paramString.parseParams("anon",null,true,false,false);
3191 var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
3192 title = getParam(params,"title",title);
3193 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
3197 //--
3198 //-- Search macro
3199 //--
3201 config.macros.search.handler = function(place,macroName,params)
3203 var searchTimeout = null;
3204 var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick);
3205 var txt = createTiddlyElement(place,"input",null,"txtOptionInput");
3206 if(params[0])
3207 txt.value = params[0];
3208 txt.onkeyup = this.onKeyPress;
3209 txt.onfocus = this.onFocus;
3210 txt.setAttribute("size",this.sizeTextbox);
3211 txt.setAttribute("accessKey",this.accessKey);
3212 txt.setAttribute("autocomplete","off");
3213 txt.setAttribute("lastSearchText","");
3214 if(config.browser.isSafari) {
3215 txt.setAttribute("type","search");
3216 txt.setAttribute("results","5");
3217 } else {
3218 txt.setAttribute("type","text");
3222 // Global because there's only ever one outstanding incremental search timer
3223 config.macros.search.timeout = null;
3225 config.macros.search.doSearch = function(txt)
3227 if(txt.value.length > 0) {
3228 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
3229 txt.setAttribute("lastSearchText",txt.value);
3233 config.macros.search.onClick = function(e)
3235 config.macros.search.doSearch(this.nextSibling);
3236 return false;
3239 config.macros.search.onKeyPress = function(ev)
3241 var e = ev ? ev : window.event;
3242 switch(e.keyCode) {
3243 case 13: // Ctrl-Enter
3244 case 10: // Ctrl-Enter on IE PC
3245 config.macros.search.doSearch(this);
3246 break;
3247 case 27: // Escape
3248 this.value = "";
3249 clearMessage();
3250 break;
3252 if(this.value.length > 2) {
3253 if(this.value != this.getAttribute("lastSearchText")) {
3254 if(config.macros.search.timeout)
3255 clearTimeout(config.macros.search.timeout);
3256 var txt = this;
3257 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3259 } else {
3260 if(config.macros.search.timeout)
3261 clearTimeout(config.macros.search.timeout);
3265 config.macros.search.onFocus = function(e)
3267 this.select();
3270 //--
3271 //-- Tabs macro
3272 //--
3274 config.macros.tabs.handler = function(place,macroName,params)
3276 var cookie = params[0];
3277 var numTabs = (params.length-1)/3;
3278 var wrapper = createTiddlyElement(null,"div",null,cookie);
3279 var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
3280 tabset.setAttribute("cookie",cookie);
3281 var validTab = false;
3282 for(var t=0; t<numTabs; t++) {
3283 var label = params[t*3+1];
3284 var prompt = params[t*3+2];
3285 var content = params[t*3+3];
3286 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
3287 tab.setAttribute("tab",label);
3288 tab.setAttribute("content",content);
3289 tab.title = prompt;
3290 if(config.options[cookie] == label)
3291 validTab = true;
3293 if(!validTab)
3294 config.options[cookie] = params[1];
3295 place.appendChild(wrapper);
3296 this.switchTab(tabset,config.options[cookie]);
3299 config.macros.tabs.onClickTab = function(e)
3301 config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
3302 return false;
3305 config.macros.tabs.switchTab = function(tabset,tab)
3307 var cookie = tabset.getAttribute("cookie");
3308 var theTab = null;
3309 var nodes = tabset.childNodes;
3310 for(var t=0; t<nodes.length; t++) {
3311 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
3312 theTab = nodes[t];
3313 theTab.className = "tab tabSelected";
3314 } else {
3315 nodes[t].className = "tab tabUnselected";
3318 if(theTab) {
3319 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
3320 removeNode(tabset.nextSibling);
3321 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
3322 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
3323 var contentTitle = theTab.getAttribute("content");
3324 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
3325 if(cookie) {
3326 config.options[cookie] = tab;
3327 saveOptionCookie(cookie);
3332 //--
3333 //-- Tiddler toolbar
3334 //--
3336 // Create a toolbar command button
3337 config.macros.toolbar.createCommand = function(place,commandName,tiddler,theClass)
3339 if(typeof commandName != "string") {
3340 var c = null;
3341 for(var t in config.commands) {
3342 if(config.commands[t] == commandName)
3343 c = t;
3345 commandName = c;
3347 if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
3348 var command = config.commands[commandName];
3349 if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
3350 var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
3351 var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
3352 var cmd;
3353 switch(command.type) {
3354 case "popup":
3355 cmd = this.onClickPopup;
3356 break;
3357 case "command":
3358 default:
3359 cmd = this.onClickCommand;
3360 break;
3362 var btn = createTiddlyButton(null,text,tooltip,cmd);
3363 btn.setAttribute("commandName",commandName);
3364 btn.setAttribute("tiddler",tiddler.title);
3365 if(theClass)
3366 addClass(btn,theClass);
3367 place.appendChild(btn);
3372 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
3374 var title = tiddler.title;
3375 var ro = tiddler.isReadOnly();
3376 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
3377 return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
3380 config.macros.toolbar.getCommandText = function(command,tiddler)
3382 return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
3385 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
3387 return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
3390 config.macros.toolbar.onClickCommand = function(ev)
3392 var e = ev ? ev : window.event;
3393 e.cancelBubble = true;
3394 if (e.stopPropagation) e.stopPropagation();
3395 var command = config.commands[this.getAttribute("commandName")];
3396 return command.handler(e,this,this.getAttribute("tiddler"));
3399 config.macros.toolbar.onClickPopup = function(ev)
3401 var e = ev ? ev : window.event;
3402 e.cancelBubble = true;
3403 if (e.stopPropagation) e.stopPropagation();
3404 var popup = Popup.create(this);
3405 var command = config.commands[this.getAttribute("commandName")];
3406 var title = this.getAttribute("tiddler");
3407 var tiddler = store.fetchTiddler(title);
3408 popup.setAttribute("tiddler",title);
3409 command.handlePopup(popup,title);
3410 Popup.show();
3411 return false;
3414 // Invoke the first command encountered from a given place that is tagged with a specified class
3415 config.macros.toolbar.invokeCommand = function(place,theClass,event)
3417 var children = place.getElementsByTagName("a");
3418 for(var t=0; t<children.length; t++) {
3419 var c = children[t];
3420 if(hasClass(c,theClass) && c.getAttribute && c.getAttribute("commandName")) {
3421 if(c.onclick instanceof Function)
3422 c.onclick.call(c,event);
3423 break;
3428 config.macros.toolbar.onClickMore = function(ev)
3430 var e = this.nextSibling;
3431 e.style.display = "inline";
3432 removeNode(this);
3433 return false;
3436 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3438 for(var t=0; t<params.length; t++) {
3439 var c = params[t];
3440 switch(c) {
3441 case '>':
3442 var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
3443 addClass(btn,"moreCommand");
3444 var e = createTiddlyElement(place,"span",null,"moreCommand");
3445 e.style.display = "none";
3446 place = e;
3447 break;
3448 default:
3449 var theClass = "";
3450 switch(c.substr(0,1)) {
3451 case "+":
3452 theClass = "defaultCommand";
3453 c = c.substr(1);
3454 break;
3455 case "-":
3456 theClass = "cancelCommand";
3457 c = c.substr(1);
3458 break;
3460 if(c in config.commands)
3461 this.createCommand(place,c,tiddler,theClass);
3462 break;
3467 //--
3468 //-- Menu and toolbar commands
3469 //--
3471 config.commands.closeTiddler.handler = function(event,src,title)
3473 story.closeTiddler(title,true);
3474 return false;
3477 config.commands.closeOthers.handler = function(event,src,title)
3479 story.closeAllTiddlers(title);
3480 return false;
3483 config.commands.editTiddler.handler = function(event,src,title)
3485 clearMessage();
3486 var tiddlerElem = document.getElementById(story.idPrefix + title);
3487 var fields = tiddlerElem.getAttribute("tiddlyFields");
3488 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
3489 story.focusTiddler(title,"text");
3490 return false;
3493 config.commands.saveTiddler.handler = function(event,src,title)
3495 var newTitle = story.saveTiddler(title,event.shiftKey);
3496 if(newTitle)
3497 story.displayTiddler(null,newTitle);
3498 return false;
3501 config.commands.cancelTiddler.handler = function(event,src,title)
3503 if(story.hasChanges(title) && !readOnly) {
3504 if(!confirm(this.warning.format([title])))
3505 return false;
3507 story.setDirty(title,false);
3508 story.displayTiddler(null,title);
3509 return false;
3512 config.commands.deleteTiddler.handler = function(event,src,title)
3514 var deleteIt = true;
3515 if (config.options.chkConfirmDelete)
3516 deleteIt = confirm(this.warning.format([title]));
3517 if (deleteIt) {
3518 store.removeTiddler(title);
3519 story.closeTiddler(title,true);
3520 autoSaveChanges();
3522 return false;
3525 config.commands.permalink.handler = function(event,src,title)
3527 var t = encodeURIComponent(String.encodeTiddlyLink(title));
3528 if(window.location.hash != t)
3529 window.location.hash = t;
3530 return false;
3533 config.commands.references.handlePopup = function(popup,title)
3535 var references = store.getReferringTiddlers(title);
3536 var c = false;
3537 for(var r=0; r<references.length; r++) {
3538 if(references[r].title != title && !references[r].isTagged("excludeLists")) {
3539 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
3540 c = true;
3543 if(!c)
3544 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
3547 config.commands.jump.handlePopup = function(popup,title)
3549 story.forEachTiddler(function(title,element) {
3550 createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
3554 config.commands.syncing.handlePopup = function(popup,title)
3556 var tiddler = store.fetchTiddler(title);
3557 if(!tiddler)
3558 return;
3559 var serverType = tiddler.getServerType();
3560 var serverHost = tiddler.fields['server.host'];
3561 var serverWorkspace = tiddler.fields['server.workspace'];
3562 if(!serverWorkspace)
3563 serverWorkspace = "";
3564 if(serverType) {
3565 var e = createTiddlyElement(popup,"li",null,"popupMessage");
3566 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3567 } else {
3568 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
3570 if(serverType) {
3571 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3572 var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
3573 btn.setAttribute("tiddler",title);
3574 btn.setAttribute("server.type","");
3576 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3577 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
3578 var feeds = store.getTaggedTiddlers("systemServer","title");
3579 for(var t=0; t<feeds.length; t++) {
3580 var f = feeds[t];
3581 var feedServerType = store.getTiddlerSlice(f.title,"Type");
3582 if(!feedServerType)
3583 feedServerType = "file";
3584 var feedServerHost = store.getTiddlerSlice(f.title,"URL");
3585 if(!feedServerHost)
3586 feedServerHost = "";
3587 var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
3588 if(!feedServerWorkspace)
3589 feedServerWorkspace = "";
3590 var caption = f.title;
3591 if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
3592 caption = config.commands.syncing.currServerMarker + caption;
3593 } else {
3594 caption = config.commands.syncing.notCurrServerMarker + caption;
3596 btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
3597 btn.setAttribute("tiddler",title);
3598 btn.setAttribute("server.type",feedServerType);
3599 btn.setAttribute("server.host",feedServerHost);
3600 btn.setAttribute("server.workspace",feedServerWorkspace);
3604 config.commands.syncing.onChooseServer = function(e)
3606 var tiddler = this.getAttribute("tiddler");
3607 var serverType = this.getAttribute("server.type");
3608 if(serverType) {
3609 store.addTiddlerFields(tiddler,{
3610 'server.type': serverType,
3611 'server.host': this.getAttribute("server.host"),
3612 'server.workspace': this.getAttribute("server.workspace")
3614 } else {
3615 store.setValue(tiddler,'server',null);
3617 return false;
3620 config.commands.fields.handlePopup = function(popup,title)
3622 var tiddler = store.fetchTiddler(title);
3623 if(!tiddler)
3624 return;
3625 var fields = {};
3626 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
3627 var items = [];
3628 for(var t in fields) {
3629 items.push({field: t,value: fields[t]});
3631 items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
3632 if(items.length > 0)
3633 ListView.create(popup,items,this.listViewTemplate);
3634 else
3635 createTiddlyElement(popup,"div",null,null,this.emptyText);
3638 //--
3639 //-- Tiddler() object
3640 //--
3642 function Tiddler(title)
3644 this.title = title;
3645 this.text = null;
3646 this.modifier = null;
3647 this.modified = new Date();
3648 this.created = new Date();
3649 this.links = [];
3650 this.linksUpdated = false;
3651 this.tags = [];
3652 this.fields = {};
3653 return this;
3656 Tiddler.prototype.getLinks = function()
3658 if(this.linksUpdated==false)
3659 this.changed();
3660 return this.links;
3663 // Returns the fields that are inherited in string field:"value" field2:"value2" format
3664 Tiddler.prototype.getInheritedFields = function()
3666 var f = {};
3667 for(i in this.fields) {
3668 if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
3669 f[i] = this.fields[i];
3672 return String.encodeHashMap(f);
3675 // Increment the changeCount of a tiddler
3676 Tiddler.prototype.incChangeCount = function()
3678 var c = this.fields['changecount'];
3679 c = c ? parseInt(c) : 0;
3680 this.fields['changecount'] = String(c+1);
3683 // Clear the changeCount of a tiddler
3684 Tiddler.prototype.clearChangeCount = function()
3686 if(this.fields['changecount']) {
3687 delete this.fields['changecount'];
3691 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
3692 Tiddler.prototype.isTouched = function()
3694 var changeCount = this.fields['changecount'];
3695 if(changeCount === undefined)
3696 changeCount = 0;
3697 return changeCount > 0;
3700 // Return the tiddler as an RSS item
3701 Tiddler.prototype.toRssItem = function(uri)
3703 var s = [];
3704 s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
3705 s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
3706 for(var t=0; t<this.tags.length; t++)
3707 s.push("<category>" + this.tags[t] + "</category>");
3708 s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
3709 s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
3710 return s.join("\n");
3713 // Format the text for storage in an RSS item
3714 Tiddler.prototype.saveToRss = function(uri)
3716 return "<item>\n" + this.toRssItem(uri) + "\n</item>";
3719 // Change the text and other attributes of a tiddler
3720 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
3722 this.assign(title,text,modifier,modified,tags,created,fields);
3723 this.changed();
3724 return this;
3727 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
3728 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
3730 if(title != undefined)
3731 this.title = title;
3732 if(text != undefined)
3733 this.text = text;
3734 if(modifier != undefined)
3735 this.modifier = modifier;
3736 if(modified != undefined)
3737 this.modified = modified;
3738 if(created != undefined)
3739 this.created = created;
3740 if(fields != undefined)
3741 this.fields = fields;
3742 if(tags != undefined)
3743 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
3744 else if(this.tags == undefined)
3745 this.tags = [];
3746 return this;
3749 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
3750 Tiddler.prototype.getTags = function()
3752 return String.encodeTiddlyLinkList(this.tags);
3755 // Test if a tiddler carries a tag
3756 Tiddler.prototype.isTagged = function(tag)
3758 return this.tags.indexOf(tag) != -1;
3761 // Static method to convert "\n" to newlines, "\s" to "\"
3762 Tiddler.unescapeLineBreaks = function(text)
3764 return text ? text.unescapeLineBreaks() : "";
3767 // Convert newlines to "\n", "\" to "\s"
3768 Tiddler.prototype.escapeLineBreaks = function()
3770 return this.text.escapeLineBreaks();
3773 // Updates the secondary information (like links[] array) after a change to a tiddler
3774 Tiddler.prototype.changed = function()
3776 this.links = [];
3777 var t = this.autoLinkWikiWords() ? 0 : 1;
3778 var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
3779 tiddlerLinkRegExp.lastIndex = 0;
3780 var formatMatch = tiddlerLinkRegExp.exec(this.text);
3781 while(formatMatch) {
3782 var lastIndex = tiddlerLinkRegExp.lastIndex;
3783 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
3784 // wikiWordLink
3785 if(formatMatch.index > 0) {
3786 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
3787 preRegExp.lastIndex = formatMatch.index-1;
3788 var preMatch = preRegExp.exec(this.text);
3789 if(preMatch.index != formatMatch.index-1)
3790 this.links.pushUnique(formatMatch[1]);
3791 } else {
3792 this.links.pushUnique(formatMatch[1]);
3795 else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
3796 this.links.pushUnique(formatMatch[3-t]);
3797 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
3798 this.links.pushUnique(formatMatch[4-t]);
3799 tiddlerLinkRegExp.lastIndex = lastIndex;
3800 formatMatch = tiddlerLinkRegExp.exec(this.text);
3802 this.linksUpdated = true;
3805 Tiddler.prototype.getSubtitle = function()
3807 var modifier = this.modifier;
3808 if(!modifier)
3809 modifier = config.messages.subtitleUnknown;
3810 var modified = this.modified;
3811 if(modified)
3812 modified = modified.toLocaleString();
3813 else
3814 modified = config.messages.subtitleUnknown;
3815 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
3818 Tiddler.prototype.isReadOnly = function()
3820 return readOnly;
3823 Tiddler.prototype.autoLinkWikiWords = function()
3825 return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
3828 Tiddler.prototype.generateFingerprint = function()
3830 return "0x" + Crypto.hexSha1Str(this.text);
3833 Tiddler.prototype.getServerType = function()
3835 var serverType = null;
3836 if(this.fields && this.fields['server.type'])
3837 serverType = this.fields['server.type'];
3838 if(!serverType)
3839 serverType = this.fields['wikiformat'];
3840 if(serverType && !config.adaptors[serverType])
3841 serverType = null;
3842 return serverType;
3845 Tiddler.prototype.getAdaptor = function()
3847 var serverType = this.getServerType();
3848 return serverType ? new config.adaptors[serverType] : null;
3851 //--
3852 //-- TiddlyWiki() object contains Tiddler()s
3853 //--
3855 function TiddlyWiki()
3857 var tiddlers = {}; // Hashmap by name of tiddlers
3858 this.tiddlersUpdated = false;
3859 this.namedNotifications = []; // Array of {name:,notify:} of notification functions
3860 this.notificationLevel = 0;
3861 this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
3862 this.clear = function() {
3863 tiddlers = {};
3864 this.setDirty(false);
3866 this.fetchTiddler = function(title) {
3867 var t = tiddlers[title];
3868 return t instanceof Tiddler ? t : null;
3870 this.deleteTiddler = function(title) {
3871 delete this.slices[title];
3872 delete tiddlers[title];
3874 this.addTiddler = function(tiddler) {
3875 delete this.slices[tiddler.title];
3876 tiddlers[tiddler.title] = tiddler;
3878 this.forEachTiddler = function(callback) {
3879 for(var t in tiddlers) {
3880 var tiddler = tiddlers[t];
3881 if(tiddler instanceof Tiddler)
3882 callback.call(this,t,tiddler);
3887 TiddlyWiki.prototype.setDirty = function(dirty)
3889 this.dirty = dirty;
3892 TiddlyWiki.prototype.isDirty = function()
3894 return this.dirty;
3897 TiddlyWiki.prototype.suspendNotifications = function()
3899 this.notificationLevel--;
3902 TiddlyWiki.prototype.resumeNotifications = function()
3904 this.notificationLevel++;
3907 // Invoke the notification handlers for a particular tiddler
3908 TiddlyWiki.prototype.notify = function(title,doBlanket)
3910 if(!this.notificationLevel) {
3911 for(var t=0; t<this.namedNotifications.length; t++) {
3912 var n = this.namedNotifications[t];
3913 if((n.name == null && doBlanket) || (n.name == title))
3914 n.notify(title);
3919 // Invoke the notification handlers for all tiddlers
3920 TiddlyWiki.prototype.notifyAll = function()
3922 if(!this.notificationLevel) {
3923 for(var t=0; t<this.namedNotifications.length; t++) {
3924 var n = this.namedNotifications[t];
3925 if(n.name)
3926 n.notify(n.name);
3931 // Add a notification handler to a tiddler
3932 TiddlyWiki.prototype.addNotification = function(title,fn)
3934 for(var i=0; i<this.namedNotifications.length; i++) {
3935 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
3936 return this;
3938 this.namedNotifications.push({name: title, notify: fn});
3939 return this;
3942 TiddlyWiki.prototype.removeTiddler = function(title)
3944 var tiddler = this.fetchTiddler(title);
3945 if(tiddler) {
3946 this.deleteTiddler(title);
3947 this.notify(title,true);
3948 this.setDirty(true);
3952 TiddlyWiki.prototype.tiddlerExists = function(title)
3954 var t = this.fetchTiddler(title);
3955 return t != undefined;
3958 TiddlyWiki.prototype.isShadowTiddler = function(title)
3960 return typeof config.shadowTiddlers[title] == "string";
3963 TiddlyWiki.prototype.getTiddler = function(title)
3965 var t = this.fetchTiddler(title);
3966 if(t != undefined)
3967 return t;
3968 else
3969 return null;
3972 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
3974 if(!title)
3975 return defaultText;
3976 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
3977 var section = null;
3978 if(pos != -1) {
3979 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
3980 title = title.substr(0,pos);
3982 pos = title.indexOf(config.textPrimitives.sliceSeparator);
3983 if(pos != -1) {
3984 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
3985 if(slice)
3986 return slice;
3988 var tiddler = this.fetchTiddler(title);
3989 if(tiddler) {
3990 if(!section)
3991 return tiddler.text;
3992 var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + ")","mg");
3993 re.lastIndex = 0;
3994 var match = re.exec(tiddler.text);
3995 if(match) {
3996 var t = tiddler.text.substr(match.index+match[1].length);
3997 var re2 = /^!/mg;
3998 re2.lastIndex = 0;
3999 match = re2.exec(t); //# search for the next heading
4000 if(match)
4001 t = t.substr(0,match.index);
4002 return t;
4004 return defaultText;
4006 if(this.isShadowTiddler(title))
4007 return config.shadowTiddlers[title];
4008 if(defaultText != undefined)
4009 return defaultText;
4010 return null;
4013 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
4015 // @internal
4016 TiddlyWiki.prototype.calcAllSlices = function(title)
4018 var slices = {};
4019 var text = this.getTiddlerText(title,"");
4020 this.slicesRE.lastIndex = 0;
4021 do {
4022 var m = this.slicesRE.exec(text);
4023 if(m) {
4024 if(m[1])
4025 slices[m[1]] = m[2];
4026 else
4027 slices[m[3]] = m[4];
4029 } while(m);
4030 return slices;
4033 // Returns the slice of text of the given name
4034 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4036 var slices = this.slices[title];
4037 if(!slices) {
4038 slices = this.calcAllSlices(title);
4039 this.slices[title] = slices;
4041 return slices[sliceName];
4044 // Build an hashmap of the specified named slices of a tiddler
4045 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
4047 var r = {};
4048 for(var t=0; t<sliceNames.length; t++) {
4049 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4050 if(slice)
4051 r[sliceNames[t]] = slice;
4053 return r;
4056 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4058 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
4059 var text = this.getTiddlerText(title,null);
4060 if(text == null)
4061 return defaultText;
4062 var textOut = [];
4063 var lastPos = 0;
4064 do {
4065 var match = bracketRegExp.exec(text);
4066 if(match) {
4067 textOut.push(text.substr(lastPos,match.index-lastPos));
4068 if(match[1]) {
4069 if(depth <= 0)
4070 textOut.push(match[1]);
4071 else
4072 textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
4074 lastPos = match.index + match[0].length;
4075 } else {
4076 textOut.push(text.substr(lastPos));
4078 } while(match);
4079 return textOut.join("");
4082 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4084 var tiddler = this.fetchTiddler(title);
4085 if(tiddler) {
4086 var t = tiddler.tags.indexOf(tag);
4087 if(t != -1)
4088 tiddler.tags.splice(t,1);
4089 if(status)
4090 tiddler.tags.push(tag);
4091 tiddler.changed();
4092 this.incChangeCount(title);
4093 this.notify(title,true);
4094 this.setDirty(true);
4098 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
4100 var tiddler = this.fetchTiddler(title);
4101 if(!tiddler)
4102 return;
4103 merge(tiddler.fields,fields);
4104 tiddler.changed();
4105 this.incChangeCount(title);
4106 this.notify(title,true);
4107 this.setDirty(true);
4110 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
4112 var tiddler = this.fetchTiddler(title);
4113 if(tiddler) {
4114 created = created ? created : tiddler.created; // Preserve created date
4115 this.deleteTiddler(title);
4116 } else {
4117 created = created ? created : modified;
4118 tiddler = new Tiddler();
4120 tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
4121 this.addTiddler(tiddler);
4122 if(clearChangeCount)
4123 tiddler.clearChangeCount();
4124 else
4125 tiddler.incChangeCount();
4126 if(title != newTitle)
4127 this.notify(title,true);
4128 this.notify(newTitle,true);
4129 this.setDirty(true);
4130 return tiddler;
4133 // Reset the sync status of a freshly synced tiddler
4134 TiddlyWiki.prototype.resetTiddler = function(title)
4136 var tiddler = this.fetchTiddler(title);
4137 if(tiddler) {
4138 tiddler.clearChangeCount();
4139 this.notify(title,true);
4140 this.setDirty(true);
4144 TiddlyWiki.prototype.incChangeCount = function(title)
4146 var tiddler = this.fetchTiddler(title);
4147 if(tiddler)
4148 tiddler.incChangeCount();
4151 TiddlyWiki.prototype.createTiddler = function(title)
4153 var tiddler = this.fetchTiddler(title);
4154 if(!tiddler) {
4155 tiddler = new Tiddler();
4156 tiddler.title = title;
4157 this.addTiddler(tiddler);
4158 this.setDirty(true);
4160 return tiddler;
4163 // Load contents of a TiddlyWiki from an HTML DIV
4164 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
4166 this.idPrefix = idPrefix;
4167 var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
4168 if(!storeElem)
4169 return;
4170 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4171 this.setDirty(false);
4172 if(!noUpdate) {
4173 for(var i = 0;i<tiddlers.length; i++)
4174 tiddlers[i].changed();
4178 // Load contents of a TiddlyWiki from a string
4179 // Returns null if there's an error
4180 TiddlyWiki.prototype.importTiddlyWiki = function(text)
4182 var posDiv = locateStoreArea(text);
4183 if(!posDiv)
4184 return null;
4185 var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
4186 // Create the iframe
4187 var iframe = document.createElement("iframe");
4188 iframe.style.display = "none";
4189 document.body.appendChild(iframe);
4190 var doc = iframe.document;
4191 if(iframe.contentDocument)
4192 doc = iframe.contentDocument; // For NS6
4193 else if(iframe.contentWindow)
4194 doc = iframe.contentWindow.document; // For IE5.5 and IE6
4195 // Put the content in the iframe
4196 doc.open();
4197 doc.writeln(content);
4198 doc.close();
4199 // Load the content into a TiddlyWiki() object
4200 var storeArea = doc.getElementById("storeArea");
4201 this.loadFromDiv(storeArea,"store");
4202 // Get rid of the iframe
4203 iframe.parentNode.removeChild(iframe);
4204 return this;
4207 TiddlyWiki.prototype.updateTiddlers = function()
4209 this.tiddlersUpdated = true;
4210 this.forEachTiddler(function(title,tiddler) {
4211 tiddler.changed();
4215 // Return all tiddlers formatted as an HTML string
4216 TiddlyWiki.prototype.allTiddlersAsHtml = function()
4218 return store.getSaver().externalize(store);
4221 // Return an array of tiddlers matching a search regular expression
4222 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
4224 var candidates = this.reverseLookup("tags",excludeTag,!!match);
4225 var results = [];
4226 for(var t=0; t<candidates.length; t++) {
4227 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
4228 results.push(candidates[t]);
4230 if(!sortField)
4231 sortField = "title";
4232 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4233 return results;
4236 // Returns a list of all tags in use
4237 // excludeTag - if present, excludes tags that are themselves tagged with excludeTag
4238 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
4239 TiddlyWiki.prototype.getTags = function(excludeTag)
4241 var results = [];
4242 this.forEachTiddler(function(title,tiddler) {
4243 for(var g=0; g<tiddler.tags.length; g++) {
4244 var tag = tiddler.tags[g];
4245 var n = true;
4246 for(var c=0; c<results.length; c++) {
4247 if(results[c][0] == tag) {
4248 n = false;
4249 results[c][1]++;
4252 if(n && excludeTag) {
4253 var t = store.fetchTiddler(tag);
4254 if(t && t.isTagged(excludeTag))
4255 n = false;
4257 if(n)
4258 results.push([tag,1]);
4261 results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
4262 return results;
4265 // Return an array of the tiddlers that are tagged with a given tag
4266 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
4268 return this.reverseLookup("tags",tag,true,sortField);
4271 // Return an array of the tiddlers that link to a given tiddler
4272 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
4274 if(!this.tiddlersUpdated)
4275 this.updateTiddlers();
4276 return this.reverseLookup("links",title,true,sortField);
4279 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
4280 // lookupMatch == true to match tiddlers, false to exclude tiddlers
4281 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
4283 var results = [];
4284 this.forEachTiddler(function(title,tiddler) {
4285 var f = !lookupMatch;
4286 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
4287 if(tiddler[lookupField][lookup] == lookupValue)
4288 f = lookupMatch;
4290 if(f)
4291 results.push(tiddler);
4293 if(!sortField)
4294 sortField = "title";
4295 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4296 return results;
4299 // Return the tiddlers as a sorted array
4300 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4302 var results = [];
4303 this.forEachTiddler(function(title,tiddler) {
4304 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4305 results.push(tiddler);
4307 if(field)
4308 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
4309 return results;
4312 // Return array of names of tiddlers that are referred to but not defined
4313 TiddlyWiki.prototype.getMissingLinks = function(sortField)
4315 if(!this.tiddlersUpdated)
4316 this.updateTiddlers();
4317 var results = [];
4318 this.forEachTiddler(function (title,tiddler) {
4319 if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
4320 return;
4321 for(var n=0; n<tiddler.links.length;n++) {
4322 var link = tiddler.links[n];
4323 if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
4324 results.pushUnique(link);
4327 results.sort();
4328 return results;
4331 // Return an array of names of tiddlers that are defined but not referred to
4332 TiddlyWiki.prototype.getOrphans = function()
4334 var results = [];
4335 this.forEachTiddler(function (title,tiddler) {
4336 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
4337 results.push(title);
4339 results.sort();
4340 return results;
4343 // Return an array of names of all the shadow tiddlers
4344 TiddlyWiki.prototype.getShadowed = function()
4346 var results = [];
4347 for(var t in config.shadowTiddlers) {
4348 if(typeof config.shadowTiddlers[t] == "string")
4349 results.push(t);
4351 results.sort();
4352 return results;
4355 // Return an array of tiddlers that have been touched since they were downloaded or created
4356 TiddlyWiki.prototype.getTouched = function()
4358 var results = [];
4359 this.forEachTiddler(function(title,tiddler) {
4360 if(tiddler.isTouched())
4361 results.push(tiddler);
4363 results.sort();
4364 return results;
4367 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
4368 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
4370 var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
4371 return t instanceof Tiddler ? t : null;
4374 TiddlyWiki.prototype.getLoader = function()
4376 if(!this.loader)
4377 this.loader = new TW21Loader();
4378 return this.loader;
4381 TiddlyWiki.prototype.getSaver = function()
4383 if(!this.saver)
4384 this.saver = new TW21Saver();
4385 return this.saver;
4388 // Filter a list of tiddlers
4389 TiddlyWiki.prototype.filterTiddlers = function(filter)
4391 var results = [];
4392 if(filter) {
4393 var tiddler;
4394 var re = /([^ \[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4395 var match = re.exec(filter);
4396 while(match) {
4397 if(match[1] || match[4]) {
4398 var title = match[1] ? match[1] : match[4];
4399 tiddler = this.fetchTiddler(title);
4400 if(tiddler) {
4401 results.pushUnique(tiddler);
4402 } else if(store.isShadowTiddler(title)) {
4403 tiddler = new Tiddler();
4404 tiddler.set(title,store.getTiddlerText(title));
4405 results.pushUnique(tiddler);
4407 } else if(match[2]) {
4408 switch(match[2]) {
4409 case "tag":
4410 this.forEachTiddler(function(title,tiddler) {
4411 if(tiddler.isTagged(match[3]))
4412 results.pushUnique(tiddler);
4414 break;
4415 case "sort":
4416 results = this.sortTiddlers(results,match[3]);
4417 break;
4420 match = re.exec(filter);
4423 return results;
4426 // Sort a list of tiddlers
4427 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4429 var asc = +1;
4430 switch(field.substr(0,1)) {
4431 case "-":
4432 asc = -1;
4433 // Note: this fall-through is intentional
4434 case "+":
4435 field = field.substr(1);
4436 break;
4438 if(TiddlyWiki.standardFieldAccess[field])
4439 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4440 else
4441 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
4442 return tiddlers;
4444 // Returns true if path is a valid field name (path),
4445 // i.e. a sequence of identifiers, separated by '.'
4446 TiddlyWiki.isValidFieldName = function(name)
4448 var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
4449 return match && (match[0] == name);
4452 // Throws an exception when name is not a valid field name.
4453 TiddlyWiki.checkFieldName = function(name)
4455 if(!TiddlyWiki.isValidFieldName(name))
4456 throw config.messages.invalidFieldName.format([name]);
4459 function StringFieldAccess(n,readOnly)
4461 this.set = readOnly ?
4462 function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
4463 function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
4464 this.get = function(t) {return t[n];};
4467 function DateFieldAccess(n)
4469 this.set = function(t,v) {
4470 var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
4471 if(d != t[n]) {
4472 t[n] = d; return true;
4475 this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
4478 function LinksFieldAccess(n)
4480 this.set = function(t,v) {
4481 var s = (typeof v == "string") ? v.readBracketedList() : v;
4482 if(s.toString() != t[n].toString()) {
4483 t[n] = s; return true;
4486 this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
4489 TiddlyWiki.standardFieldAccess = {
4490 // The set functions return true when setting the data has changed the value.
4491 "title": new StringFieldAccess("title",true),
4492 // Handle the "tiddler" field name as the title
4493 "tiddler": new StringFieldAccess("title",true),
4494 "text": new StringFieldAccess("text"),
4495 "modifier": new StringFieldAccess("modifier"),
4496 "modified": new DateFieldAccess("modified"),
4497 "created": new DateFieldAccess("created"),
4498 "tags": new LinksFieldAccess("tags")
4501 TiddlyWiki.isStandardField = function(name)
4503 return TiddlyWiki.standardFieldAccess[name] != undefined;
4506 // Sets the value of the given field of the tiddler to the value.
4507 // Setting an ExtendedField's value to null or undefined removes the field.
4508 // Setting a namespace to undefined removes all fields of that namespace.
4509 // The fieldName is case-insensitive.
4510 // All values will be converted to a string value.
4511 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
4513 TiddlyWiki.checkFieldName(fieldName);
4514 var t = this.resolveTiddler(tiddler);
4515 if(!t)
4516 return;
4517 fieldName = fieldName.toLowerCase();
4518 var isRemove = (value === undefined) || (value === null);
4519 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4520 if(accessor) {
4521 if(isRemove)
4522 // don't remove StandardFields
4523 return;
4524 var h = TiddlyWiki.standardFieldAccess[fieldName];
4525 if(!h.set(t,value))
4526 return;
4527 } else {
4528 var oldValue = t.fields[fieldName];
4529 if(isRemove) {
4530 if(oldValue !== undefined) {
4531 // deletes a single field
4532 delete t.fields[fieldName];
4533 } else {
4534 // no concrete value is defined for the fieldName
4535 // so we guess this is a namespace path.
4536 // delete all fields in a namespace
4537 var re = new RegExp('^'+fieldName+'\\.');
4538 var dirty = false;
4539 for(var n in t.fields) {
4540 if(n.match(re)) {
4541 delete t.fields[n];
4542 dirty = true;
4545 if(!dirty)
4546 return;
4548 } else {
4549 // the "normal" set case. value is defined (not null/undefined)
4550 // For convenience provide a nicer conversion Date->String
4551 value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
4552 if(oldValue == value)
4553 return;
4554 t.fields[fieldName] = value;
4557 // When we are here the tiddler/store really was changed.
4558 this.notify(t.title,true);
4559 if(!fieldName.match(/^temp\./))
4560 this.setDirty(true);
4563 // Returns the value of the given field of the tiddler.
4564 // The fieldName is case-insensitive.
4565 // Will only return String values (or undefined).
4566 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
4568 var t = this.resolveTiddler(tiddler);
4569 if(!t)
4570 return undefined;
4571 fieldName = fieldName.toLowerCase();
4572 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4573 if(accessor) {
4574 return accessor.get(t);
4576 return t.fields[fieldName];
4579 // Calls the callback function for every field in the tiddler.
4580 // When callback function returns a non-false value the iteration stops
4581 // and that value is returned.
4582 // The order of the fields is not defined.
4583 // @param callback a function(tiddler,fieldName,value).
4584 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
4586 var t = this.resolveTiddler(tiddler);
4587 if(!t)
4588 return undefined;
4589 var n,result;
4590 for(n in t.fields) {
4591 result = callback(t,n,t.fields[n]);
4592 if(result)
4593 return result;
4595 if(onlyExtendedFields)
4596 return undefined;
4597 for(n in TiddlyWiki.standardFieldAccess) {
4598 if(n == "tiddler")
4599 // even though the "title" field can also be referenced through the name "tiddler"
4600 // we only visit this field once.
4601 continue;
4602 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4603 if(result)
4604 return result;
4606 return undefined;
4609 //--
4610 //-- Story functions
4611 //--
4613 function Story(container,idPrefix)
4615 this.container = container;
4616 this.idPrefix = idPrefix;
4617 this.highlightRegExp = null;
4620 Story.prototype.forEachTiddler = function(fn)
4622 var place = document.getElementById(this.container);
4623 if(!place)
4624 return;
4625 var e = place.firstChild;
4626 while(e) {
4627 var n = e.nextSibling;
4628 var title = e.getAttribute("tiddler");
4629 fn.call(this,title,e);
4630 e = n;
4634 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
4636 for(var t = titles.length-1;t>=0;t--)
4637 this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
4640 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle)
4642 var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
4643 var place = document.getElementById(this.container);
4644 var tiddlerElem = document.getElementById(this.idPrefix + title);
4645 if(tiddlerElem) {
4646 if(toggle)
4647 this.closeTiddler(title,true);
4648 else
4649 this.refreshTiddler(title,template,false,customFields);
4650 } else {
4651 var before = this.positionTiddler(srcElement);
4652 tiddlerElem = this.createTiddler(place,before,title,template,customFields);
4654 if(srcElement && typeof srcElement !== "string") {
4655 if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
4656 anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
4657 else
4658 window.scrollTo(0,ensureVisible(tiddlerElem));
4662 Story.prototype.positionTiddler = function(srcElement)
4664 var place = document.getElementById(this.container);
4665 var before = null;
4666 if(typeof srcElement == "string") {
4667 switch(srcElement) {
4668 case "top":
4669 before = place.firstChild;
4670 break;
4671 case "bottom":
4672 before = null;
4673 break;
4675 } else {
4676 var after = this.findContainingTiddler(srcElement);
4677 if(after == null) {
4678 before = place.firstChild;
4679 } else if(after.nextSibling) {
4680 before = after.nextSibling;
4681 if(before.nodeType != 1)
4682 before = null;
4685 return before;
4688 Story.prototype.createTiddler = function(place,before,title,template,customFields)
4690 var tiddlerElem = createTiddlyElement(null,"div",this.idPrefix + title,"tiddler");
4691 tiddlerElem.setAttribute("refresh","tiddler");
4692 if(customFields)
4693 tiddlerElem.setAttribute("tiddlyFields",customFields);
4694 place.insertBefore(tiddlerElem,before);
4695 var defaultText = null;
4696 if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
4697 defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
4698 this.refreshTiddler(title,template,false,customFields,defaultText);
4699 return tiddlerElem;
4702 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
4704 var tiddler = new Tiddler(title);
4705 tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
4706 var serverType = tiddler.getServerType();
4707 var host = tiddler.fields['server.host'];
4708 var workspace = tiddler.fields['server.workspace'];
4709 if(!serverType || !host)
4710 return null;
4711 var sm = new SyncMachine(serverType,{
4712 start: function() {
4713 return this.openHost(host,"openWorkspace");
4715 openWorkspace: function() {
4716 return this.openWorkspace(workspace,"getTiddler");
4718 getTiddler: function() {
4719 return this.getTiddler(title,"onGetTiddler");
4721 onGetTiddler: function(context) {
4722 var tiddler = context.tiddler;
4723 if(tiddler && tiddler.text) {
4724 var downloaded = new Date();
4725 if(!tiddler.created)
4726 tiddler.created = downloaded;
4727 if(!tiddler.modified)
4728 tiddler.modified = tiddler.created;
4729 store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
4730 autoSaveChanges();
4732 delete this;
4733 return true;
4735 error: function(message) {
4736 displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
4739 sm.go();
4740 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
4743 Story.prototype.chooseTemplateForTiddler = function(title,template)
4745 if(!template)
4746 template = DEFAULT_VIEW_TEMPLATE;
4747 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
4748 template = config.tiddlerTemplates[template];
4749 return template;
4752 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
4754 return store.getRecursiveTiddlerText(template,null,10);
4757 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
4759 var tiddlerElem = document.getElementById(this.idPrefix + title);
4760 if(tiddlerElem) {
4761 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
4762 return tiddlerElem;
4763 template = this.chooseTemplateForTiddler(title,template);
4764 var currTemplate = tiddlerElem.getAttribute("template");
4765 if((template != currTemplate) || force) {
4766 var tiddler = store.getTiddler(title);
4767 if(!tiddler) {
4768 tiddler = new Tiddler();
4769 if(store.isShadowTiddler(title)) {
4770 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
4771 } else {
4772 var text = template=="EditTemplate" ?
4773 config.views.editor.defaultText.format([title]) :
4774 config.views.wikified.defaultText.format([title]);
4775 text = defaultText ? defaultText : text;
4776 var fields = customFields ? customFields.decodeHashMap() : null;
4777 tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
4780 tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
4781 tiddlerElem.setAttribute("tiddler",title);
4782 tiddlerElem.setAttribute("template",template);
4783 var me = this;
4784 tiddlerElem.onmouseover = this.onTiddlerMouseOver;
4785 tiddlerElem.onmouseout = this.onTiddlerMouseOut;
4786 tiddlerElem.ondblclick = this.onTiddlerDblClick;
4787 tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
4788 var html = this.getTemplateForTiddler(title,template,tiddler);
4789 tiddlerElem.innerHTML = html;
4790 applyHtmlMacros(tiddlerElem,tiddler);
4791 if(store.getTaggedTiddlers(title).length > 0)
4792 addClass(tiddlerElem,"isTag");
4793 else
4794 removeClass(tiddlerElem,"isTag");
4795 if(!store.tiddlerExists(title)) {
4796 if(store.isShadowTiddler(title))
4797 addClass(tiddlerElem,"shadow");
4798 else
4799 addClass(tiddlerElem,"missing");
4800 } else {
4801 removeClass(tiddlerElem,"shadow");
4802 removeClass(tiddlerElem,"missing");
4804 if(customFields)
4805 this.addCustomFields(tiddlerElem,customFields);
4806 forceReflow();
4809 return tiddlerElem;
4812 Story.prototype.addCustomFields = function(place,customFields)
4814 var fields = customFields.decodeHashMap();
4815 var w = document.createElement("div");
4816 w.style.display = "none";
4817 place.appendChild(w);
4818 for(var t in fields) {
4819 var e = document.createElement("input");
4820 e.setAttribute("type","text");
4821 e.setAttribute("value",fields[t]);
4822 w.appendChild(e);
4823 e.setAttribute("edit",t);
4827 Story.prototype.refreshAllTiddlers = function(force)
4829 var place = document.getElementById(this.container);
4830 var e = place.firstChild;
4831 if(!e)
4832 return;
4833 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
4834 while((e = e.nextSibling) != null)
4835 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
4838 Story.prototype.onTiddlerMouseOver = function(e)
4840 if(window.addClass instanceof Function)
4841 addClass(this,"selected");
4844 Story.prototype.onTiddlerMouseOut = function(e)
4846 if(window.removeClass instanceof Function)
4847 removeClass(this,"selected");
4850 Story.prototype.onTiddlerDblClick = function(ev)
4852 var e = ev ? ev : window.event;
4853 var theTarget = resolveTarget(e);
4854 if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
4855 if(document.selection && document.selection.empty)
4856 document.selection.empty();
4857 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
4858 e.cancelBubble = true;
4859 if(e.stopPropagation) e.stopPropagation();
4860 return true;
4861 } else {
4862 return false;
4866 Story.prototype.onTiddlerKeyPress = function(ev)
4868 var e = ev ? ev : window.event;
4869 clearMessage();
4870 var consume = false;
4871 var title = this.getAttribute("tiddler");
4872 var target = resolveTarget(e);
4873 switch(e.keyCode) {
4874 case 9: // Tab
4875 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
4876 replaceSelection(target,String.fromCharCode(9));
4877 consume = true;
4879 if(config.isOpera) {
4880 target.onblur = function() {
4881 this.focus();
4882 this.onblur = null;
4885 break;
4886 case 13: // Ctrl-Enter
4887 case 10: // Ctrl-Enter on IE PC
4888 case 77: // Ctrl-Enter is "M" on some platforms
4889 if(e.ctrlKey) {
4890 blurElement(this);
4891 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
4892 consume = true;
4894 break;
4895 case 27: // Escape
4896 blurElement(this);
4897 config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
4898 consume = true;
4899 break;
4901 e.cancelBubble = consume;
4902 if(consume) {
4903 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
4904 e.returnValue = true; // Cancel The Event in IE
4905 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
4907 return !consume;
4910 Story.prototype.getTiddlerField = function(title,field)
4912 var tiddlerElem = document.getElementById(this.idPrefix + title);
4913 var e = null;
4914 if(tiddlerElem != null) {
4915 var children = tiddlerElem.getElementsByTagName("*");
4916 for(var t=0; t<children.length; t++) {
4917 var c = children[t];
4918 if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
4919 if(!e)
4920 e = c;
4921 if(c.getAttribute("edit") == field)
4922 e = c;
4926 return e;
4929 Story.prototype.focusTiddler = function(title,field)
4931 var e = this.getTiddlerField(title,field);
4932 if(e) {
4933 e.focus();
4934 e.select();
4938 Story.prototype.blurTiddler = function(title)
4940 var tiddlerElem = document.getElementById(this.idPrefix + title);
4941 if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
4942 tiddlerElem.focus();
4943 tiddlerElem.blur();
4947 Story.prototype.setTiddlerField = function(title,tag,mode,field)
4949 var c = story.getTiddlerField(title,field);
4951 var tags = c.value.readBracketedList();
4952 tags.setItem(tag,mode);
4953 c.value = String.encodeTiddlyLinkList(tags);
4956 Story.prototype.setTiddlerTag = function(title,tag,mode)
4958 Story.prototype.setTiddlerField(title,tag,mode,"tags");
4961 Story.prototype.closeTiddler = function(title,animate,unused)
4963 var tiddlerElem = document.getElementById(this.idPrefix + title);
4964 if(tiddlerElem != null) {
4965 clearMessage();
4966 this.scrubTiddler(tiddlerElem);
4967 if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
4968 anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
4969 else {
4970 removeNode(tiddlerElem);
4971 forceReflow();
4976 Story.prototype.scrubTiddler = function(tiddlerElem)
4978 tiddlerElem.id = null;
4981 Story.prototype.setDirty = function(title,dirty)
4983 var tiddlerElem = document.getElementById(this.idPrefix + title);
4984 if(tiddlerElem != null)
4985 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
4988 Story.prototype.isDirty = function(title)
4990 var tiddlerElem = document.getElementById(this.idPrefix + title);
4991 if(tiddlerElem != null)
4992 return tiddlerElem.getAttribute("dirty") == "true";
4993 return null;
4996 Story.prototype.areAnyDirty = function()
4998 var r = false;
4999 this.forEachTiddler(function(title,element) {
5000 if(this.isDirty(title))
5001 r = true;
5003 return r;
5006 Story.prototype.closeAllTiddlers = function(exclude)
5008 clearMessage();
5009 this.forEachTiddler(function(title,element) {
5010 if((title != exclude) && element.getAttribute("dirty") != "true")
5011 this.closeTiddler(title);
5013 window.scrollTo(0,ensureVisible(this.container));
5016 Story.prototype.isEmpty = function()
5018 var place = document.getElementById(this.container);
5019 return place && place.firstChild == null;
5022 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
5024 this.closeAllTiddlers();
5025 highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
5026 var matches = store.search(highlightHack,"title","excludeSearch");
5027 this.displayTiddlers(null,matches);
5028 highlightHack = null;
5029 var q = useRegExp ? "/" : "'";
5030 if(matches.length > 0)
5031 displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
5032 else
5033 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5036 Story.prototype.findContainingTiddler = function(e)
5038 while(e && !hasClass(e,"tiddler"))
5039 e = e.parentNode;
5040 return e;
5043 Story.prototype.gatherSaveFields = function(e,fields)
5045 if(e && e.getAttribute) {
5046 var f = e.getAttribute("edit");
5047 if(f)
5048 fields[f] = e.value.replace(/\r/mg,"");
5049 if(e.hasChildNodes()) {
5050 var c = e.childNodes;
5051 for(var t=0; t<c.length; t++)
5052 this.gatherSaveFields(c[t],fields);
5057 Story.prototype.hasChanges = function(title)
5059 var e = document.getElementById(this.idPrefix + title);
5060 if(e != null) {
5061 var fields = {};
5062 this.gatherSaveFields(e,fields);
5063 var tiddler = store.fetchTiddler(title);
5064 if(!tiddler)
5065 return false;
5066 for(var n in fields) {
5067 if(store.getValue(title,n) != fields[n])
5068 return true;
5071 return false;
5074 Story.prototype.saveTiddler = function(title,minorUpdate)
5076 var tiddlerElem = document.getElementById(this.idPrefix + title);
5077 if(tiddlerElem != null) {
5078 var fields = {};
5079 this.gatherSaveFields(tiddlerElem,fields);
5080 var newTitle = fields.title ? fields.title : title;
5081 if(!store.tiddlerExists(newTitle))
5082 newTitle = newTitle.trim();
5083 if(store.tiddlerExists(newTitle) && newTitle != title) {
5084 if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
5085 return null;
5087 if(newTitle != title)
5088 this.closeTiddler(newTitle,false);
5089 tiddlerElem.id = this.idPrefix + newTitle;
5090 tiddlerElem.setAttribute("tiddler",newTitle);
5091 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
5092 tiddlerElem.setAttribute("dirty","false");
5093 if(config.options.chkForceMinorUpdate)
5094 minorUpdate = !minorUpdate;
5095 if(!store.tiddlerExists(newTitle))
5096 minorUpdate = false;
5097 var newDate = new Date();
5098 var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : {});
5099 for(var n in fields) {
5100 if(!TiddlyWiki.isStandardField(n))
5101 extendedFields[n] = fields[n];
5103 var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
5104 autoSaveChanges(null,[tiddler]);
5105 return newTitle;
5107 return null;
5110 Story.prototype.permaView = function()
5112 var links = [];
5113 this.forEachTiddler(function(title,element) {
5114 links.push(String.encodeTiddlyLink(title));
5116 var t = encodeURIComponent(links.join(" "));
5117 if(t == "")
5118 t = "#";
5119 if(window.location.hash != t)
5120 window.location.hash = t;
5124 Story.prototype.switchTheme = function(theme)
5126 if(safeMode)
5127 return;
5129 isAvailable = function(title) {
5130 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5131 if(s!=-1)
5132 title = title.substr(0,s);
5133 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5136 getSlice = function(theme,slice) {
5137 var r = store.getTiddlerSlice(theme,slice);
5138 if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
5139 r = theme + r;
5140 return isAvailable(r) ? r : slice;
5143 replaceNotification = function(i,name,newName) {
5144 if(name==newName)
5145 return name;
5146 if(store.namedNotifications[i].name == name) {
5147 store.namedNotifications[i].name = newName;
5148 return newName;
5150 return name;
5153 for(var i=0; i<config.notifyTiddlers.length; i++) {
5154 var name = config.notifyTiddlers[i].name;
5155 switch(name) {
5156 case "PageTemplate":
5157 config.refreshers.pageTemplate = replaceNotification(i,config.refreshers.pageTemplate,getSlice(theme,name));
5158 break;
5159 case "StyleSheet":
5160 removeStyleSheet(config.refreshers.styleSheet);
5161 config.refreshers.styleSheet = replaceNotification(i,config.refreshers.styleSheet,getSlice(theme,name));
5162 break;
5163 case "ColorPalette":
5164 config.refreshers.colorPalette = replaceNotification(i,config.refreshers.colorPalette,getSlice(theme,name));
5165 break;
5166 default:
5167 break;
5170 config.tiddlerTemplates[DEFAULT_VIEW_TEMPLATE] = getSlice(theme,"ViewTemplate");
5171 config.tiddlerTemplates[DEFAULT_EDIT_TEMPLATE] = getSlice(theme,"EditTemplate");
5172 if(!startingUp) {
5173 refreshAll();
5174 story.refreshAllTiddlers(true);
5175 config.options.txtTheme = theme;
5176 saveOptionCookie("txtTheme");
5180 //--
5181 //-- Backstage
5182 //--
5184 var backstage = {
5185 area: null,
5186 toolbar: null,
5187 button: null,
5188 showButton: null,
5189 hideButton: null,
5190 cloak: null,
5191 panel: null,
5192 panelBody: null,
5193 panelFooter: null,
5194 currTabName: null,
5195 currTabElem: null,
5196 content: null,
5198 init: function() {
5199 var cmb = config.messages.backstage;
5200 this.area = document.getElementById("backstageArea");
5201 this.toolbar = document.getElementById("backstageToolbar");
5202 this.button = document.getElementById("backstageButton");
5203 this.button.style.display = "block";
5204 var t = cmb.open.text + " " + glyph("bentArrowLeft");
5205 this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
5206 function (e) {backstage.show(); return false;},null,"backstageShow");
5207 t = glyph("bentArrowRight") + " " + cmb.close.text;
5208 this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
5209 function (e) {backstage.hide(); return false;},null,"backstageHide");
5210 this.cloak = document.getElementById("backstageCloak");
5211 this.panel = document.getElementById("backstagePanel");
5212 this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
5213 this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
5214 this.cloak.onmousedown = function(e) {
5215 backstage.switchTab(null);
5217 createTiddlyText(this.toolbar,cmb.prompt);
5218 for(t=0; t<config.backstageTasks.length; t++) {
5219 var taskName = config.backstageTasks[t];
5220 var task = config.tasks[taskName];
5221 var handler = task.action ? this.onClickCommand : this.onClickTab;
5222 var text = task.text + (task.action ? "" : glyph("downTriangle"));
5223 var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
5224 btn.setAttribute("task",taskName);
5225 addClass(btn,task.action ? "backstageAction" : "backstageTask");
5227 this.content = document.getElementById("contentWrapper");
5228 if(config.options.chkBackstage)
5229 this.show();
5230 else
5231 this.hide();
5234 isVisible: function() {
5235 return this.area ? this.area.style.display == "block" : false;
5238 show: function() {
5239 this.area.style.display = "block";
5240 if(anim && config.options.chkAnimate) {
5241 backstage.toolbar.style.left = findWindowWidth() + "px";
5242 var p = [
5243 {style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
5245 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
5246 } else {
5247 backstage.area.style.left = "0px";
5249 this.showButton.style.display = "none";
5250 this.hideButton.style.display = "block";
5251 config.options.chkBackstage = true;
5252 saveOptionCookie("chkBackstage");
5253 addClass(this.content,"backstageVisible");
5256 hide: function() {
5257 if(this.currTabElem) {
5258 this.switchTab(null);
5259 } else {
5260 backstage.toolbar.style.left = "0px";
5261 if(anim && config.options.chkAnimate) {
5262 var p = [
5263 {style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
5265 var c = function(element,properties) {backstage.area.style.display = "none";};
5266 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
5267 } else {
5268 this.area.style.display = "none";
5270 this.showButton.style.display = "block";
5271 this.hideButton.style.display = "none";
5272 config.options.chkBackstage = false;
5273 saveOptionCookie("chkBackstage");
5274 removeClass(this.content,"backstageVisible");
5278 onClickCommand: function(e) {
5279 var task = config.tasks[this.getAttribute("task")];
5280 displayMessage(task);
5281 if(task.action) {
5282 backstage.switchTab(null);
5283 task.action();
5285 return false;
5288 onClickTab: function(e) {
5289 backstage.switchTab(this.getAttribute("task"));
5290 return false;
5293 // Switch to a given tab, or none if null is passed
5294 switchTab: function(tabName) {
5295 var tabElem = null;
5296 var e = this.toolbar.firstChild;
5297 while(e)
5299 if(e.getAttribute && e.getAttribute("task") == tabName)
5300 tabElem = e;
5301 e = e.nextSibling;
5303 if(tabName == backstage.currTabName)
5304 return;
5305 if(backstage.currTabElem) {
5306 removeClass(this.currTabElem,"backstageSelTab");
5308 if(tabElem && tabName) {
5309 backstage.preparePanel();
5310 addClass(tabElem,"backstageSelTab");
5311 var task = config.tasks[tabName];
5312 wikify(task.content,backstage.panelBody,null,null);
5313 backstage.showPanel();
5314 } else if(backstage.currTabElem) {
5315 backstage.hidePanel();
5317 backstage.currTabName = tabName;
5318 backstage.currTabElem = tabElem;
5321 isPanelVisible: function() {
5322 return backstage.panel ? backstage.panel.style.display == "block" : false;
5325 preparePanel: function() {
5326 backstage.cloak.style.height = findWindowHeight() + "px";
5327 backstage.cloak.style.display = "block";
5328 removeChildren(backstage.panelBody);
5329 return backstage.panelBody;
5332 showPanel: function() {
5333 backstage.panel.style.display = "block";
5334 if(anim && config.options.chkAnimate) {
5335 backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
5336 var p = [
5337 {style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
5339 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
5340 } else {
5341 backstage.panel.style.top = "0px";
5343 return backstage.panelBody;
5346 hidePanel: function() {
5347 backstage.currTabName = null;
5348 backstage.currTabElem = null;
5349 if(anim && config.options.chkAnimate) {
5350 var p = [
5351 {style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
5352 {style: "display", atEnd: "none"}
5354 var c = function(element,properties) {backstage.cloak.style.display = "none";};
5355 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
5356 } else {
5357 backstage.panel.style.display = "none";
5358 backstage.cloak.style.display = "none";
5363 config.macros.backstage = {};
5365 config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5367 var backstageTask = config.tasks[params[0]];
5368 if(backstageTask)
5369 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5372 //--
5373 //-- ImportTiddlers macro
5374 //--
5376 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5378 if(readOnly) {
5379 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
5380 return;
5382 var w = new Wizard();
5383 w.createWizard(place,this.wizardTitle);
5384 this.restart(w);
5387 config.macros.importTiddlers.onCancel = function(e)
5389 var wizard = new Wizard(this);
5390 var place = wizard.clear();
5391 config.macros.importTiddlers.restart(wizard);
5392 return false;
5395 config.macros.importTiddlers.restart = function(wizard)
5397 wizard.addStep(this.step1Title,this.step1Html);
5398 var s = wizard.getElement("selTypes");
5399 for(var t in config.adaptors) {
5400 var e = createTiddlyElement(s,"option",null,null,t);
5401 e.value = t;
5403 s = wizard.getElement("selFeeds");
5404 var feeds = this.getFeeds();
5405 for(t in feeds) {
5406 e = createTiddlyElement(s,"option",null,null,t);
5407 e.value = t;
5409 wizard.setValue("feeds",feeds);
5410 s.onchange = config.macros.importTiddlers.onFeedChange;
5411 var fileInput = wizard.getElement("txtBrowse");
5412 fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
5413 fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
5414 wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
5417 config.macros.importTiddlers.getFeeds = function()
5419 var feeds = {};
5420 var tagged = store.getTaggedTiddlers("systemServer","title");
5421 for(var t=0; t<tagged.length; t++) {
5422 var title = tagged[t].title;
5423 var serverType = store.getTiddlerSlice(title,"Type");
5424 if(!serverType)
5425 serverType = "file";
5426 feeds[title] = {title: title,
5427 url: store.getTiddlerSlice(title,"URL"),
5428 workspace: store.getTiddlerSlice(title,"Workspace"),
5429 workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
5430 tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
5431 serverType: serverType,
5432 description: store.getTiddlerSlice(title,"Description")};
5434 return feeds;
5437 config.macros.importTiddlers.onFeedChange = function(e)
5439 var wizard = new Wizard(this);
5440 var selTypes = wizard.getElement("selTypes");
5441 var fileInput = wizard.getElement("txtPath");
5442 var feeds = wizard.getValue("feeds");
5443 var f = feeds[this.value];
5444 if(f) {
5445 selTypes.value = f.serverType;
5446 fileInput.value = f.url;
5447 this.selectedIndex = 0;
5448 wizard.setValue("feedName",f.serverType);
5449 wizard.setValue("feedHost",f.url);
5450 wizard.setValue("feedWorkspace",f.workspace);
5451 wizard.setValue("feedWorkspaceList",f.workspaceList);
5452 wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
5454 return false;
5457 config.macros.importTiddlers.onBrowseChange = function(e)
5459 var wizard = new Wizard(this);
5460 var fileInput = wizard.getElement("txtPath");
5461 fileInput.value = "file://" + this.value;
5462 var serverType = wizard.getElement("selTypes");
5463 serverType.value = "file";
5464 return false;
5467 config.macros.importTiddlers.onOpen = function(e)
5469 var wizard = new Wizard(this);
5470 var fileInput = wizard.getElement("txtPath");
5471 var url = fileInput.value;
5472 var serverType = wizard.getElement("selTypes").value;
5473 var adaptor = new config.adaptors[serverType];
5474 wizard.setValue("adaptor",adaptor);
5475 wizard.setValue("serverType",serverType);
5476 wizard.setValue("host",url);
5477 var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
5478 if(ret !== true)
5479 displayMessage(ret);
5480 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
5481 return false;
5484 config.macros.importTiddlers.onOpenHost = function(context,wizard)
5486 var adaptor = wizard.getValue("adaptor");
5487 if(context.status !== true)
5488 displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
5489 var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
5490 if(ret !== true)
5491 displayMessage(ret);
5492 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
5495 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
5497 if(context.status !== true)
5498 displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
5499 wizard.setValue("context",context);
5500 var workspace = wizard.getValue("feedWorkspace");
5501 if(!workspace && context.workspaces.length==1)
5502 workspace = context.workspaces[0].title;
5503 if(workspace) {
5504 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5505 if(ret !== true)
5506 displayMessage(ret);
5507 wizard.setValue("workspace",workspace);
5508 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5509 return;
5511 wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
5512 var s = wizard.getElement("selWorkspace");
5513 s.onchange = config.macros.importTiddlers.onWorkspaceChange;
5514 for(var t=0; t<context.workspaces.length; t++) {
5515 var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
5516 e.value = context.workspaces[t].title;
5518 var workspaceList = wizard.getValue("feedWorkspaceList");
5519 if(workspaceList) {
5520 var list = workspaceList.parseParams("workspace",null,false,true);
5521 for(var n=1; n<list.length; n++) {
5522 if(context.workspaces.findByField("title",list[n].value) == null) {
5523 e = createTiddlyElement(s,"option",null,null,list[n].value);
5524 e.value = list[n].value;
5528 if(workspace) {
5529 t = wizard.getElement("txtWorkspace");
5530 t.value = workspace;
5532 wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
5535 config.macros.importTiddlers.onWorkspaceChange = function(e)
5537 var wizard = new Wizard(this);
5538 var t = wizard.getElement("txtWorkspace");
5539 t.value = this.value;
5540 this.selectedIndex = 0;
5541 return false;
5544 config.macros.importTiddlers.onChooseWorkspace = function(e)
5546 var wizard = new Wizard(this);
5547 var adaptor = wizard.getValue("adaptor");
5548 var workspace = wizard.getElement("txtWorkspace").value;
5549 wizard.setValue("workspace",workspace);
5550 var context = wizard.getValue("context");
5551 var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5552 if(ret !== true)
5553 displayMessage(ret);
5554 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5555 return false;
5558 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
5560 if(context.status !== true)
5561 displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
5562 var adaptor = wizard.getValue("adaptor");
5563 var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
5564 if(ret !== true)
5565 displayMessage(ret);
5566 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
5569 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
5571 if(context.status !== true)
5572 displayMessage("Error in importTiddlers.onGetTiddlerList: " + context.statusText);
5573 // Extract data for the listview
5574 var listedTiddlers = [];
5575 if(context.tiddlers) {
5576 for(var n=0; n<context.tiddlers.length; n++) {
5577 var tiddler = context.tiddlers[n];
5578 listedTiddlers.push({
5579 title: tiddler.title,
5580 modified: tiddler.modified,
5581 modifier: tiddler.modifier,
5582 text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
5583 tags: tiddler.tags,
5584 size: tiddler.text ? tiddler.text.length : 0,
5585 tiddler: tiddler
5589 listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5590 // Display the listview
5591 wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
5592 var markList = wizard.getElement("markList");
5593 var listWrapper = document.createElement("div");
5594 markList.parentNode.insertBefore(listWrapper,markList);
5595 var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
5596 wizard.setValue("listView",listView);
5597 var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
5598 txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
5599 wizard.setButtons([
5600 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
5601 {caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
5605 config.macros.importTiddlers.generateSystemServerName = function(wizard)
5607 var serverType = wizard.getValue("serverType");
5608 var host = wizard.getValue("host");
5609 var workspace = wizard.getValue("workspace");
5610 var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
5611 return pattern.format([serverType,host,workspace]);
5614 config.macros.importTiddlers.saveServerTiddler = function(wizard)
5616 var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
5617 if(store.tiddlerExists(txtSaveTiddler)) {
5618 if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
5619 return;
5620 store.suspendNotifications();
5621 store.removeTiddler(txtSaveTiddler);
5622 store.resumeNotifications();
5624 var serverType = wizard.getValue("serverType");
5625 var host = wizard.getValue("host");
5626 var workspace = wizard.getValue("workspace");
5627 var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
5628 store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
5631 config.macros.importTiddlers.doImport = function(e)
5633 var wizard = new Wizard(this);
5634 if(wizard.getElement("chkSave").checked)
5635 config.macros.importTiddlers.saveServerTiddler(wizard);
5636 var chkSync = wizard.getElement("chkSync").checked;
5637 wizard.setValue("sync",chkSync);
5638 var listView = wizard.getValue("listView");
5639 var rowNames = ListView.getSelectedRows(listView);
5640 var adaptor = wizard.getValue("adaptor");
5641 var overwrite = new Array();
5642 var t;
5643 for(t=0; t<rowNames.length; t++) {
5644 if(store.tiddlerExists(rowNames[t]))
5645 overwrite.push(rowNames[t]);
5647 if(overwrite.length > 0) {
5648 if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
5649 return false;
5651 wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
5652 for(t=0; t<rowNames.length; t++) {
5653 var link = document.createElement("div");
5654 createTiddlyLink(link,rowNames[t],true);
5655 var place = wizard.getElement("markReport");
5656 place.parentNode.insertBefore(link,place);
5658 wizard.setValue("remainingImports",rowNames.length);
5659 wizard.setButtons([
5660 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
5661 ],config.macros.importTiddlers.statusDoingImport);
5662 for(t=0; t<rowNames.length; t++) {
5663 var context = {};
5664 context.allowSynchronous = true;
5665 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
5667 return false;
5670 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
5672 if(!context.status)
5673 displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
5674 var tiddler = context.tiddler;
5675 store.suspendNotifications();
5676 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
5677 if(!wizard.getValue("sync")) {
5678 store.setValue(tiddler.title,'server',null);
5680 store.resumeNotifications();
5681 if(!context.isSynchronous)
5682 store.notify(tiddler.title,true);
5683 var remainingImports = wizard.getValue("remainingImports")-1;
5684 wizard.setValue("remainingImports",remainingImports);
5685 if(remainingImports == 0) {
5686 if(context.isSynchronous) {
5687 store.notifyAll();
5688 refreshDisplay();
5690 wizard.setButtons([
5691 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onCancel}
5692 ],config.macros.importTiddlers.statusDoneImport);
5693 autoSaveChanges();
5697 //--
5698 //-- Sync macro
5699 //--
5701 // Synchronisation handlers
5702 config.syncers = {};
5704 // Sync state.
5705 var currSync = null;
5707 // sync macro
5708 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5710 if(!wikifier.isStatic)
5711 this.startSync(place);
5714 config.macros.sync.startSync = function(place)
5716 if(currSync)
5717 config.macros.sync.cancelSync();
5718 currSync = {};
5719 currSync.syncList = this.getSyncableTiddlers();
5720 this.createSyncTasks();
5721 this.preProcessSyncableTiddlers();
5722 var wizard = new Wizard();
5723 currSync.wizard = wizard;
5724 wizard.createWizard(place,this.wizardTitle);
5725 wizard.addStep(this.step1Title,this.step1Html);
5726 var markList = wizard.getElement("markList");
5727 var listWrapper = document.createElement("div");
5728 markList.parentNode.insertBefore(listWrapper,markList);
5729 currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
5730 this.processSyncableTiddlers();
5731 wizard.setButtons([
5732 {caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
5736 config.macros.sync.getSyncableTiddlers = function()
5738 var list = [];
5739 store.forEachTiddler(function(title,tiddler) {
5740 var syncItem = {};
5741 syncItem.serverType = tiddler.getServerType();
5742 syncItem.serverHost = tiddler.fields['server.host'];
5743 syncItem.serverWorkspace = tiddler.fields['server.workspace'];
5744 syncItem.tiddler = tiddler;
5745 syncItem.title = tiddler.title;
5746 syncItem.isTouched = tiddler.isTouched();
5747 syncItem.selected = syncItem.isTouched;
5748 syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
5749 syncItem.status = syncItem.syncStatus.text;
5750 if(syncItem.serverType && syncItem.serverHost)
5751 list.push(syncItem);
5753 list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5754 return list;
5757 config.macros.sync.preProcessSyncableTiddlers = function()
5759 for(var t=0; t<currSync.syncList.length; t++) {
5760 si = currSync.syncList[t];
5761 var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
5762 si.serverUrl = ti.uri;
5766 config.macros.sync.processSyncableTiddlers = function()
5768 for(var t=0; t<currSync.syncList.length; t++) {
5769 si = currSync.syncList[t];
5770 si.rowElement.style.backgroundColor = si.syncStatus.color;
5774 config.macros.sync.createSyncTasks = function()
5776 currSync.syncTasks = [];
5777 for(var t=0; t<currSync.syncList.length; t++) {
5778 var si = currSync.syncList[t];
5779 var r = null;
5780 for(var st=0; st<currSync.syncTasks.length; st++) {
5781 var cst = currSync.syncTasks[st];
5782 if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
5783 r = cst;
5785 if(r == null) {
5786 si.syncTask = this.createSyncTask(si);
5787 currSync.syncTasks.push(si.syncTask);
5788 } else {
5789 si.syncTask = r;
5790 r.syncItems.push(si);
5795 config.macros.sync.createSyncTask = function(syncItem)
5797 var st = {};
5798 st.serverType = syncItem.serverType;
5799 st.serverHost = syncItem.serverHost;
5800 st.serverWorkspace = syncItem.serverWorkspace;
5801 st.syncItems = [syncItem];
5802 st.syncMachine = new SyncMachine(st.serverType,{
5803 start: function() {
5804 return this.openHost(st.serverHost,"openWorkspace");
5806 openWorkspace: function() {
5807 return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
5809 getTiddlerList: function() {
5810 return this.getTiddlerList("onGetTiddlerList");
5812 onGetTiddlerList: function(context) {
5813 var tiddlers = context.tiddlers;
5814 for(var t=0; t<st.syncItems.length; t++) {
5815 var si = st.syncItems[t];
5816 var f = tiddlers.findByField("title",si.title);
5817 if(f !== null) {
5818 if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
5819 si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
5821 } else {
5822 si.syncStatus = config.macros.sync.syncStatusList.notFound;
5824 config.macros.sync.updateSyncStatus(si);
5827 getTiddler: function(title) {
5828 return this.getTiddler(title,"onGetTiddler");
5830 onGetTiddler: function(context) {
5831 var tiddler = context.tiddler;
5832 var syncItem = st.syncItems.findByField("title",tiddler.title);
5833 if(syncItem !== null) {
5834 syncItem = st.syncItems[syncItem];
5835 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
5836 syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
5837 config.macros.sync.updateSyncStatus(syncItem);
5840 putTiddler: function(tiddler) {
5841 return this.putTiddler(tiddler,"onPutTiddler");
5843 onPutTiddler: function(context) {
5844 var title = context.title;
5845 var syncItem = st.syncItems.findByField("title",title);
5846 if(syncItem !== null) {
5847 syncItem = st.syncItems[syncItem];
5848 store.resetTiddler(title);
5849 syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
5850 config.macros.sync.updateSyncStatus(syncItem);
5854 st.syncMachine.go();
5855 return st;
5858 config.macros.sync.updateSyncStatus = function(syncItem)
5860 var e = syncItem.colElements["status"];
5861 removeChildren(e);
5862 createTiddlyText(e,syncItem.syncStatus.text);
5863 syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
5866 config.macros.sync.doSync = function(e)
5868 var rowNames = ListView.getSelectedRows(currSync.listView);
5869 for(var t=0; t<currSync.syncList.length; t++) {
5870 var si = currSync.syncList[t];
5871 if(rowNames.indexOf(si.title) != -1) {
5872 config.macros.sync.doSyncItem(si);
5875 return false;
5878 config.macros.sync.doSyncItem = function(syncItem)
5880 var r = true;
5881 var sl = config.macros.sync.syncStatusList;
5882 switch(syncItem.syncStatus) {
5883 case sl.changedServer:
5884 r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
5885 break;
5886 case sl.notFound:
5887 case sl.changedLocally:
5888 case sl.changedBoth:
5889 r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
5890 break;
5891 default:
5892 break;
5894 if(r !== true)
5895 displayMessage("Error in doSyncItem: " + r);
5898 config.macros.sync.cancelSync = function()
5900 currSync = null;
5903 function SyncMachine(serverType,steps)
5905 this.serverType = serverType;
5906 this.adaptor = new config.adaptors[serverType];
5907 this.steps = steps;
5910 SyncMachine.prototype.go = function(step,context)
5912 var r = context ? context.status : null;
5913 if(typeof r == "string") {
5914 this.invokeError(r);
5915 return r;
5917 var h = this.steps[step ? step : "start"];
5918 if(!h)
5919 return null;
5920 r = h.call(this,context);
5921 if(typeof r == "string")
5922 this.invokeError(r);
5923 return r;
5926 SyncMachine.prototype.invokeError = function(message)
5928 if(this.steps.error)
5929 this.steps.error(message);
5932 SyncMachine.prototype.openHost = function(host,nextStep)
5934 var me = this;
5935 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
5938 SyncMachine.prototype.getWorkspaceList = function(nextStep)
5940 var me = this;
5941 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
5944 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
5946 var me = this;
5947 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
5950 SyncMachine.prototype.getTiddlerList = function(nextStep)
5952 var me = this;
5953 return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
5956 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
5958 return this.adaptor.generateTiddlerInfo(tiddler);
5961 SyncMachine.prototype.getTiddler = function(title,nextStep)
5963 var me = this;
5964 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
5967 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
5969 var me = this;
5970 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
5973 //--
5974 //-- Manager UI for groups of tiddlers
5975 //--
5977 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5979 var wizard = new Wizard();
5980 wizard.createWizard(place,this.wizardTitle);
5981 wizard.addStep(this.step1Title,this.step1Html);
5982 var markList = wizard.getElement("markList");
5983 var listWrapper = document.createElement("div");
5984 markList.parentNode.insertBefore(listWrapper,markList);
5985 listWrapper.setAttribute("refresh","macro");
5986 listWrapper.setAttribute("macroName","plugins");
5987 listWrapper.setAttribute("params",paramString);
5988 this.refresh(listWrapper,paramString);
5991 config.macros.plugins.refresh = function(listWrapper,params)
5993 var wizard = new Wizard(listWrapper);
5994 var selectedRows = [];
5995 ListView.forEachSelector(listWrapper,function(e,rowName) {
5996 if(e.checked)
5997 selectedRows.push(e.getAttribute("rowName"));
5999 removeChildren(listWrapper);
6000 params = params.parseParams("anon");
6001 var plugins = installedPlugins.slice(0);
6002 var t,tiddler,p;
6003 var configTiddlers = store.getTaggedTiddlers("systemConfig");
6004 for(t=0; t<configTiddlers.length; t++) {
6005 tiddler = configTiddlers[t];
6006 if(plugins.findByField("title",tiddler.title) == null) {
6007 p = getPluginInfo(tiddler);
6008 p.executed = false;
6009 p.log.splice(0,0,this.skippedText);
6010 plugins.push(p);
6013 for(t=0; t<plugins.length; t++) {
6014 p = plugins[t];
6015 p.size = p.tiddler.text ? p.tiddler.text.length : 0;
6016 p.forced = p.tiddler.isTagged("systemConfigForce");
6017 p.disabled = p.tiddler.isTagged("systemConfigDisable");
6018 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
6020 if(plugins.length == 0) {
6021 createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
6022 wizard.setButtons([]);
6023 } else {
6024 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6025 wizard.setValue("listView",listView);
6026 wizard.setButtons([
6027 {caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
6028 {caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
6033 config.macros.plugins.doRemoveTag = function(e)
6035 var wizard = new Wizard(this);
6036 var listView = wizard.getValue("listView");
6037 var rowNames = ListView.getSelectedRows(listView);
6038 if(rowNames.length == 0) {
6039 alert(config.messages.nothingSelected);
6040 } else {
6041 for(var t=0; t<rowNames.length; t++)
6042 store.setTiddlerTag(rowNames[t],false,"systemConfig");
6046 config.macros.plugins.doDelete = function(e)
6048 var wizard = new Wizard(this);
6049 var listView = wizard.getValue("listView");
6050 var rowNames = ListView.getSelectedRows(listView);
6051 if(rowNames.length == 0) {
6052 alert(config.messages.nothingSelected);
6053 } else {
6054 if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
6055 for(t=0; t<rowNames.length; t++) {
6056 store.removeTiddler(rowNames[t]);
6057 story.closeTiddler(rowNames[t],true);
6063 //--
6064 //-- Message area
6065 //--
6067 function getMessageDiv()
6069 var msgArea = document.getElementById("messageArea");
6070 if(!msgArea)
6071 return null;
6072 if(!msgArea.hasChildNodes())
6073 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
6074 config.messages.messageClose.text,
6075 config.messages.messageClose.tooltip,
6076 clearMessage);
6077 msgArea.style.display = "block";
6078 return createTiddlyElement(msgArea,"div");
6081 function displayMessage(text,linkText)
6083 var e = getMessageDiv();
6084 if(!e) {
6085 alert(text);
6086 return;
6088 if(linkText) {
6089 var link = createTiddlyElement(e,"a",null,null,text);
6090 link.href = linkText;
6091 link.target = "_blank";
6092 } else {
6093 e.appendChild(document.createTextNode(text));
6097 function clearMessage()
6099 var msgArea = document.getElementById("messageArea");
6100 if(msgArea) {
6101 removeChildren(msgArea);
6102 msgArea.style.display = "none";
6104 return false;
6107 //--
6108 //-- Refresh mechanism
6109 //--
6111 config.refreshers = {
6112 link: function(e,changeList)
6114 var title = e.getAttribute("tiddlyLink");
6115 refreshTiddlyLink(e,title);
6116 return true;
6119 tiddler: function(e,changeList)
6121 var title = e.getAttribute("tiddler");
6122 var template = e.getAttribute("template");
6123 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
6124 story.refreshTiddler(title,template,true);
6125 else
6126 refreshElements(e,changeList);
6127 return true;
6130 content: function(e,changeList)
6132 var title = e.getAttribute("tiddler");
6133 var force = e.getAttribute("force");
6134 if(force != null || changeList == null || changeList.indexOf(title) != -1) {
6135 removeChildren(e);
6136 wikify(store.getTiddlerText(title,title),e,null);
6137 return true;
6138 } else
6139 return false;
6142 macro: function(e,changeList)
6144 var macro = e.getAttribute("macroName");
6145 var params = e.getAttribute("params");
6146 if(macro)
6147 macro = config.macros[macro];
6148 if(macro && macro.refresh)
6149 macro.refresh(e,params);
6150 return true;
6152 styleSheet: "StyleSheet",
6153 defaultStyleSheet: "StyleSheet",
6154 pageTemplate: "PageTemplate",
6155 defaultPageTemplate: "PageTemplate",
6156 colorPalette: "ColorPalette",
6157 defaultColorPalette: "ColorPalette"
6160 function refreshElements(root,changeList)
6162 var nodes = root.childNodes;
6163 for(var c=0; c<nodes.length; c++) {
6164 var e = nodes[c], type = null;
6165 if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
6166 type = e.getAttribute("refresh");
6167 var refresher = config.refreshers[type];
6168 var refreshed = false;
6169 if(refresher != undefined)
6170 refreshed = refresher(e,changeList);
6171 if(e.hasChildNodes() && !refreshed)
6172 refreshElements(e,changeList);
6176 function applyHtmlMacros(root,tiddler)
6178 var e = root.firstChild;
6179 while(e) {
6180 var nextChild = e.nextSibling;
6181 if(e.getAttribute) {
6182 var macro = e.getAttribute("macro");
6183 if(macro) {
6184 var params = "";
6185 var p = macro.indexOf(" ");
6186 if(p != -1) {
6187 params = macro.substr(p+1);
6188 macro = macro.substr(0,p);
6190 invokeMacro(e,macro,params,null,tiddler);
6193 if(e.hasChildNodes())
6194 applyHtmlMacros(e,tiddler);
6195 e = nextChild;
6199 function refreshPageTemplate(title)
6201 var stash = createTiddlyElement(document.body,"div");
6202 stash.style.display = "none";
6203 var display = document.getElementById("tiddlerDisplay");
6204 var nodes,t;
6205 if(display) {
6206 nodes = display.childNodes;
6207 for(t=nodes.length-1; t>=0; t--)
6208 stash.appendChild(nodes[t]);
6210 var wrapper = document.getElementById("contentWrapper");
6212 isAvailable = function(title) {
6213 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
6214 if(s!=-1)
6215 title = title.substr(0,s);
6216 return store.tiddlerExists(title) || store.isShadowTiddler(title);
6218 if(!title || !isAvailable(title))
6219 title = config.refreshers.pageTemplate;
6220 if(!isAvailable(title))
6221 title = config.refreshers.defaultPageTemplate; //# this one is always avaialable
6222 html = store.getRecursiveTiddlerText(title,null,10);
6223 wrapper.innerHTML = html;
6224 applyHtmlMacros(wrapper);
6225 refreshElements(wrapper);
6226 display = document.getElementById("tiddlerDisplay");
6227 removeChildren(display);
6228 if(!display)
6229 display = createTiddlyElement(wrapper,"div","tiddlerDisplay");
6230 nodes = stash.childNodes;
6231 for(t=nodes.length-1; t>=0; t--)
6232 display.appendChild(nodes[t]);
6233 removeNode(stash);
6236 function refreshDisplay(hint)
6238 if(typeof hint == "string")
6239 hint = [hint];
6240 var e = document.getElementById("contentWrapper");
6241 refreshElements(e,hint);
6242 if(backstage.isPanelVisible()) {
6243 e = document.getElementById("backstage");
6244 refreshElements(e,hint);
6248 function refreshPageTitle()
6250 document.title = getPageTitle();
6253 function getPageTitle()
6255 var st = wikifyPlain("SiteTitle");
6256 var ss = wikifyPlain("SiteSubtitle");
6257 return st + ((st == "" || ss == "") ? "" : " - ") + ss;
6260 function refreshStyles(title,doc)
6262 setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc ? doc : document);
6265 function refreshColorPalette(title)
6267 if(!startingUp)
6268 refreshAll();
6271 function refreshAll()
6273 refreshPageTemplate();
6274 refreshDisplay();
6275 refreshStyles("StyleSheetLayout");
6276 refreshStyles("StyleSheetColors");
6277 refreshStyles(config.refreshers.styleSheet);
6278 refreshStyles("StyleSheetPrint");
6281 //--
6282 //-- Options stuff
6283 //--
6285 config.optionHandlers = {
6286 'txt': {
6287 get: function(name) {return encodeCookie(config.options[name].toString());},
6288 set: function(name,value) {config.options[name] = decodeCookie(value);}
6290 'chk': {
6291 get: function(name) {return config.options[name] ? "true" : "false";},
6292 set: function(name,value) {config.options[name] = value == "true";}
6296 function loadOptionsCookie()
6298 if(safeMode)
6299 return;
6300 var cookies = document.cookie.split(";");
6301 for(var c=0; c<cookies.length; c++) {
6302 var p = cookies[c].indexOf("=");
6303 if(p != -1) {
6304 var name = cookies[c].substr(0,p).trim();
6305 var value = cookies[c].substr(p+1).trim();
6306 var optType = name.substr(0,3);
6307 if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
6308 config.optionHandlers[optType].set(name,value);
6313 function saveOptionCookie(name)
6315 if(safeMode)
6316 return;
6317 var c = name + "=";
6318 var optType = name.substr(0,3);
6319 if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
6320 c += config.optionHandlers[optType].get(name);
6321 c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
6322 document.cookie = c;
6325 function encodeCookie(s)
6327 return escape(manualConvertUnicodeToUTF8(s));
6330 function decodeCookie(s)
6332 s = unescape(s);
6333 var re = /&#[0-9]{1,5};/g;
6334 return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
6338 config.macros.option.genericCreate = function(place,type,opt,className,desc)
6340 var typeInfo = config.macros.option.types[type];
6341 var c = document.createElement(typeInfo.elementType);
6342 if(typeInfo.typeValue)
6343 c.setAttribute("type",typeInfo.typeValue);
6344 c[typeInfo.eventName] = typeInfo.onChange;
6345 c.setAttribute("option",opt);
6346 if(className)
6347 c.className = className;
6348 else
6349 c.className = typeInfo.className;
6350 if(config.optionsDesc[opt])
6351 c.setAttribute("title",config.optionsDesc[opt]);
6352 place.appendChild(c);
6353 if(desc != "no")
6354 createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
6355 c[typeInfo.valueField] = config.options[opt];
6356 return c;
6359 config.macros.option.genericOnChange = function(e)
6361 var opt = this.getAttribute("option");
6362 if(opt) {
6363 var optType = opt.substr(0,3);
6364 var handler = config.macros.option.types[optType];
6365 if (handler.elementType && handler.valueField)
6366 config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
6368 return true;
6371 config.macros.option.types = {
6372 'txt': {
6373 elementType: "input",
6374 valueField: "value",
6375 eventName: "onkeyup",
6376 className: "txtOptionInput",
6377 create: config.macros.option.genericCreate,
6378 onChange: config.macros.option.genericOnChange
6380 'chk': {
6381 elementType: "input",
6382 valueField: "checked",
6383 eventName: "onclick",
6384 className: "chkOptionInput",
6385 typeValue: "checkbox",
6386 create: config.macros.option.genericCreate,
6387 onChange: config.macros.option.genericOnChange
6391 config.macros.option.propagateOption = function(opt,valueField,value,elementType)
6393 config.options[opt] = value;
6394 saveOptionCookie(opt);
6395 var nodes = document.getElementsByTagName(elementType);
6396 for(var t=0; t<nodes.length; t++) {
6397 var optNode = nodes[t].getAttribute("option");
6398 if(opt == optNode)
6399 nodes[t][valueField] = value;
6403 config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6405 params = paramString.parseParams("anon",null,true,false,false);
6406 var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
6407 var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
6408 var desc = getParam(params,"desc","no");
6409 var type = opt.substr(0,3);
6410 var h = config.macros.option.types[type];
6411 if (h && h.create)
6412 h.create(place,type,opt,className,desc);
6415 config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6417 params = paramString.parseParams("anon",null,true,false,false);
6418 var showUnknown = getParam(params,"showUnknown","no");
6419 var wizard = new Wizard();
6420 wizard.createWizard(place,this.wizardTitle);
6421 wizard.addStep(this.step1Title,this.step1Html);
6422 var markList = wizard.getElement("markList");
6423 var chkUnknown = wizard.getElement("chkUnknown");
6424 chkUnknown.checked = showUnknown == "yes";
6425 chkUnknown.onchange = this.onChangeUnknown;
6426 var listWrapper = document.createElement("div");
6427 markList.parentNode.insertBefore(listWrapper,markList);
6428 wizard.setValue("listWrapper",listWrapper);
6429 this.refreshOptions(listWrapper,showUnknown == "yes");
6432 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
6434 var opts = [];
6435 for(var n in config.options) {
6436 var opt = {};
6437 opt.option = "";
6438 opt.name = n;
6439 opt.lowlight = !config.optionsDesc[n];
6440 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6441 if(!opt.lowlight || showUnknown)
6442 opts.push(opt);
6444 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);});
6445 var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
6446 for(n=0; n<opts.length; n++) {
6447 var type = opts[n].name.substr(0,3);
6448 var h = config.macros.option.types[type];
6449 if (h && h.create) {
6450 h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
6455 config.macros.options.onChangeUnknown = function(e)
6457 var wizard = new Wizard(this);
6458 var listWrapper = wizard.getValue("listWrapper");
6459 removeChildren(listWrapper);
6460 config.macros.options.refreshOptions(listWrapper,this.checked);
6461 return false;
6464 //--
6465 //-- Saving
6466 //--
6468 var saveUsingSafari = false;
6470 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
6471 var endSaveArea = '</d' + 'iv>';
6473 // If there are unsaved changes, force the user to confirm before exitting
6474 function confirmExit()
6476 hadConfirmExit = true;
6477 if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
6478 return config.messages.confirmExit;
6481 // Give the user a chance to save changes before exitting
6482 function checkUnsavedChanges()
6484 if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
6485 if(confirm(config.messages.unsavedChangesWarning))
6486 saveChanges();
6490 function updateLanguageAttribute(s)
6492 if(config.locale) {
6493 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
6494 var m = mRE.exec(s);
6495 if(m) {
6496 var t = m[1];
6497 if(m[2])
6498 t += ' xml:lang="' + config.locale + '"';
6499 if(m[3])
6500 t += ' lang="' + config.locale + '"';
6501 t += ">";
6502 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
6505 return s;
6508 function updateMarkupBlock(s,blockName,tiddlerName)
6510 return s.replaceChunk(
6511 "<!--%0-START-->".format([blockName]),
6512 "<!--%0-END-->".format([blockName]),
6513 "\n" + store.getRecursiveTiddlerText(tiddlerName,"") + "\n");
6516 function updateOriginal(original,posDiv)
6518 if(!posDiv)
6519 posDiv = locateStoreArea(original);
6520 if(!posDiv) {
6521 alert(config.messages.invalidFileError.format([localPath]));
6522 return null;
6524 var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
6525 convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
6526 original.substr(posDiv[1]);
6527 var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
6528 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
6529 revised = updateLanguageAttribute(revised);
6530 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
6531 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
6532 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
6533 revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
6534 return revised;
6537 function locateStoreArea(original)
6539 // Locate the storeArea div's
6540 var posOpeningDiv = original.indexOf(startSaveArea);
6541 var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
6542 if(limitClosingDiv == -1)
6543 limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
6544 var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
6545 return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
6548 function autoSaveChanges(onlyIfDirty,tiddlers)
6550 if(config.options.chkAutoSave)
6551 saveChanges(onlyIfDirty,tiddlers);
6554 // Save this tiddlywiki with the pending changes
6555 function saveChanges(onlyIfDirty,tiddlers)
6557 if(onlyIfDirty && !store.isDirty())
6558 return;
6559 clearMessage();
6560 // Get the URL of the document
6561 var originalPath = document.location.toString();
6562 // Check we were loaded from a file URL
6563 if(originalPath.substr(0,5) != "file:") {
6564 alert(config.messages.notFileUrlError);
6565 if(store.tiddlerExists(config.messages.saveInstructions))
6566 story.displayTiddler(null,config.messages.saveInstructions);
6567 return;
6569 var localPath = getLocalPath(originalPath);
6570 // Load the original file
6571 var original = loadFile(localPath);
6572 if(original == null) {
6573 alert(config.messages.cantSaveError);
6574 if(store.tiddlerExists(config.messages.saveInstructions))
6575 story.displayTiddler(null,config.messages.saveInstructions);
6576 return;
6578 // Locate the storeArea div's
6579 var posDiv = locateStoreArea(original);
6580 if(!posDiv) {
6581 alert(config.messages.invalidFileError.format([localPath]));
6582 return;
6584 saveBackup(localPath,original);
6585 saveRss(localPath);
6586 saveEmpty(localPath,original,posDiv);
6587 saveMain(localPath,original,posDiv);
6590 function saveBackup(localPath,original)
6592 if(config.options.chkSaveBackups) {
6593 var backupPath = getBackupPath(localPath);
6594 var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
6595 if(backup)
6596 displayMessage(config.messages.backupSaved,"file://" + backupPath);
6597 else
6598 alert(config.messages.backupFailed);
6602 function saveRss(localPath)
6604 if(config.options.chkGenerateAnRssFeed) {
6605 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
6606 var rssSave = saveFile(rssPath,convertUnicodeToUTF8(generateRss()));
6607 if(rssSave)
6608 displayMessage(config.messages.rssSaved,"file://" + rssPath);
6609 else
6610 alert(config.messages.rssFailed);
6614 function saveEmpty(localPath,original,posDiv)
6616 if(config.options.chkSaveEmptyTemplate) {
6617 var emptyPath,p;
6618 if((p = localPath.lastIndexOf("/")) != -1)
6619 emptyPath = localPath.substr(0,p) + "/empty.html";
6620 else if((p = localPath.lastIndexOf("\\")) != -1)
6621 emptyPath = localPath.substr(0,p) + "\\empty.html";
6622 else
6623 emptyPath = localPath + ".empty.html";
6624 var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
6625 var emptySave = saveFile(emptyPath,empty);
6626 if(emptySave)
6627 displayMessage(config.messages.emptySaved,"file://" + emptyPath);
6628 else
6629 alert(config.messages.emptyFailed);
6633 function saveMain(localPath,original,posDiv)
6635 var save;
6636 try {
6637 var revised = updateOriginal(original,posDiv);
6638 save = saveFile(localPath,revised);
6639 } catch (ex) {
6640 showException(ex);
6642 if(save) {
6643 displayMessage(config.messages.mainSaved,"file://" + localPath);
6644 store.setDirty(false);
6645 } else {
6646 alert(config.messages.mainFailed);
6650 function getLocalPath(origPath)
6652 var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
6653 // Remove any location or query part of the URL
6654 var argPos = originalPath.indexOf("?");
6655 if(argPos != -1)
6656 originalPath = originalPath.substr(0,argPos);
6657 var hashPos = originalPath.indexOf("#");
6658 if(hashPos != -1)
6659 originalPath = originalPath.substr(0,hashPos);
6660 // Convert file://localhost/ to file:///
6661 if(originalPath.indexOf("file://localhost/") == 0)
6662 originalPath = "file://" + originalPath.substr(16);
6663 // Convert to a native file format
6664 var localPath;
6665 if(originalPath.charAt(9) == ":") // pc local file
6666 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
6667 else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
6668 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
6669 else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
6670 localPath = unescape(originalPath.substr(7));
6671 else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
6672 localPath = unescape(originalPath.substr(5));
6673 else // pc network file
6674 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
6675 return localPath;
6678 function getBackupPath(localPath,title,extension)
6680 var slash = "\\";
6681 var dirPathPos = localPath.lastIndexOf("\\");
6682 if(dirPathPos == -1) {
6683 dirPathPos = localPath.lastIndexOf("/");
6684 slash = "/";
6686 var backupFolder = config.options.txtBackupFolder;
6687 if(!backupFolder || backupFolder == "")
6688 backupFolder = ".";
6689 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
6690 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
6691 if(title)
6692 backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
6693 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension ? extension : "html");
6694 return backupPath;
6697 function generateRss()
6699 var s = [];
6700 var d = new Date();
6701 var u = store.getTiddlerText("SiteUrl");
6702 // Assemble the header
6703 s.push("<" + "?xml version=\"1.0\"?" + ">");
6704 s.push("<rss version=\"2.0\">");
6705 s.push("<channel>");
6706 s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
6707 if(u)
6708 s.push("<link>" + u.htmlEncode() + "</link>");
6709 s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
6710 s.push("<language>en-us</language>");
6711 s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
6712 s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
6713 s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
6714 s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
6715 s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
6716 // The body
6717 var tiddlers = store.getTiddlers("modified","excludeLists");
6718 var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
6719 for (var t=tiddlers.length-1; t>=n; t--) {
6720 s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
6722 // And footer
6723 s.push("</channel>");
6724 s.push("</rss>");
6725 // Save it all
6726 return s.join("\n");
6729 //--
6730 //-- Filesystem code
6731 //--
6733 function convertUTF8ToUnicode(u)
6735 return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
6738 function manualConvertUTF8ToUnicode(utf)
6740 var uni = utf;
6741 var src = 0;
6742 var dst = 0;
6743 var b1, b2, b3;
6744 var c;
6745 while(src < utf.length) {
6746 b1 = utf.charCodeAt(src++);
6747 if(b1 < 0x80) {
6748 dst++;
6749 } else if(b1 < 0xE0) {
6750 b2 = utf.charCodeAt(src++);
6751 c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
6752 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
6753 } else {
6754 b2 = utf.charCodeAt(src++);
6755 b3 = utf.charCodeAt(src++);
6756 c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
6757 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
6760 return uni;
6763 function mozConvertUTF8ToUnicode(u)
6765 try {
6766 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6767 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
6768 converter.charset = "UTF-8";
6769 } catch(ex) {
6770 return manualConvertUTF8ToUnicode(u);
6771 } // fallback
6772 var s = converter.ConvertToUnicode(u);
6773 var fin = converter.Finish();
6774 return (fin.length > 0) ? s+fin : s;
6777 function convertUnicodeToUTF8(s)
6779 if(window.netscape == undefined)
6780 return manualConvertUnicodeToUTF8(s);
6781 else
6782 return mozConvertUnicodeToUTF8(s);
6785 function manualConvertUnicodeToUTF8(s)
6787 var re = /[^\u0000-\u007F]/g ;
6788 return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
6791 function mozConvertUnicodeToUTF8(s)
6793 try {
6794 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6795 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
6796 converter.charset = "UTF-8";
6797 } catch(ex) {
6798 return manualConvertUnicodeToUTF8(s);
6799 } // fallback
6800 var u = converter.ConvertFromUnicode(s);
6801 var fin = converter.Finish();
6802 return fin.length > 0 ? u + fin : u;
6805 function convertUriToUTF8(uri,charSet)
6807 if(window.netscape == undefined || charSet == undefined || charSet == "")
6808 return uri;
6809 try {
6810 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6811 var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
6812 } catch(ex) {
6813 return uri;
6815 return converter.convertURISpecToUTF8(uri,charSet);
6818 function saveFile(fileUrl,content)
6820 var r = mozillaSaveFile(fileUrl,content);
6821 if(!r)
6822 r = ieSaveFile(fileUrl,content);
6823 if(!r)
6824 r = javaSaveFile(fileUrl,content);
6825 return r;
6828 function loadFile(fileUrl)
6830 var r = mozillaLoadFile(fileUrl);
6831 if((r == null) || (r == false))
6832 r = ieLoadFile(fileUrl);
6833 if((r == null) || (r == false))
6834 r = javaLoadFile(fileUrl);
6835 return r;
6838 function ieCreatePath(path)
6840 try {
6841 var fso = new ActiveXObject("Scripting.FileSystemObject");
6842 } catch(ex) {
6843 return null;
6846 var pos = path.lastIndexOf("\\");
6847 if(pos!=-1)
6848 path = path.substring(0, pos+1);
6850 var scan = [];
6851 scan.push(path);
6852 var i = 0;
6853 do {
6854 var parent = fso.GetParentFolderName(scan[i++]);
6855 if (fso.FolderExists(parent))
6856 break;
6857 scan.push(parent);
6858 } while(true);
6860 for(i=scan.length-1;i>=0;i--) {
6861 if (!fso.FolderExists(scan[i]))
6862 fso.CreateFolder(scan[i]);
6864 return true;
6867 // Returns null if it can't do it, false if there's an error, true if it saved OK
6868 function ieSaveFile(filePath,content)
6870 ieCreatePath(filePath);
6871 try {
6872 var fso = new ActiveXObject("Scripting.FileSystemObject");
6873 } catch(ex) {
6874 return null;
6876 var file = fso.OpenTextFile(filePath,2,-1,0);
6877 file.Write(content);
6878 file.Close();
6879 return true;
6882 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
6883 function ieLoadFile(filePath)
6885 try {
6886 var fso = new ActiveXObject("Scripting.FileSystemObject");
6887 var file = fso.OpenTextFile(filePath,1);
6888 var content = file.ReadAll();
6889 file.Close();
6890 } catch(ex) {
6891 return null;
6893 return content;
6896 function ieCopyFile(dest,source)
6898 ieCreatePath(dest);
6899 try {
6900 var fso = new ActiveXObject("Scripting.FileSystemObject");
6901 fso.GetFile(source).Copy(dest);
6902 } catch(ex) {
6903 return false;
6905 return true;
6908 // Returns null if it can't do it, false if there's an error, true if it saved OK
6909 function mozillaSaveFile(filePath,content)
6911 if(window.Components) {
6912 try {
6913 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6914 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
6915 file.initWithPath(filePath);
6916 if(!file.exists())
6917 file.create(0,0664);
6918 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
6919 out.init(file,0x20|0x02,00004,null);
6920 out.write(content,content.length);
6921 out.flush();
6922 out.close();
6923 return true;
6924 } catch(ex) {
6925 return false;
6928 return null;
6931 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
6932 function mozillaLoadFile(filePath)
6934 if(window.Components) {
6935 try {
6936 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6937 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
6938 file.initWithPath(filePath);
6939 if(!file.exists())
6940 return null;
6941 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
6942 inputStream.init(file,0x01,00004,null);
6943 var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
6944 sInputStream.init(inputStream);
6945 return sInputStream.read(sInputStream.available());
6946 } catch(ex) {
6947 return false;
6950 return null;
6953 function javaUrlToFilename(url)
6955 var f = "//localhost";
6956 if(url.indexOf(f) == 0)
6957 return url.substring(f.length);
6958 var i = url.indexOf(":");
6959 if(i > 0)
6960 return url.substring(i-1);
6961 return url;
6964 function javaSaveFile(filePath,content)
6966 try {
6967 if(document.applets["TiddlySaver"])
6968 return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
6969 } catch(ex) {
6971 try {
6972 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
6973 s.print(content);
6974 s.close();
6975 } catch(ex) {
6976 return null;
6978 return true;
6981 function javaLoadFile(filePath)
6983 try {
6984 if(document.applets["TiddlySaver"])
6985 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
6986 } catch(ex) {
6988 var content = [];
6989 try {
6990 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
6991 var line;
6992 while((line = r.readLine()) != null)
6993 content.push(new String(line));
6994 r.close();
6995 } catch(ex) {
6996 return null;
6998 return content.join("\n");
7001 //--
7002 //-- Server adaptor for talking to static TiddlyWiki files
7003 //--
7005 function FileAdaptor()
7007 this.host = null;
7008 this.store = null;
7009 return this;
7012 FileAdaptor.serverType = 'file';
7014 FileAdaptor.prototype.setContext = function(context,userParams,callback)
7016 if(!context) context = {};
7017 context.userParams = userParams;
7018 if(callback) context.callback = callback;
7019 context.adaptor = this;
7020 if(!context.host)
7021 context.host = this.host;
7022 context.host = FileAdaptor.fullHostName(context.host);
7023 if(!context.workspace)
7024 context.workspace = this.workspace;
7025 return context;
7028 FileAdaptor.fullHostName = function(host)
7030 if(!host)
7031 return '';
7032 if(!host.match(/:\/\//))
7033 host = 'http://' + host;
7034 return host;
7037 FileAdaptor.minHostName = function(host)
7039 return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
7042 // Open the specified host
7043 FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
7045 this.host = host;
7046 context = this.setContext(context,userParams,callback);
7047 context.status = true;
7048 if(callback)
7049 window.setTimeout(function() {callback(context,userParams);},10);
7050 return true;
7053 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7055 context.status = status;
7056 if(!status) {
7057 context.statusText = "Error reading file: " + xhr.statusText;
7058 } else {
7059 context.adaptor.store = new TiddlyWiki();
7060 if(!context.adaptor.store.importTiddlyWiki(responseText))
7061 context.statusText = config.messages.invalidFileError.format([url]);
7063 context.complete(context,context.userParams);
7066 // Get the list of workspaces on a given server
7067 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
7069 context = this.setContext(context,userParams,callback);
7070 context.workspaces = [{title:"(default)"}];
7071 context.status = true;
7072 if(callback)
7073 window.setTimeout(function() {callback(context,userParams);},10);
7074 return true;
7077 // Open the specified workspace
7078 FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
7080 this.workspace = workspace;
7081 context = this.setContext(context,userParams,callback);
7082 context.status = true;
7083 if(callback)
7084 window.setTimeout(function() {callback(context,userParams);},10);
7085 return true;
7088 // Gets the list of tiddlers within a given workspace
7089 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
7091 context = this.setContext(context,userParams,callback);
7092 if(!context.filter)
7093 context.filter = filter;
7094 context.complete = FileAdaptor.getTiddlerListComplete;
7095 if(this.store) {
7096 var ret = context.complete(context,context.userParams);
7097 } else {
7098 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7099 if(typeof ret != "string")
7100 ret = true;
7102 return ret;
7105 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7107 if(context.filter) {
7108 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
7109 } else {
7110 context.tiddlers = [];
7111 context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
7113 for(var i=0; i<context.tiddlers.length; i++) {
7114 context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
7115 context.tiddlers[i].fields['server.host'] = FileAdaptor.minHostName(context.host);
7116 context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
7118 context.status = true;
7119 if(context.callback) {
7120 window.setTimeout(function() {context.callback(context,userParams);},10);
7122 return true;
7125 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7127 var info = {};
7128 info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
7129 return info;
7132 // Retrieve a tiddler from a given workspace on a given server
7133 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
7135 context = this.setContext(context,userParams,callback);
7136 context.title = title;
7137 context.complete = FileAdaptor.getTiddlerComplete;
7138 return context.adaptor.store ?
7139 context.complete(context,context.userParams) :
7140 loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7143 FileAdaptor.getTiddlerComplete = function(context,userParams)
7145 var t = context.adaptor.store.fetchTiddler(context.title);
7146 t.fields['server.type'] = FileAdaptor.serverType;
7147 t.fields['server.host'] = FileAdaptor.minHostName(context.host);
7148 t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
7149 context.tiddler = t;
7150 context.status = true;
7151 if(context.allowSynchronous) {
7152 context.isSynchronous = true;
7153 context.callback(context,userParams);
7154 } else {
7155 window.setTimeout(function() {callback(context,userParams);},10);
7157 return true;
7160 FileAdaptor.prototype.close = function()
7162 delete this.store;
7163 this.store = null;
7166 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7168 //--
7169 //-- Remote HTTP requests
7170 //--
7172 function loadRemoteFile(url,callback,params)
7174 return doHttp("GET",url,null,null,null,null,callback,params,null);
7177 // HTTP status codes
7178 var httpStatus = {
7179 OK: 200,
7180 ContentCreated: 201,
7181 NoContent: 204,
7182 MultiStatus: 207,
7183 Unauthorized: 401,
7184 Forbidden: 403,
7185 NotFound: 404,
7186 MethodNotAllowed: 405
7189 function doHttp(type,url,data,contentType,username,password,callback,params,headers)
7191 var x = getXMLHttpRequest();
7192 if(!x)
7193 return "Can't create XMLHttpRequest object";
7194 x.onreadystatechange = function() {
7195 try {
7196 var status = x.status;
7197 } catch(ex) {
7198 status = false;
7200 if (x.readyState == 4 && callback && (status !== undefined)) {
7201 if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent, httpStatus.MultiStatus].contains(status))
7202 callback(true,params,x.responseText,url,x);
7203 else
7204 callback(false,params,null,url,x);
7205 x.onreadystatechange = function(){};
7206 x = null;
7209 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
7210 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
7211 try {
7212 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
7213 x.open(type,url,true,username,password);
7214 if(data)
7215 x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
7216 if(x.overrideMimeType)
7217 x.setRequestHeader("Connection", "close");
7218 if(headers) {
7219 for(var n in headers)
7220 x.setRequestHeader(n,headers[n]);
7222 x.setRequestHeader("X-Requested-With", "TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
7223 x.send(data);
7224 } catch(ex) {
7225 return exceptionText(ex);
7227 return x;
7230 function getXMLHttpRequest()
7232 try {
7233 var x = new XMLHttpRequest(); // Modern
7234 } catch(ex) {
7235 try {
7236 x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
7237 } catch (ex2) {
7238 return null;
7241 return x;
7244 //--
7245 //-- TiddlyWiki-specific utility functions
7246 //--
7248 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7250 var btn = document.createElement("a");
7251 if(action) {
7252 btn.onclick = action;
7253 btn.setAttribute("href","javascript:;");
7255 if(tooltip)
7256 btn.setAttribute("title",tooltip);
7257 if(text)
7258 btn.appendChild(document.createTextNode(text));
7259 btn.className = className ? className : "button";
7260 if(id)
7261 btn.id = id;
7262 if(attribs) {
7263 for(var n in attribs) {
7264 btn.setAttribute(n,attribs[n]);
7267 if(parent)
7268 parent.appendChild(btn);
7269 if(accessKey)
7270 btn.setAttribute("accessKey",accessKey);
7271 return btn;
7274 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
7276 var text = includeText ? title : null;
7277 var i = getTiddlyLinkInfo(title,className);
7278 var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
7279 btn.setAttribute("refresh","link");
7280 btn.setAttribute("tiddlyLink",title);
7281 if(noToggle)
7282 btn.setAttribute("noToggle","true");
7283 if(linkedFromTiddler) {
7284 var fields = linkedFromTiddler.getInheritedFields();
7285 if(fields)
7286 btn.setAttribute("tiddlyFields",fields);
7288 return btn;
7291 function refreshTiddlyLink(e,title)
7293 var i = getTiddlyLinkInfo(title,e.className);
7294 e.className = i.classes;
7295 e.title = i.subTitle;
7298 function getTiddlyLinkInfo(title,currClasses)
7300 var classes = currClasses ? currClasses.split(" ") : [];
7301 classes.pushUnique("tiddlyLink");
7302 var tiddler = store.fetchTiddler(title);
7303 var subTitle;
7304 if(tiddler) {
7305 subTitle = tiddler.getSubtitle();
7306 classes.pushUnique("tiddlyLinkExisting");
7307 classes.remove("tiddlyLinkNonExisting");
7308 classes.remove("shadow");
7309 } else {
7310 classes.remove("tiddlyLinkExisting");
7311 classes.pushUnique("tiddlyLinkNonExisting");
7312 if(store.isShadowTiddler(title)) {
7313 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7314 classes.pushUnique("shadow");
7315 } else {
7316 subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
7317 classes.remove("shadow");
7320 if(typeof config.annotations[title]=="string")
7321 subTitle = config.annotations[title];
7322 return {classes: classes.join(" "),subTitle: subTitle};
7325 function createExternalLink(place,url)
7327 var link = document.createElement("a");
7328 link.className = "externalLink";
7329 link.href = url;
7330 link.title = config.messages.externalLinkTooltip.format([url]);
7331 if(config.options.chkOpenInNewWindow)
7332 link.target = "_blank";
7333 place.appendChild(link);
7334 return link;
7337 // Event handler for clicking on a tiddly link
7338 function onClickTiddlerLink(ev)
7340 var e = ev ? ev : window.event;
7341 var target = resolveTarget(e);
7342 var link = target;
7343 var title = null;
7344 var fields = null;
7345 var noToggle = null;
7346 do {
7347 title = link.getAttribute("tiddlyLink");
7348 fields = link.getAttribute("tiddlyFields");
7349 noToggle = link.getAttribute("noToggle");
7350 link = link.parentNode;
7351 } while(title == null && link != null);
7352 if(!store.isShadowTiddler(title)) {
7353 var f = fields ? fields.decodeHashMap() : {};
7354 fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
7356 if(title) {
7357 var toggling = e.metaKey || e.ctrlKey;
7358 if(config.options.chkToggleLinks)
7359 toggling = !toggling;
7360 if(noToggle)
7361 toggling = false;
7362 if(store.getTiddler(title))
7363 fields = null;
7364 story.displayTiddler(target,title,null,true,null,fields,toggling);
7366 clearMessage();
7367 return false;
7370 // Create a button for a tag with a popup listing all the tiddlers that it tags
7371 function createTagButton(place,tag,excludeTiddler)
7373 var btn = createTiddlyButton(place,tag,config.views.wikified.tag.tooltip.format([tag]),onClickTag);
7374 btn.setAttribute("tag",tag);
7375 if(excludeTiddler)
7376 btn.setAttribute("tiddler",excludeTiddler);
7377 return btn;
7380 // Event handler for clicking on a tiddler tag
7381 function onClickTag(ev)
7383 var e = ev ? ev : window.event;
7384 var popup = Popup.create(this);
7385 var tag = this.getAttribute("tag");
7386 var title = this.getAttribute("tiddler");
7387 if(popup && tag) {
7388 var tagged = store.getTaggedTiddlers(tag);
7389 var titles = [];
7390 var li,r;
7391 for(r=0;r<tagged.length;r++) {
7392 if(tagged[r].title != title)
7393 titles.push(tagged[r].title);
7395 var lingo = config.views.wikified.tag;
7396 if(titles.length > 0) {
7397 var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
7398 openAll.setAttribute("tag",tag);
7399 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7400 for(r=0; r<titles.length; r++) {
7401 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
7403 } else {
7404 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
7406 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7407 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
7408 createTiddlyText(h,lingo.openTag.format([tag]));
7410 Popup.show();
7411 e.cancelBubble = true;
7412 if(e.stopPropagation) e.stopPropagation();
7413 return false;
7416 // Event handler for 'open all' on a tiddler popup
7417 function onClickTagOpenAll(ev)
7419 var e = ev ? ev : window.event;
7420 var tag = this.getAttribute("tag");
7421 var tagged = store.getTaggedTiddlers(tag);
7422 story.displayTiddlers(this,tagged);
7423 return false;
7426 function onClickError(ev)
7428 var e = ev ? ev : window.event;
7429 var popup = Popup.create(this);
7430 var lines = this.getAttribute("errorText").split("\n");
7431 for(var t=0; t<lines.length; t++)
7432 createTiddlyElement(popup,"li",null,null,lines[t]);
7433 Popup.show();
7434 e.cancelBubble = true;
7435 if(e.stopPropagation) e.stopPropagation();
7436 return false;
7439 function createTiddlyDropDown(place,onchange,options,defaultValue)
7441 var sel = createTiddlyElement(place,"select");
7442 sel.onchange = onchange;
7443 for(var t=0; t<options.length; t++) {
7444 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
7445 e.value = options[t].name;
7446 if(options[t].name == defaultValue)
7447 e.selected = true;
7449 return sel;
7452 function createTiddlyPopup(place,caption,tooltip,tiddler)
7454 if(tiddler.text) {
7455 createTiddlyLink(place,caption,true);
7456 var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
7457 btn.tiddler = tiddler;
7458 } else {
7459 createTiddlyText(place,caption);
7463 function onClickTiddlyPopup(ev)
7465 var e = ev ? ev : window.event;
7466 var tiddler = this.tiddler;
7467 if(tiddler.text) {
7468 var popup = Popup.create(this,"div","popupTiddler");
7469 wikify(tiddler.text,popup,null,tiddler);
7470 Popup.show();
7472 if(e) e.cancelBubble = true;
7473 if(e && e.stopPropagation) e.stopPropagation();
7474 return false;
7477 function createTiddlyError(place,title,text)
7479 var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
7480 if(text) btn.setAttribute("errorText",text);
7483 function merge(dst,src,preserveExisting)
7485 for(p in src) {
7486 if(!preserveExisting || dst[p] === undefined)
7487 dst[p] = src[p];
7489 return dst;
7492 // Returns a string containing the description of an exception, optionally prepended by a message
7493 function exceptionText(e,message)
7495 var s = e.description ? e.description : e.toString();
7496 return message ? "%0:\n%1".format([message,s]) : s;
7499 // Displays an alert of an exception description with optional message
7500 function showException(e,message)
7502 alert(exceptionText(e,message));
7505 function alertAndThrow(m)
7507 alert(m);
7508 throw(m);
7511 function glyph(name)
7513 var g = config.glyphs;
7514 var b = g.currBrowser;
7515 if(b == null) {
7516 b = 0;
7517 while(!g.browsers[b]() && b < g.browsers.length-1)
7518 b++;
7519 g.currBrowser = b;
7521 if(!g.codes[name])
7522 return "";
7523 return g.codes[name][b];
7528 //- Animation engine
7531 function Animator()
7533 this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
7534 this.timerID = 0; // ID of the timer used for animating
7535 this.animations = []; // List of animations in progress
7536 return this;
7539 // Start animation engine
7540 Animator.prototype.startAnimating = function() //# Variable number of arguments
7542 for(var t=0; t<arguments.length; t++)
7543 this.animations.push(arguments[t]);
7544 if(this.running == 0) {
7545 var me = this;
7546 this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
7548 this.running += arguments.length;
7551 // Perform an animation engine tick, calling each of the known animation modules
7552 Animator.prototype.doAnimate = function(me)
7554 var a = 0;
7555 while(a < me.animations.length) {
7556 var animation = me.animations[a];
7557 if(animation.tick()) {
7558 a++;
7559 } else {
7560 me.animations.splice(a,1);
7561 if(--me.running == 0)
7562 window.clearInterval(me.timerID);
7567 Animator.slowInSlowOut = function(progress)
7569 return(1-((Math.cos(progress * Math.PI)+1)/2));
7572 //--
7573 //-- Morpher animation
7574 //--
7576 // Animate a set of properties of an element
7577 function Morpher(element,duration,properties,callback)
7579 this.element = element;
7580 this.duration = duration;
7581 this.properties = properties;
7582 this.startTime = new Date();
7583 this.endTime = Number(this.startTime) + duration;
7584 this.callback = callback;
7585 this.tick();
7586 return this;
7589 Morpher.prototype.assignStyle = function(element,style,value)
7591 switch(style) {
7592 case "-tw-vertScroll":
7593 window.scrollTo(findScrollX(),value);
7594 break;
7595 case "-tw-horizScroll":
7596 window.scrollTo(value,findScrollY());
7597 break;
7598 default:
7599 element.style[style] = value;
7600 break;
7604 Morpher.prototype.stop = function()
7606 for(var t=0; t<this.properties.length; t++) {
7607 var p = this.properties[t];
7608 if(p.atEnd !== undefined) {
7609 this.assignStyle(this.element,p.style,p.atEnd);
7612 if(this.callback)
7613 this.callback(this.element,this.properties);
7616 Morpher.prototype.tick = function()
7618 var currTime = Number(new Date());
7619 progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
7620 for(var t=0; t<this.properties.length; t++) {
7621 var p = this.properties[t];
7622 if(p.start !== undefined && p.end !== undefined) {
7623 var template = p.template ? p.template : "%0";
7624 switch(p.format) {
7625 case undefined:
7626 case "style":
7627 var v = p.start + (p.end-p.start) * progress;
7628 this.assignStyle(this.element,p.style,template.format([v]));
7629 break;
7630 case "color":
7631 break;
7635 if(currTime >= this.endTime) {
7636 this.stop();
7637 return false;
7639 return true;
7642 //--
7643 //-- Zoomer animation
7644 //--
7646 function Zoomer(text,startElement,targetElement,unused)
7648 var e = createTiddlyElement(document.body,"div",null,"zoomer");
7649 createTiddlyElement(e,"div",null,null,text);
7650 var winWidth = findWindowWidth();
7651 var winHeight = findWindowHeight();
7652 var p = [
7653 {style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
7654 {style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
7655 {style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
7656 {style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
7657 {style: 'fontSize', start: 8, end: 24, template: '%0pt'}
7659 var c = function(element,properties) {removeNode(element);};
7660 return new Morpher(e,config.animDuration,p,c);
7663 //--
7664 //-- Scroller animation
7665 //--
7667 function Scroller(targetElement,unused)
7669 var p = [
7670 {style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
7672 return new Morpher(targetElement,config.animDuration,p);
7675 //--
7676 //-- Slider animation
7677 //--
7679 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
7680 function Slider(element,opening,unused,deleteMode)
7682 element.style.overflow = 'hidden';
7683 if(opening)
7684 element.style.height = '0px'; // Resolves a Firefox flashing bug
7685 element.style.display = 'block';
7686 var left = findPosX(element);
7687 var width = element.scrollWidth;
7688 var height = element.scrollHeight;
7689 var winWidth = findWindowWidth();
7690 var p = [];
7691 var c = null;
7692 if(opening) {
7693 p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
7694 p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
7695 p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
7696 } else {
7697 p.push({style: 'height', start: height, end: 0, template: '%0px'});
7698 p.push({style: 'display', atEnd: 'none'});
7699 p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
7700 p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
7701 switch(deleteMode) {
7702 case "all":
7703 c = function(element,properties) {removeNode(element);};
7704 break;
7705 case "children":
7706 c = function(element,properties) {removeChildren(element);};
7707 break;
7710 return new Morpher(element,config.animDuration,p,c);
7713 //--
7714 //-- Popup menu
7715 //--
7717 var Popup = {
7718 stack: [] // Array of objects with members root: and popup:
7721 Popup.create = function(root,elem,theClass)
7723 Popup.remove();
7724 var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
7725 Popup.stack.push({root: root, popup: popup});
7726 return popup;
7729 Popup.onDocumentClick = function(ev)
7731 var e = ev ? ev : window.event;
7732 if(e.eventPhase == undefined)
7733 Popup.remove();
7734 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
7735 Popup.remove();
7736 return true;
7739 Popup.show = function(unused1,unused2)
7741 var curr = Popup.stack[Popup.stack.length-1];
7742 this.place(curr.root,curr.popup);
7743 addClass(curr.root,"highlight");
7744 if(config.options.chkAnimate && anim && typeof Scroller == "function")
7745 anim.startAnimating(new Scroller(curr.popup));
7746 else
7747 window.scrollTo(0,ensureVisible(curr.popup));
7750 Popup.place = function(root,popup,offset)
7752 if(!offset) var offset = {x:0, y:0};
7753 var rootLeft = findPosX(root);
7754 var rootTop = findPosY(root);
7755 var rootHeight = root.offsetHeight;
7756 var popupLeft = rootLeft + offset.x;
7757 var popupTop = rootTop + rootHeight + offset.y;
7758 var winWidth = findWindowWidth();
7759 if(popup.offsetWidth > winWidth*0.75)
7760 popup.style.width = winWidth*0.75 + "px";
7761 var popupWidth = popup.offsetWidth;
7762 if(popupLeft + popupWidth > winWidth)
7763 popupLeft = winWidth - popupWidth;
7764 popup.style.left = popupLeft + "px";
7765 popup.style.top = popupTop + "px";
7766 popup.style.display = "block";
7769 Popup.remove = function()
7771 if(Popup.stack.length > 0) {
7772 Popup.removeFrom(0);
7776 Popup.removeFrom = function(from)
7778 for(var t=Popup.stack.length-1; t>=from; t--) {
7779 var p = Popup.stack[t];
7780 removeClass(p.root,"highlight");
7781 removeNode(p.popup);
7783 Popup.stack = Popup.stack.slice(0,from);
7786 //--
7787 //-- Wizard support
7788 //--
7790 function Wizard(elem)
7792 if(elem) {
7793 this.formElem = findRelated(elem,"wizard","className");
7794 this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
7795 this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
7796 } else {
7797 this.formElem = null;
7798 this.bodyElem = null;
7799 this.footElem = null;
7803 Wizard.prototype.setValue = function(name,value)
7805 if(this.formElem)
7806 this.formElem[name] = value;
7809 Wizard.prototype.getValue = function(name)
7811 return this.formElem ? this.formElem[name] : null;
7814 Wizard.prototype.createWizard = function(place,title)
7816 this.formElem = createTiddlyElement(place,"form",null,"wizard");
7817 createTiddlyElement(this.formElem,"h1",null,null,title);
7818 this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
7819 this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
7822 Wizard.prototype.clear = function()
7824 removeChildren(this.bodyElem);
7827 Wizard.prototype.setButtons = function(buttonInfo,status)
7829 removeChildren(this.footElem);
7830 for(var t=0; t<buttonInfo.length; t++) {
7831 createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
7832 insertSpacer(this.footElem);
7834 if(typeof status == "string") {
7835 createTiddlyElement(this.footElem,"span",null,"status",status);
7839 Wizard.prototype.addStep = function(stepTitle,html)
7841 removeChildren(this.bodyElem);
7842 var w = createTiddlyElement(this.bodyElem,"div");
7843 createTiddlyElement(w,"h2",null,null,stepTitle);
7844 var step = createTiddlyElement(w,"div",null,"wizardStep");
7845 step.innerHTML = html;
7846 applyHtmlMacros(step,tiddler);
7849 Wizard.prototype.getElement = function(name)
7851 return this.formElem.elements[name];
7854 //--
7855 //-- ListView gadget
7856 //--
7858 var ListView = {};
7860 // Create a listview
7861 ListView.create = function(place,listObject,listTemplate,callback,className)
7863 var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
7864 var thead = createTiddlyElement(table,"thead");
7865 var r = createTiddlyElement(thead,"tr");
7866 for(var t=0; t<listTemplate.columns.length; t++) {
7867 var columnTemplate = listTemplate.columns[t];
7868 var c = createTiddlyElement(r,"th");
7869 var colType = ListView.columnTypes[columnTemplate.type];
7870 if(colType && colType.createHeader)
7871 colType.createHeader(c,columnTemplate,t);
7873 var tbody = createTiddlyElement(table,"tbody");
7874 for(var rc=0; rc<listObject.length; rc++) {
7875 rowObject = listObject[rc];
7876 r = createTiddlyElement(tbody,"tr");
7877 for(c=0; c<listTemplate.rowClasses.length; c++) {
7878 if(rowObject[listTemplate.rowClasses[c].field])
7879 addClass(r,listTemplate.rowClasses[c].className);
7881 rowObject.rowElement = r;
7882 rowObject.colElements = {};
7883 for(var cc=0; cc<listTemplate.columns.length; cc++) {
7884 c = createTiddlyElement(r,"td");
7885 columnTemplate = listTemplate.columns[cc];
7886 var field = columnTemplate.field;
7887 colType = ListView.columnTypes[columnTemplate.type];
7888 if(colType && colType.createItem)
7889 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
7890 rowObject.colElements[field] = c;
7893 if(callback && listTemplate.actions)
7894 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
7895 if(callback && listTemplate.buttons) {
7896 for(t=0; t<listTemplate.buttons.length; t++) {
7897 var a = listTemplate.buttons[t];
7898 if(a && a.name != "")
7899 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
7902 return table;
7905 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
7907 return function(e) {
7908 var view = findRelated(this,"TABLE",null,"previousSibling");
7909 var tiddlers = [];
7910 ListView.forEachSelector(view,function(e,rowName) {
7911 if(e.checked)
7912 tiddlers.push(rowName);
7914 if(tiddlers.length == 0 && !allowEmptySelection) {
7915 alert(config.messages.nothingSelected);
7916 } else {
7917 if(this.nodeName.toLowerCase() == "select") {
7918 callback(view,this.value,tiddlers);
7919 this.selectedIndex = 0;
7920 } else {
7921 callback(view,name,tiddlers);
7927 // Invoke a callback for each selector checkbox in the listview
7928 ListView.forEachSelector = function(view,callback)
7930 var checkboxes = view.getElementsByTagName("input");
7931 var hadOne = false;
7932 for(var t=0; t<checkboxes.length; t++) {
7933 var cb = checkboxes[t];
7934 if(cb.getAttribute("type") == "checkbox") {
7935 var rn = cb.getAttribute("rowName");
7936 if(rn) {
7937 callback(cb,rn);
7938 hadOne = true;
7942 return hadOne;
7945 ListView.getSelectedRows = function(view)
7947 var rowNames = [];
7948 ListView.forEachSelector(view,function(e,rowName) {
7949 if(e.checked)
7950 rowNames.push(rowName);
7952 return rowNames;
7955 ListView.columnTypes = {};
7957 ListView.columnTypes.String = {
7958 createHeader: function(place,columnTemplate,col)
7960 createTiddlyText(place,columnTemplate.title);
7962 createItem: function(place,listObject,field,columnTemplate,col,row)
7964 var v = listObject[field];
7965 if(v != undefined)
7966 createTiddlyText(place,v);
7970 ListView.columnTypes.WikiText = {
7971 createHeader: ListView.columnTypes.String.createHeader,
7972 createItem: function(place,listObject,field,columnTemplate,col,row)
7974 var v = listObject[field];
7975 if(v != undefined)
7976 wikify(v,place,null,null);
7980 ListView.columnTypes.Tiddler = {
7981 createHeader: ListView.columnTypes.String.createHeader,
7982 createItem: function(place,listObject,field,columnTemplate,col,row)
7984 var v = listObject[field];
7985 if(v != undefined && v.title)
7986 createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
7990 ListView.columnTypes.Size = {
7991 createHeader: ListView.columnTypes.String.createHeader,
7992 createItem: function(place,listObject,field,columnTemplate,col,row)
7994 var v = listObject[field];
7995 if(v != undefined) {
7996 var t = 0;
7997 while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
7998 t++;
7999 createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
8004 ListView.columnTypes.Link = {
8005 createHeader: ListView.columnTypes.String.createHeader,
8006 createItem: function(place,listObject,field,columnTemplate,col,row)
8008 var v = listObject[field];
8009 var c = columnTemplate.text;
8010 if(v != undefined)
8011 createTiddlyText(createExternalLink(place,v),c ? c : v);
8015 ListView.columnTypes.Date = {
8016 createHeader: ListView.columnTypes.String.createHeader,
8017 createItem: function(place,listObject,field,columnTemplate,col,row)
8019 var v = listObject[field];
8020 if(v != undefined)
8021 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
8025 ListView.columnTypes.StringList = {
8026 createHeader: ListView.columnTypes.String.createHeader,
8027 createItem: function(place,listObject,field,columnTemplate,col,row)
8029 var v = listObject[field];
8030 if(v != undefined) {
8031 for(var t=0; t<v.length; t++) {
8032 createTiddlyText(place,v[t]);
8033 createTiddlyElement(place,"br");
8039 ListView.columnTypes.Selector = {
8040 createHeader: function(place,columnTemplate,col)
8042 createTiddlyCheckbox(place,null,false,this.onHeaderChange);
8044 createItem: function(place,listObject,field,columnTemplate,col,row)
8046 var e = createTiddlyCheckbox(place,null,listObject[field],null);
8047 e.setAttribute("rowName",listObject[columnTemplate.rowName]);
8049 onHeaderChange: function(e)
8051 var state = this.checked;
8052 var view = findRelated(this,"TABLE");
8053 if(!view)
8054 return;
8055 ListView.forEachSelector(view,function(e,rowName) {
8056 e.checked = state;
8061 ListView.columnTypes.Tags = {
8062 createHeader: ListView.columnTypes.String.createHeader,
8063 createItem: function(place,listObject,field,columnTemplate,col,row)
8065 var tags = listObject[field];
8066 createTiddlyText(place,String.encodeTiddlyLinkList(tags));
8070 ListView.columnTypes.Boolean = {
8071 createHeader: ListView.columnTypes.String.createHeader,
8072 createItem: function(place,listObject,field,columnTemplate,col,row)
8074 if(listObject[field] == true)
8075 createTiddlyText(place,columnTemplate.trueText);
8076 if(listObject[field] == false)
8077 createTiddlyText(place,columnTemplate.falseText);
8081 ListView.columnTypes.TagCheckbox = {
8082 createHeader: ListView.columnTypes.String.createHeader,
8083 createItem: function(place,listObject,field,columnTemplate,col,row)
8085 var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
8086 e.setAttribute("tiddler",listObject.title);
8087 e.setAttribute("tag",columnTemplate.tag);
8089 onChange : function(e)
8091 var tag = this.getAttribute("tag");
8092 var tiddler = this.getAttribute("tiddler");
8093 store.setTiddlerTag(tiddler,this.checked,tag);
8097 ListView.columnTypes.TiddlerLink = {
8098 createHeader: ListView.columnTypes.String.createHeader,
8099 createItem: function(place,listObject,field,columnTemplate,col,row)
8101 var v = listObject[field];
8102 if(v != undefined) {
8103 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
8104 createTiddlyText(link,listObject[field]);
8109 //--
8110 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8111 //--
8113 // Clamp a number to a range
8114 Number.prototype.clamp = function(min,max)
8116 var c = this;
8117 if(c < min)
8118 c = min;
8119 if(c > max)
8120 c = max;
8121 return c;
8124 // Add indexOf function if browser does not support it
8125 if(!Array.indexOf) {
8126 Array.prototype.indexOf = function(item,from)
8128 if(!from)
8129 from = 0;
8130 for(var i=from; i<this.length; i++) {
8131 if(this[i] === item)
8132 return i;
8134 return -1;
8137 // Find an entry in a given field of the members of an array
8138 Array.prototype.findByField = function(field,value)
8140 for(var t=0; t<this.length; t++) {
8141 if(this[t][field] == value)
8142 return t;
8144 return null;
8147 // Return whether an entry exists in an array
8148 Array.prototype.contains = function(item)
8150 return this.indexOf(item) != -1;
8153 // Adds, removes or toggles a particular value within an array
8154 // value - value to add
8155 // mode - +1 to add value, -1 to remove value, 0 to toggle it
8156 Array.prototype.setItem = function(value,mode)
8158 var p = this.indexOf(value);
8159 if(mode == 0)
8160 mode = (p == -1) ? +1 : -1;
8161 if(mode == +1) {
8162 if(p == -1)
8163 this.push(value);
8164 } else if(mode == -1) {
8165 if(p != -1)
8166 this.splice(p,1);
8170 // Return whether one of a list of values exists in an array
8171 Array.prototype.containsAny = function(items)
8173 for(var i=0; i<items.length; i++) {
8174 if (this.indexOf(items[i]) != -1)
8175 return true;
8177 return false;
8180 // Return whether all of a list of values exists in an array
8181 Array.prototype.containsAll = function(items)
8183 for (var i = 0; i<items.length; i++) {
8184 if (this.indexOf(items[i]) == -1)
8185 return false;
8187 return true;
8190 // 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
8191 Array.prototype.pushUnique = function(item,unique)
8193 if(unique === false) {
8194 this.push(item);
8195 } else {
8196 if(this.indexOf(item) == -1)
8197 this.push(item);
8201 Array.prototype.remove = function(item)
8203 var p = this.indexOf(item);
8204 if(p != -1)
8205 this.splice(p,1);
8208 if(!Array.prototype.map){
8209 Array.prototype.map = function(fn, thisObj) {
8210 var scope = thisObj || window;
8211 var a = [];
8212 for ( var i=0, j=this.length; i < j; ++i ) {
8213 a.push(fn.call(scope, this[i], i, this));
8215 return a;
8216 };}// Get characters from the right end of a string
8217 String.prototype.right = function(n)
8219 return n < this.length ? this.slice(this.length-n) : this;
8222 // Trim whitespace from both ends of a string
8223 String.prototype.trim = function()
8225 return this.replace(/^\s*|\s*$/g,"");
8228 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
8229 String.prototype.unDash = function()
8231 var s = this.split("-");
8232 if(s.length > 1) {
8233 for(var t=1; t<s.length; t++)
8234 s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
8236 return s.join("");
8239 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
8240 String.prototype.format = function(substrings)
8242 var subRegExp = /(?:%(\d+))/mg;
8243 var currPos = 0;
8244 var r = [];
8245 do {
8246 var match = subRegExp.exec(this);
8247 if(match && match[1]) {
8248 if(match.index > currPos)
8249 r.push(this.substring(currPos,match.index));
8250 r.push(substrings[parseInt(match[1])]);
8251 currPos = subRegExp.lastIndex;
8253 } while(match);
8254 if(currPos < this.length)
8255 r.push(this.substring(currPos,this.length));
8256 return r.join("");
8259 // Escape any special RegExp characters with that character preceded by a backslash
8260 String.prototype.escapeRegExp = function()
8262 var s = "\\^$*+?()=!|,{}[].";
8263 var c = this;
8264 for(var t=0; t<s.length; t++)
8265 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
8266 return c;
8269 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
8270 String.prototype.escapeLineBreaks = function()
8272 return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
8275 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
8276 String.prototype.unescapeLineBreaks = function()
8278 return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
8281 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
8282 String.prototype.htmlEncode = function()
8284 return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
8287 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
8288 String.prototype.htmlDecode = function()
8290 return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
8293 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
8294 String.prototype.toJSONString = function()
8296 var m = {
8297 '\b': '\\b',
8298 '\f': '\\f',
8299 '\n': '\\n',
8300 '\r': '\\r',
8301 '\t': '\\t',
8302 '"' : '\\"',
8303 '\\': '\\\\'
8305 var replaceFn = function(a,b) {
8306 var c = m[b];
8307 if(c)
8308 return c;
8309 c = b.charCodeAt();
8310 return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
8312 if(/["\\\x00-\x1f]/.test(this))
8313 return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
8314 return '"' + this + '"';
8317 // Parse a space-separated string of name:value parameters
8318 // The result is an array of objects:
8319 // result[0] = object with a member for each parameter name, value of that member being an array of values
8320 // result[1..n] = one object for each parameter, with 'name' and 'value' members
8321 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
8323 var parseToken = function(match,p) {
8324 var n;
8325 if(match[p]) // Double quoted
8326 n = match[p];
8327 else if(match[p+1]) // Single quoted
8328 n = match[p+1];
8329 else if(match[p+2]) // Double-square-bracket quoted
8330 n = match[p+2];
8331 else if(match[p+3]) // Double-brace quoted
8332 try {
8333 n = match[p+3];
8334 if(allowEval)
8335 n = window.eval(n);
8336 } catch(ex) {
8337 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
8339 else if(match[p+4]) // Unquoted
8340 n = match[p+4];
8341 else if(match[p+5]) // empty quote
8342 n = "";
8343 return n;
8345 var r = [{}];
8346 var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
8347 var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
8348 var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
8349 var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
8350 var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
8351 var emptyQuote = "((?:\"\")|(?:''))";
8352 var skipSpace = "(?:\\s*)";
8353 var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
8354 var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
8355 var params = [];
8356 do {
8357 var match = re.exec(this);
8358 if(match) {
8359 var n = parseToken(match,1);
8360 if(noNames) {
8361 r.push({name:"",value:n});
8362 } else {
8363 var v = parseToken(match,8);
8364 if(v == null && defaultName) {
8365 v = n;
8366 n = defaultName;
8367 } else if(v == null && defaultValue) {
8368 v = defaultValue;
8370 r.push({name:n,value:v});
8371 if(cascadeDefaults) {
8372 defaultName = n;
8373 defaultValue = v;
8377 } while(match);
8378 // Summarise parameters into first element
8379 for(var t=1; t<r.length; t++) {
8380 if(r[0][r[t].name])
8381 r[0][r[t].name].push(r[t].value);
8382 else
8383 r[0][r[t].name] = [r[t].value];
8385 return r;
8388 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
8389 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
8390 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
8391 String.prototype.readMacroParams = function()
8393 var p = this.parseParams("list",null,true,true);
8394 var n = [];
8395 for(var t=1; t<p.length; t++)
8396 n.push(p[t].value);
8397 return n;
8400 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
8401 String.prototype.readBracketedList = function(unique)
8403 var p = this.parseParams("list",null,false,true);
8404 var n = [];
8405 for(var t=1; t<p.length; t++) {
8406 if(p[t].value)
8407 n.pushUnique(p[t].value,unique);
8409 return n;
8412 // Returns array with start and end index of chunk between given start and end marker, or undefined.
8413 String.prototype.getChunkRange = function(start,end)
8415 var s = this.indexOf(start);
8416 if(s != -1) {
8417 s += start.length;
8418 var e = this.indexOf(end,s);
8419 if(e != -1)
8420 return [s,e];
8424 // Replace a chunk of a string given start and end markers
8425 String.prototype.replaceChunk = function(start,end,sub)
8427 var r = this.getChunkRange(start,end);
8428 return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
8431 // Returns a chunk of a string between start and end markers, or undefined
8432 String.prototype.getChunk = function(start,end)
8434 var r = this.getChunkRange(start,end);
8435 if(r)
8436 return this.substring(r[0],r[1]);
8440 // Static method to bracket a string with double square brackets if it contains a space
8441 String.encodeTiddlyLink = function(title)
8443 return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
8446 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
8447 String.encodeTiddlyLinkList = function(list)
8449 if(list) {
8450 var results = [];
8451 for(var t=0; t<list.length; t++)
8452 results.push(String.encodeTiddlyLink(list[t]));
8453 return results.join(" ");
8454 } else {
8455 return "";
8459 // Convert a string as a sequence of name:"value" pairs into a hashmap
8460 String.prototype.decodeHashMap = function()
8462 var fields = this.parseParams("anon","",false);
8463 var r = {};
8464 for(var t=1; t<fields.length; t++)
8465 r[fields[t].name] = fields[t].value;
8466 return r;
8469 // Static method to encode a hashmap into a name:"value"... string
8470 String.encodeHashMap = function(hashmap)
8472 var r = [];
8473 for(var t in hashmap)
8474 r.push(t + ':"' + hashmap[t] + '"');
8475 return r.join(" ");
8478 // Static method to left-pad a string with 0s to a certain width
8479 String.zeroPad = function(n,d)
8481 var s = n.toString();
8482 if(s.length < d)
8483 s = "000000000000000000000000000".substr(0,d-s.length) + s;
8484 return s;
8487 String.prototype.startsWith = function(prefix)
8489 return !prefix || this.substring(0,prefix.length) == prefix;
8492 // Returns the first value of the given named parameter.
8493 function getParam(params,name,defaultValue)
8495 if(!params)
8496 return defaultValue;
8497 var p = params[0][name];
8498 return p ? p[0] : defaultValue;
8501 // Returns the first value of the given boolean named parameter.
8502 function getFlag(params,name,defaultValue)
8504 return !!getParam(params,name,defaultValue);
8507 // Substitute date components into a string
8508 Date.prototype.formatString = function(template)
8510 var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
8511 t = t.replace(/hh12/g,this.getHours12());
8512 t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
8513 t = t.replace(/hh/g,this.getHours());
8514 t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
8515 t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
8516 t = t.replace(/mm/g,this.getMinutes());
8517 t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
8518 t = t.replace(/ss/g,this.getSeconds());
8519 t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
8520 t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
8521 t = t.replace(/wYYYY/g,this.getYearForWeekNo());
8522 t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
8523 t = t.replace(/YYYY/g,this.getFullYear());
8524 t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
8525 t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
8526 t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
8527 t = t.replace(/MM/g,this.getMonth()+1);
8528 t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
8529 t = t.replace(/WW/g,this.getWeek());
8530 t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
8531 t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
8532 t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
8533 t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
8534 t = t.replace(/DD/g,this.getDate());
8535 return t;
8538 Date.prototype.getWeek = function()
8540 var dt = new Date(this.getTime());
8541 var d = dt.getDay();
8542 if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8543 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
8544 var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
8545 return Math.floor(n/7)+1;
8548 Date.prototype.getYearForWeekNo = function()
8550 var dt = new Date(this.getTime());
8551 var d = dt.getDay();
8552 if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8553 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
8554 return dt.getFullYear();
8557 Date.prototype.getHours12 = function()
8559 var h = this.getHours();
8560 return h > 12 ? h-12 : ( h > 0 ? h : 12 );
8563 Date.prototype.getAmPm = function()
8565 return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
8568 Date.prototype.daySuffix = function()
8570 return config.messages.dates.daySuffixes[this.getDate()-1];
8573 // Convert a date to local YYYYMMDDHHMM string format
8574 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
8576 return String.zeroPad(this.getFullYear(),4) + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
8579 // Convert a date to UTC YYYYMMDDHHMM string format
8580 Date.prototype.convertToYYYYMMDDHHMM = function()
8582 return String.zeroPad(this.getUTCFullYear(),4) + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
8585 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
8586 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
8588 return String.zeroPad(this.getUTCFullYear(),4) + 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);
8591 // Static method to create a date from a UTC YYYYMMDDHHMM format string
8592 Date.convertFromYYYYMMDDHHMM = function(d)
8594 return new Date(Date.UTC(parseInt(d.substr(0,4),10),
8595 parseInt(d.substr(4,2),10)-1,
8596 parseInt(d.substr(6,2),10),
8597 parseInt(d.substr(8,2),10),
8598 parseInt(d.substr(10,2),10),0,0));
8601 //--
8602 //-- RGB colour object
8603 //--
8605 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
8606 function RGB(r,g,b)
8608 this.r = 0;
8609 this.g = 0;
8610 this.b = 0;
8611 if(typeof r == "string") {
8612 if(r.substr(0,1) == "#") {
8613 if(r.length == 7) {
8614 this.r = parseInt(r.substr(1,2),16)/255;
8615 this.g = parseInt(r.substr(3,2),16)/255;
8616 this.b = parseInt(r.substr(5,2),16)/255;
8617 } else {
8618 this.r = parseInt(r.substr(1,1),16)/15;
8619 this.g = parseInt(r.substr(2,1),16)/15;
8620 this.b = parseInt(r.substr(3,1),16)/15;
8622 } else {
8623 var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
8624 var c = r.match(rgbPattern);
8625 if(c) {
8626 this.r = parseInt(c[1],10)/255;
8627 this.g = parseInt(c[2],10)/255;
8628 this.b = parseInt(c[3],10)/255;
8631 } else {
8632 this.r = r;
8633 this.g = g;
8634 this.b = b;
8636 return this;
8639 // Mixes this colour with another in a specified proportion
8640 // c = other colour to mix
8641 // f = 0..1 where 0 is this colour and 1 is the new colour
8642 // Returns an RGB object
8643 RGB.prototype.mix = function(c,f)
8645 return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
8648 // Return an rgb colour as a #rrggbb format hex string
8649 RGB.prototype.toString = function()
8651 return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
8652 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
8653 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
8656 //--
8657 //-- DOM utilities - many derived from www.quirksmode.org
8658 //--
8660 function drawGradient(place,horiz,colours)
8662 for(var t=0; t<= 100; t+=2) {
8663 var bar = document.createElement("div");
8664 place.appendChild(bar);
8665 bar.style.position = "absolute";
8666 bar.style.left = horiz ? t + "%" : 0;
8667 bar.style.top = horiz ? 0 : t + "%";
8668 bar.style.width = horiz ? (101-t) + "%" : "100%";
8669 bar.style.height = horiz ? "100%" : (101-t) + "%";
8670 bar.style.zIndex = -1;
8671 var f = t/100;
8672 var p = f*(colours.length-1);
8673 bar.style.backgroundColor = colours[Math.floor(p)].mix(colours[Math.ceil(p)],p-Math.floor(p)).toString();
8677 function createTiddlyText(theParent,theText)
8679 return theParent.appendChild(document.createTextNode(theText));
8682 function createTiddlyCheckbox(theParent,caption,checked,onChange)
8684 var cb = document.createElement("input");
8685 cb.setAttribute("type","checkbox");
8686 cb.onclick = onChange;
8687 theParent.appendChild(cb);
8688 cb.checked = checked;
8689 cb.className = "chkOptionInput";
8690 if(caption)
8691 wikify(caption,theParent);
8692 return cb;
8695 function createTiddlyElement(theParent,theElement,theID,theClass,theText,attribs)
8697 var e = document.createElement(theElement);
8698 if(theClass != null)
8699 e.className = theClass;
8700 if(theID != null)
8701 e.setAttribute("id",theID);
8702 if(theText != null)
8703 e.appendChild(document.createTextNode(theText));
8704 if(attribs){
8705 for(var n in attribs){
8706 e.setAttribute(n,attribs[n]);
8709 if(theParent != null)
8710 theParent.appendChild(e);
8711 return e;
8714 function addEvent(obj,type,fn)
8716 if(obj.attachEvent) {
8717 obj['e'+type+fn] = fn;
8718 obj[type+fn] = function(){obj['e'+type+fn](window.event);};
8719 obj.attachEvent('on'+type,obj[type+fn]);
8720 } else {
8721 obj.addEventListener(type,fn,false);
8725 function removeEvent(obj,type,fn)
8727 if(obj.detachEvent) {
8728 obj.detachEvent('on'+type,obj[type+fn]);
8729 obj[type+fn] = null;
8730 } else {
8731 obj.removeEventListener(type,fn,false);
8735 function addClass(e,theClass)
8737 var currClass = e.className.split(" ");
8738 if(currClass.indexOf(theClass) == -1)
8739 e.className += " " + theClass;
8742 function removeClass(e,theClass)
8744 var currClass = e.className.split(" ");
8745 var i = currClass.indexOf(theClass);
8746 while(i != -1) {
8747 currClass.splice(i,1);
8748 i = currClass.indexOf(theClass);
8750 e.className = currClass.join(" ");
8753 function hasClass(e,theClass)
8755 if(e.className) {
8756 if(e.className.split(" ").indexOf(theClass) != -1)
8757 return true;
8759 return false;
8762 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
8763 function findRelated(e,value,name,relative)
8765 name = name ? name : "tagName";
8766 relative = relative ? relative : "parentNode";
8767 if(name == "className") {
8768 while(e && !hasClass(e,value)) {
8769 e = e[relative];
8771 } else {
8772 while(e && e[name] != value) {
8773 e = e[relative];
8776 return e;
8779 // Resolve the target object of an event
8780 function resolveTarget(e)
8782 var obj;
8783 if(e.target)
8784 obj = e.target;
8785 else if(e.srcElement)
8786 obj = e.srcElement;
8787 if(obj.nodeType == 3) // defeat Safari bug
8788 obj = obj.parentNode;
8789 return obj;
8792 // Prevent an event from bubbling
8793 function stopEvent(e){
8794 var ev = e? e : window.event;
8795 ev.cancelBubble = true;
8796 if (ev.stopPropagation) ev.stopPropagation();
8797 return false;
8800 // Return the content of an element as plain text with no formatting
8801 function getPlainText(e)
8803 var text = "";
8804 if(e.innerText)
8805 text = e.innerText;
8806 else if(e.textContent)
8807 text = e.textContent;
8808 return text;
8811 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
8812 function ensureVisible(e)
8814 var posTop = findPosY(e);
8815 var posBot = posTop + e.offsetHeight;
8816 var winTop = findScrollY();
8817 var winHeight = findWindowHeight();
8818 var winBot = winTop + winHeight;
8819 if(posTop < winTop) {
8820 return posTop;
8821 } else if(posBot > winBot) {
8822 if(e.offsetHeight < winHeight)
8823 return posTop - (winHeight - e.offsetHeight);
8824 else
8825 return posTop;
8826 } else {
8827 return winTop;
8831 // Get the current width of the display window
8832 function findWindowWidth()
8834 return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
8837 // Get the current height of the display window
8838 function findWindowHeight()
8840 return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
8843 // Get the current horizontal page scroll position
8844 function findScrollX()
8846 return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
8849 // Get the current vertical page scroll position
8850 function findScrollY()
8852 return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
8855 function findPosX(obj)
8857 var curleft = 0;
8858 while(obj.offsetParent) {
8859 curleft += obj.offsetLeft;
8860 obj = obj.offsetParent;
8862 return curleft;
8865 function findPosY(obj)
8867 var curtop = 0;
8868 while(obj.offsetParent) {
8869 curtop += obj.offsetTop;
8870 obj = obj.offsetParent;
8872 return curtop;
8875 // Blur a particular element
8876 function blurElement(e)
8878 if(e != null && e.focus && e.blur) {
8879 e.focus();
8880 e.blur();
8884 // Create a non-breaking space
8885 function insertSpacer(place)
8887 var e = document.createTextNode(String.fromCharCode(160));
8888 if(place)
8889 place.appendChild(e);
8890 return e;
8893 // Remove all children of a node
8894 function removeChildren(e)
8896 while(e && e.hasChildNodes())
8897 removeNode(e.firstChild);
8900 // Remove a node and all it's children
8901 function removeNode(e)
8903 scrubNode(e);
8904 e.parentNode.removeChild(e);
8907 // Remove any event handlers or non-primitve custom attributes
8908 function scrubNode(e)
8910 if(!config.browser.isIE)
8911 return;
8912 var att = e.attributes;
8913 if(att) {
8914 for(var t=0; t<att.length; t++) {
8915 var n = att[t].name;
8916 if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
8917 try {
8918 e[n] = null;
8919 } catch(ex) {
8924 var c = e.firstChild;
8925 while(c) {
8926 scrubNode(c);
8927 c = c.nextSibling;
8931 // Add a stylesheet, replacing any previous custom stylesheet
8932 function setStylesheet(s,id,doc)
8934 if(!id)
8935 id = "customStyleSheet";
8936 if(!doc)
8937 doc = document;
8938 var n = doc.getElementById(id);
8939 if(doc.createStyleSheet) {
8940 // Test for IE's non-standard createStyleSheet method
8941 if(n)
8942 n.parentNode.removeChild(n);
8943 // This failed without the &nbsp;
8944 doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
8945 } else {
8946 if(n) {
8947 n.replaceChild(doc.createTextNode(s),n.firstChild);
8948 } else {
8949 n = doc.createElement("style");
8950 n.type = "text/css";
8951 n.id = id;
8952 n.appendChild(doc.createTextNode(s));
8953 doc.getElementsByTagName("head")[0].appendChild(n);
8958 function removeStyleSheet(id)
8960 var e = document.getElementById(id);
8961 if(e)
8962 e.parentNode.removeChild(e);
8965 // Force the browser to do a document reflow when needed to workaround browser bugs
8966 function forceReflow()
8968 if(config.browser.isGecko) {
8969 setStylesheet("body {top:-1em;margin-top:1em;}");
8970 setStylesheet("");
8974 // Replace the current selection of a textarea or text input and scroll it into view
8975 function replaceSelection(e,text)
8977 if(e.setSelectionRange) {
8978 var oldpos = e.selectionStart;
8979 var isRange = e.selectionEnd > e.selectionStart;
8980 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
8981 e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
8982 var linecount = e.value.split('\n').length;
8983 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
8984 e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
8985 } else if(document.selection) {
8986 var range = document.selection.createRange();
8987 if(range.parentElement() == e) {
8988 var isCollapsed = range.text == "";
8989 range.text = text;
8990 if(!isCollapsed) {
8991 range.moveStart('character', -text.length);
8992 range.select();
8998 // Returns the text of the given (text) node, possibly merging subsequent text nodes
8999 function getNodeText(e)
9001 var t = "";
9002 while(e && e.nodeName == "#text") {
9003 t += e.nodeValue;
9004 e = e.nextSibling;
9006 return t;
9009 //--
9010 //-- LoaderBase and SaverBase
9011 //--
9013 function LoaderBase() {}
9015 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9017 var title = this.getTitle(store,node);
9018 if(title) {
9019 var tiddler = store.createTiddler(title);
9020 this.internalizeTiddler(store,tiddler,title,node);
9021 tiddlers.push(tiddler);
9025 LoaderBase.prototype.loadTiddlers = function(store,nodes)
9027 var tiddlers = [];
9028 for(var t = 0; t < nodes.length; t++) {
9029 try {
9030 this.loadTiddler(store,nodes[t],tiddlers);
9031 } catch(ex) {
9032 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9035 return tiddlers;
9038 function SaverBase() {}
9040 SaverBase.prototype.externalize = function(store)
9042 var results = [];
9043 var tiddlers = store.getTiddlers("title");
9044 for(var t = 0; t < tiddlers.length; t++)
9045 results.push(this.externalizeTiddler(store,tiddlers[t]));
9046 return results.join("\n");
9049 //--
9050 //-- TW21Loader (inherits from LoaderBase)
9051 //--
9053 function TW21Loader() {}
9055 TW21Loader.prototype = new LoaderBase();
9057 TW21Loader.prototype.getTitle = function(store,node)
9059 var title = null;
9060 if(node.getAttribute) {
9061 title = node.getAttribute("title");
9062 if(!title)
9063 title = node.getAttribute("tiddler");
9065 if(!title && node.id) {
9066 var lenPrefix = store.idPrefix.length;
9067 if (node.id.substr(0,lenPrefix) == store.idPrefix)
9068 title = node.id.substr(lenPrefix);
9070 return title;
9073 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9075 var e = node.firstChild;
9076 var text = null;
9077 if(node.getAttribute("tiddler")) {
9078 text = getNodeText(e).unescapeLineBreaks();
9079 } else {
9080 while(e.nodeName!="PRE" && e.nodeName!="pre") {
9081 e = e.nextSibling;
9083 text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
9085 var modifier = node.getAttribute("modifier");
9086 var c = node.getAttribute("created");
9087 var m = node.getAttribute("modified");
9088 var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
9089 var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
9090 var tags = node.getAttribute("tags");
9091 var fields = {};
9092 var attrs = node.attributes;
9093 for(var i = attrs.length-1; i >= 0; i--) {
9094 var name = attrs[i].name;
9095 if (attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
9096 fields[name] = attrs[i].value.unescapeLineBreaks();
9099 tiddler.assign(title,text,modifier,modified,tags,created,fields);
9100 return tiddler;
9103 //--
9104 //-- TW21Saver (inherits from SaverBase)
9105 //--
9107 function TW21Saver() {}
9109 TW21Saver.prototype = new SaverBase();
9111 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
9113 try {
9114 var extendedAttributes = "";
9115 var usePre = config.options.chkUsePreForStorage;
9116 store.forEachField(tiddler,
9117 function(tiddler,fieldName,value) {
9118 // don't store stuff from the temp namespace
9119 if(typeof value != "string")
9120 value = "";
9121 if (!fieldName.match(/^temp\./))
9122 extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
9123 },true);
9124 var created = tiddler.created.convertToYYYYMMDDHHMM();
9125 var modified = tiddler.modified.convertToYYYYMMDDHHMM();
9126 var vdate = version.date.convertToYYYYMMDDHHMM();
9127 var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
9128 attributes += (usePre && modified == created) ? "" : ' modified="' + modified +'"';
9129 attributes += (usePre && created == vdate) ? "" :' created="' + created + '"';
9130 var tags = tiddler.getTags();
9131 if(!usePre || tags)
9132 attributes += ' tags="' + tags.htmlEncode() + '"';
9133 return ('<div %0="%1"%2%3>%4</'+'div>').format([
9134 usePre ? "title" : "tiddler",
9135 tiddler.title.htmlEncode(),
9136 attributes,
9137 extendedAttributes,
9138 usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9140 } catch (ex) {
9141 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9145 //--
9146 //-- End of scripts
9147 //--
9148 //]]>
9149 </script>
9150 <script type="text/javascript">
9151 //<![CDATA[
9152 if(useJavaSaver)
9153 document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9154 //]]>
9155 </script>
9156 <!--POST-SCRIPT-START-->
9158 <!--POST-SCRIPT-END-->
9159 </body>
9160 </html>