better 'About' in wiki
[openlease.git] / htdocs / wiki.html
blob718127f5ac1cdff4c4a099a9cad8a46991457c51
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="About" modifier="Jason McVetta" modified="200803120322" created="200803120154" changecount="11">
502 <pre>Open Lease is [[open source|Licensing]] rental property management software. It aims to automate many of the mundane tasks of managing rental properties, and streamline operations by providing a tenant self-service portal. It is based on the [[Django|http://djangoproject.com]] web application framework, and is written in [[Python|http://python.org]].
503 </pre>
504 </div>
505 <div title="Boilerplate Leases" modifier="Jason McVetta" modified="200803120255" created="200803112239" changecount="6">
506 <pre>* Need to be written on a per-state, or per-city, basis?
507 * What kind of ~CreativeCommons license is appropriate?</pre>
508 </div>
509 <div title="Business Model" modifier="Jason McVetta" modified="200803120210" created="200803112321" changecount="5">
510 <pre>I am developing Open Lease with thought to my own profit. The application will be open source, [[licensed under the AGPLv3|Licensing]]. My nascent company, for now just '[[Open Lease Company]]', will earn (handsome, I suspect) returns by offering the Open Lease software as a hosted service. The [[AGPL|Licensing]] allows open and free code distribution, while retaining 'first-mover' competitive advantage for the author. We're hitching a ride on the [[Cluetrain|http://cluetrain.com]] -- let's see where it goes!</pre>
511 </div>
512 <div title="CryptoFunctionsPlugin" created="200710131134" tags="systemConfig excludeLists excludeSearch">
513 <pre>/***
514 |''Name:''|CryptoFunctionsPlugin|
515 |''Description:''|Support for cryptographic functions|
516 ***/
517 //{{{
518 if(!version.extensions.CryptoFunctionsPlugin) {
519 version.extensions.CryptoFunctionsPlugin = {installed:true};
521 //--
522 //-- Crypto functions and associated conversion routines
523 //--
525 // Crypto &quot;namespace&quot;
526 function Crypto() {}
528 // Convert a string to an array of big-endian 32-bit words
529 Crypto.strToBe32s = function(str)
531 var be = Array();
532 var len = Math.floor(str.length/4);
533 var i, j;
534 for(i=0, j=0; i&lt;len; i++, j+=4) {
535 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);
537 while (j&lt;str.length) {
538 be[j&gt;&gt;2] |= (str.charCodeAt(j)&amp;0xff)&lt;&lt;(24-(j*8)%32);
539 j++;
541 return be;
544 // Convert an array of big-endian 32-bit words to a string
545 Crypto.be32sToStr = function(be)
547 var str = &quot;&quot;;
548 for(var i=0;i&lt;be.length*32;i+=8)
549 str += String.fromCharCode((be[i&gt;&gt;5]&gt;&gt;&gt;(24-i%32)) &amp; 0xff);
550 return str;
553 // Convert an array of big-endian 32-bit words to a hex string
554 Crypto.be32sToHex = function(be)
556 var hex = &quot;0123456789ABCDEF&quot;;
557 var str = &quot;&quot;;
558 for(var i=0;i&lt;be.length*4;i++)
559 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);
560 return str;
563 // Return, in hex, the SHA-1 hash of a string
564 Crypto.hexSha1Str = function(str)
566 return Crypto.be32sToHex(Crypto.sha1Str(str));
569 // Return the SHA-1 hash of a string
570 Crypto.sha1Str = function(str)
572 return Crypto.sha1(Crypto.strToBe32s(str),str.length);
575 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
576 Crypto.sha1 = function(x,blen)
578 // Add 32-bit integers, wrapping at 32 bits
579 add32 = function(a,b)
581 var lsw = (a&amp;0xFFFF)+(b&amp;0xFFFF);
582 var msw = (a&gt;&gt;16)+(b&gt;&gt;16)+(lsw&gt;&gt;16);
583 return (msw&lt;&lt;16)|(lsw&amp;0xFFFF);
585 // Add five 32-bit integers, wrapping at 32 bits
586 add32x5 = function(a,b,c,d,e)
588 var lsw = (a&amp;0xFFFF)+(b&amp;0xFFFF)+(c&amp;0xFFFF)+(d&amp;0xFFFF)+(e&amp;0xFFFF);
589 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);
590 return (msw&lt;&lt;16)|(lsw&amp;0xFFFF);
592 // Bitwise rotate left a 32-bit integer by 1 bit
593 rol32 = function(n)
595 return (n&gt;&gt;&gt;31)|(n&lt;&lt;1);
598 var len = blen*8;
599 // Append padding so length in bits is 448 mod 512
600 x[len&gt;&gt;5] |= 0x80 &lt;&lt; (24-len%32);
601 // Append length
602 x[((len+64&gt;&gt;9)&lt;&lt;4)+15] = len;
603 var w = Array(80);
605 var k1 = 0x5A827999;
606 var k2 = 0x6ED9EBA1;
607 var k3 = 0x8F1BBCDC;
608 var k4 = 0xCA62C1D6;
610 var h0 = 0x67452301;
611 var h1 = 0xEFCDAB89;
612 var h2 = 0x98BADCFE;
613 var h3 = 0x10325476;
614 var h4 = 0xC3D2E1F0;
616 for(var i=0;i&lt;x.length;i+=16) {
617 var j,t;
618 var a = h0;
619 var b = h1;
620 var c = h2;
621 var d = h3;
622 var e = h4;
623 for(j = 0;j&lt;16;j++) {
624 w[j] = x[i+j];
625 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),d^(b&amp;(c^d)),w[j],k1);
626 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
628 for(j=16;j&lt;20;j++) {
629 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
630 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),d^(b&amp;(c^d)),w[j],k1);
631 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
633 for(j=20;j&lt;40;j++) {
634 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
635 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),b^c^d,w[j],k2);
636 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
638 for(j=40;j&lt;60;j++) {
639 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
640 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),(b&amp;c)|(d&amp;(b|c)),w[j],k3);
641 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
643 for(j=60;j&lt;80;j++) {
644 w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
645 t = add32x5(e,(a&gt;&gt;&gt;27)|(a&lt;&lt;5),b^c^d,w[j],k4);
646 e=d; d=c; c=(b&gt;&gt;&gt;2)|(b&lt;&lt;30); b=a; a = t;
649 h0 = add32(h0,a);
650 h1 = add32(h1,b);
651 h2 = add32(h2,c);
652 h3 = add32(h3,d);
653 h4 = add32(h4,e);
655 return Array(h0,h1,h2,h3,h4);
660 //}}}</pre>
661 </div>
662 <div title="DefaultTiddlers" modifier="Jason McVetta" modified="200803120251" created="200803112341" changecount="3">
663 <pre>[[About]]
664 [[Features]]
665 [[Development]]</pre>
666 </div>
667 <div title="DeprecatedFunctionsPlugin" created="200710140259" tags="systemConfig excludeLists excludeSearch">
668 <pre>/***
669 |''Name:''|DeprecatedFunctionsPlugin|
670 |''Description:''|Support for deprecated functions removed from core|
671 ***/
672 //{{{
673 if(!version.extensions.DeprecatedFunctionsPlugin) {
674 version.extensions.DeprecatedFunctionsPlugin = {installed:true};
676 //--
677 //-- Deprecated code
678 //--
680 // @Deprecated: Use createElementAndWikify and this.termRegExp instead
681 config.formatterHelpers.charFormatHelper = function(w)
683 w.subWikify(createTiddlyElement(w.output,this.element),this.terminator);
686 // @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
687 config.formatterHelpers.monospacedByLineHelper = function(w)
689 var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
690 lookaheadRegExp.lastIndex = w.matchStart;
691 var lookaheadMatch = lookaheadRegExp.exec(w.source);
692 if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
693 var text = lookaheadMatch[1];
694 if(config.browser.isIE)
695 text = text.replace(/\n/g,&quot;\r&quot;);
696 createTiddlyElement(w.output,&quot;pre&quot;,null,null,text);
697 w.nextMatch = lookaheadRegExp.lastIndex;
701 // @Deprecated: Use &lt;br&gt; or &lt;br /&gt; instead of &lt;&lt;br&gt;&gt;
702 config.macros.br = {};
703 config.macros.br.handler = function(place)
705 createTiddlyElement(place,&quot;br&quot;);
708 // Find an entry in an array. Returns the array index or null
709 // @Deprecated: Use indexOf instead
710 Array.prototype.find = function(item)
712 var i = this.indexOf(item);
713 return i == -1 ? null : i;
716 // Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
717 // @Deprecated: Use store.getLoader().internalizeTiddler instead
718 Tiddler.prototype.loadFromDiv = function(divRef,title)
720 return store.getLoader().internalizeTiddler(store,this,title,divRef);
723 // Format the text for storage in an HTML DIV
724 // @Deprecated Use store.getSaver().externalizeTiddler instead.
725 Tiddler.prototype.saveToDiv = function()
727 return store.getSaver().externalizeTiddler(store,this);
730 // @Deprecated: Use store.allTiddlersAsHtml() instead
731 function allTiddlersAsHtml()
733 return store.allTiddlersAsHtml();
736 // @Deprecated: Use refreshPageTemplate instead
737 function applyPageTemplate(title)
739 refreshPageTemplate(title);
742 // @Deprecated: Use story.displayTiddlers instead
743 function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,unused3)
745 story.displayTiddlers(srcElement,titles,template,animate);
748 // @Deprecated: Use story.displayTiddler instead
749 function displayTiddler(srcElement,title,template,unused1,unused2,animate,unused3)
751 story.displayTiddler(srcElement,title,template,animate);
754 // @Deprecated: Use functions on right hand side directly instead
755 var createTiddlerPopup = Popup.create;
756 var scrollToTiddlerPopup = Popup.show;
757 var hideTiddlerPopup = Popup.remove;
759 // @Deprecated: Use right hand side directly instead
760 var regexpBackSlashEn = new RegExp(&quot;\\\\n&quot;,&quot;mg&quot;);
761 var regexpBackSlash = new RegExp(&quot;\\\\&quot;,&quot;mg&quot;);
762 var regexpBackSlashEss = new RegExp(&quot;\\\\s&quot;,&quot;mg&quot;);
763 var regexpNewLine = new RegExp(&quot;\n&quot;,&quot;mg&quot;);
764 var regexpCarriageReturn = new RegExp(&quot;\r&quot;,&quot;mg&quot;);
767 //}}}</pre>
768 </div>
769 <div title="Development" modifier="Jason McVetta" modified="200803120221" created="200803112323" changecount="7">
770 <pre>Open Lease is being actively developed, but there is not yet a working release. It is open source ([[AGPLv3|Licensing]]). If you are interested in the project, either as a potential user or as a developer, [[drop me a line|Jason McVetta]].
772 !Git
773 Public [[git|http://git.or.cz]] repository, hosted courtesy of Linus et al: http://repo.or.cz/w/openlease.git
775 !Wiki
776 This website is an instance [[TiddlyWiki|http://tiddlywiki.com]], a ridiculously clever and useful tool. I have it setup single user, and thus read-only to the public, at the moment -- this, however, is more due to laziness than to policy. </pre>
777 </div>
778 <div title="Features" modifier="Jason McVetta" modified="200803120311" created="200803112201" changecount="24">
779 <pre>Open Lease aims to support features including:
780 * administrative automation
781 ** [[new leases|New Leases]]
782 ** [[subleases|Subleases]]
783 ** renewals
784 ** move-outs|New Leases]]
785 * rent management
786 ** billing
787 *** corporate accounts?
788 *** printed bills?
789 ** rent collection (via credit card)
790 ** rent recording
791 ** late fees
792 *** automatic billing
793 *** notification by email or postal mail
794 ** reconciliation
795 ** reporting
796 * tenant self-service
797 ** [[online rent payment|Paying Rent]]
798 *** via credit card
799 *** handled via integration w/ credit processing partner(s)
800 *** no credit card data stored in Open Lease system
801 ** maintenance requests
802 *** online request trackig
803 *** later, workflow for maint. request to work order</pre>
804 </div>
805 <div title="GettingStarted" modifier="Jason McVetta" created="200803112311" changecount="1">
806 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
807 * SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
808 * MainMenu: The menu (usually on the left)
809 * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
810 You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
811 </div>
812 <div title="Installation" modifier="Jason McVetta" created="200803112312" changecount="1">
813 <pre>Open Lease does not work yet. See [[Development]] for current status.</pre>
814 </div>
815 <div title="Jason McVetta" modifier="Jason McVetta" modified="200803112337" created="200803112255" changecount="8">
816 <pre>Hi, I'm Jason -- this project is my new toy. With Open Lease I hope to:
817 * conquer the weakass, proprietary rental management software competition
818 * simplify life for landlords &amp; tenants alike
819 * automate a lot of mindless, stupid paperwork
820 * [[make myself filthy rich|Business Model]]
822 Feel free to email me if you have something to say. Be forewarned that I am a terrible procrastinator when it comes returning email; but I always do get around to it.
824 [[jason.mcvetta@gmail.com|mailto:jason.mcvetta@gmail.com]]</pre>
825 </div>
826 <div title="LegacyStrikeThroughPlugin" modifier="MartinBudden" created="200607210000" tags="systemConfig">
827 <pre>/***
828 |''Name:''|LegacyStrikeThroughPlugin|
829 |''Description:''|Support for legacy (pre 2.1) strike through formatting|
830 |''Version:''|1.0.2|
831 |''Date:''|Jul 21, 2006|
832 |''Source:''|http://www.tiddlywiki.com/#LegacyStrikeThroughPlugin|
833 |''Author:''|MartinBudden (mjbudden (at) gmail (dot) com)|
834 |''License:''|[[BSD open source license]]|
835 |''CoreVersion:''|2.1.0|
836 ***/
838 //{{{
839 // Ensure that the LegacyStrikeThrough Plugin is only installed once.
840 if(!version.extensions.LegacyStrikeThroughPlugin) {
841 version.extensions.LegacyStrikeThroughPlugin = {installed:true};
843 config.formatters.push(
845 name: &quot;legacyStrikeByChar&quot;,
846 match: &quot;==&quot;,
847 termRegExp: /(==)/mg,
848 element: &quot;strike&quot;,
849 handler: config.formatterHelpers.createElementAndWikify
852 } //# end of &quot;install only once&quot;
853 //}}}</pre>
854 </div>
855 <div title="Licensing" modifier="Jason McVetta" modified="200803112329" created="200803112328" changecount="4">
856 <pre>Open Lease is released under the [[GNU Affero General Public License|http://www.fsf.org/licensing/licenses/agpl-3.0.html]]. From [[Wikipedia|http://en.wikipedia.org/wiki/Affero_General_Public_License]]:
857 &lt;&lt;&lt;
858 The GNU Affero General Public License or GNU AGPL is a free software license published by the Free Software Foundation. The GNU AGPL is similar to the GNU General Public License, except that it has an additional section to cover use over a computer network. It closes what is commonly known as the Application service provider loophole of the GNU General Public License. The additional section requires that the complete source code be made available to any network user of the ~AGPLed work, typically a web application.
859 &lt;&lt;&lt;
860 </pre>
861 </div>
862 <div title="MainMenu" modifier="Jason McVetta" modified="200803120254" created="200803112251" changecount="10">
863 <pre>[[About]]
864 [[Features]]
865 [[Development]]
866 [[Licensing]]
867 [[Installation]]
868 </pre>
869 </div>
870 <div title="New Leases" modifier="Jason McVetta" modified="200803120254" created="200803120253" changecount="3">
871 <pre>* [[Boilerplate leases|Boilerplate Lease Contracts]] that match the default functionality of the app* Incomplete leases can be entered
872 ** need {{{lease.is_complete()}}} instance method
873 ** A lease cannot take effect until it {{{is_complete()}}}
874 ** System can tell user (property manager //and// tenant) what deliverables (steps/documents) must yet be completed
875 ** {{{DeliverableItem}}} defines a generic deliverable object
876 *** Web-forms
877 *** Printable/returnable PDF documents
878 *** Human tasks -- e.g. credit check
879 * A sublease is just like a lease, except it has a master tenant and a chain of liability
880 ** Sublease must have its own contract, which wholly supercedes that of the master lease -- clarity
881 *** always?
882 *** 'cascading' contract from master lease? </pre>
883 </div>
884 <div title="Open Lease Company" modifier="Jason McVetta" created="200803112338" changecount="1">
885 <pre>This company does not yet exist in any legal sort of way. It's just a placeholder name.</pre>
886 </div>
887 <div title="Paying Rent" modifier="Jason McVetta" modified="200803120258" created="200803120151" changecount="2">
888 <pre>Open Lease will allow property managers to accept rent payment by credit/debit card. Accepting rent via credit card removes a landlord to a whole class of credit risk inherent in accepting checks. </pre>
889 </div>
890 <div title="Self-Service" modifier="Jason McVetta" created="200803112212" changecount="1">
891 <pre>* Rental payment via credit card
892 ** Easy for tenants &amp; subtenants to pay individually
893 ** Tenants &amp; subtenants can be assigned explicit proportional responsibility for rent payment</pre>
894 </div>
895 <div title="SiteSubtitle" modifier="Jason McVetta" modified="200803112200" created="200803112159" changecount="2">
896 <pre>open source rental property management software</pre>
897 </div>
898 <div title="SiteTitle" modifier="Jason McVetta" modified="200803112200" created="200803112159" changecount="2">
899 <pre>Open Lease</pre>
900 </div>
901 <div title="SparklinePlugin" created="200710140259" tags="systemConfig excludeLists excludeSearch">
902 <pre>/***
903 |''Name:''|SparklinePlugin|
904 |''Description:''|Sparklines macro|
905 ***/
906 //{{{
907 if(!version.extensions.SparklinePlugin) {
908 version.extensions.SparklinePlugin = {installed:true};
910 //--
911 //-- Sparklines
912 //--
914 config.macros.sparkline = {};
915 config.macros.sparkline.handler = function(place,macroName,params)
917 var data = [];
918 var min = 0;
919 var max = 0;
920 var v;
921 for(var t=0; t&lt;params.length; t++) {
922 v = parseInt(params[t]);
923 if(v &lt; min)
924 min = v;
925 if(v &gt; max)
926 max = v;
927 data.push(v);
929 if(data.length &lt; 1)
930 return;
931 var box = createTiddlyElement(place,&quot;span&quot;,null,&quot;sparkline&quot;,String.fromCharCode(160));
932 box.title = data.join(&quot;,&quot;);
933 var w = box.offsetWidth;
934 var h = box.offsetHeight;
935 box.style.paddingRight = (data.length * 2 - w) + &quot;px&quot;;
936 box.style.position = &quot;relative&quot;;
937 for(var d=0; d&lt;data.length; d++) {
938 var tick = document.createElement(&quot;img&quot;);
939 tick.border = 0;
940 tick.className = &quot;sparktick&quot;;
941 tick.style.position = &quot;absolute&quot;;
942 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;;
943 tick.style.left = d*2 + &quot;px&quot;;
944 tick.style.width = &quot;2px&quot;;
945 v = Math.floor(((data[d] - min)/(max-min)) * h);
946 tick.style.top = (h-v) + &quot;px&quot;;
947 tick.style.height = v + &quot;px&quot;;
948 box.appendChild(tick);
954 //}}}</pre>
955 </div>
956 <div title="Subleases" modifier="Jason McVetta" modified="200803120310" created="200803120256" changecount="2">
957 <pre>The proportional rent responsibility built-in to the system and its associated ready-made leases 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>
958 </div>
959 </div>
960 <!--POST-STOREAREA-->
961 <!--POST-BODY-START-->
962 <!--POST-BODY-END-->
963 <script type="text/javascript">
964 //<![CDATA[
966 // Please note:
968 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
969 // in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
971 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
972 // without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
975 //--
976 //-- Configuration repository
977 //--
979 // Miscellaneous options
980 var config = {
981 numRssItems: 20, // Number of items in the RSS feed
982 animDuration: 400, // Duration of UI animations in milliseconds
983 cascadeFast: 20, // Speed for cascade animations (higher == slower)
984 cascadeSlow: 60, // Speed for EasterEgg cascade animations
985 cascadeDepth: 5 // Depth of cascade animation
988 // Adaptors
989 config.adaptors = {};
991 // Backstage tasks
992 config.tasks = {};
994 // Annotations
995 config.annotations = {};
997 // Custom fields to be automatically added to new tiddlers
998 config.defaultCustomFields = {};
1000 // Messages
1001 config.messages = {
1002 messageClose: {},
1003 dates: {},
1004 tiddlerPopup: {}
1007 // Options that can be set in the options panel and/or cookies
1008 config.options = {
1009 chkRegExpSearch: false,
1010 chkCaseSensitiveSearch: false,
1011 chkAnimate: true,
1012 chkSaveBackups: true,
1013 chkAutoSave: false,
1014 chkGenerateAnRssFeed: false,
1015 chkSaveEmptyTemplate: false,
1016 chkOpenInNewWindow: true,
1017 chkToggleLinks: false,
1018 chkHttpReadOnly: true,
1019 chkForceMinorUpdate: false,
1020 chkConfirmDelete: true,
1021 chkInsertTabs: false,
1022 chkUsePreForStorage: true, // Whether to use <pre> format for storage
1023 chkDisplayStartupTime: false,
1024 txtBackupFolder: "",
1025 txtMainTab: "tabTimeline",
1026 txtMoreTab: "moreTabAll",
1027 txtMaxEditRows: "30",
1028 txtFileSystemCharSet: "UTF-8",
1029 txtTheme: ""
1031 config.optionsDesc = {};
1033 // List of notification functions to be called when certain tiddlers are changed or deleted
1034 config.notifyTiddlers = [
1035 {name: "StyleSheetLayout", notify: refreshStyles},
1036 {name: "StyleSheetColors", notify: refreshStyles},
1037 {name: "StyleSheet", notify: refreshStyles},
1038 {name: "StyleSheetPrint", notify: refreshStyles},
1039 {name: "PageTemplate", notify: refreshPageTemplate},
1040 {name: "SiteTitle", notify: refreshPageTitle},
1041 {name: "SiteSubtitle", notify: refreshPageTitle},
1042 {name: "ColorPalette", notify: refreshColorPalette},
1043 {name: null, notify: refreshDisplay}
1046 // Default tiddler templates
1047 var DEFAULT_VIEW_TEMPLATE = 1;
1048 var DEFAULT_EDIT_TEMPLATE = 2;
1049 config.tiddlerTemplates = {
1050 1: "ViewTemplate",
1051 2: "EditTemplate"
1054 // More messages (rather a legacy layout that shouldn't really be like this)
1055 config.views = {
1056 wikified: {
1057 tag: {}
1059 editor: {
1060 tagChooser: {}
1064 // Backstage tasks
1065 config.backstageTasks = ["save","sync","importTask","tweak","plugins"];
1067 // Macros; each has a 'handler' member that is inserted later
1068 config.macros = {
1069 today: {},
1070 version: {},
1071 search: {sizeTextbox: 15},
1072 tiddler: {},
1073 tag: {},
1074 tags: {},
1075 tagging: {},
1076 timeline: {},
1077 allTags: {},
1078 list: {
1079 all: {},
1080 missing: {},
1081 orphans: {},
1082 shadowed: {},
1083 touched: {},
1084 filter: {}
1086 closeAll: {},
1087 permaview: {},
1088 saveChanges: {},
1089 slider: {},
1090 option: {},
1091 options: {},
1092 newTiddler: {},
1093 newJournal: {},
1094 tabs: {},
1095 gradient: {},
1096 message: {},
1097 view: {},
1098 edit: {},
1099 tagChooser: {},
1100 toolbar: {},
1101 plugins: {},
1102 refreshDisplay: {},
1103 importTiddlers: {},
1104 sync: {},
1105 annotations: {}
1108 // Commands supported by the toolbar macro
1109 config.commands = {
1110 closeTiddler: {},
1111 closeOthers: {},
1112 editTiddler: {},
1113 saveTiddler: {hideReadOnly: true},
1114 cancelTiddler: {},
1115 deleteTiddler: {hideReadOnly: true},
1116 permalink: {},
1117 references: {type: "popup"},
1118 jump: {type: "popup"},
1119 syncing: {type: "popup"},
1120 fields: {type: "popup"}
1123 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
1124 config.userAgent = navigator.userAgent.toLowerCase();
1125 config.browser = {
1126 isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
1127 isGecko: config.userAgent.indexOf("gecko") != -1,
1128 ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
1129 isSafari: config.userAgent.indexOf("applewebkit") != -1,
1130 isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
1131 firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
1132 isOpera: config.userAgent.indexOf("opera") != -1,
1133 isLinux: config.userAgent.indexOf("linux") != -1,
1134 isUnix: config.userAgent.indexOf("x11") != -1,
1135 isMac: config.userAgent.indexOf("mac") != -1,
1136 isWindows: config.userAgent.indexOf("win") != -1
1139 // Basic regular expressions
1140 config.textPrimitives = {
1141 upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
1142 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
1143 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
1144 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
1146 if(config.browser.isBadSafari) {
1147 config.textPrimitives = {
1148 upperLetter: "[A-Z\u00c0-\u00de]",
1149 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
1150 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
1151 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
1154 config.textPrimitives.sliceSeparator = "::";
1155 config.textPrimitives.sectionSeparator = "##";
1156 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
1157 config.textPrimitives.unWikiLink = "~";
1158 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
1159 config.textPrimitives.lowerLetter + "+" +
1160 config.textPrimitives.upperLetter +
1161 config.textPrimitives.anyLetter + "*)|(?:" +
1162 config.textPrimitives.upperLetter + "{2,}" +
1163 config.textPrimitives.lowerLetter + "+))";
1165 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
1166 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
1168 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
1169 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
1170 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
1171 config.textPrimitives.brackettedLink + ")|(?:" +
1172 config.textPrimitives.urlPattern + ")","mg");
1173 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
1174 config.textPrimitives.titledBrackettedLink + ")|(?:" +
1175 config.textPrimitives.brackettedLink + ")|(?:" +
1176 config.textPrimitives.urlPattern + ")","mg");
1178 config.glyphs = {
1179 browsers: [
1180 function() {return config.browser.isIE;},
1181 function() {return true;}
1183 currBrowser: null,
1184 codes: {
1185 downTriangle: ["\u25BC","\u25BE"],
1186 downArrow: ["\u2193","\u2193"],
1187 bentArrowLeft: ["\u2190","\u21A9"],
1188 bentArrowRight: ["\u2192","\u21AA"]
1192 //--
1193 //-- Shadow tiddlers
1194 //--
1196 config.shadowTiddlers = {
1197 StyleSheet: "",
1198 MarkupPreHead: "",
1199 MarkupPostHead: "",
1200 MarkupPreBody: "",
1201 MarkupPostBody: "",
1202 TabTimeline: '<<timeline>>',
1203 TabAll: '<<list all>>',
1204 TabTags: '<<allTags excludeLists>>',
1205 TabMoreMissing: '<<list missing>>',
1206 TabMoreOrphans: '<<list orphans>>',
1207 TabMoreShadowed: '<<list shadowed>>',
1208 AdvancedOptions: '<<options>>',
1209 PluginManager: '<<plugins>>'
1212 //--
1213 //-- Translateable strings
1214 //--
1216 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
1218 merge(config.options,{
1219 txtUserName: "YourName"});
1221 merge(config.tasks,{
1222 save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
1223 sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
1224 importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
1225 tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
1226 plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
1229 // Options that can be set in the options panel and/or cookies
1230 merge(config.optionsDesc,{
1231 txtUserName: "Username for signing your edits",
1232 chkRegExpSearch: "Enable regular expressions for searches",
1233 chkCaseSensitiveSearch: "Case-sensitive searching",
1234 chkAnimate: "Enable animations",
1235 chkSaveBackups: "Keep backup file when saving changes",
1236 chkAutoSave: "Automatically save changes",
1237 chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
1238 chkSaveEmptyTemplate: "Generate an empty template when saving changes",
1239 chkOpenInNewWindow: "Open external links in a new window",
1240 chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
1241 chkHttpReadOnly: "Hide editing features when viewed over HTTP",
1242 chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
1243 chkConfirmDelete: "Require confirmation before deleting tiddlers",
1244 chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
1245 txtBackupFolder: "Name of folder to use for backups",
1246 txtMaxEditRows: "Maximum number of rows in edit boxes",
1247 txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
1249 merge(config.messages,{
1250 customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
1251 pluginError: "Error: %0",
1252 pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
1253 pluginForced: "Executed because forced via 'systemConfigForce' tag",
1254 pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
1255 nothingSelected: "Nothing is selected. You must select one or more items first",
1256 savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
1257 subtitleUnknown: "(unknown)",
1258 undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
1259 shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
1260 tiddlerLinkTooltip: "%0 - %1, %2",
1261 externalLinkTooltip: "External link to %0",
1262 noTags: "There are no tagged tiddlers",
1263 notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
1264 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",
1265 invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
1266 backupSaved: "Backup saved",
1267 backupFailed: "Failed to save backup file",
1268 rssSaved: "RSS feed saved",
1269 rssFailed: "Failed to save RSS feed file",
1270 emptySaved: "Empty template saved",
1271 emptyFailed: "Failed to save empty template file",
1272 mainSaved: "Main TiddlyWiki file saved",
1273 mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
1274 macroError: "Error in macro <<\%0>>",
1275 macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
1276 missingMacro: "No such macro",
1277 overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
1278 unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
1279 confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
1280 saveInstructions: "SaveChanges",
1281 unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
1282 tiddlerSaveError: "Error when saving tiddler '%0'",
1283 tiddlerLoadError: "Error when loading tiddler '%0'",
1284 wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
1285 invalidFieldName: "Invalid field name %0",
1286 fieldCannotBeChanged: "Field '%0' cannot be changed",
1287 loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'"});
1289 merge(config.messages.messageClose,{
1290 text: "close",
1291 tooltip: "close this message area"});
1293 config.messages.backstage = {
1294 open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
1295 close: {text: "close", tooltip: "Close the backstage area"},
1296 prompt: "backstage: ",
1297 decal: {
1298 edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
1302 config.messages.listView = {
1303 tiddlerTooltip: "Click for the full text of this tiddler",
1304 previewUnavailable: "(preview not available)"
1307 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
1308 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1309 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1310 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1311 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
1312 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
1313 "th","th","th","th","th","th","th","th","th","th",
1314 "st","nd","rd","th","th","th","th","th","th","th",
1315 "st"];
1316 config.messages.dates.am = "am";
1317 config.messages.dates.pm = "pm";
1319 merge(config.messages.tiddlerPopup,{
1322 merge(config.views.wikified.tag,{
1323 labelNoTags: "no tags",
1324 labelTags: "tags: ",
1325 openTag: "Open tag '%0'",
1326 tooltip: "Show tiddlers tagged with '%0'",
1327 openAllText: "Open all",
1328 openAllTooltip: "Open all of these tiddlers",
1329 popupNone: "No other tiddlers tagged with '%0'"});
1331 merge(config.views.wikified,{
1332 defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
1333 defaultModifier: "(missing)",
1334 shadowModifier: "(built-in shadow tiddler)",
1335 dateFormat: "DD MMM YYYY",
1336 createdPrompt: "created"});
1338 merge(config.views.editor,{
1339 tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
1340 defaultText: "Type the text for '%0'"});
1342 merge(config.views.editor.tagChooser,{
1343 text: "tags",
1344 tooltip: "Choose existing tags to add to this tiddler",
1345 popupNone: "There are no tags defined",
1346 tagTooltip: "Add the tag '%0'"});
1348 merge(config.messages,{
1349 sizeTemplates:
1351 {unit: 1024*1024*1024, template: "%0\u00a0GB"},
1352 {unit: 1024*1024, template: "%0\u00a0MB"},
1353 {unit: 1024, template: "%0\u00a0KB"},
1354 {unit: 1, template: "%0\u00a0B"}
1355 ]});
1357 merge(config.macros.search,{
1358 label: "search",
1359 prompt: "Search this TiddlyWiki",
1360 accessKey: "F",
1361 successMsg: "%0 tiddlers found matching %1",
1362 failureMsg: "No tiddlers found matching %0"});
1364 merge(config.macros.tagging,{
1365 label: "tagging: ",
1366 labelNotTag: "not tagging",
1367 tooltip: "List of tiddlers tagged with '%0'"});
1369 merge(config.macros.timeline,{
1370 dateFormat: "DD MMM YYYY"});
1372 merge(config.macros.allTags,{
1373 tooltip: "Show tiddlers tagged with '%0'",
1374 noTags: "There are no tagged tiddlers"});
1376 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
1377 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
1378 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
1379 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
1380 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
1382 merge(config.macros.closeAll,{
1383 label: "close all",
1384 prompt: "Close all displayed tiddlers (except any that are being edited)"});
1386 merge(config.macros.permaview,{
1387 label: "permaview",
1388 prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
1390 merge(config.macros.saveChanges,{
1391 label: "save changes",
1392 prompt: "Save all tiddlers to create a new TiddlyWiki",
1393 accessKey: "S"});
1395 merge(config.macros.newTiddler,{
1396 label: "new tiddler",
1397 prompt: "Create a new tiddler",
1398 title: "New Tiddler",
1399 accessKey: "N"});
1401 merge(config.macros.newJournal,{
1402 label: "new journal",
1403 prompt: "Create a new tiddler from the current date and time",
1404 accessKey: "J"});
1406 merge(config.macros.options,{
1407 wizardTitle: "Tweak advanced options",
1408 step1Title: "These options are saved in cookies in your browser",
1409 step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
1410 unknownDescription: "//(unknown)//",
1411 listViewTemplate: {
1412 columns: [
1413 {name: 'Option', field: 'option', title: "Option", type: 'String'},
1414 {name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
1415 {name: 'Name', field: 'name', title: "Name", type: 'String'}
1417 rowClasses: [
1418 {className: 'lowlight', field: 'lowlight'}
1422 merge(config.macros.plugins,{
1423 wizardTitle: "Manage plugins",
1424 step1Title: "Currently loaded plugins",
1425 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1426 skippedText: "(This plugin has not been executed because it was added since startup)",
1427 noPluginText: "There are no plugins installed",
1428 confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
1429 removeLabel: "remove systemConfig tag",
1430 removePrompt: "Remove systemConfig tag",
1431 deleteLabel: "delete",
1432 deletePrompt: "Delete these tiddlers forever",
1433 listViewTemplate: {
1434 columns: [
1435 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1436 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1437 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1438 {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
1439 {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
1440 {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
1441 {name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
1442 {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
1443 {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
1445 rowClasses: [
1446 {className: 'error', field: 'error'},
1447 {className: 'warning', field: 'warning'}
1451 merge(config.macros.toolbar,{
1452 moreLabel: "more",
1453 morePrompt: "Reveal further commands"
1456 merge(config.macros.refreshDisplay,{
1457 label: "refresh",
1458 prompt: "Redraw the entire TiddlyWiki display"
1461 merge(config.macros.importTiddlers,{
1462 readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
1463 wizardTitle: "Import tiddlers from another file or server",
1464 step1Title: "Step 1: Locate the server or TiddlyWiki file",
1465 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>",
1466 openLabel: "open",
1467 openPrompt: "Open the connection to this file or server",
1468 openError: "There were problems fetching the tiddlywiki file",
1469 statusOpenHost: "Opening the host",
1470 statusGetWorkspaceList: "Getting the list of available workspaces",
1471 step2Title: "Step 2: Choose the workspace",
1472 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>",
1473 cancelLabel: "cancel",
1474 cancelPrompt: "Cancel this import",
1475 statusOpenWorkspace: "Opening the workspace",
1476 statusGetTiddlerList: "Getting the list of available tiddlers",
1477 step3Title: "Step 3: Choose the tiddlers to import",
1478 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'>",
1479 importLabel: "import",
1480 importPrompt: "Import these tiddlers",
1481 confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
1482 step4Title: "Step 4: Importing %0 tiddler(s)",
1483 step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
1484 doneLabel: "done",
1485 donePrompt: "Close this wizard",
1486 statusDoingImport: "Importing tiddlers",
1487 statusDoneImport: "All tiddlers imported",
1488 systemServerNamePattern: "%2 on %1",
1489 systemServerNamePatternNoWorkspace: "%1",
1490 confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
1491 serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
1492 serverSaveModifier: "(System)",
1493 listViewTemplate: {
1494 columns: [
1495 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1496 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1497 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1498 {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
1500 rowClasses: [
1504 merge(config.macros.sync,{
1505 listViewTemplate: {
1506 columns: [
1507 {name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
1508 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1509 {name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
1510 {name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
1511 {name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
1512 {name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
1513 {name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
1515 rowClasses: [
1517 buttons: [
1518 {caption: "Sync these tiddlers", name: 'sync'}
1520 wizardTitle: "Synchronize with external servers and files",
1521 step1Title: "Choose the tiddlers you want to synchronize",
1522 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1523 syncLabel: "sync",
1524 syncPrompt: "Sync these tiddlers",
1525 hasChanged: "Changed while unplugged",
1526 hasNotChanged: "Unchanged while unplugged",
1527 syncStatusList: {
1528 none: {text: "...", color: "transparent"},
1529 changedServer: {text: "Changed on server", color: '#80ff80'},
1530 changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
1531 changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
1532 notFound: {text: "Not found on server", color: '#ffff80'},
1533 putToServer: {text: "Saved update on server", color: '#ff80ff'},
1534 gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
1538 merge(config.macros.annotations,{
1541 merge(config.commands.closeTiddler,{
1542 text: "close",
1543 tooltip: "Close this tiddler"});
1545 merge(config.commands.closeOthers,{
1546 text: "close others",
1547 tooltip: "Close all other tiddlers"});
1549 merge(config.commands.editTiddler,{
1550 text: "edit",
1551 tooltip: "Edit this tiddler",
1552 readOnlyText: "view",
1553 readOnlyTooltip: "View the source of this tiddler"});
1555 merge(config.commands.saveTiddler,{
1556 text: "done",
1557 tooltip: "Save changes to this tiddler"});
1559 merge(config.commands.cancelTiddler,{
1560 text: "cancel",
1561 tooltip: "Undo changes to this tiddler",
1562 warning: "Are you sure you want to abandon your changes to '%0'?",
1563 readOnlyText: "done",
1564 readOnlyTooltip: "View this tiddler normally"});
1566 merge(config.commands.deleteTiddler,{
1567 text: "delete",
1568 tooltip: "Delete this tiddler",
1569 warning: "Are you sure you want to delete '%0'?"});
1571 merge(config.commands.permalink,{
1572 text: "permalink",
1573 tooltip: "Permalink for this tiddler"});
1575 merge(config.commands.references,{
1576 text: "references",
1577 tooltip: "Show tiddlers that link to this one",
1578 popupNone: "No references"});
1580 merge(config.commands.jump,{
1581 text: "jump",
1582 tooltip: "Jump to another open tiddler"});
1584 merge(config.commands.syncing,{
1585 text: "syncing",
1586 tooltip: "Control synchronisation of this tiddler with a server or external file",
1587 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
1588 notCurrentlySyncing: "Not currently syncing",
1589 captionUnSync: "Stop synchronising this tiddler",
1590 chooseServer: "Synchronise this tiddler with another server:",
1591 currServerMarker: "\u25cf ",
1592 notCurrServerMarker: " "});
1594 merge(config.commands.fields,{
1595 text: "fields",
1596 tooltip: "Show the extended fields of this tiddler",
1597 emptyText: "There are no extended fields for this tiddler",
1598 listViewTemplate: {
1599 columns: [
1600 {name: 'Field', field: 'field', title: "Field", type: 'String'},
1601 {name: 'Value', field: 'value', title: "Value", type: 'String'}
1603 rowClasses: [
1605 buttons: [
1606 ]}});
1608 merge(config.shadowTiddlers,{
1609 DefaultTiddlers: "GettingStarted",
1610 MainMenu: "GettingStarted",
1611 SiteTitle: "My TiddlyWiki",
1612 SiteSubtitle: "a reusable non-linear personal web notebook",
1613 SiteUrl: "http://www.tiddlywiki.com/",
1614 SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>',
1615 SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
1616 TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'});
1618 merge(config.annotations,{
1619 AdvancedOptions: "This shadow tiddler provides access to several advanced options",
1620 ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
1621 DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
1622 EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
1623 GettingStarted: "This shadow tiddler provides basic usage instructions",
1624 ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
1625 MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
1626 MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
1627 MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
1628 MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
1629 MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately before the script block",
1630 OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
1631 PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
1632 PluginManager: "This shadow tiddler provides access to the plugin manager",
1633 SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
1634 SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
1635 SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
1636 SiteTitle: "This shadow tiddler is used as the first part of the page title",
1637 SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
1638 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.",
1639 StyleSheet: "This tiddler can contain custom CSS definitions",
1640 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.",
1641 StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
1642 StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
1643 TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
1644 TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
1645 TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
1646 TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
1647 TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
1648 TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
1649 TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
1650 ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
1653 //--
1654 //-- Main
1655 //--
1657 var params = null; // Command line parameters
1658 var store = null; // TiddlyWiki storage
1659 var story = null; // Main story
1660 var formatter = null; // Default formatters for the wikifier
1661 config.parsers = {}; // Hashmap of alternative parsers for the wikifier
1662 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
1663 var readOnly = false; // Whether we're in readonly mode
1664 var highlightHack = null; // Embarrassing hack department...
1665 var hadConfirmExit = false; // Don't warn more than once
1666 var safeMode = false; // Disable all plugins and cookies
1667 var showBackstage; // Whether to include the backstage area
1668 var installedPlugins = []; // Information filled in when plugins are executed
1669 var startingUp = false; // Whether we're in the process of starting up
1670 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
1672 // Whether to use the JavaSaver applet
1673 var useJavaSaver = config.browser.isSafari || config.browser.isOpera;
1675 // Starting up
1676 function main()
1678 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1679 startingUp = true;
1680 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1681 params = getParameters();
1682 if(params)
1683 params = params.parseParams("open",null,false);
1684 store = new TiddlyWiki();
1685 invokeParamifier(params,"oninit");
1686 story = new Story("tiddlerDisplay","tiddler");
1687 addEvent(document,"click",Popup.onDocumentClick);
1688 saveTest();
1689 loadOptionsCookie();
1690 for(var s=0; s<config.notifyTiddlers.length; s++)
1691 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1692 t1 = new Date();
1693 store.loadFromDiv("storeArea","store",true);
1694 t2 = new Date();
1695 loadShadowTiddlers();
1696 t3 = new Date();
1697 invokeParamifier(params,"onload");
1698 t4 = new Date();
1699 readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
1700 showBackstage = !readOnly;
1701 var pluginProblem = loadPlugins();
1702 t5 = new Date();
1703 formatter = new Formatter(config.formatters);
1704 story.switchTheme(config.options.txtTheme);
1705 invokeParamifier(params,"onconfig");
1706 t6 = new Date();
1707 store.notifyAll();
1708 t7 = new Date();
1709 restart();
1710 t8 = new Date();
1711 if(pluginProblem) {
1712 story.displayTiddler(null,"PluginManager");
1713 displayMessage(config.messages.customConfigError);
1715 for(var m in config.macros) {
1716 if(config.macros[m].init)
1717 config.macros[m].init();
1719 t9 = new Date();
1720 if(showBackstage)
1721 backstage.init();
1722 t10 = new Date();
1723 if(config.options.chkDisplayStartupTime) {
1724 displayMessage("LoadFromDiv " + (t2-t1) + " ms");
1725 displayMessage("LoadShadows " + (t3-t2) + " ms");
1726 displayMessage("LoadPlugins " + (t5-t4) + " ms");
1727 displayMessage("Notify " + (t7-t6) + " ms");
1728 displayMessage("Restart " + (t8-t7) + " ms");
1729 displayMessage("Macro init " + (t9-t8) + " ms");
1730 displayMessage("Total: " + (t10-t0) + " ms");
1732 startingUp = false;
1735 // Restarting
1736 function restart()
1738 invokeParamifier(params,"onstart");
1739 if(story.isEmpty()) {
1740 var tiddlers = store.filterTiddlers(store.getTiddlerText("DefaultTiddlers"));
1741 story.displayTiddlers(null,tiddlers);
1743 window.scrollTo(0,0);
1746 function saveTest()
1748 var s = document.getElementById("saveTest");
1749 if(s.hasChildNodes())
1750 alert(config.messages.savedSnapshotError);
1751 s.appendChild(document.createTextNode("savetest"));
1754 function loadShadowTiddlers()
1756 var shadows = new TiddlyWiki();
1757 shadows.loadFromDiv("shadowArea","shadows",true);
1758 shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
1759 delete shadows;
1762 function loadPlugins()
1764 if(safeMode)
1765 return false;
1766 var tiddlers = store.getTaggedTiddlers("systemConfig");
1767 var toLoad = [];
1768 var nLoaded = 0;
1769 var map = {};
1770 var nPlugins = tiddlers.length;
1771 installedPlugins = [];
1772 for(var i=0; i<nPlugins; i++) {
1773 var p = getPluginInfo(tiddlers[i]);
1774 installedPlugins[i] = p;
1775 var n = p.Name;
1776 if(n)
1777 map[n] = p;
1778 n = p.Source;
1779 if(n)
1780 map[n] = p;
1782 var visit = function(p) {
1783 if(!p || p.done)
1784 return;
1785 p.done = 1;
1786 var reqs = p.Requires;
1787 if(reqs) {
1788 reqs = reqs.readBracketedList();
1789 for(var i=0; i<reqs.length; i++)
1790 visit(map[reqs[i]]);
1792 toLoad.push(p);
1794 for(i=0; i<nPlugins; i++)
1795 visit(installedPlugins[i]);
1796 for(i=0; i<toLoad.length; i++) {
1797 p = toLoad[i];
1798 pluginInfo = p;
1799 tiddler = p.tiddler;
1800 if(isPluginExecutable(p)) {
1801 if(isPluginEnabled(p)) {
1802 p.executed = true;
1803 var startTime = new Date();
1804 try {
1805 if(tiddler.text)
1806 window.eval(tiddler.text);
1807 nLoaded++;
1808 } catch(ex) {
1809 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
1810 p.error = true;
1812 pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
1813 } else {
1814 nPlugins--;
1816 } else {
1817 p.warning = true;
1820 return nLoaded != nPlugins;
1823 function getPluginInfo(tiddler)
1825 var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
1826 p.tiddler = tiddler;
1827 p.title = tiddler.title;
1828 p.log = [];
1829 return p;
1832 // Check that a particular plugin is valid for execution
1833 function isPluginExecutable(plugin)
1835 if(plugin.tiddler.isTagged("systemConfigForce"))
1836 return verifyTail(plugin,true,config.messages.pluginForced);
1837 if(plugin["CoreVersion"]) {
1838 var coreVersion = plugin["CoreVersion"].split(".");
1839 var w = parseInt(coreVersion[0]) - version.major;
1840 if(w == 0 && coreVersion[1])
1841 w = parseInt(coreVersion[1]) - version.minor;
1842 if(w == 0 && coreVersion[2])
1843 w = parseInt(coreVersion[2]) - version.revision;
1844 if(w > 0)
1845 return verifyTail(plugin,false,config.messages.pluginVersionError);
1847 return true;
1850 function isPluginEnabled(plugin)
1852 if(plugin.tiddler.isTagged("systemConfigDisable"))
1853 return verifyTail(plugin,false,config.messages.pluginDisabled);
1854 return true;
1857 function verifyTail(plugin,result,message)
1859 plugin.log.push(message);
1860 return result;
1863 function invokeMacro(place,macro,params,wikifier,tiddler)
1865 try {
1866 var m = config.macros[macro];
1867 if(m && m.handler)
1868 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
1869 else
1870 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
1871 } catch(ex) {
1872 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
1876 //--
1877 //-- Paramifiers
1878 //--
1880 function getParameters()
1882 var p = null;
1883 if(window.location.hash) {
1884 p = decodeURI(window.location.hash.substr(1));
1885 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
1886 p = convertUTF8ToUnicode(p);
1888 return p;
1891 function invokeParamifier(params,handler)
1893 if(!params || params.length == undefined || params.length <= 1)
1894 return;
1895 for(var t=1; t<params.length; t++) {
1896 var p = config.paramifiers[params[t].name];
1897 if(p && p[handler] instanceof Function)
1898 p[handler](params[t].value);
1902 config.paramifiers = {};
1904 config.paramifiers.start = {
1905 oninit: function(v) {
1906 safeMode = v.toLowerCase() == "safe";
1910 config.paramifiers.open = {
1911 onstart: function(v) {
1912 story.displayTiddler("bottom",v,null,false,null);
1916 config.paramifiers.story = {
1917 onstart: function(v) {
1918 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
1919 invokeParamifier(list,"onstart");
1923 config.paramifiers.search = {
1924 onstart: function(v) {
1925 story.search(v,false,false);
1929 config.paramifiers.searchRegExp = {
1930 onstart: function(v) {
1931 story.prototype.search(v,false,true);
1935 config.paramifiers.tag = {
1936 onstart: function(v) {
1937 var tagged = store.getTaggedTiddlers(v,"title");
1938 story.displayTiddlers(null,tagged,null,false,null);
1942 config.paramifiers.newTiddler = {
1943 onstart: function(v) {
1944 if(!readOnly) {
1945 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
1946 story.focusTiddler(v,"text");
1951 config.paramifiers.newJournal = {
1952 onstart: function(v) {
1953 if(!readOnly) {
1954 var now = new Date();
1955 var title = now.formatString(v.trim());
1956 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
1957 story.focusTiddler(title,"text");
1962 config.paramifiers.readOnly = {
1963 onconfig: function(v) {
1964 var p = v.toLowerCase();
1965 readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
1970 config.paramifiers.theme = {
1971 onconfig: function(v) {
1972 story.switchTheme(v);
1976 //--
1977 //-- Formatter helpers
1978 //--
1980 function Formatter(formatters)
1982 this.formatters = [];
1983 var pattern = [];
1984 for(var n=0; n<formatters.length; n++) {
1985 pattern.push("(" + formatters[n].match + ")");
1986 this.formatters.push(formatters[n]);
1988 this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
1991 config.formatterHelpers = {
1993 createElementAndWikify: function(w)
1995 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
1998 inlineCssHelper: function(w)
2000 var styles = [];
2001 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2002 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2003 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2004 var s,v;
2005 if(lookaheadMatch[1]) {
2006 s = lookaheadMatch[1].unDash();
2007 v = lookaheadMatch[2];
2008 } else {
2009 s = lookaheadMatch[3].unDash();
2010 v = lookaheadMatch[4];
2012 if (s=="bgcolor")
2013 s = "backgroundColor";
2014 styles.push({style: s, value: v});
2015 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2016 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2017 lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2019 return styles;
2022 applyCssHelper: function(e,styles)
2024 for(var t=0; t< styles.length; t++) {
2025 try {
2026 e.style[styles[t].style] = styles[t].value;
2027 } catch (ex) {
2032 enclosedTextHelper: function(w)
2034 this.lookaheadRegExp.lastIndex = w.matchStart;
2035 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2036 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2037 var text = lookaheadMatch[1];
2038 if(config.browser.isIE)
2039 text = text.replace(/\n/g,"\r");
2040 createTiddlyElement(w.output,this.element,null,null,text);
2041 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2045 isExternalLink: function(link)
2047 if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
2048 return false;
2050 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
2051 if(urlRegExp.exec(link)) {
2052 return true;
2054 if (link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
2055 return true;
2057 return false;
2062 //--
2063 //-- Standard formatters
2064 //--
2066 config.formatters = [
2068 name: "table",
2069 match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
2070 lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
2071 rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
2072 cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
2073 cellTermRegExp: /((?:\x20*)\|)/mg,
2074 rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
2075 handler: function(w)
2077 var table = createTiddlyElement(w.output,"table",null,"twtable");
2078 var prevColumns = [];
2079 var currRowType = null;
2080 var rowContainer;
2081 var rowCount = 0;
2082 w.nextMatch = w.matchStart;
2083 this.lookaheadRegExp.lastIndex = w.nextMatch;
2084 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2085 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2086 var nextRowType = lookaheadMatch[2];
2087 if(nextRowType == "k") {
2088 table.className = lookaheadMatch[1];
2089 w.nextMatch += lookaheadMatch[0].length+1;
2090 } else {
2091 if(nextRowType != currRowType) {
2092 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2093 currRowType = nextRowType;
2095 if(currRowType == "c") {
2096 // Caption
2097 w.nextMatch++;
2098 if(rowContainer != table.firstChild)
2099 table.insertBefore(rowContainer,table.firstChild);
2100 rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
2101 w.subWikifyTerm(rowContainer,this.rowTermRegExp);
2102 } else {
2103 var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
2104 theRow.onmouseover = function() {addClass(this,"hoverRow");};
2105 theRow.onmouseout = function() {removeClass(this,"hoverRow");};
2106 this.rowHandler(w,theRow,prevColumns);
2107 rowCount++;
2110 this.lookaheadRegExp.lastIndex = w.nextMatch;
2111 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2114 rowHandler: function(w,e,prevColumns)
2116 var col = 0;
2117 var colSpanCount = 1;
2118 var prevCell = null;
2119 this.cellRegExp.lastIndex = w.nextMatch;
2120 var cellMatch = this.cellRegExp.exec(w.source);
2121 while(cellMatch && cellMatch.index == w.nextMatch) {
2122 if(cellMatch[1] == "~") {
2123 // Rowspan
2124 var last = prevColumns[col];
2125 if(last) {
2126 last.rowSpanCount++;
2127 last.element.setAttribute("rowspan",last.rowSpanCount);
2128 last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
2129 last.element.valign = "center";
2131 w.nextMatch = this.cellRegExp.lastIndex-1;
2132 } else if(cellMatch[1] == ">") {
2133 // Colspan
2134 colSpanCount++;
2135 w.nextMatch = this.cellRegExp.lastIndex-1;
2136 } else if(cellMatch[2]) {
2137 // End of row
2138 if(prevCell && colSpanCount > 1) {
2139 prevCell.setAttribute("colspan",colSpanCount);
2140 prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
2142 w.nextMatch = this.cellRegExp.lastIndex;
2143 break;
2144 } else {
2145 // Cell
2146 w.nextMatch++;
2147 var styles = config.formatterHelpers.inlineCssHelper(w);
2148 var spaceLeft = false;
2149 var chr = w.source.substr(w.nextMatch,1);
2150 while(chr == " ") {
2151 spaceLeft = true;
2152 w.nextMatch++;
2153 chr = w.source.substr(w.nextMatch,1);
2155 var cell;
2156 if(chr == "!") {
2157 cell = createTiddlyElement(e,"th");
2158 w.nextMatch++;
2159 } else {
2160 cell = createTiddlyElement(e,"td");
2162 prevCell = cell;
2163 prevColumns[col] = {rowSpanCount:1,element:cell};
2164 if(colSpanCount > 1) {
2165 cell.setAttribute("colspan",colSpanCount);
2166 cell.setAttribute("colSpan",colSpanCount); // Needed for IE
2167 colSpanCount = 1;
2169 config.formatterHelpers.applyCssHelper(cell,styles);
2170 w.subWikifyTerm(cell,this.cellTermRegExp);
2171 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
2172 cell.align = spaceLeft ? "center" : "left";
2173 else if(spaceLeft)
2174 cell.align = "right";
2175 w.nextMatch--;
2177 col++;
2178 this.cellRegExp.lastIndex = w.nextMatch;
2179 cellMatch = this.cellRegExp.exec(w.source);
2185 name: "heading",
2186 match: "^!{1,6}",
2187 termRegExp: /(\n)/mg,
2188 handler: function(w)
2190 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
2195 name: "list",
2196 match: "^(?:[\\*#;:]+)",
2197 lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
2198 termRegExp: /(\n)/mg,
2199 handler: function(w)
2201 var stack = [w.output];
2202 var currLevel = 0, currType = null;
2203 var listLevel, listType, itemType, baseType;
2204 w.nextMatch = w.matchStart;
2205 this.lookaheadRegExp.lastIndex = w.nextMatch;
2206 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2207 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2208 if(lookaheadMatch[1]) {
2209 listType = "ul";
2210 itemType = "li";
2211 } else if(lookaheadMatch[2]) {
2212 listType = "ol";
2213 itemType = "li";
2214 } else if(lookaheadMatch[3]) {
2215 listType = "dl";
2216 itemType = "dt";
2217 } else if(lookaheadMatch[4]) {
2218 listType = "dl";
2219 itemType = "dd";
2221 if(!baseType)
2222 baseType = listType;
2223 listLevel = lookaheadMatch[0].length;
2224 w.nextMatch += lookaheadMatch[0].length;
2225 var t;
2226 if(listLevel > currLevel) {
2227 for(t=currLevel; t<listLevel; t++) {
2228 var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
2229 stack.push(createTiddlyElement(target,listType));
2231 } else if(listType!=baseType && listLevel==1) {
2232 w.nextMatch -= lookaheadMatch[0].length;
2233 return;
2234 } else if(listLevel < currLevel) {
2235 for(t=currLevel; t>listLevel; t--)
2236 stack.pop();
2237 } else if(listLevel == currLevel && listType != currType) {
2238 stack.pop();
2239 stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
2241 currLevel = listLevel;
2242 currType = listType;
2243 var e = createTiddlyElement(stack[stack.length-1],itemType);
2244 w.subWikifyTerm(e,this.termRegExp);
2245 this.lookaheadRegExp.lastIndex = w.nextMatch;
2246 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2252 name: "quoteByBlock",
2253 match: "^<<<\\n",
2254 termRegExp: /(^<<<(\n|$))/mg,
2255 element: "blockquote",
2256 handler: config.formatterHelpers.createElementAndWikify
2260 name: "quoteByLine",
2261 match: "^>+",
2262 lookaheadRegExp: /^>+/mg,
2263 termRegExp: /(\n)/mg,
2264 element: "blockquote",
2265 handler: function(w)
2267 var stack = [w.output];
2268 var currLevel = 0;
2269 var newLevel = w.matchLength;
2270 var t;
2271 do {
2272 if(newLevel > currLevel) {
2273 for(t=currLevel; t<newLevel; t++)
2274 stack.push(createTiddlyElement(stack[stack.length-1],this.element));
2275 } else if(newLevel < currLevel) {
2276 for(t=currLevel; t>newLevel; t--)
2277 stack.pop();
2279 currLevel = newLevel;
2280 w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
2281 createTiddlyElement(stack[stack.length-1],"br");
2282 this.lookaheadRegExp.lastIndex = w.nextMatch;
2283 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2284 var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
2285 if(matched) {
2286 newLevel = lookaheadMatch[0].length;
2287 w.nextMatch += lookaheadMatch[0].length;
2289 } while(matched);
2294 name: "rule",
2295 match: "^----+$\\n?",
2296 handler: function(w)
2298 createTiddlyElement(w.output,"hr");
2303 name: "monospacedByLine",
2304 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
2305 element: "pre",
2306 handler: function(w)
2308 switch(w.matchText) {
2309 case "/*{{{*/\n": // CSS
2310 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2311 break;
2312 case "{{{\n": // monospaced block
2313 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2314 break;
2315 case "//{{{\n": // plugin
2316 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2317 break;
2318 case "<!--{{{-->\n": //template
2319 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
2320 break;
2321 default:
2322 break;
2324 config.formatterHelpers.enclosedTextHelper.call(this,w);
2329 name: "wikifyComment",
2330 match: "^(?:/\\*\\*\\*|<!---)\\n",
2331 handler: function(w)
2333 var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
2334 w.subWikifyTerm(w.output,termRegExp);
2339 name: "macro",
2340 match: "<<",
2341 lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
2342 handler: function(w)
2344 this.lookaheadRegExp.lastIndex = w.matchStart;
2345 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2346 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
2347 w.nextMatch = this.lookaheadRegExp.lastIndex;
2348 invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
2354 name: "prettyLink",
2355 match: "\\[\\[",
2356 lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
2357 handler: function(w)
2359 this.lookaheadRegExp.lastIndex = w.matchStart;
2360 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2361 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2362 var e;
2363 var text = lookaheadMatch[1];
2364 if(lookaheadMatch[3]) {
2365 // Pretty bracketted link
2366 var link = lookaheadMatch[3];
2367 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
2368 createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2369 } else {
2370 // Simple bracketted link
2371 e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
2373 createTiddlyText(e,text);
2374 w.nextMatch = this.lookaheadRegExp.lastIndex;
2380 name: "wikiLink",
2381 match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
2382 handler: function(w)
2384 if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
2385 w.outputText(w.output,w.matchStart+1,w.nextMatch);
2386 return;
2388 if(w.matchStart > 0) {
2389 var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
2390 preRegExp.lastIndex = w.matchStart-1;
2391 var preMatch = preRegExp.exec(w.source);
2392 if(preMatch.index == w.matchStart-1) {
2393 w.outputText(w.output,w.matchStart,w.nextMatch);
2394 return;
2397 if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
2398 var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
2399 w.outputText(link,w.matchStart,w.nextMatch);
2400 } else {
2401 w.outputText(w.output,w.matchStart,w.nextMatch);
2407 name: "urlLink",
2408 match: config.textPrimitives.urlPattern,
2409 handler: function(w)
2411 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
2416 name: "image",
2417 match: "\\[[<>]?[Ii][Mm][Gg]\\[",
2418 lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
2419 handler: function(w)
2421 this.lookaheadRegExp.lastIndex = w.matchStart;
2422 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2423 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2424 var e = w.output;
2425 if(lookaheadMatch[5]) {
2426 var link = lookaheadMatch[5];
2427 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2428 addClass(e,"imageLink");
2430 var img = createTiddlyElement(e,"img");
2431 if(lookaheadMatch[1])
2432 img.align = "left";
2433 else if(lookaheadMatch[2])
2434 img.align = "right";
2435 if(lookaheadMatch[3])
2436 img.title = lookaheadMatch[3];
2437 img.src = lookaheadMatch[4];
2438 w.nextMatch = this.lookaheadRegExp.lastIndex;
2444 name: "html",
2445 match: "<[Hh][Tt][Mm][Ll]>",
2446 lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
2447 handler: function(w)
2449 this.lookaheadRegExp.lastIndex = w.matchStart;
2450 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2451 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2452 createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
2453 w.nextMatch = this.lookaheadRegExp.lastIndex;
2459 name: "commentByBlock",
2460 match: "/%",
2461 lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
2462 handler: function(w)
2464 this.lookaheadRegExp.lastIndex = w.matchStart;
2465 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2466 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
2467 w.nextMatch = this.lookaheadRegExp.lastIndex;
2472 name: "characterFormat",
2473 match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
2474 handler: function(w)
2476 switch(w.matchText) {
2477 case "''":
2478 w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
2479 break;
2480 case "//":
2481 w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
2482 break;
2483 case "__":
2484 w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
2485 break;
2486 case "^^":
2487 w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
2488 break;
2489 case "~~":
2490 w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
2491 break;
2492 case "--":
2493 w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
2494 break;
2495 case "{{{":
2496 var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
2497 lookaheadRegExp.lastIndex = w.matchStart;
2498 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2499 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2500 createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
2501 w.nextMatch = lookaheadRegExp.lastIndex;
2503 break;
2509 name: "customFormat",
2510 match: "@@|\\{\\{",
2511 handler: function(w)
2513 switch(w.matchText) {
2514 case "@@":
2515 var e = createTiddlyElement(w.output,"span");
2516 var styles = config.formatterHelpers.inlineCssHelper(w);
2517 if(styles.length == 0)
2518 e.className = "marked";
2519 else
2520 config.formatterHelpers.applyCssHelper(e,styles);
2521 w.subWikifyTerm(e,/(@@)/mg);
2522 break;
2523 case "{{":
2524 lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
2525 lookaheadRegExp.lastIndex = w.matchStart;
2526 lookaheadMatch = lookaheadRegExp.exec(w.source);
2527 if(lookaheadMatch) {
2528 w.nextMatch = lookaheadRegExp.lastIndex;
2529 e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
2530 w.subWikifyTerm(e,/(\}\}\})/mg);
2532 break;
2538 name: "mdash",
2539 match: "--",
2540 handler: function(w)
2542 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
2547 name: "lineBreak",
2548 match: "\\n|<br ?/?>",
2549 handler: function(w)
2551 createTiddlyElement(w.output,"br");
2556 name: "rawText",
2557 match: "\\\"{3}|<nowiki>",
2558 lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
2559 handler: function(w)
2561 this.lookaheadRegExp.lastIndex = w.matchStart;
2562 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2563 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2564 createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
2565 w.nextMatch = this.lookaheadRegExp.lastIndex;
2571 name: "htmlEntitiesEncoding",
2572 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};)",
2573 handler: function(w)
2575 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
2581 //--
2582 //-- Wikifier
2583 //--
2585 function getParser(tiddler,format)
2587 if(tiddler) {
2588 if(!format)
2589 format = tiddler.fields["wikiformat"];
2590 var i;
2591 if(format) {
2592 for(i in config.parsers) {
2593 if(format == config.parsers[i].format)
2594 return config.parsers[i];
2596 } else {
2597 for(i in config.parsers) {
2598 if(tiddler.isTagged(config.parsers[i].formatTag))
2599 return config.parsers[i];
2603 return formatter;
2606 function wikify(source,output,highlightRegExp,tiddler)
2608 if(source && source != "") {
2609 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
2610 wikifier.subWikifyUnterm(output);
2614 function wikifyStatic(source,highlightRegExp,tiddler,format)
2616 var e = createTiddlyElement(document.body,"div");
2617 e.style.display = "none";
2618 var html = "";
2619 if(source && source != "") {
2620 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2621 wikifier.isStatic = true;
2622 wikifier.subWikifyUnterm(e);
2623 html = e.innerHTML;
2624 removeNode(e);
2626 return html;
2629 function wikifyPlain(title,theStore,limit)
2631 if(!theStore)
2632 theStore = store;
2633 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2634 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2635 } else {
2636 return "";
2640 function wikifyPlainText(text,limit,tiddler)
2642 if(limit > 0)
2643 text = text.substr(0,limit);
2644 var wikifier = new Wikifier(text,formatter,null,tiddler);
2645 return wikifier.wikifyPlain();
2648 function highlightify(source,output,highlightRegExp,tiddler)
2650 if(source && source != "") {
2651 var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
2652 wikifier.outputText(output,0,source.length);
2656 function Wikifier(source,formatter,highlightRegExp,tiddler)
2658 this.source = source;
2659 this.output = null;
2660 this.formatter = formatter;
2661 this.nextMatch = 0;
2662 this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
2663 this.highlightRegExp = highlightRegExp;
2664 this.highlightMatch = null;
2665 this.isStatic = false;
2666 if(highlightRegExp) {
2667 highlightRegExp.lastIndex = 0;
2668 this.highlightMatch = highlightRegExp.exec(source);
2670 this.tiddler = tiddler;
2673 Wikifier.prototype.wikifyPlain = function()
2675 var e = createTiddlyElement(document.body,"div");
2676 this.subWikify(e);
2677 var text = getPlainText(e);
2678 removeNode(e);
2679 return text;
2682 Wikifier.prototype.subWikify = function(output,terminator)
2684 if(terminator)
2685 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
2686 else
2687 this.subWikifyUnterm(output);
2690 Wikifier.prototype.subWikifyUnterm = function(output)
2692 var oldOutput = this.output;
2693 this.output = output;
2694 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2695 var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2696 while(formatterMatch) {
2697 // Output any text before the match
2698 if(formatterMatch.index > this.nextMatch)
2699 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2700 // Set the match parameters for the handler
2701 this.matchStart = formatterMatch.index;
2702 this.matchLength = formatterMatch[0].length;
2703 this.matchText = formatterMatch[0];
2704 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2705 for(var t=1; t<formatterMatch.length; t++) {
2706 if(formatterMatch[t]) {
2707 this.formatter.formatters[t-1].handler(this);
2708 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2709 break;
2712 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2714 if(this.nextMatch < this.source.length) {
2715 this.outputText(this.output,this.nextMatch,this.source.length);
2716 this.nextMatch = this.source.length;
2718 this.output = oldOutput;
2721 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
2723 var oldOutput = this.output;
2724 this.output = output;
2725 terminatorRegExp.lastIndex = this.nextMatch;
2726 var terminatorMatch = terminatorRegExp.exec(this.source);
2727 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2728 var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2729 while(terminatorMatch || formatterMatch) {
2730 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
2731 if(terminatorMatch.index > this.nextMatch)
2732 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
2733 this.matchText = terminatorMatch[1];
2734 this.matchLength = terminatorMatch[1].length;
2735 this.matchStart = terminatorMatch.index;
2736 this.nextMatch = this.matchStart + this.matchLength;
2737 this.output = oldOutput;
2738 return;
2740 if(formatterMatch.index > this.nextMatch)
2741 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2742 this.matchStart = formatterMatch.index;
2743 this.matchLength = formatterMatch[0].length;
2744 this.matchText = formatterMatch[0];
2745 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2746 for(var t=1; t<formatterMatch.length; t++) {
2747 if(formatterMatch[t]) {
2748 this.formatter.formatters[t-1].handler(this);
2749 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2750 break;
2753 terminatorRegExp.lastIndex = this.nextMatch;
2754 terminatorMatch = terminatorRegExp.exec(this.source);
2755 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2757 if(this.nextMatch < this.source.length) {
2758 this.outputText(this.output,this.nextMatch,this.source.length);
2759 this.nextMatch = this.source.length;
2761 this.output = oldOutput;
2764 Wikifier.prototype.outputText = function(place,startPos,endPos)
2766 while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
2767 if(this.highlightMatch.index > startPos) {
2768 createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
2769 startPos = this.highlightMatch.index;
2771 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
2772 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
2773 startPos = highlightEnd;
2774 if(startPos >= this.highlightRegExp.lastIndex)
2775 this.highlightMatch = this.highlightRegExp.exec(this.source);
2777 if(startPos < endPos) {
2778 createTiddlyText(place,this.source.substring(startPos,endPos));
2782 //--
2783 //-- Macro definitions
2784 //--
2786 config.macros.today.handler = function(place,macroName,params)
2788 var now = new Date();
2789 var text = params[0] ? now.formatString(params[0].trim()) : text = now.toLocaleString();
2790 createTiddlyElement(place,"span",null,null,text);
2793 config.macros.version.handler = function(place)
2795 createTiddlyElement(place,"span",null,null,version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
2798 config.macros.list.handler = function(place,macroName,params)
2800 var type = params[0] ? params[0] : "all";
2801 var list = document.createElement("ul");
2802 place.appendChild(list);
2803 if(this[type].prompt)
2804 createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
2805 var results;
2806 if(this[type].handler)
2807 results = this[type].handler(params);
2808 for(var t = 0; t < results.length; t++) {
2809 var li = document.createElement("li");
2810 list.appendChild(li);
2811 createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
2815 config.macros.list.all.handler = function(params)
2817 return store.reverseLookup("tags","excludeLists",false,"title");
2820 config.macros.list.missing.handler = function(params)
2822 return store.getMissingLinks();
2825 config.macros.list.orphans.handler = function(params)
2827 return store.getOrphans();
2830 config.macros.list.shadowed.handler = function(params)
2832 return store.getShadowed();
2835 config.macros.list.touched.handler = function(params)
2837 return store.getTouched();
2840 config.macros.list.filter.handler = function(params)
2842 var filter = params[1];
2843 var results = [];
2844 if(filter) {
2845 var tiddlers = store.filterTiddlers(filter);
2846 for(var t=0; t<tiddlers.length; t++)
2847 results.push(tiddlers[t].title);
2849 return results;
2852 config.macros.allTags.handler = function(place,macroName,params)
2854 var tags = store.getTags(params[0]);
2855 var ul = createTiddlyElement(place,"ul");
2856 if(tags.length == 0)
2857 createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
2858 for(var t=0; t<tags.length; t++) {
2859 var title = tags[t][0];
2860 var info = getTiddlyLinkInfo(title);
2861 var li =createTiddlyElement(ul,"li");
2862 var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
2863 btn.setAttribute("tag",title);
2864 btn.setAttribute("refresh","link");
2865 btn.setAttribute("tiddlyLink",title);
2869 config.macros.timeline.handler = function(place,macroName,params)
2871 var field = params[0] ? params[0] : "modified";
2872 var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
2873 var lastDay = "";
2874 var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
2875 var dateFormat = params[2] ? params[2] : this.dateFormat;
2876 for(var t=tiddlers.length-1; t>=last; t--) {
2877 var tiddler = tiddlers[t];
2878 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
2879 if(theDay != lastDay) {
2880 var ul = document.createElement("ul");
2881 place.appendChild(ul);
2882 createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
2883 lastDay = theDay;
2885 createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
2889 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2891 params = paramString.parseParams("name",null,true,false,true);
2892 var names = params[0]["name"];
2893 var tiddlerName = names[0];
2894 var className = names[1] ? names[1] : null;
2895 var args = params[0]["with"];
2896 var wrapper = createTiddlyElement(place,"span",null,className);
2897 if(!args) {
2898 wrapper.setAttribute("refresh","content");
2899 wrapper.setAttribute("tiddler",tiddlerName);
2901 var text = store.getTiddlerText(tiddlerName);
2902 if(text) {
2903 var stack = config.macros.tiddler.tiddlerStack;
2904 if(stack.indexOf(tiddlerName) !== -1)
2905 return;
2906 stack.push(tiddlerName);
2907 try {
2908 var n = args ? Math.min(args.length,9) : 0;
2909 for(var i=0; i<n; i++) {
2910 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
2911 text = text.replace(placeholderRE,args[i]);
2913 config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
2914 } finally {
2915 stack.pop();
2920 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
2922 wikify(text,place,null,store.getTiddler(tiddlerName));
2925 config.macros.tiddler.tiddlerStack = [];
2927 config.macros.tag.handler = function(place,macroName,params)
2929 createTagButton(place,params[0]);
2932 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2934 params = paramString.parseParams("anon",null,true,false,false);
2935 var ul = createTiddlyElement(place,"ul");
2936 var title = getParam(params,"anon","");
2937 if(title && store.tiddlerExists(title))
2938 tiddler = store.getTiddler(title);
2939 var sep = getParam(params,"sep"," ");
2940 var lingo = config.views.wikified.tag;
2941 var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
2942 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
2943 for(var t=0; t<tiddler.tags.length; t++) {
2944 createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
2945 if(t<tiddler.tags.length-1)
2946 createTiddlyText(ul,sep);
2950 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2952 params = paramString.parseParams("anon",null,true,false,false);
2953 var ul = createTiddlyElement(place,"ul");
2954 var title = getParam(params,"anon","");
2955 if(title == "" && tiddler instanceof Tiddler)
2956 title = tiddler.title;
2957 var sep = getParam(params,"sep"," ");
2958 ul.setAttribute("title",this.tooltip.format([title]));
2959 var tagged = store.getTaggedTiddlers(title);
2960 var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
2961 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
2962 for(var t=0; t<tagged.length; t++) {
2963 createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
2964 if(t<tagged.length-1)
2965 createTiddlyText(ul,sep);
2969 config.macros.closeAll.handler = function(place)
2971 createTiddlyButton(place,this.label,this.prompt,this.onClick);
2974 config.macros.closeAll.onClick = function(e)
2976 story.closeAllTiddlers();
2977 return false;
2980 config.macros.permaview.handler = function(place)
2982 createTiddlyButton(place,this.label,this.prompt,this.onClick);
2985 config.macros.permaview.onClick = function(e)
2987 story.permaView();
2988 return false;
2991 config.macros.saveChanges.handler = function(place)
2993 if(!readOnly)
2994 createTiddlyButton(place,this.label,this.prompt,this.onClick,null,null,this.accessKey);
2997 config.macros.saveChanges.onClick = function(e)
2999 saveChanges();
3000 return false;
3003 config.macros.slider.onClickSlider = function(ev)
3005 var e = ev ? ev : window.event;
3006 var n = this.nextSibling;
3007 var cookie = n.getAttribute("cookie");
3008 var isOpen = n.style.display != "none";
3009 if(config.options.chkAnimate && anim && typeof Slider == "function")
3010 anim.startAnimating(new Slider(n,!isOpen,null,"none"));
3011 else
3012 n.style.display = isOpen ? "none" : "block";
3013 config.options[cookie] = !isOpen;
3014 saveOptionCookie(cookie);
3015 return false;
3018 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
3020 var c = cookie ? cookie : "";
3021 var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
3022 var panel = createTiddlyElement(null,"div",null,"sliderPanel");
3023 panel.setAttribute("cookie",c);
3024 panel.style.display = config.options[c] ? "block" : "none";
3025 place.appendChild(panel);
3026 return panel;
3029 config.macros.slider.handler = function(place,macroName,params)
3031 var panel = this.createSlider(place,params[0],params[2],params[3]);
3032 var text = store.getTiddlerText(params[1]);
3033 panel.setAttribute("refresh","content");
3034 panel.setAttribute("tiddler",params[1]);
3035 if(text)
3036 wikify(text,panel,null,store.getTiddler(params[1]));
3039 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
3040 config.macros.gradient.handler = function(place,macroName,params,wikifier)
3042 var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
3043 panel.style.position = "relative";
3044 panel.style.overflow = "hidden";
3045 panel.style.zIndex = "0";
3046 if(wikifier) {
3047 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
3048 config.formatterHelpers.applyCssHelper(panel,styles);
3050 var colours = [];
3051 for(var t=1; t<params.length; t++) {
3052 var c = new RGB(params[t]);
3053 if(c)
3054 colours.push(c);
3056 drawGradient(panel,params[0] != "vert",colours);
3057 if(wikifier)
3058 wikifier.subWikify(panel,">>");
3059 if(document.all) {
3060 panel.style.height = "100%";
3061 panel.style.width = "100%";
3065 config.macros.message.handler = function(place,macroName,params)
3067 if(params[0]) {
3068 var m = config;
3069 var p = params[0].split(".");
3070 for(var t=0; t<p.length; t++) {
3071 if(p[t] in m)
3072 m = m[p[t]];
3073 else
3074 break;
3076 createTiddlyText(place,m.toString().format(params.splice(1)));
3080 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3082 if((tiddler instanceof Tiddler) && params[0]) {
3083 var value = store.getValue(tiddler,params[0]);
3084 if(value != undefined) {
3085 switch(params[1]) {
3086 case undefined:
3087 highlightify(value,place,highlightHack,tiddler);
3088 break;
3089 case "link":
3090 createTiddlyLink(place,value,true);
3091 break;
3092 case "wikified":
3093 wikify(value,place,highlightHack,tiddler);
3094 break;
3095 case "date":
3096 value = Date.convertFromYYYYMMDDHHMM(value);
3097 createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
3098 break;
3104 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3106 var field = params[0];
3107 var rows = params[1] || 0;
3108 var defVal = params[2] || '';
3109 if((tiddler instanceof Tiddler) && field) {
3110 story.setDirty(tiddler.title,true);
3111 var e,v;
3112 if(field != "text" && !rows) {
3113 e = createTiddlyElement(null,"input");
3114 if(tiddler.isReadOnly())
3115 e.setAttribute("readOnly","readOnly");
3116 e.setAttribute("edit",field);
3117 e.setAttribute("type","text");
3118 e.value = store.getValue(tiddler,field) || defVal;
3119 e.setAttribute("size","40");
3120 e.setAttribute("autocomplete","off");
3121 place.appendChild(e);
3122 } else {
3123 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
3124 var wrapper2 = createTiddlyElement(wrapper1,"div");
3125 e = createTiddlyElement(wrapper2,"textarea");
3126 if(tiddler.isReadOnly())
3127 e.setAttribute("readOnly","readOnly");
3128 e.value = v = store.getValue(tiddler,field) || defVal;
3129 rows = rows ? rows : 10;
3130 var lines = v.match(/\n/mg);
3131 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
3132 if(lines != null && lines.length > rows)
3133 rows = lines.length + 5;
3134 rows = Math.min(rows,maxLines);
3135 e.setAttribute("rows",rows);
3136 e.setAttribute("edit",field);
3137 place.appendChild(wrapper1);
3139 return e;
3143 config.macros.tagChooser.onClick = function(ev)
3145 var e = ev ? ev : window.event;
3146 var lingo = config.views.editor.tagChooser;
3147 var popup = Popup.create(this);
3148 var tags = store.getTags();
3149 if(tags.length == 0)
3150 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
3151 for(var t=0; t<tags.length; t++) {
3152 var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
3153 tag.setAttribute("tag",tags[t][0]);
3154 tag.setAttribute("tiddler",this.getAttribute("tiddler"));
3156 Popup.show();
3157 e.cancelBubble = true;
3158 if(e.stopPropagation) e.stopPropagation();
3159 return false;
3162 config.macros.tagChooser.onTagClick = function(ev)
3164 var e = ev ? ev : window.event;
3165 var tag = this.getAttribute("tag");
3166 var title = this.getAttribute("tiddler");
3167 if(!readOnly)
3168 story.setTiddlerTag(title,tag,0);
3169 return false;
3172 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3174 if(tiddler instanceof Tiddler) {
3175 var lingo = config.views.editor.tagChooser;
3176 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
3177 btn.setAttribute("tiddler",tiddler.title);
3181 config.macros.refreshDisplay.handler = function(place)
3183 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3186 config.macros.refreshDisplay.onClick = function(e)
3188 refreshAll();
3189 return false;
3192 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3194 var title = tiddler ? tiddler.title : null;
3195 var a = title ? config.annotations[title] : null;
3196 if(!tiddler || !title || !a)
3197 return;
3198 var text = a.format([title]);
3199 wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
3202 //--
3203 //-- NewTiddler and NewJournal macros
3204 //--
3206 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
3208 var tags = [];
3209 for(var t=1; t<params.length; t++) {
3210 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
3211 tags.push(params[t].value);
3213 label = getParam(params,"label",label);
3214 prompt = getParam(params,"prompt",prompt);
3215 accessKey = getParam(params,"accessKey",accessKey);
3216 newFocus = getParam(params,"focus",newFocus);
3217 var customFields = getParam(params,"fields","");
3218 if(!customFields && !store.isShadowTiddler(title))
3219 customFields = String.encodeHashMap(config.defaultCustomFields);
3220 var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
3221 btn.setAttribute("newTitle",title);
3222 btn.setAttribute("isJournal",isJournal ? "true" : "false");
3223 if(tags.length > 0)
3224 btn.setAttribute("params",tags.join("|"));
3225 btn.setAttribute("newFocus",newFocus);
3226 btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
3227 if(customFields !== "")
3228 btn.setAttribute("customFields",customFields);
3229 var text = getParam(params,"text");
3230 if(text !== undefined)
3231 btn.setAttribute("newText",text);
3232 return btn;
3235 config.macros.newTiddler.onClickNewTiddler = function()
3237 var title = this.getAttribute("newTitle");
3238 if(this.getAttribute("isJournal") == "true") {
3239 var now = new Date();
3240 title = now.formatString(title.trim());
3242 var params = this.getAttribute("params");
3243 var tags = params ? params.split("|") : [];
3244 var focus = this.getAttribute("newFocus");
3245 var template = this.getAttribute("newTemplate");
3246 var customFields = this.getAttribute("customFields");
3247 story.displayTiddler(null,title,template,false,null,null);
3248 var tiddlerElem = document.getElementById(story.idPrefix + title);
3249 if(customFields)
3250 story.addCustomFields(tiddlerElem,customFields);
3251 var text = this.getAttribute("newText");
3252 if(typeof text == "string")
3253 story.getTiddlerField(title,"text").value = text.format([title]);
3254 for(var t=0;t<tags.length;t++)
3255 story.setTiddlerTag(title,tags[t],+1);
3256 story.focusTiddler(title,focus);
3257 return false;
3260 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3262 if(!readOnly) {
3263 params = paramString.parseParams("anon",null,true,false,false);
3264 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
3265 title = getParam(params,"title",title);
3266 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
3270 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3272 if(!readOnly) {
3273 params = paramString.parseParams("anon",null,true,false,false);
3274 var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
3275 title = getParam(params,"title",title);
3276 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
3280 //--
3281 //-- Search macro
3282 //--
3284 config.macros.search.handler = function(place,macroName,params)
3286 var searchTimeout = null;
3287 var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick);
3288 var txt = createTiddlyElement(place,"input",null,"txtOptionInput");
3289 if(params[0])
3290 txt.value = params[0];
3291 txt.onkeyup = this.onKeyPress;
3292 txt.onfocus = this.onFocus;
3293 txt.setAttribute("size",this.sizeTextbox);
3294 txt.setAttribute("accessKey",this.accessKey);
3295 txt.setAttribute("autocomplete","off");
3296 txt.setAttribute("lastSearchText","");
3297 if(config.browser.isSafari) {
3298 txt.setAttribute("type","search");
3299 txt.setAttribute("results","5");
3300 } else {
3301 txt.setAttribute("type","text");
3305 // Global because there's only ever one outstanding incremental search timer
3306 config.macros.search.timeout = null;
3308 config.macros.search.doSearch = function(txt)
3310 if(txt.value.length > 0) {
3311 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
3312 txt.setAttribute("lastSearchText",txt.value);
3316 config.macros.search.onClick = function(e)
3318 config.macros.search.doSearch(this.nextSibling);
3319 return false;
3322 config.macros.search.onKeyPress = function(ev)
3324 var e = ev ? ev : window.event;
3325 switch(e.keyCode) {
3326 case 13: // Ctrl-Enter
3327 case 10: // Ctrl-Enter on IE PC
3328 config.macros.search.doSearch(this);
3329 break;
3330 case 27: // Escape
3331 this.value = "";
3332 clearMessage();
3333 break;
3335 if(this.value.length > 2) {
3336 if(this.value != this.getAttribute("lastSearchText")) {
3337 if(config.macros.search.timeout)
3338 clearTimeout(config.macros.search.timeout);
3339 var txt = this;
3340 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3342 } else {
3343 if(config.macros.search.timeout)
3344 clearTimeout(config.macros.search.timeout);
3348 config.macros.search.onFocus = function(e)
3350 this.select();
3353 //--
3354 //-- Tabs macro
3355 //--
3357 config.macros.tabs.handler = function(place,macroName,params)
3359 var cookie = params[0];
3360 var numTabs = (params.length-1)/3;
3361 var wrapper = createTiddlyElement(null,"div",null,cookie);
3362 var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
3363 tabset.setAttribute("cookie",cookie);
3364 var validTab = false;
3365 for(var t=0; t<numTabs; t++) {
3366 var label = params[t*3+1];
3367 var prompt = params[t*3+2];
3368 var content = params[t*3+3];
3369 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
3370 tab.setAttribute("tab",label);
3371 tab.setAttribute("content",content);
3372 tab.title = prompt;
3373 if(config.options[cookie] == label)
3374 validTab = true;
3376 if(!validTab)
3377 config.options[cookie] = params[1];
3378 place.appendChild(wrapper);
3379 this.switchTab(tabset,config.options[cookie]);
3382 config.macros.tabs.onClickTab = function(e)
3384 config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
3385 return false;
3388 config.macros.tabs.switchTab = function(tabset,tab)
3390 var cookie = tabset.getAttribute("cookie");
3391 var theTab = null;
3392 var nodes = tabset.childNodes;
3393 for(var t=0; t<nodes.length; t++) {
3394 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
3395 theTab = nodes[t];
3396 theTab.className = "tab tabSelected";
3397 } else {
3398 nodes[t].className = "tab tabUnselected";
3401 if(theTab) {
3402 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
3403 removeNode(tabset.nextSibling);
3404 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
3405 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
3406 var contentTitle = theTab.getAttribute("content");
3407 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
3408 if(cookie) {
3409 config.options[cookie] = tab;
3410 saveOptionCookie(cookie);
3415 //--
3416 //-- Tiddler toolbar
3417 //--
3419 // Create a toolbar command button
3420 config.macros.toolbar.createCommand = function(place,commandName,tiddler,theClass)
3422 if(typeof commandName != "string") {
3423 var c = null;
3424 for(var t in config.commands) {
3425 if(config.commands[t] == commandName)
3426 c = t;
3428 commandName = c;
3430 if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
3431 var command = config.commands[commandName];
3432 if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
3433 var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
3434 var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
3435 var cmd;
3436 switch(command.type) {
3437 case "popup":
3438 cmd = this.onClickPopup;
3439 break;
3440 case "command":
3441 default:
3442 cmd = this.onClickCommand;
3443 break;
3445 var btn = createTiddlyButton(null,text,tooltip,cmd);
3446 btn.setAttribute("commandName",commandName);
3447 btn.setAttribute("tiddler",tiddler.title);
3448 if(theClass)
3449 addClass(btn,theClass);
3450 place.appendChild(btn);
3455 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
3457 var title = tiddler.title;
3458 var ro = tiddler.isReadOnly();
3459 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
3460 return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
3463 config.macros.toolbar.getCommandText = function(command,tiddler)
3465 return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
3468 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
3470 return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
3473 config.macros.toolbar.onClickCommand = function(ev)
3475 var e = ev ? ev : window.event;
3476 e.cancelBubble = true;
3477 if (e.stopPropagation) e.stopPropagation();
3478 var command = config.commands[this.getAttribute("commandName")];
3479 return command.handler(e,this,this.getAttribute("tiddler"));
3482 config.macros.toolbar.onClickPopup = function(ev)
3484 var e = ev ? ev : window.event;
3485 e.cancelBubble = true;
3486 if (e.stopPropagation) e.stopPropagation();
3487 var popup = Popup.create(this);
3488 var command = config.commands[this.getAttribute("commandName")];
3489 var title = this.getAttribute("tiddler");
3490 var tiddler = store.fetchTiddler(title);
3491 popup.setAttribute("tiddler",title);
3492 command.handlePopup(popup,title);
3493 Popup.show();
3494 return false;
3497 // Invoke the first command encountered from a given place that is tagged with a specified class
3498 config.macros.toolbar.invokeCommand = function(place,theClass,event)
3500 var children = place.getElementsByTagName("a");
3501 for(var t=0; t<children.length; t++) {
3502 var c = children[t];
3503 if(hasClass(c,theClass) && c.getAttribute && c.getAttribute("commandName")) {
3504 if(c.onclick instanceof Function)
3505 c.onclick.call(c,event);
3506 break;
3511 config.macros.toolbar.onClickMore = function(ev)
3513 var e = this.nextSibling;
3514 e.style.display = "inline";
3515 removeNode(this);
3516 return false;
3519 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3521 for(var t=0; t<params.length; t++) {
3522 var c = params[t];
3523 switch(c) {
3524 case '>':
3525 var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
3526 addClass(btn,"moreCommand");
3527 var e = createTiddlyElement(place,"span",null,"moreCommand");
3528 e.style.display = "none";
3529 place = e;
3530 break;
3531 default:
3532 var theClass = "";
3533 switch(c.substr(0,1)) {
3534 case "+":
3535 theClass = "defaultCommand";
3536 c = c.substr(1);
3537 break;
3538 case "-":
3539 theClass = "cancelCommand";
3540 c = c.substr(1);
3541 break;
3543 if(c in config.commands)
3544 this.createCommand(place,c,tiddler,theClass);
3545 break;
3550 //--
3551 //-- Menu and toolbar commands
3552 //--
3554 config.commands.closeTiddler.handler = function(event,src,title)
3556 story.closeTiddler(title,true);
3557 return false;
3560 config.commands.closeOthers.handler = function(event,src,title)
3562 story.closeAllTiddlers(title);
3563 return false;
3566 config.commands.editTiddler.handler = function(event,src,title)
3568 clearMessage();
3569 var tiddlerElem = document.getElementById(story.idPrefix + title);
3570 var fields = tiddlerElem.getAttribute("tiddlyFields");
3571 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
3572 story.focusTiddler(title,"text");
3573 return false;
3576 config.commands.saveTiddler.handler = function(event,src,title)
3578 var newTitle = story.saveTiddler(title,event.shiftKey);
3579 if(newTitle)
3580 story.displayTiddler(null,newTitle);
3581 return false;
3584 config.commands.cancelTiddler.handler = function(event,src,title)
3586 if(story.hasChanges(title) && !readOnly) {
3587 if(!confirm(this.warning.format([title])))
3588 return false;
3590 story.setDirty(title,false);
3591 story.displayTiddler(null,title);
3592 return false;
3595 config.commands.deleteTiddler.handler = function(event,src,title)
3597 var deleteIt = true;
3598 if (config.options.chkConfirmDelete)
3599 deleteIt = confirm(this.warning.format([title]));
3600 if (deleteIt) {
3601 store.removeTiddler(title);
3602 story.closeTiddler(title,true);
3603 autoSaveChanges();
3605 return false;
3608 config.commands.permalink.handler = function(event,src,title)
3610 var t = encodeURIComponent(String.encodeTiddlyLink(title));
3611 if(window.location.hash != t)
3612 window.location.hash = t;
3613 return false;
3616 config.commands.references.handlePopup = function(popup,title)
3618 var references = store.getReferringTiddlers(title);
3619 var c = false;
3620 for(var r=0; r<references.length; r++) {
3621 if(references[r].title != title && !references[r].isTagged("excludeLists")) {
3622 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
3623 c = true;
3626 if(!c)
3627 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
3630 config.commands.jump.handlePopup = function(popup,title)
3632 story.forEachTiddler(function(title,element) {
3633 createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
3637 config.commands.syncing.handlePopup = function(popup,title)
3639 var tiddler = store.fetchTiddler(title);
3640 if(!tiddler)
3641 return;
3642 var serverType = tiddler.getServerType();
3643 var serverHost = tiddler.fields['server.host'];
3644 var serverWorkspace = tiddler.fields['server.workspace'];
3645 if(!serverWorkspace)
3646 serverWorkspace = "";
3647 if(serverType) {
3648 var e = createTiddlyElement(popup,"li",null,"popupMessage");
3649 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3650 } else {
3651 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
3653 if(serverType) {
3654 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3655 var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
3656 btn.setAttribute("tiddler",title);
3657 btn.setAttribute("server.type","");
3659 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3660 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
3661 var feeds = store.getTaggedTiddlers("systemServer","title");
3662 for(var t=0; t<feeds.length; t++) {
3663 var f = feeds[t];
3664 var feedServerType = store.getTiddlerSlice(f.title,"Type");
3665 if(!feedServerType)
3666 feedServerType = "file";
3667 var feedServerHost = store.getTiddlerSlice(f.title,"URL");
3668 if(!feedServerHost)
3669 feedServerHost = "";
3670 var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
3671 if(!feedServerWorkspace)
3672 feedServerWorkspace = "";
3673 var caption = f.title;
3674 if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
3675 caption = config.commands.syncing.currServerMarker + caption;
3676 } else {
3677 caption = config.commands.syncing.notCurrServerMarker + caption;
3679 btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
3680 btn.setAttribute("tiddler",title);
3681 btn.setAttribute("server.type",feedServerType);
3682 btn.setAttribute("server.host",feedServerHost);
3683 btn.setAttribute("server.workspace",feedServerWorkspace);
3687 config.commands.syncing.onChooseServer = function(e)
3689 var tiddler = this.getAttribute("tiddler");
3690 var serverType = this.getAttribute("server.type");
3691 if(serverType) {
3692 store.addTiddlerFields(tiddler,{
3693 'server.type': serverType,
3694 'server.host': this.getAttribute("server.host"),
3695 'server.workspace': this.getAttribute("server.workspace")
3697 } else {
3698 store.setValue(tiddler,'server',null);
3700 return false;
3703 config.commands.fields.handlePopup = function(popup,title)
3705 var tiddler = store.fetchTiddler(title);
3706 if(!tiddler)
3707 return;
3708 var fields = {};
3709 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
3710 var items = [];
3711 for(var t in fields) {
3712 items.push({field: t,value: fields[t]});
3714 items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
3715 if(items.length > 0)
3716 ListView.create(popup,items,this.listViewTemplate);
3717 else
3718 createTiddlyElement(popup,"div",null,null,this.emptyText);
3721 //--
3722 //-- Tiddler() object
3723 //--
3725 function Tiddler(title)
3727 this.title = title;
3728 this.text = null;
3729 this.modifier = null;
3730 this.modified = new Date();
3731 this.created = new Date();
3732 this.links = [];
3733 this.linksUpdated = false;
3734 this.tags = [];
3735 this.fields = {};
3736 return this;
3739 Tiddler.prototype.getLinks = function()
3741 if(this.linksUpdated==false)
3742 this.changed();
3743 return this.links;
3746 // Returns the fields that are inherited in string field:"value" field2:"value2" format
3747 Tiddler.prototype.getInheritedFields = function()
3749 var f = {};
3750 for(i in this.fields) {
3751 if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
3752 f[i] = this.fields[i];
3755 return String.encodeHashMap(f);
3758 // Increment the changeCount of a tiddler
3759 Tiddler.prototype.incChangeCount = function()
3761 var c = this.fields['changecount'];
3762 c = c ? parseInt(c) : 0;
3763 this.fields['changecount'] = String(c+1);
3766 // Clear the changeCount of a tiddler
3767 Tiddler.prototype.clearChangeCount = function()
3769 if(this.fields['changecount']) {
3770 delete this.fields['changecount'];
3774 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
3775 Tiddler.prototype.isTouched = function()
3777 var changeCount = this.fields['changecount'];
3778 if(changeCount === undefined)
3779 changeCount = 0;
3780 return changeCount > 0;
3783 // Return the tiddler as an RSS item
3784 Tiddler.prototype.toRssItem = function(uri)
3786 var s = [];
3787 s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
3788 s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
3789 for(var t=0; t<this.tags.length; t++)
3790 s.push("<category>" + this.tags[t] + "</category>");
3791 s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
3792 s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
3793 return s.join("\n");
3796 // Format the text for storage in an RSS item
3797 Tiddler.prototype.saveToRss = function(uri)
3799 return "<item>\n" + this.toRssItem(uri) + "\n</item>";
3802 // Change the text and other attributes of a tiddler
3803 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
3805 this.assign(title,text,modifier,modified,tags,created,fields);
3806 this.changed();
3807 return this;
3810 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
3811 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
3813 if(title != undefined)
3814 this.title = title;
3815 if(text != undefined)
3816 this.text = text;
3817 if(modifier != undefined)
3818 this.modifier = modifier;
3819 if(modified != undefined)
3820 this.modified = modified;
3821 if(created != undefined)
3822 this.created = created;
3823 if(fields != undefined)
3824 this.fields = fields;
3825 if(tags != undefined)
3826 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
3827 else if(this.tags == undefined)
3828 this.tags = [];
3829 return this;
3832 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
3833 Tiddler.prototype.getTags = function()
3835 return String.encodeTiddlyLinkList(this.tags);
3838 // Test if a tiddler carries a tag
3839 Tiddler.prototype.isTagged = function(tag)
3841 return this.tags.indexOf(tag) != -1;
3844 // Static method to convert "\n" to newlines, "\s" to "\"
3845 Tiddler.unescapeLineBreaks = function(text)
3847 return text ? text.unescapeLineBreaks() : "";
3850 // Convert newlines to "\n", "\" to "\s"
3851 Tiddler.prototype.escapeLineBreaks = function()
3853 return this.text.escapeLineBreaks();
3856 // Updates the secondary information (like links[] array) after a change to a tiddler
3857 Tiddler.prototype.changed = function()
3859 this.links = [];
3860 var t = this.autoLinkWikiWords() ? 0 : 1;
3861 var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
3862 tiddlerLinkRegExp.lastIndex = 0;
3863 var formatMatch = tiddlerLinkRegExp.exec(this.text);
3864 while(formatMatch) {
3865 var lastIndex = tiddlerLinkRegExp.lastIndex;
3866 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
3867 // wikiWordLink
3868 if(formatMatch.index > 0) {
3869 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
3870 preRegExp.lastIndex = formatMatch.index-1;
3871 var preMatch = preRegExp.exec(this.text);
3872 if(preMatch.index != formatMatch.index-1)
3873 this.links.pushUnique(formatMatch[1]);
3874 } else {
3875 this.links.pushUnique(formatMatch[1]);
3878 else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
3879 this.links.pushUnique(formatMatch[3-t]);
3880 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
3881 this.links.pushUnique(formatMatch[4-t]);
3882 tiddlerLinkRegExp.lastIndex = lastIndex;
3883 formatMatch = tiddlerLinkRegExp.exec(this.text);
3885 this.linksUpdated = true;
3888 Tiddler.prototype.getSubtitle = function()
3890 var modifier = this.modifier;
3891 if(!modifier)
3892 modifier = config.messages.subtitleUnknown;
3893 var modified = this.modified;
3894 if(modified)
3895 modified = modified.toLocaleString();
3896 else
3897 modified = config.messages.subtitleUnknown;
3898 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
3901 Tiddler.prototype.isReadOnly = function()
3903 return readOnly;
3906 Tiddler.prototype.autoLinkWikiWords = function()
3908 return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
3911 Tiddler.prototype.generateFingerprint = function()
3913 return "0x" + Crypto.hexSha1Str(this.text);
3916 Tiddler.prototype.getServerType = function()
3918 var serverType = null;
3919 if(this.fields && this.fields['server.type'])
3920 serverType = this.fields['server.type'];
3921 if(!serverType)
3922 serverType = this.fields['wikiformat'];
3923 if(serverType && !config.adaptors[serverType])
3924 serverType = null;
3925 return serverType;
3928 Tiddler.prototype.getAdaptor = function()
3930 var serverType = this.getServerType();
3931 return serverType ? new config.adaptors[serverType] : null;
3934 //--
3935 //-- TiddlyWiki() object contains Tiddler()s
3936 //--
3938 function TiddlyWiki()
3940 var tiddlers = {}; // Hashmap by name of tiddlers
3941 this.tiddlersUpdated = false;
3942 this.namedNotifications = []; // Array of {name:,notify:} of notification functions
3943 this.notificationLevel = 0;
3944 this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
3945 this.clear = function() {
3946 tiddlers = {};
3947 this.setDirty(false);
3949 this.fetchTiddler = function(title) {
3950 var t = tiddlers[title];
3951 return t instanceof Tiddler ? t : null;
3953 this.deleteTiddler = function(title) {
3954 delete this.slices[title];
3955 delete tiddlers[title];
3957 this.addTiddler = function(tiddler) {
3958 delete this.slices[tiddler.title];
3959 tiddlers[tiddler.title] = tiddler;
3961 this.forEachTiddler = function(callback) {
3962 for(var t in tiddlers) {
3963 var tiddler = tiddlers[t];
3964 if(tiddler instanceof Tiddler)
3965 callback.call(this,t,tiddler);
3970 TiddlyWiki.prototype.setDirty = function(dirty)
3972 this.dirty = dirty;
3975 TiddlyWiki.prototype.isDirty = function()
3977 return this.dirty;
3980 TiddlyWiki.prototype.suspendNotifications = function()
3982 this.notificationLevel--;
3985 TiddlyWiki.prototype.resumeNotifications = function()
3987 this.notificationLevel++;
3990 // Invoke the notification handlers for a particular tiddler
3991 TiddlyWiki.prototype.notify = function(title,doBlanket)
3993 if(!this.notificationLevel) {
3994 for(var t=0; t<this.namedNotifications.length; t++) {
3995 var n = this.namedNotifications[t];
3996 if((n.name == null && doBlanket) || (n.name == title))
3997 n.notify(title);
4002 // Invoke the notification handlers for all tiddlers
4003 TiddlyWiki.prototype.notifyAll = function()
4005 if(!this.notificationLevel) {
4006 for(var t=0; t<this.namedNotifications.length; t++) {
4007 var n = this.namedNotifications[t];
4008 if(n.name)
4009 n.notify(n.name);
4014 // Add a notification handler to a tiddler
4015 TiddlyWiki.prototype.addNotification = function(title,fn)
4017 for(var i=0; i<this.namedNotifications.length; i++) {
4018 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
4019 return this;
4021 this.namedNotifications.push({name: title, notify: fn});
4022 return this;
4025 TiddlyWiki.prototype.removeTiddler = function(title)
4027 var tiddler = this.fetchTiddler(title);
4028 if(tiddler) {
4029 this.deleteTiddler(title);
4030 this.notify(title,true);
4031 this.setDirty(true);
4035 TiddlyWiki.prototype.tiddlerExists = function(title)
4037 var t = this.fetchTiddler(title);
4038 return t != undefined;
4041 TiddlyWiki.prototype.isShadowTiddler = function(title)
4043 return typeof config.shadowTiddlers[title] == "string";
4046 TiddlyWiki.prototype.getTiddler = function(title)
4048 var t = this.fetchTiddler(title);
4049 if(t != undefined)
4050 return t;
4051 else
4052 return null;
4055 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
4057 if(!title)
4058 return defaultText;
4059 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
4060 var section = null;
4061 if(pos != -1) {
4062 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
4063 title = title.substr(0,pos);
4065 pos = title.indexOf(config.textPrimitives.sliceSeparator);
4066 if(pos != -1) {
4067 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
4068 if(slice)
4069 return slice;
4071 var tiddler = this.fetchTiddler(title);
4072 if(tiddler) {
4073 if(!section)
4074 return tiddler.text;
4075 var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + ")","mg");
4076 re.lastIndex = 0;
4077 var match = re.exec(tiddler.text);
4078 if(match) {
4079 var t = tiddler.text.substr(match.index+match[1].length);
4080 var re2 = /^!/mg;
4081 re2.lastIndex = 0;
4082 match = re2.exec(t); //# search for the next heading
4083 if(match)
4084 t = t.substr(0,match.index);
4085 return t;
4087 return defaultText;
4089 if(this.isShadowTiddler(title))
4090 return config.shadowTiddlers[title];
4091 if(defaultText != undefined)
4092 return defaultText;
4093 return null;
4096 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
4098 // @internal
4099 TiddlyWiki.prototype.calcAllSlices = function(title)
4101 var slices = {};
4102 var text = this.getTiddlerText(title,"");
4103 this.slicesRE.lastIndex = 0;
4104 do {
4105 var m = this.slicesRE.exec(text);
4106 if(m) {
4107 if(m[1])
4108 slices[m[1]] = m[2];
4109 else
4110 slices[m[3]] = m[4];
4112 } while(m);
4113 return slices;
4116 // Returns the slice of text of the given name
4117 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4119 var slices = this.slices[title];
4120 if(!slices) {
4121 slices = this.calcAllSlices(title);
4122 this.slices[title] = slices;
4124 return slices[sliceName];
4127 // Build an hashmap of the specified named slices of a tiddler
4128 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
4130 var r = {};
4131 for(var t=0; t<sliceNames.length; t++) {
4132 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4133 if(slice)
4134 r[sliceNames[t]] = slice;
4136 return r;
4139 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4141 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
4142 var text = this.getTiddlerText(title,null);
4143 if(text == null)
4144 return defaultText;
4145 var textOut = [];
4146 var lastPos = 0;
4147 do {
4148 var match = bracketRegExp.exec(text);
4149 if(match) {
4150 textOut.push(text.substr(lastPos,match.index-lastPos));
4151 if(match[1]) {
4152 if(depth <= 0)
4153 textOut.push(match[1]);
4154 else
4155 textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
4157 lastPos = match.index + match[0].length;
4158 } else {
4159 textOut.push(text.substr(lastPos));
4161 } while(match);
4162 return textOut.join("");
4165 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4167 var tiddler = this.fetchTiddler(title);
4168 if(tiddler) {
4169 var t = tiddler.tags.indexOf(tag);
4170 if(t != -1)
4171 tiddler.tags.splice(t,1);
4172 if(status)
4173 tiddler.tags.push(tag);
4174 tiddler.changed();
4175 this.incChangeCount(title);
4176 this.notify(title,true);
4177 this.setDirty(true);
4181 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
4183 var tiddler = this.fetchTiddler(title);
4184 if(!tiddler)
4185 return;
4186 merge(tiddler.fields,fields);
4187 tiddler.changed();
4188 this.incChangeCount(title);
4189 this.notify(title,true);
4190 this.setDirty(true);
4193 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
4195 var tiddler = this.fetchTiddler(title);
4196 if(tiddler) {
4197 created = created ? created : tiddler.created; // Preserve created date
4198 this.deleteTiddler(title);
4199 } else {
4200 created = created ? created : modified;
4201 tiddler = new Tiddler();
4203 tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
4204 this.addTiddler(tiddler);
4205 if(clearChangeCount)
4206 tiddler.clearChangeCount();
4207 else
4208 tiddler.incChangeCount();
4209 if(title != newTitle)
4210 this.notify(title,true);
4211 this.notify(newTitle,true);
4212 this.setDirty(true);
4213 return tiddler;
4216 // Reset the sync status of a freshly synced tiddler
4217 TiddlyWiki.prototype.resetTiddler = function(title)
4219 var tiddler = this.fetchTiddler(title);
4220 if(tiddler) {
4221 tiddler.clearChangeCount();
4222 this.notify(title,true);
4223 this.setDirty(true);
4227 TiddlyWiki.prototype.incChangeCount = function(title)
4229 var tiddler = this.fetchTiddler(title);
4230 if(tiddler)
4231 tiddler.incChangeCount();
4234 TiddlyWiki.prototype.createTiddler = function(title)
4236 var tiddler = this.fetchTiddler(title);
4237 if(!tiddler) {
4238 tiddler = new Tiddler();
4239 tiddler.title = title;
4240 this.addTiddler(tiddler);
4241 this.setDirty(true);
4243 return tiddler;
4246 // Load contents of a TiddlyWiki from an HTML DIV
4247 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
4249 this.idPrefix = idPrefix;
4250 var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
4251 if(!storeElem)
4252 return;
4253 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4254 this.setDirty(false);
4255 if(!noUpdate) {
4256 for(var i = 0;i<tiddlers.length; i++)
4257 tiddlers[i].changed();
4261 // Load contents of a TiddlyWiki from a string
4262 // Returns null if there's an error
4263 TiddlyWiki.prototype.importTiddlyWiki = function(text)
4265 var posDiv = locateStoreArea(text);
4266 if(!posDiv)
4267 return null;
4268 var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
4269 // Create the iframe
4270 var iframe = document.createElement("iframe");
4271 iframe.style.display = "none";
4272 document.body.appendChild(iframe);
4273 var doc = iframe.document;
4274 if(iframe.contentDocument)
4275 doc = iframe.contentDocument; // For NS6
4276 else if(iframe.contentWindow)
4277 doc = iframe.contentWindow.document; // For IE5.5 and IE6
4278 // Put the content in the iframe
4279 doc.open();
4280 doc.writeln(content);
4281 doc.close();
4282 // Load the content into a TiddlyWiki() object
4283 var storeArea = doc.getElementById("storeArea");
4284 this.loadFromDiv(storeArea,"store");
4285 // Get rid of the iframe
4286 iframe.parentNode.removeChild(iframe);
4287 return this;
4290 TiddlyWiki.prototype.updateTiddlers = function()
4292 this.tiddlersUpdated = true;
4293 this.forEachTiddler(function(title,tiddler) {
4294 tiddler.changed();
4298 // Return all tiddlers formatted as an HTML string
4299 TiddlyWiki.prototype.allTiddlersAsHtml = function()
4301 return store.getSaver().externalize(store);
4304 // Return an array of tiddlers matching a search regular expression
4305 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
4307 var candidates = this.reverseLookup("tags",excludeTag,!!match);
4308 var results = [];
4309 for(var t=0; t<candidates.length; t++) {
4310 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
4311 results.push(candidates[t]);
4313 if(!sortField)
4314 sortField = "title";
4315 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4316 return results;
4319 // Returns a list of all tags in use
4320 // excludeTag - if present, excludes tags that are themselves tagged with excludeTag
4321 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
4322 TiddlyWiki.prototype.getTags = function(excludeTag)
4324 var results = [];
4325 this.forEachTiddler(function(title,tiddler) {
4326 for(var g=0; g<tiddler.tags.length; g++) {
4327 var tag = tiddler.tags[g];
4328 var n = true;
4329 for(var c=0; c<results.length; c++) {
4330 if(results[c][0] == tag) {
4331 n = false;
4332 results[c][1]++;
4335 if(n && excludeTag) {
4336 var t = store.fetchTiddler(tag);
4337 if(t && t.isTagged(excludeTag))
4338 n = false;
4340 if(n)
4341 results.push([tag,1]);
4344 results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
4345 return results;
4348 // Return an array of the tiddlers that are tagged with a given tag
4349 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
4351 return this.reverseLookup("tags",tag,true,sortField);
4354 // Return an array of the tiddlers that link to a given tiddler
4355 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
4357 if(!this.tiddlersUpdated)
4358 this.updateTiddlers();
4359 return this.reverseLookup("links",title,true,sortField);
4362 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
4363 // lookupMatch == true to match tiddlers, false to exclude tiddlers
4364 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
4366 var results = [];
4367 this.forEachTiddler(function(title,tiddler) {
4368 var f = !lookupMatch;
4369 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
4370 if(tiddler[lookupField][lookup] == lookupValue)
4371 f = lookupMatch;
4373 if(f)
4374 results.push(tiddler);
4376 if(!sortField)
4377 sortField = "title";
4378 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4379 return results;
4382 // Return the tiddlers as a sorted array
4383 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4385 var results = [];
4386 this.forEachTiddler(function(title,tiddler) {
4387 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4388 results.push(tiddler);
4390 if(field)
4391 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
4392 return results;
4395 // Return array of names of tiddlers that are referred to but not defined
4396 TiddlyWiki.prototype.getMissingLinks = function(sortField)
4398 if(!this.tiddlersUpdated)
4399 this.updateTiddlers();
4400 var results = [];
4401 this.forEachTiddler(function (title,tiddler) {
4402 if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
4403 return;
4404 for(var n=0; n<tiddler.links.length;n++) {
4405 var link = tiddler.links[n];
4406 if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
4407 results.pushUnique(link);
4410 results.sort();
4411 return results;
4414 // Return an array of names of tiddlers that are defined but not referred to
4415 TiddlyWiki.prototype.getOrphans = function()
4417 var results = [];
4418 this.forEachTiddler(function (title,tiddler) {
4419 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
4420 results.push(title);
4422 results.sort();
4423 return results;
4426 // Return an array of names of all the shadow tiddlers
4427 TiddlyWiki.prototype.getShadowed = function()
4429 var results = [];
4430 for(var t in config.shadowTiddlers) {
4431 if(typeof config.shadowTiddlers[t] == "string")
4432 results.push(t);
4434 results.sort();
4435 return results;
4438 // Return an array of tiddlers that have been touched since they were downloaded or created
4439 TiddlyWiki.prototype.getTouched = function()
4441 var results = [];
4442 this.forEachTiddler(function(title,tiddler) {
4443 if(tiddler.isTouched())
4444 results.push(tiddler);
4446 results.sort();
4447 return results;
4450 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
4451 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
4453 var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
4454 return t instanceof Tiddler ? t : null;
4457 TiddlyWiki.prototype.getLoader = function()
4459 if(!this.loader)
4460 this.loader = new TW21Loader();
4461 return this.loader;
4464 TiddlyWiki.prototype.getSaver = function()
4466 if(!this.saver)
4467 this.saver = new TW21Saver();
4468 return this.saver;
4471 // Filter a list of tiddlers
4472 TiddlyWiki.prototype.filterTiddlers = function(filter)
4474 var results = [];
4475 if(filter) {
4476 var tiddler;
4477 var re = /([^ \[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4478 var match = re.exec(filter);
4479 while(match) {
4480 if(match[1] || match[4]) {
4481 var title = match[1] ? match[1] : match[4];
4482 tiddler = this.fetchTiddler(title);
4483 if(tiddler) {
4484 results.pushUnique(tiddler);
4485 } else if(store.isShadowTiddler(title)) {
4486 tiddler = new Tiddler();
4487 tiddler.set(title,store.getTiddlerText(title));
4488 results.pushUnique(tiddler);
4490 } else if(match[2]) {
4491 switch(match[2]) {
4492 case "tag":
4493 this.forEachTiddler(function(title,tiddler) {
4494 if(tiddler.isTagged(match[3]))
4495 results.pushUnique(tiddler);
4497 break;
4498 case "sort":
4499 results = this.sortTiddlers(results,match[3]);
4500 break;
4503 match = re.exec(filter);
4506 return results;
4509 // Sort a list of tiddlers
4510 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4512 var asc = +1;
4513 switch(field.substr(0,1)) {
4514 case "-":
4515 asc = -1;
4516 // Note: this fall-through is intentional
4517 case "+":
4518 field = field.substr(1);
4519 break;
4521 if(TiddlyWiki.standardFieldAccess[field])
4522 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4523 else
4524 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
4525 return tiddlers;
4527 // Returns true if path is a valid field name (path),
4528 // i.e. a sequence of identifiers, separated by '.'
4529 TiddlyWiki.isValidFieldName = function(name)
4531 var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
4532 return match && (match[0] == name);
4535 // Throws an exception when name is not a valid field name.
4536 TiddlyWiki.checkFieldName = function(name)
4538 if(!TiddlyWiki.isValidFieldName(name))
4539 throw config.messages.invalidFieldName.format([name]);
4542 function StringFieldAccess(n,readOnly)
4544 this.set = readOnly ?
4545 function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
4546 function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
4547 this.get = function(t) {return t[n];};
4550 function DateFieldAccess(n)
4552 this.set = function(t,v) {
4553 var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
4554 if(d != t[n]) {
4555 t[n] = d; return true;
4558 this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
4561 function LinksFieldAccess(n)
4563 this.set = function(t,v) {
4564 var s = (typeof v == "string") ? v.readBracketedList() : v;
4565 if(s.toString() != t[n].toString()) {
4566 t[n] = s; return true;
4569 this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
4572 TiddlyWiki.standardFieldAccess = {
4573 // The set functions return true when setting the data has changed the value.
4574 "title": new StringFieldAccess("title",true),
4575 // Handle the "tiddler" field name as the title
4576 "tiddler": new StringFieldAccess("title",true),
4577 "text": new StringFieldAccess("text"),
4578 "modifier": new StringFieldAccess("modifier"),
4579 "modified": new DateFieldAccess("modified"),
4580 "created": new DateFieldAccess("created"),
4581 "tags": new LinksFieldAccess("tags")
4584 TiddlyWiki.isStandardField = function(name)
4586 return TiddlyWiki.standardFieldAccess[name] != undefined;
4589 // Sets the value of the given field of the tiddler to the value.
4590 // Setting an ExtendedField's value to null or undefined removes the field.
4591 // Setting a namespace to undefined removes all fields of that namespace.
4592 // The fieldName is case-insensitive.
4593 // All values will be converted to a string value.
4594 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
4596 TiddlyWiki.checkFieldName(fieldName);
4597 var t = this.resolveTiddler(tiddler);
4598 if(!t)
4599 return;
4600 fieldName = fieldName.toLowerCase();
4601 var isRemove = (value === undefined) || (value === null);
4602 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4603 if(accessor) {
4604 if(isRemove)
4605 // don't remove StandardFields
4606 return;
4607 var h = TiddlyWiki.standardFieldAccess[fieldName];
4608 if(!h.set(t,value))
4609 return;
4610 } else {
4611 var oldValue = t.fields[fieldName];
4612 if(isRemove) {
4613 if(oldValue !== undefined) {
4614 // deletes a single field
4615 delete t.fields[fieldName];
4616 } else {
4617 // no concrete value is defined for the fieldName
4618 // so we guess this is a namespace path.
4619 // delete all fields in a namespace
4620 var re = new RegExp('^'+fieldName+'\\.');
4621 var dirty = false;
4622 for(var n in t.fields) {
4623 if(n.match(re)) {
4624 delete t.fields[n];
4625 dirty = true;
4628 if(!dirty)
4629 return;
4631 } else {
4632 // the "normal" set case. value is defined (not null/undefined)
4633 // For convenience provide a nicer conversion Date->String
4634 value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
4635 if(oldValue == value)
4636 return;
4637 t.fields[fieldName] = value;
4640 // When we are here the tiddler/store really was changed.
4641 this.notify(t.title,true);
4642 if(!fieldName.match(/^temp\./))
4643 this.setDirty(true);
4646 // Returns the value of the given field of the tiddler.
4647 // The fieldName is case-insensitive.
4648 // Will only return String values (or undefined).
4649 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
4651 var t = this.resolveTiddler(tiddler);
4652 if(!t)
4653 return undefined;
4654 fieldName = fieldName.toLowerCase();
4655 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4656 if(accessor) {
4657 return accessor.get(t);
4659 return t.fields[fieldName];
4662 // Calls the callback function for every field in the tiddler.
4663 // When callback function returns a non-false value the iteration stops
4664 // and that value is returned.
4665 // The order of the fields is not defined.
4666 // @param callback a function(tiddler,fieldName,value).
4667 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
4669 var t = this.resolveTiddler(tiddler);
4670 if(!t)
4671 return undefined;
4672 var n,result;
4673 for(n in t.fields) {
4674 result = callback(t,n,t.fields[n]);
4675 if(result)
4676 return result;
4678 if(onlyExtendedFields)
4679 return undefined;
4680 for(n in TiddlyWiki.standardFieldAccess) {
4681 if(n == "tiddler")
4682 // even though the "title" field can also be referenced through the name "tiddler"
4683 // we only visit this field once.
4684 continue;
4685 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4686 if(result)
4687 return result;
4689 return undefined;
4692 //--
4693 //-- Story functions
4694 //--
4696 function Story(container,idPrefix)
4698 this.container = container;
4699 this.idPrefix = idPrefix;
4700 this.highlightRegExp = null;
4703 Story.prototype.forEachTiddler = function(fn)
4705 var place = document.getElementById(this.container);
4706 if(!place)
4707 return;
4708 var e = place.firstChild;
4709 while(e) {
4710 var n = e.nextSibling;
4711 var title = e.getAttribute("tiddler");
4712 fn.call(this,title,e);
4713 e = n;
4717 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
4719 for(var t = titles.length-1;t>=0;t--)
4720 this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
4723 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle)
4725 var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
4726 var place = document.getElementById(this.container);
4727 var tiddlerElem = document.getElementById(this.idPrefix + title);
4728 if(tiddlerElem) {
4729 if(toggle)
4730 this.closeTiddler(title,true);
4731 else
4732 this.refreshTiddler(title,template,false,customFields);
4733 } else {
4734 var before = this.positionTiddler(srcElement);
4735 tiddlerElem = this.createTiddler(place,before,title,template,customFields);
4737 if(srcElement && typeof srcElement !== "string") {
4738 if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
4739 anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
4740 else
4741 window.scrollTo(0,ensureVisible(tiddlerElem));
4745 Story.prototype.positionTiddler = function(srcElement)
4747 var place = document.getElementById(this.container);
4748 var before = null;
4749 if(typeof srcElement == "string") {
4750 switch(srcElement) {
4751 case "top":
4752 before = place.firstChild;
4753 break;
4754 case "bottom":
4755 before = null;
4756 break;
4758 } else {
4759 var after = this.findContainingTiddler(srcElement);
4760 if(after == null) {
4761 before = place.firstChild;
4762 } else if(after.nextSibling) {
4763 before = after.nextSibling;
4764 if(before.nodeType != 1)
4765 before = null;
4768 return before;
4771 Story.prototype.createTiddler = function(place,before,title,template,customFields)
4773 var tiddlerElem = createTiddlyElement(null,"div",this.idPrefix + title,"tiddler");
4774 tiddlerElem.setAttribute("refresh","tiddler");
4775 if(customFields)
4776 tiddlerElem.setAttribute("tiddlyFields",customFields);
4777 place.insertBefore(tiddlerElem,before);
4778 var defaultText = null;
4779 if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
4780 defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
4781 this.refreshTiddler(title,template,false,customFields,defaultText);
4782 return tiddlerElem;
4785 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
4787 var tiddler = new Tiddler(title);
4788 tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
4789 var serverType = tiddler.getServerType();
4790 var host = tiddler.fields['server.host'];
4791 var workspace = tiddler.fields['server.workspace'];
4792 if(!serverType || !host)
4793 return null;
4794 var sm = new SyncMachine(serverType,{
4795 start: function() {
4796 return this.openHost(host,"openWorkspace");
4798 openWorkspace: function() {
4799 return this.openWorkspace(workspace,"getTiddler");
4801 getTiddler: function() {
4802 return this.getTiddler(title,"onGetTiddler");
4804 onGetTiddler: function(context) {
4805 var tiddler = context.tiddler;
4806 if(tiddler && tiddler.text) {
4807 var downloaded = new Date();
4808 if(!tiddler.created)
4809 tiddler.created = downloaded;
4810 if(!tiddler.modified)
4811 tiddler.modified = tiddler.created;
4812 store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
4813 autoSaveChanges();
4815 delete this;
4816 return true;
4818 error: function(message) {
4819 displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
4822 sm.go();
4823 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
4826 Story.prototype.chooseTemplateForTiddler = function(title,template)
4828 if(!template)
4829 template = DEFAULT_VIEW_TEMPLATE;
4830 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
4831 template = config.tiddlerTemplates[template];
4832 return template;
4835 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
4837 return store.getRecursiveTiddlerText(template,null,10);
4840 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
4842 var tiddlerElem = document.getElementById(this.idPrefix + title);
4843 if(tiddlerElem) {
4844 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
4845 return tiddlerElem;
4846 template = this.chooseTemplateForTiddler(title,template);
4847 var currTemplate = tiddlerElem.getAttribute("template");
4848 if((template != currTemplate) || force) {
4849 var tiddler = store.getTiddler(title);
4850 if(!tiddler) {
4851 tiddler = new Tiddler();
4852 if(store.isShadowTiddler(title)) {
4853 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
4854 } else {
4855 var text = template=="EditTemplate" ?
4856 config.views.editor.defaultText.format([title]) :
4857 config.views.wikified.defaultText.format([title]);
4858 text = defaultText ? defaultText : text;
4859 var fields = customFields ? customFields.decodeHashMap() : null;
4860 tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
4863 tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
4864 tiddlerElem.setAttribute("tiddler",title);
4865 tiddlerElem.setAttribute("template",template);
4866 var me = this;
4867 tiddlerElem.onmouseover = this.onTiddlerMouseOver;
4868 tiddlerElem.onmouseout = this.onTiddlerMouseOut;
4869 tiddlerElem.ondblclick = this.onTiddlerDblClick;
4870 tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
4871 var html = this.getTemplateForTiddler(title,template,tiddler);
4872 tiddlerElem.innerHTML = html;
4873 applyHtmlMacros(tiddlerElem,tiddler);
4874 if(store.getTaggedTiddlers(title).length > 0)
4875 addClass(tiddlerElem,"isTag");
4876 else
4877 removeClass(tiddlerElem,"isTag");
4878 if(!store.tiddlerExists(title)) {
4879 if(store.isShadowTiddler(title))
4880 addClass(tiddlerElem,"shadow");
4881 else
4882 addClass(tiddlerElem,"missing");
4883 } else {
4884 removeClass(tiddlerElem,"shadow");
4885 removeClass(tiddlerElem,"missing");
4887 if(customFields)
4888 this.addCustomFields(tiddlerElem,customFields);
4889 forceReflow();
4892 return tiddlerElem;
4895 Story.prototype.addCustomFields = function(place,customFields)
4897 var fields = customFields.decodeHashMap();
4898 var w = document.createElement("div");
4899 w.style.display = "none";
4900 place.appendChild(w);
4901 for(var t in fields) {
4902 var e = document.createElement("input");
4903 e.setAttribute("type","text");
4904 e.setAttribute("value",fields[t]);
4905 w.appendChild(e);
4906 e.setAttribute("edit",t);
4910 Story.prototype.refreshAllTiddlers = function(force)
4912 var place = document.getElementById(this.container);
4913 var e = place.firstChild;
4914 if(!e)
4915 return;
4916 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
4917 while((e = e.nextSibling) != null)
4918 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
4921 Story.prototype.onTiddlerMouseOver = function(e)
4923 if(window.addClass instanceof Function)
4924 addClass(this,"selected");
4927 Story.prototype.onTiddlerMouseOut = function(e)
4929 if(window.removeClass instanceof Function)
4930 removeClass(this,"selected");
4933 Story.prototype.onTiddlerDblClick = function(ev)
4935 var e = ev ? ev : window.event;
4936 var theTarget = resolveTarget(e);
4937 if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
4938 if(document.selection && document.selection.empty)
4939 document.selection.empty();
4940 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
4941 e.cancelBubble = true;
4942 if(e.stopPropagation) e.stopPropagation();
4943 return true;
4944 } else {
4945 return false;
4949 Story.prototype.onTiddlerKeyPress = function(ev)
4951 var e = ev ? ev : window.event;
4952 clearMessage();
4953 var consume = false;
4954 var title = this.getAttribute("tiddler");
4955 var target = resolveTarget(e);
4956 switch(e.keyCode) {
4957 case 9: // Tab
4958 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
4959 replaceSelection(target,String.fromCharCode(9));
4960 consume = true;
4962 if(config.isOpera) {
4963 target.onblur = function() {
4964 this.focus();
4965 this.onblur = null;
4968 break;
4969 case 13: // Ctrl-Enter
4970 case 10: // Ctrl-Enter on IE PC
4971 case 77: // Ctrl-Enter is "M" on some platforms
4972 if(e.ctrlKey) {
4973 blurElement(this);
4974 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
4975 consume = true;
4977 break;
4978 case 27: // Escape
4979 blurElement(this);
4980 config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
4981 consume = true;
4982 break;
4984 e.cancelBubble = consume;
4985 if(consume) {
4986 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
4987 e.returnValue = true; // Cancel The Event in IE
4988 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
4990 return !consume;
4993 Story.prototype.getTiddlerField = function(title,field)
4995 var tiddlerElem = document.getElementById(this.idPrefix + title);
4996 var e = null;
4997 if(tiddlerElem != null) {
4998 var children = tiddlerElem.getElementsByTagName("*");
4999 for(var t=0; t<children.length; t++) {
5000 var c = children[t];
5001 if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
5002 if(!e)
5003 e = c;
5004 if(c.getAttribute("edit") == field)
5005 e = c;
5009 return e;
5012 Story.prototype.focusTiddler = function(title,field)
5014 var e = this.getTiddlerField(title,field);
5015 if(e) {
5016 e.focus();
5017 e.select();
5021 Story.prototype.blurTiddler = function(title)
5023 var tiddlerElem = document.getElementById(this.idPrefix + title);
5024 if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
5025 tiddlerElem.focus();
5026 tiddlerElem.blur();
5030 Story.prototype.setTiddlerField = function(title,tag,mode,field)
5032 var c = story.getTiddlerField(title,field);
5034 var tags = c.value.readBracketedList();
5035 tags.setItem(tag,mode);
5036 c.value = String.encodeTiddlyLinkList(tags);
5039 Story.prototype.setTiddlerTag = function(title,tag,mode)
5041 Story.prototype.setTiddlerField(title,tag,mode,"tags");
5044 Story.prototype.closeTiddler = function(title,animate,unused)
5046 var tiddlerElem = document.getElementById(this.idPrefix + title);
5047 if(tiddlerElem != null) {
5048 clearMessage();
5049 this.scrubTiddler(tiddlerElem);
5050 if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
5051 anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
5052 else {
5053 removeNode(tiddlerElem);
5054 forceReflow();
5059 Story.prototype.scrubTiddler = function(tiddlerElem)
5061 tiddlerElem.id = null;
5064 Story.prototype.setDirty = function(title,dirty)
5066 var tiddlerElem = document.getElementById(this.idPrefix + title);
5067 if(tiddlerElem != null)
5068 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
5071 Story.prototype.isDirty = function(title)
5073 var tiddlerElem = document.getElementById(this.idPrefix + title);
5074 if(tiddlerElem != null)
5075 return tiddlerElem.getAttribute("dirty") == "true";
5076 return null;
5079 Story.prototype.areAnyDirty = function()
5081 var r = false;
5082 this.forEachTiddler(function(title,element) {
5083 if(this.isDirty(title))
5084 r = true;
5086 return r;
5089 Story.prototype.closeAllTiddlers = function(exclude)
5091 clearMessage();
5092 this.forEachTiddler(function(title,element) {
5093 if((title != exclude) && element.getAttribute("dirty") != "true")
5094 this.closeTiddler(title);
5096 window.scrollTo(0,ensureVisible(this.container));
5099 Story.prototype.isEmpty = function()
5101 var place = document.getElementById(this.container);
5102 return place && place.firstChild == null;
5105 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
5107 this.closeAllTiddlers();
5108 highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
5109 var matches = store.search(highlightHack,"title","excludeSearch");
5110 this.displayTiddlers(null,matches);
5111 highlightHack = null;
5112 var q = useRegExp ? "/" : "'";
5113 if(matches.length > 0)
5114 displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
5115 else
5116 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5119 Story.prototype.findContainingTiddler = function(e)
5121 while(e && !hasClass(e,"tiddler"))
5122 e = e.parentNode;
5123 return e;
5126 Story.prototype.gatherSaveFields = function(e,fields)
5128 if(e && e.getAttribute) {
5129 var f = e.getAttribute("edit");
5130 if(f)
5131 fields[f] = e.value.replace(/\r/mg,"");
5132 if(e.hasChildNodes()) {
5133 var c = e.childNodes;
5134 for(var t=0; t<c.length; t++)
5135 this.gatherSaveFields(c[t],fields);
5140 Story.prototype.hasChanges = function(title)
5142 var e = document.getElementById(this.idPrefix + title);
5143 if(e != null) {
5144 var fields = {};
5145 this.gatherSaveFields(e,fields);
5146 var tiddler = store.fetchTiddler(title);
5147 if(!tiddler)
5148 return false;
5149 for(var n in fields) {
5150 if(store.getValue(title,n) != fields[n])
5151 return true;
5154 return false;
5157 Story.prototype.saveTiddler = function(title,minorUpdate)
5159 var tiddlerElem = document.getElementById(this.idPrefix + title);
5160 if(tiddlerElem != null) {
5161 var fields = {};
5162 this.gatherSaveFields(tiddlerElem,fields);
5163 var newTitle = fields.title ? fields.title : title;
5164 if(!store.tiddlerExists(newTitle))
5165 newTitle = newTitle.trim();
5166 if(store.tiddlerExists(newTitle) && newTitle != title) {
5167 if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
5168 return null;
5170 if(newTitle != title)
5171 this.closeTiddler(newTitle,false);
5172 tiddlerElem.id = this.idPrefix + newTitle;
5173 tiddlerElem.setAttribute("tiddler",newTitle);
5174 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
5175 tiddlerElem.setAttribute("dirty","false");
5176 if(config.options.chkForceMinorUpdate)
5177 minorUpdate = !minorUpdate;
5178 if(!store.tiddlerExists(newTitle))
5179 minorUpdate = false;
5180 var newDate = new Date();
5181 var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : {});
5182 for(var n in fields) {
5183 if(!TiddlyWiki.isStandardField(n))
5184 extendedFields[n] = fields[n];
5186 var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
5187 autoSaveChanges(null,[tiddler]);
5188 return newTitle;
5190 return null;
5193 Story.prototype.permaView = function()
5195 var links = [];
5196 this.forEachTiddler(function(title,element) {
5197 links.push(String.encodeTiddlyLink(title));
5199 var t = encodeURIComponent(links.join(" "));
5200 if(t == "")
5201 t = "#";
5202 if(window.location.hash != t)
5203 window.location.hash = t;
5207 Story.prototype.switchTheme = function(theme)
5209 if(safeMode)
5210 return;
5212 isAvailable = function(title) {
5213 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5214 if(s!=-1)
5215 title = title.substr(0,s);
5216 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5219 getSlice = function(theme,slice) {
5220 var r = store.getTiddlerSlice(theme,slice);
5221 if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
5222 r = theme + r;
5223 return isAvailable(r) ? r : slice;
5226 replaceNotification = function(i,name,newName) {
5227 if(name==newName)
5228 return name;
5229 if(store.namedNotifications[i].name == name) {
5230 store.namedNotifications[i].name = newName;
5231 return newName;
5233 return name;
5236 for(var i=0; i<config.notifyTiddlers.length; i++) {
5237 var name = config.notifyTiddlers[i].name;
5238 switch(name) {
5239 case "PageTemplate":
5240 config.refreshers.pageTemplate = replaceNotification(i,config.refreshers.pageTemplate,getSlice(theme,name));
5241 break;
5242 case "StyleSheet":
5243 removeStyleSheet(config.refreshers.styleSheet);
5244 config.refreshers.styleSheet = replaceNotification(i,config.refreshers.styleSheet,getSlice(theme,name));
5245 break;
5246 case "ColorPalette":
5247 config.refreshers.colorPalette = replaceNotification(i,config.refreshers.colorPalette,getSlice(theme,name));
5248 break;
5249 default:
5250 break;
5253 config.tiddlerTemplates[DEFAULT_VIEW_TEMPLATE] = getSlice(theme,"ViewTemplate");
5254 config.tiddlerTemplates[DEFAULT_EDIT_TEMPLATE] = getSlice(theme,"EditTemplate");
5255 if(!startingUp) {
5256 refreshAll();
5257 story.refreshAllTiddlers(true);
5258 config.options.txtTheme = theme;
5259 saveOptionCookie("txtTheme");
5263 //--
5264 //-- Backstage
5265 //--
5267 var backstage = {
5268 area: null,
5269 toolbar: null,
5270 button: null,
5271 showButton: null,
5272 hideButton: null,
5273 cloak: null,
5274 panel: null,
5275 panelBody: null,
5276 panelFooter: null,
5277 currTabName: null,
5278 currTabElem: null,
5279 content: null,
5281 init: function() {
5282 var cmb = config.messages.backstage;
5283 this.area = document.getElementById("backstageArea");
5284 this.toolbar = document.getElementById("backstageToolbar");
5285 this.button = document.getElementById("backstageButton");
5286 this.button.style.display = "block";
5287 var t = cmb.open.text + " " + glyph("bentArrowLeft");
5288 this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
5289 function (e) {backstage.show(); return false;},null,"backstageShow");
5290 t = glyph("bentArrowRight") + " " + cmb.close.text;
5291 this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
5292 function (e) {backstage.hide(); return false;},null,"backstageHide");
5293 this.cloak = document.getElementById("backstageCloak");
5294 this.panel = document.getElementById("backstagePanel");
5295 this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
5296 this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
5297 this.cloak.onmousedown = function(e) {
5298 backstage.switchTab(null);
5300 createTiddlyText(this.toolbar,cmb.prompt);
5301 for(t=0; t<config.backstageTasks.length; t++) {
5302 var taskName = config.backstageTasks[t];
5303 var task = config.tasks[taskName];
5304 var handler = task.action ? this.onClickCommand : this.onClickTab;
5305 var text = task.text + (task.action ? "" : glyph("downTriangle"));
5306 var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
5307 btn.setAttribute("task",taskName);
5308 addClass(btn,task.action ? "backstageAction" : "backstageTask");
5310 this.content = document.getElementById("contentWrapper");
5311 if(config.options.chkBackstage)
5312 this.show();
5313 else
5314 this.hide();
5317 isVisible: function() {
5318 return this.area ? this.area.style.display == "block" : false;
5321 show: function() {
5322 this.area.style.display = "block";
5323 if(anim && config.options.chkAnimate) {
5324 backstage.toolbar.style.left = findWindowWidth() + "px";
5325 var p = [
5326 {style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
5328 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
5329 } else {
5330 backstage.area.style.left = "0px";
5332 this.showButton.style.display = "none";
5333 this.hideButton.style.display = "block";
5334 config.options.chkBackstage = true;
5335 saveOptionCookie("chkBackstage");
5336 addClass(this.content,"backstageVisible");
5339 hide: function() {
5340 if(this.currTabElem) {
5341 this.switchTab(null);
5342 } else {
5343 backstage.toolbar.style.left = "0px";
5344 if(anim && config.options.chkAnimate) {
5345 var p = [
5346 {style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
5348 var c = function(element,properties) {backstage.area.style.display = "none";};
5349 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
5350 } else {
5351 this.area.style.display = "none";
5353 this.showButton.style.display = "block";
5354 this.hideButton.style.display = "none";
5355 config.options.chkBackstage = false;
5356 saveOptionCookie("chkBackstage");
5357 removeClass(this.content,"backstageVisible");
5361 onClickCommand: function(e) {
5362 var task = config.tasks[this.getAttribute("task")];
5363 displayMessage(task);
5364 if(task.action) {
5365 backstage.switchTab(null);
5366 task.action();
5368 return false;
5371 onClickTab: function(e) {
5372 backstage.switchTab(this.getAttribute("task"));
5373 return false;
5376 // Switch to a given tab, or none if null is passed
5377 switchTab: function(tabName) {
5378 var tabElem = null;
5379 var e = this.toolbar.firstChild;
5380 while(e)
5382 if(e.getAttribute && e.getAttribute("task") == tabName)
5383 tabElem = e;
5384 e = e.nextSibling;
5386 if(tabName == backstage.currTabName)
5387 return;
5388 if(backstage.currTabElem) {
5389 removeClass(this.currTabElem,"backstageSelTab");
5391 if(tabElem && tabName) {
5392 backstage.preparePanel();
5393 addClass(tabElem,"backstageSelTab");
5394 var task = config.tasks[tabName];
5395 wikify(task.content,backstage.panelBody,null,null);
5396 backstage.showPanel();
5397 } else if(backstage.currTabElem) {
5398 backstage.hidePanel();
5400 backstage.currTabName = tabName;
5401 backstage.currTabElem = tabElem;
5404 isPanelVisible: function() {
5405 return backstage.panel ? backstage.panel.style.display == "block" : false;
5408 preparePanel: function() {
5409 backstage.cloak.style.height = findWindowHeight() + "px";
5410 backstage.cloak.style.display = "block";
5411 removeChildren(backstage.panelBody);
5412 return backstage.panelBody;
5415 showPanel: function() {
5416 backstage.panel.style.display = "block";
5417 if(anim && config.options.chkAnimate) {
5418 backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
5419 var p = [
5420 {style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
5422 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
5423 } else {
5424 backstage.panel.style.top = "0px";
5426 return backstage.panelBody;
5429 hidePanel: function() {
5430 backstage.currTabName = null;
5431 backstage.currTabElem = null;
5432 if(anim && config.options.chkAnimate) {
5433 var p = [
5434 {style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
5435 {style: "display", atEnd: "none"}
5437 var c = function(element,properties) {backstage.cloak.style.display = "none";};
5438 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
5439 } else {
5440 backstage.panel.style.display = "none";
5441 backstage.cloak.style.display = "none";
5446 config.macros.backstage = {};
5448 config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5450 var backstageTask = config.tasks[params[0]];
5451 if(backstageTask)
5452 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5455 //--
5456 //-- ImportTiddlers macro
5457 //--
5459 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5461 if(readOnly) {
5462 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
5463 return;
5465 var w = new Wizard();
5466 w.createWizard(place,this.wizardTitle);
5467 this.restart(w);
5470 config.macros.importTiddlers.onCancel = function(e)
5472 var wizard = new Wizard(this);
5473 var place = wizard.clear();
5474 config.macros.importTiddlers.restart(wizard);
5475 return false;
5478 config.macros.importTiddlers.restart = function(wizard)
5480 wizard.addStep(this.step1Title,this.step1Html);
5481 var s = wizard.getElement("selTypes");
5482 for(var t in config.adaptors) {
5483 var e = createTiddlyElement(s,"option",null,null,t);
5484 e.value = t;
5486 s = wizard.getElement("selFeeds");
5487 var feeds = this.getFeeds();
5488 for(t in feeds) {
5489 e = createTiddlyElement(s,"option",null,null,t);
5490 e.value = t;
5492 wizard.setValue("feeds",feeds);
5493 s.onchange = config.macros.importTiddlers.onFeedChange;
5494 var fileInput = wizard.getElement("txtBrowse");
5495 fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
5496 fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
5497 wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
5500 config.macros.importTiddlers.getFeeds = function()
5502 var feeds = {};
5503 var tagged = store.getTaggedTiddlers("systemServer","title");
5504 for(var t=0; t<tagged.length; t++) {
5505 var title = tagged[t].title;
5506 var serverType = store.getTiddlerSlice(title,"Type");
5507 if(!serverType)
5508 serverType = "file";
5509 feeds[title] = {title: title,
5510 url: store.getTiddlerSlice(title,"URL"),
5511 workspace: store.getTiddlerSlice(title,"Workspace"),
5512 workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
5513 tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
5514 serverType: serverType,
5515 description: store.getTiddlerSlice(title,"Description")};
5517 return feeds;
5520 config.macros.importTiddlers.onFeedChange = function(e)
5522 var wizard = new Wizard(this);
5523 var selTypes = wizard.getElement("selTypes");
5524 var fileInput = wizard.getElement("txtPath");
5525 var feeds = wizard.getValue("feeds");
5526 var f = feeds[this.value];
5527 if(f) {
5528 selTypes.value = f.serverType;
5529 fileInput.value = f.url;
5530 this.selectedIndex = 0;
5531 wizard.setValue("feedName",f.serverType);
5532 wizard.setValue("feedHost",f.url);
5533 wizard.setValue("feedWorkspace",f.workspace);
5534 wizard.setValue("feedWorkspaceList",f.workspaceList);
5535 wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
5537 return false;
5540 config.macros.importTiddlers.onBrowseChange = function(e)
5542 var wizard = new Wizard(this);
5543 var fileInput = wizard.getElement("txtPath");
5544 fileInput.value = "file://" + this.value;
5545 var serverType = wizard.getElement("selTypes");
5546 serverType.value = "file";
5547 return false;
5550 config.macros.importTiddlers.onOpen = function(e)
5552 var wizard = new Wizard(this);
5553 var fileInput = wizard.getElement("txtPath");
5554 var url = fileInput.value;
5555 var serverType = wizard.getElement("selTypes").value;
5556 var adaptor = new config.adaptors[serverType];
5557 wizard.setValue("adaptor",adaptor);
5558 wizard.setValue("serverType",serverType);
5559 wizard.setValue("host",url);
5560 var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
5561 if(ret !== true)
5562 displayMessage(ret);
5563 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
5564 return false;
5567 config.macros.importTiddlers.onOpenHost = function(context,wizard)
5569 var adaptor = wizard.getValue("adaptor");
5570 if(context.status !== true)
5571 displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
5572 var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
5573 if(ret !== true)
5574 displayMessage(ret);
5575 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
5578 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
5580 if(context.status !== true)
5581 displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
5582 wizard.setValue("context",context);
5583 var workspace = wizard.getValue("feedWorkspace");
5584 if(!workspace && context.workspaces.length==1)
5585 workspace = context.workspaces[0].title;
5586 if(workspace) {
5587 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5588 if(ret !== true)
5589 displayMessage(ret);
5590 wizard.setValue("workspace",workspace);
5591 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5592 return;
5594 wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
5595 var s = wizard.getElement("selWorkspace");
5596 s.onchange = config.macros.importTiddlers.onWorkspaceChange;
5597 for(var t=0; t<context.workspaces.length; t++) {
5598 var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
5599 e.value = context.workspaces[t].title;
5601 var workspaceList = wizard.getValue("feedWorkspaceList");
5602 if(workspaceList) {
5603 var list = workspaceList.parseParams("workspace",null,false,true);
5604 for(var n=1; n<list.length; n++) {
5605 if(context.workspaces.findByField("title",list[n].value) == null) {
5606 e = createTiddlyElement(s,"option",null,null,list[n].value);
5607 e.value = list[n].value;
5611 if(workspace) {
5612 t = wizard.getElement("txtWorkspace");
5613 t.value = workspace;
5615 wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
5618 config.macros.importTiddlers.onWorkspaceChange = function(e)
5620 var wizard = new Wizard(this);
5621 var t = wizard.getElement("txtWorkspace");
5622 t.value = this.value;
5623 this.selectedIndex = 0;
5624 return false;
5627 config.macros.importTiddlers.onChooseWorkspace = function(e)
5629 var wizard = new Wizard(this);
5630 var adaptor = wizard.getValue("adaptor");
5631 var workspace = wizard.getElement("txtWorkspace").value;
5632 wizard.setValue("workspace",workspace);
5633 var context = wizard.getValue("context");
5634 var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5635 if(ret !== true)
5636 displayMessage(ret);
5637 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5638 return false;
5641 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
5643 if(context.status !== true)
5644 displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
5645 var adaptor = wizard.getValue("adaptor");
5646 var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
5647 if(ret !== true)
5648 displayMessage(ret);
5649 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
5652 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
5654 if(context.status !== true)
5655 displayMessage("Error in importTiddlers.onGetTiddlerList: " + context.statusText);
5656 // Extract data for the listview
5657 var listedTiddlers = [];
5658 if(context.tiddlers) {
5659 for(var n=0; n<context.tiddlers.length; n++) {
5660 var tiddler = context.tiddlers[n];
5661 listedTiddlers.push({
5662 title: tiddler.title,
5663 modified: tiddler.modified,
5664 modifier: tiddler.modifier,
5665 text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
5666 tags: tiddler.tags,
5667 size: tiddler.text ? tiddler.text.length : 0,
5668 tiddler: tiddler
5672 listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5673 // Display the listview
5674 wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
5675 var markList = wizard.getElement("markList");
5676 var listWrapper = document.createElement("div");
5677 markList.parentNode.insertBefore(listWrapper,markList);
5678 var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
5679 wizard.setValue("listView",listView);
5680 var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
5681 txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
5682 wizard.setButtons([
5683 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
5684 {caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
5688 config.macros.importTiddlers.generateSystemServerName = function(wizard)
5690 var serverType = wizard.getValue("serverType");
5691 var host = wizard.getValue("host");
5692 var workspace = wizard.getValue("workspace");
5693 var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
5694 return pattern.format([serverType,host,workspace]);
5697 config.macros.importTiddlers.saveServerTiddler = function(wizard)
5699 var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
5700 if(store.tiddlerExists(txtSaveTiddler)) {
5701 if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
5702 return;
5703 store.suspendNotifications();
5704 store.removeTiddler(txtSaveTiddler);
5705 store.resumeNotifications();
5707 var serverType = wizard.getValue("serverType");
5708 var host = wizard.getValue("host");
5709 var workspace = wizard.getValue("workspace");
5710 var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
5711 store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
5714 config.macros.importTiddlers.doImport = function(e)
5716 var wizard = new Wizard(this);
5717 if(wizard.getElement("chkSave").checked)
5718 config.macros.importTiddlers.saveServerTiddler(wizard);
5719 var chkSync = wizard.getElement("chkSync").checked;
5720 wizard.setValue("sync",chkSync);
5721 var listView = wizard.getValue("listView");
5722 var rowNames = ListView.getSelectedRows(listView);
5723 var adaptor = wizard.getValue("adaptor");
5724 var overwrite = new Array();
5725 var t;
5726 for(t=0; t<rowNames.length; t++) {
5727 if(store.tiddlerExists(rowNames[t]))
5728 overwrite.push(rowNames[t]);
5730 if(overwrite.length > 0) {
5731 if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
5732 return false;
5734 wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
5735 for(t=0; t<rowNames.length; t++) {
5736 var link = document.createElement("div");
5737 createTiddlyLink(link,rowNames[t],true);
5738 var place = wizard.getElement("markReport");
5739 place.parentNode.insertBefore(link,place);
5741 wizard.setValue("remainingImports",rowNames.length);
5742 wizard.setButtons([
5743 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
5744 ],config.macros.importTiddlers.statusDoingImport);
5745 for(t=0; t<rowNames.length; t++) {
5746 var context = {};
5747 context.allowSynchronous = true;
5748 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
5750 return false;
5753 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
5755 if(!context.status)
5756 displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
5757 var tiddler = context.tiddler;
5758 store.suspendNotifications();
5759 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
5760 if(!wizard.getValue("sync")) {
5761 store.setValue(tiddler.title,'server',null);
5763 store.resumeNotifications();
5764 if(!context.isSynchronous)
5765 store.notify(tiddler.title,true);
5766 var remainingImports = wizard.getValue("remainingImports")-1;
5767 wizard.setValue("remainingImports",remainingImports);
5768 if(remainingImports == 0) {
5769 if(context.isSynchronous) {
5770 store.notifyAll();
5771 refreshDisplay();
5773 wizard.setButtons([
5774 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onCancel}
5775 ],config.macros.importTiddlers.statusDoneImport);
5776 autoSaveChanges();
5780 //--
5781 //-- Sync macro
5782 //--
5784 // Synchronisation handlers
5785 config.syncers = {};
5787 // Sync state.
5788 var currSync = null;
5790 // sync macro
5791 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5793 if(!wikifier.isStatic)
5794 this.startSync(place);
5797 config.macros.sync.startSync = function(place)
5799 if(currSync)
5800 config.macros.sync.cancelSync();
5801 currSync = {};
5802 currSync.syncList = this.getSyncableTiddlers();
5803 this.createSyncTasks();
5804 this.preProcessSyncableTiddlers();
5805 var wizard = new Wizard();
5806 currSync.wizard = wizard;
5807 wizard.createWizard(place,this.wizardTitle);
5808 wizard.addStep(this.step1Title,this.step1Html);
5809 var markList = wizard.getElement("markList");
5810 var listWrapper = document.createElement("div");
5811 markList.parentNode.insertBefore(listWrapper,markList);
5812 currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
5813 this.processSyncableTiddlers();
5814 wizard.setButtons([
5815 {caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
5819 config.macros.sync.getSyncableTiddlers = function()
5821 var list = [];
5822 store.forEachTiddler(function(title,tiddler) {
5823 var syncItem = {};
5824 syncItem.serverType = tiddler.getServerType();
5825 syncItem.serverHost = tiddler.fields['server.host'];
5826 syncItem.serverWorkspace = tiddler.fields['server.workspace'];
5827 syncItem.tiddler = tiddler;
5828 syncItem.title = tiddler.title;
5829 syncItem.isTouched = tiddler.isTouched();
5830 syncItem.selected = syncItem.isTouched;
5831 syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
5832 syncItem.status = syncItem.syncStatus.text;
5833 if(syncItem.serverType && syncItem.serverHost)
5834 list.push(syncItem);
5836 list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5837 return list;
5840 config.macros.sync.preProcessSyncableTiddlers = function()
5842 for(var t=0; t<currSync.syncList.length; t++) {
5843 si = currSync.syncList[t];
5844 var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
5845 si.serverUrl = ti.uri;
5849 config.macros.sync.processSyncableTiddlers = function()
5851 for(var t=0; t<currSync.syncList.length; t++) {
5852 si = currSync.syncList[t];
5853 si.rowElement.style.backgroundColor = si.syncStatus.color;
5857 config.macros.sync.createSyncTasks = function()
5859 currSync.syncTasks = [];
5860 for(var t=0; t<currSync.syncList.length; t++) {
5861 var si = currSync.syncList[t];
5862 var r = null;
5863 for(var st=0; st<currSync.syncTasks.length; st++) {
5864 var cst = currSync.syncTasks[st];
5865 if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
5866 r = cst;
5868 if(r == null) {
5869 si.syncTask = this.createSyncTask(si);
5870 currSync.syncTasks.push(si.syncTask);
5871 } else {
5872 si.syncTask = r;
5873 r.syncItems.push(si);
5878 config.macros.sync.createSyncTask = function(syncItem)
5880 var st = {};
5881 st.serverType = syncItem.serverType;
5882 st.serverHost = syncItem.serverHost;
5883 st.serverWorkspace = syncItem.serverWorkspace;
5884 st.syncItems = [syncItem];
5885 st.syncMachine = new SyncMachine(st.serverType,{
5886 start: function() {
5887 return this.openHost(st.serverHost,"openWorkspace");
5889 openWorkspace: function() {
5890 return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
5892 getTiddlerList: function() {
5893 return this.getTiddlerList("onGetTiddlerList");
5895 onGetTiddlerList: function(context) {
5896 var tiddlers = context.tiddlers;
5897 for(var t=0; t<st.syncItems.length; t++) {
5898 var si = st.syncItems[t];
5899 var f = tiddlers.findByField("title",si.title);
5900 if(f !== null) {
5901 if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
5902 si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
5904 } else {
5905 si.syncStatus = config.macros.sync.syncStatusList.notFound;
5907 config.macros.sync.updateSyncStatus(si);
5910 getTiddler: function(title) {
5911 return this.getTiddler(title,"onGetTiddler");
5913 onGetTiddler: function(context) {
5914 var tiddler = context.tiddler;
5915 var syncItem = st.syncItems.findByField("title",tiddler.title);
5916 if(syncItem !== null) {
5917 syncItem = st.syncItems[syncItem];
5918 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
5919 syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
5920 config.macros.sync.updateSyncStatus(syncItem);
5923 putTiddler: function(tiddler) {
5924 return this.putTiddler(tiddler,"onPutTiddler");
5926 onPutTiddler: function(context) {
5927 var title = context.title;
5928 var syncItem = st.syncItems.findByField("title",title);
5929 if(syncItem !== null) {
5930 syncItem = st.syncItems[syncItem];
5931 store.resetTiddler(title);
5932 syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
5933 config.macros.sync.updateSyncStatus(syncItem);
5937 st.syncMachine.go();
5938 return st;
5941 config.macros.sync.updateSyncStatus = function(syncItem)
5943 var e = syncItem.colElements["status"];
5944 removeChildren(e);
5945 createTiddlyText(e,syncItem.syncStatus.text);
5946 syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
5949 config.macros.sync.doSync = function(e)
5951 var rowNames = ListView.getSelectedRows(currSync.listView);
5952 for(var t=0; t<currSync.syncList.length; t++) {
5953 var si = currSync.syncList[t];
5954 if(rowNames.indexOf(si.title) != -1) {
5955 config.macros.sync.doSyncItem(si);
5958 return false;
5961 config.macros.sync.doSyncItem = function(syncItem)
5963 var r = true;
5964 var sl = config.macros.sync.syncStatusList;
5965 switch(syncItem.syncStatus) {
5966 case sl.changedServer:
5967 r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
5968 break;
5969 case sl.notFound:
5970 case sl.changedLocally:
5971 case sl.changedBoth:
5972 r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
5973 break;
5974 default:
5975 break;
5977 if(r !== true)
5978 displayMessage("Error in doSyncItem: " + r);
5981 config.macros.sync.cancelSync = function()
5983 currSync = null;
5986 function SyncMachine(serverType,steps)
5988 this.serverType = serverType;
5989 this.adaptor = new config.adaptors[serverType];
5990 this.steps = steps;
5993 SyncMachine.prototype.go = function(step,context)
5995 var r = context ? context.status : null;
5996 if(typeof r == "string") {
5997 this.invokeError(r);
5998 return r;
6000 var h = this.steps[step ? step : "start"];
6001 if(!h)
6002 return null;
6003 r = h.call(this,context);
6004 if(typeof r == "string")
6005 this.invokeError(r);
6006 return r;
6009 SyncMachine.prototype.invokeError = function(message)
6011 if(this.steps.error)
6012 this.steps.error(message);
6015 SyncMachine.prototype.openHost = function(host,nextStep)
6017 var me = this;
6018 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
6021 SyncMachine.prototype.getWorkspaceList = function(nextStep)
6023 var me = this;
6024 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
6027 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
6029 var me = this;
6030 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
6033 SyncMachine.prototype.getTiddlerList = function(nextStep)
6035 var me = this;
6036 return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
6039 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
6041 return this.adaptor.generateTiddlerInfo(tiddler);
6044 SyncMachine.prototype.getTiddler = function(title,nextStep)
6046 var me = this;
6047 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
6050 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
6052 var me = this;
6053 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
6056 //--
6057 //-- Manager UI for groups of tiddlers
6058 //--
6060 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6062 var wizard = new Wizard();
6063 wizard.createWizard(place,this.wizardTitle);
6064 wizard.addStep(this.step1Title,this.step1Html);
6065 var markList = wizard.getElement("markList");
6066 var listWrapper = document.createElement("div");
6067 markList.parentNode.insertBefore(listWrapper,markList);
6068 listWrapper.setAttribute("refresh","macro");
6069 listWrapper.setAttribute("macroName","plugins");
6070 listWrapper.setAttribute("params",paramString);
6071 this.refresh(listWrapper,paramString);
6074 config.macros.plugins.refresh = function(listWrapper,params)
6076 var wizard = new Wizard(listWrapper);
6077 var selectedRows = [];
6078 ListView.forEachSelector(listWrapper,function(e,rowName) {
6079 if(e.checked)
6080 selectedRows.push(e.getAttribute("rowName"));
6082 removeChildren(listWrapper);
6083 params = params.parseParams("anon");
6084 var plugins = installedPlugins.slice(0);
6085 var t,tiddler,p;
6086 var configTiddlers = store.getTaggedTiddlers("systemConfig");
6087 for(t=0; t<configTiddlers.length; t++) {
6088 tiddler = configTiddlers[t];
6089 if(plugins.findByField("title",tiddler.title) == null) {
6090 p = getPluginInfo(tiddler);
6091 p.executed = false;
6092 p.log.splice(0,0,this.skippedText);
6093 plugins.push(p);
6096 for(t=0; t<plugins.length; t++) {
6097 p = plugins[t];
6098 p.size = p.tiddler.text ? p.tiddler.text.length : 0;
6099 p.forced = p.tiddler.isTagged("systemConfigForce");
6100 p.disabled = p.tiddler.isTagged("systemConfigDisable");
6101 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
6103 if(plugins.length == 0) {
6104 createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
6105 wizard.setButtons([]);
6106 } else {
6107 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6108 wizard.setValue("listView",listView);
6109 wizard.setButtons([
6110 {caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
6111 {caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
6116 config.macros.plugins.doRemoveTag = function(e)
6118 var wizard = new Wizard(this);
6119 var listView = wizard.getValue("listView");
6120 var rowNames = ListView.getSelectedRows(listView);
6121 if(rowNames.length == 0) {
6122 alert(config.messages.nothingSelected);
6123 } else {
6124 for(var t=0; t<rowNames.length; t++)
6125 store.setTiddlerTag(rowNames[t],false,"systemConfig");
6129 config.macros.plugins.doDelete = function(e)
6131 var wizard = new Wizard(this);
6132 var listView = wizard.getValue("listView");
6133 var rowNames = ListView.getSelectedRows(listView);
6134 if(rowNames.length == 0) {
6135 alert(config.messages.nothingSelected);
6136 } else {
6137 if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
6138 for(t=0; t<rowNames.length; t++) {
6139 store.removeTiddler(rowNames[t]);
6140 story.closeTiddler(rowNames[t],true);
6146 //--
6147 //-- Message area
6148 //--
6150 function getMessageDiv()
6152 var msgArea = document.getElementById("messageArea");
6153 if(!msgArea)
6154 return null;
6155 if(!msgArea.hasChildNodes())
6156 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
6157 config.messages.messageClose.text,
6158 config.messages.messageClose.tooltip,
6159 clearMessage);
6160 msgArea.style.display = "block";
6161 return createTiddlyElement(msgArea,"div");
6164 function displayMessage(text,linkText)
6166 var e = getMessageDiv();
6167 if(!e) {
6168 alert(text);
6169 return;
6171 if(linkText) {
6172 var link = createTiddlyElement(e,"a",null,null,text);
6173 link.href = linkText;
6174 link.target = "_blank";
6175 } else {
6176 e.appendChild(document.createTextNode(text));
6180 function clearMessage()
6182 var msgArea = document.getElementById("messageArea");
6183 if(msgArea) {
6184 removeChildren(msgArea);
6185 msgArea.style.display = "none";
6187 return false;
6190 //--
6191 //-- Refresh mechanism
6192 //--
6194 config.refreshers = {
6195 link: function(e,changeList)
6197 var title = e.getAttribute("tiddlyLink");
6198 refreshTiddlyLink(e,title);
6199 return true;
6202 tiddler: function(e,changeList)
6204 var title = e.getAttribute("tiddler");
6205 var template = e.getAttribute("template");
6206 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
6207 story.refreshTiddler(title,template,true);
6208 else
6209 refreshElements(e,changeList);
6210 return true;
6213 content: function(e,changeList)
6215 var title = e.getAttribute("tiddler");
6216 var force = e.getAttribute("force");
6217 if(force != null || changeList == null || changeList.indexOf(title) != -1) {
6218 removeChildren(e);
6219 wikify(store.getTiddlerText(title,title),e,null);
6220 return true;
6221 } else
6222 return false;
6225 macro: function(e,changeList)
6227 var macro = e.getAttribute("macroName");
6228 var params = e.getAttribute("params");
6229 if(macro)
6230 macro = config.macros[macro];
6231 if(macro && macro.refresh)
6232 macro.refresh(e,params);
6233 return true;
6235 styleSheet: "StyleSheet",
6236 defaultStyleSheet: "StyleSheet",
6237 pageTemplate: "PageTemplate",
6238 defaultPageTemplate: "PageTemplate",
6239 colorPalette: "ColorPalette",
6240 defaultColorPalette: "ColorPalette"
6243 function refreshElements(root,changeList)
6245 var nodes = root.childNodes;
6246 for(var c=0; c<nodes.length; c++) {
6247 var e = nodes[c], type = null;
6248 if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
6249 type = e.getAttribute("refresh");
6250 var refresher = config.refreshers[type];
6251 var refreshed = false;
6252 if(refresher != undefined)
6253 refreshed = refresher(e,changeList);
6254 if(e.hasChildNodes() && !refreshed)
6255 refreshElements(e,changeList);
6259 function applyHtmlMacros(root,tiddler)
6261 var e = root.firstChild;
6262 while(e) {
6263 var nextChild = e.nextSibling;
6264 if(e.getAttribute) {
6265 var macro = e.getAttribute("macro");
6266 if(macro) {
6267 var params = "";
6268 var p = macro.indexOf(" ");
6269 if(p != -1) {
6270 params = macro.substr(p+1);
6271 macro = macro.substr(0,p);
6273 invokeMacro(e,macro,params,null,tiddler);
6276 if(e.hasChildNodes())
6277 applyHtmlMacros(e,tiddler);
6278 e = nextChild;
6282 function refreshPageTemplate(title)
6284 var stash = createTiddlyElement(document.body,"div");
6285 stash.style.display = "none";
6286 var display = document.getElementById("tiddlerDisplay");
6287 var nodes,t;
6288 if(display) {
6289 nodes = display.childNodes;
6290 for(t=nodes.length-1; t>=0; t--)
6291 stash.appendChild(nodes[t]);
6293 var wrapper = document.getElementById("contentWrapper");
6295 isAvailable = function(title) {
6296 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
6297 if(s!=-1)
6298 title = title.substr(0,s);
6299 return store.tiddlerExists(title) || store.isShadowTiddler(title);
6301 if(!title || !isAvailable(title))
6302 title = config.refreshers.pageTemplate;
6303 if(!isAvailable(title))
6304 title = config.refreshers.defaultPageTemplate; //# this one is always avaialable
6305 html = store.getRecursiveTiddlerText(title,null,10);
6306 wrapper.innerHTML = html;
6307 applyHtmlMacros(wrapper);
6308 refreshElements(wrapper);
6309 display = document.getElementById("tiddlerDisplay");
6310 removeChildren(display);
6311 if(!display)
6312 display = createTiddlyElement(wrapper,"div","tiddlerDisplay");
6313 nodes = stash.childNodes;
6314 for(t=nodes.length-1; t>=0; t--)
6315 display.appendChild(nodes[t]);
6316 removeNode(stash);
6319 function refreshDisplay(hint)
6321 if(typeof hint == "string")
6322 hint = [hint];
6323 var e = document.getElementById("contentWrapper");
6324 refreshElements(e,hint);
6325 if(backstage.isPanelVisible()) {
6326 e = document.getElementById("backstage");
6327 refreshElements(e,hint);
6331 function refreshPageTitle()
6333 document.title = getPageTitle();
6336 function getPageTitle()
6338 var st = wikifyPlain("SiteTitle");
6339 var ss = wikifyPlain("SiteSubtitle");
6340 return st + ((st == "" || ss == "") ? "" : " - ") + ss;
6343 function refreshStyles(title,doc)
6345 setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc ? doc : document);
6348 function refreshColorPalette(title)
6350 if(!startingUp)
6351 refreshAll();
6354 function refreshAll()
6356 refreshPageTemplate();
6357 refreshDisplay();
6358 refreshStyles("StyleSheetLayout");
6359 refreshStyles("StyleSheetColors");
6360 refreshStyles(config.refreshers.styleSheet);
6361 refreshStyles("StyleSheetPrint");
6364 //--
6365 //-- Options stuff
6366 //--
6368 config.optionHandlers = {
6369 'txt': {
6370 get: function(name) {return encodeCookie(config.options[name].toString());},
6371 set: function(name,value) {config.options[name] = decodeCookie(value);}
6373 'chk': {
6374 get: function(name) {return config.options[name] ? "true" : "false";},
6375 set: function(name,value) {config.options[name] = value == "true";}
6379 function loadOptionsCookie()
6381 if(safeMode)
6382 return;
6383 var cookies = document.cookie.split(";");
6384 for(var c=0; c<cookies.length; c++) {
6385 var p = cookies[c].indexOf("=");
6386 if(p != -1) {
6387 var name = cookies[c].substr(0,p).trim();
6388 var value = cookies[c].substr(p+1).trim();
6389 var optType = name.substr(0,3);
6390 if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
6391 config.optionHandlers[optType].set(name,value);
6396 function saveOptionCookie(name)
6398 if(safeMode)
6399 return;
6400 var c = name + "=";
6401 var optType = name.substr(0,3);
6402 if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
6403 c += config.optionHandlers[optType].get(name);
6404 c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
6405 document.cookie = c;
6408 function encodeCookie(s)
6410 return escape(manualConvertUnicodeToUTF8(s));
6413 function decodeCookie(s)
6415 s = unescape(s);
6416 var re = /&#[0-9]{1,5};/g;
6417 return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
6421 config.macros.option.genericCreate = function(place,type,opt,className,desc)
6423 var typeInfo = config.macros.option.types[type];
6424 var c = document.createElement(typeInfo.elementType);
6425 if(typeInfo.typeValue)
6426 c.setAttribute("type",typeInfo.typeValue);
6427 c[typeInfo.eventName] = typeInfo.onChange;
6428 c.setAttribute("option",opt);
6429 if(className)
6430 c.className = className;
6431 else
6432 c.className = typeInfo.className;
6433 if(config.optionsDesc[opt])
6434 c.setAttribute("title",config.optionsDesc[opt]);
6435 place.appendChild(c);
6436 if(desc != "no")
6437 createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
6438 c[typeInfo.valueField] = config.options[opt];
6439 return c;
6442 config.macros.option.genericOnChange = function(e)
6444 var opt = this.getAttribute("option");
6445 if(opt) {
6446 var optType = opt.substr(0,3);
6447 var handler = config.macros.option.types[optType];
6448 if (handler.elementType && handler.valueField)
6449 config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
6451 return true;
6454 config.macros.option.types = {
6455 'txt': {
6456 elementType: "input",
6457 valueField: "value",
6458 eventName: "onkeyup",
6459 className: "txtOptionInput",
6460 create: config.macros.option.genericCreate,
6461 onChange: config.macros.option.genericOnChange
6463 'chk': {
6464 elementType: "input",
6465 valueField: "checked",
6466 eventName: "onclick",
6467 className: "chkOptionInput",
6468 typeValue: "checkbox",
6469 create: config.macros.option.genericCreate,
6470 onChange: config.macros.option.genericOnChange
6474 config.macros.option.propagateOption = function(opt,valueField,value,elementType)
6476 config.options[opt] = value;
6477 saveOptionCookie(opt);
6478 var nodes = document.getElementsByTagName(elementType);
6479 for(var t=0; t<nodes.length; t++) {
6480 var optNode = nodes[t].getAttribute("option");
6481 if(opt == optNode)
6482 nodes[t][valueField] = value;
6486 config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6488 params = paramString.parseParams("anon",null,true,false,false);
6489 var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
6490 var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
6491 var desc = getParam(params,"desc","no");
6492 var type = opt.substr(0,3);
6493 var h = config.macros.option.types[type];
6494 if (h && h.create)
6495 h.create(place,type,opt,className,desc);
6498 config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6500 params = paramString.parseParams("anon",null,true,false,false);
6501 var showUnknown = getParam(params,"showUnknown","no");
6502 var wizard = new Wizard();
6503 wizard.createWizard(place,this.wizardTitle);
6504 wizard.addStep(this.step1Title,this.step1Html);
6505 var markList = wizard.getElement("markList");
6506 var chkUnknown = wizard.getElement("chkUnknown");
6507 chkUnknown.checked = showUnknown == "yes";
6508 chkUnknown.onchange = this.onChangeUnknown;
6509 var listWrapper = document.createElement("div");
6510 markList.parentNode.insertBefore(listWrapper,markList);
6511 wizard.setValue("listWrapper",listWrapper);
6512 this.refreshOptions(listWrapper,showUnknown == "yes");
6515 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
6517 var opts = [];
6518 for(var n in config.options) {
6519 var opt = {};
6520 opt.option = "";
6521 opt.name = n;
6522 opt.lowlight = !config.optionsDesc[n];
6523 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6524 if(!opt.lowlight || showUnknown)
6525 opts.push(opt);
6527 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);});
6528 var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
6529 for(n=0; n<opts.length; n++) {
6530 var type = opts[n].name.substr(0,3);
6531 var h = config.macros.option.types[type];
6532 if (h && h.create) {
6533 h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
6538 config.macros.options.onChangeUnknown = function(e)
6540 var wizard = new Wizard(this);
6541 var listWrapper = wizard.getValue("listWrapper");
6542 removeChildren(listWrapper);
6543 config.macros.options.refreshOptions(listWrapper,this.checked);
6544 return false;
6547 //--
6548 //-- Saving
6549 //--
6551 var saveUsingSafari = false;
6553 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
6554 var endSaveArea = '</d' + 'iv>';
6556 // If there are unsaved changes, force the user to confirm before exitting
6557 function confirmExit()
6559 hadConfirmExit = true;
6560 if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
6561 return config.messages.confirmExit;
6564 // Give the user a chance to save changes before exitting
6565 function checkUnsavedChanges()
6567 if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
6568 if(confirm(config.messages.unsavedChangesWarning))
6569 saveChanges();
6573 function updateLanguageAttribute(s)
6575 if(config.locale) {
6576 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
6577 var m = mRE.exec(s);
6578 if(m) {
6579 var t = m[1];
6580 if(m[2])
6581 t += ' xml:lang="' + config.locale + '"';
6582 if(m[3])
6583 t += ' lang="' + config.locale + '"';
6584 t += ">";
6585 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
6588 return s;
6591 function updateMarkupBlock(s,blockName,tiddlerName)
6593 return s.replaceChunk(
6594 "<!--%0-START-->".format([blockName]),
6595 "<!--%0-END-->".format([blockName]),
6596 "\n" + store.getRecursiveTiddlerText(tiddlerName,"") + "\n");
6599 function updateOriginal(original,posDiv)
6601 if(!posDiv)
6602 posDiv = locateStoreArea(original);
6603 if(!posDiv) {
6604 alert(config.messages.invalidFileError.format([localPath]));
6605 return null;
6607 var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
6608 convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
6609 original.substr(posDiv[1]);
6610 var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
6611 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
6612 revised = updateLanguageAttribute(revised);
6613 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
6614 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
6615 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
6616 revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
6617 return revised;
6620 function locateStoreArea(original)
6622 // Locate the storeArea div's
6623 var posOpeningDiv = original.indexOf(startSaveArea);
6624 var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
6625 if(limitClosingDiv == -1)
6626 limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
6627 var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
6628 return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
6631 function autoSaveChanges(onlyIfDirty,tiddlers)
6633 if(config.options.chkAutoSave)
6634 saveChanges(onlyIfDirty,tiddlers);
6637 // Save this tiddlywiki with the pending changes
6638 function saveChanges(onlyIfDirty,tiddlers)
6640 if(onlyIfDirty && !store.isDirty())
6641 return;
6642 clearMessage();
6643 // Get the URL of the document
6644 var originalPath = document.location.toString();
6645 // Check we were loaded from a file URL
6646 if(originalPath.substr(0,5) != "file:") {
6647 alert(config.messages.notFileUrlError);
6648 if(store.tiddlerExists(config.messages.saveInstructions))
6649 story.displayTiddler(null,config.messages.saveInstructions);
6650 return;
6652 var localPath = getLocalPath(originalPath);
6653 // Load the original file
6654 var original = loadFile(localPath);
6655 if(original == null) {
6656 alert(config.messages.cantSaveError);
6657 if(store.tiddlerExists(config.messages.saveInstructions))
6658 story.displayTiddler(null,config.messages.saveInstructions);
6659 return;
6661 // Locate the storeArea div's
6662 var posDiv = locateStoreArea(original);
6663 if(!posDiv) {
6664 alert(config.messages.invalidFileError.format([localPath]));
6665 return;
6667 saveBackup(localPath,original);
6668 saveRss(localPath);
6669 saveEmpty(localPath,original,posDiv);
6670 saveMain(localPath,original,posDiv);
6673 function saveBackup(localPath,original)
6675 if(config.options.chkSaveBackups) {
6676 var backupPath = getBackupPath(localPath);
6677 var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
6678 if(backup)
6679 displayMessage(config.messages.backupSaved,"file://" + backupPath);
6680 else
6681 alert(config.messages.backupFailed);
6685 function saveRss(localPath)
6687 if(config.options.chkGenerateAnRssFeed) {
6688 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
6689 var rssSave = saveFile(rssPath,convertUnicodeToUTF8(generateRss()));
6690 if(rssSave)
6691 displayMessage(config.messages.rssSaved,"file://" + rssPath);
6692 else
6693 alert(config.messages.rssFailed);
6697 function saveEmpty(localPath,original,posDiv)
6699 if(config.options.chkSaveEmptyTemplate) {
6700 var emptyPath,p;
6701 if((p = localPath.lastIndexOf("/")) != -1)
6702 emptyPath = localPath.substr(0,p) + "/empty.html";
6703 else if((p = localPath.lastIndexOf("\\")) != -1)
6704 emptyPath = localPath.substr(0,p) + "\\empty.html";
6705 else
6706 emptyPath = localPath + ".empty.html";
6707 var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
6708 var emptySave = saveFile(emptyPath,empty);
6709 if(emptySave)
6710 displayMessage(config.messages.emptySaved,"file://" + emptyPath);
6711 else
6712 alert(config.messages.emptyFailed);
6716 function saveMain(localPath,original,posDiv)
6718 var save;
6719 try {
6720 var revised = updateOriginal(original,posDiv);
6721 save = saveFile(localPath,revised);
6722 } catch (ex) {
6723 showException(ex);
6725 if(save) {
6726 displayMessage(config.messages.mainSaved,"file://" + localPath);
6727 store.setDirty(false);
6728 } else {
6729 alert(config.messages.mainFailed);
6733 function getLocalPath(origPath)
6735 var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
6736 // Remove any location or query part of the URL
6737 var argPos = originalPath.indexOf("?");
6738 if(argPos != -1)
6739 originalPath = originalPath.substr(0,argPos);
6740 var hashPos = originalPath.indexOf("#");
6741 if(hashPos != -1)
6742 originalPath = originalPath.substr(0,hashPos);
6743 // Convert file://localhost/ to file:///
6744 if(originalPath.indexOf("file://localhost/") == 0)
6745 originalPath = "file://" + originalPath.substr(16);
6746 // Convert to a native file format
6747 var localPath;
6748 if(originalPath.charAt(9) == ":") // pc local file
6749 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
6750 else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
6751 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
6752 else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
6753 localPath = unescape(originalPath.substr(7));
6754 else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
6755 localPath = unescape(originalPath.substr(5));
6756 else // pc network file
6757 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
6758 return localPath;
6761 function getBackupPath(localPath,title,extension)
6763 var slash = "\\";
6764 var dirPathPos = localPath.lastIndexOf("\\");
6765 if(dirPathPos == -1) {
6766 dirPathPos = localPath.lastIndexOf("/");
6767 slash = "/";
6769 var backupFolder = config.options.txtBackupFolder;
6770 if(!backupFolder || backupFolder == "")
6771 backupFolder = ".";
6772 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
6773 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
6774 if(title)
6775 backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
6776 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension ? extension : "html");
6777 return backupPath;
6780 function generateRss()
6782 var s = [];
6783 var d = new Date();
6784 var u = store.getTiddlerText("SiteUrl");
6785 // Assemble the header
6786 s.push("<" + "?xml version=\"1.0\"?" + ">");
6787 s.push("<rss version=\"2.0\">");
6788 s.push("<channel>");
6789 s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
6790 if(u)
6791 s.push("<link>" + u.htmlEncode() + "</link>");
6792 s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
6793 s.push("<language>en-us</language>");
6794 s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
6795 s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
6796 s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
6797 s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
6798 s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
6799 // The body
6800 var tiddlers = store.getTiddlers("modified","excludeLists");
6801 var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
6802 for (var t=tiddlers.length-1; t>=n; t--) {
6803 s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
6805 // And footer
6806 s.push("</channel>");
6807 s.push("</rss>");
6808 // Save it all
6809 return s.join("\n");
6812 //--
6813 //-- Filesystem code
6814 //--
6816 function convertUTF8ToUnicode(u)
6818 return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
6821 function manualConvertUTF8ToUnicode(utf)
6823 var uni = utf;
6824 var src = 0;
6825 var dst = 0;
6826 var b1, b2, b3;
6827 var c;
6828 while(src < utf.length) {
6829 b1 = utf.charCodeAt(src++);
6830 if(b1 < 0x80) {
6831 dst++;
6832 } else if(b1 < 0xE0) {
6833 b2 = utf.charCodeAt(src++);
6834 c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
6835 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
6836 } else {
6837 b2 = utf.charCodeAt(src++);
6838 b3 = utf.charCodeAt(src++);
6839 c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
6840 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
6843 return uni;
6846 function mozConvertUTF8ToUnicode(u)
6848 try {
6849 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6850 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
6851 converter.charset = "UTF-8";
6852 } catch(ex) {
6853 return manualConvertUTF8ToUnicode(u);
6854 } // fallback
6855 var s = converter.ConvertToUnicode(u);
6856 var fin = converter.Finish();
6857 return (fin.length > 0) ? s+fin : s;
6860 function convertUnicodeToUTF8(s)
6862 if(window.netscape == undefined)
6863 return manualConvertUnicodeToUTF8(s);
6864 else
6865 return mozConvertUnicodeToUTF8(s);
6868 function manualConvertUnicodeToUTF8(s)
6870 var re = /[^\u0000-\u007F]/g ;
6871 return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
6874 function mozConvertUnicodeToUTF8(s)
6876 try {
6877 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6878 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
6879 converter.charset = "UTF-8";
6880 } catch(ex) {
6881 return manualConvertUnicodeToUTF8(s);
6882 } // fallback
6883 var u = converter.ConvertFromUnicode(s);
6884 var fin = converter.Finish();
6885 return fin.length > 0 ? u + fin : u;
6888 function convertUriToUTF8(uri,charSet)
6890 if(window.netscape == undefined || charSet == undefined || charSet == "")
6891 return uri;
6892 try {
6893 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6894 var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
6895 } catch(ex) {
6896 return uri;
6898 return converter.convertURISpecToUTF8(uri,charSet);
6901 function saveFile(fileUrl,content)
6903 var r = mozillaSaveFile(fileUrl,content);
6904 if(!r)
6905 r = ieSaveFile(fileUrl,content);
6906 if(!r)
6907 r = javaSaveFile(fileUrl,content);
6908 return r;
6911 function loadFile(fileUrl)
6913 var r = mozillaLoadFile(fileUrl);
6914 if((r == null) || (r == false))
6915 r = ieLoadFile(fileUrl);
6916 if((r == null) || (r == false))
6917 r = javaLoadFile(fileUrl);
6918 return r;
6921 function ieCreatePath(path)
6923 try {
6924 var fso = new ActiveXObject("Scripting.FileSystemObject");
6925 } catch(ex) {
6926 return null;
6929 var pos = path.lastIndexOf("\\");
6930 if(pos!=-1)
6931 path = path.substring(0, pos+1);
6933 var scan = [];
6934 scan.push(path);
6935 var i = 0;
6936 do {
6937 var parent = fso.GetParentFolderName(scan[i++]);
6938 if (fso.FolderExists(parent))
6939 break;
6940 scan.push(parent);
6941 } while(true);
6943 for(i=scan.length-1;i>=0;i--) {
6944 if (!fso.FolderExists(scan[i]))
6945 fso.CreateFolder(scan[i]);
6947 return true;
6950 // Returns null if it can't do it, false if there's an error, true if it saved OK
6951 function ieSaveFile(filePath,content)
6953 ieCreatePath(filePath);
6954 try {
6955 var fso = new ActiveXObject("Scripting.FileSystemObject");
6956 } catch(ex) {
6957 return null;
6959 var file = fso.OpenTextFile(filePath,2,-1,0);
6960 file.Write(content);
6961 file.Close();
6962 return true;
6965 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
6966 function ieLoadFile(filePath)
6968 try {
6969 var fso = new ActiveXObject("Scripting.FileSystemObject");
6970 var file = fso.OpenTextFile(filePath,1);
6971 var content = file.ReadAll();
6972 file.Close();
6973 } catch(ex) {
6974 return null;
6976 return content;
6979 function ieCopyFile(dest,source)
6981 ieCreatePath(dest);
6982 try {
6983 var fso = new ActiveXObject("Scripting.FileSystemObject");
6984 fso.GetFile(source).Copy(dest);
6985 } catch(ex) {
6986 return false;
6988 return true;
6991 // Returns null if it can't do it, false if there's an error, true if it saved OK
6992 function mozillaSaveFile(filePath,content)
6994 if(window.Components) {
6995 try {
6996 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
6997 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
6998 file.initWithPath(filePath);
6999 if(!file.exists())
7000 file.create(0,0664);
7001 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
7002 out.init(file,0x20|0x02,00004,null);
7003 out.write(content,content.length);
7004 out.flush();
7005 out.close();
7006 return true;
7007 } catch(ex) {
7008 return false;
7011 return null;
7014 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7015 function mozillaLoadFile(filePath)
7017 if(window.Components) {
7018 try {
7019 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7020 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7021 file.initWithPath(filePath);
7022 if(!file.exists())
7023 return null;
7024 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
7025 inputStream.init(file,0x01,00004,null);
7026 var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
7027 sInputStream.init(inputStream);
7028 return sInputStream.read(sInputStream.available());
7029 } catch(ex) {
7030 return false;
7033 return null;
7036 function javaUrlToFilename(url)
7038 var f = "//localhost";
7039 if(url.indexOf(f) == 0)
7040 return url.substring(f.length);
7041 var i = url.indexOf(":");
7042 if(i > 0)
7043 return url.substring(i-1);
7044 return url;
7047 function javaSaveFile(filePath,content)
7049 try {
7050 if(document.applets["TiddlySaver"])
7051 return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
7052 } catch(ex) {
7054 try {
7055 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
7056 s.print(content);
7057 s.close();
7058 } catch(ex) {
7059 return null;
7061 return true;
7064 function javaLoadFile(filePath)
7066 try {
7067 if(document.applets["TiddlySaver"])
7068 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
7069 } catch(ex) {
7071 var content = [];
7072 try {
7073 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
7074 var line;
7075 while((line = r.readLine()) != null)
7076 content.push(new String(line));
7077 r.close();
7078 } catch(ex) {
7079 return null;
7081 return content.join("\n");
7084 //--
7085 //-- Server adaptor for talking to static TiddlyWiki files
7086 //--
7088 function FileAdaptor()
7090 this.host = null;
7091 this.store = null;
7092 return this;
7095 FileAdaptor.serverType = 'file';
7097 FileAdaptor.prototype.setContext = function(context,userParams,callback)
7099 if(!context) context = {};
7100 context.userParams = userParams;
7101 if(callback) context.callback = callback;
7102 context.adaptor = this;
7103 if(!context.host)
7104 context.host = this.host;
7105 context.host = FileAdaptor.fullHostName(context.host);
7106 if(!context.workspace)
7107 context.workspace = this.workspace;
7108 return context;
7111 FileAdaptor.fullHostName = function(host)
7113 if(!host)
7114 return '';
7115 if(!host.match(/:\/\//))
7116 host = 'http://' + host;
7117 return host;
7120 FileAdaptor.minHostName = function(host)
7122 return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
7125 // Open the specified host
7126 FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
7128 this.host = host;
7129 context = this.setContext(context,userParams,callback);
7130 context.status = true;
7131 if(callback)
7132 window.setTimeout(function() {callback(context,userParams);},10);
7133 return true;
7136 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7138 context.status = status;
7139 if(!status) {
7140 context.statusText = "Error reading file: " + xhr.statusText;
7141 } else {
7142 context.adaptor.store = new TiddlyWiki();
7143 if(!context.adaptor.store.importTiddlyWiki(responseText))
7144 context.statusText = config.messages.invalidFileError.format([url]);
7146 context.complete(context,context.userParams);
7149 // Get the list of workspaces on a given server
7150 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
7152 context = this.setContext(context,userParams,callback);
7153 context.workspaces = [{title:"(default)"}];
7154 context.status = true;
7155 if(callback)
7156 window.setTimeout(function() {callback(context,userParams);},10);
7157 return true;
7160 // Open the specified workspace
7161 FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
7163 this.workspace = workspace;
7164 context = this.setContext(context,userParams,callback);
7165 context.status = true;
7166 if(callback)
7167 window.setTimeout(function() {callback(context,userParams);},10);
7168 return true;
7171 // Gets the list of tiddlers within a given workspace
7172 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
7174 context = this.setContext(context,userParams,callback);
7175 if(!context.filter)
7176 context.filter = filter;
7177 context.complete = FileAdaptor.getTiddlerListComplete;
7178 if(this.store) {
7179 var ret = context.complete(context,context.userParams);
7180 } else {
7181 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7182 if(typeof ret != "string")
7183 ret = true;
7185 return ret;
7188 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7190 if(context.filter) {
7191 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
7192 } else {
7193 context.tiddlers = [];
7194 context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
7196 for(var i=0; i<context.tiddlers.length; i++) {
7197 context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
7198 context.tiddlers[i].fields['server.host'] = FileAdaptor.minHostName(context.host);
7199 context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
7201 context.status = true;
7202 if(context.callback) {
7203 window.setTimeout(function() {context.callback(context,userParams);},10);
7205 return true;
7208 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7210 var info = {};
7211 info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
7212 return info;
7215 // Retrieve a tiddler from a given workspace on a given server
7216 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
7218 context = this.setContext(context,userParams,callback);
7219 context.title = title;
7220 context.complete = FileAdaptor.getTiddlerComplete;
7221 return context.adaptor.store ?
7222 context.complete(context,context.userParams) :
7223 loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7226 FileAdaptor.getTiddlerComplete = function(context,userParams)
7228 var t = context.adaptor.store.fetchTiddler(context.title);
7229 t.fields['server.type'] = FileAdaptor.serverType;
7230 t.fields['server.host'] = FileAdaptor.minHostName(context.host);
7231 t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
7232 context.tiddler = t;
7233 context.status = true;
7234 if(context.allowSynchronous) {
7235 context.isSynchronous = true;
7236 context.callback(context,userParams);
7237 } else {
7238 window.setTimeout(function() {callback(context,userParams);},10);
7240 return true;
7243 FileAdaptor.prototype.close = function()
7245 delete this.store;
7246 this.store = null;
7249 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7251 //--
7252 //-- Remote HTTP requests
7253 //--
7255 function loadRemoteFile(url,callback,params)
7257 return doHttp("GET",url,null,null,null,null,callback,params,null);
7260 // HTTP status codes
7261 var httpStatus = {
7262 OK: 200,
7263 ContentCreated: 201,
7264 NoContent: 204,
7265 MultiStatus: 207,
7266 Unauthorized: 401,
7267 Forbidden: 403,
7268 NotFound: 404,
7269 MethodNotAllowed: 405
7272 function doHttp(type,url,data,contentType,username,password,callback,params,headers)
7274 var x = getXMLHttpRequest();
7275 if(!x)
7276 return "Can't create XMLHttpRequest object";
7277 x.onreadystatechange = function() {
7278 try {
7279 var status = x.status;
7280 } catch(ex) {
7281 status = false;
7283 if (x.readyState == 4 && callback && (status !== undefined)) {
7284 if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent, httpStatus.MultiStatus].contains(status))
7285 callback(true,params,x.responseText,url,x);
7286 else
7287 callback(false,params,null,url,x);
7288 x.onreadystatechange = function(){};
7289 x = null;
7292 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
7293 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
7294 try {
7295 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
7296 x.open(type,url,true,username,password);
7297 if(data)
7298 x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
7299 if(x.overrideMimeType)
7300 x.setRequestHeader("Connection", "close");
7301 if(headers) {
7302 for(var n in headers)
7303 x.setRequestHeader(n,headers[n]);
7305 x.setRequestHeader("X-Requested-With", "TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
7306 x.send(data);
7307 } catch(ex) {
7308 return exceptionText(ex);
7310 return x;
7313 function getXMLHttpRequest()
7315 try {
7316 var x = new XMLHttpRequest(); // Modern
7317 } catch(ex) {
7318 try {
7319 x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
7320 } catch (ex2) {
7321 return null;
7324 return x;
7327 //--
7328 //-- TiddlyWiki-specific utility functions
7329 //--
7331 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7333 var btn = document.createElement("a");
7334 if(action) {
7335 btn.onclick = action;
7336 btn.setAttribute("href","javascript:;");
7338 if(tooltip)
7339 btn.setAttribute("title",tooltip);
7340 if(text)
7341 btn.appendChild(document.createTextNode(text));
7342 btn.className = className ? className : "button";
7343 if(id)
7344 btn.id = id;
7345 if(attribs) {
7346 for(var n in attribs) {
7347 btn.setAttribute(n,attribs[n]);
7350 if(parent)
7351 parent.appendChild(btn);
7352 if(accessKey)
7353 btn.setAttribute("accessKey",accessKey);
7354 return btn;
7357 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
7359 var text = includeText ? title : null;
7360 var i = getTiddlyLinkInfo(title,className);
7361 var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
7362 btn.setAttribute("refresh","link");
7363 btn.setAttribute("tiddlyLink",title);
7364 if(noToggle)
7365 btn.setAttribute("noToggle","true");
7366 if(linkedFromTiddler) {
7367 var fields = linkedFromTiddler.getInheritedFields();
7368 if(fields)
7369 btn.setAttribute("tiddlyFields",fields);
7371 return btn;
7374 function refreshTiddlyLink(e,title)
7376 var i = getTiddlyLinkInfo(title,e.className);
7377 e.className = i.classes;
7378 e.title = i.subTitle;
7381 function getTiddlyLinkInfo(title,currClasses)
7383 var classes = currClasses ? currClasses.split(" ") : [];
7384 classes.pushUnique("tiddlyLink");
7385 var tiddler = store.fetchTiddler(title);
7386 var subTitle;
7387 if(tiddler) {
7388 subTitle = tiddler.getSubtitle();
7389 classes.pushUnique("tiddlyLinkExisting");
7390 classes.remove("tiddlyLinkNonExisting");
7391 classes.remove("shadow");
7392 } else {
7393 classes.remove("tiddlyLinkExisting");
7394 classes.pushUnique("tiddlyLinkNonExisting");
7395 if(store.isShadowTiddler(title)) {
7396 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7397 classes.pushUnique("shadow");
7398 } else {
7399 subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
7400 classes.remove("shadow");
7403 if(typeof config.annotations[title]=="string")
7404 subTitle = config.annotations[title];
7405 return {classes: classes.join(" "),subTitle: subTitle};
7408 function createExternalLink(place,url)
7410 var link = document.createElement("a");
7411 link.className = "externalLink";
7412 link.href = url;
7413 link.title = config.messages.externalLinkTooltip.format([url]);
7414 if(config.options.chkOpenInNewWindow)
7415 link.target = "_blank";
7416 place.appendChild(link);
7417 return link;
7420 // Event handler for clicking on a tiddly link
7421 function onClickTiddlerLink(ev)
7423 var e = ev ? ev : window.event;
7424 var target = resolveTarget(e);
7425 var link = target;
7426 var title = null;
7427 var fields = null;
7428 var noToggle = null;
7429 do {
7430 title = link.getAttribute("tiddlyLink");
7431 fields = link.getAttribute("tiddlyFields");
7432 noToggle = link.getAttribute("noToggle");
7433 link = link.parentNode;
7434 } while(title == null && link != null);
7435 if(!store.isShadowTiddler(title)) {
7436 var f = fields ? fields.decodeHashMap() : {};
7437 fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
7439 if(title) {
7440 var toggling = e.metaKey || e.ctrlKey;
7441 if(config.options.chkToggleLinks)
7442 toggling = !toggling;
7443 if(noToggle)
7444 toggling = false;
7445 if(store.getTiddler(title))
7446 fields = null;
7447 story.displayTiddler(target,title,null,true,null,fields,toggling);
7449 clearMessage();
7450 return false;
7453 // Create a button for a tag with a popup listing all the tiddlers that it tags
7454 function createTagButton(place,tag,excludeTiddler)
7456 var btn = createTiddlyButton(place,tag,config.views.wikified.tag.tooltip.format([tag]),onClickTag);
7457 btn.setAttribute("tag",tag);
7458 if(excludeTiddler)
7459 btn.setAttribute("tiddler",excludeTiddler);
7460 return btn;
7463 // Event handler for clicking on a tiddler tag
7464 function onClickTag(ev)
7466 var e = ev ? ev : window.event;
7467 var popup = Popup.create(this);
7468 var tag = this.getAttribute("tag");
7469 var title = this.getAttribute("tiddler");
7470 if(popup && tag) {
7471 var tagged = store.getTaggedTiddlers(tag);
7472 var titles = [];
7473 var li,r;
7474 for(r=0;r<tagged.length;r++) {
7475 if(tagged[r].title != title)
7476 titles.push(tagged[r].title);
7478 var lingo = config.views.wikified.tag;
7479 if(titles.length > 0) {
7480 var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
7481 openAll.setAttribute("tag",tag);
7482 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7483 for(r=0; r<titles.length; r++) {
7484 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
7486 } else {
7487 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
7489 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7490 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
7491 createTiddlyText(h,lingo.openTag.format([tag]));
7493 Popup.show();
7494 e.cancelBubble = true;
7495 if(e.stopPropagation) e.stopPropagation();
7496 return false;
7499 // Event handler for 'open all' on a tiddler popup
7500 function onClickTagOpenAll(ev)
7502 var e = ev ? ev : window.event;
7503 var tag = this.getAttribute("tag");
7504 var tagged = store.getTaggedTiddlers(tag);
7505 story.displayTiddlers(this,tagged);
7506 return false;
7509 function onClickError(ev)
7511 var e = ev ? ev : window.event;
7512 var popup = Popup.create(this);
7513 var lines = this.getAttribute("errorText").split("\n");
7514 for(var t=0; t<lines.length; t++)
7515 createTiddlyElement(popup,"li",null,null,lines[t]);
7516 Popup.show();
7517 e.cancelBubble = true;
7518 if(e.stopPropagation) e.stopPropagation();
7519 return false;
7522 function createTiddlyDropDown(place,onchange,options,defaultValue)
7524 var sel = createTiddlyElement(place,"select");
7525 sel.onchange = onchange;
7526 for(var t=0; t<options.length; t++) {
7527 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
7528 e.value = options[t].name;
7529 if(options[t].name == defaultValue)
7530 e.selected = true;
7532 return sel;
7535 function createTiddlyPopup(place,caption,tooltip,tiddler)
7537 if(tiddler.text) {
7538 createTiddlyLink(place,caption,true);
7539 var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
7540 btn.tiddler = tiddler;
7541 } else {
7542 createTiddlyText(place,caption);
7546 function onClickTiddlyPopup(ev)
7548 var e = ev ? ev : window.event;
7549 var tiddler = this.tiddler;
7550 if(tiddler.text) {
7551 var popup = Popup.create(this,"div","popupTiddler");
7552 wikify(tiddler.text,popup,null,tiddler);
7553 Popup.show();
7555 if(e) e.cancelBubble = true;
7556 if(e && e.stopPropagation) e.stopPropagation();
7557 return false;
7560 function createTiddlyError(place,title,text)
7562 var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
7563 if(text) btn.setAttribute("errorText",text);
7566 function merge(dst,src,preserveExisting)
7568 for(p in src) {
7569 if(!preserveExisting || dst[p] === undefined)
7570 dst[p] = src[p];
7572 return dst;
7575 // Returns a string containing the description of an exception, optionally prepended by a message
7576 function exceptionText(e,message)
7578 var s = e.description ? e.description : e.toString();
7579 return message ? "%0:\n%1".format([message,s]) : s;
7582 // Displays an alert of an exception description with optional message
7583 function showException(e,message)
7585 alert(exceptionText(e,message));
7588 function alertAndThrow(m)
7590 alert(m);
7591 throw(m);
7594 function glyph(name)
7596 var g = config.glyphs;
7597 var b = g.currBrowser;
7598 if(b == null) {
7599 b = 0;
7600 while(!g.browsers[b]() && b < g.browsers.length-1)
7601 b++;
7602 g.currBrowser = b;
7604 if(!g.codes[name])
7605 return "";
7606 return g.codes[name][b];
7611 //- Animation engine
7614 function Animator()
7616 this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
7617 this.timerID = 0; // ID of the timer used for animating
7618 this.animations = []; // List of animations in progress
7619 return this;
7622 // Start animation engine
7623 Animator.prototype.startAnimating = function() //# Variable number of arguments
7625 for(var t=0; t<arguments.length; t++)
7626 this.animations.push(arguments[t]);
7627 if(this.running == 0) {
7628 var me = this;
7629 this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
7631 this.running += arguments.length;
7634 // Perform an animation engine tick, calling each of the known animation modules
7635 Animator.prototype.doAnimate = function(me)
7637 var a = 0;
7638 while(a < me.animations.length) {
7639 var animation = me.animations[a];
7640 if(animation.tick()) {
7641 a++;
7642 } else {
7643 me.animations.splice(a,1);
7644 if(--me.running == 0)
7645 window.clearInterval(me.timerID);
7650 Animator.slowInSlowOut = function(progress)
7652 return(1-((Math.cos(progress * Math.PI)+1)/2));
7655 //--
7656 //-- Morpher animation
7657 //--
7659 // Animate a set of properties of an element
7660 function Morpher(element,duration,properties,callback)
7662 this.element = element;
7663 this.duration = duration;
7664 this.properties = properties;
7665 this.startTime = new Date();
7666 this.endTime = Number(this.startTime) + duration;
7667 this.callback = callback;
7668 this.tick();
7669 return this;
7672 Morpher.prototype.assignStyle = function(element,style,value)
7674 switch(style) {
7675 case "-tw-vertScroll":
7676 window.scrollTo(findScrollX(),value);
7677 break;
7678 case "-tw-horizScroll":
7679 window.scrollTo(value,findScrollY());
7680 break;
7681 default:
7682 element.style[style] = value;
7683 break;
7687 Morpher.prototype.stop = function()
7689 for(var t=0; t<this.properties.length; t++) {
7690 var p = this.properties[t];
7691 if(p.atEnd !== undefined) {
7692 this.assignStyle(this.element,p.style,p.atEnd);
7695 if(this.callback)
7696 this.callback(this.element,this.properties);
7699 Morpher.prototype.tick = function()
7701 var currTime = Number(new Date());
7702 progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
7703 for(var t=0; t<this.properties.length; t++) {
7704 var p = this.properties[t];
7705 if(p.start !== undefined && p.end !== undefined) {
7706 var template = p.template ? p.template : "%0";
7707 switch(p.format) {
7708 case undefined:
7709 case "style":
7710 var v = p.start + (p.end-p.start) * progress;
7711 this.assignStyle(this.element,p.style,template.format([v]));
7712 break;
7713 case "color":
7714 break;
7718 if(currTime >= this.endTime) {
7719 this.stop();
7720 return false;
7722 return true;
7725 //--
7726 //-- Zoomer animation
7727 //--
7729 function Zoomer(text,startElement,targetElement,unused)
7731 var e = createTiddlyElement(document.body,"div",null,"zoomer");
7732 createTiddlyElement(e,"div",null,null,text);
7733 var winWidth = findWindowWidth();
7734 var winHeight = findWindowHeight();
7735 var p = [
7736 {style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
7737 {style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
7738 {style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
7739 {style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
7740 {style: 'fontSize', start: 8, end: 24, template: '%0pt'}
7742 var c = function(element,properties) {removeNode(element);};
7743 return new Morpher(e,config.animDuration,p,c);
7746 //--
7747 //-- Scroller animation
7748 //--
7750 function Scroller(targetElement,unused)
7752 var p = [
7753 {style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
7755 return new Morpher(targetElement,config.animDuration,p);
7758 //--
7759 //-- Slider animation
7760 //--
7762 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
7763 function Slider(element,opening,unused,deleteMode)
7765 element.style.overflow = 'hidden';
7766 if(opening)
7767 element.style.height = '0px'; // Resolves a Firefox flashing bug
7768 element.style.display = 'block';
7769 var left = findPosX(element);
7770 var width = element.scrollWidth;
7771 var height = element.scrollHeight;
7772 var winWidth = findWindowWidth();
7773 var p = [];
7774 var c = null;
7775 if(opening) {
7776 p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
7777 p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
7778 p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
7779 } else {
7780 p.push({style: 'height', start: height, end: 0, template: '%0px'});
7781 p.push({style: 'display', atEnd: 'none'});
7782 p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
7783 p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
7784 switch(deleteMode) {
7785 case "all":
7786 c = function(element,properties) {removeNode(element);};
7787 break;
7788 case "children":
7789 c = function(element,properties) {removeChildren(element);};
7790 break;
7793 return new Morpher(element,config.animDuration,p,c);
7796 //--
7797 //-- Popup menu
7798 //--
7800 var Popup = {
7801 stack: [] // Array of objects with members root: and popup:
7804 Popup.create = function(root,elem,theClass)
7806 Popup.remove();
7807 var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
7808 Popup.stack.push({root: root, popup: popup});
7809 return popup;
7812 Popup.onDocumentClick = function(ev)
7814 var e = ev ? ev : window.event;
7815 if(e.eventPhase == undefined)
7816 Popup.remove();
7817 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
7818 Popup.remove();
7819 return true;
7822 Popup.show = function(unused1,unused2)
7824 var curr = Popup.stack[Popup.stack.length-1];
7825 this.place(curr.root,curr.popup);
7826 addClass(curr.root,"highlight");
7827 if(config.options.chkAnimate && anim && typeof Scroller == "function")
7828 anim.startAnimating(new Scroller(curr.popup));
7829 else
7830 window.scrollTo(0,ensureVisible(curr.popup));
7833 Popup.place = function(root,popup,offset)
7835 if(!offset) var offset = {x:0, y:0};
7836 var rootLeft = findPosX(root);
7837 var rootTop = findPosY(root);
7838 var rootHeight = root.offsetHeight;
7839 var popupLeft = rootLeft + offset.x;
7840 var popupTop = rootTop + rootHeight + offset.y;
7841 var winWidth = findWindowWidth();
7842 if(popup.offsetWidth > winWidth*0.75)
7843 popup.style.width = winWidth*0.75 + "px";
7844 var popupWidth = popup.offsetWidth;
7845 if(popupLeft + popupWidth > winWidth)
7846 popupLeft = winWidth - popupWidth;
7847 popup.style.left = popupLeft + "px";
7848 popup.style.top = popupTop + "px";
7849 popup.style.display = "block";
7852 Popup.remove = function()
7854 if(Popup.stack.length > 0) {
7855 Popup.removeFrom(0);
7859 Popup.removeFrom = function(from)
7861 for(var t=Popup.stack.length-1; t>=from; t--) {
7862 var p = Popup.stack[t];
7863 removeClass(p.root,"highlight");
7864 removeNode(p.popup);
7866 Popup.stack = Popup.stack.slice(0,from);
7869 //--
7870 //-- Wizard support
7871 //--
7873 function Wizard(elem)
7875 if(elem) {
7876 this.formElem = findRelated(elem,"wizard","className");
7877 this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
7878 this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
7879 } else {
7880 this.formElem = null;
7881 this.bodyElem = null;
7882 this.footElem = null;
7886 Wizard.prototype.setValue = function(name,value)
7888 if(this.formElem)
7889 this.formElem[name] = value;
7892 Wizard.prototype.getValue = function(name)
7894 return this.formElem ? this.formElem[name] : null;
7897 Wizard.prototype.createWizard = function(place,title)
7899 this.formElem = createTiddlyElement(place,"form",null,"wizard");
7900 createTiddlyElement(this.formElem,"h1",null,null,title);
7901 this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
7902 this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
7905 Wizard.prototype.clear = function()
7907 removeChildren(this.bodyElem);
7910 Wizard.prototype.setButtons = function(buttonInfo,status)
7912 removeChildren(this.footElem);
7913 for(var t=0; t<buttonInfo.length; t++) {
7914 createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
7915 insertSpacer(this.footElem);
7917 if(typeof status == "string") {
7918 createTiddlyElement(this.footElem,"span",null,"status",status);
7922 Wizard.prototype.addStep = function(stepTitle,html)
7924 removeChildren(this.bodyElem);
7925 var w = createTiddlyElement(this.bodyElem,"div");
7926 createTiddlyElement(w,"h2",null,null,stepTitle);
7927 var step = createTiddlyElement(w,"div",null,"wizardStep");
7928 step.innerHTML = html;
7929 applyHtmlMacros(step,tiddler);
7932 Wizard.prototype.getElement = function(name)
7934 return this.formElem.elements[name];
7937 //--
7938 //-- ListView gadget
7939 //--
7941 var ListView = {};
7943 // Create a listview
7944 ListView.create = function(place,listObject,listTemplate,callback,className)
7946 var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
7947 var thead = createTiddlyElement(table,"thead");
7948 var r = createTiddlyElement(thead,"tr");
7949 for(var t=0; t<listTemplate.columns.length; t++) {
7950 var columnTemplate = listTemplate.columns[t];
7951 var c = createTiddlyElement(r,"th");
7952 var colType = ListView.columnTypes[columnTemplate.type];
7953 if(colType && colType.createHeader)
7954 colType.createHeader(c,columnTemplate,t);
7956 var tbody = createTiddlyElement(table,"tbody");
7957 for(var rc=0; rc<listObject.length; rc++) {
7958 rowObject = listObject[rc];
7959 r = createTiddlyElement(tbody,"tr");
7960 for(c=0; c<listTemplate.rowClasses.length; c++) {
7961 if(rowObject[listTemplate.rowClasses[c].field])
7962 addClass(r,listTemplate.rowClasses[c].className);
7964 rowObject.rowElement = r;
7965 rowObject.colElements = {};
7966 for(var cc=0; cc<listTemplate.columns.length; cc++) {
7967 c = createTiddlyElement(r,"td");
7968 columnTemplate = listTemplate.columns[cc];
7969 var field = columnTemplate.field;
7970 colType = ListView.columnTypes[columnTemplate.type];
7971 if(colType && colType.createItem)
7972 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
7973 rowObject.colElements[field] = c;
7976 if(callback && listTemplate.actions)
7977 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
7978 if(callback && listTemplate.buttons) {
7979 for(t=0; t<listTemplate.buttons.length; t++) {
7980 var a = listTemplate.buttons[t];
7981 if(a && a.name != "")
7982 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
7985 return table;
7988 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
7990 return function(e) {
7991 var view = findRelated(this,"TABLE",null,"previousSibling");
7992 var tiddlers = [];
7993 ListView.forEachSelector(view,function(e,rowName) {
7994 if(e.checked)
7995 tiddlers.push(rowName);
7997 if(tiddlers.length == 0 && !allowEmptySelection) {
7998 alert(config.messages.nothingSelected);
7999 } else {
8000 if(this.nodeName.toLowerCase() == "select") {
8001 callback(view,this.value,tiddlers);
8002 this.selectedIndex = 0;
8003 } else {
8004 callback(view,name,tiddlers);
8010 // Invoke a callback for each selector checkbox in the listview
8011 ListView.forEachSelector = function(view,callback)
8013 var checkboxes = view.getElementsByTagName("input");
8014 var hadOne = false;
8015 for(var t=0; t<checkboxes.length; t++) {
8016 var cb = checkboxes[t];
8017 if(cb.getAttribute("type") == "checkbox") {
8018 var rn = cb.getAttribute("rowName");
8019 if(rn) {
8020 callback(cb,rn);
8021 hadOne = true;
8025 return hadOne;
8028 ListView.getSelectedRows = function(view)
8030 var rowNames = [];
8031 ListView.forEachSelector(view,function(e,rowName) {
8032 if(e.checked)
8033 rowNames.push(rowName);
8035 return rowNames;
8038 ListView.columnTypes = {};
8040 ListView.columnTypes.String = {
8041 createHeader: function(place,columnTemplate,col)
8043 createTiddlyText(place,columnTemplate.title);
8045 createItem: function(place,listObject,field,columnTemplate,col,row)
8047 var v = listObject[field];
8048 if(v != undefined)
8049 createTiddlyText(place,v);
8053 ListView.columnTypes.WikiText = {
8054 createHeader: ListView.columnTypes.String.createHeader,
8055 createItem: function(place,listObject,field,columnTemplate,col,row)
8057 var v = listObject[field];
8058 if(v != undefined)
8059 wikify(v,place,null,null);
8063 ListView.columnTypes.Tiddler = {
8064 createHeader: ListView.columnTypes.String.createHeader,
8065 createItem: function(place,listObject,field,columnTemplate,col,row)
8067 var v = listObject[field];
8068 if(v != undefined && v.title)
8069 createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
8073 ListView.columnTypes.Size = {
8074 createHeader: ListView.columnTypes.String.createHeader,
8075 createItem: function(place,listObject,field,columnTemplate,col,row)
8077 var v = listObject[field];
8078 if(v != undefined) {
8079 var t = 0;
8080 while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
8081 t++;
8082 createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
8087 ListView.columnTypes.Link = {
8088 createHeader: ListView.columnTypes.String.createHeader,
8089 createItem: function(place,listObject,field,columnTemplate,col,row)
8091 var v = listObject[field];
8092 var c = columnTemplate.text;
8093 if(v != undefined)
8094 createTiddlyText(createExternalLink(place,v),c ? c : v);
8098 ListView.columnTypes.Date = {
8099 createHeader: ListView.columnTypes.String.createHeader,
8100 createItem: function(place,listObject,field,columnTemplate,col,row)
8102 var v = listObject[field];
8103 if(v != undefined)
8104 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
8108 ListView.columnTypes.StringList = {
8109 createHeader: ListView.columnTypes.String.createHeader,
8110 createItem: function(place,listObject,field,columnTemplate,col,row)
8112 var v = listObject[field];
8113 if(v != undefined) {
8114 for(var t=0; t<v.length; t++) {
8115 createTiddlyText(place,v[t]);
8116 createTiddlyElement(place,"br");
8122 ListView.columnTypes.Selector = {
8123 createHeader: function(place,columnTemplate,col)
8125 createTiddlyCheckbox(place,null,false,this.onHeaderChange);
8127 createItem: function(place,listObject,field,columnTemplate,col,row)
8129 var e = createTiddlyCheckbox(place,null,listObject[field],null);
8130 e.setAttribute("rowName",listObject[columnTemplate.rowName]);
8132 onHeaderChange: function(e)
8134 var state = this.checked;
8135 var view = findRelated(this,"TABLE");
8136 if(!view)
8137 return;
8138 ListView.forEachSelector(view,function(e,rowName) {
8139 e.checked = state;
8144 ListView.columnTypes.Tags = {
8145 createHeader: ListView.columnTypes.String.createHeader,
8146 createItem: function(place,listObject,field,columnTemplate,col,row)
8148 var tags = listObject[field];
8149 createTiddlyText(place,String.encodeTiddlyLinkList(tags));
8153 ListView.columnTypes.Boolean = {
8154 createHeader: ListView.columnTypes.String.createHeader,
8155 createItem: function(place,listObject,field,columnTemplate,col,row)
8157 if(listObject[field] == true)
8158 createTiddlyText(place,columnTemplate.trueText);
8159 if(listObject[field] == false)
8160 createTiddlyText(place,columnTemplate.falseText);
8164 ListView.columnTypes.TagCheckbox = {
8165 createHeader: ListView.columnTypes.String.createHeader,
8166 createItem: function(place,listObject,field,columnTemplate,col,row)
8168 var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
8169 e.setAttribute("tiddler",listObject.title);
8170 e.setAttribute("tag",columnTemplate.tag);
8172 onChange : function(e)
8174 var tag = this.getAttribute("tag");
8175 var tiddler = this.getAttribute("tiddler");
8176 store.setTiddlerTag(tiddler,this.checked,tag);
8180 ListView.columnTypes.TiddlerLink = {
8181 createHeader: ListView.columnTypes.String.createHeader,
8182 createItem: function(place,listObject,field,columnTemplate,col,row)
8184 var v = listObject[field];
8185 if(v != undefined) {
8186 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
8187 createTiddlyText(link,listObject[field]);
8192 //--
8193 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8194 //--
8196 // Clamp a number to a range
8197 Number.prototype.clamp = function(min,max)
8199 var c = this;
8200 if(c < min)
8201 c = min;
8202 if(c > max)
8203 c = max;
8204 return c;
8207 // Add indexOf function if browser does not support it
8208 if(!Array.indexOf) {
8209 Array.prototype.indexOf = function(item,from)
8211 if(!from)
8212 from = 0;
8213 for(var i=from; i<this.length; i++) {
8214 if(this[i] === item)
8215 return i;
8217 return -1;
8220 // Find an entry in a given field of the members of an array
8221 Array.prototype.findByField = function(field,value)
8223 for(var t=0; t<this.length; t++) {
8224 if(this[t][field] == value)
8225 return t;
8227 return null;
8230 // Return whether an entry exists in an array
8231 Array.prototype.contains = function(item)
8233 return this.indexOf(item) != -1;
8236 // Adds, removes or toggles a particular value within an array
8237 // value - value to add
8238 // mode - +1 to add value, -1 to remove value, 0 to toggle it
8239 Array.prototype.setItem = function(value,mode)
8241 var p = this.indexOf(value);
8242 if(mode == 0)
8243 mode = (p == -1) ? +1 : -1;
8244 if(mode == +1) {
8245 if(p == -1)
8246 this.push(value);
8247 } else if(mode == -1) {
8248 if(p != -1)
8249 this.splice(p,1);
8253 // Return whether one of a list of values exists in an array
8254 Array.prototype.containsAny = function(items)
8256 for(var i=0; i<items.length; i++) {
8257 if (this.indexOf(items[i]) != -1)
8258 return true;
8260 return false;
8263 // Return whether all of a list of values exists in an array
8264 Array.prototype.containsAll = function(items)
8266 for (var i = 0; i<items.length; i++) {
8267 if (this.indexOf(items[i]) == -1)
8268 return false;
8270 return true;
8273 // 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
8274 Array.prototype.pushUnique = function(item,unique)
8276 if(unique === false) {
8277 this.push(item);
8278 } else {
8279 if(this.indexOf(item) == -1)
8280 this.push(item);
8284 Array.prototype.remove = function(item)
8286 var p = this.indexOf(item);
8287 if(p != -1)
8288 this.splice(p,1);
8291 if(!Array.prototype.map){
8292 Array.prototype.map = function(fn, thisObj) {
8293 var scope = thisObj || window;
8294 var a = [];
8295 for ( var i=0, j=this.length; i < j; ++i ) {
8296 a.push(fn.call(scope, this[i], i, this));
8298 return a;
8299 };}// Get characters from the right end of a string
8300 String.prototype.right = function(n)
8302 return n < this.length ? this.slice(this.length-n) : this;
8305 // Trim whitespace from both ends of a string
8306 String.prototype.trim = function()
8308 return this.replace(/^\s*|\s*$/g,"");
8311 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
8312 String.prototype.unDash = function()
8314 var s = this.split("-");
8315 if(s.length > 1) {
8316 for(var t=1; t<s.length; t++)
8317 s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
8319 return s.join("");
8322 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
8323 String.prototype.format = function(substrings)
8325 var subRegExp = /(?:%(\d+))/mg;
8326 var currPos = 0;
8327 var r = [];
8328 do {
8329 var match = subRegExp.exec(this);
8330 if(match && match[1]) {
8331 if(match.index > currPos)
8332 r.push(this.substring(currPos,match.index));
8333 r.push(substrings[parseInt(match[1])]);
8334 currPos = subRegExp.lastIndex;
8336 } while(match);
8337 if(currPos < this.length)
8338 r.push(this.substring(currPos,this.length));
8339 return r.join("");
8342 // Escape any special RegExp characters with that character preceded by a backslash
8343 String.prototype.escapeRegExp = function()
8345 var s = "\\^$*+?()=!|,{}[].";
8346 var c = this;
8347 for(var t=0; t<s.length; t++)
8348 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
8349 return c;
8352 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
8353 String.prototype.escapeLineBreaks = function()
8355 return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
8358 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
8359 String.prototype.unescapeLineBreaks = function()
8361 return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
8364 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
8365 String.prototype.htmlEncode = function()
8367 return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
8370 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
8371 String.prototype.htmlDecode = function()
8373 return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
8376 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
8377 String.prototype.toJSONString = function()
8379 var m = {
8380 '\b': '\\b',
8381 '\f': '\\f',
8382 '\n': '\\n',
8383 '\r': '\\r',
8384 '\t': '\\t',
8385 '"' : '\\"',
8386 '\\': '\\\\'
8388 var replaceFn = function(a,b) {
8389 var c = m[b];
8390 if(c)
8391 return c;
8392 c = b.charCodeAt();
8393 return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
8395 if(/["\\\x00-\x1f]/.test(this))
8396 return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
8397 return '"' + this + '"';
8400 // Parse a space-separated string of name:value parameters
8401 // The result is an array of objects:
8402 // result[0] = object with a member for each parameter name, value of that member being an array of values
8403 // result[1..n] = one object for each parameter, with 'name' and 'value' members
8404 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
8406 var parseToken = function(match,p) {
8407 var n;
8408 if(match[p]) // Double quoted
8409 n = match[p];
8410 else if(match[p+1]) // Single quoted
8411 n = match[p+1];
8412 else if(match[p+2]) // Double-square-bracket quoted
8413 n = match[p+2];
8414 else if(match[p+3]) // Double-brace quoted
8415 try {
8416 n = match[p+3];
8417 if(allowEval)
8418 n = window.eval(n);
8419 } catch(ex) {
8420 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
8422 else if(match[p+4]) // Unquoted
8423 n = match[p+4];
8424 else if(match[p+5]) // empty quote
8425 n = "";
8426 return n;
8428 var r = [{}];
8429 var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
8430 var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
8431 var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
8432 var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
8433 var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
8434 var emptyQuote = "((?:\"\")|(?:''))";
8435 var skipSpace = "(?:\\s*)";
8436 var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
8437 var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
8438 var params = [];
8439 do {
8440 var match = re.exec(this);
8441 if(match) {
8442 var n = parseToken(match,1);
8443 if(noNames) {
8444 r.push({name:"",value:n});
8445 } else {
8446 var v = parseToken(match,8);
8447 if(v == null && defaultName) {
8448 v = n;
8449 n = defaultName;
8450 } else if(v == null && defaultValue) {
8451 v = defaultValue;
8453 r.push({name:n,value:v});
8454 if(cascadeDefaults) {
8455 defaultName = n;
8456 defaultValue = v;
8460 } while(match);
8461 // Summarise parameters into first element
8462 for(var t=1; t<r.length; t++) {
8463 if(r[0][r[t].name])
8464 r[0][r[t].name].push(r[t].value);
8465 else
8466 r[0][r[t].name] = [r[t].value];
8468 return r;
8471 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
8472 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
8473 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
8474 String.prototype.readMacroParams = function()
8476 var p = this.parseParams("list",null,true,true);
8477 var n = [];
8478 for(var t=1; t<p.length; t++)
8479 n.push(p[t].value);
8480 return n;
8483 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
8484 String.prototype.readBracketedList = function(unique)
8486 var p = this.parseParams("list",null,false,true);
8487 var n = [];
8488 for(var t=1; t<p.length; t++) {
8489 if(p[t].value)
8490 n.pushUnique(p[t].value,unique);
8492 return n;
8495 // Returns array with start and end index of chunk between given start and end marker, or undefined.
8496 String.prototype.getChunkRange = function(start,end)
8498 var s = this.indexOf(start);
8499 if(s != -1) {
8500 s += start.length;
8501 var e = this.indexOf(end,s);
8502 if(e != -1)
8503 return [s,e];
8507 // Replace a chunk of a string given start and end markers
8508 String.prototype.replaceChunk = function(start,end,sub)
8510 var r = this.getChunkRange(start,end);
8511 return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
8514 // Returns a chunk of a string between start and end markers, or undefined
8515 String.prototype.getChunk = function(start,end)
8517 var r = this.getChunkRange(start,end);
8518 if(r)
8519 return this.substring(r[0],r[1]);
8523 // Static method to bracket a string with double square brackets if it contains a space
8524 String.encodeTiddlyLink = function(title)
8526 return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
8529 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
8530 String.encodeTiddlyLinkList = function(list)
8532 if(list) {
8533 var results = [];
8534 for(var t=0; t<list.length; t++)
8535 results.push(String.encodeTiddlyLink(list[t]));
8536 return results.join(" ");
8537 } else {
8538 return "";
8542 // Convert a string as a sequence of name:"value" pairs into a hashmap
8543 String.prototype.decodeHashMap = function()
8545 var fields = this.parseParams("anon","",false);
8546 var r = {};
8547 for(var t=1; t<fields.length; t++)
8548 r[fields[t].name] = fields[t].value;
8549 return r;
8552 // Static method to encode a hashmap into a name:"value"... string
8553 String.encodeHashMap = function(hashmap)
8555 var r = [];
8556 for(var t in hashmap)
8557 r.push(t + ':"' + hashmap[t] + '"');
8558 return r.join(" ");
8561 // Static method to left-pad a string with 0s to a certain width
8562 String.zeroPad = function(n,d)
8564 var s = n.toString();
8565 if(s.length < d)
8566 s = "000000000000000000000000000".substr(0,d-s.length) + s;
8567 return s;
8570 String.prototype.startsWith = function(prefix)
8572 return !prefix || this.substring(0,prefix.length) == prefix;
8575 // Returns the first value of the given named parameter.
8576 function getParam(params,name,defaultValue)
8578 if(!params)
8579 return defaultValue;
8580 var p = params[0][name];
8581 return p ? p[0] : defaultValue;
8584 // Returns the first value of the given boolean named parameter.
8585 function getFlag(params,name,defaultValue)
8587 return !!getParam(params,name,defaultValue);
8590 // Substitute date components into a string
8591 Date.prototype.formatString = function(template)
8593 var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
8594 t = t.replace(/hh12/g,this.getHours12());
8595 t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
8596 t = t.replace(/hh/g,this.getHours());
8597 t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
8598 t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
8599 t = t.replace(/mm/g,this.getMinutes());
8600 t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
8601 t = t.replace(/ss/g,this.getSeconds());
8602 t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
8603 t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
8604 t = t.replace(/wYYYY/g,this.getYearForWeekNo());
8605 t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
8606 t = t.replace(/YYYY/g,this.getFullYear());
8607 t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
8608 t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
8609 t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
8610 t = t.replace(/MM/g,this.getMonth()+1);
8611 t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
8612 t = t.replace(/WW/g,this.getWeek());
8613 t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
8614 t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
8615 t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
8616 t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
8617 t = t.replace(/DD/g,this.getDate());
8618 return t;
8621 Date.prototype.getWeek = function()
8623 var dt = new Date(this.getTime());
8624 var d = dt.getDay();
8625 if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8626 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
8627 var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
8628 return Math.floor(n/7)+1;
8631 Date.prototype.getYearForWeekNo = function()
8633 var dt = new Date(this.getTime());
8634 var d = dt.getDay();
8635 if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8636 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
8637 return dt.getFullYear();
8640 Date.prototype.getHours12 = function()
8642 var h = this.getHours();
8643 return h > 12 ? h-12 : ( h > 0 ? h : 12 );
8646 Date.prototype.getAmPm = function()
8648 return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
8651 Date.prototype.daySuffix = function()
8653 return config.messages.dates.daySuffixes[this.getDate()-1];
8656 // Convert a date to local YYYYMMDDHHMM string format
8657 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
8659 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);
8662 // Convert a date to UTC YYYYMMDDHHMM string format
8663 Date.prototype.convertToYYYYMMDDHHMM = function()
8665 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);
8668 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
8669 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
8671 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);
8674 // Static method to create a date from a UTC YYYYMMDDHHMM format string
8675 Date.convertFromYYYYMMDDHHMM = function(d)
8677 return new Date(Date.UTC(parseInt(d.substr(0,4),10),
8678 parseInt(d.substr(4,2),10)-1,
8679 parseInt(d.substr(6,2),10),
8680 parseInt(d.substr(8,2),10),
8681 parseInt(d.substr(10,2),10),0,0));
8684 //--
8685 //-- RGB colour object
8686 //--
8688 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
8689 function RGB(r,g,b)
8691 this.r = 0;
8692 this.g = 0;
8693 this.b = 0;
8694 if(typeof r == "string") {
8695 if(r.substr(0,1) == "#") {
8696 if(r.length == 7) {
8697 this.r = parseInt(r.substr(1,2),16)/255;
8698 this.g = parseInt(r.substr(3,2),16)/255;
8699 this.b = parseInt(r.substr(5,2),16)/255;
8700 } else {
8701 this.r = parseInt(r.substr(1,1),16)/15;
8702 this.g = parseInt(r.substr(2,1),16)/15;
8703 this.b = parseInt(r.substr(3,1),16)/15;
8705 } else {
8706 var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
8707 var c = r.match(rgbPattern);
8708 if(c) {
8709 this.r = parseInt(c[1],10)/255;
8710 this.g = parseInt(c[2],10)/255;
8711 this.b = parseInt(c[3],10)/255;
8714 } else {
8715 this.r = r;
8716 this.g = g;
8717 this.b = b;
8719 return this;
8722 // Mixes this colour with another in a specified proportion
8723 // c = other colour to mix
8724 // f = 0..1 where 0 is this colour and 1 is the new colour
8725 // Returns an RGB object
8726 RGB.prototype.mix = function(c,f)
8728 return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
8731 // Return an rgb colour as a #rrggbb format hex string
8732 RGB.prototype.toString = function()
8734 return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
8735 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
8736 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
8739 //--
8740 //-- DOM utilities - many derived from www.quirksmode.org
8741 //--
8743 function drawGradient(place,horiz,colours)
8745 for(var t=0; t<= 100; t+=2) {
8746 var bar = document.createElement("div");
8747 place.appendChild(bar);
8748 bar.style.position = "absolute";
8749 bar.style.left = horiz ? t + "%" : 0;
8750 bar.style.top = horiz ? 0 : t + "%";
8751 bar.style.width = horiz ? (101-t) + "%" : "100%";
8752 bar.style.height = horiz ? "100%" : (101-t) + "%";
8753 bar.style.zIndex = -1;
8754 var f = t/100;
8755 var p = f*(colours.length-1);
8756 bar.style.backgroundColor = colours[Math.floor(p)].mix(colours[Math.ceil(p)],p-Math.floor(p)).toString();
8760 function createTiddlyText(theParent,theText)
8762 return theParent.appendChild(document.createTextNode(theText));
8765 function createTiddlyCheckbox(theParent,caption,checked,onChange)
8767 var cb = document.createElement("input");
8768 cb.setAttribute("type","checkbox");
8769 cb.onclick = onChange;
8770 theParent.appendChild(cb);
8771 cb.checked = checked;
8772 cb.className = "chkOptionInput";
8773 if(caption)
8774 wikify(caption,theParent);
8775 return cb;
8778 function createTiddlyElement(theParent,theElement,theID,theClass,theText,attribs)
8780 var e = document.createElement(theElement);
8781 if(theClass != null)
8782 e.className = theClass;
8783 if(theID != null)
8784 e.setAttribute("id",theID);
8785 if(theText != null)
8786 e.appendChild(document.createTextNode(theText));
8787 if(attribs){
8788 for(var n in attribs){
8789 e.setAttribute(n,attribs[n]);
8792 if(theParent != null)
8793 theParent.appendChild(e);
8794 return e;
8797 function addEvent(obj,type,fn)
8799 if(obj.attachEvent) {
8800 obj['e'+type+fn] = fn;
8801 obj[type+fn] = function(){obj['e'+type+fn](window.event);};
8802 obj.attachEvent('on'+type,obj[type+fn]);
8803 } else {
8804 obj.addEventListener(type,fn,false);
8808 function removeEvent(obj,type,fn)
8810 if(obj.detachEvent) {
8811 obj.detachEvent('on'+type,obj[type+fn]);
8812 obj[type+fn] = null;
8813 } else {
8814 obj.removeEventListener(type,fn,false);
8818 function addClass(e,theClass)
8820 var currClass = e.className.split(" ");
8821 if(currClass.indexOf(theClass) == -1)
8822 e.className += " " + theClass;
8825 function removeClass(e,theClass)
8827 var currClass = e.className.split(" ");
8828 var i = currClass.indexOf(theClass);
8829 while(i != -1) {
8830 currClass.splice(i,1);
8831 i = currClass.indexOf(theClass);
8833 e.className = currClass.join(" ");
8836 function hasClass(e,theClass)
8838 if(e.className) {
8839 if(e.className.split(" ").indexOf(theClass) != -1)
8840 return true;
8842 return false;
8845 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
8846 function findRelated(e,value,name,relative)
8848 name = name ? name : "tagName";
8849 relative = relative ? relative : "parentNode";
8850 if(name == "className") {
8851 while(e && !hasClass(e,value)) {
8852 e = e[relative];
8854 } else {
8855 while(e && e[name] != value) {
8856 e = e[relative];
8859 return e;
8862 // Resolve the target object of an event
8863 function resolveTarget(e)
8865 var obj;
8866 if(e.target)
8867 obj = e.target;
8868 else if(e.srcElement)
8869 obj = e.srcElement;
8870 if(obj.nodeType == 3) // defeat Safari bug
8871 obj = obj.parentNode;
8872 return obj;
8875 // Prevent an event from bubbling
8876 function stopEvent(e){
8877 var ev = e? e : window.event;
8878 ev.cancelBubble = true;
8879 if (ev.stopPropagation) ev.stopPropagation();
8880 return false;
8883 // Return the content of an element as plain text with no formatting
8884 function getPlainText(e)
8886 var text = "";
8887 if(e.innerText)
8888 text = e.innerText;
8889 else if(e.textContent)
8890 text = e.textContent;
8891 return text;
8894 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
8895 function ensureVisible(e)
8897 var posTop = findPosY(e);
8898 var posBot = posTop + e.offsetHeight;
8899 var winTop = findScrollY();
8900 var winHeight = findWindowHeight();
8901 var winBot = winTop + winHeight;
8902 if(posTop < winTop) {
8903 return posTop;
8904 } else if(posBot > winBot) {
8905 if(e.offsetHeight < winHeight)
8906 return posTop - (winHeight - e.offsetHeight);
8907 else
8908 return posTop;
8909 } else {
8910 return winTop;
8914 // Get the current width of the display window
8915 function findWindowWidth()
8917 return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
8920 // Get the current height of the display window
8921 function findWindowHeight()
8923 return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
8926 // Get the current horizontal page scroll position
8927 function findScrollX()
8929 return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
8932 // Get the current vertical page scroll position
8933 function findScrollY()
8935 return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
8938 function findPosX(obj)
8940 var curleft = 0;
8941 while(obj.offsetParent) {
8942 curleft += obj.offsetLeft;
8943 obj = obj.offsetParent;
8945 return curleft;
8948 function findPosY(obj)
8950 var curtop = 0;
8951 while(obj.offsetParent) {
8952 curtop += obj.offsetTop;
8953 obj = obj.offsetParent;
8955 return curtop;
8958 // Blur a particular element
8959 function blurElement(e)
8961 if(e != null && e.focus && e.blur) {
8962 e.focus();
8963 e.blur();
8967 // Create a non-breaking space
8968 function insertSpacer(place)
8970 var e = document.createTextNode(String.fromCharCode(160));
8971 if(place)
8972 place.appendChild(e);
8973 return e;
8976 // Remove all children of a node
8977 function removeChildren(e)
8979 while(e && e.hasChildNodes())
8980 removeNode(e.firstChild);
8983 // Remove a node and all it's children
8984 function removeNode(e)
8986 scrubNode(e);
8987 e.parentNode.removeChild(e);
8990 // Remove any event handlers or non-primitve custom attributes
8991 function scrubNode(e)
8993 if(!config.browser.isIE)
8994 return;
8995 var att = e.attributes;
8996 if(att) {
8997 for(var t=0; t<att.length; t++) {
8998 var n = att[t].name;
8999 if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
9000 try {
9001 e[n] = null;
9002 } catch(ex) {
9007 var c = e.firstChild;
9008 while(c) {
9009 scrubNode(c);
9010 c = c.nextSibling;
9014 // Add a stylesheet, replacing any previous custom stylesheet
9015 function setStylesheet(s,id,doc)
9017 if(!id)
9018 id = "customStyleSheet";
9019 if(!doc)
9020 doc = document;
9021 var n = doc.getElementById(id);
9022 if(doc.createStyleSheet) {
9023 // Test for IE's non-standard createStyleSheet method
9024 if(n)
9025 n.parentNode.removeChild(n);
9026 // This failed without the &nbsp;
9027 doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
9028 } else {
9029 if(n) {
9030 n.replaceChild(doc.createTextNode(s),n.firstChild);
9031 } else {
9032 n = doc.createElement("style");
9033 n.type = "text/css";
9034 n.id = id;
9035 n.appendChild(doc.createTextNode(s));
9036 doc.getElementsByTagName("head")[0].appendChild(n);
9041 function removeStyleSheet(id)
9043 var e = document.getElementById(id);
9044 if(e)
9045 e.parentNode.removeChild(e);
9048 // Force the browser to do a document reflow when needed to workaround browser bugs
9049 function forceReflow()
9051 if(config.browser.isGecko) {
9052 setStylesheet("body {top:-1em;margin-top:1em;}");
9053 setStylesheet("");
9057 // Replace the current selection of a textarea or text input and scroll it into view
9058 function replaceSelection(e,text)
9060 if(e.setSelectionRange) {
9061 var oldpos = e.selectionStart;
9062 var isRange = e.selectionEnd > e.selectionStart;
9063 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
9064 e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
9065 var linecount = e.value.split('\n').length;
9066 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
9067 e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
9068 } else if(document.selection) {
9069 var range = document.selection.createRange();
9070 if(range.parentElement() == e) {
9071 var isCollapsed = range.text == "";
9072 range.text = text;
9073 if(!isCollapsed) {
9074 range.moveStart('character', -text.length);
9075 range.select();
9081 // Returns the text of the given (text) node, possibly merging subsequent text nodes
9082 function getNodeText(e)
9084 var t = "";
9085 while(e && e.nodeName == "#text") {
9086 t += e.nodeValue;
9087 e = e.nextSibling;
9089 return t;
9092 //--
9093 //-- LoaderBase and SaverBase
9094 //--
9096 function LoaderBase() {}
9098 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9100 var title = this.getTitle(store,node);
9101 if(title) {
9102 var tiddler = store.createTiddler(title);
9103 this.internalizeTiddler(store,tiddler,title,node);
9104 tiddlers.push(tiddler);
9108 LoaderBase.prototype.loadTiddlers = function(store,nodes)
9110 var tiddlers = [];
9111 for(var t = 0; t < nodes.length; t++) {
9112 try {
9113 this.loadTiddler(store,nodes[t],tiddlers);
9114 } catch(ex) {
9115 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9118 return tiddlers;
9121 function SaverBase() {}
9123 SaverBase.prototype.externalize = function(store)
9125 var results = [];
9126 var tiddlers = store.getTiddlers("title");
9127 for(var t = 0; t < tiddlers.length; t++)
9128 results.push(this.externalizeTiddler(store,tiddlers[t]));
9129 return results.join("\n");
9132 //--
9133 //-- TW21Loader (inherits from LoaderBase)
9134 //--
9136 function TW21Loader() {}
9138 TW21Loader.prototype = new LoaderBase();
9140 TW21Loader.prototype.getTitle = function(store,node)
9142 var title = null;
9143 if(node.getAttribute) {
9144 title = node.getAttribute("title");
9145 if(!title)
9146 title = node.getAttribute("tiddler");
9148 if(!title && node.id) {
9149 var lenPrefix = store.idPrefix.length;
9150 if (node.id.substr(0,lenPrefix) == store.idPrefix)
9151 title = node.id.substr(lenPrefix);
9153 return title;
9156 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9158 var e = node.firstChild;
9159 var text = null;
9160 if(node.getAttribute("tiddler")) {
9161 text = getNodeText(e).unescapeLineBreaks();
9162 } else {
9163 while(e.nodeName!="PRE" && e.nodeName!="pre") {
9164 e = e.nextSibling;
9166 text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
9168 var modifier = node.getAttribute("modifier");
9169 var c = node.getAttribute("created");
9170 var m = node.getAttribute("modified");
9171 var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
9172 var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
9173 var tags = node.getAttribute("tags");
9174 var fields = {};
9175 var attrs = node.attributes;
9176 for(var i = attrs.length-1; i >= 0; i--) {
9177 var name = attrs[i].name;
9178 if (attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
9179 fields[name] = attrs[i].value.unescapeLineBreaks();
9182 tiddler.assign(title,text,modifier,modified,tags,created,fields);
9183 return tiddler;
9186 //--
9187 //-- TW21Saver (inherits from SaverBase)
9188 //--
9190 function TW21Saver() {}
9192 TW21Saver.prototype = new SaverBase();
9194 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
9196 try {
9197 var extendedAttributes = "";
9198 var usePre = config.options.chkUsePreForStorage;
9199 store.forEachField(tiddler,
9200 function(tiddler,fieldName,value) {
9201 // don't store stuff from the temp namespace
9202 if(typeof value != "string")
9203 value = "";
9204 if (!fieldName.match(/^temp\./))
9205 extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
9206 },true);
9207 var created = tiddler.created.convertToYYYYMMDDHHMM();
9208 var modified = tiddler.modified.convertToYYYYMMDDHHMM();
9209 var vdate = version.date.convertToYYYYMMDDHHMM();
9210 var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
9211 attributes += (usePre && modified == created) ? "" : ' modified="' + modified +'"';
9212 attributes += (usePre && created == vdate) ? "" :' created="' + created + '"';
9213 var tags = tiddler.getTags();
9214 if(!usePre || tags)
9215 attributes += ' tags="' + tags.htmlEncode() + '"';
9216 return ('<div %0="%1"%2%3>%4</'+'div>').format([
9217 usePre ? "title" : "tiddler",
9218 tiddler.title.htmlEncode(),
9219 attributes,
9220 extendedAttributes,
9221 usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9223 } catch (ex) {
9224 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9228 //--
9229 //-- End of scripts
9230 //--
9231 //]]>
9232 </script>
9233 <script type="text/javascript">
9234 //<![CDATA[
9235 if(useJavaSaver)
9236 document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9237 //]]>
9238 </script>
9239 <!--POST-SCRIPT-START-->
9241 <!--POST-SCRIPT-END-->
9242 </body>
9243 </html>