Merge branch 'master' of git+ssh://wrburdick@repo.or.cz/srv/git/SauerbratenRemote
[SauerbratenRemote.git] / P2PMud-sauerbraten / index.html
blob293b992e4ca1d2e80890e4da2d3f5ba9696aa855
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3 <head>
4 <script id="versionArea" type="text/javascript">
5 //<![CDATA[
6 var version = {title: "TiddlyWiki", major: 2, minor: 4, revision: 1, date: new Date("Aug 4, 2008"), extensions: {}};
7 //]]>
8 </script>
9 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
10 <meta name="copyright" content="
11 TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
13 Copyright (c) UnaMesa Association 2004-2008
15 Redistribution and use in source and binary forms, with or without modification,
16 are permitted provided that the following conditions are met:
18 Redistributions of source code must retain the above copyright notice, this
19 list of conditions and the following disclaimer.
21 Redistributions in binary form must reproduce the above copyright notice, this
22 list of conditions and the following disclaimer in the documentation and/or other
23 materials provided with the distribution.
25 Neither the name of the UnaMesa Association nor the names of its contributors may be
26 used to endorse or promote products derived from this software without specific
27 prior written permission.
29 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY
30 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
31 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
32 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
33 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
34 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
37 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
38 DAMAGE.
39 " />
40 <!--PRE-HEAD-START-->
41 <!--{{{-->
42 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
43 <!--}}}-->
44 <!--PRE-HEAD-END-->
45 <title> The Killer App of the Future! Reloaded </title>
46 <style id="styleArea" type="text/css">
47 #saveTest {display:none;}
48 #messageArea {display:none;}
49 #copyright {display:none;}
50 #storeArea {display:none;}
51 #storeArea div {padding:0.5em; margin:1em 0em 0em 0em; border-color:#fff #666 #444 #ddd; border-style:solid; border-width:2px; overflow:auto;}
52 #shadowArea {display:none;}
53 #javascriptWarning {width:100%; text-align:center; font-weight:bold; background-color:#dd1100; color:#fff; padding:1em 0em;}
54 </style>
55 <!--POST-HEAD-START-->
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 .wizard .notChanged {background:transparent;}
155 .wizard .changedLocally {background:#80ff80;}
156 .wizard .changedServer {background:#8080ff;}
157 .wizard .changedBoth {background:#ff8080;}
158 .wizard .notFound {background:#ffff80;}
159 .wizard .putToServer {background:#ff80ff;}
160 .wizard .gotFromServer {background:#80ffff;}
162 #messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
163 #messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
165 .popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
167 .popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
168 .popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
169 .popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
170 .popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
171 .popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
172 .popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
173 .popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
174 .listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
176 .tiddler .defaultCommand {font-weight:bold;}
178 .shadow .title {color:[[ColorPalette::TertiaryDark]];}
180 .title {color:[[ColorPalette::SecondaryDark]];}
181 .subtitle {color:[[ColorPalette::TertiaryDark]];}
183 .toolbar {color:[[ColorPalette::PrimaryMid]];}
184 .toolbar a {color:[[ColorPalette::TertiaryLight]];}
185 .selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
186 .selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
188 .tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
189 .selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
190 .tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
191 .tagging .button, .tagged .button {border:none;}
193 .footer {color:[[ColorPalette::TertiaryLight]];}
194 .selected .footer {color:[[ColorPalette::TertiaryMid]];}
196 .sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
197 .sparktick {background:[[ColorPalette::PrimaryDark]];}
199 .error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
200 .warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
201 .lowlight {background:[[ColorPalette::TertiaryLight]];}
203 .zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
205 .imageLink, #displayArea .imageLink {background:transparent;}
207 .annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
209 .viewer .listTitle {list-style-type:none; margin-left:-2em;}
210 .viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
211 .viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
213 .viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
214 .viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
215 .viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
217 .viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
218 .viewer code {color:[[ColorPalette::SecondaryDark]];}
219 .viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
221 .highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
223 .editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
224 .editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
225 .editorFooter {color:[[ColorPalette::TertiaryMid]];}
227 #backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
228 #backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
229 #backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
230 #backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
231 #backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
232 #backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
233 #backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
234 .backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
235 .backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
236 #backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
237 /*}}}*/</pre>
238 </div>
239 <div title="StyleSheetLayout">
240 <pre>/*{{{*/
241 * html .tiddler {height:1%;}
243 body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
245 h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
246 h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
247 h4,h5,h6 {margin-top:1em;}
248 h1 {font-size:1.35em;}
249 h2 {font-size:1.25em;}
250 h3 {font-size:1.1em;}
251 h4 {font-size:1em;}
252 h5 {font-size:.9em;}
254 hr {height:1px;}
256 a {text-decoration:none;}
258 dt {font-weight:bold;}
260 ol {list-style-type:decimal;}
261 ol ol {list-style-type:lower-alpha;}
262 ol ol ol {list-style-type:lower-roman;}
263 ol ol ol ol {list-style-type:decimal;}
264 ol ol ol ol ol {list-style-type:lower-alpha;}
265 ol ol ol ol ol ol {list-style-type:lower-roman;}
266 ol ol ol ol ol ol ol {list-style-type:decimal;}
268 .txtOptionInput {width:11em;}
270 #contentWrapper .chkOptionInput {border:0;}
272 .externalLink {text-decoration:underline;}
274 .indent {margin-left:3em;}
275 .outdent {margin-left:3em; text-indent:-3em;}
276 code.escaped {white-space:nowrap;}
278 .tiddlyLinkExisting {font-weight:bold;}
279 .tiddlyLinkNonExisting {font-style:italic;}
281 /* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
282 a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
284 #mainMenu .tiddlyLinkExisting,
285 #mainMenu .tiddlyLinkNonExisting,
286 #sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
287 #sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
289 .header {position:relative;}
290 .header a:hover {background:transparent;}
291 .headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
292 .headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
294 .siteTitle {font-size:3em;}
295 .siteSubtitle {font-size:1.2em;}
297 #mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
299 #sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
300 #sidebarOptions {padding-top:0.3em;}
301 #sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
302 #sidebarOptions input {margin:0.4em 0.5em;}
303 #sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
304 #sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
305 #sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
306 #sidebarTabs .tabContents {width:15em; overflow:hidden;}
308 .wizard {padding:0.1em 1em 0em 2em;}
309 .wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
310 .wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
311 .wizardStep {padding:1em 1em 1em 1em;}
312 .wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
313 .wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
314 .wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
315 .wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
317 #messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
318 .messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
319 #messageArea a {text-decoration:underline;}
321 .tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
322 .popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
324 .popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
325 .popup .popupMessage {padding:0.4em;}
326 .popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
327 .popup li.disabled {padding:0.4em;}
328 .popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
329 .listBreak {font-size:1px; line-height:1px;}
330 .listBreak div {margin:2px 0;}
332 .tabset {padding:1em 0em 0em 0.5em;}
333 .tab {margin:0em 0em 0em 0.25em; padding:2px;}
334 .tabContents {padding:0.5em;}
335 .tabContents ul, .tabContents ol {margin:0; padding:0;}
336 .txtMainTab .tabContents li {list-style:none;}
337 .tabContents li.listLink { margin-left:.75em;}
339 #contentWrapper {display:block;}
340 #splashScreen {display:none;}
342 #displayArea {margin:1em 17em 0em 14em;}
344 .toolbar {text-align:right; font-size:.9em;}
346 .tiddler {padding:1em 1em 0em 1em;}
348 .missing .viewer,.missing .title {font-style:italic;}
350 .title {font-size:1.6em; font-weight:bold;}
352 .missing .subtitle {display:none;}
353 .subtitle {font-size:1.1em;}
355 .tiddler .button {padding:0.2em 0.4em;}
357 .tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
358 .isTag .tagging {display:block;}
359 .tagged {margin:0.5em; float:right;}
360 .tagging, .tagged {font-size:0.9em; padding:0.25em;}
361 .tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
362 .tagClear {clear:both;}
364 .footer {font-size:.9em;}
365 .footer li {display:inline;}
367 .annotation {padding:0.5em; margin:0.5em;}
369 * html .viewer pre {width:99%; padding:0 0 1em 0;}
370 .viewer {line-height:1.4em; padding-top:0.5em;}
371 .viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
372 .viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
373 .viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
375 .viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
376 .viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
377 table.listView {font-size:0.85em; margin:0.8em 1.0em;}
378 table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
380 .viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
381 .viewer code {font-size:1.2em; line-height:1.4em;}
383 .editor {font-size:1.1em;}
384 .editor input, .editor textarea {display:block; width:100%; font:inherit;}
385 .editorFooter {padding:0.25em 0em; font-size:.9em;}
386 .editorFooter .button {padding-top:0px; padding-bottom:0px;}
388 .fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
390 .sparkline {line-height:1em;}
391 .sparktick {outline:0;}
393 .zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
394 .zoomer div {padding:1em;}
396 * html #backstage {width:99%;}
397 * html #backstageArea {width:99%;}
398 #backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
399 #backstageToolbar {position:relative;}
400 #backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
401 #backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
402 #backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
403 #backstage {position:relative; width:100%; z-index:50;}
404 #backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
405 .backstagePanelFooter {padding-top:0.2em; float:right;}
406 .backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
407 #backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
409 .whenBackstage {display:none;}
410 .backstageVisible .whenBackstage {display:block;}
411 /*}}}*/</pre>
412 </div>
413 <div title="StyleSheetLocale">
414 <pre>/***
415 StyleSheet for use when a translation requires any css style changes.
416 This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
417 ***/
418 /*{{{*/
419 body {font-size:0.8em;}
420 #sidebarOptions {font-size:1.05em;}
421 #sidebarOptions a {font-style:normal;}
422 #sidebarOptions .sliderPanel {font-size:0.95em;}
423 .subtitle {font-size:0.8em;}
424 .viewer table.listView {font-size:0.95em;}
425 /*}}}*/</pre>
426 </div>
427 <div title="StyleSheetPrint">
428 <pre>/*{{{*/
429 @media print {
430 #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
431 #displayArea {margin: 1em 1em 0em 1em;}
432 /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
433 noscript {display:none;}
435 /*}}}*/</pre>
436 </div>
437 <div title="PageTemplate">
438 <pre>&lt;!--{{{--&gt;
439 &lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
440 &lt;div class='headerShadow'&gt;
441 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
442 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
443 &lt;/div&gt;
444 &lt;div class='headerForeground'&gt;
445 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
446 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
447 &lt;/div&gt;
448 &lt;/div&gt;
449 &lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
450 &lt;div id='sidebar'&gt;
451 &lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
452 &lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
453 &lt;/div&gt;
454 &lt;div id='displayArea'&gt;
455 &lt;div id='messageArea'&gt;&lt;/div&gt;
456 &lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
457 &lt;/div&gt;
458 &lt;!--}}}--&gt;</pre>
459 </div>
460 <div title="ViewTemplate">
461 <pre>&lt;!--{{{--&gt;
462 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
463 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
464 &lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
465 &lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
466 &lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
467 &lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
468 &lt;div class='tagClear'&gt;&lt;/div&gt;
469 &lt;!--}}}--&gt;</pre>
470 </div>
471 <div title="EditTemplate">
472 <pre>&lt;!--{{{--&gt;
473 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
474 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
475 &lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
476 &lt;div macro='annotations'&gt;&lt;/div&gt;
477 &lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
478 &lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
479 &lt;!--}}}--&gt;</pre>
480 </div>
481 <div title="GettingStarted">
482 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
483 * SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
484 * MainMenu: The menu (usually on the left)
485 * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
486 You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
487 </div>
488 <div title="OptionsPanel">
489 <pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser
491 Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)
493 &lt;&lt;option txtUserName&gt;&gt;
494 &lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
495 &lt;&lt;option chkAutoSave&gt;&gt; AutoSave
496 &lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
497 &lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
498 &lt;&lt;option chkAnimate&gt;&gt; EnableAnimations
500 ----
501 Also see AdvancedOptions</pre>
502 </div>
503 <div title="ImportTiddlers">
504 <pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
505 </div>
506 </div>
507 <!--POST-SHADOWAREA-->
508 <div id="storeArea">
509 <div title="Commands" modifier="Zot" created="200806191121" changecount="1">
510 <pre></pre>
511 </div>
512 <div title="CompilingInLinux" modifier="YourName" created="200809032031" modified="200809032034" changecount="6">
513 <pre>Get [[Eclipse 3.4|http://www.eclipse.org]]</pre>
514 </div>
515 <div title="Completed" modifier="BillBurdick" created="200808151459" changecount="1">
516 <pre>*[''DONE''] The Wedge
517 **Remote scripting
518 **Sauerbraten scripting hooks into event flows using the Chain of Command pattern
519 **Simple server
520 *[''DONE'']Two guys in a box
521 **Peer sends login request
522 **Master sends reference to map and initial location (coordinates and orientation)
523 **location updates
524 **Chat
525 *[''DONE'']Tentacles
526 **N player enhancements
527 **use Scribe instead of point-to-point messaging
528 *[''DONE'']~Shub-Niggurath
529 **map sharing with PAST
530 **Wow Mode
531 **HUD
532 ***remote status
533 </pre>
534 </div>
535 <div title="DefaultTiddlers" modifier="BillBurdick" created="200806171607" modified="200809130549" changecount="16">
536 <pre>[[Plexus]]
537 StartingOut
538 RoadMap
539 UseCases
540 [[News]]</pre>
541 </div>
542 <div title="Done" modifier="WRB" created="200807192043" modified="200807192045" changecount="2">
543 <pre>7/18/08 Roy: Upgrade to latest code base (2008 6/19)
544 </pre>
545 </div>
546 <div title="EmergencyWorspaceReconstruction" modifier="BillBurdick" created="200809272113" changecount="1">
547 <pre># quit eclipse
548 # find your eclipse workspace directory
549 # rename .metadata to .metadata.bak
550 # restart eclilpse
551 # File-&gt;Import...
552 # General-&gt;Existing Projects into Workspace
553 # select your workspace directory as the root directory
554 # import the projects</pre>
555 </div>
556 <div title="FeatureList" modifier="BillBurdick" created="200809161520" modified="200809241829" changecount="3">
557 <pre>Dev Features (copied from Roy's post on the cubedevblog)
559 *P2P Networking – All information is stored in a cloud. Models, maps, everything. No central server means that there’s no downtime, and no need to worry about which server your friends are on. Uses Free Pastry for communication. Although this is all independent of our Sauer mods, it's a big deal, so I had to mention it!
561 *Socket level control of Sauerbraten - We've spliced in a socket to communicate with Sauer so you can control the engine from any outside application that uses this protocol. We're writing our stuff in Groovy but you could make your own game using Python or what have you. This allows developers to use whatever language they wish.
563 *World of Warcraft style mouse handling - Sauers mouse handling is frightening for anything other than an FPS, so we've reworked it to be exactly like WoW's. You can even orbit the camera around your toon for screenshots. There are still some bugs/quirks with this in edit mode, but in general it makes editing Sauer maps so much easier.
565 *Randomly generated maze maps - Nothing too fancy, but we don't have many artists helping on the project yet so anything that up the appearance of content is good. Our sauer socket protocol has additional functions to allow external creation of maps.
566 *Importing of Dwarf Fortress maps - There's a popular indie game called Dwarf Fortress which uses old school ASCII style maps. We have an importer that can load these maps into Sauer using the same interface as the random maze generation.
568 *Custom models/costumes – I wrote a model importer that imports md2 and md3 models and creates the md3.cfg so you can load them into Sauer. We modded the engine to allow changing player models on the fly.
570 *Glass Pane Image Layer. Add in images and text to the hud on the fly.
571 !Upcoming
572 *Sandboxed, mobile code (attached to maps, libraries in the cloud)</pre>
573 </div>
574 <div title="Flow" modifier="Zot" created="200806191220" modified="200806191220" changecount="2">
575 <pre>~PCs can only perform limited actions without approval
576 * talking
577 * looking around
578 * walking within range of the current beacon
580 Actions requiring approval
581 * moving to another beacon
582 * shooting things
583 * moving things
584 * opening things (doors, chests, etc.)
586 When a PC is awaiting approval, it FREEZES
587 * it doesn't queue up multiple requests and overload the moderator
589 The hudgun will change based on context (i.e. where the cursor is)
590 * walking
591 * interacting with an object
592 * interacting with a monster
594 Interaction
595 * if there is one choice, indicate it with the hudgun
596 * if there is more than one choice
597 ** indicate multiple with the hudgun
598 ** pop up a menu for the player to choose an appropriate action
599 </pre>
600 </div>
601 <div title="Goals" modifier="WRB" created="200807151359" changecount="1">
602 <pre>*Basic Interaction
603 *Networking
604 **Sauerbraten uses a high performance, client/server network layer.
605 **Since we are using Java to control Sauerbraten, we have the option of using one of several available p2p platforms instead of Sauerbraten's networking
606 **This may unacceptably impact performance, but we'll start off using [[Pastry|http://freepastry.rice.edu]] and see where that leads us
607 **Since MUDS are more social, they may not need such rapid interaction (the ones we wrote in the late 80s were certainly a lot slower than what we can do today).
608 *Moderation
609 *MUD (automatic moderation)
610 *MudLingua</pre>
611 </div>
612 <div title="HowToReallyCleanAnEclipseProject" modifier="BillBurdick" created="200809272000" modified="200809272020" changecount="2">
613 <pre># select the project in the Package Explorer view
614 # uncheck the menu item Project-&gt;Build Automatically
615 # choose the menu item Project-&gt;Clean...
616 # make sure Clean projects selected below is selected
617 # make sure your project is selected in the list
618 # click OK
619 # recheck Project-&gt;Build Automatically
620 # if the project is a groovy project, right-click on it and choose Refresh</pre>
621 </div>
622 <div title="License" modifier="BillBurdick" created="200808291619" changecount="1">
623 <pre>Plexus uses the very friendly, ZLIB open source license (http://www.opensource.org/licenses/zlib-license.php):
625 Copyright (c) 2008 TEAM CTHULHU, Bill Burdick, Roy Riggs
627 This software is provided 'as-is', without any express or implied
628 warranty. In no event will the authors be held liable for any damages
629 arising from the use of this software.
631 Permission is granted to anyone to use this software for any purpose,
632 including commercial applications, and to alter it and redistribute it
633 freely, subject to the following restrictions:
635 1. The origin of this software must not be misrepresented; you must not
636 claim that you wrote the original software. If you use this software
637 in a product, an acknowledgment in the product documentation would be
638 appreciated but is not required.
640 2. Altered source versions must be plainly marked as such, and must not be
641 misrepresented as being the original software.
643 3. This notice may not be removed or altered from any source
644 distribution.</pre>
645 </div>
646 <div title="MainMenu" modifier="BillBurdick" created="200806171608" modified="200809280522" changecount="19">
647 <pre>[img[TEAM CTHULHU|docs/images/cthulhu.jpg][http://teamcthulhu.com]]
648 [[Philosophy]]
649 MobileCode
650 ToDo
651 UseCases
652 PotentialIssues
653 CompilingInLinux
654 FeatureList
655 [[Commands|docs/COMMANDS.txt]]
656 PeerToPeer
657 MudLingua
658 RoadMap
659 [[Flow]]
660 OldDocs
661 ----
662 MainMenu
663 DefaultTiddlers
665 [[TiddlyWiki|http://www.tiddlywiki.com]] &lt;&lt;version&gt;&gt;</pre>
666 </div>
667 <div title="MapStorage" modifier="BillBurdick" created="200808080153" modified="200808122251" changecount="8">
668 <pre>!ToDo
669 groovy scripts
670 !Goals
671 [DONE] Directories
672 *[DONE] stored by ID
673 *[DONE] properties file: path -&gt; id (+ metadata)
674 [DONE] Root documents
675 *[DONE] directories stored by well known keys, and mutable
676 [DONE] Files
677 *[DONE] stored by ID
678 !Notes
679 *way to save the cloud to disk that handles old versions
680 **save replace operation in cloud queue
681 **store new map
682 **update map list
683 **remove old map
684 **update cloud queue
685 *save the map list to the cloud
686 *way to save map to the cloud</pre>
687 </div>
688 <div title="MobileCode" modifier="BillBurdick" created="200809280523" modified="200809280742" changecount="6">
689 <pre>{{cthulhu{
690 !Mobile Code
691 What can you use a peer-to-peer network for? Messaging and file sharing. Duh.
693 How about a hive-mind?
695 We have a new feature now: Groovy code for maps. In Sauerbraten, your map is stored in a map.ogz file. Plexus stores each map in its own directory in your profile's cache. You can use textures and sounds just like you would normally, store in the packages directory and also from the current map's directory, using a variable that provides the cache directory which currently contains the map. As usualy, there is a map.cfg file, but there is also an important map.groovy file which you can provide.
697 Map.groovy can contain Groovy code which executes in a sandbox in the peer. This means you can load other peoples' maps with their associated Groovy code and you don't have to worry about their code accessing your files. We use the Java sandbox to restrict read and write access to only the map's directory (among other draconian restrictions). Please, if you are into this sort of thing, check out our Sandbox.groovy code to verify for yourself! So you have groovy code that can live in each map. We also provide a mapProps map (i.e. hash table) for you which you can use to store persistent map data. It's reinitialized when maps load, but map.groovy can save and load data with it. And we provide a globalProps map that maps can share with each other. We'll also provide some cloud storage access for stronger persistence.
699 OK, so you have code for each map. Now you can make brains for shop keepers, traps, etc. and of course you can use this to implement game mechanics. In the next weeks and months, we're going to implement &quot;cloud libraries&quot; which you can use in your map.groovy code. This will allow people to write game mechanics and other frameworks that you can use across different maps. We'll be upgrading the plexus cache to be a real Git cache (it already shares some ideas with Git anyway; it implements content-addressable files and directories) and you'll be able to configure your peer to back up selected parts of the cloud in your own private Git cache, for good measure. This will help protect the integrity of the cloud, since there is no central server.
701 That's all kind of a logical extension of what we have, but there's something more that I'm pretty interested in seeing actually run. We plan to allow distributed control of brains. The current idea is to make brains //relatively// stateless, so that each &quot;tick,&quot; a brain &quot;wakes up&quot; and starts over (kind of like the movie Memento). This model allows brains to transfer around between peers. The peer in a world with an ID closest to the brain's ID will control it. This is using a DHT to arbitrate control over the set of available brains. When peers join or leave from a world, brains will shuffle around to accommodate the new state of the ring (Pastry nodes are arranged in a ring, according to their ~IDs). To help manage this, brains will have two &quot;storage areas:&quot; a local &quot;peer area&quot; for incidental bookkeeping and a global &quot;cloud area&quot; for major information that has to be restored when a brain jarringly transfers over to another peer.
703 All of the peers have the code for all of the brains because they come with the map but a given brain will only have one 'pilot peer' at a time. A world's DHT consists of only the peers in that world, not the full Plexus Pastry ring. Pastry doesn't have &quot;subrings&quot; right now that would make this easy to implement, but each world does use a SCRIBE topic for communication, so we can leverage that. My current approach is to use a cloud properties object (like the one that Plexus uses for players, maps, and costumes) to assign brains to peers. Maybe the topic root can be in charge of shuffling brains after a join or leave. Right now, cloud properties are not stored in PAST -- they are read in by the root peer and then managed entirely with SCRIBE and direct messaging (for booting), but that can change if it's not good enough.
704 }}}</pre>
705 </div>
706 <div title="MudLingua" modifier="Zot" created="200806191123" changecount="1">
707 <pre>A primordial language of creation</pre>
708 </div>
709 <div title="News" modifier="Zot" created="200806190726" changecount="1">
710 <pre>!2008 6/19 Capture the Flag
711 Well, a new version of Sauerbraten is out: Capture the Flag (http://www.cubeengine.com/forum.php4?action=display_thread&amp;thread_id=1782). I made a &quot;dist&quot; branch and put it in there and tagged the assassin version and that one (with date tags). I'll have to merge that into the main branch now. Let's see how well git's famous merging capabilities work...</pre>
712 </div>
713 <div title="OldDocs" modifier="BillBurdick" created="200806191152" modified="200806191156" changecount="2">
714 <pre>[[Philosophy|docs/p2pmud-old/philosophy.html]]
715 [[Tutorial|docs/p2pmud-old/tutorial.html]]
716 [[Parser|docs/p2pmud-old/parser_tutorial.html]]
717 </pre>
718 </div>
719 <div title="PeerToPeer" modifier="Zot" created="200806191132" changecount="1">
720 <pre>Here is a slippery topic. At the most basic level, peer to peer (p2p) used to mean that applications communicate directly instead of having to use a server (or several servers). Peer to peer has come to mean the use of an &quot;overlay&quot; network atop a conventional network. There is no central control in a pure p2p network.</pre>
721 </div>
722 <div title="Philosophy" modifier="BillBurdick" created="200809280504" modified="200810040542" changecount="14">
723 <pre>!NOTE! THIS IS FROM OUR PREVIOUS PROJECT, ~P2PMUD, BUT THE PHILOSOPHY CARRIES ON IN PLEXUS
725 Plexus uses Groovy, instead of Javascript
726 Plexus uses Sauerbraten and SwingX, instead of a web browser (XUL and HTML)
727 Plexus uses Pastry for true peer-to-peer networking, instead of AJAX
730 {{cthulhu{
731 &lt;html&gt;
732 &lt;p&gt;&lt;h2&gt;Basic design goals and philosophy behind the p2pmud project&lt;/h2&gt;
733 &lt;p&gt;Read this if you're interested in developing with p2pmud
735 &lt;p&gt;p2pmud is written in a combination of XUL and Javascript. XUL is an XML based language used for describing UI
736 (user interface) and if you are a coding/scripting type of person you've surely heard of Javascript. Odds are
737 however, you probably don't really know today's Javascript.
739 &lt;p&gt;Javascript has silently evolved over the last few years. While it started out as a clunkly little scripting
740 language, it has blossomed into a very dynamic, full-featured language. Java may have gotten the spotlight
741 for a long time, but its going to have to move over and make way for Javascript sometime soon.
743 &lt;h2&gt;p2pmud is a layered system&lt;/h2&gt;
744 Each layer builds or relies on the previous layers beneath them. You can choose
745 to pitch in and help out on the project at any layer(s) that suit you. There is a core layer, Level I, which
746 provides a foundation layer. The next layer implementing actual game mechanics, is called Level II. Sometimes
747 people are referred to by their Level, where Level here describes what they like to do. Someone who likes to
748 create adventures and populate them with monsters and treasure is referred to Level III. They may or may not
749 write anything in Javascript, they may just use what was provided by Level II. Level IV is considered a player
750 who doesn't code or build at all, they just want to play. Level IV's are not to be looked down upon, after all,
751 they are ultimately the ones we went to all this trouble for!
753 &lt;h2 class=&quot;cmd&quot;&gt;Level I&lt;/h2&gt;
754 P2PMud at its heart implements mechanism, NOT policy. P2PMud is an enabling technology to provide the framework
755 to make distributed MUDs work. It provides the ability for each local mud to discover other MUDs and create dynamic
756 portals between them. We're not talking just instant messages here either. When your character steps through a portal,
757 they and all their inventory are streamed across the virtual network so they actually appear in the other MUD. It also
758 affords transaction base world persistence, meaning it continuously saves the state of your local MUD so it is *always*
759 backed up and can be restored from any point. No matter whatever happens out there, you are safe in knowing you can always
760 put things back the way they were on your own MUD. p2pmud provides access to all of this from a Javascript interface.
763 &lt;h2 class=&quot;cmd&quot;&gt;Level II&lt;/h2&gt;
764 Since just a framework by itself is not very interesting, p2pmud provides a default game mechanics system (GMS)
765 written in Javascript. This reference layer can be used as is to create your own world, or you can modify it as you see
766 fit to build your own GMS! This is where all of the game mechanics are described and game policy is implemented. Love d20
767 rules? Start a project to implement d20!
769 &lt;h2 class=&quot;cmd&quot;&gt;Level III&lt;/h2&gt;
770 Even a GMS by itself is not too useful to players. If your love is building worlds and adventures, just pick a
771 GMS that suits you and start building away. Each GMS will have a set of tools or commands you can use to create rooms,
772 spaceships, weapons, monsters, or whatever. They likely will come will entire libraries of objects you can use to build with.
773 If you get bit by the scripting bug, you too can use Javascript to implement that +20 flaming broadsword with whatever special
774 powers you can dream up! Whichever GMS you choose will provide an interface for game interactions your scripts can build on.
776 &lt;h2 class=&quot;cmd&quot;&gt;Level IV&lt;/h2&gt;
777 If you're screaming that you just want to play already, the intent is that each GMS comes with a default world
778 that consists of a single house your character can live in. Install p2pmud and your GMS of choice. Once you've launched it,
779 you can use the peer discovery to create portals to worlds created by other people. Venture out through a portal into the virtual
780 world, kill the baddies and get the girl! Maybe some day you'll learn to understand why others love to build so much and choose
781 to expand your own little corner of the world!
783 &lt;p&gt;Objects created in one GMS are not likely to work in another, so we expect to see a number of different virtual worlds spring
784 up around each GMS. Since the GMS layers implement policy, these will likely start to develop around particular themes. For
785 example, one GMS might be more suited for space adventures, one for ancient worlds, etc.
786 &lt;/html&gt;
787 }}}</pre>
788 </div>
789 <div title="Plexus" modifier="BillBurdick" created="200806171636" modified="200808291619" changecount="12">
790 <pre>P2PMud is a peer-to-peer multiuser dungeon with a [[long history|Reloaded]]
792 And it's The Killer App of the Future!
794 The //''P2PMud [[Reloaded]]''// project adds support to the fantastic ''[[Sauerbraten|http://sauerbraten.org]]'' engine to allow remote scripting and hooking in at deep levels, with the goal of minimizing changes to the Sauerbraten source code.
796 Part of this is putting in hooks for moderated play, for instance, chain of command-style hooks for weapon hits. You can register a hook that Sauerbraten calls instead of the normal hit code. The hook may invoke a &quot;defaulthit&quot; command to proceed with normal damage.
798 [[Here|http://repo.or.cz/w/SauerbratenRemote.git?a=blob_plain;f=index.html;hb=HEAD]] is the latest version of this document and [[here|http://repo.or.cz/w/SauerbratenRemote.git]] is the source code repository.</pre>
799 </div>
800 <div title="PotentialIssues" modifier="Zot" created="200806191223" modified="200806191224" changecount="2">
801 <pre>relaying (if using Sauerbraten's server)
802 *to prevent, set curmsg = p.length(), otherwise it multicasts the current message</pre>
803 </div>
804 <div title="Reloaded" modifier="BillBurdick" created="200806171638" modified="200810101524" changecount="20">
805 <pre>~P2PMud traces its roots back to the murky depths of history. In 1988, Roy Riggs first wrote a tiny multi-user dungeon in C, one of the earliest ~MMORPGs that ran over the Internet (while, maybe an MORPG, anyway.) It took only a matter of days, or perhaps even hours, for Bill Burdick to join Roy in this glorious endevour and together they formed the sinister TEAM CTHULHU!
807 Since that time, TEAM CTHULHU's MUD has metamophosed many times, each time shedding its carapace to reveal something bizarrely different from its predecessor.
809 You can even see a reference to TEAM CTHULHU in the CREDITS file of [[LPMud|http://www.genesismud.org/]]. We came up with an idea called &quot;forwarders&quot; for our original MUD which became &quot;shadows&quot; in ~LPMud. There's a fairly early version of ~LPMud [[here|http://ibiblio.net/pub/historic-linux/ftp-archives/sunsite.unc.edu/Nov-06-1994/games/muds/]].
811 !TEAM CTHULHU Timeline
812 #Fall 1988, Roy Riggs write a miniscule MUD in C
813 #Fall 1988, Bill Burdick jumps on board and we write a Franz LISP-based version, quickly switching to KCL
814 #Fall 1988, we switch to our own language, MOB, a multiple-inheritance, OOPL on top of LISP, with Smalltalk-like syntax in square brackets (ala Objective-C)
815 **over the next two years, we get 4 semesters of credit for enhancements to this version
816 #Summer 1990, we switch to Sean Barrett's excellent Sludge VM, which supports prototype-based inheritance.
817 #...
818 #Spring 2002, we start p2pmud in using JXTA and Kawa, a Scheme implementation on top of Java
819 #Early 2004, we switch to Mozilla and Javascript. The MUD lives entirely in one HTML file on your disk (like tiddly wiki). Your browser hooks up to other browsers via a PHP &quot;switchboard&quot;
820 **Several releases on the p2pmud.com website
821 #Fall 2005, we commission Steven Missal to create Lovecraftian art
822 #Spring 2008, we switch to Sauerbraten, Groovy, and Pastry (on top of Java)</pre>
823 </div>
824 <div title="RoadMap" modifier="BillBurdick" created="200806191122" modified="200810210428" changecount="258">
825 <pre>!Mondo Yith
826 *Cycle plubble peers every 45-60 mins
827 *Player names seem screwy -- bubba + fur = fur + fur?
828 *Do not allow connection unless Plubble can connect back
829 *Handle error loading a map from a peer that drops out
830 *camera rotation problem on Linux
831 !Next
832 *if this is a new plexus version, offer to toast the cache
833 *Pastry reconnect business (multiple root peers?)
834 *set CPPFLAGS -DTC in src/Makefile's call to src/enet/configure
835 *Make md3load handle missing files (deactivate md3skin, md3pitch, md3anim, md3link, mdlspec, mdlscale, mdltrans, etc.) -- maybe make it load a default model (like Zippy the Pinhead)
836 *make updateMappingDiag() also display results for dumpents
837 *Diag panes for sauer and pastry cmds
838 *plexus profile editor in sauer (prepopulate wizard)
839 *combine swing windows
840 *only show diag tools until the first successful connect. Then make it an option.
841 *Items
842 *Monsters (phantom players)
843 *Brains
844 *responsibility allocation
845 **maybe have a doc for each world that allocates responsibilities among the peers of a world
846 **when responsibilities or peers join or leave, responsibilities shuffle around
847 **we can store responsibility allocation in PAST
848 **might need a world-ping
849 *Create, destroy, and move objects (GUIDs)
850 !Future
852 **Save hook defined in map.groovy
853 **Save menu choice (which calls map's save hook)
854 *World Building
855 **clean up obsolete maps
856 *map/costume export
857 *Simple game
858 **use crosshairs for aiming when they are visible
859 **register sauer cmds for hit effects based on shooter and/or target
860 **show bio when you shoot someone with an examination gun
861 *Tomb files!
862 *Authentication
863 **Groups
864 **Directory entry ownership by digital signatures (peers and groups) -- only an owner can change it
865 *Vehicles
866 **BIG model
867 **specify box with attachment point, vector, and height
868 **specify driving position with attachment point
869 **driver can switch between player view and vehicle view
870 **physics interacts with box
871 **players can move around inside, fire grenades, etc.
872 *groovy libraries in the cloud
873 !Done
874 *DONE Detect double nat with upnp
875 *DONE Update costume list as thumbs arrive (don't wait for all of them before updating)
876 *DONE constumes getting reset to null (mr fixit) -- seems connected to costume uploading
877 *DONE when costume becomes null, sometimes it doesn't change it
878 *DONE fix case for md2 import
879 *DONE check response values to verify mappings succeed, remove upnp mappings during shutdown, remove cleanup call (make button?)
880 *DONE limbo name not showing up
881 *DONE taunt bug
882 *DONE Map name sometimes shows up as &quot;none&quot; when you are on a map
883 *DONE Blank costume no longer switches to Mr Fixit
884 *DONE players can taunt each other
885 *DONE Filter out Thumbs.db and 0 length files
886 *DONE drag selection with left button
887 *DONE cleanup connecting to existing sauer
888 *DONE remove date from directory properties so that dirs are not mistakenly unique
889 *DONE cloud &quot;power light&quot;
890 *DONE aiming/edit selection problem
891 *DONE Hidden worlds (not advertised, but part of the cloud)
892 *DONE Finish tutorial menus
893 *DONE [replaced by map.groovy] fix bug in saveGroovyData()
894 *DONE [replaced by map.groovy &amp; mapProps] persist JSON object as groovy file in map dir for metadata map development
895 *DONE allow turning off recoil for shots
896 *DONE allow user to restart plexus from sauer
897 *DONE cache player updates for your map so follow can teleport immediately
898 **DONE restore world when you restart sauer
899 *DONE pick random port in range
900 *DONE disable Launch Sauer until connected
901 *DONE test costume uploading
902 *DONE broadcast disconnect with shutting down
903 *DONE check java version on startup
904 *DONE remote guild names not showing
905 *DONE incoming cloud change notification to user
906 *DONE show feedback for unsuccessful port back-connection during ip discovery
907 *DONE Missing player problem -- when loading map, players clear out of sauer but not out of plexus id/name tables
908 *DONE Groovy version of build script
909 *DONE JSON format for cloud properties
910 *DONE (fluke?) on find map, couldn't find map with tume id instead of map id
911 *DONE upload/download progress notification
912 *DONE notification of droppage
913 *DONE testing peer
914 *DONE Clean button in prep gui
915 *DONE options for cleanup on start/completely fresh start
916 *DONE ZLIB licensing
917 *DONE left msg uses nodename instead of player name
918 *DONE switches remote player costume before d/l completed
919 *DONE download into temp dirs and only rename when completed
920 *DONE map member count is always 0
921 *DONE don't subscribe to map until successfully downloaded
922 *DONE Deadlocking?
923 *DONE Rework existing editing menu, much doesn't apply
924 *DONE decouple camera speed from players speed
925 *DONE Make players start off in Limbo
926 *DONE World portals
927 *DONE make LMB detach camera to orbit around the toon
928 *DONE make sauer load models from plexus folder
929 *DONE Global whispers
930 *DONE Map name on load screen
931 *DONE Make push handle regular maps (make manifest that places it in a subdir)
932 *DONE MapStorage
933 *DONE Replace map should broadcast &quot;switchmap&quot; pastry cmd
934 *DONE Message dialog: showmessage title text
935 *DONE make cursors load from plexus/dist
936 *DONE separate backup directory
937 *DONE Use UPnP to discover external IP address
938 *DONE delete player on peer disconnect
939 *DONE clean up players on world connection
940 *DONE Show connection count in world menu
941 *DONE Investigate occasional sauer/groovy lag on linux
942 *DONE Headless peer on Plubble
943 *DONE include UPnP to open external port in firewall
944 *DONE add tc_respawn command to put players back at spawn points
945 *DONE don't show original menu on startup
946 *DONE Hide Gun in First Person mode
947 *DONE Disable weapon swapping, disable shooting
948 *DONE Make Limbo map come up by default
949 *DONE Rename wowmode to tcmode
950 *DONE make cursor float above HUD
951 *DONE load limbo in sauer on startup
952 *DONE Show only filename on map thumbnails when loading
953 *DONE HUD add peer count
954 *DONE launch sauer in sep thread while connecting to pastry
955 *DONE add button to reload sauer
956 *DONE move commands from groovy init into a .cfg file
957 *DONE Storage
958 **DONE Check out out of PAST disk space errors
959 **DONE Global, shared directory for cloud
960 **DONE Costumes
961 *DONE Plexus topic
962 **DONE Presence topic for total online count
963 **DONE available world listing/selection from gui
964 *DONE HUD
965 **DONE Current map
966 **DONE Map connection count
967 *DONE Plubble Peer
968 **DONE Test
970 [[Goals]]
971 [[Completed]]
972 [[Toys]]
973 !Long Term
974 *Game
975 **materials for sensors (use var to set material id) (callback set on material id)
976 **inventory mgmt (+ gui)
977 **stats (gold)
978 **shop keeper
979 **basic monster
980 **dead monsters drop loot
981 **wizard guns (create monster, move, talk, paralyze, destroy)
982 **saved map chunks</pre>
983 </div>
984 <div title="SiteSubtitle" modifier="Zot" created="200806171609" modified="200806191121" changecount="11">
985 <pre>The Killer App of the Future! //[[Reloaded]]//</pre>
986 </div>
987 <div title="SiteTitle" modifier="Zot" created="200806171608" modified="200806191146" changecount="9">
988 <pre>[img[P2PMud|docs/images/p2pmud.gif]]</pre>
989 </div>
990 <div title="StartingOut" modifier="BillBurdick" created="200807142041" modified="200809272113" changecount="12">
991 <pre>*P2PMud is currently based on the Assassin version of Sauerbraten, which you can download [[here|http://sourceforge.net/project/showfiles.php?group_id=102911&amp;package_id=110363&amp;release_id=563483]]
992 *This repository is an Eclipse project, so you'll probably want Eclipse and the CDT feature for C/C++
993 Once you have the stuff, you need to:
994 *Build the project
995 *Copy/link sauer_client and scripts/autoexec.cfg into the top level of the sauerbraten directory (in the same directory as server.bat).
996 *Start the groovy command server in Eclipse (using the Cmd Server launcher)
997 *Start sauer (I find it's much better to start up with the -t option when testing, so it doesn't take over my screen)
998 !&lt;html&gt;&lt;span style=&quot;background: red&quot;&gt;HAVING PROBLEMS?&lt;/span&gt;&lt;/html&gt;
999 HowToReallyCleanAnEclipseProject
1000 EmergencyWorspaceReconstruction</pre>
1001 </div>
1002 <div title="StyleSheet" modifier="BillBurdick" created="200809280501" modified="200809280525" changecount="6">
1003 <pre>/*{{{*/
1004 .cthulhu { text-decoration: none;
1005 color: #cccccc;
1006 font-family: &quot;arial&quot;;
1007 font-size: 12pt;
1008 font-weight: medium;
1009 background-color: #282619;
1010 padding-top: 3px;
1011 padding-bottom: 3px;
1013 .cmd.cthulhu { text-decoration: none;
1014 color: #FF9900;
1015 font-family: &quot;arial&quot;;
1016 font-size: 16pt;
1017 font-weight: medium;
1019 a.cthulhu:link { text-decoration: none;
1020 color: #FF9900;
1021 font-family: &quot;arial&quot;;
1022 font-size: 12pt;
1023 font-weight: heavy;
1025 a.cthulhu:visited { text-decoration: none;
1026 color: #FF9900;
1027 font-family: &quot;arial&quot;;
1028 font-size: 12pt;
1029 font-weight: heavy;
1031 /*}}}*/</pre>
1032 </div>
1033 <div title="ToDo" modifier="WRB" created="200806171610" modified="200807192045" changecount="5">
1034 <pre>[[Done]]
1036 Use Scribe
1037 fix orientation
1039 !Update
1040 Update docs to refer to new platform (Groovy/Sauerbraten) instead of old (Javascript/Mozilla)
1042 !Extensions
1043 browser plugin
1044 Drag and drop for browser images?
1046 !Moderation
1047 master player -- invisible/intangible and does not transmit coords to the server
1048 movement modes
1049 puppet -- move according to instructions (default is frozen)
1050 normal -- moves with limitations (movement blockers)
1051 movement blockers
1052 beacons: players can move as long as they remain within the radius of at least one authorized beacon
1053 This allows cell-style hand off
1054 walls: players can pass through authorized walls
1057 !Commands
1058 moveto id x y z -- sets id's movement target to location
1059 gunset id model
1060 contexthook hook -- when active, execute hook on context change for player1
1061 usecontext on/off -- set whether player1's contexthook is active
1062 monstersay command
1063 authorize id blocker
1064 unauthorize id blocker
1065 puppet id -- makes id move toward its movement target and if it's a player unyoke the controls
1066 unpuppet id -- makes id ignore its movement target and if it's a player, yoke the controls
1069 !Guns
1070 talk
1071 move -- shoot destination, then target(s)
1072 authorize -- shoot blocker, then target(s)
1073 unauthorize -- shoot blocker, then target(s)
1074 dirt
1075 blast
1076 paralyze
1077 puppet
1078 die roll
1080 !Network
1081 slaverequest req
1082 mastercommand cmd</pre>
1083 </div>
1084 <div title="Toys" modifier="BillBurdick" created="200808151458" changecount="1">
1085 <pre>launch gun -- sets velocity on opponents
1086 jet pack
1087 Acme world
1088 *jet-skate ramp target contest: skates raise your max speed, turn on rumble pack, and apply small random adjustments to your position and orientation at random intervals
1089 *cannon golf
1090 might be doable with only triggers and watcher code
1091 Guns and weapons
1092 roman candles/wand of fireballs
1093 physics for wall splat
1094 sensors
1095 monsters control assigned by DHT
1096 map sharing includes map.groovy file in addition to map.ogz and map.cfg
1097 Sharing with Jgit
1098 *Each world gets a branch off the initial starting directory (a dir with a readme)
1099 *Git tree objects are stored in PAST, cached in a local Git repo, and extracted into a local directory</pre>
1100 </div>
1101 <div title="UseCases" modifier="BillBurdick" created="200807300630" modified="200807300650" changecount="3">
1102 <pre>#Items
1103 ##player rolls over an item
1104 ##item adds to inventory
1105 ##player presses &quot;i&quot; key
1106 ##inventory menu pops up
1107 ##player chooses a different item from menu
1108 ##submenu pops up
1109 ##player chooses drop
1110 ##item leaves inventory and appears on map
1111 #Keys and Doors
1112 ##player approaches door
1113 ##door says &quot;locked&quot;
1114 ##player finds key and reapproaches door
1115 ##door opens
1116 #The Shop Keeper
1117 ##player enters shop
1118 ##shop keeper says &quot;welcome to the shop&quot;
1119 ##player switches to shopping gun
1120 ##items are visible on the counter
1121 ##player shoots item
1122 ##menu pops up and offers buy choice
1123 ##player selects &quot;buy&quot; and item gets added to inventory and gold is deducted
1124 ##player shoots shop keeper
1125 ##shop keeper menu pops up and offers to buy items</pre>
1126 </div>
1127 <div title="Zot" modifier="Zot" created="200806171649" changecount="1">
1128 <pre>Zot is one of the founding members of [[TEAM CTHULHU|http://teamcthulhu.com]]</pre>
1129 </div>
1130 </div>
1131 <!--POST-STOREAREA-->
1132 <!--POST-BODY-START-->
1133 <!--POST-BODY-END-->
1134 <script id="jsArea" type="text/javascript">
1135 //<![CDATA[
1137 // Please note:
1139 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
1140 // in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
1142 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
1143 // without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
1146 //--
1147 //-- Configuration repository
1148 //--
1150 // Miscellaneous options
1151 var config = {
1152 numRssItems: 20, // Number of items in the RSS feed
1153 animDuration: 400, // Duration of UI animations in milliseconds
1154 cascadeFast: 20, // Speed for cascade animations (higher == slower)
1155 cascadeSlow: 60, // Speed for EasterEgg cascade animations
1156 cascadeDepth: 5, // Depth of cascade animation
1157 locale: "en" // W3C language tag
1160 // Hashmap of alternative parsers for the wikifier
1161 config.parsers = {};
1163 // Adaptors
1164 config.adaptors = {};
1165 config.defaultAdaptor = null;
1167 // Backstage tasks
1168 config.tasks = {};
1170 // Annotations
1171 config.annotations = {};
1173 // Custom fields to be automatically added to new tiddlers
1174 config.defaultCustomFields = {};
1176 // Messages
1177 config.messages = {
1178 messageClose: {},
1179 dates: {},
1180 tiddlerPopup: {}
1183 // Options that can be set in the options panel and/or cookies
1184 config.options = {
1185 chkRegExpSearch: false,
1186 chkCaseSensitiveSearch: false,
1187 chkIncrementalSearch: true,
1188 chkAnimate: true,
1189 chkSaveBackups: true,
1190 chkAutoSave: false,
1191 chkGenerateAnRssFeed: false,
1192 chkSaveEmptyTemplate: false,
1193 chkOpenInNewWindow: true,
1194 chkToggleLinks: false,
1195 chkHttpReadOnly: true,
1196 chkForceMinorUpdate: false,
1197 chkConfirmDelete: true,
1198 chkInsertTabs: false,
1199 chkUsePreForStorage: true, // Whether to use <pre> format for storage
1200 chkDisplayInstrumentation: false,
1201 txtBackupFolder: "",
1202 txtEditorFocus: "text",
1203 txtMainTab: "tabTimeline",
1204 txtMoreTab: "moreTabAll",
1205 txtMaxEditRows: "30",
1206 txtFileSystemCharSet: "UTF-8",
1207 txtTheme: ""
1209 config.optionsDesc = {};
1211 // Default tiddler templates
1212 var DEFAULT_VIEW_TEMPLATE = 1;
1213 var DEFAULT_EDIT_TEMPLATE = 2;
1214 config.tiddlerTemplates = {
1215 1: "ViewTemplate",
1216 2: "EditTemplate"
1219 // More messages (rather a legacy layout that should not really be like this)
1220 config.views = {
1221 wikified: {
1222 tag: {}
1224 editor: {
1225 tagChooser: {}
1229 // Backstage tasks
1230 config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];
1232 // Macros; each has a 'handler' member that is inserted later
1233 config.macros = {
1234 today: {},
1235 version: {},
1236 search: {sizeTextbox: 15},
1237 tiddler: {},
1238 tag: {},
1239 tags: {},
1240 tagging: {},
1241 timeline: {},
1242 allTags: {},
1243 list: {
1244 all: {},
1245 missing: {},
1246 orphans: {},
1247 shadowed: {},
1248 touched: {},
1249 filter: {}
1251 closeAll: {},
1252 permaview: {},
1253 saveChanges: {},
1254 slider: {},
1255 option: {},
1256 options: {},
1257 newTiddler: {},
1258 newJournal: {},
1259 tabs: {},
1260 gradient: {},
1261 message: {},
1262 view: {defaultView: "text"},
1263 edit: {},
1264 tagChooser: {},
1265 toolbar: {},
1266 plugins: {},
1267 refreshDisplay: {},
1268 importTiddlers: {},
1269 upgrade: {
1270 source: "http://www.tiddlywiki.com/upgrade/",
1271 backupExtension: "pre.core.upgrade"
1273 sync: {},
1274 annotations: {}
1277 // Commands supported by the toolbar macro
1278 config.commands = {
1279 closeTiddler: {},
1280 closeOthers: {},
1281 editTiddler: {},
1282 saveTiddler: {hideReadOnly: true},
1283 cancelTiddler: {},
1284 deleteTiddler: {hideReadOnly: true},
1285 permalink: {},
1286 references: {type: "popup"},
1287 jump: {type: "popup"},
1288 syncing: {type: "popup"},
1289 fields: {type: "popup"}
1292 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
1293 config.userAgent = navigator.userAgent.toLowerCase();
1294 config.browser = {
1295 isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
1296 isGecko: config.userAgent.indexOf("gecko") != -1,
1297 ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
1298 isSafari: config.userAgent.indexOf("applewebkit") != -1,
1299 isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
1300 firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
1301 isOpera: config.userAgent.indexOf("opera") != -1,
1302 isLinux: config.userAgent.indexOf("linux") != -1,
1303 isUnix: config.userAgent.indexOf("x11") != -1,
1304 isMac: config.userAgent.indexOf("mac") != -1,
1305 isWindows: config.userAgent.indexOf("win") != -1
1308 // Basic regular expressions
1309 config.textPrimitives = {
1310 upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
1311 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
1312 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
1313 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
1315 if(config.browser.isBadSafari) {
1316 config.textPrimitives = {
1317 upperLetter: "[A-Z\u00c0-\u00de]",
1318 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
1319 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
1320 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
1323 config.textPrimitives.sliceSeparator = "::";
1324 config.textPrimitives.sectionSeparator = "##";
1325 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
1326 config.textPrimitives.unWikiLink = "~";
1327 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
1328 config.textPrimitives.lowerLetter + "+" +
1329 config.textPrimitives.upperLetter +
1330 config.textPrimitives.anyLetter + "*)|(?:" +
1331 config.textPrimitives.upperLetter + "{2,}" +
1332 config.textPrimitives.lowerLetter + "+))";
1334 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
1335 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
1337 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
1338 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
1339 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
1340 config.textPrimitives.brackettedLink + ")|(?:" +
1341 config.textPrimitives.urlPattern + ")","mg");
1342 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
1343 config.textPrimitives.titledBrackettedLink + ")|(?:" +
1344 config.textPrimitives.brackettedLink + ")|(?:" +
1345 config.textPrimitives.urlPattern + ")","mg");
1347 config.glyphs = {
1348 browsers: [
1349 function() {return config.browser.isIE;},
1350 function() {return true;}
1352 currBrowser: null,
1353 codes: {
1354 downTriangle: ["\u25BC","\u25BE"],
1355 downArrow: ["\u2193","\u2193"],
1356 bentArrowLeft: ["\u2190","\u21A9"],
1357 bentArrowRight: ["\u2192","\u21AA"]
1361 //--
1362 //-- Shadow tiddlers
1363 //--
1365 config.shadowTiddlers = {
1366 StyleSheet: "",
1367 MarkupPreHead: "",
1368 MarkupPostHead: "",
1369 MarkupPreBody: "",
1370 MarkupPostBody: "",
1371 TabTimeline: '<<timeline>>',
1372 TabAll: '<<list all>>',
1373 TabTags: '<<allTags excludeLists>>',
1374 TabMoreMissing: '<<list missing>>',
1375 TabMoreOrphans: '<<list orphans>>',
1376 TabMoreShadowed: '<<list shadowed>>',
1377 AdvancedOptions: '<<options>>',
1378 PluginManager: '<<plugins>>',
1379 ToolbarCommands: '|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|'
1382 //--
1383 //-- Translateable strings
1384 //--
1386 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
1388 merge(config.options,{
1389 txtUserName: "YourName"});
1391 merge(config.tasks,{
1392 save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
1393 sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
1394 importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
1395 tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
1396 upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
1397 plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
1400 // Options that can be set in the options panel and/or cookies
1401 merge(config.optionsDesc,{
1402 txtUserName: "Username for signing your edits",
1403 chkRegExpSearch: "Enable regular expressions for searches",
1404 chkCaseSensitiveSearch: "Case-sensitive searching",
1405 chkIncrementalSearch: "Incremental key-by-key searching",
1406 chkAnimate: "Enable animations",
1407 chkSaveBackups: "Keep backup file when saving changes",
1408 chkAutoSave: "Automatically save changes",
1409 chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
1410 chkSaveEmptyTemplate: "Generate an empty template when saving changes",
1411 chkOpenInNewWindow: "Open external links in a new window",
1412 chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
1413 chkHttpReadOnly: "Hide editing features when viewed over HTTP",
1414 chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
1415 chkConfirmDelete: "Require confirmation before deleting tiddlers",
1416 chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
1417 txtBackupFolder: "Name of folder to use for backups",
1418 txtMaxEditRows: "Maximum number of rows in edit boxes",
1419 txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
1421 merge(config.messages,{
1422 customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
1423 pluginError: "Error: %0",
1424 pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
1425 pluginForced: "Executed because forced via 'systemConfigForce' tag",
1426 pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
1427 nothingSelected: "Nothing is selected. You must select one or more items first",
1428 savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
1429 subtitleUnknown: "(unknown)",
1430 undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
1431 shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
1432 tiddlerLinkTooltip: "%0 - %1, %2",
1433 externalLinkTooltip: "External link to %0",
1434 noTags: "There are no tagged tiddlers",
1435 notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
1436 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",
1437 invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
1438 backupSaved: "Backup saved",
1439 backupFailed: "Failed to save backup file",
1440 rssSaved: "RSS feed saved",
1441 rssFailed: "Failed to save RSS feed file",
1442 emptySaved: "Empty template saved",
1443 emptyFailed: "Failed to save empty template file",
1444 mainSaved: "Main TiddlyWiki file saved",
1445 mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
1446 macroError: "Error in macro <<\%0>>",
1447 macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
1448 missingMacro: "No such macro",
1449 overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
1450 unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
1451 confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
1452 saveInstructions: "SaveChanges",
1453 unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
1454 tiddlerSaveError: "Error when saving tiddler '%0'",
1455 tiddlerLoadError: "Error when loading tiddler '%0'",
1456 wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
1457 invalidFieldName: "Invalid field name %0",
1458 fieldCannotBeChanged: "Field '%0' cannot be changed",
1459 loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
1460 upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});
1462 merge(config.messages.messageClose,{
1463 text: "close",
1464 tooltip: "close this message area"});
1466 config.messages.backstage = {
1467 open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
1468 close: {text: "close", tooltip: "Close the backstage area"},
1469 prompt: "backstage: ",
1470 decal: {
1471 edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
1475 config.messages.listView = {
1476 tiddlerTooltip: "Click for the full text of this tiddler",
1477 previewUnavailable: "(preview not available)"
1480 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
1481 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1482 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1483 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1484 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
1485 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
1486 "th","th","th","th","th","th","th","th","th","th",
1487 "st","nd","rd","th","th","th","th","th","th","th",
1488 "st"];
1489 config.messages.dates.am = "am";
1490 config.messages.dates.pm = "pm";
1492 merge(config.messages.tiddlerPopup,{
1495 merge(config.views.wikified.tag,{
1496 labelNoTags: "no tags",
1497 labelTags: "tags: ",
1498 openTag: "Open tag '%0'",
1499 tooltip: "Show tiddlers tagged with '%0'",
1500 openAllText: "Open all",
1501 openAllTooltip: "Open all of these tiddlers",
1502 popupNone: "No other tiddlers tagged with '%0'"});
1504 merge(config.views.wikified,{
1505 defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
1506 defaultModifier: "(missing)",
1507 shadowModifier: "(built-in shadow tiddler)",
1508 dateFormat: "DD MMM YYYY",
1509 createdPrompt: "created"});
1511 merge(config.views.editor,{
1512 tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
1513 defaultText: "Type the text for '%0'"});
1515 merge(config.views.editor.tagChooser,{
1516 text: "tags",
1517 tooltip: "Choose existing tags to add to this tiddler",
1518 popupNone: "There are no tags defined",
1519 tagTooltip: "Add the tag '%0'"});
1521 merge(config.messages,{
1522 sizeTemplates:
1524 {unit: 1024*1024*1024, template: "%0\u00a0GB"},
1525 {unit: 1024*1024, template: "%0\u00a0MB"},
1526 {unit: 1024, template: "%0\u00a0KB"},
1527 {unit: 1, template: "%0\u00a0B"}
1528 ]});
1530 merge(config.macros.search,{
1531 label: "search",
1532 prompt: "Search this TiddlyWiki",
1533 accessKey: "F",
1534 successMsg: "%0 tiddlers found matching %1",
1535 failureMsg: "No tiddlers found matching %0"});
1537 merge(config.macros.tagging,{
1538 label: "tagging: ",
1539 labelNotTag: "not tagging",
1540 tooltip: "List of tiddlers tagged with '%0'"});
1542 merge(config.macros.timeline,{
1543 dateFormat: "DD MMM YYYY"});
1545 merge(config.macros.allTags,{
1546 tooltip: "Show tiddlers tagged with '%0'",
1547 noTags: "There are no tagged tiddlers"});
1549 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
1550 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
1551 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
1552 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
1553 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
1555 merge(config.macros.closeAll,{
1556 label: "close all",
1557 prompt: "Close all displayed tiddlers (except any that are being edited)"});
1559 merge(config.macros.permaview,{
1560 label: "permaview",
1561 prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
1563 merge(config.macros.saveChanges,{
1564 label: "save changes",
1565 prompt: "Save all tiddlers to create a new TiddlyWiki",
1566 accessKey: "S"});
1568 merge(config.macros.newTiddler,{
1569 label: "new tiddler",
1570 prompt: "Create a new tiddler",
1571 title: "New Tiddler",
1572 accessKey: "N"});
1574 merge(config.macros.newJournal,{
1575 label: "new journal",
1576 prompt: "Create a new tiddler from the current date and time",
1577 accessKey: "J"});
1579 merge(config.macros.options,{
1580 wizardTitle: "Tweak advanced options",
1581 step1Title: "These options are saved in cookies in your browser",
1582 step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
1583 unknownDescription: "//(unknown)//",
1584 listViewTemplate: {
1585 columns: [
1586 {name: 'Option', field: 'option', title: "Option", type: 'String'},
1587 {name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
1588 {name: 'Name', field: 'name', title: "Name", type: 'String'}
1590 rowClasses: [
1591 {className: 'lowlight', field: 'lowlight'}
1595 merge(config.macros.plugins,{
1596 wizardTitle: "Manage plugins",
1597 step1Title: "Currently loaded plugins",
1598 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1599 skippedText: "(This plugin has not been executed because it was added since startup)",
1600 noPluginText: "There are no plugins installed",
1601 confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
1602 removeLabel: "remove systemConfig tag",
1603 removePrompt: "Remove systemConfig tag",
1604 deleteLabel: "delete",
1605 deletePrompt: "Delete these tiddlers forever",
1606 listViewTemplate: {
1607 columns: [
1608 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1609 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1610 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1611 {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
1612 {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
1613 {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
1614 {name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
1615 {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
1616 {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
1618 rowClasses: [
1619 {className: 'error', field: 'error'},
1620 {className: 'warning', field: 'warning'}
1624 merge(config.macros.toolbar,{
1625 moreLabel: "more",
1626 morePrompt: "Reveal further commands"
1629 merge(config.macros.refreshDisplay,{
1630 label: "refresh",
1631 prompt: "Redraw the entire TiddlyWiki display"
1634 merge(config.macros.importTiddlers,{
1635 readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
1636 wizardTitle: "Import tiddlers from another file or server",
1637 step1Title: "Step 1: Locate the server or TiddlyWiki file",
1638 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>",
1639 openLabel: "open",
1640 openPrompt: "Open the connection to this file or server",
1641 openError: "There were problems fetching the tiddlywiki file",
1642 statusOpenHost: "Opening the host",
1643 statusGetWorkspaceList: "Getting the list of available workspaces",
1644 step2Title: "Step 2: Choose the workspace",
1645 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>",
1646 cancelLabel: "cancel",
1647 cancelPrompt: "Cancel this import",
1648 statusOpenWorkspace: "Opening the workspace",
1649 statusGetTiddlerList: "Getting the list of available tiddlers",
1650 errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
1651 step3Title: "Step 3: Choose the tiddlers to import",
1652 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'>",
1653 importLabel: "import",
1654 importPrompt: "Import these tiddlers",
1655 confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
1656 step4Title: "Step 4: Importing %0 tiddler(s)",
1657 step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
1658 doneLabel: "done",
1659 donePrompt: "Close this wizard",
1660 statusDoingImport: "Importing tiddlers",
1661 statusDoneImport: "All tiddlers imported",
1662 systemServerNamePattern: "%2 on %1",
1663 systemServerNamePatternNoWorkspace: "%1",
1664 confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
1665 serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
1666 serverSaveModifier: "(System)",
1667 listViewTemplate: {
1668 columns: [
1669 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1670 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1671 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1672 {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
1674 rowClasses: [
1678 merge(config.macros.upgrade,{
1679 wizardTitle: "Upgrade TiddlyWiki core code",
1680 step1Title: "Update or repair this TiddlyWiki to the latest release",
1681 step1Html: "You are about to upgrade to the latest release of the TiddlyWiki core code (from <a href='%0' class='externalLink' target='_blank'>%1</a>). Your content will be preserved across the upgrade.<br><br>Note that core upgrades have been known to interfere with older plugins. If you run into problems with the upgraded file, see <a href='http://www.tiddlywiki.org/wiki/CoreUpgrades' class='externalLink' target='_blank'>http://www.tiddlywiki.org/wiki/CoreUpgrades</a>",
1682 errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
1683 errorNotSaved: "You must save changes before you can perform an upgrade",
1684 step2Title: "Confirm the upgrade details",
1685 step2Html_downgrade: "You are about to downgrade to TiddlyWiki version %0 from %1.<br><br>Downgrading to an earlier version of the core code is not recommended",
1686 step2Html_restore: "This TiddlyWiki appears to be already using the latest version of the core code (%0).<br><br>You can continue to upgrade anyway to ensure that the core code hasn't been corrupted or damaged",
1687 step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
1688 upgradeLabel: "upgrade",
1689 upgradePrompt: "Prepare for the upgrade process",
1690 statusPreparingBackup: "Preparing backup",
1691 statusSavingBackup: "Saving backup file",
1692 errorSavingBackup: "There was a problem saving the backup file",
1693 statusLoadingCore: "Loading core code",
1694 errorLoadingCore: "Error loading the core code",
1695 errorCoreFormat: "Error with the new core code",
1696 statusSavingCore: "Saving the new core code",
1697 statusReloadingCore: "Reloading the new core code",
1698 startLabel: "start",
1699 startPrompt: "Start the upgrade process",
1700 cancelLabel: "cancel",
1701 cancelPrompt: "Cancel the upgrade process",
1702 step3Title: "Upgrade cancelled",
1703 step3Html: "You have cancelled the upgrade process"
1706 merge(config.macros.sync,{
1707 listViewTemplate: {
1708 columns: [
1709 {name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
1710 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1711 {name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
1712 {name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
1713 {name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
1714 {name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
1715 {name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
1717 rowClasses: [
1719 buttons: [
1720 {caption: "Sync these tiddlers", name: 'sync'}
1722 wizardTitle: "Synchronize with external servers and files",
1723 step1Title: "Choose the tiddlers you want to synchronize",
1724 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1725 syncLabel: "sync",
1726 syncPrompt: "Sync these tiddlers",
1727 hasChanged: "Changed while unplugged",
1728 hasNotChanged: "Unchanged while unplugged",
1729 syncStatusList: {
1730 none: {text: "...", display:null, className:'notChanged'},
1731 changedServer: {text: "Changed on server", display:null, className:'changedServer'},
1732 changedLocally: {text: "Changed while unplugged", display:null, className:'changedLocally'},
1733 changedBoth: {text: "Changed while unplugged and on server", display:null, className:'changedBoth'},
1734 notFound: {text: "Not found on server", display:null, className:'notFound'},
1735 putToServer: {text: "Saved update on server", display:null, className:'putToServer'},
1736 gotFromServer: {text: "Retrieved update from server", display:null, className:'gotFromServer'}
1740 merge(config.macros.annotations,{
1743 merge(config.commands.closeTiddler,{
1744 text: "close",
1745 tooltip: "Close this tiddler"});
1747 merge(config.commands.closeOthers,{
1748 text: "close others",
1749 tooltip: "Close all other tiddlers"});
1751 merge(config.commands.editTiddler,{
1752 text: "edit",
1753 tooltip: "Edit this tiddler",
1754 readOnlyText: "view",
1755 readOnlyTooltip: "View the source of this tiddler"});
1757 merge(config.commands.saveTiddler,{
1758 text: "done",
1759 tooltip: "Save changes to this tiddler"});
1761 merge(config.commands.cancelTiddler,{
1762 text: "cancel",
1763 tooltip: "Undo changes to this tiddler",
1764 warning: "Are you sure you want to abandon your changes to '%0'?",
1765 readOnlyText: "done",
1766 readOnlyTooltip: "View this tiddler normally"});
1768 merge(config.commands.deleteTiddler,{
1769 text: "delete",
1770 tooltip: "Delete this tiddler",
1771 warning: "Are you sure you want to delete '%0'?"});
1773 merge(config.commands.permalink,{
1774 text: "permalink",
1775 tooltip: "Permalink for this tiddler"});
1777 merge(config.commands.references,{
1778 text: "references",
1779 tooltip: "Show tiddlers that link to this one",
1780 popupNone: "No references"});
1782 merge(config.commands.jump,{
1783 text: "jump",
1784 tooltip: "Jump to another open tiddler"});
1786 merge(config.commands.syncing,{
1787 text: "syncing",
1788 tooltip: "Control synchronisation of this tiddler with a server or external file",
1789 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
1790 notCurrentlySyncing: "Not currently syncing",
1791 captionUnSync: "Stop synchronising this tiddler",
1792 chooseServer: "Synchronise this tiddler with another server:",
1793 currServerMarker: "\u25cf ",
1794 notCurrServerMarker: " "});
1796 merge(config.commands.fields,{
1797 text: "fields",
1798 tooltip: "Show the extended fields of this tiddler",
1799 emptyText: "There are no extended fields for this tiddler",
1800 listViewTemplate: {
1801 columns: [
1802 {name: 'Field', field: 'field', title: "Field", type: 'String'},
1803 {name: 'Value', field: 'value', title: "Value", type: 'String'}
1805 rowClasses: [
1807 buttons: [
1808 ]}});
1810 merge(config.shadowTiddlers,{
1811 DefaultTiddlers: "[[GettingStarted]]",
1812 MainMenu: "[[GettingStarted]]",
1813 SiteTitle: "My TiddlyWiki",
1814 SiteSubtitle: "a reusable non-linear personal web notebook",
1815 SiteUrl: "http://www.tiddlywiki.com/",
1816 SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
1817 SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
1818 TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'
1821 merge(config.annotations,{
1822 AdvancedOptions: "This shadow tiddler provides access to several advanced options",
1823 ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
1824 DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
1825 EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
1826 GettingStarted: "This shadow tiddler provides basic usage instructions",
1827 ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
1828 MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
1829 MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
1830 MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
1831 MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
1832 MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
1833 OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
1834 PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
1835 PluginManager: "This shadow tiddler provides access to the plugin manager",
1836 SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
1837 SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
1838 SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
1839 SiteTitle: "This shadow tiddler is used as the first part of the page title",
1840 SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
1841 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",
1842 StyleSheet: "This tiddler can contain custom CSS definitions",
1843 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",
1844 StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
1845 StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
1846 TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
1847 TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
1848 TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
1849 TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
1850 TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
1851 TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
1852 TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
1853 ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
1854 ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
1857 //--
1858 //-- Main
1859 //--
1861 var params = null; // Command line parameters
1862 var store = null; // TiddlyWiki storage
1863 var story = null; // Main story
1864 var formatter = null; // Default formatters for the wikifier
1865 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
1866 var readOnly = false; // Whether we're in readonly mode
1867 var highlightHack = null; // Embarrassing hack department...
1868 var hadConfirmExit = false; // Don't warn more than once
1869 var safeMode = false; // Disable all plugins and cookies
1870 var showBackstage; // Whether to include the backstage area
1871 var installedPlugins = []; // Information filled in when plugins are executed
1872 var startingUp = false; // Whether we're in the process of starting up
1873 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
1875 // Whether to use the JavaSaver applet
1876 var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0,4) != "http");
1878 // Starting up
1879 function main()
1881 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1882 startingUp = true;
1883 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1884 params = getParameters();
1885 if(params)
1886 params = params.parseParams("open",null,false);
1887 store = new TiddlyWiki();
1888 invokeParamifier(params,"oninit");
1889 story = new Story("tiddlerDisplay","tiddler");
1890 addEvent(document,"click",Popup.onDocumentClick);
1891 saveTest();
1892 loadOptionsCookie();
1893 for(var s=0; s<config.notifyTiddlers.length; s++)
1894 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1895 t1 = new Date();
1896 loadShadowTiddlers();
1897 t2 = new Date();
1898 store.loadFromDiv("storeArea","store",true);
1899 t3 = new Date();
1900 invokeParamifier(params,"onload");
1901 t4 = new Date();
1902 readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
1903 var pluginProblem = loadPlugins();
1904 t5 = new Date();
1905 formatter = new Formatter(config.formatters);
1906 invokeParamifier(params,"onconfig");
1907 story.switchTheme(config.options.txtTheme);
1908 showBackstage = !readOnly;
1909 t6 = new Date();
1910 store.notifyAll();
1911 t7 = new Date();
1912 restart();
1913 refreshDisplay();
1914 t8 = new Date();
1915 if(pluginProblem) {
1916 story.displayTiddler(null,"PluginManager");
1917 displayMessage(config.messages.customConfigError);
1919 for(var m in config.macros) {
1920 if(config.macros[m].init)
1921 config.macros[m].init();
1923 t9 = new Date();
1924 if(showBackstage)
1925 backstage.init();
1926 t10 = new Date();
1927 if(config.options.chkDisplayInstrumentation) {
1928 displayMessage("LoadShadows " + (t2-t1) + " ms");
1929 displayMessage("LoadFromDiv " + (t3-t2) + " ms");
1930 displayMessage("LoadPlugins " + (t5-t4) + " ms");
1931 displayMessage("Notify " + (t7-t6) + " ms");
1932 displayMessage("Restart " + (t8-t7) + " ms");
1933 displayMessage("Macro init " + (t9-t8) + " ms");
1934 displayMessage("Total: " + (t10-t0) + " ms");
1936 startingUp = false;
1939 // Restarting
1940 function restart()
1942 invokeParamifier(params,"onstart");
1943 if(story.isEmpty()) {
1944 story.displayDefaultTiddlers();
1946 window.scrollTo(0,0);
1949 function saveTest()
1951 var s = document.getElementById("saveTest");
1952 if(s.hasChildNodes())
1953 alert(config.messages.savedSnapshotError);
1954 s.appendChild(document.createTextNode("savetest"));
1957 function loadShadowTiddlers()
1959 var shadows = new TiddlyWiki();
1960 shadows.loadFromDiv("shadowArea","shadows",true);
1961 shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
1962 delete shadows;
1965 function loadPlugins()
1967 if(safeMode)
1968 return false;
1969 var tiddlers = store.getTaggedTiddlers("systemConfig");
1970 var toLoad = [];
1971 var nLoaded = 0;
1972 var map = {};
1973 var nPlugins = tiddlers.length;
1974 installedPlugins = [];
1975 for(var i=0; i<nPlugins; i++) {
1976 var p = getPluginInfo(tiddlers[i]);
1977 installedPlugins[i] = p;
1978 var n = p.Name;
1979 if(n)
1980 map[n] = p;
1981 n = p.Source;
1982 if(n)
1983 map[n] = p;
1985 var visit = function(p) {
1986 if(!p || p.done)
1987 return;
1988 p.done = 1;
1989 var reqs = p.Requires;
1990 if(reqs) {
1991 reqs = reqs.readBracketedList();
1992 for(var i=0; i<reqs.length; i++)
1993 visit(map[reqs[i]]);
1995 toLoad.push(p);
1997 for(i=0; i<nPlugins; i++)
1998 visit(installedPlugins[i]);
1999 for(i=0; i<toLoad.length; i++) {
2000 p = toLoad[i];
2001 pluginInfo = p;
2002 tiddler = p.tiddler;
2003 if(isPluginExecutable(p)) {
2004 if(isPluginEnabled(p)) {
2005 p.executed = true;
2006 var startTime = new Date();
2007 try {
2008 if(tiddler.text)
2009 window.eval(tiddler.text);
2010 nLoaded++;
2011 } catch(ex) {
2012 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
2013 p.error = true;
2015 pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
2016 } else {
2017 nPlugins--;
2019 } else {
2020 p.warning = true;
2023 return nLoaded != nPlugins;
2026 function getPluginInfo(tiddler)
2028 var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
2029 p.tiddler = tiddler;
2030 p.title = tiddler.title;
2031 p.log = [];
2032 return p;
2035 // Check that a particular plugin is valid for execution
2036 function isPluginExecutable(plugin)
2038 if(plugin.tiddler.isTagged("systemConfigForce")) {
2039 plugin.log.push(config.messages.pluginForced);
2040 return true;
2042 if(plugin["CoreVersion"]) {
2043 var coreVersion = plugin["CoreVersion"].split(".");
2044 var w = parseInt(coreVersion[0],10) - version.major;
2045 if(w == 0 && coreVersion[1])
2046 w = parseInt(coreVersion[1],10) - version.minor;
2047 if(w == 0 && coreVersion[2])
2048 w = parseInt(coreVersion[2],10) - version.revision;
2049 if(w > 0) {
2050 plugin.log.push(config.messages.pluginVersionError);
2051 return false;
2054 return true;
2057 function isPluginEnabled(plugin)
2059 if(plugin.tiddler.isTagged("systemConfigDisable")) {
2060 plugin.log.push(config.messages.pluginDisabled);
2061 return false;
2063 return true;
2066 function invokeMacro(place,macro,params,wikifier,tiddler)
2068 try {
2069 var m = config.macros[macro];
2070 if(m && m.handler)
2071 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
2072 else
2073 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
2074 } catch(ex) {
2075 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
2079 //--
2080 //-- Paramifiers
2081 //--
2083 function getParameters()
2085 var p = null;
2086 if(window.location.hash) {
2087 p = decodeURIComponent(window.location.hash.substr(1));
2088 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
2089 p = convertUTF8ToUnicode(p);
2091 return p;
2094 function invokeParamifier(params,handler)
2096 if(!params || params.length == undefined || params.length <= 1)
2097 return;
2098 for(var t=1; t<params.length; t++) {
2099 var p = config.paramifiers[params[t].name];
2100 if(p && p[handler] instanceof Function)
2101 p[handler](params[t].value);
2105 config.paramifiers = {};
2107 config.paramifiers.start = {
2108 oninit: function(v) {
2109 safeMode = v.toLowerCase() == "safe";
2113 config.paramifiers.open = {
2114 onstart: function(v) {
2115 if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
2116 story.displayTiddler("bottom",v,null,false,null);
2120 config.paramifiers.story = {
2121 onstart: function(v) {
2122 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
2123 invokeParamifier(list,"onstart");
2127 config.paramifiers.search = {
2128 onstart: function(v) {
2129 story.search(v,false,false);
2133 config.paramifiers.searchRegExp = {
2134 onstart: function(v) {
2135 story.prototype.search(v,false,true);
2139 config.paramifiers.tag = {
2140 onstart: function(v) {
2141 story.displayTiddlers(null,store.filterTiddlers("[tag["+v+"]]"),null,false,null);
2145 config.paramifiers.newTiddler = {
2146 onstart: function(v) {
2147 if(!readOnly) {
2148 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
2149 story.focusTiddler(v,"text");
2154 config.paramifiers.newJournal = {
2155 onstart: function(v) {
2156 if(!readOnly) {
2157 var now = new Date();
2158 var title = now.formatString(v.trim());
2159 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
2160 story.focusTiddler(title,"text");
2165 config.paramifiers.readOnly = {
2166 onconfig: function(v) {
2167 var p = v.toLowerCase();
2168 readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
2172 config.paramifiers.theme = {
2173 onconfig: function(v) {
2174 story.switchTheme(v);
2178 config.paramifiers.upgrade = {
2179 onstart: function(v) {
2180 upgradeFrom(v);
2184 config.paramifiers.recent= {
2185 onstart: function(v) {
2186 var titles=[];
2187 var tiddlers=store.getTiddlers("modified","excludeLists").reverse();
2188 for(var i=0; i<v && i<tiddlers.length; i++)
2189 titles.push(tiddlers[i].title);
2190 story.displayTiddlers(null,titles);
2194 config.paramifiers.filter = {
2195 onstart: function(v) {
2196 story.displayTiddlers(null,store.filterTiddlers(v),null,false);
2200 //--
2201 //-- Formatter helpers
2202 //--
2204 function Formatter(formatters)
2206 this.formatters = [];
2207 var pattern = [];
2208 for(var n=0; n<formatters.length; n++) {
2209 pattern.push("(" + formatters[n].match + ")");
2210 this.formatters.push(formatters[n]);
2212 this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
2215 config.formatterHelpers = {
2217 createElementAndWikify: function(w)
2219 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
2222 inlineCssHelper: function(w)
2224 var styles = [];
2225 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2226 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2227 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2228 var s,v;
2229 if(lookaheadMatch[1]) {
2230 s = lookaheadMatch[1].unDash();
2231 v = lookaheadMatch[2];
2232 } else {
2233 s = lookaheadMatch[3].unDash();
2234 v = lookaheadMatch[4];
2236 if(s=="bgcolor")
2237 s = "backgroundColor";
2238 styles.push({style: s, value: v});
2239 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2240 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2241 lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2243 return styles;
2246 applyCssHelper: function(e,styles)
2248 for(var t=0; t< styles.length; t++) {
2249 try {
2250 e.style[styles[t].style] = styles[t].value;
2251 } catch (ex) {
2256 enclosedTextHelper: function(w)
2258 this.lookaheadRegExp.lastIndex = w.matchStart;
2259 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2260 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2261 var text = lookaheadMatch[1];
2262 if(config.browser.isIE)
2263 text = text.replace(/\n/g,"\r");
2264 createTiddlyElement(w.output,this.element,null,null,text);
2265 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2269 isExternalLink: function(link)
2271 if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
2272 return false;
2274 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
2275 if(urlRegExp.exec(link)) {
2276 return true;
2278 if(link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
2279 return true;
2281 return false;
2286 //--
2287 //-- Standard formatters
2288 //--
2290 config.formatters = [
2292 name: "table",
2293 match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
2294 lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
2295 rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
2296 cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
2297 cellTermRegExp: /((?:\x20*)\|)/mg,
2298 rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
2299 handler: function(w)
2301 var table = createTiddlyElement(w.output,"table",null,"twtable");
2302 var prevColumns = [];
2303 var currRowType = null;
2304 var rowContainer;
2305 var rowCount = 0;
2306 w.nextMatch = w.matchStart;
2307 this.lookaheadRegExp.lastIndex = w.nextMatch;
2308 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2309 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2310 var nextRowType = lookaheadMatch[2];
2311 if(nextRowType == "k") {
2312 table.className = lookaheadMatch[1];
2313 w.nextMatch += lookaheadMatch[0].length+1;
2314 } else {
2315 if(nextRowType != currRowType) {
2316 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2317 currRowType = nextRowType;
2319 if(currRowType == "c") {
2320 // Caption
2321 w.nextMatch++;
2322 if(rowContainer != table.firstChild)
2323 table.insertBefore(rowContainer,table.firstChild);
2324 rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
2325 w.subWikifyTerm(rowContainer,this.rowTermRegExp);
2326 } else {
2327 var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
2328 theRow.onmouseover = function() {addClass(this,"hoverRow");};
2329 theRow.onmouseout = function() {removeClass(this,"hoverRow");};
2330 this.rowHandler(w,theRow,prevColumns);
2331 rowCount++;
2334 this.lookaheadRegExp.lastIndex = w.nextMatch;
2335 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2338 rowHandler: function(w,e,prevColumns)
2340 var col = 0;
2341 var colSpanCount = 1;
2342 var prevCell = null;
2343 this.cellRegExp.lastIndex = w.nextMatch;
2344 var cellMatch = this.cellRegExp.exec(w.source);
2345 while(cellMatch && cellMatch.index == w.nextMatch) {
2346 if(cellMatch[1] == "~") {
2347 // Rowspan
2348 var last = prevColumns[col];
2349 if(last) {
2350 last.rowSpanCount++;
2351 last.element.setAttribute("rowspan",last.rowSpanCount);
2352 last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
2353 last.element.valign = "center";
2355 w.nextMatch = this.cellRegExp.lastIndex-1;
2356 } else if(cellMatch[1] == ">") {
2357 // Colspan
2358 colSpanCount++;
2359 w.nextMatch = this.cellRegExp.lastIndex-1;
2360 } else if(cellMatch[2]) {
2361 // End of row
2362 if(prevCell && colSpanCount > 1) {
2363 prevCell.setAttribute("colspan",colSpanCount);
2364 prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
2366 w.nextMatch = this.cellRegExp.lastIndex;
2367 break;
2368 } else {
2369 // Cell
2370 w.nextMatch++;
2371 var styles = config.formatterHelpers.inlineCssHelper(w);
2372 var spaceLeft = false;
2373 var chr = w.source.substr(w.nextMatch,1);
2374 while(chr == " ") {
2375 spaceLeft = true;
2376 w.nextMatch++;
2377 chr = w.source.substr(w.nextMatch,1);
2379 var cell;
2380 if(chr == "!") {
2381 cell = createTiddlyElement(e,"th");
2382 w.nextMatch++;
2383 } else {
2384 cell = createTiddlyElement(e,"td");
2386 prevCell = cell;
2387 prevColumns[col] = {rowSpanCount:1,element:cell};
2388 if(colSpanCount > 1) {
2389 cell.setAttribute("colspan",colSpanCount);
2390 cell.setAttribute("colSpan",colSpanCount); // Needed for IE
2391 colSpanCount = 1;
2393 config.formatterHelpers.applyCssHelper(cell,styles);
2394 w.subWikifyTerm(cell,this.cellTermRegExp);
2395 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
2396 cell.align = spaceLeft ? "center" : "left";
2397 else if(spaceLeft)
2398 cell.align = "right";
2399 w.nextMatch--;
2401 col++;
2402 this.cellRegExp.lastIndex = w.nextMatch;
2403 cellMatch = this.cellRegExp.exec(w.source);
2409 name: "heading",
2410 match: "^!{1,6}",
2411 termRegExp: /(\n)/mg,
2412 handler: function(w)
2414 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
2419 name: "list",
2420 match: "^(?:[\\*#;:]+)",
2421 lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
2422 termRegExp: /(\n)/mg,
2423 handler: function(w)
2425 var stack = [w.output];
2426 var currLevel = 0, currType = null;
2427 var listLevel, listType, itemType, baseType;
2428 w.nextMatch = w.matchStart;
2429 this.lookaheadRegExp.lastIndex = w.nextMatch;
2430 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2431 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2432 if(lookaheadMatch[1]) {
2433 listType = "ul";
2434 itemType = "li";
2435 } else if(lookaheadMatch[2]) {
2436 listType = "ol";
2437 itemType = "li";
2438 } else if(lookaheadMatch[3]) {
2439 listType = "dl";
2440 itemType = "dt";
2441 } else if(lookaheadMatch[4]) {
2442 listType = "dl";
2443 itemType = "dd";
2445 if(!baseType)
2446 baseType = listType;
2447 listLevel = lookaheadMatch[0].length;
2448 w.nextMatch += lookaheadMatch[0].length;
2449 var t;
2450 if(listLevel > currLevel) {
2451 for(t=currLevel; t<listLevel; t++) {
2452 var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
2453 stack.push(createTiddlyElement(target,listType));
2455 } else if(listType!=baseType && listLevel==1) {
2456 w.nextMatch -= lookaheadMatch[0].length;
2457 return;
2458 } else if(listLevel < currLevel) {
2459 for(t=currLevel; t>listLevel; t--)
2460 stack.pop();
2461 } else if(listLevel == currLevel && listType != currType) {
2462 stack.pop();
2463 stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
2465 currLevel = listLevel;
2466 currType = listType;
2467 var e = createTiddlyElement(stack[stack.length-1],itemType);
2468 w.subWikifyTerm(e,this.termRegExp);
2469 this.lookaheadRegExp.lastIndex = w.nextMatch;
2470 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2476 name: "quoteByBlock",
2477 match: "^<<<\\n",
2478 termRegExp: /(^<<<(\n|$))/mg,
2479 element: "blockquote",
2480 handler: config.formatterHelpers.createElementAndWikify
2484 name: "quoteByLine",
2485 match: "^>+",
2486 lookaheadRegExp: /^>+/mg,
2487 termRegExp: /(\n)/mg,
2488 element: "blockquote",
2489 handler: function(w)
2491 var stack = [w.output];
2492 var currLevel = 0;
2493 var newLevel = w.matchLength;
2494 var t;
2495 do {
2496 if(newLevel > currLevel) {
2497 for(t=currLevel; t<newLevel; t++)
2498 stack.push(createTiddlyElement(stack[stack.length-1],this.element));
2499 } else if(newLevel < currLevel) {
2500 for(t=currLevel; t>newLevel; t--)
2501 stack.pop();
2503 currLevel = newLevel;
2504 w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
2505 createTiddlyElement(stack[stack.length-1],"br");
2506 this.lookaheadRegExp.lastIndex = w.nextMatch;
2507 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2508 var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
2509 if(matched) {
2510 newLevel = lookaheadMatch[0].length;
2511 w.nextMatch += lookaheadMatch[0].length;
2513 } while(matched);
2518 name: "rule",
2519 match: "^----+$\\n?",
2520 handler: function(w)
2522 createTiddlyElement(w.output,"hr");
2527 name: "monospacedByLine",
2528 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
2529 element: "pre",
2530 handler: function(w)
2532 switch(w.matchText) {
2533 case "/*{{{*/\n": // CSS
2534 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2535 break;
2536 case "{{{\n": // monospaced block
2537 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2538 break;
2539 case "//{{{\n": // plugin
2540 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2541 break;
2542 case "<!--{{{-->\n": //template
2543 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
2544 break;
2545 default:
2546 break;
2548 config.formatterHelpers.enclosedTextHelper.call(this,w);
2553 name: "wikifyComment",
2554 match: "^(?:/\\*\\*\\*|<!---)\\n",
2555 handler: function(w)
2557 var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
2558 w.subWikifyTerm(w.output,termRegExp);
2563 name: "macro",
2564 match: "<<",
2565 lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
2566 handler: function(w)
2568 this.lookaheadRegExp.lastIndex = w.matchStart;
2569 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2570 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
2571 w.nextMatch = this.lookaheadRegExp.lastIndex;
2572 invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
2578 name: "prettyLink",
2579 match: "\\[\\[",
2580 lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
2581 handler: function(w)
2583 this.lookaheadRegExp.lastIndex = w.matchStart;
2584 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2585 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2586 var e;
2587 var text = lookaheadMatch[1];
2588 if(lookaheadMatch[3]) {
2589 // Pretty bracketted link
2590 var link = lookaheadMatch[3];
2591 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
2592 createExternalLink(w.output,link) : createTiddlyLink(w.output,decodeURIComponent(link),false,null,w.isStatic,w.tiddler);
2593 } else {
2594 // Simple bracketted link
2595 e = createTiddlyLink(w.output,decodeURIComponent(text),false,null,w.isStatic,w.tiddler);
2597 createTiddlyText(e,text);
2598 w.nextMatch = this.lookaheadRegExp.lastIndex;
2604 name: "wikiLink",
2605 match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
2606 handler: function(w)
2608 if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
2609 w.outputText(w.output,w.matchStart+1,w.nextMatch);
2610 return;
2612 if(w.matchStart > 0) {
2613 var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
2614 preRegExp.lastIndex = w.matchStart-1;
2615 var preMatch = preRegExp.exec(w.source);
2616 if(preMatch.index == w.matchStart-1) {
2617 w.outputText(w.output,w.matchStart,w.nextMatch);
2618 return;
2621 if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
2622 var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
2623 w.outputText(link,w.matchStart,w.nextMatch);
2624 } else {
2625 w.outputText(w.output,w.matchStart,w.nextMatch);
2631 name: "urlLink",
2632 match: config.textPrimitives.urlPattern,
2633 handler: function(w)
2635 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
2640 name: "image",
2641 match: "\\[[<>]?[Ii][Mm][Gg]\\[",
2642 lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
2643 handler: function(w)
2645 this.lookaheadRegExp.lastIndex = w.matchStart;
2646 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2647 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2648 var e = w.output;
2649 if(lookaheadMatch[5]) {
2650 var link = lookaheadMatch[5];
2651 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2652 addClass(e,"imageLink");
2654 var img = createTiddlyElement(e,"img");
2655 if(lookaheadMatch[1])
2656 img.align = "left";
2657 else if(lookaheadMatch[2])
2658 img.align = "right";
2659 if(lookaheadMatch[3]) {
2660 img.title = lookaheadMatch[3];
2661 img.setAttribute("alt",lookaheadMatch[3]);
2663 img.src = lookaheadMatch[4];
2664 w.nextMatch = this.lookaheadRegExp.lastIndex;
2670 name: "html",
2671 match: "<[Hh][Tt][Mm][Ll]>",
2672 lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
2673 handler: function(w)
2675 this.lookaheadRegExp.lastIndex = w.matchStart;
2676 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2677 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2678 createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
2679 w.nextMatch = this.lookaheadRegExp.lastIndex;
2685 name: "commentByBlock",
2686 match: "/%",
2687 lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
2688 handler: function(w)
2690 this.lookaheadRegExp.lastIndex = w.matchStart;
2691 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2692 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
2693 w.nextMatch = this.lookaheadRegExp.lastIndex;
2698 name: "characterFormat",
2699 match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
2700 handler: function(w)
2702 switch(w.matchText) {
2703 case "''":
2704 w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
2705 break;
2706 case "//":
2707 w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
2708 break;
2709 case "__":
2710 w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
2711 break;
2712 case "^^":
2713 w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
2714 break;
2715 case "~~":
2716 w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
2717 break;
2718 case "--":
2719 w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
2720 break;
2721 case "{{{":
2722 var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
2723 lookaheadRegExp.lastIndex = w.matchStart;
2724 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2725 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2726 createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
2727 w.nextMatch = lookaheadRegExp.lastIndex;
2729 break;
2735 name: "customFormat",
2736 match: "@@|\\{\\{",
2737 handler: function(w)
2739 switch(w.matchText) {
2740 case "@@":
2741 var e = createTiddlyElement(w.output,"span");
2742 var styles = config.formatterHelpers.inlineCssHelper(w);
2743 if(styles.length == 0)
2744 e.className = "marked";
2745 else
2746 config.formatterHelpers.applyCssHelper(e,styles);
2747 w.subWikifyTerm(e,/(@@)/mg);
2748 break;
2749 case "{{":
2750 var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
2751 lookaheadRegExp.lastIndex = w.matchStart;
2752 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2753 if(lookaheadMatch) {
2754 w.nextMatch = lookaheadRegExp.lastIndex;
2755 e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
2756 w.subWikifyTerm(e,/(\}\}\})/mg);
2758 break;
2764 name: "mdash",
2765 match: "--",
2766 handler: function(w)
2768 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
2773 name: "lineBreak",
2774 match: "\\n|<br ?/?>",
2775 handler: function(w)
2777 createTiddlyElement(w.output,"br");
2782 name: "rawText",
2783 match: "\\\"{3}|<nowiki>",
2784 lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
2785 handler: function(w)
2787 this.lookaheadRegExp.lastIndex = w.matchStart;
2788 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2789 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2790 createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
2791 w.nextMatch = this.lookaheadRegExp.lastIndex;
2797 name: "htmlEntitiesEncoding",
2798 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};)",
2799 handler: function(w)
2801 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
2807 //--
2808 //-- Wikifier
2809 //--
2811 function getParser(tiddler,format)
2813 if(tiddler) {
2814 if(!format)
2815 format = tiddler.fields["wikiformat"];
2816 var i;
2817 if(format) {
2818 for(i in config.parsers) {
2819 if(format == config.parsers[i].format)
2820 return config.parsers[i];
2822 } else {
2823 for(i in config.parsers) {
2824 if(tiddler.isTagged(config.parsers[i].formatTag))
2825 return config.parsers[i];
2829 return formatter;
2832 function wikify(source,output,highlightRegExp,tiddler)
2834 if(source) {
2835 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
2836 var t0 = new Date();
2837 wikifier.subWikify(output);
2838 if(tiddler && config.options.chkDisplayInstrumentation)
2839 displayMessage("wikify:" +tiddler.title+ " in " + (new Date()-t0) + " ms");
2843 function wikifyStatic(source,highlightRegExp,tiddler,format)
2845 var e = createTiddlyElement(document.body,"pre");
2846 e.style.display = "none";
2847 var html = "";
2848 if(source && source != "") {
2849 if(!tiddler)
2850 tiddler = new Tiddler("temp");
2851 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2852 wikifier.isStatic = true;
2853 wikifier.subWikify(e);
2854 html = e.innerHTML;
2855 removeNode(e);
2857 return html;
2860 function wikifyPlain(title,theStore,limit)
2862 if(!theStore)
2863 theStore = store;
2864 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2865 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2866 } else {
2867 return "";
2871 function wikifyPlainText(text,limit,tiddler)
2873 if(limit > 0)
2874 text = text.substr(0,limit);
2875 var wikifier = new Wikifier(text,formatter,null,tiddler);
2876 return wikifier.wikifyPlain();
2879 function highlightify(source,output,highlightRegExp,tiddler)
2881 if(source) {
2882 var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
2883 wikifier.outputText(output,0,source.length);
2887 function Wikifier(source,formatter,highlightRegExp,tiddler)
2889 this.source = source;
2890 this.output = null;
2891 this.formatter = formatter;
2892 this.nextMatch = 0;
2893 this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
2894 this.highlightRegExp = highlightRegExp;
2895 this.highlightMatch = null;
2896 this.isStatic = false;
2897 if(highlightRegExp) {
2898 highlightRegExp.lastIndex = 0;
2899 this.highlightMatch = highlightRegExp.exec(source);
2901 this.tiddler = tiddler;
2904 Wikifier.prototype.wikifyPlain = function()
2906 var e = createTiddlyElement(document.body,"div");
2907 e.style.display = "none";
2908 this.subWikify(e);
2909 var text = getPlainText(e);
2910 removeNode(e);
2911 return text;
2914 Wikifier.prototype.subWikify = function(output,terminator)
2916 try {
2917 if(terminator)
2918 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
2919 else
2920 this.subWikifyUnterm(output);
2921 } catch(ex) {
2922 showException(ex);
2926 Wikifier.prototype.subWikifyUnterm = function(output)
2928 var oldOutput = this.output;
2929 this.output = output;
2930 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2931 var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2932 while(formatterMatch) {
2933 // Output any text before the match
2934 if(formatterMatch.index > this.nextMatch)
2935 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2936 // Set the match parameters for the handler
2937 this.matchStart = formatterMatch.index;
2938 this.matchLength = formatterMatch[0].length;
2939 this.matchText = formatterMatch[0];
2940 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2941 for(var t=1; t<formatterMatch.length; t++) {
2942 if(formatterMatch[t]) {
2943 this.formatter.formatters[t-1].handler(this);
2944 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2945 break;
2948 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2950 if(this.nextMatch < this.source.length) {
2951 this.outputText(this.output,this.nextMatch,this.source.length);
2952 this.nextMatch = this.source.length;
2954 this.output = oldOutput;
2957 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
2959 var oldOutput = this.output;
2960 this.output = output;
2961 terminatorRegExp.lastIndex = this.nextMatch;
2962 var terminatorMatch = terminatorRegExp.exec(this.source);
2963 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2964 var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2965 while(terminatorMatch || formatterMatch) {
2966 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
2967 if(terminatorMatch.index > this.nextMatch)
2968 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
2969 this.matchText = terminatorMatch[1];
2970 this.matchLength = terminatorMatch[1].length;
2971 this.matchStart = terminatorMatch.index;
2972 this.nextMatch = this.matchStart + this.matchLength;
2973 this.output = oldOutput;
2974 return;
2976 if(formatterMatch.index > this.nextMatch)
2977 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2978 this.matchStart = formatterMatch.index;
2979 this.matchLength = formatterMatch[0].length;
2980 this.matchText = formatterMatch[0];
2981 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2982 for(var t=1; t<formatterMatch.length; t++) {
2983 if(formatterMatch[t]) {
2984 this.formatter.formatters[t-1].handler(this);
2985 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2986 break;
2989 terminatorRegExp.lastIndex = this.nextMatch;
2990 terminatorMatch = terminatorRegExp.exec(this.source);
2991 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2993 if(this.nextMatch < this.source.length) {
2994 this.outputText(this.output,this.nextMatch,this.source.length);
2995 this.nextMatch = this.source.length;
2997 this.output = oldOutput;
3000 Wikifier.prototype.outputText = function(place,startPos,endPos)
3002 while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
3003 if(this.highlightMatch.index > startPos) {
3004 createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
3005 startPos = this.highlightMatch.index;
3007 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
3008 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
3009 startPos = highlightEnd;
3010 if(startPos >= this.highlightRegExp.lastIndex)
3011 this.highlightMatch = this.highlightRegExp.exec(this.source);
3013 if(startPos < endPos) {
3014 createTiddlyText(place,this.source.substring(startPos,endPos));
3018 //--
3019 //-- Macro definitions
3020 //--
3022 config.macros.today.handler = function(place,macroName,params)
3024 var now = new Date();
3025 var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
3026 createTiddlyElement(place,"span",null,null,text);
3029 config.macros.version.handler = function(place)
3031 createTiddlyElement(place,"span",null,null,formatVersion());
3034 config.macros.list.handler = function(place,macroName,params)
3036 var type = params[0] || "all";
3037 var list = document.createElement("ul");
3038 place.appendChild(list);
3039 if(this[type].prompt)
3040 createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
3041 var results;
3042 if(this[type].handler)
3043 results = this[type].handler(params);
3044 for(var t = 0; t < results.length; t++) {
3045 var li = document.createElement("li");
3046 list.appendChild(li);
3047 createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
3051 config.macros.list.all.handler = function(params)
3053 return store.reverseLookup("tags","excludeLists",false,"title");
3056 config.macros.list.missing.handler = function(params)
3058 return store.getMissingLinks();
3061 config.macros.list.orphans.handler = function(params)
3063 return store.getOrphans();
3066 config.macros.list.shadowed.handler = function(params)
3068 return store.getShadowed();
3071 config.macros.list.touched.handler = function(params)
3073 return store.getTouched();
3076 config.macros.list.filter.handler = function(params)
3078 var filter = params[1];
3079 var results = [];
3080 if(filter) {
3081 var tiddlers = store.filterTiddlers(filter);
3082 for(var t=0; t<tiddlers.length; t++)
3083 results.push(tiddlers[t].title);
3085 return results;
3088 config.macros.allTags.handler = function(place,macroName,params)
3090 var tags = store.getTags(params[0]);
3091 var ul = createTiddlyElement(place,"ul");
3092 if(tags.length == 0)
3093 createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
3094 for(var t=0; t<tags.length; t++) {
3095 var title = tags[t][0];
3096 var info = getTiddlyLinkInfo(title);
3097 var li = createTiddlyElement(ul,"li");
3098 var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
3099 btn.setAttribute("tag",title);
3100 btn.setAttribute("refresh","link");
3101 btn.setAttribute("tiddlyLink",title);
3105 config.macros.timeline.handler = function(place,macroName,params)
3107 var field = params[0] || "modified";
3108 var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
3109 var lastDay = "";
3110 var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
3111 var dateFormat = params[2] || this.dateFormat;
3112 for(var t=tiddlers.length-1; t>=last; t--) {
3113 var tiddler = tiddlers[t];
3114 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
3115 if(theDay != lastDay) {
3116 var ul = document.createElement("ul");
3117 place.appendChild(ul);
3118 createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
3119 lastDay = theDay;
3121 createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
3125 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3127 params = paramString.parseParams("name",null,true,false,true);
3128 var names = params[0]["name"];
3129 var tiddlerName = names[0];
3130 var className = names[1] || null;
3131 var args = params[0]["with"];
3132 var wrapper = createTiddlyElement(place,"span",null,className);
3133 if(!args) {
3134 wrapper.setAttribute("refresh","content");
3135 wrapper.setAttribute("tiddler",tiddlerName);
3137 var text = store.getTiddlerText(tiddlerName);
3138 if(text) {
3139 var stack = config.macros.tiddler.tiddlerStack;
3140 if(stack.indexOf(tiddlerName) !== -1)
3141 return;
3142 stack.push(tiddlerName);
3143 try {
3144 var n = args ? Math.min(args.length,9) : 0;
3145 for(var i=0; i<n; i++) {
3146 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
3147 text = text.replace(placeholderRE,args[i]);
3149 config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
3150 } finally {
3151 stack.pop();
3156 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
3158 wikify(text,place,null,store.getTiddler(tiddlerName));
3161 config.macros.tiddler.tiddlerStack = [];
3163 config.macros.tag.handler = function(place,macroName,params)
3165 createTagButton(place,params[0],null,params[1],params[2]);
3168 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3170 params = paramString.parseParams("anon",null,true,false,false);
3171 var ul = createTiddlyElement(place,"ul");
3172 var title = getParam(params,"anon","");
3173 if(title && store.tiddlerExists(title))
3174 tiddler = store.getTiddler(title);
3175 var sep = getParam(params,"sep"," ");
3176 var lingo = config.views.wikified.tag;
3177 var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
3178 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
3179 for(var t=0; t<tiddler.tags.length; t++) {
3180 createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
3181 if(t<tiddler.tags.length-1)
3182 createTiddlyText(ul,sep);
3186 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3188 params = paramString.parseParams("anon",null,true,false,false);
3189 var ul = createTiddlyElement(place,"ul");
3190 var title = getParam(params,"anon","");
3191 if(title == "" && tiddler instanceof Tiddler)
3192 title = tiddler.title;
3193 var sep = getParam(params,"sep"," ");
3194 ul.setAttribute("title",this.tooltip.format([title]));
3195 var tagged = store.getTaggedTiddlers(title);
3196 var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
3197 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
3198 for(var t=0; t<tagged.length; t++) {
3199 createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
3200 if(t<tagged.length-1)
3201 createTiddlyText(ul,sep);
3205 config.macros.closeAll.handler = function(place)
3207 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3210 config.macros.closeAll.onClick = function(e)
3212 story.closeAllTiddlers();
3213 return false;
3216 config.macros.permaview.handler = function(place)
3218 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3221 config.macros.permaview.onClick = function(e)
3223 story.permaView();
3224 return false;
3227 config.macros.saveChanges.handler = function(place,macroName,params)
3229 if(!readOnly)
3230 createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
3233 config.macros.saveChanges.onClick = function(e)
3235 saveChanges();
3236 return false;
3239 config.macros.slider.onClickSlider = function(ev)
3241 var e = ev || window.event;
3242 var n = this.nextSibling;
3243 var cookie = n.getAttribute("cookie");
3244 var isOpen = n.style.display != "none";
3245 if(config.options.chkAnimate && anim && typeof Slider == "function")
3246 anim.startAnimating(new Slider(n,!isOpen,null,"none"));
3247 else
3248 n.style.display = isOpen ? "none" : "block";
3249 config.options[cookie] = !isOpen;
3250 saveOptionCookie(cookie);
3251 return false;
3254 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
3256 var c = cookie || "";
3257 var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
3258 var panel = createTiddlyElement(null,"div",null,"sliderPanel");
3259 panel.setAttribute("cookie",c);
3260 panel.style.display = config.options[c] ? "block" : "none";
3261 place.appendChild(panel);
3262 return panel;
3265 config.macros.slider.handler = function(place,macroName,params)
3267 var panel = this.createSlider(place,params[0],params[2],params[3]);
3268 var text = store.getTiddlerText(params[1]);
3269 panel.setAttribute("refresh","content");
3270 panel.setAttribute("tiddler",params[1]);
3271 if(text)
3272 wikify(text,panel,null,store.getTiddler(params[1]));
3275 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
3276 config.macros.gradient.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3278 var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
3279 panel.style.position = "relative";
3280 panel.style.overflow = "hidden";
3281 panel.style.zIndex = "0";
3282 if(wikifier) {
3283 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
3284 config.formatterHelpers.applyCssHelper(panel,styles);
3286 params = paramString.parseParams("color");
3287 var locolors = [], hicolors = [];
3288 for(var t=2; t<params.length; t++) {
3289 var c = new RGB(params[t].value);
3290 if(params[t].name == "snap") {
3291 hicolors[hicolors.length-1] = c;
3292 } else {
3293 locolors.push(c);
3294 hicolors.push(c);
3297 drawGradient(panel,params[1].value != "vert",locolors,hicolors);
3298 if(wikifier)
3299 wikifier.subWikify(panel,">>");
3300 if(document.all) {
3301 panel.style.height = "100%";
3302 panel.style.width = "100%";
3306 config.macros.message.handler = function(place,macroName,params)
3308 if(params[0]) {
3309 var names = params[0].split(".");
3310 var lookupMessage = function(root,nameIndex) {
3311 if(names[nameIndex] in root) {
3312 if(nameIndex < names.length-1)
3313 return (lookupMessage(root[names[nameIndex]],nameIndex+1));
3314 else
3315 return root[names[nameIndex]];
3316 } else
3317 return null;
3319 var m = lookupMessage(config,0);
3320 if(m == null)
3321 m = lookupMessage(window,0);
3322 createTiddlyText(place,m.toString().format(params.splice(1)));
3327 config.macros.view.views = {
3328 text: function(value,place,params,wikifier,paramString,tiddler) {
3329 highlightify(value,place,highlightHack,tiddler);
3331 link: function(value,place,params,wikifier,paramString,tiddler) {
3332 createTiddlyLink(place,value,true);
3334 wikified: function(value,place,params,wikifier,paramString,tiddler) {
3335 if(params[2])
3336 value=params[2].unescapeLineBreaks().format([value]);
3337 wikify(value,place,highlightHack,tiddler);
3339 date: function(value,place,params,wikifier,paramString,tiddler) {
3340 value = Date.convertFromYYYYMMDDHHMM(value);
3341 createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
3345 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3347 if((tiddler instanceof Tiddler) && params[0]) {
3348 var value = store.getValue(tiddler,params[0]);
3349 if(value) {
3350 var type = params[1] || config.macros.view.defaultView;
3351 var handler = config.macros.view.views[type];
3352 if(handler)
3353 handler(value,place,params,wikifier,paramString,tiddler);
3358 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3360 var field = params[0];
3361 var rows = params[1] || 0;
3362 var defVal = params[2] || '';
3363 if((tiddler instanceof Tiddler) && field) {
3364 story.setDirty(tiddler.title,true);
3365 var e,v;
3366 if(field != "text" && !rows) {
3367 e = createTiddlyElement(null,"input");
3368 if(tiddler.isReadOnly())
3369 e.setAttribute("readOnly","readOnly");
3370 e.setAttribute("edit",field);
3371 e.setAttribute("type","text");
3372 e.value = store.getValue(tiddler,field) || defVal;
3373 e.setAttribute("size","40");
3374 e.setAttribute("autocomplete","off");
3375 place.appendChild(e);
3376 } else {
3377 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
3378 var wrapper2 = createTiddlyElement(wrapper1,"div");
3379 e = createTiddlyElement(wrapper2,"textarea");
3380 if(tiddler.isReadOnly())
3381 e.setAttribute("readOnly","readOnly");
3382 e.value = v = store.getValue(tiddler,field) || defVal;
3383 rows = rows || 10;
3384 var lines = v.match(/\n/mg);
3385 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
3386 if(lines != null && lines.length > rows)
3387 rows = lines.length + 5;
3388 rows = Math.min(rows,maxLines);
3389 e.setAttribute("rows",rows);
3390 e.setAttribute("edit",field);
3391 place.appendChild(wrapper1);
3393 return e;
3397 config.macros.tagChooser.onClick = function(ev)
3399 var e = ev || window.event;
3400 var lingo = config.views.editor.tagChooser;
3401 var popup = Popup.create(this);
3402 var tags = store.getTags("excludeLists");
3403 if(tags.length == 0)
3404 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
3405 for(var t=0; t<tags.length; t++) {
3406 var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
3407 tag.setAttribute("tag",tags[t][0]);
3408 tag.setAttribute("tiddler",this.getAttribute("tiddler"));
3410 Popup.show();
3411 e.cancelBubble = true;
3412 if(e.stopPropagation) e.stopPropagation();
3413 return false;
3416 config.macros.tagChooser.onTagClick = function(ev)
3418 var e = ev || window.event;
3419 if(e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
3420 var tag = this.getAttribute("tag");
3421 var title = this.getAttribute("tiddler");
3422 if(!readOnly)
3423 story.setTiddlerTag(title,tag,0);
3424 return false;
3427 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3429 if(tiddler instanceof Tiddler) {
3430 var lingo = config.views.editor.tagChooser;
3431 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
3432 btn.setAttribute("tiddler",tiddler.title);
3436 config.macros.refreshDisplay.handler = function(place)
3438 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3441 config.macros.refreshDisplay.onClick = function(e)
3443 refreshAll();
3444 return false;
3447 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3449 var title = tiddler ? tiddler.title : null;
3450 var a = title ? config.annotations[title] : null;
3451 if(!tiddler || !title || !a)
3452 return;
3453 var text = a.format([title]);
3454 wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
3457 //--
3458 //-- NewTiddler and NewJournal macros
3459 //--
3461 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
3463 var tags = [];
3464 for(var t=1; t<params.length; t++) {
3465 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
3466 tags.push(params[t].value);
3468 label = getParam(params,"label",label);
3469 prompt = getParam(params,"prompt",prompt);
3470 accessKey = getParam(params,"accessKey",accessKey);
3471 newFocus = getParam(params,"focus",newFocus);
3472 var customFields = getParam(params,"fields","");
3473 if(!customFields && !store.isShadowTiddler(title))
3474 customFields = String.encodeHashMap(config.defaultCustomFields);
3475 var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
3476 btn.setAttribute("newTitle",title);
3477 btn.setAttribute("isJournal",isJournal ? "true" : "false");
3478 if(tags.length > 0)
3479 btn.setAttribute("params",tags.join("|"));
3480 btn.setAttribute("newFocus",newFocus);
3481 btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
3482 if(customFields !== "")
3483 btn.setAttribute("customFields",customFields);
3484 var text = getParam(params,"text");
3485 if(text !== undefined)
3486 btn.setAttribute("newText",text);
3487 return btn;
3490 config.macros.newTiddler.onClickNewTiddler = function()
3492 var title = this.getAttribute("newTitle");
3493 if(this.getAttribute("isJournal") == "true") {
3494 title = new Date().formatString(title.trim());
3496 var params = this.getAttribute("params");
3497 var tags = params ? params.split("|") : [];
3498 var focus = this.getAttribute("newFocus");
3499 var template = this.getAttribute("newTemplate");
3500 var customFields = this.getAttribute("customFields");
3501 if(!customFields && !store.isShadowTiddler(title))
3502 customFields = String.encodeHashMap(config.defaultCustomFields);
3503 story.displayTiddler(null,title,template,false,null,null);
3504 var tiddlerElem = story.getTiddler(title);
3505 if(customFields)
3506 story.addCustomFields(tiddlerElem,customFields);
3507 var text = this.getAttribute("newText");
3508 if(typeof text == "string")
3509 story.getTiddlerField(title,"text").value = text.format([title]);
3510 for(var t=0;t<tags.length;t++)
3511 story.setTiddlerTag(title,tags[t],+1);
3512 story.focusTiddler(title,focus);
3513 return false;
3516 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString)
3518 if(!readOnly) {
3519 params = paramString.parseParams("anon",null,true,false,false);
3520 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
3521 title = getParam(params,"title",title);
3522 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
3526 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString)
3528 if(!readOnly) {
3529 params = paramString.parseParams("anon",null,true,false,false);
3530 var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
3531 title = getParam(params,"title",title);
3532 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
3536 //--
3537 //-- Search macro
3538 //--
3540 config.macros.search.handler = function(place,macroName,params)
3542 var searchTimeout = null;
3543 var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick,"searchButton");
3544 var txt = createTiddlyElement(place,"input",null,"txtOptionInput searchField");
3545 if(params[0])
3546 txt.value = params[0];
3547 txt.onkeyup = this.onKeyPress;
3548 txt.onfocus = this.onFocus;
3549 txt.setAttribute("size",this.sizeTextbox);
3550 txt.setAttribute("accessKey",this.accessKey);
3551 txt.setAttribute("autocomplete","off");
3552 txt.setAttribute("lastSearchText","");
3553 if(config.browser.isSafari) {
3554 txt.setAttribute("type","search");
3555 txt.setAttribute("results","5");
3556 } else {
3557 txt.setAttribute("type","text");
3561 // Global because there's only ever one outstanding incremental search timer
3562 config.macros.search.timeout = null;
3564 config.macros.search.doSearch = function(txt)
3566 if(txt.value.length > 0) {
3567 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
3568 txt.setAttribute("lastSearchText",txt.value);
3572 config.macros.search.onClick = function(e)
3574 config.macros.search.doSearch(this.nextSibling);
3575 return false;
3578 config.macros.search.onKeyPress = function(ev)
3580 var e = ev || window.event;
3581 switch(e.keyCode) {
3582 case 13: // Ctrl-Enter
3583 case 10: // Ctrl-Enter on IE PC
3584 config.macros.search.doSearch(this);
3585 break;
3586 case 27: // Escape
3587 this.value = "";
3588 clearMessage();
3589 break;
3591 if(config.options.chkIncrementalSearch) {
3592 if(this.value.length > 2) {
3593 if(this.value != this.getAttribute("lastSearchText")) {
3594 if(config.macros.search.timeout)
3595 clearTimeout(config.macros.search.timeout);
3596 var txt = this;
3597 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3599 } else {
3600 if(config.macros.search.timeout)
3601 clearTimeout(config.macros.search.timeout);
3606 config.macros.search.onFocus = function(e)
3608 this.select();
3611 //--
3612 //-- Tabs macro
3613 //--
3615 config.macros.tabs.handler = function(place,macroName,params)
3617 var cookie = params[0];
3618 var numTabs = (params.length-1)/3;
3619 var wrapper = createTiddlyElement(null,"div",null,"tabsetWrapper " + cookie);
3620 var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
3621 tabset.setAttribute("cookie",cookie);
3622 var validTab = false;
3623 for(var t=0; t<numTabs; t++) {
3624 var label = params[t*3+1];
3625 var prompt = params[t*3+2];
3626 var content = params[t*3+3];
3627 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
3628 tab.setAttribute("tab",label);
3629 tab.setAttribute("content",content);
3630 tab.title = prompt;
3631 if(config.options[cookie] == label)
3632 validTab = true;
3634 if(!validTab)
3635 config.options[cookie] = params[1];
3636 place.appendChild(wrapper);
3637 this.switchTab(tabset,config.options[cookie]);
3640 config.macros.tabs.onClickTab = function(e)
3642 config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
3643 return false;
3646 config.macros.tabs.switchTab = function(tabset,tab)
3648 var cookie = tabset.getAttribute("cookie");
3649 var theTab = null;
3650 var nodes = tabset.childNodes;
3651 for(var t=0; t<nodes.length; t++) {
3652 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
3653 theTab = nodes[t];
3654 theTab.className = "tab tabSelected";
3655 } else {
3656 nodes[t].className = "tab tabUnselected";
3659 if(theTab) {
3660 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
3661 removeNode(tabset.nextSibling);
3662 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
3663 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
3664 var contentTitle = theTab.getAttribute("content");
3665 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
3666 if(cookie) {
3667 config.options[cookie] = tab;
3668 saveOptionCookie(cookie);
3673 //--
3674 //-- Tiddler toolbar
3675 //--
3677 // Create a toolbar command button
3678 config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
3680 if(typeof commandName != "string") {
3681 var c = null;
3682 for(var t in config.commands) {
3683 if(config.commands[t] == commandName)
3684 c = t;
3686 commandName = c;
3688 if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
3689 var command = config.commands[commandName];
3690 if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
3691 var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
3692 var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
3693 var cmd;
3694 switch(command.type) {
3695 case "popup":
3696 cmd = this.onClickPopup;
3697 break;
3698 case "command":
3699 default:
3700 cmd = this.onClickCommand;
3701 break;
3703 var btn = createTiddlyButton(null,text,tooltip,cmd);
3704 btn.setAttribute("commandName",commandName);
3705 btn.setAttribute("tiddler",tiddler.title);
3706 if(className)
3707 addClass(btn,className);
3708 place.appendChild(btn);
3713 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
3715 var title = tiddler.title;
3716 var ro = tiddler.isReadOnly();
3717 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
3718 return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
3721 config.macros.toolbar.getCommandText = function(command,tiddler)
3723 return tiddler.isReadOnly() && command.readOnlyText || command.text;
3726 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
3728 return tiddler.isReadOnly() && command.readOnlyTooltip || command.tooltip;
3731 config.macros.toolbar.onClickCommand = function(ev)
3733 var e = ev || window.event;
3734 e.cancelBubble = true;
3735 if(e.stopPropagation) e.stopPropagation();
3736 var command = config.commands[this.getAttribute("commandName")];
3737 return command.handler(e,this,this.getAttribute("tiddler"));
3740 config.macros.toolbar.onClickPopup = function(ev)
3742 var e = ev || window.event;
3743 e.cancelBubble = true;
3744 if(e.stopPropagation) e.stopPropagation();
3745 var popup = Popup.create(this);
3746 var command = config.commands[this.getAttribute("commandName")];
3747 var title = this.getAttribute("tiddler");
3748 var tiddler = store.fetchTiddler(title);
3749 popup.setAttribute("tiddler",title);
3750 command.handlePopup(popup,title);
3751 Popup.show();
3752 return false;
3755 // Invoke the first command encountered from a given place that is tagged with a specified class
3756 config.macros.toolbar.invokeCommand = function(place,className,event)
3758 var children = place.getElementsByTagName("a");
3759 for(var t=0; t<children.length; t++) {
3760 var c = children[t];
3761 if(hasClass(c,className) && c.getAttribute && c.getAttribute("commandName")) {
3762 if(c.onclick instanceof Function)
3763 c.onclick.call(c,event);
3764 break;
3769 config.macros.toolbar.onClickMore = function(ev)
3771 var e = this.nextSibling;
3772 e.style.display = "inline";
3773 removeNode(this);
3774 return false;
3777 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3779 for(var t=0; t<params.length; t++) {
3780 var c = params[t];
3781 switch(c) {
3782 case '>':
3783 var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
3784 addClass(btn,"moreCommand");
3785 var e = createTiddlyElement(place,"span",null,"moreCommand");
3786 e.style.display = "none";
3787 place = e;
3788 break;
3789 default:
3790 var className = "";
3791 switch(c.substr(0,1)) {
3792 case "+":
3793 className = "defaultCommand";
3794 c = c.substr(1);
3795 break;
3796 case "-":
3797 className = "cancelCommand";
3798 c = c.substr(1);
3799 break;
3801 if(c in config.commands)
3802 this.createCommand(place,c,tiddler,className);
3803 break;
3808 //--
3809 //-- Menu and toolbar commands
3810 //--
3812 config.commands.closeTiddler.handler = function(event,src,title)
3814 if(story.isDirty(title) && !readOnly) {
3815 if(!confirm(config.commands.cancelTiddler.warning.format([title])))
3816 return false;
3818 story.setDirty(title,false);
3819 story.closeTiddler(title,true);
3820 return false;
3823 config.commands.closeOthers.handler = function(event,src,title)
3825 story.closeAllTiddlers(title);
3826 return false;
3829 config.commands.editTiddler.handler = function(event,src,title)
3831 clearMessage();
3832 var tiddlerElem = story.getTiddler(title);
3833 var fields = tiddlerElem.getAttribute("tiddlyFields");
3834 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
3835 story.focusTiddler(title,config.options.txtEditorFocus||"text");
3836 return false;
3839 config.commands.saveTiddler.handler = function(event,src,title)
3841 var newTitle = story.saveTiddler(title,event.shiftKey);
3842 if(newTitle)
3843 story.displayTiddler(null,newTitle);
3844 return false;
3847 config.commands.cancelTiddler.handler = function(event,src,title)
3849 if(story.hasChanges(title) && !readOnly) {
3850 if(!confirm(this.warning.format([title])))
3851 return false;
3853 story.setDirty(title,false);
3854 story.displayTiddler(null,title);
3855 return false;
3858 config.commands.deleteTiddler.handler = function(event,src,title)
3860 var deleteIt = true;
3861 if(config.options.chkConfirmDelete)
3862 deleteIt = confirm(this.warning.format([title]));
3863 if(deleteIt) {
3864 store.removeTiddler(title);
3865 story.closeTiddler(title,true);
3866 autoSaveChanges();
3868 return false;
3871 config.commands.permalink.handler = function(event,src,title)
3873 var t = encodeURIComponent(String.encodeTiddlyLink(title));
3874 if(window.location.hash != t)
3875 window.location.hash = t;
3876 return false;
3879 config.commands.references.handlePopup = function(popup,title)
3881 var references = store.getReferringTiddlers(title);
3882 var c = false;
3883 for(var r=0; r<references.length; r++) {
3884 if(references[r].title != title && !references[r].isTagged("excludeLists")) {
3885 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
3886 c = true;
3889 if(!c)
3890 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
3893 config.commands.jump.handlePopup = function(popup,title)
3895 story.forEachTiddler(function(title,element) {
3896 createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
3900 config.commands.syncing.handlePopup = function(popup,title)
3902 var tiddler = store.fetchTiddler(title);
3903 if(!tiddler)
3904 return;
3905 var serverType = tiddler.getServerType();
3906 var serverHost = tiddler.fields['server.host'];
3907 var serverWorkspace = tiddler.fields['server.workspace'];
3908 if(!serverWorkspace)
3909 serverWorkspace = "";
3910 if(serverType) {
3911 var e = createTiddlyElement(popup,"li",null,"popupMessage");
3912 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3913 } else {
3914 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
3916 if(serverType) {
3917 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3918 var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
3919 btn.setAttribute("tiddler",title);
3920 btn.setAttribute("server.type","");
3922 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3923 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
3924 var feeds = store.getTaggedTiddlers("systemServer","title");
3925 for(var t=0; t<feeds.length; t++) {
3926 var f = feeds[t];
3927 var feedServerType = store.getTiddlerSlice(f.title,"Type");
3928 if(!feedServerType)
3929 feedServerType = "file";
3930 var feedServerHost = store.getTiddlerSlice(f.title,"URL");
3931 if(!feedServerHost)
3932 feedServerHost = "";
3933 var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
3934 if(!feedServerWorkspace)
3935 feedServerWorkspace = "";
3936 var caption = f.title;
3937 if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
3938 caption = config.commands.syncing.currServerMarker + caption;
3939 } else {
3940 caption = config.commands.syncing.notCurrServerMarker + caption;
3942 btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
3943 btn.setAttribute("tiddler",title);
3944 btn.setAttribute("server.type",feedServerType);
3945 btn.setAttribute("server.host",feedServerHost);
3946 btn.setAttribute("server.workspace",feedServerWorkspace);
3950 config.commands.syncing.onChooseServer = function(e)
3952 var tiddler = this.getAttribute("tiddler");
3953 var serverType = this.getAttribute("server.type");
3954 if(serverType) {
3955 store.addTiddlerFields(tiddler,{
3956 "server.type": serverType,
3957 "server.host": this.getAttribute("server.host"),
3958 "server.workspace": this.getAttribute("server.workspace")
3960 } else {
3961 store.setValue(tiddler,"server",null);
3963 return false;
3966 config.commands.fields.handlePopup = function(popup,title)
3968 var tiddler = store.fetchTiddler(title);
3969 if(!tiddler)
3970 return;
3971 var fields = {};
3972 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
3973 var items = [];
3974 for(var t in fields) {
3975 items.push({field: t,value: fields[t]});
3977 items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
3978 if(items.length > 0)
3979 ListView.create(popup,items,this.listViewTemplate);
3980 else
3981 createTiddlyElement(popup,"div",null,null,this.emptyText);
3984 //--
3985 //-- Tiddler() object
3986 //--
3988 function Tiddler(title)
3990 this.title = title;
3991 this.text = "";
3992 this.modifier = null;
3993 this.created = new Date();
3994 this.modified = this.created;
3995 this.links = [];
3996 this.linksUpdated = false;
3997 this.tags = [];
3998 this.fields = {};
3999 return this;
4002 Tiddler.prototype.getLinks = function()
4004 if(this.linksUpdated==false)
4005 this.changed();
4006 return this.links;
4009 // Returns the fields that are inherited in string field:"value" field2:"value2" format
4010 Tiddler.prototype.getInheritedFields = function()
4012 var f = {};
4013 for(var i in this.fields) {
4014 if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
4015 f[i] = this.fields[i];
4018 return String.encodeHashMap(f);
4021 // Increment the changeCount of a tiddler
4022 Tiddler.prototype.incChangeCount = function()
4024 var c = this.fields['changecount'];
4025 c = c ? parseInt(c,10) : 0;
4026 this.fields['changecount'] = String(c+1);
4029 // Clear the changeCount of a tiddler
4030 Tiddler.prototype.clearChangeCount = function()
4032 if(this.fields['changecount']) {
4033 delete this.fields['changecount'];
4037 Tiddler.prototype.doNotSave = function()
4039 return this.fields['doNotSave'];
4042 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
4043 Tiddler.prototype.isTouched = function()
4045 var changeCount = this.fields['changecount'];
4046 if(changeCount === undefined)
4047 changeCount = 0;
4048 return changeCount > 0;
4051 // Return the tiddler as an RSS item
4052 Tiddler.prototype.toRssItem = function(uri)
4054 var s = [];
4055 s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
4056 s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
4057 for(var t=0; t<this.tags.length; t++)
4058 s.push("<category>" + this.tags[t] + "</category>");
4059 s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
4060 s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
4061 return s.join("\n");
4064 // Format the text for storage in an RSS item
4065 Tiddler.prototype.saveToRss = function(uri)
4067 return "<item>\n" + this.toRssItem(uri) + "\n</item>";
4070 // Change the text and other attributes of a tiddler
4071 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
4073 this.assign(title,text,modifier,modified,tags,created,fields);
4074 this.changed();
4075 return this;
4078 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
4079 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
4081 if(title != undefined)
4082 this.title = title;
4083 if(text != undefined)
4084 this.text = text;
4085 if(modifier != undefined)
4086 this.modifier = modifier;
4087 if(modified != undefined)
4088 this.modified = modified;
4089 if(created != undefined)
4090 this.created = created;
4091 if(fields != undefined)
4092 this.fields = fields;
4093 if(tags != undefined)
4094 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
4095 else if(this.tags == undefined)
4096 this.tags = [];
4097 return this;
4100 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
4101 Tiddler.prototype.getTags = function()
4103 return String.encodeTiddlyLinkList(this.tags);
4106 // Test if a tiddler carries a tag
4107 Tiddler.prototype.isTagged = function(tag)
4109 return this.tags.indexOf(tag) != -1;
4112 // Static method to convert "\n" to newlines, "\s" to "\"
4113 Tiddler.unescapeLineBreaks = function(text)
4115 return text ? text.unescapeLineBreaks() : "";
4118 // Convert newlines to "\n", "\" to "\s"
4119 Tiddler.prototype.escapeLineBreaks = function()
4121 return this.text.escapeLineBreaks();
4124 // Updates the secondary information (like links[] array) after a change to a tiddler
4125 Tiddler.prototype.changed = function()
4127 this.links = [];
4128 var t = this.autoLinkWikiWords() ? 0 : 1;
4129 var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
4130 tiddlerLinkRegExp.lastIndex = 0;
4131 var formatMatch = tiddlerLinkRegExp.exec(this.text);
4132 while(formatMatch) {
4133 var lastIndex = tiddlerLinkRegExp.lastIndex;
4134 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
4135 // wikiWordLink
4136 if(formatMatch.index > 0) {
4137 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
4138 preRegExp.lastIndex = formatMatch.index-1;
4139 var preMatch = preRegExp.exec(this.text);
4140 if(preMatch.index != formatMatch.index-1)
4141 this.links.pushUnique(formatMatch[1]);
4142 } else {
4143 this.links.pushUnique(formatMatch[1]);
4146 else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
4147 this.links.pushUnique(formatMatch[3-t]);
4148 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
4149 this.links.pushUnique(formatMatch[4-t]);
4150 tiddlerLinkRegExp.lastIndex = lastIndex;
4151 formatMatch = tiddlerLinkRegExp.exec(this.text);
4153 this.linksUpdated = true;
4156 Tiddler.prototype.getSubtitle = function()
4158 var modifier = this.modifier;
4159 if(!modifier)
4160 modifier = config.messages.subtitleUnknown;
4161 var modified = this.modified;
4162 if(modified)
4163 modified = modified.toLocaleString();
4164 else
4165 modified = config.messages.subtitleUnknown;
4166 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
4169 Tiddler.prototype.isReadOnly = function()
4171 return readOnly;
4174 Tiddler.prototype.autoLinkWikiWords = function()
4176 return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
4179 Tiddler.prototype.generateFingerprint = function()
4181 return "0x" + Crypto.hexSha1Str(this.text);
4184 Tiddler.prototype.getServerType = function()
4186 var serverType = null;
4187 if(this.fields['server.type'])
4188 serverType = this.fields['server.type'];
4189 if(!serverType)
4190 serverType = this.fields['wikiformat'];
4191 if(serverType && !config.adaptors[serverType])
4192 serverType = null;
4193 return serverType;
4196 Tiddler.prototype.getAdaptor = function()
4198 var serverType = this.getServerType();
4199 return serverType ? new config.adaptors[serverType]() : null;
4202 //--
4203 //-- TiddlyWiki() object contains Tiddler()s
4204 //--
4206 function TiddlyWiki()
4208 var tiddlers = {}; // Hashmap by name of tiddlers
4209 this.tiddlersUpdated = false;
4210 this.namedNotifications = []; // Array of {name:,notify:} of notification functions
4211 this.notificationLevel = 0;
4212 this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
4213 this.clear = function() {
4214 tiddlers = {};
4215 this.setDirty(false);
4217 this.fetchTiddler = function(title) {
4218 var t = tiddlers[title];
4219 return t instanceof Tiddler ? t : null;
4221 this.deleteTiddler = function(title) {
4222 delete this.slices[title];
4223 delete tiddlers[title];
4225 this.addTiddler = function(tiddler) {
4226 delete this.slices[tiddler.title];
4227 tiddlers[tiddler.title] = tiddler;
4229 this.forEachTiddler = function(callback) {
4230 for(var t in tiddlers) {
4231 var tiddler = tiddlers[t];
4232 if(tiddler instanceof Tiddler)
4233 callback.call(this,t,tiddler);
4238 TiddlyWiki.prototype.setDirty = function(dirty)
4240 this.dirty = dirty;
4243 TiddlyWiki.prototype.isDirty = function()
4245 return this.dirty;
4248 TiddlyWiki.prototype.tiddlerExists = function(title)
4250 var t = this.fetchTiddler(title);
4251 return t != undefined;
4254 TiddlyWiki.prototype.isShadowTiddler = function(title)
4256 return typeof config.shadowTiddlers[title] == "string";
4259 TiddlyWiki.prototype.createTiddler = function(title)
4261 var tiddler = this.fetchTiddler(title);
4262 if(!tiddler) {
4263 tiddler = new Tiddler(title);
4264 this.addTiddler(tiddler);
4265 this.setDirty(true);
4267 return tiddler;
4270 TiddlyWiki.prototype.getTiddler = function(title)
4272 var t = this.fetchTiddler(title);
4273 if(t != undefined)
4274 return t;
4275 else
4276 return null;
4279 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
4281 if(!title)
4282 return defaultText;
4283 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
4284 var section = null;
4285 if(pos != -1) {
4286 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
4287 title = title.substr(0,pos);
4289 pos = title.indexOf(config.textPrimitives.sliceSeparator);
4290 if(pos != -1) {
4291 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
4292 if(slice)
4293 return slice;
4295 var tiddler = this.fetchTiddler(title);
4296 if(tiddler) {
4297 if(!section)
4298 return tiddler.text;
4299 var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)","mg");
4300 re.lastIndex = 0;
4301 var match = re.exec(tiddler.text);
4302 if(match) {
4303 var t = tiddler.text.substr(match.index+match[1].length);
4304 var re2 = /^!/mg;
4305 re2.lastIndex = 0;
4306 match = re2.exec(t); //# search for the next heading
4307 if(match)
4308 t = t.substr(0,match.index-1);//# don't include final \n
4309 return t;
4311 return defaultText;
4313 if(this.isShadowTiddler(title))
4314 return config.shadowTiddlers[title];
4315 if(defaultText != undefined)
4316 return defaultText;
4317 return null;
4320 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4322 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
4323 var text = this.getTiddlerText(title,null);
4324 if(text == null)
4325 return defaultText;
4326 var textOut = [];
4327 var lastPos = 0;
4328 do {
4329 var match = bracketRegExp.exec(text);
4330 if(match) {
4331 textOut.push(text.substr(lastPos,match.index-lastPos));
4332 if(match[1]) {
4333 if(depth <= 0)
4334 textOut.push(match[1]);
4335 else
4336 textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
4338 lastPos = match.index + match[0].length;
4339 } else {
4340 textOut.push(text.substr(lastPos));
4342 } while(match);
4343 return textOut.join("");
4346 TiddlyWiki.prototype.slicesRE = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1\s*([^\n]+)\s*$)|(?:^\|([\'\/]{0,2})~?([\.\w]+)\:?\4\|\s*([^\|\n]+)\s*\|$)/gm;
4348 // @internal
4349 TiddlyWiki.prototype.calcAllSlices = function(title)
4351 var slices = {};
4352 var text = this.getTiddlerText(title,"");
4353 this.slicesRE.lastIndex = 0;
4354 var m = this.slicesRE.exec(text);
4355 while(m) {
4356 if(m[2])
4357 slices[m[2]] = m[3];
4358 else
4359 slices[m[5]] = m[6];
4360 m = this.slicesRE.exec(text);
4362 return slices;
4365 // Returns the slice of text of the given name
4366 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4368 var slices = this.slices[title];
4369 if(!slices) {
4370 slices = this.calcAllSlices(title);
4371 this.slices[title] = slices;
4373 return slices[sliceName];
4376 // Build an hashmap of the specified named slices of a tiddler
4377 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
4379 var r = {};
4380 for(var t=0; t<sliceNames.length; t++) {
4381 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4382 if(slice)
4383 r[sliceNames[t]] = slice;
4385 return r;
4388 TiddlyWiki.prototype.suspendNotifications = function()
4390 this.notificationLevel--;
4393 TiddlyWiki.prototype.resumeNotifications = function()
4395 this.notificationLevel++;
4398 // Invoke the notification handlers for a particular tiddler
4399 TiddlyWiki.prototype.notify = function(title,doBlanket)
4401 if(!this.notificationLevel) {
4402 for(var t=0; t<this.namedNotifications.length; t++) {
4403 var n = this.namedNotifications[t];
4404 if((n.name == null && doBlanket) || (n.name == title))
4405 n.notify(title);
4410 // Invoke the notification handlers for all tiddlers
4411 TiddlyWiki.prototype.notifyAll = function()
4413 if(!this.notificationLevel) {
4414 for(var t=0; t<this.namedNotifications.length; t++) {
4415 var n = this.namedNotifications[t];
4416 if(n.name)
4417 n.notify(n.name);
4422 // Add a notification handler to a tiddler
4423 TiddlyWiki.prototype.addNotification = function(title,fn)
4425 for(var i=0; i<this.namedNotifications.length; i++) {
4426 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
4427 return this;
4429 this.namedNotifications.push({name: title, notify: fn});
4430 return this;
4433 TiddlyWiki.prototype.removeTiddler = function(title)
4435 var tiddler = this.fetchTiddler(title);
4436 if(tiddler) {
4437 this.deleteTiddler(title);
4438 this.notify(title,true);
4439 this.setDirty(true);
4443 // Reset the sync status of a freshly synced tiddler
4444 TiddlyWiki.prototype.resetTiddler = function(title)
4446 var tiddler = this.fetchTiddler(title);
4447 if(tiddler) {
4448 tiddler.clearChangeCount();
4449 this.notify(title,true);
4450 this.setDirty(true);
4454 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4456 var tiddler = this.fetchTiddler(title);
4457 if(tiddler) {
4458 var t = tiddler.tags.indexOf(tag);
4459 if(t != -1)
4460 tiddler.tags.splice(t,1);
4461 if(status)
4462 tiddler.tags.push(tag);
4463 tiddler.changed();
4464 tiddler.incChangeCount(title);
4465 this.notify(title,true);
4466 this.setDirty(true);
4470 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
4472 var tiddler = this.fetchTiddler(title);
4473 if(!tiddler)
4474 return;
4475 merge(tiddler.fields,fields);
4476 tiddler.changed();
4477 tiddler.incChangeCount(title);
4478 this.notify(title,true);
4479 this.setDirty(true);
4482 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
4484 var tiddler = this.fetchTiddler(title);
4485 if(tiddler) {
4486 created = created || tiddler.created; // Preserve created date
4487 this.deleteTiddler(title);
4488 } else {
4489 created = created || modified;
4490 tiddler = new Tiddler();
4492 tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
4493 this.addTiddler(tiddler);
4494 if(clearChangeCount)
4495 tiddler.clearChangeCount();
4496 else
4497 tiddler.incChangeCount();
4498 if(title != newTitle)
4499 this.notify(title,true);
4500 this.notify(newTitle,true);
4501 this.setDirty(true);
4502 return tiddler;
4505 TiddlyWiki.prototype.incChangeCount = function(title)
4507 var tiddler = this.fetchTiddler(title);
4508 if(tiddler)
4509 tiddler.incChangeCount();
4512 TiddlyWiki.prototype.getLoader = function()
4514 if(!this.loader)
4515 this.loader = new TW21Loader();
4516 return this.loader;
4519 TiddlyWiki.prototype.getSaver = function()
4521 if(!this.saver)
4522 this.saver = new TW21Saver();
4523 return this.saver;
4526 // Return all tiddlers formatted as an HTML string
4527 TiddlyWiki.prototype.allTiddlersAsHtml = function()
4529 return this.getSaver().externalize(store);
4532 // Load contents of a TiddlyWiki from an HTML DIV
4533 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
4535 this.idPrefix = idPrefix;
4536 var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
4537 if(!storeElem)
4538 return;
4539 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4540 this.setDirty(false);
4541 if(!noUpdate) {
4542 for(var i = 0;i<tiddlers.length; i++)
4543 tiddlers[i].changed();
4547 // Load contents of a TiddlyWiki from a string
4548 // Returns null if there's an error
4549 TiddlyWiki.prototype.importTiddlyWiki = function(text)
4551 var posDiv = locateStoreArea(text);
4552 if(!posDiv)
4553 return null;
4554 var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
4555 // Create the iframe
4556 var iframe = document.createElement("iframe");
4557 iframe.style.display = "none";
4558 document.body.appendChild(iframe);
4559 var doc = iframe.document;
4560 if(iframe.contentDocument)
4561 doc = iframe.contentDocument; // For NS6
4562 else if(iframe.contentWindow)
4563 doc = iframe.contentWindow.document; // For IE5.5 and IE6
4564 // Put the content in the iframe
4565 doc.open();
4566 doc.writeln(content);
4567 doc.close();
4568 // Load the content into a TiddlyWiki() object
4569 var storeArea = doc.getElementById("storeArea");
4570 this.loadFromDiv(storeArea,"store");
4571 // Get rid of the iframe
4572 iframe.parentNode.removeChild(iframe);
4573 return this;
4576 TiddlyWiki.prototype.updateTiddlers = function()
4578 this.tiddlersUpdated = true;
4579 this.forEachTiddler(function(title,tiddler) {
4580 tiddler.changed();
4584 // Return an array of tiddlers matching a search regular expression
4585 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
4587 var candidates = this.reverseLookup("tags",excludeTag,!!match);
4588 var results = [];
4589 for(var t=0; t<candidates.length; t++) {
4590 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
4591 results.push(candidates[t]);
4593 if(!sortField)
4594 sortField = "title";
4595 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4596 return results;
4599 // Returns a list of all tags in use
4600 // excludeTag - if present, excludes tags that are themselves tagged with excludeTag
4601 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
4602 TiddlyWiki.prototype.getTags = function(excludeTag)
4604 var results = [];
4605 this.forEachTiddler(function(title,tiddler) {
4606 for(var g=0; g<tiddler.tags.length; g++) {
4607 var tag = tiddler.tags[g];
4608 var n = true;
4609 for(var c=0; c<results.length; c++) {
4610 if(results[c][0] == tag) {
4611 n = false;
4612 results[c][1]++;
4615 if(n && excludeTag) {
4616 var t = this.fetchTiddler(tag);
4617 if(t && t.isTagged(excludeTag))
4618 n = false;
4620 if(n)
4621 results.push([tag,1]);
4624 results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
4625 return results;
4628 // Return an array of the tiddlers that are tagged with a given tag
4629 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
4631 return this.reverseLookup("tags",tag,true,sortField);
4634 // Return an array of the tiddlers that link to a given tiddler
4635 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
4637 if(!this.tiddlersUpdated)
4638 this.updateTiddlers();
4639 return this.reverseLookup("links",title,true,sortField);
4642 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
4643 // lookupMatch == true to match tiddlers, false to exclude tiddlers
4644 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
4646 var results = [];
4647 this.forEachTiddler(function(title,tiddler) {
4648 var f = !lookupMatch;
4649 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
4650 if(tiddler[lookupField][lookup] == lookupValue)
4651 f = lookupMatch;
4653 if(f)
4654 results.push(tiddler);
4656 if(!sortField)
4657 sortField = "title";
4658 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4659 return results;
4662 // Return the tiddlers as a sorted array
4663 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4665 var results = [];
4666 this.forEachTiddler(function(title,tiddler) {
4667 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4668 results.push(tiddler);
4670 if(field)
4671 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
4672 return results;
4675 // Return array of names of tiddlers that are referred to but not defined
4676 TiddlyWiki.prototype.getMissingLinks = function(sortField)
4678 if(!this.tiddlersUpdated)
4679 this.updateTiddlers();
4680 var results = [];
4681 this.forEachTiddler(function (title,tiddler) {
4682 if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
4683 return;
4684 for(var n=0; n<tiddler.links.length;n++) {
4685 var link = tiddler.links[n];
4686 if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
4687 results.pushUnique(link);
4690 results.sort();
4691 return results;
4694 // Return an array of names of tiddlers that are defined but not referred to
4695 TiddlyWiki.prototype.getOrphans = function()
4697 var results = [];
4698 this.forEachTiddler(function (title,tiddler) {
4699 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
4700 results.push(title);
4702 results.sort();
4703 return results;
4706 // Return an array of names of all the shadow tiddlers
4707 TiddlyWiki.prototype.getShadowed = function()
4709 var results = [];
4710 for(var t in config.shadowTiddlers) {
4711 if(typeof config.shadowTiddlers[t] == "string")
4712 results.push(t);
4714 results.sort();
4715 return results;
4718 // Return an array of tiddlers that have been touched since they were downloaded or created
4719 TiddlyWiki.prototype.getTouched = function()
4721 var results = [];
4722 this.forEachTiddler(function(title,tiddler) {
4723 if(tiddler.isTouched())
4724 results.push(tiddler);
4726 results.sort();
4727 return results;
4730 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
4731 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
4733 var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
4734 return t instanceof Tiddler ? t : null;
4737 // Filter a list of tiddlers
4738 TiddlyWiki.prototype.filterTiddlers = function(filter)
4740 var results = [];
4741 if(filter) {
4742 var tiddler;
4743 var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4744 var match = re.exec(filter);
4745 while(match) {
4746 if(match[1] || match[4]) {
4747 var title = match[1] || match[4];
4748 tiddler = this.fetchTiddler(title);
4749 if(tiddler) {
4750 results.pushUnique(tiddler);
4751 } else if(this.isShadowTiddler(title)) {
4752 tiddler = new Tiddler();
4753 tiddler.set(title,this.getTiddlerText(title));
4754 results.pushUnique(tiddler);
4756 } else if(match[2]) {
4757 switch(match[2]) {
4758 case "tag":
4759 var matched = this.getTaggedTiddlers(match[3]);
4760 for(var m = 0; m < matched.length; m++)
4761 results.pushUnique(matched[m]);
4762 break;
4763 case "sort":
4764 results = this.sortTiddlers(results,match[3]);
4765 break;
4768 match = re.exec(filter);
4771 return results;
4774 // Sort a list of tiddlers
4775 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4777 var asc = +1;
4778 switch(field.substr(0,1)) {
4779 case "-":
4780 asc = -1;
4781 // Note: this fall-through is intentional
4782 /*jsl:fallthru*/
4783 case "+":
4784 field = field.substr(1);
4785 break;
4787 if(TiddlyWiki.standardFieldAccess[field])
4788 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4789 else
4790 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
4791 return tiddlers;
4794 // Returns true if path is a valid field name (path),
4795 // i.e. a sequence of identifiers, separated by '.'
4796 TiddlyWiki.isValidFieldName = function(name)
4798 var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
4799 return match && (match[0] == name);
4802 // Throws an exception when name is not a valid field name.
4803 TiddlyWiki.checkFieldName = function(name)
4805 if(!TiddlyWiki.isValidFieldName(name))
4806 throw config.messages.invalidFieldName.format([name]);
4809 function StringFieldAccess(n,readOnly)
4811 this.set = readOnly ?
4812 function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
4813 function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
4814 this.get = function(t) {return t[n];};
4817 function DateFieldAccess(n)
4819 this.set = function(t,v) {
4820 var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
4821 if(d != t[n]) {
4822 t[n] = d; return true;
4825 this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
4828 function LinksFieldAccess(n)
4830 this.set = function(t,v) {
4831 var s = (typeof v == "string") ? v.readBracketedList() : v;
4832 if(s.toString() != t[n].toString()) {
4833 t[n] = s; return true;
4836 this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
4839 TiddlyWiki.standardFieldAccess = {
4840 // The set functions return true when setting the data has changed the value.
4841 "title": new StringFieldAccess("title",true),
4842 // Handle the "tiddler" field name as the title
4843 "tiddler": new StringFieldAccess("title",true),
4844 "text": new StringFieldAccess("text"),
4845 "modifier": new StringFieldAccess("modifier"),
4846 "modified": new DateFieldAccess("modified"),
4847 "created": new DateFieldAccess("created"),
4848 "tags": new LinksFieldAccess("tags")
4851 TiddlyWiki.isStandardField = function(name)
4853 return TiddlyWiki.standardFieldAccess[name] != undefined;
4856 // Sets the value of the given field of the tiddler to the value.
4857 // Setting an ExtendedField's value to null or undefined removes the field.
4858 // Setting a namespace to undefined removes all fields of that namespace.
4859 // The fieldName is case-insensitive.
4860 // All values will be converted to a string value.
4861 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
4863 TiddlyWiki.checkFieldName(fieldName);
4864 var t = this.resolveTiddler(tiddler);
4865 if(!t)
4866 return;
4867 fieldName = fieldName.toLowerCase();
4868 var isRemove = (value === undefined) || (value === null);
4869 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4870 if(accessor) {
4871 if(isRemove)
4872 // don't remove StandardFields
4873 return;
4874 var h = TiddlyWiki.standardFieldAccess[fieldName];
4875 if(!h.set(t,value))
4876 return;
4877 } else {
4878 var oldValue = t.fields[fieldName];
4879 if(isRemove) {
4880 if(oldValue !== undefined) {
4881 // deletes a single field
4882 delete t.fields[fieldName];
4883 } else {
4884 // no concrete value is defined for the fieldName
4885 // so we guess this is a namespace path.
4886 // delete all fields in a namespace
4887 var re = new RegExp('^'+fieldName+'\\.');
4888 var dirty = false;
4889 for(var n in t.fields) {
4890 if(n.match(re)) {
4891 delete t.fields[n];
4892 dirty = true;
4895 if(!dirty)
4896 return;
4898 } else {
4899 // the "normal" set case. value is defined (not null/undefined)
4900 // For convenience provide a nicer conversion Date->String
4901 value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
4902 if(oldValue == value)
4903 return;
4904 t.fields[fieldName] = value;
4907 // When we are here the tiddler/store really was changed.
4908 this.notify(t.title,true);
4909 if(!fieldName.match(/^temp\./))
4910 this.setDirty(true);
4913 // Returns the value of the given field of the tiddler.
4914 // The fieldName is case-insensitive.
4915 // Will only return String values (or undefined).
4916 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
4918 var t = this.resolveTiddler(tiddler);
4919 if(!t)
4920 return undefined;
4921 fieldName = fieldName.toLowerCase();
4922 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4923 if(accessor) {
4924 return accessor.get(t);
4926 return t.fields[fieldName];
4929 // Calls the callback function for every field in the tiddler.
4930 // When callback function returns a non-false value the iteration stops
4931 // and that value is returned.
4932 // The order of the fields is not defined.
4933 // @param callback a function(tiddler,fieldName,value).
4934 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
4936 var t = this.resolveTiddler(tiddler);
4937 if(!t)
4938 return undefined;
4939 var n,result;
4940 for(n in t.fields) {
4941 result = callback(t,n,t.fields[n]);
4942 if(result)
4943 return result;
4945 if(onlyExtendedFields)
4946 return undefined;
4947 for(n in TiddlyWiki.standardFieldAccess) {
4948 if(n == "tiddler")
4949 // even though the "title" field can also be referenced through the name "tiddler"
4950 // we only visit this field once.
4951 continue;
4952 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4953 if(result)
4954 return result;
4956 return undefined;
4959 //--
4960 //-- Story functions
4961 //--
4963 function Story(containerId,idPrefix)
4965 this.container = containerId;
4966 this.idPrefix = idPrefix;
4967 this.highlightRegExp = null;
4968 this.tiddlerId = function(title) {
4969 var id = this.idPrefix + title;
4970 return id==this.container ? this.idPrefix + "_" + title : id;
4972 this.containerId = function() {
4973 return this.container;
4977 Story.prototype.getTiddler = function(title)
4979 return document.getElementById(this.tiddlerId(title));
4982 Story.prototype.getContainer = function()
4984 return document.getElementById(this.containerId());
4987 Story.prototype.forEachTiddler = function(fn)
4989 var place = this.getContainer();
4990 if(!place)
4991 return;
4992 var e = place.firstChild;
4993 while(e) {
4994 var n = e.nextSibling;
4995 var title = e.getAttribute("tiddler");
4996 fn.call(this,title,e);
4997 e = n;
5001 Story.prototype.displayDefaultTiddlers = function()
5003 this.displayTiddlers(null,store.filterTiddlers(store.getTiddlerText("DefaultTiddlers")));
5006 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
5008 for(var t = titles.length-1;t>=0;t--)
5009 this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
5012 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc)
5014 var title = (tiddler instanceof Tiddler) ? tiddler.title : tiddler;
5015 var tiddlerElem = this.getTiddler(title);
5016 if(tiddlerElem) {
5017 if(toggle)
5018 this.closeTiddler(title,true);
5019 else
5020 this.refreshTiddler(title,template,false,customFields);
5021 } else {
5022 var place = this.getContainer();
5023 var before = this.positionTiddler(srcElement);
5024 tiddlerElem = this.createTiddler(place,before,title,template,customFields);
5026 if(animationSrc && typeof animationSrc !== "string") {
5027 srcElement = animationSrc;
5029 if(srcElement && typeof srcElement !== "string") {
5030 if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
5031 anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
5032 else
5033 window.scrollTo(0,ensureVisible(tiddlerElem));
5037 Story.prototype.positionTiddler = function(srcElement)
5039 var place = this.getContainer();
5040 var before = null;
5041 if(typeof srcElement == "string") {
5042 switch(srcElement) {
5043 case "top":
5044 before = place.firstChild;
5045 break;
5046 case "bottom":
5047 before = null;
5048 break;
5050 } else {
5051 var after = this.findContainingTiddler(srcElement);
5052 if(after == null) {
5053 before = place.firstChild;
5054 } else if(after.nextSibling) {
5055 before = after.nextSibling;
5056 if(before.nodeType != 1)
5057 before = null;
5060 return before;
5063 Story.prototype.createTiddler = function(place,before,title,template,customFields)
5065 var tiddlerElem = createTiddlyElement(null,"div",this.tiddlerId(title),"tiddler");
5066 tiddlerElem.setAttribute("refresh","tiddler");
5067 if(customFields)
5068 tiddlerElem.setAttribute("tiddlyFields",customFields);
5069 place.insertBefore(tiddlerElem,before);
5070 var defaultText = null;
5071 if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
5072 defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
5073 this.refreshTiddler(title,template,false,customFields,defaultText);
5074 return tiddlerElem;
5077 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
5079 var tiddler = new Tiddler(title);
5080 tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields || {});
5081 var serverType = tiddler.getServerType();
5082 var host = tiddler.fields['server.host'];
5083 var workspace = tiddler.fields['server.workspace'];
5084 if(!serverType || !host)
5085 return null;
5086 var sm = new SyncMachine(serverType,{
5087 start: function() {
5088 return this.openHost(host,"openWorkspace");
5090 openWorkspace: function() {
5091 return this.openWorkspace(workspace,"getTiddler");
5093 getTiddler: function() {
5094 return this.getTiddler(title,"onGetTiddler");
5096 onGetTiddler: function(context) {
5097 var tiddler = context.tiddler;
5098 if(tiddler && tiddler.text) {
5099 var downloaded = new Date();
5100 if(!tiddler.created)
5101 tiddler.created = downloaded;
5102 if(!tiddler.modified)
5103 tiddler.modified = tiddler.created;
5104 store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
5105 autoSaveChanges();
5107 delete this;
5108 return true;
5110 error: function(message) {
5111 displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
5114 sm.go();
5115 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
5118 Story.prototype.chooseTemplateForTiddler = function(title,template)
5120 if(!template)
5121 template = DEFAULT_VIEW_TEMPLATE;
5122 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
5123 template = config.tiddlerTemplates[template];
5124 return template;
5127 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
5129 return store.getRecursiveTiddlerText(template,null,10);
5132 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
5134 var tiddlerElem = this.getTiddler(title);
5135 if(tiddlerElem) {
5136 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
5137 return tiddlerElem;
5138 template = this.chooseTemplateForTiddler(title,template);
5139 var currTemplate = tiddlerElem.getAttribute("template");
5140 if((template != currTemplate) || force) {
5141 var tiddler = store.getTiddler(title);
5142 if(!tiddler) {
5143 tiddler = new Tiddler();
5144 if(store.isShadowTiddler(title)) {
5145 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
5146 } else {
5147 var text = template=="EditTemplate" ?
5148 config.views.editor.defaultText.format([title]) :
5149 config.views.wikified.defaultText.format([title]);
5150 text = defaultText || text;
5151 var fields = customFields ? customFields.decodeHashMap() : null;
5152 tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
5155 tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
5156 tiddlerElem.setAttribute("tiddler",title);
5157 tiddlerElem.setAttribute("template",template);
5158 tiddlerElem.onmouseover = this.onTiddlerMouseOver;
5159 tiddlerElem.onmouseout = this.onTiddlerMouseOut;
5160 tiddlerElem.ondblclick = this.onTiddlerDblClick;
5161 tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
5162 tiddlerElem.innerHTML = this.getTemplateForTiddler(title,template,tiddler);
5163 applyHtmlMacros(tiddlerElem,tiddler);
5164 if(store.getTaggedTiddlers(title).length > 0)
5165 addClass(tiddlerElem,"isTag");
5166 else
5167 removeClass(tiddlerElem,"isTag");
5168 if(store.tiddlerExists(title)) {
5169 removeClass(tiddlerElem,"shadow");
5170 removeClass(tiddlerElem,"missing");
5171 } else {
5172 addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow" : "missing");
5174 if(customFields)
5175 this.addCustomFields(tiddlerElem,customFields);
5176 forceReflow();
5179 return tiddlerElem;
5182 Story.prototype.addCustomFields = function(place,customFields)
5184 var fields = customFields.decodeHashMap();
5185 var w = document.createElement("div");
5186 w.style.display = "none";
5187 place.appendChild(w);
5188 for(var t in fields) {
5189 var e = document.createElement("input");
5190 e.setAttribute("type","text");
5191 e.setAttribute("value",fields[t]);
5192 w.appendChild(e);
5193 e.setAttribute("edit",t);
5197 Story.prototype.refreshAllTiddlers = function(force)
5199 var e = this.getContainer().firstChild;
5200 while(e) {
5201 var template = e.getAttribute("template");
5202 if(template && e.getAttribute("dirty") != "true") {
5203 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : template,true);
5205 e = e.nextSibling;
5209 Story.prototype.onTiddlerMouseOver = function(e)
5211 if(window.addClass instanceof Function)
5212 addClass(this,"selected");
5215 Story.prototype.onTiddlerMouseOut = function(e)
5217 if(window.removeClass instanceof Function)
5218 removeClass(this,"selected");
5221 Story.prototype.onTiddlerDblClick = function(ev)
5223 var e = ev || window.event;
5224 var target = resolveTarget(e);
5225 if(target && target.nodeName.toLowerCase() != "input" && target.nodeName.toLowerCase() != "textarea") {
5226 if(document.selection && document.selection.empty)
5227 document.selection.empty();
5228 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5229 e.cancelBubble = true;
5230 if(e.stopPropagation) e.stopPropagation();
5231 return true;
5233 return false;
5236 Story.prototype.onTiddlerKeyPress = function(ev)
5238 var e = ev || window.event;
5239 clearMessage();
5240 var consume = false;
5241 var title = this.getAttribute("tiddler");
5242 var target = resolveTarget(e);
5243 switch(e.keyCode) {
5244 case 9: // Tab
5245 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
5246 replaceSelection(target,String.fromCharCode(9));
5247 consume = true;
5249 if(config.isOpera) {
5250 target.onblur = function() {
5251 this.focus();
5252 this.onblur = null;
5255 break;
5256 case 13: // Ctrl-Enter
5257 case 10: // Ctrl-Enter on IE PC
5258 case 77: // Ctrl-Enter is "M" on some platforms
5259 if(e.ctrlKey) {
5260 blurElement(this);
5261 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5262 consume = true;
5264 break;
5265 case 27: // Escape
5266 blurElement(this);
5267 config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
5268 consume = true;
5269 break;
5271 e.cancelBubble = consume;
5272 if(consume) {
5273 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
5274 e.returnValue = true; // Cancel The Event in IE
5275 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
5277 return !consume;
5280 Story.prototype.getTiddlerField = function(title,field)
5282 var tiddlerElem = this.getTiddler(title);
5283 var e = null;
5284 if(tiddlerElem ) {
5285 var children = tiddlerElem.getElementsByTagName("*");
5286 for(var t=0; t<children.length; t++) {
5287 var c = children[t];
5288 if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
5289 if(!e)
5290 e = c;
5291 if(c.getAttribute("edit") == field)
5292 e = c;
5296 return e;
5299 Story.prototype.focusTiddler = function(title,field)
5301 var e = this.getTiddlerField(title,field);
5302 if(e) {
5303 e.focus();
5304 e.select();
5308 Story.prototype.blurTiddler = function(title)
5310 var tiddlerElem = this.getTiddler(title);
5311 if(tiddlerElem && tiddlerElem.focus && tiddlerElem.blur) {
5312 tiddlerElem.focus();
5313 tiddlerElem.blur();
5317 Story.prototype.setTiddlerField = function(title,tag,mode,field)
5319 var c = this.getTiddlerField(title,field);
5320 var tags = c.value.readBracketedList();
5321 tags.setItem(tag,mode);
5322 c.value = String.encodeTiddlyLinkList(tags);
5325 Story.prototype.setTiddlerTag = function(title,tag,mode)
5327 this.setTiddlerField(title,tag,mode,"tags");
5330 Story.prototype.closeTiddler = function(title,animate,unused)
5332 var tiddlerElem = this.getTiddler(title);
5333 if(tiddlerElem) {
5334 clearMessage();
5335 this.scrubTiddler(tiddlerElem);
5336 if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
5337 anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
5338 else {
5339 removeNode(tiddlerElem);
5340 forceReflow();
5345 Story.prototype.scrubTiddler = function(tiddlerElem)
5347 tiddlerElem.id = null;
5350 Story.prototype.setDirty = function(title,dirty)
5352 var tiddlerElem = this.getTiddler(title);
5353 if(tiddlerElem)
5354 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
5357 Story.prototype.isDirty = function(title)
5359 var tiddlerElem = this.getTiddler(title);
5360 if(tiddlerElem)
5361 return tiddlerElem.getAttribute("dirty") == "true";
5362 return null;
5365 Story.prototype.areAnyDirty = function()
5367 var r = false;
5368 this.forEachTiddler(function(title,element) {
5369 if(this.isDirty(title))
5370 r = true;
5372 return r;
5375 Story.prototype.closeAllTiddlers = function(exclude)
5377 clearMessage();
5378 this.forEachTiddler(function(title,element) {
5379 if((title != exclude) && element.getAttribute("dirty") != "true")
5380 this.closeTiddler(title);
5382 window.scrollTo(0,ensureVisible(this.container));
5385 Story.prototype.isEmpty = function()
5387 var place = this.getContainer();
5388 return place && place.firstChild == null;
5391 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
5393 this.closeAllTiddlers();
5394 highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
5395 var matches = store.search(highlightHack,"title","excludeSearch");
5396 this.displayTiddlers(null,matches);
5397 highlightHack = null;
5398 var q = useRegExp ? "/" : "'";
5399 if(matches.length > 0)
5400 displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
5401 else
5402 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5405 Story.prototype.findContainingTiddler = function(e)
5407 while(e && !hasClass(e,"tiddler"))
5408 e = e.parentNode;
5409 return e;
5412 Story.prototype.gatherSaveFields = function(e,fields)
5414 if(e && e.getAttribute) {
5415 var f = e.getAttribute("edit");
5416 if(f)
5417 fields[f] = e.value.replace(/\r/mg,"");
5418 if(e.hasChildNodes()) {
5419 var c = e.childNodes;
5420 for(var t=0; t<c.length; t++)
5421 this.gatherSaveFields(c[t],fields);
5426 Story.prototype.hasChanges = function(title)
5428 var e = this.getTiddler(title);
5429 if(e) {
5430 var fields = {};
5431 this.gatherSaveFields(e,fields);
5432 var tiddler = store.fetchTiddler(title);
5433 if(!tiddler)
5434 return false;
5435 for(var n in fields) {
5436 if(store.getValue(title,n) != fields[n])
5437 return true;
5440 return false;
5443 Story.prototype.saveTiddler = function(title,minorUpdate)
5445 var tiddlerElem = this.getTiddler(title);
5446 if(tiddlerElem) {
5447 var fields = {};
5448 this.gatherSaveFields(tiddlerElem,fields);
5449 var newTitle = fields.title || title;
5450 if(!store.tiddlerExists(newTitle))
5451 newTitle = newTitle.trim();
5452 if(store.tiddlerExists(newTitle) && newTitle != title) {
5453 if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
5454 return null;
5456 if(newTitle != title)
5457 this.closeTiddler(newTitle,false);
5458 tiddlerElem.id = this.tiddlerId(newTitle);
5459 tiddlerElem.setAttribute("tiddler",newTitle);
5460 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
5461 tiddlerElem.setAttribute("dirty","false");
5462 if(config.options.chkForceMinorUpdate)
5463 minorUpdate = !minorUpdate;
5464 if(!store.tiddlerExists(newTitle))
5465 minorUpdate = false;
5466 var newDate = new Date();
5467 var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : config.defaultCustomFields);
5468 for(var n in fields) {
5469 if(!TiddlyWiki.isStandardField(n))
5470 extendedFields[n] = fields[n];
5472 var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
5473 autoSaveChanges(null,[tiddler]);
5474 return newTitle;
5476 return null;
5479 Story.prototype.permaView = function()
5481 var links = [];
5482 this.forEachTiddler(function(title,element) {
5483 links.push(String.encodeTiddlyLink(title));
5485 var t = encodeURIComponent(links.join(" "));
5486 if(t == "")
5487 t = "#";
5488 if(window.location.hash != t)
5489 window.location.hash = t;
5492 Story.prototype.switchTheme = function(theme)
5494 if(safeMode)
5495 return;
5497 var isAvailable = function(title) {
5498 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5499 if(s!=-1)
5500 title = title.substr(0,s);
5501 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5504 var getSlice = function(theme,slice) {
5505 var r;
5506 if(readOnly)
5507 r = store.getTiddlerSlice(theme,slice+"ReadOnly") || store.getTiddlerSlice(theme,"Web"+slice);
5508 r = r || store.getTiddlerSlice(theme,slice);
5509 if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
5510 r = theme + r;
5511 return isAvailable(r) ? r : slice;
5514 var replaceNotification = function(i,name,theme,slice) {
5515 var newName = getSlice(theme,slice);
5516 if(name!=newName && store.namedNotifications[i].name==name) {
5517 store.namedNotifications[i].name = newName;
5518 return newName;
5520 return name;
5523 var pt = config.refresherData.pageTemplate;
5524 var vi = DEFAULT_VIEW_TEMPLATE;
5525 var vt = config.tiddlerTemplates[vi];
5526 var ei = DEFAULT_EDIT_TEMPLATE;
5527 var et = config.tiddlerTemplates[ei];
5529 for(var i=0; i<config.notifyTiddlers.length; i++) {
5530 var name = config.notifyTiddlers[i].name;
5531 switch(name) {
5532 case "PageTemplate":
5533 config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
5534 break;
5535 case "StyleSheet":
5536 removeStyleSheet(config.refresherData.styleSheet);
5537 config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
5538 break;
5539 case "ColorPalette":
5540 config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
5541 break;
5542 default:
5543 break;
5546 config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate");
5547 config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate");
5548 if(!startingUp) {
5549 if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
5550 refreshAll();
5551 this.refreshAllTiddlers(true);
5552 } else {
5553 setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet,"",10),config.refreshers.styleSheet);
5555 config.options.txtTheme = theme;
5556 saveOptionCookie("txtTheme");
5560 //--
5561 //-- Backstage
5562 //--
5564 var backstage = {
5565 area: null,
5566 toolbar: null,
5567 button: null,
5568 showButton: null,
5569 hideButton: null,
5570 cloak: null,
5571 panel: null,
5572 panelBody: null,
5573 panelFooter: null,
5574 currTabName: null,
5575 currTabElem: null,
5576 content: null,
5578 init: function() {
5579 var cmb = config.messages.backstage;
5580 this.area = document.getElementById("backstageArea");
5581 this.toolbar = document.getElementById("backstageToolbar");
5582 this.button = document.getElementById("backstageButton");
5583 this.button.style.display = "block";
5584 var t = cmb.open.text + " " + glyph("bentArrowLeft");
5585 this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
5586 function(e) {backstage.show(); return false;},null,"backstageShow");
5587 t = glyph("bentArrowRight") + " " + cmb.close.text;
5588 this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
5589 function(e) {backstage.hide(); return false;},null,"backstageHide");
5590 this.cloak = document.getElementById("backstageCloak");
5591 this.panel = document.getElementById("backstagePanel");
5592 this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
5593 this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
5594 this.cloak.onmousedown = function(e) {backstage.switchTab(null);};
5595 createTiddlyText(this.toolbar,cmb.prompt);
5596 for(t=0; t<config.backstageTasks.length; t++) {
5597 var taskName = config.backstageTasks[t];
5598 var task = config.tasks[taskName];
5599 var handler = task.action ? this.onClickCommand : this.onClickTab;
5600 var text = task.text + (task.action ? "" : glyph("downTriangle"));
5601 var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
5602 btn.setAttribute("task",taskName);
5603 addClass(btn,task.action ? "backstageAction" : "backstageTask");
5605 this.content = document.getElementById("contentWrapper");
5606 if(config.options.chkBackstage)
5607 this.show();
5608 else
5609 this.hide();
5612 isVisible: function() {
5613 return this.area ? this.area.style.display == "block" : false;
5616 show: function() {
5617 this.area.style.display = "block";
5618 if(anim && config.options.chkAnimate) {
5619 backstage.toolbar.style.left = findWindowWidth() + "px";
5620 var p = [{style: "left", start: findWindowWidth(), end: 0, template: "%0px"}];
5621 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
5622 } else {
5623 backstage.area.style.left = "0px";
5625 this.showButton.style.display = "none";
5626 this.hideButton.style.display = "block";
5627 config.options.chkBackstage = true;
5628 saveOptionCookie("chkBackstage");
5629 addClass(this.content,"backstageVisible");
5632 hide: function() {
5633 if(this.currTabElem) {
5634 this.switchTab(null);
5635 } else {
5636 backstage.toolbar.style.left = "0px";
5637 if(anim && config.options.chkAnimate) {
5638 var p = [{style: "left", start: 0, end: findWindowWidth(), template: "%0px"}];
5639 var c = function(element,properties) {backstage.area.style.display = "none";};
5640 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
5641 } else {
5642 this.area.style.display = "none";
5644 this.showButton.style.display = "block";
5645 this.hideButton.style.display = "none";
5646 config.options.chkBackstage = false;
5647 saveOptionCookie("chkBackstage");
5648 removeClass(this.content,"backstageVisible");
5652 onClickCommand: function(e) {
5653 var task = config.tasks[this.getAttribute("task")];
5654 displayMessage(task);
5655 if(task.action) {
5656 backstage.switchTab(null);
5657 task.action();
5659 return false;
5662 onClickTab: function(e) {
5663 backstage.switchTab(this.getAttribute("task"));
5664 return false;
5667 // Switch to a given tab, or none if null is passed
5668 switchTab: function(tabName) {
5669 var tabElem = null;
5670 var e = this.toolbar.firstChild;
5671 while(e)
5673 if(e.getAttribute && e.getAttribute("task") == tabName)
5674 tabElem = e;
5675 e = e.nextSibling;
5677 if(tabName == backstage.currTabName)
5678 return;
5679 if(backstage.currTabElem) {
5680 removeClass(this.currTabElem,"backstageSelTab");
5682 if(tabElem && tabName) {
5683 backstage.preparePanel();
5684 addClass(tabElem,"backstageSelTab");
5685 var task = config.tasks[tabName];
5686 wikify(task.content,backstage.panelBody,null,null);
5687 backstage.showPanel();
5688 } else if(backstage.currTabElem) {
5689 backstage.hidePanel();
5691 backstage.currTabName = tabName;
5692 backstage.currTabElem = tabElem;
5695 isPanelVisible: function() {
5696 return backstage.panel ? backstage.panel.style.display == "block" : false;
5699 preparePanel: function() {
5700 backstage.cloak.style.height = findWindowHeight() + "px";
5701 backstage.cloak.style.display = "block";
5702 removeChildren(backstage.panelBody);
5703 return backstage.panelBody;
5706 showPanel: function() {
5707 backstage.panel.style.display = "block";
5708 if(anim && config.options.chkAnimate) {
5709 backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
5710 var p = [{style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}];
5711 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
5712 } else {
5713 backstage.panel.style.top = "0px";
5715 return backstage.panelBody;
5718 hidePanel: function() {
5719 if(backstage.currTabElem)
5720 removeClass(backstage.currTabElem,"backstageSelTab");
5721 backstage.currTabElem = null;
5722 backstage.currTabName = null;
5723 if(anim && config.options.chkAnimate) {
5724 var p = [
5725 {style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
5726 {style: "display", atEnd: "none"}
5728 var c = function(element,properties) {backstage.cloak.style.display = "none";};
5729 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
5730 } else {
5731 backstage.panel.style.display = "none";
5732 backstage.cloak.style.display = "none";
5737 config.macros.backstage = {};
5739 config.macros.backstage.handler = function(place,macroName,params)
5741 var backstageTask = config.tasks[params[0]];
5742 if(backstageTask)
5743 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5746 //--
5747 //-- ImportTiddlers macro
5748 //--
5750 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5752 if(readOnly) {
5753 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
5754 return;
5756 var w = new Wizard();
5757 w.createWizard(place,this.wizardTitle);
5758 this.restart(w);
5761 config.macros.importTiddlers.onCancel = function(e)
5763 var wizard = new Wizard(this);
5764 var place = wizard.clear();
5765 config.macros.importTiddlers.restart(wizard);
5766 return false;
5769 config.macros.importTiddlers.onClose = function(e)
5771 backstage.hidePanel();
5772 return false;
5775 config.macros.importTiddlers.restart = function(wizard)
5777 wizard.addStep(this.step1Title,this.step1Html);
5778 var s = wizard.getElement("selTypes");
5779 for(var t in config.adaptors) {
5780 var e = createTiddlyElement(s,"option",null,null,config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
5781 e.value = t;
5783 if(config.defaultAdaptor)
5784 s.value = config.defaultAdaptor;
5785 s = wizard.getElement("selFeeds");
5786 var feeds = this.getFeeds();
5787 for(t in feeds) {
5788 e = createTiddlyElement(s,"option",null,null,t);
5789 e.value = t;
5791 wizard.setValue("feeds",feeds);
5792 s.onchange = config.macros.importTiddlers.onFeedChange;
5793 var fileInput = wizard.getElement("txtBrowse");
5794 fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
5795 fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
5796 wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
5797 wizard.formElem.action = "javascript:;";
5798 wizard.formElem.onsubmit = function() {
5799 if(this.txtPath.value.length)
5800 this.lastChild.firstChild.onclick();
5804 config.macros.importTiddlers.getFeeds = function()
5806 var feeds = {};
5807 var tagged = store.getTaggedTiddlers("systemServer","title");
5808 for(var t=0; t<tagged.length; t++) {
5809 var title = tagged[t].title;
5810 var serverType = store.getTiddlerSlice(title,"Type");
5811 if(!serverType)
5812 serverType = "file";
5813 feeds[title] = {title: title,
5814 url: store.getTiddlerSlice(title,"URL"),
5815 workspace: store.getTiddlerSlice(title,"Workspace"),
5816 workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
5817 tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
5818 serverType: serverType,
5819 description: store.getTiddlerSlice(title,"Description")};
5821 return feeds;
5824 config.macros.importTiddlers.onFeedChange = function(e)
5826 var wizard = new Wizard(this);
5827 var selTypes = wizard.getElement("selTypes");
5828 var fileInput = wizard.getElement("txtPath");
5829 var feeds = wizard.getValue("feeds");
5830 var f = feeds[this.value];
5831 if(f) {
5832 selTypes.value = f.serverType;
5833 fileInput.value = f.url;
5834 wizard.setValue("feedName",f.serverType);
5835 wizard.setValue("feedHost",f.url);
5836 wizard.setValue("feedWorkspace",f.workspace);
5837 wizard.setValue("feedWorkspaceList",f.workspaceList);
5838 wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
5840 return false;
5843 config.macros.importTiddlers.onBrowseChange = function(e)
5845 var wizard = new Wizard(this);
5846 var fileInput = wizard.getElement("txtPath");
5847 fileInput.value = config.macros.importTiddlers.getURLFromLocalPath(this.value);
5848 var serverType = wizard.getElement("selTypes");
5849 serverType.value = "file";
5850 return true;
5853 config.macros.importTiddlers.getURLFromLocalPath = function(v)
5855 if(!v||!v.length)
5856 return v;
5857 v = v.replace(/\\/g,"/"); // use "/" for cross-platform consistency
5858 var u;
5859 var t = v.split(":");
5860 var p = t[1]||t[0]; // remove drive letter (if any)
5861 if (t[1] && (t[0]=="http"||t[0]=="https"||t[0]=="file")) {
5862 u = v;
5863 } else if(p.substr(0,1)=="/") {
5864 u = document.location.protocol + "//" + document.location.hostname + (t[1] ? "/" : "") + v;
5865 } else {
5866 var c = document.location.href.replace(/\\/g,"/");
5867 var pos = c.lastIndexOf("/");
5868 if (pos!=-1)
5869 c = c.substr(0,pos); // remove filename
5870 u = c + "/" + p;
5872 return u;
5875 config.macros.importTiddlers.onOpen = function(e)
5877 var wizard = new Wizard(this);
5878 var fileInput = wizard.getElement("txtPath");
5879 var url = fileInput.value;
5880 var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
5881 var adaptor = new config.adaptors[serverType]();
5882 wizard.setValue("adaptor",adaptor);
5883 wizard.setValue("serverType",serverType);
5884 wizard.setValue("host",url);
5885 var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
5886 if(ret !== true)
5887 displayMessage(ret);
5888 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
5889 return false;
5892 config.macros.importTiddlers.onOpenHost = function(context,wizard)
5894 var adaptor = wizard.getValue("adaptor");
5895 if(context.status !== true)
5896 displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
5897 var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
5898 if(ret !== true)
5899 displayMessage(ret);
5900 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
5903 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
5905 if(context.status !== true)
5906 displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
5907 wizard.setValue("context",context);
5908 var workspace = wizard.getValue("feedWorkspace");
5909 if(!workspace && context.workspaces.length==1)
5910 workspace = context.workspaces[0].title;
5911 if(workspace) {
5912 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5913 if(ret !== true)
5914 displayMessage(ret);
5915 wizard.setValue("workspace",workspace);
5916 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5917 return;
5919 wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
5920 var s = wizard.getElement("selWorkspace");
5921 s.onchange = config.macros.importTiddlers.onWorkspaceChange;
5922 for(var t=0; t<context.workspaces.length; t++) {
5923 var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
5924 e.value = context.workspaces[t].title;
5926 var workspaceList = wizard.getValue("feedWorkspaceList");
5927 if(workspaceList) {
5928 var list = workspaceList.parseParams("workspace",null,false,true);
5929 for(var n=1; n<list.length; n++) {
5930 if(context.workspaces.findByField("title",list[n].value) == null) {
5931 e = createTiddlyElement(s,"option",null,null,list[n].value);
5932 e.value = list[n].value;
5936 if(workspace) {
5937 t = wizard.getElement("txtWorkspace");
5938 t.value = workspace;
5940 wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
5943 config.macros.importTiddlers.onWorkspaceChange = function(e)
5945 var wizard = new Wizard(this);
5946 var t = wizard.getElement("txtWorkspace");
5947 t.value = this.value;
5948 this.selectedIndex = 0;
5949 return false;
5952 config.macros.importTiddlers.onChooseWorkspace = function(e)
5954 var wizard = new Wizard(this);
5955 var adaptor = wizard.getValue("adaptor");
5956 var workspace = wizard.getElement("txtWorkspace").value;
5957 wizard.setValue("workspace",workspace);
5958 var context = wizard.getValue("context");
5959 var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5960 if(ret !== true)
5961 displayMessage(ret);
5962 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5963 return false;
5966 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
5968 if(context.status !== true)
5969 displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
5970 var adaptor = wizard.getValue("adaptor");
5971 var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
5972 if(ret !== true)
5973 displayMessage(ret);
5974 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
5977 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
5979 if(context.status !== true) {
5980 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.errorGettingTiddlerList);
5981 return;
5983 // Extract data for the listview
5984 var listedTiddlers = [];
5985 if(context.tiddlers) {
5986 for(var n=0; n<context.tiddlers.length; n++) {
5987 var tiddler = context.tiddlers[n];
5988 listedTiddlers.push({
5989 title: tiddler.title,
5990 modified: tiddler.modified,
5991 modifier: tiddler.modifier,
5992 text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
5993 tags: tiddler.tags,
5994 size: tiddler.text ? tiddler.text.length : 0,
5995 tiddler: tiddler
5999 listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
6000 // Display the listview
6001 wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
6002 var markList = wizard.getElement("markList");
6003 var listWrapper = document.createElement("div");
6004 markList.parentNode.insertBefore(listWrapper,markList);
6005 var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
6006 wizard.setValue("listView",listView);
6007 var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
6008 txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
6009 wizard.setButtons([
6010 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
6011 {caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
6015 config.macros.importTiddlers.generateSystemServerName = function(wizard)
6017 var serverType = wizard.getValue("serverType");
6018 var host = wizard.getValue("host");
6019 var workspace = wizard.getValue("workspace");
6020 var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
6021 return pattern.format([serverType,host,workspace]);
6024 config.macros.importTiddlers.saveServerTiddler = function(wizard)
6026 var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
6027 if(store.tiddlerExists(txtSaveTiddler)) {
6028 if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
6029 return;
6030 store.suspendNotifications();
6031 store.removeTiddler(txtSaveTiddler);
6032 store.resumeNotifications();
6034 var serverType = wizard.getValue("serverType");
6035 var host = wizard.getValue("host");
6036 var workspace = wizard.getValue("workspace");
6037 var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
6038 store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
6041 config.macros.importTiddlers.doImport = function(e)
6043 var wizard = new Wizard(this);
6044 if(wizard.getElement("chkSave").checked)
6045 config.macros.importTiddlers.saveServerTiddler(wizard);
6046 var chkSync = wizard.getElement("chkSync").checked;
6047 wizard.setValue("sync",chkSync);
6048 var listView = wizard.getValue("listView");
6049 var rowNames = ListView.getSelectedRows(listView);
6050 var adaptor = wizard.getValue("adaptor");
6051 var overwrite = [];
6052 var t;
6053 for(t=0; t<rowNames.length; t++) {
6054 if(store.tiddlerExists(rowNames[t]))
6055 overwrite.push(rowNames[t]);
6057 if(overwrite.length > 0) {
6058 if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
6059 return false;
6061 wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
6062 for(t=0; t<rowNames.length; t++) {
6063 var link = document.createElement("div");
6064 createTiddlyLink(link,rowNames[t],true);
6065 var place = wizard.getElement("markReport");
6066 place.parentNode.insertBefore(link,place);
6068 wizard.setValue("remainingImports",rowNames.length);
6069 wizard.setButtons([
6070 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
6071 ],config.macros.importTiddlers.statusDoingImport);
6072 for(t=0; t<rowNames.length; t++) {
6073 var context = {};
6074 context.allowSynchronous = true;
6075 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
6077 return false;
6080 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
6082 if(!context.status)
6083 displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
6084 var tiddler = context.tiddler;
6085 store.suspendNotifications();
6086 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
6087 if(!wizard.getValue("sync")) {
6088 store.setValue(tiddler.title,'server',null);
6090 store.resumeNotifications();
6091 if(!context.isSynchronous)
6092 store.notify(tiddler.title,true);
6093 var remainingImports = wizard.getValue("remainingImports")-1;
6094 wizard.setValue("remainingImports",remainingImports);
6095 if(remainingImports == 0) {
6096 if(context.isSynchronous) {
6097 store.notifyAll();
6098 refreshDisplay();
6100 wizard.setButtons([
6101 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
6102 ],config.macros.importTiddlers.statusDoneImport);
6103 autoSaveChanges();
6107 //--
6108 //-- Upgrade macro
6109 //--
6111 config.macros.upgrade.handler = function(place)
6113 var w = new Wizard();
6114 w.createWizard(place,this.wizardTitle);
6115 w.addStep(this.step1Title,this.step1Html.format([this.source,this.source]));
6116 w.setButtons([{caption: this.upgradeLabel, tooltip: this.upgradePrompt, onClick: this.onClickUpgrade}]);
6119 config.macros.upgrade.onClickUpgrade = function(e)
6121 var me = config.macros.upgrade;
6122 var w = new Wizard(this);
6123 if(window.location.protocol != "file:") {
6124 alert(me.errorCantUpgrade);
6125 return false;
6127 if(story.areAnyDirty() || store.isDirty()) {
6128 alert(me.errorNotSaved);
6129 return false;
6131 var localPath = getLocalPath(document.location.toString());
6132 var backupPath = getBackupPath(localPath,me.backupExtension);
6133 w.setValue("backupPath",backupPath);
6134 w.setButtons([],me.statusPreparingBackup);
6135 var original = loadOriginal(localPath);
6136 w.setButtons([],me.statusSavingBackup);
6137 var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
6138 if(backup != true) {
6139 w.setButtons([],me.errorSavingBackup);
6140 alert(me.errorSavingBackup);
6141 return false;
6143 w.setButtons([],me.statusLoadingCore);
6144 var load = loadRemoteFile(me.source,me.onLoadCore,w);
6145 if(typeof load == "string") {
6146 w.setButtons([],me.errorLoadingCore);
6147 alert(me.errorLoadingCore);
6148 return false;
6150 return false;
6153 config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
6155 var me = config.macros.upgrade;
6156 var w = params;
6157 var errMsg;
6158 if(!status)
6159 errMsg = me.errorLoadingCore;
6160 var newVer = me.extractVersion(responseText);
6161 if(!newVer)
6162 errMsg = me.errorCoreFormat;
6163 if(errMsg) {
6164 w.setButtons([],errMsg);
6165 alert(errMsg);
6166 return;
6168 var onStartUpgrade = function(e) {
6169 w.setButtons([],me.statusSavingCore);
6170 var localPath = getLocalPath(document.location.toString());
6171 saveFile(localPath,responseText);
6172 w.setButtons([],me.statusReloadingCore);
6173 var backupPath = w.getValue("backupPath");
6174 var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM() + '#upgrade:[[' + encodeURI(backupPath) + ']]';
6175 window.setTimeout(function () {window.location = newLoc;},10);
6177 var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version,newVer) + 1];
6178 w.addStep(me.step2Title,step2.format([formatVersion(newVer),formatVersion(version)]));
6179 w.setButtons([{caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade},{caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel}]);
6182 config.macros.upgrade.onCancel = function(e)
6184 var me = config.macros.upgrade;
6185 var w = new Wizard(this);
6186 w.addStep(me.step3Title,me.step3Html);
6187 w.setButtons([]);
6188 return false;
6191 config.macros.upgrade.extractVersion = function(upgradeFile)
6193 var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
6194 var m = re.exec(upgradeFile);
6195 return m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
6198 function upgradeFrom(path)
6200 var importStore = new TiddlyWiki();
6201 var tw = loadFile(path);
6202 if(window.netscape !== undefined)
6203 tw = convertUTF8ToUnicode(tw);
6204 importStore.importTiddlyWiki(tw);
6205 importStore.forEachTiddler(function(title,tiddler) {
6206 if(!store.getTiddler(title)) {
6207 store.addTiddler(tiddler);
6210 refreshDisplay();
6211 saveChanges(); //# To create appropriate Markup* sections
6212 alert(config.messages.upgradeDone.format([formatVersion()]));
6213 window.location = window.location.toString().substr(0,window.location.toString().lastIndexOf('?'));
6216 //--
6217 //-- Sync macro
6218 //--
6220 // Synchronisation handlers
6221 config.syncers = {};
6223 // Sync state.
6224 var currSync = null;
6226 // sync macro
6227 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6229 if(!wikifier.isStatic)
6230 this.startSync(place);
6233 config.macros.sync.cancelSync = function()
6235 currSync = null;
6238 config.macros.sync.startSync = function(place)
6240 if(currSync)
6241 config.macros.sync.cancelSync();
6242 currSync = {};
6243 currSync.syncList = this.getSyncableTiddlers();
6244 currSync.syncTasks = this.createSyncTasks(currSync.syncList);
6245 this.preProcessSyncableTiddlers(currSync.syncList);
6246 var wizard = new Wizard();
6247 currSync.wizard = wizard;
6248 wizard.createWizard(place,this.wizardTitle);
6249 wizard.addStep(this.step1Title,this.step1Html);
6250 var markList = wizard.getElement("markList");
6251 var listWrapper = document.createElement("div");
6252 markList.parentNode.insertBefore(listWrapper,markList);
6253 currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
6254 this.processSyncableTiddlers(currSync.syncList);
6255 wizard.setButtons([{caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}]);
6258 config.macros.sync.getSyncableTiddlers = function()
6260 var list = [];
6261 store.forEachTiddler(function(title,tiddler) {
6262 var syncItem = {};
6263 syncItem.serverType = tiddler.getServerType();
6264 syncItem.serverHost = tiddler.fields['server.host'];
6265 if(syncItem.serverType && syncItem.serverHost) {
6266 syncItem.serverWorkspace = tiddler.fields['server.workspace'];
6267 syncItem.tiddler = tiddler;
6268 syncItem.title = tiddler.title;
6269 syncItem.isTouched = tiddler.isTouched();
6270 syncItem.selected = syncItem.isTouched;
6271 syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
6272 syncItem.status = syncItem.syncStatus.text;
6273 list.push(syncItem);
6276 list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
6277 return list;
6280 config.macros.sync.preProcessSyncableTiddlers = function(syncList)
6282 for(var i=0; i<syncList.length; i++) {
6283 var si = syncList[i];
6284 si.serverUrl = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler).uri;
6288 config.macros.sync.processSyncableTiddlers = function(syncList)
6290 for(var i=0; i<syncList.length; i++) {
6291 var si = syncList[i];
6292 si.rowElement.style.display = si.syncStatus.display;
6293 if(si.syncStatus.className)
6294 si.rowElement.className = si.syncStatus.className;
6298 config.macros.sync.createSyncTasks = function(syncList)
6300 var syncTasks = [];
6301 for(var i=0; i<syncList.length; i++) {
6302 var si = syncList[i];
6303 var r = null;
6304 for(var j=0; j<syncTasks.length; j++) {
6305 var cst = syncTasks[j];
6306 if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
6307 r = cst;
6309 if(r) {
6310 si.syncTask = r;
6311 r.syncItems.push(si);
6312 } else {
6313 si.syncTask = this.createSyncTask(si);
6314 syncTasks.push(si.syncTask);
6317 return syncTasks;
6320 config.macros.sync.createSyncTask = function(syncItem)
6322 var st = {};
6323 st.serverType = syncItem.serverType;
6324 st.serverHost = syncItem.serverHost;
6325 st.serverWorkspace = syncItem.serverWorkspace;
6326 st.syncItems = [syncItem];
6327 st.syncMachine = new SyncMachine(st.serverType,{
6328 start: function() {
6329 return this.openHost(st.serverHost,"openWorkspace");
6331 openWorkspace: function() {
6332 return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
6334 getTiddlerList: function() {
6335 return this.getTiddlerList("onGetTiddlerList");
6337 onGetTiddlerList: function(context) {
6338 var tiddlers = context.tiddlers;
6339 for(var i=0; i<st.syncItems.length; i++) {
6340 var si = st.syncItems[i];
6341 var f = tiddlers.findByField("title",si.title);
6342 if(f !== null) {
6343 if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
6344 si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
6346 } else {
6347 si.syncStatus = config.macros.sync.syncStatusList.notFound;
6349 config.macros.sync.updateSyncStatus(si);
6352 getTiddler: function(title) {
6353 return this.getTiddler(title,"onGetTiddler");
6355 onGetTiddler: function(context) {
6356 var tiddler = context.tiddler;
6357 var syncItem = st.syncItems.findByField("title",tiddler.title);
6358 if(syncItem !== null) {
6359 syncItem = st.syncItems[syncItem];
6360 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
6361 syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
6362 config.macros.sync.updateSyncStatus(syncItem);
6365 putTiddler: function(tiddler) {
6366 return this.putTiddler(tiddler,"onPutTiddler");
6368 onPutTiddler: function(context) {
6369 var title = context.title;
6370 var syncItem = st.syncItems.findByField("title",title);
6371 if(syncItem !== null) {
6372 syncItem = st.syncItems[syncItem];
6373 store.resetTiddler(title);
6374 if(context.status) {
6375 syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
6376 config.macros.sync.updateSyncStatus(syncItem);
6381 st.syncMachine.go();
6382 return st;
6385 config.macros.sync.updateSyncStatus = function(syncItem)
6387 var e = syncItem.colElements["status"];
6388 removeChildren(e);
6389 createTiddlyText(e,syncItem.syncStatus.text);
6390 syncItem.rowElement.style.display = syncItem.syncStatus.display;
6391 if(syncItem.syncStatus.className)
6392 syncItem.rowElement.className = syncItem.syncStatus.className;
6395 config.macros.sync.doSync = function(e)
6397 var rowNames = ListView.getSelectedRows(currSync.listView);
6398 var sl = config.macros.sync.syncStatusList;
6399 for(var i=0; i<currSync.syncList.length; i++) {
6400 var si = currSync.syncList[i];
6401 if(rowNames.indexOf(si.title) != -1) {
6402 var r = true;
6403 switch(si.syncStatus) {
6404 case sl.changedServer:
6405 r = si.syncTask.syncMachine.go("getTiddler",si.title);
6406 break;
6407 case sl.notFound:
6408 case sl.changedLocally:
6409 case sl.changedBoth:
6410 r = si.syncTask.syncMachine.go("putTiddler",si.tiddler);
6411 break;
6412 default:
6413 break;
6415 if(!r)
6416 displayMessage("Error in doSync: " + r);
6419 return false;
6422 function SyncMachine(serverType,steps)
6424 this.serverType = serverType;
6425 this.adaptor = new config.adaptors[serverType]();
6426 this.steps = steps;
6429 SyncMachine.prototype.go = function(step,context)
6431 var r = context ? context.status : null;
6432 if(typeof r == "string") {
6433 this.invokeError(r);
6434 return r;
6436 var h = this.steps[step ? step : "start"];
6437 if(!h)
6438 return null;
6439 r = h.call(this,context);
6440 if(typeof r == "string")
6441 this.invokeError(r);
6442 return r;
6445 SyncMachine.prototype.invokeError = function(message)
6447 if(this.steps.error)
6448 this.steps.error(message);
6451 SyncMachine.prototype.openHost = function(host,nextStep)
6453 var me = this;
6454 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
6457 SyncMachine.prototype.getWorkspaceList = function(nextStep)
6459 var me = this;
6460 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
6463 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
6465 var me = this;
6466 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
6469 SyncMachine.prototype.getTiddlerList = function(nextStep)
6471 var me = this;
6472 return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
6475 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
6477 return this.adaptor.generateTiddlerInfo(tiddler);
6480 SyncMachine.prototype.getTiddler = function(title,nextStep)
6482 var me = this;
6483 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
6486 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
6488 var me = this;
6489 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
6492 //--
6493 //-- Manager UI for groups of tiddlers
6494 //--
6496 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString)
6498 var wizard = new Wizard();
6499 wizard.createWizard(place,this.wizardTitle);
6500 wizard.addStep(this.step1Title,this.step1Html);
6501 var markList = wizard.getElement("markList");
6502 var listWrapper = document.createElement("div");
6503 markList.parentNode.insertBefore(listWrapper,markList);
6504 listWrapper.setAttribute("refresh","macro");
6505 listWrapper.setAttribute("macroName","plugins");
6506 listWrapper.setAttribute("params",paramString);
6507 this.refresh(listWrapper,paramString);
6510 config.macros.plugins.refresh = function(listWrapper,params)
6512 var wizard = new Wizard(listWrapper);
6513 var selectedRows = [];
6514 ListView.forEachSelector(listWrapper,function(e,rowName) {
6515 if(e.checked)
6516 selectedRows.push(e.getAttribute("rowName"));
6518 removeChildren(listWrapper);
6519 params = params.parseParams("anon");
6520 var plugins = installedPlugins.slice(0);
6521 var t,tiddler,p;
6522 var configTiddlers = store.getTaggedTiddlers("systemConfig");
6523 for(t=0; t<configTiddlers.length; t++) {
6524 tiddler = configTiddlers[t];
6525 if(plugins.findByField("title",tiddler.title) == null) {
6526 p = getPluginInfo(tiddler);
6527 p.executed = false;
6528 p.log.splice(0,0,this.skippedText);
6529 plugins.push(p);
6532 for(t=0; t<plugins.length; t++) {
6533 p = plugins[t];
6534 p.size = p.tiddler.text ? p.tiddler.text.length : 0;
6535 p.forced = p.tiddler.isTagged("systemConfigForce");
6536 p.disabled = p.tiddler.isTagged("systemConfigDisable");
6537 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
6539 if(plugins.length == 0) {
6540 createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
6541 wizard.setButtons([]);
6542 } else {
6543 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6544 wizard.setValue("listView",listView);
6545 wizard.setButtons([
6546 {caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
6547 {caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
6552 config.macros.plugins.doRemoveTag = function(e)
6554 var wizard = new Wizard(this);
6555 var listView = wizard.getValue("listView");
6556 var rowNames = ListView.getSelectedRows(listView);
6557 if(rowNames.length == 0) {
6558 alert(config.messages.nothingSelected);
6559 } else {
6560 for(var t=0; t<rowNames.length; t++)
6561 store.setTiddlerTag(rowNames[t],false,"systemConfig");
6565 config.macros.plugins.doDelete = function(e)
6567 var wizard = new Wizard(this);
6568 var listView = wizard.getValue("listView");
6569 var rowNames = ListView.getSelectedRows(listView);
6570 if(rowNames.length == 0) {
6571 alert(config.messages.nothingSelected);
6572 } else {
6573 if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
6574 for(var t=0; t<rowNames.length; t++) {
6575 store.removeTiddler(rowNames[t]);
6576 story.closeTiddler(rowNames[t],true);
6582 //--
6583 //-- Message area
6584 //--
6586 function getMessageDiv()
6588 var msgArea = document.getElementById("messageArea");
6589 if(!msgArea)
6590 return null;
6591 if(!msgArea.hasChildNodes())
6592 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
6593 config.messages.messageClose.text,
6594 config.messages.messageClose.tooltip,
6595 clearMessage);
6596 msgArea.style.display = "block";
6597 return createTiddlyElement(msgArea,"div");
6600 function displayMessage(text,linkText)
6602 var e = getMessageDiv();
6603 if(!e) {
6604 alert(text);
6605 return;
6607 if(linkText) {
6608 var link = createTiddlyElement(e,"a",null,null,text);
6609 link.href = linkText;
6610 link.target = "_blank";
6611 } else {
6612 e.appendChild(document.createTextNode(text));
6616 function clearMessage()
6618 var msgArea = document.getElementById("messageArea");
6619 if(msgArea) {
6620 removeChildren(msgArea);
6621 msgArea.style.display = "none";
6623 return false;
6626 //--
6627 //-- Refresh mechanism
6628 //--
6630 config.notifyTiddlers = [
6631 {name: "StyleSheetLayout", notify: refreshStyles},
6632 {name: "StyleSheetColors", notify: refreshStyles},
6633 {name: "StyleSheet", notify: refreshStyles},
6634 {name: "StyleSheetPrint", notify: refreshStyles},
6635 {name: "PageTemplate", notify: refreshPageTemplate},
6636 {name: "SiteTitle", notify: refreshPageTitle},
6637 {name: "SiteSubtitle", notify: refreshPageTitle},
6638 {name: "ColorPalette", notify: refreshColorPalette},
6639 {name: null, notify: refreshDisplay}
6642 config.refreshers = {
6643 link: function(e,changeList)
6645 var title = e.getAttribute("tiddlyLink");
6646 refreshTiddlyLink(e,title);
6647 return true;
6650 tiddler: function(e,changeList)
6652 var title = e.getAttribute("tiddler");
6653 var template = e.getAttribute("template");
6654 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
6655 story.refreshTiddler(title,template,true);
6656 else
6657 refreshElements(e,changeList);
6658 return true;
6661 content: function(e,changeList)
6663 var title = e.getAttribute("tiddler");
6664 var force = e.getAttribute("force");
6665 if(force != null || changeList == null || changeList.indexOf(title) != -1) {
6666 removeChildren(e);
6667 wikify(store.getTiddlerText(title,""),e,null,store.fetchTiddler(title));
6668 return true;
6669 } else
6670 return false;
6673 macro: function(e,changeList)
6675 var macro = e.getAttribute("macroName");
6676 var params = e.getAttribute("params");
6677 if(macro)
6678 macro = config.macros[macro];
6679 if(macro && macro.refresh)
6680 macro.refresh(e,params);
6681 return true;
6685 config.refresherData = {
6686 styleSheet: "StyleSheet",
6687 defaultStyleSheet: "StyleSheet",
6688 pageTemplate: "PageTemplate",
6689 defaultPageTemplate: "PageTemplate",
6690 colorPalette: "ColorPalette",
6691 defaultColorPalette: "ColorPalette"
6694 function refreshElements(root,changeList)
6696 var nodes = root.childNodes;
6697 for(var c=0; c<nodes.length; c++) {
6698 var e = nodes[c], type = null;
6699 if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
6700 type = e.getAttribute("refresh");
6701 var refresher = config.refreshers[type];
6702 var refreshed = false;
6703 if(refresher != undefined)
6704 refreshed = refresher(e,changeList);
6705 if(e.hasChildNodes() && !refreshed)
6706 refreshElements(e,changeList);
6710 function applyHtmlMacros(root,tiddler)
6712 var e = root.firstChild;
6713 while(e) {
6714 var nextChild = e.nextSibling;
6715 if(e.getAttribute) {
6716 var macro = e.getAttribute("macro");
6717 if(macro) {
6718 e.removeAttribute("macro");
6719 var params = "";
6720 var p = macro.indexOf(" ");
6721 if(p != -1) {
6722 params = macro.substr(p+1);
6723 macro = macro.substr(0,p);
6725 invokeMacro(e,macro,params,null,tiddler);
6728 if(e.hasChildNodes())
6729 applyHtmlMacros(e,tiddler);
6730 e = nextChild;
6734 function refreshPageTemplate(title)
6736 var stash = createTiddlyElement(document.body,"div");
6737 stash.style.display = "none";
6738 var display = story.getContainer();
6739 var nodes,t;
6740 if(display) {
6741 nodes = display.childNodes;
6742 for(t=nodes.length-1; t>=0; t--)
6743 stash.appendChild(nodes[t]);
6745 var wrapper = document.getElementById("contentWrapper");
6747 var isAvailable = function(title) {
6748 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
6749 if(s!=-1)
6750 title = title.substr(0,s);
6751 return store.tiddlerExists(title) || store.isShadowTiddler(title);
6753 if(!title || !isAvailable(title))
6754 title = config.refresherData.pageTemplate;
6755 if(!isAvailable(title))
6756 title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
6757 wrapper.innerHTML = store.getRecursiveTiddlerText(title,null,10);
6758 applyHtmlMacros(wrapper);
6759 refreshElements(wrapper);
6760 display = story.getContainer();
6761 removeChildren(display);
6762 if(!display)
6763 display = createTiddlyElement(wrapper,"div",story.containerId());
6764 nodes = stash.childNodes;
6765 for(t=nodes.length-1; t>=0; t--)
6766 display.appendChild(nodes[t]);
6767 removeNode(stash);
6770 function refreshDisplay(hint)
6772 if(typeof hint == "string")
6773 hint = [hint];
6774 var e = document.getElementById("contentWrapper");
6775 refreshElements(e,hint);
6776 if(backstage.isPanelVisible()) {
6777 e = document.getElementById("backstage");
6778 refreshElements(e,hint);
6782 function refreshPageTitle()
6784 document.title = getPageTitle();
6787 function getPageTitle()
6789 var st = wikifyPlain("SiteTitle");
6790 var ss = wikifyPlain("SiteSubtitle");
6791 return st + ((st == "" || ss == "") ? "" : " - ") + ss;
6794 function refreshStyles(title,doc)
6796 setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc || document);
6799 function refreshColorPalette(title)
6801 if(!startingUp)
6802 refreshAll();
6805 function refreshAll()
6807 refreshPageTemplate();
6808 refreshDisplay();
6809 refreshStyles("StyleSheetLayout");
6810 refreshStyles("StyleSheetColors");
6811 refreshStyles(config.refresherData.styleSheet);
6812 refreshStyles("StyleSheetPrint");
6815 //--
6816 //-- Options stuff
6817 //--
6819 config.optionHandlers = {
6820 'txt': {
6821 get: function(name) {return encodeCookie(config.options[name].toString());},
6822 set: function(name,value) {config.options[name] = decodeCookie(value);}
6824 'chk': {
6825 get: function(name) {return config.options[name] ? "true" : "false";},
6826 set: function(name,value) {config.options[name] = value == "true";}
6830 function loadOptionsCookie()
6832 if(safeMode)
6833 return;
6834 var cookies = document.cookie.split(";");
6835 for(var c=0; c<cookies.length; c++) {
6836 var p = cookies[c].indexOf("=");
6837 if(p != -1) {
6838 var name = cookies[c].substr(0,p).trim();
6839 var value = cookies[c].substr(p+1).trim();
6840 var optType = name.substr(0,3);
6841 if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
6842 config.optionHandlers[optType].set(name,value);
6847 function saveOptionCookie(name)
6849 if(safeMode)
6850 return;
6851 var c = name + "=";
6852 var optType = name.substr(0,3);
6853 if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
6854 c += config.optionHandlers[optType].get(name);
6855 c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
6856 document.cookie = c;
6859 function encodeCookie(s)
6861 return escape(convertUnicodeToHtmlEntities(s));
6864 function decodeCookie(s)
6866 s = unescape(s);
6867 var re = /&#[0-9]{1,5};/g;
6868 return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
6872 config.macros.option.genericCreate = function(place,type,opt,className,desc)
6874 var typeInfo = config.macros.option.types[type];
6875 var c = document.createElement(typeInfo.elementType);
6876 if(typeInfo.typeValue)
6877 c.setAttribute("type",typeInfo.typeValue);
6878 c[typeInfo.eventName] = typeInfo.onChange;
6879 c.setAttribute("option",opt);
6880 c.className = className || typeInfo.className;
6881 if(config.optionsDesc[opt])
6882 c.setAttribute("title",config.optionsDesc[opt]);
6883 place.appendChild(c);
6884 if(desc != "no")
6885 createTiddlyText(place,config.optionsDesc[opt] || opt);
6886 c[typeInfo.valueField] = config.options[opt];
6887 return c;
6890 config.macros.option.genericOnChange = function(e)
6892 var opt = this.getAttribute("option");
6893 if(opt) {
6894 var optType = opt.substr(0,3);
6895 var handler = config.macros.option.types[optType];
6896 if(handler.elementType && handler.valueField)
6897 config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType,this);
6899 return true;
6902 config.macros.option.types = {
6903 'txt': {
6904 elementType: "input",
6905 valueField: "value",
6906 eventName: "onchange",
6907 className: "txtOptionInput",
6908 create: config.macros.option.genericCreate,
6909 onChange: config.macros.option.genericOnChange
6911 'chk': {
6912 elementType: "input",
6913 valueField: "checked",
6914 eventName: "onclick",
6915 className: "chkOptionInput",
6916 typeValue: "checkbox",
6917 create: config.macros.option.genericCreate,
6918 onChange: config.macros.option.genericOnChange
6922 config.macros.option.propagateOption = function(opt,valueField,value,elementType,elem)
6924 config.options[opt] = value;
6925 saveOptionCookie(opt);
6926 var nodes = document.getElementsByTagName(elementType);
6927 for(var t=0; t<nodes.length; t++) {
6928 var optNode = nodes[t].getAttribute("option");
6929 if(opt == optNode && nodes[t]!=elem)
6930 nodes[t][valueField] = value;
6934 config.macros.option.handler = function(place,macroName,params,wikifier,paramString)
6936 params = paramString.parseParams("anon",null,true,false,false);
6937 var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
6938 var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
6939 var desc = getParam(params,"desc","no");
6940 var type = opt.substr(0,3);
6941 var h = config.macros.option.types[type];
6942 if(h && h.create)
6943 h.create(place,type,opt,className,desc);
6946 config.macros.options.handler = function(place,macroName,params,wikifier,paramString)
6948 params = paramString.parseParams("anon",null,true,false,false);
6949 var showUnknown = getParam(params,"showUnknown","no");
6950 var wizard = new Wizard();
6951 wizard.createWizard(place,this.wizardTitle);
6952 wizard.addStep(this.step1Title,this.step1Html);
6953 var markList = wizard.getElement("markList");
6954 var chkUnknown = wizard.getElement("chkUnknown");
6955 chkUnknown.checked = showUnknown == "yes";
6956 chkUnknown.onchange = this.onChangeUnknown;
6957 var listWrapper = document.createElement("div");
6958 markList.parentNode.insertBefore(listWrapper,markList);
6959 wizard.setValue("listWrapper",listWrapper);
6960 this.refreshOptions(listWrapper,showUnknown == "yes");
6963 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
6965 var opts = [];
6966 for(var n in config.options) {
6967 var opt = {};
6968 opt.option = "";
6969 opt.name = n;
6970 opt.lowlight = !config.optionsDesc[n];
6971 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6972 if(!opt.lowlight || showUnknown)
6973 opts.push(opt);
6975 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);});
6976 var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
6977 for(n=0; n<opts.length; n++) {
6978 var type = opts[n].name.substr(0,3);
6979 var h = config.macros.option.types[type];
6980 if(h && h.create) {
6981 h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
6986 config.macros.options.onChangeUnknown = function(e)
6988 var wizard = new Wizard(this);
6989 var listWrapper = wizard.getValue("listWrapper");
6990 removeChildren(listWrapper);
6991 config.macros.options.refreshOptions(listWrapper,this.checked);
6992 return false;
6995 //--
6996 //-- Saving
6997 //--
6999 var saveUsingSafari = false;
7001 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
7002 var endSaveArea = '</d' + 'iv>';
7004 // If there are unsaved changes, force the user to confirm before exitting
7005 function confirmExit()
7007 hadConfirmExit = true;
7008 if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
7009 return config.messages.confirmExit;
7012 // Give the user a chance to save changes before exitting
7013 function checkUnsavedChanges()
7015 if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
7016 if(confirm(config.messages.unsavedChangesWarning))
7017 saveChanges();
7021 function updateLanguageAttribute(s)
7023 if(config.locale) {
7024 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
7025 var m = mRE.exec(s);
7026 if(m) {
7027 var t = m[1];
7028 if(m[2])
7029 t += ' xml:lang="' + config.locale + '"';
7030 if(m[3])
7031 t += ' lang="' + config.locale + '"';
7032 t += ">";
7033 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
7036 return s;
7039 function updateMarkupBlock(s,blockName,tiddlerName)
7041 return s.replaceChunk(
7042 "<!--%0-START-->".format([blockName]),
7043 "<!--%0-END-->".format([blockName]),
7044 "\n" + convertUnicodeToFileFormat(store.getRecursiveTiddlerText(tiddlerName,"")) + "\n");
7047 function updateOriginal(original,posDiv,localPath)
7049 if(!posDiv)
7050 posDiv = locateStoreArea(original);
7051 if(!posDiv) {
7052 alert(config.messages.invalidFileError.format([localPath]));
7053 return null;
7055 var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
7056 convertUnicodeToFileFormat(store.allTiddlersAsHtml()) + "\n" +
7057 original.substr(posDiv[1]);
7058 var newSiteTitle = convertUnicodeToFileFormat(getPageTitle()).htmlEncode();
7059 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
7060 revised = updateLanguageAttribute(revised);
7061 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
7062 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
7063 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
7064 revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
7065 return revised;
7068 function locateStoreArea(original)
7070 // Locate the storeArea div's
7071 var posOpeningDiv = original.indexOf(startSaveArea);
7072 var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
7073 if(limitClosingDiv == -1)
7074 limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
7075 var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
7076 return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
7079 function autoSaveChanges(onlyIfDirty,tiddlers)
7081 if(config.options.chkAutoSave)
7082 saveChanges(onlyIfDirty,tiddlers);
7085 function loadOriginal(localPath)
7087 return loadFile(localPath);
7090 // Save this tiddlywiki with the pending changes
7091 function saveChanges(onlyIfDirty,tiddlers)
7093 if(onlyIfDirty && !store.isDirty())
7094 return;
7095 clearMessage();
7096 var t0 = new Date();
7097 var originalPath = document.location.toString();
7098 if(originalPath.substr(0,5) != "file:") {
7099 alert(config.messages.notFileUrlError);
7100 if(store.tiddlerExists(config.messages.saveInstructions))
7101 story.displayTiddler(null,config.messages.saveInstructions);
7102 return;
7104 var localPath = getLocalPath(originalPath);
7105 var original = loadOriginal(localPath);
7106 if(original == null) {
7107 alert(config.messages.cantSaveError);
7108 if(store.tiddlerExists(config.messages.saveInstructions))
7109 story.displayTiddler(null,config.messages.saveInstructions);
7110 return;
7112 var posDiv = locateStoreArea(original);
7113 if(!posDiv) {
7114 alert(config.messages.invalidFileError.format([localPath]));
7115 return;
7117 saveMain(localPath,original,posDiv);
7118 if(config.options.chkSaveBackups)
7119 saveBackup(localPath,original);
7120 if(config.options.chkSaveEmptyTemplate)
7121 saveEmpty(localPath,original,posDiv);
7122 if(config.options.chkGenerateAnRssFeed && saveRss instanceof Function)
7123 saveRss(localPath);
7124 if(config.options.chkDisplayInstrumentation)
7125 displayMessage("saveChanges " + (new Date()-t0) + " ms");
7128 function saveMain(localPath,original,posDiv)
7130 var save;
7131 try {
7132 var revised = updateOriginal(original,posDiv,localPath);
7133 save = saveFile(localPath,revised);
7134 } catch (ex) {
7135 showException(ex);
7137 if(save) {
7138 displayMessage(config.messages.mainSaved,"file://" + localPath);
7139 store.setDirty(false);
7140 } else {
7141 alert(config.messages.mainFailed);
7145 function saveBackup(localPath,original)
7147 var backupPath = getBackupPath(localPath);
7148 var backup = copyFile(backupPath,localPath);
7149 if(!backup)
7150 backup = saveFile(backupPath,original);
7151 if(backup)
7152 displayMessage(config.messages.backupSaved,"file://" + backupPath);
7153 else
7154 alert(config.messages.backupFailed);
7157 function saveEmpty(localPath,original,posDiv)
7159 var emptyPath,p;
7160 if((p = localPath.lastIndexOf("/")) != -1)
7161 emptyPath = localPath.substr(0,p) + "/";
7162 else if((p = localPath.lastIndexOf("\\")) != -1)
7163 emptyPath = localPath.substr(0,p) + "\\";
7164 else
7165 emptyPath = localPath + ".";
7166 emptyPath += "empty.html";
7167 var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
7168 var emptySave = saveFile(emptyPath,empty);
7169 if(emptySave)
7170 displayMessage(config.messages.emptySaved,"file://" + emptyPath);
7171 else
7172 alert(config.messages.emptyFailed);
7175 function getLocalPath(origPath)
7177 var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
7178 // Remove any location or query part of the URL
7179 var argPos = originalPath.indexOf("?");
7180 if(argPos != -1)
7181 originalPath = originalPath.substr(0,argPos);
7182 var hashPos = originalPath.indexOf("#");
7183 if(hashPos != -1)
7184 originalPath = originalPath.substr(0,hashPos);
7185 // Convert file://localhost/ to file:///
7186 if(originalPath.indexOf("file://localhost/") == 0)
7187 originalPath = "file://" + originalPath.substr(16);
7188 // Convert to a native file format
7189 var localPath;
7190 if(originalPath.charAt(9) == ":") // pc local file
7191 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
7192 else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
7193 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
7194 else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
7195 localPath = unescape(originalPath.substr(7));
7196 else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
7197 localPath = unescape(originalPath.substr(5));
7198 else // pc network file
7199 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
7200 return localPath;
7203 function getBackupPath(localPath,title,extension)
7205 var slash = "\\";
7206 var dirPathPos = localPath.lastIndexOf("\\");
7207 if(dirPathPos == -1) {
7208 dirPathPos = localPath.lastIndexOf("/");
7209 slash = "/";
7211 var backupFolder = config.options.txtBackupFolder;
7212 if(!backupFolder || backupFolder == "")
7213 backupFolder = ".";
7214 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
7215 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
7216 if(title)
7217 backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
7218 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension || "html");
7219 return backupPath;
7222 //--
7223 //-- RSS Saving
7224 //--
7226 function saveRss(localPath)
7228 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
7229 if(saveFile(rssPath,convertUnicodeToFileFormat(generateRss())))
7230 displayMessage(config.messages.rssSaved,"file://" + rssPath);
7231 else
7232 alert(config.messages.rssFailed);
7235 function generateRss()
7237 var s = [];
7238 var d = new Date();
7239 var u = store.getTiddlerText("SiteUrl");
7240 // Assemble the header
7241 s.push("<" + "?xml version=\"1.0\"?" + ">");
7242 s.push("<rss version=\"2.0\">");
7243 s.push("<channel>");
7244 s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
7245 if(u)
7246 s.push("<link>" + u.htmlEncode() + "</link>");
7247 s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
7248 s.push("<language>" + config.locale + "</language>");
7249 s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
7250 s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
7251 s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
7252 s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
7253 s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
7254 // The body
7255 var tiddlers = store.getTiddlers("modified","excludeLists");
7256 var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
7257 for(var t=tiddlers.length-1; t>=n; t--) {
7258 s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
7260 // And footer
7261 s.push("</channel>");
7262 s.push("</rss>");
7263 // Save it all
7264 return s.join("\n");
7267 //--
7268 //-- Filesystem code
7269 //--
7271 function convertUTF8ToUnicode(u)
7273 return config.browser.isOpera || !window.netscape ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
7276 function manualConvertUTF8ToUnicode(utf)
7278 var uni = utf;
7279 var src = 0;
7280 var dst = 0;
7281 var b1, b2, b3;
7282 var c;
7283 while(src < utf.length) {
7284 b1 = utf.charCodeAt(src++);
7285 if(b1 < 0x80) {
7286 dst++;
7287 } else if(b1 < 0xE0) {
7288 b2 = utf.charCodeAt(src++);
7289 c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
7290 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7291 } else {
7292 b2 = utf.charCodeAt(src++);
7293 b3 = utf.charCodeAt(src++);
7294 c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
7295 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7298 return uni;
7301 function mozConvertUTF8ToUnicode(u)
7303 try {
7304 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7305 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7306 converter.charset = "UTF-8";
7307 } catch(ex) {
7308 return manualConvertUTF8ToUnicode(u);
7309 } // fallback
7310 var s = converter.ConvertToUnicode(u);
7311 var fin = converter.Finish();
7312 return fin.length > 0 ? s+fin : s;
7315 function convertUnicodeToFileFormat(s)
7317 return config.browser.isOpera || !window.netscape ? convertUnicodeToHtmlEntities(s) : mozConvertUnicodeToUTF8(s);
7320 function convertUnicodeToHtmlEntities(s)
7322 var re = /[^\u0000-\u007F]/g;
7323 return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
7326 function convertUnicodeToUTF8(s)
7328 // return convertUnicodeToFileFormat to allow plugin migration
7329 return convertUnicodeToFileFormat(s);
7332 function manualConvertUnicodeToUTF8(s)
7334 return unescape(encodeURIComponent(s));
7337 function mozConvertUnicodeToUTF8(s)
7339 try {
7340 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7341 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7342 converter.charset = "UTF-8";
7343 } catch(ex) {
7344 return manualConvertUnicodeToUTF8(s);
7345 } // fallback
7346 var u = converter.ConvertFromUnicode(s);
7347 var fin = converter.Finish();
7348 return fin.length > 0 ? u + fin : u;
7351 function convertUriToUTF8(uri,charSet)
7353 if(window.netscape == undefined || charSet == undefined || charSet == "")
7354 return uri;
7355 try {
7356 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7357 var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
7358 } catch(ex) {
7359 return uri;
7361 return converter.convertURISpecToUTF8(uri,charSet);
7364 function copyFile(dest,source)
7366 return config.browser.isIE ? ieCopyFile(dest,source) : false;
7369 function saveFile(fileUrl,content)
7371 var r = mozillaSaveFile(fileUrl,content);
7372 if(!r)
7373 r = ieSaveFile(fileUrl,content);
7374 if(!r)
7375 r = javaSaveFile(fileUrl,content);
7376 return r;
7379 function loadFile(fileUrl)
7381 var r = mozillaLoadFile(fileUrl);
7382 if((r == null) || (r == false))
7383 r = ieLoadFile(fileUrl);
7384 if((r == null) || (r == false))
7385 r = javaLoadFile(fileUrl);
7386 return r;
7389 function ieCreatePath(path)
7391 try {
7392 var fso = new ActiveXObject("Scripting.FileSystemObject");
7393 } catch(ex) {
7394 return null;
7397 var pos = path.lastIndexOf("\\");
7398 if(pos!=-1)
7399 path = path.substring(0,pos+1);
7401 var scan = [path];
7402 var parent = fso.GetParentFolderName(path);
7403 while(parent && !fso.FolderExists(parent)) {
7404 scan.push(parent);
7405 parent = fso.GetParentFolderName(parent);
7408 for(i=scan.length-1;i>=0;i--) {
7409 if(!fso.FolderExists(scan[i])) {
7410 fso.CreateFolder(scan[i]);
7413 return true;
7416 // Returns null if it can't do it, false if there's an error, true if it saved OK
7417 function ieSaveFile(filePath,content)
7419 ieCreatePath(filePath);
7420 try {
7421 var fso = new ActiveXObject("Scripting.FileSystemObject");
7422 } catch(ex) {
7423 return null;
7425 var file = fso.OpenTextFile(filePath,2,-1,0);
7426 file.Write(content);
7427 file.Close();
7428 return true;
7431 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7432 function ieLoadFile(filePath)
7434 try {
7435 var fso = new ActiveXObject("Scripting.FileSystemObject");
7436 var file = fso.OpenTextFile(filePath,1);
7437 var content = file.ReadAll();
7438 file.Close();
7439 } catch(ex) {
7440 return null;
7442 return content;
7445 function ieCopyFile(dest,source)
7447 ieCreatePath(dest);
7448 try {
7449 var fso = new ActiveXObject("Scripting.FileSystemObject");
7450 fso.GetFile(source).Copy(dest);
7451 } catch(ex) {
7452 return false;
7454 return true;
7457 // Returns null if it can't do it, false if there's an error, true if it saved OK
7458 function mozillaSaveFile(filePath,content)
7460 if(window.Components) {
7461 try {
7462 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7463 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7464 file.initWithPath(filePath);
7465 if(!file.exists())
7466 file.create(0,0664);
7467 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
7468 out.init(file,0x20|0x02,00004,null);
7469 out.write(content,content.length);
7470 out.flush();
7471 out.close();
7472 return true;
7473 } catch(ex) {
7474 return false;
7477 return null;
7480 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7481 function mozillaLoadFile(filePath)
7483 if(window.Components) {
7484 try {
7485 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7486 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7487 file.initWithPath(filePath);
7488 if(!file.exists())
7489 return null;
7490 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
7491 inputStream.init(file,0x01,00004,null);
7492 var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
7493 sInputStream.init(inputStream);
7494 var contents = sInputStream.read(sInputStream.available());
7495 sInputStream.close();
7496 inputStream.close();
7497 return contents;
7498 } catch(ex) {
7499 return false;
7502 return null;
7505 function javaUrlToFilename(url)
7507 var f = "//localhost";
7508 if(url.indexOf(f) == 0)
7509 return url.substring(f.length);
7510 var i = url.indexOf(":");
7511 return i > 0 ? url.substring(i-1) : url;
7514 function javaSaveFile(filePath,content)
7516 try {
7517 if(document.applets["TiddlySaver"])
7518 return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
7519 } catch(ex) {
7521 try {
7522 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
7523 s.print(content);
7524 s.close();
7525 } catch(ex) {
7526 return null;
7528 return true;
7531 function javaLoadFile(filePath)
7533 try {
7534 if(document.applets["TiddlySaver"])
7535 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
7536 } catch(ex) {
7538 var content = [];
7539 try {
7540 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
7541 var line;
7542 while((line = r.readLine()) != null)
7543 content.push(new String(line));
7544 r.close();
7545 } catch(ex) {
7546 return null;
7548 return content.join("\n");
7551 //--
7552 //-- Server adaptor base class
7553 //--
7555 function AdaptorBase()
7557 this.host = null;
7558 this.store = null;
7559 return this;
7562 AdaptorBase.prototype.close = function()
7564 return true;
7567 AdaptorBase.prototype.fullHostName = function(host)
7569 if(!host)
7570 return '';
7571 host = host.trim();
7572 if(!host.match(/:\/\//))
7573 host = 'http://' + host;
7574 if(host.substr(host.length-1) == '/')
7575 host = host.substr(0,host.length-1)
7576 return host;
7579 AdaptorBase.minHostName = function(host)
7581 return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
7584 AdaptorBase.prototype.setContext = function(context,userParams,callback)
7586 if(!context) context = {};
7587 context.userParams = userParams;
7588 if(callback) context.callback = callback;
7589 context.adaptor = this;
7590 if(!context.host)
7591 context.host = this.host;
7592 context.host = this.fullHostName(context.host);
7593 if(!context.workspace)
7594 context.workspace = this.workspace;
7595 return context;
7598 // Open the specified host
7599 AdaptorBase.prototype.openHost = function(host,context,userParams,callback)
7601 this.host = host;
7602 context = this.setContext(context,userParams,callback);
7603 context.status = true;
7604 if(callback)
7605 window.setTimeout(function() {context.callback(context,userParams);},10);
7606 return true;
7609 // Open the specified workspace
7610 AdaptorBase.prototype.openWorkspace = function(workspace,context,userParams,callback)
7612 this.workspace = workspace;
7613 context = this.setContext(context,userParams,callback);
7614 context.status = true;
7615 if(callback)
7616 window.setTimeout(function() {callback(context,userParams);},10);
7617 return true;
7620 //--
7621 //-- Server adaptor for talking to static TiddlyWiki files
7622 //--
7624 function FileAdaptor()
7628 FileAdaptor.prototype = new AdaptorBase();
7630 FileAdaptor.serverType = 'file';
7631 FileAdaptor.serverLabel = 'TiddlyWiki';
7633 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7635 context.status = status;
7636 if(!status) {
7637 context.statusText = "Error reading file";
7638 } else {
7639 context.adaptor.store = new TiddlyWiki();
7640 if(!context.adaptor.store.importTiddlyWiki(responseText)) {
7641 context.statusText = config.messages.invalidFileError.format([url]);
7642 context.status = false;
7645 context.complete(context,context.userParams);
7648 // Get the list of workspaces on a given server
7649 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
7651 context = this.setContext(context,userParams,callback);
7652 context.workspaces = [{title:"(default)"}];
7653 context.status = true;
7654 if(callback)
7655 window.setTimeout(function() {callback(context,userParams);},10);
7656 return true;
7659 // Gets the list of tiddlers within a given workspace
7660 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
7662 context = this.setContext(context,userParams,callback);
7663 if(!context.filter)
7664 context.filter = filter;
7665 context.complete = FileAdaptor.getTiddlerListComplete;
7666 if(this.store) {
7667 var ret = context.complete(context,context.userParams);
7668 } else {
7669 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7670 if(typeof ret != "string")
7671 ret = true;
7673 return ret;
7676 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7678 if(context.status) {
7679 if(context.filter) {
7680 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
7681 } else {
7682 context.tiddlers = [];
7683 context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
7685 for(var i=0; i<context.tiddlers.length; i++) {
7686 context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
7687 context.tiddlers[i].fields['server.host'] = AdaptorBase.minHostName(context.host);
7688 context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
7690 context.status = true;
7692 if(context.callback) {
7693 window.setTimeout(function() {context.callback(context,userParams);},10);
7695 return true;
7698 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7700 var info = {};
7701 info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
7702 return info;
7705 // Retrieve a tiddler from a given workspace on a given server
7706 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
7708 context = this.setContext(context,userParams,callback);
7709 context.title = title;
7710 context.complete = FileAdaptor.getTiddlerComplete;
7711 return context.adaptor.store ?
7712 context.complete(context,context.userParams) :
7713 loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7716 FileAdaptor.getTiddlerComplete = function(context,userParams)
7718 var t = context.adaptor.store.fetchTiddler(context.title);
7719 t.fields['server.type'] = FileAdaptor.serverType;
7720 t.fields['server.host'] = AdaptorBase.minHostName(context.host);
7721 t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
7722 context.tiddler = t;
7723 context.status = true;
7724 if(context.allowSynchronous) {
7725 context.isSynchronous = true;
7726 context.callback(context,userParams);
7727 } else {
7728 window.setTimeout(function() {context.callback(context,userParams);},10);
7730 return true;
7733 FileAdaptor.prototype.close = function()
7735 delete this.store;
7736 this.store = null;
7739 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7741 config.defaultAdaptor = FileAdaptor.serverType;
7743 //--
7744 //-- Remote HTTP requests
7745 //--
7747 function loadRemoteFile(url,callback,params)
7749 return httpReq("GET",url,callback,params);
7752 function httpReq(type,url,callback,params,headers,data,contentType,username,password,allowCache)
7754 var x = null;
7755 try {
7756 x = new XMLHttpRequest(); //# Modern
7757 } catch(ex) {
7758 try {
7759 x = new ActiveXObject("Msxml2.XMLHTTP"); //# IE 6
7760 } catch(ex2) {
7763 if(!x)
7764 return "Can't create XMLHttpRequest object";
7765 x.onreadystatechange = function() {
7766 try {
7767 var status = x.status;
7768 } catch(ex) {
7769 status = false;
7771 if(x.readyState == 4 && callback && (status !== undefined)) {
7772 if([0, 200, 201, 204, 207].contains(status))
7773 callback(true,params,x.responseText,url,x);
7774 else
7775 callback(false,params,null,url,x);
7776 x.onreadystatechange = function(){};
7777 x = null;
7780 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
7781 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
7782 try {
7783 if(!allowCache)
7784 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
7785 x.open(type,url,true,username,password);
7786 if(data)
7787 x.setRequestHeader("Content-Type", contentType || "application/x-www-form-urlencoded");
7788 if(x.overrideMimeType)
7789 x.setRequestHeader("Connection", "close");
7790 if(headers) {
7791 for(var n in headers)
7792 x.setRequestHeader(n,headers[n]);
7794 x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
7795 x.send(data);
7796 } catch(ex) {
7797 return exceptionText(ex);
7799 return x;
7802 // included for compatibility
7803 function getXMLHttpRequest()
7805 try {
7806 var x = new XMLHttpRequest(); // Modern
7807 } catch(ex) {
7808 try {
7809 x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
7810 } catch (ex2) {
7811 return null;
7814 return x;
7817 // included for compatibility
7818 function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
7820 return httpReq(type,url,callback,params,headers,data,contentType,username,password,allowCache);
7823 //--
7824 //-- TiddlyWiki-specific utility functions
7825 //--
7827 function formatVersion(v)
7829 v = v || version;
7830 return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
7833 function compareVersions(v1,v2)
7835 var a = ["major","minor","revision"];
7836 for(var i = 0; i<a.length; i++) {
7837 var x1 = v1[a[i]] || 0;
7838 var x2 = v2[a[i]] || 0;
7839 if(x1<x2)
7840 return 1;
7841 if(x1>x2)
7842 return -1;
7844 x1 = v1.beta || 9999;
7845 x2 = v2.beta || 9999;
7846 if(x1<x2)
7847 return 1;
7848 return x1 > x2 ? -1 : 0;
7851 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7853 var btn = document.createElement("a");
7854 if(action) {
7855 btn.onclick = action;
7856 btn.setAttribute("href","javascript:;");
7858 if(tooltip)
7859 btn.setAttribute("title",tooltip);
7860 if(text)
7861 btn.appendChild(document.createTextNode(text));
7862 btn.className = className || "button";
7863 if(id)
7864 btn.id = id;
7865 if(attribs) {
7866 for(var i in attribs) {
7867 btn.setAttribute(i,attribs[i]);
7870 if(parent)
7871 parent.appendChild(btn);
7872 if(accessKey)
7873 btn.setAttribute("accessKey",accessKey);
7874 return btn;
7877 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
7879 var text = includeText ? title : null;
7880 var i = getTiddlyLinkInfo(title,className);
7881 var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
7882 if(isStatic)
7883 btn.className += ' ' + className;
7884 btn.setAttribute("refresh","link");
7885 btn.setAttribute("tiddlyLink",title);
7886 if(noToggle)
7887 btn.setAttribute("noToggle","true");
7888 if(linkedFromTiddler) {
7889 var fields = linkedFromTiddler.getInheritedFields();
7890 if(fields)
7891 btn.setAttribute("tiddlyFields",fields);
7893 return btn;
7896 function refreshTiddlyLink(e,title)
7898 var i = getTiddlyLinkInfo(title,e.className);
7899 e.className = i.classes;
7900 e.title = i.subTitle;
7903 function getTiddlyLinkInfo(title,currClasses)
7905 var classes = currClasses ? currClasses.split(" ") : [];
7906 classes.pushUnique("tiddlyLink");
7907 var tiddler = store.fetchTiddler(title);
7908 var subTitle;
7909 if(tiddler) {
7910 subTitle = tiddler.getSubtitle();
7911 classes.pushUnique("tiddlyLinkExisting");
7912 classes.remove("tiddlyLinkNonExisting");
7913 classes.remove("shadow");
7914 } else {
7915 classes.remove("tiddlyLinkExisting");
7916 classes.pushUnique("tiddlyLinkNonExisting");
7917 if(store.isShadowTiddler(title)) {
7918 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7919 classes.pushUnique("shadow");
7920 } else {
7921 subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
7922 classes.remove("shadow");
7925 if(typeof config.annotations[title]=="string")
7926 subTitle = config.annotations[title];
7927 return {classes: classes.join(" "),subTitle: subTitle};
7930 function createExternalLink(place,url)
7932 var link = document.createElement("a");
7933 link.className = "externalLink";
7934 link.href = url;
7935 link.title = config.messages.externalLinkTooltip.format([url]);
7936 if(config.options.chkOpenInNewWindow)
7937 link.target = "_blank";
7938 place.appendChild(link);
7939 return link;
7942 // Event handler for clicking on a tiddly link
7943 function onClickTiddlerLink(ev)
7945 var e = ev || window.event;
7946 var target = resolveTarget(e);
7947 var link = target;
7948 var title = null;
7949 var fields = null;
7950 var noToggle = null;
7951 do {
7952 title = link.getAttribute("tiddlyLink");
7953 fields = link.getAttribute("tiddlyFields");
7954 noToggle = link.getAttribute("noToggle");
7955 link = link.parentNode;
7956 } while(title == null && link != null);
7957 if(!store.isShadowTiddler(title)) {
7958 var f = fields ? fields.decodeHashMap() : {};
7959 fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
7961 if(title) {
7962 var toggling = e.metaKey || e.ctrlKey;
7963 if(config.options.chkToggleLinks)
7964 toggling = !toggling;
7965 if(noToggle)
7966 toggling = false;
7967 if(store.getTiddler(title))
7968 fields = null;
7969 story.displayTiddler(target,title,null,true,null,fields,toggling);
7971 clearMessage();
7972 return false;
7975 // Create a button for a tag with a popup listing all the tiddlers that it tags
7976 function createTagButton(place,tag,excludeTiddler,title,tooltip)
7978 var btn = createTiddlyButton(place,title||tag,(tooltip||config.views.wikified.tag.tooltip).format([tag]),onClickTag);
7979 btn.setAttribute("tag",tag);
7980 if(excludeTiddler)
7981 btn.setAttribute("tiddler",excludeTiddler);
7982 return btn;
7985 // Event handler for clicking on a tiddler tag
7986 function onClickTag(ev)
7988 var e = ev || window.event;
7989 var popup = Popup.create(this);
7990 var tag = this.getAttribute("tag");
7991 var title = this.getAttribute("tiddler");
7992 if(popup && tag) {
7993 var tagged = store.getTaggedTiddlers(tag);
7994 var titles = [];
7995 var li,r;
7996 for(r=0;r<tagged.length;r++) {
7997 if(tagged[r].title != title)
7998 titles.push(tagged[r].title);
8000 var lingo = config.views.wikified.tag;
8001 if(titles.length > 0) {
8002 var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
8003 openAll.setAttribute("tag",tag);
8004 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
8005 for(r=0; r<titles.length; r++) {
8006 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
8008 } else {
8009 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
8011 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
8012 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
8013 createTiddlyText(h,lingo.openTag.format([tag]));
8015 Popup.show();
8016 e.cancelBubble = true;
8017 if(e.stopPropagation) e.stopPropagation();
8018 return false;
8021 // Event handler for 'open all' on a tiddler popup
8022 function onClickTagOpenAll(ev)
8024 var tiddlers = store.getTaggedTiddlers(this.getAttribute("tag"));
8025 story.displayTiddlers(this,tiddlers);
8026 return false;
8029 function onClickError(ev)
8031 var e = ev || window.event;
8032 var popup = Popup.create(this);
8033 var lines = this.getAttribute("errorText").split("\n");
8034 for(var t=0; t<lines.length; t++)
8035 createTiddlyElement(popup,"li",null,null,lines[t]);
8036 Popup.show();
8037 e.cancelBubble = true;
8038 if(e.stopPropagation) e.stopPropagation();
8039 return false;
8042 function createTiddlyDropDown(place,onchange,options,defaultValue)
8044 var sel = createTiddlyElement(place,"select");
8045 sel.onchange = onchange;
8046 for(var t=0; t<options.length; t++) {
8047 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
8048 e.value = options[t].name;
8049 if(options[t].name == defaultValue)
8050 e.selected = true;
8052 return sel;
8055 function createTiddlyPopup(place,caption,tooltip,tiddler)
8057 if(tiddler.text) {
8058 createTiddlyLink(place,caption,true);
8059 var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
8060 btn.tiddler = tiddler;
8061 } else {
8062 createTiddlyText(place,caption);
8066 function onClickTiddlyPopup(ev)
8068 var e = ev || window.event;
8069 var tiddler = this.tiddler;
8070 if(tiddler.text) {
8071 var popup = Popup.create(this,"div","popupTiddler");
8072 wikify(tiddler.text,popup,null,tiddler);
8073 Popup.show();
8075 if(e) e.cancelBubble = true;
8076 if(e && e.stopPropagation) e.stopPropagation();
8077 return false;
8080 function createTiddlyError(place,title,text)
8082 var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
8083 if(text) btn.setAttribute("errorText",text);
8086 function merge(dst,src,preserveExisting)
8088 for(var i in src) {
8089 if(!preserveExisting || dst[i] === undefined)
8090 dst[i] = src[i];
8092 return dst;
8095 // Returns a string containing the description of an exception, optionally prepended by a message
8096 function exceptionText(e,message)
8098 var s = e.description || e.toString();
8099 return message ? "%0:\n%1".format([message,s]) : s;
8102 // Displays an alert of an exception description with optional message
8103 function showException(e,message)
8105 alert(exceptionText(e,message));
8108 function alertAndThrow(m)
8110 alert(m);
8111 throw(m);
8114 function glyph(name)
8116 var g = config.glyphs;
8117 var b = g.currBrowser;
8118 if(b == null) {
8119 b = 0;
8120 while(!g.browsers[b]() && b < g.browsers.length-1)
8121 b++;
8122 g.currBrowser = b;
8124 if(!g.codes[name])
8125 return "";
8126 return g.codes[name][b];
8129 if(!window.console) {
8130 console = {log:function(message) {displayMessage(message);}};
8134 //- Animation engine
8137 function Animator()
8139 this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
8140 this.timerID = 0; // ID of the timer used for animating
8141 this.animations = []; // List of animations in progress
8142 return this;
8145 // Start animation engine
8146 Animator.prototype.startAnimating = function() //# Variable number of arguments
8148 for(var t=0; t<arguments.length; t++)
8149 this.animations.push(arguments[t]);
8150 if(this.running == 0) {
8151 var me = this;
8152 this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
8154 this.running += arguments.length;
8157 // Perform an animation engine tick, calling each of the known animation modules
8158 Animator.prototype.doAnimate = function(me)
8160 var a = 0;
8161 while(a < me.animations.length) {
8162 var animation = me.animations[a];
8163 if(animation.tick()) {
8164 a++;
8165 } else {
8166 me.animations.splice(a,1);
8167 if(--me.running == 0)
8168 window.clearInterval(me.timerID);
8173 Animator.slowInSlowOut = function(progress)
8175 return(1-((Math.cos(progress * Math.PI)+1)/2));
8178 //--
8179 //-- Morpher animation
8180 //--
8182 // Animate a set of properties of an element
8183 function Morpher(element,duration,properties,callback)
8185 this.element = element;
8186 this.duration = duration;
8187 this.properties = properties;
8188 this.startTime = new Date();
8189 this.endTime = Number(this.startTime) + duration;
8190 this.callback = callback;
8191 this.tick();
8192 return this;
8195 Morpher.prototype.assignStyle = function(element,style,value)
8197 switch(style) {
8198 case "-tw-vertScroll":
8199 window.scrollTo(findScrollX(),value);
8200 break;
8201 case "-tw-horizScroll":
8202 window.scrollTo(value,findScrollY());
8203 break;
8204 default:
8205 element.style[style] = value;
8206 break;
8210 Morpher.prototype.stop = function()
8212 for(var t=0; t<this.properties.length; t++) {
8213 var p = this.properties[t];
8214 if(p.atEnd !== undefined) {
8215 this.assignStyle(this.element,p.style,p.atEnd);
8218 if(this.callback)
8219 this.callback(this.element,this.properties);
8222 Morpher.prototype.tick = function()
8224 var currTime = Number(new Date());
8225 var progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
8226 for(var t=0; t<this.properties.length; t++) {
8227 var p = this.properties[t];
8228 if(p.start !== undefined && p.end !== undefined) {
8229 var template = p.template || "%0";
8230 switch(p.format) {
8231 case undefined:
8232 case "style":
8233 var v = p.start + (p.end-p.start) * progress;
8234 this.assignStyle(this.element,p.style,template.format([v]));
8235 break;
8236 case "color":
8237 break;
8241 if(currTime >= this.endTime) {
8242 this.stop();
8243 return false;
8245 return true;
8248 //--
8249 //-- Zoomer animation
8250 //--
8252 function Zoomer(text,startElement,targetElement,unused)
8254 var e = createTiddlyElement(document.body,"div",null,"zoomer");
8255 createTiddlyElement(e,"div",null,null,text);
8256 var winWidth = findWindowWidth();
8257 var winHeight = findWindowHeight();
8258 var p = [
8259 {style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
8260 {style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
8261 {style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
8262 {style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
8263 {style: 'fontSize', start: 8, end: 24, template: '%0pt'}
8265 var c = function(element,properties) {removeNode(element);};
8266 return new Morpher(e,config.animDuration,p,c);
8269 //--
8270 //-- Scroller animation
8271 //--
8273 function Scroller(targetElement)
8275 var p = [{style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}];
8276 return new Morpher(targetElement,config.animDuration,p);
8279 //--
8280 //-- Slider animation
8281 //--
8283 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
8284 function Slider(element,opening,unused,deleteMode)
8286 element.style.overflow = 'hidden';
8287 if(opening)
8288 element.style.height = '0px'; // Resolves a Firefox flashing bug
8289 element.style.display = 'block';
8290 var left = findPosX(element);
8291 var width = element.scrollWidth;
8292 var height = element.scrollHeight;
8293 var winWidth = findWindowWidth();
8294 var p = [];
8295 var c = null;
8296 if(opening) {
8297 p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
8298 p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
8299 p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
8300 } else {
8301 p.push({style: 'height', start: height, end: 0, template: '%0px'});
8302 p.push({style: 'display', atEnd: 'none'});
8303 p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
8304 p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
8305 switch(deleteMode) {
8306 case "all":
8307 c = function(element,properties) {removeNode(element);};
8308 break;
8309 case "children":
8310 c = function(element,properties) {removeChildren(element);};
8311 break;
8314 return new Morpher(element,config.animDuration,p,c);
8317 //--
8318 //-- Popup menu
8319 //--
8321 var Popup = {
8322 stack: [] // Array of objects with members root: and popup:
8325 Popup.create = function(root,elem,className)
8327 var stackPosition = this.find(root,"popup");
8328 Popup.remove(stackPosition+1);
8329 var popup = createTiddlyElement(document.body,elem || "ol","popup",className || "popup");
8330 popup.stackPosition = stackPosition;
8331 Popup.stack.push({root: root, popup: popup});
8332 return popup;
8335 Popup.onDocumentClick = function(ev)
8337 var e = ev || window.event;
8338 if(e.eventPhase == undefined)
8339 Popup.remove();
8340 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
8341 Popup.remove();
8342 return true;
8345 Popup.show = function(valign,halign,offset)
8347 var curr = Popup.stack[Popup.stack.length-1];
8348 this.place(curr.root,curr.popup,valign,halign,offset);
8349 addClass(curr.root,"highlight");
8350 if(config.options.chkAnimate && anim && typeof Scroller == "function")
8351 anim.startAnimating(new Scroller(curr.popup));
8352 else
8353 window.scrollTo(0,ensureVisible(curr.popup));
8356 Popup.place = function(root,popup,valign,halign,offset)
8358 if(!offset)
8359 var offset = {x:0,y:0};
8360 if(popup.stackPosition >= 0 && !valign && !halign) {
8361 offset.x = offset.x + root.offsetWidth;
8362 } else {
8363 offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
8364 offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
8366 var rootLeft = findPosX(root);
8367 var rootTop = findPosY(root);
8368 var popupLeft = rootLeft + offset.x;
8369 var popupTop = rootTop + offset.y;
8370 var winWidth = findWindowWidth();
8371 if(popup.offsetWidth > winWidth*0.75)
8372 popup.style.width = winWidth*0.75 + "px";
8373 var popupWidth = popup.offsetWidth;
8374 var scrollWidth = winWidth - document.body.offsetWidth;
8375 if(popupLeft + popupWidth > winWidth - scrollWidth - 1) {
8376 if(halign == 'right')
8377 popupLeft = popupLeft - root.offsetWidth - popupWidth;
8378 else
8379 popupLeft = winWidth - popupWidth - scrollWidth - 1;
8381 popup.style.left = popupLeft + "px";
8382 popup.style.top = popupTop + "px";
8383 popup.style.display = "block";
8386 Popup.find = function(e)
8388 var pos = -1;
8389 for (var t=this.stack.length-1; t>=0; t--) {
8390 if(isDescendant(e,this.stack[t].popup))
8391 pos = t;
8393 return pos;
8396 Popup.remove = function(pos)
8398 if(!pos) var pos = 0;
8399 if(Popup.stack.length > pos) {
8400 Popup.removeFrom(pos);
8404 Popup.removeFrom = function(from)
8406 for(var t=Popup.stack.length-1; t>=from; t--) {
8407 var p = Popup.stack[t];
8408 removeClass(p.root,"highlight");
8409 removeNode(p.popup);
8411 Popup.stack = Popup.stack.slice(0,from);
8414 //--
8415 //-- Wizard support
8416 //--
8418 function Wizard(elem)
8420 if(elem) {
8421 this.formElem = findRelated(elem,"wizard","className");
8422 this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
8423 this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
8424 } else {
8425 this.formElem = null;
8426 this.bodyElem = null;
8427 this.footElem = null;
8431 Wizard.prototype.setValue = function(name,value)
8433 if(this.formElem)
8434 this.formElem[name] = value;
8437 Wizard.prototype.getValue = function(name)
8439 return this.formElem ? this.formElem[name] : null;
8442 Wizard.prototype.createWizard = function(place,title)
8444 this.formElem = createTiddlyElement(place,"form",null,"wizard");
8445 createTiddlyElement(this.formElem,"h1",null,null,title);
8446 this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
8447 this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
8450 Wizard.prototype.clear = function()
8452 removeChildren(this.bodyElem);
8455 Wizard.prototype.setButtons = function(buttonInfo,status)
8457 removeChildren(this.footElem);
8458 for(var t=0; t<buttonInfo.length; t++) {
8459 createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
8460 insertSpacer(this.footElem);
8462 if(typeof status == "string") {
8463 createTiddlyElement(this.footElem,"span",null,"status",status);
8467 Wizard.prototype.addStep = function(stepTitle,html)
8469 removeChildren(this.bodyElem);
8470 var w = createTiddlyElement(this.bodyElem,"div");
8471 createTiddlyElement(w,"h2",null,null,stepTitle);
8472 var step = createTiddlyElement(w,"div",null,"wizardStep");
8473 step.innerHTML = html;
8474 applyHtmlMacros(step,tiddler);
8477 Wizard.prototype.getElement = function(name)
8479 return this.formElem.elements[name];
8482 //--
8483 //-- ListView gadget
8484 //--
8486 var ListView = {};
8488 // Create a listview
8489 ListView.create = function(place,listObject,listTemplate,callback,className)
8491 var table = createTiddlyElement(place,"table",null,className || "listView twtable");
8492 var thead = createTiddlyElement(table,"thead");
8493 var r = createTiddlyElement(thead,"tr");
8494 for(var t=0; t<listTemplate.columns.length; t++) {
8495 var columnTemplate = listTemplate.columns[t];
8496 var c = createTiddlyElement(r,"th");
8497 var colType = ListView.columnTypes[columnTemplate.type];
8498 if(colType && colType.createHeader) {
8499 colType.createHeader(c,columnTemplate,t);
8500 if(columnTemplate.className)
8501 addClass(c,columnTemplate.className);
8504 var tbody = createTiddlyElement(table,"tbody");
8505 for(var rc=0; rc<listObject.length; rc++) {
8506 var rowObject = listObject[rc];
8507 r = createTiddlyElement(tbody,"tr");
8508 for(c=0; c<listTemplate.rowClasses.length; c++) {
8509 if(rowObject[listTemplate.rowClasses[c].field])
8510 addClass(r,listTemplate.rowClasses[c].className);
8512 rowObject.rowElement = r;
8513 rowObject.colElements = {};
8514 for(var cc=0; cc<listTemplate.columns.length; cc++) {
8515 c = createTiddlyElement(r,"td");
8516 columnTemplate = listTemplate.columns[cc];
8517 var field = columnTemplate.field;
8518 colType = ListView.columnTypes[columnTemplate.type];
8519 if(colType && colType.createItem) {
8520 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
8521 if(columnTemplate.className)
8522 addClass(c,columnTemplate.className);
8524 rowObject.colElements[field] = c;
8527 if(callback && listTemplate.actions)
8528 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
8529 if(callback && listTemplate.buttons) {
8530 for(t=0; t<listTemplate.buttons.length; t++) {
8531 var a = listTemplate.buttons[t];
8532 if(a && a.name != "")
8533 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
8536 return table;
8539 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
8541 return function(e) {
8542 var view = findRelated(this,"TABLE",null,"previousSibling");
8543 var tiddlers = [];
8544 ListView.forEachSelector(view,function(e,rowName) {
8545 if(e.checked)
8546 tiddlers.push(rowName);
8548 if(tiddlers.length == 0 && !allowEmptySelection) {
8549 alert(config.messages.nothingSelected);
8550 } else {
8551 if(this.nodeName.toLowerCase() == "select") {
8552 callback(view,this.value,tiddlers);
8553 this.selectedIndex = 0;
8554 } else {
8555 callback(view,name,tiddlers);
8561 // Invoke a callback for each selector checkbox in the listview
8562 ListView.forEachSelector = function(view,callback)
8564 var checkboxes = view.getElementsByTagName("input");
8565 var hadOne = false;
8566 for(var t=0; t<checkboxes.length; t++) {
8567 var cb = checkboxes[t];
8568 if(cb.getAttribute("type") == "checkbox") {
8569 var rn = cb.getAttribute("rowName");
8570 if(rn) {
8571 callback(cb,rn);
8572 hadOne = true;
8576 return hadOne;
8579 ListView.getSelectedRows = function(view)
8581 var rowNames = [];
8582 ListView.forEachSelector(view,function(e,rowName) {
8583 if(e.checked)
8584 rowNames.push(rowName);
8586 return rowNames;
8589 ListView.columnTypes = {};
8591 ListView.columnTypes.String = {
8592 createHeader: function(place,columnTemplate,col)
8594 createTiddlyText(place,columnTemplate.title);
8596 createItem: function(place,listObject,field,columnTemplate,col,row)
8598 var v = listObject[field];
8599 if(v != undefined)
8600 createTiddlyText(place,v);
8604 ListView.columnTypes.WikiText = {
8605 createHeader: ListView.columnTypes.String.createHeader,
8606 createItem: function(place,listObject,field,columnTemplate,col,row)
8608 var v = listObject[field];
8609 if(v != undefined)
8610 wikify(v,place,null,null);
8614 ListView.columnTypes.Tiddler = {
8615 createHeader: ListView.columnTypes.String.createHeader,
8616 createItem: function(place,listObject,field,columnTemplate,col,row)
8618 var v = listObject[field];
8619 if(v != undefined && v.title)
8620 createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
8624 ListView.columnTypes.Size = {
8625 createHeader: ListView.columnTypes.String.createHeader,
8626 createItem: function(place,listObject,field,columnTemplate,col,row)
8628 var v = listObject[field];
8629 if(v != undefined) {
8630 var t = 0;
8631 while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
8632 t++;
8633 createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
8638 ListView.columnTypes.Link = {
8639 createHeader: ListView.columnTypes.String.createHeader,
8640 createItem: function(place,listObject,field,columnTemplate,col,row)
8642 var v = listObject[field];
8643 var c = columnTemplate.text;
8644 if(v != undefined)
8645 createTiddlyText(createExternalLink(place,v),c || v);
8649 ListView.columnTypes.Date = {
8650 createHeader: ListView.columnTypes.String.createHeader,
8651 createItem: function(place,listObject,field,columnTemplate,col,row)
8653 var v = listObject[field];
8654 if(v != undefined)
8655 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
8659 ListView.columnTypes.StringList = {
8660 createHeader: ListView.columnTypes.String.createHeader,
8661 createItem: function(place,listObject,field,columnTemplate,col,row)
8663 var v = listObject[field];
8664 if(v != undefined) {
8665 for(var t=0; t<v.length; t++) {
8666 createTiddlyText(place,v[t]);
8667 createTiddlyElement(place,"br");
8673 ListView.columnTypes.Selector = {
8674 createHeader: function(place,columnTemplate,col)
8676 createTiddlyCheckbox(place,null,false,this.onHeaderChange);
8678 createItem: function(place,listObject,field,columnTemplate,col,row)
8680 var e = createTiddlyCheckbox(place,null,listObject[field],null);
8681 e.setAttribute("rowName",listObject[columnTemplate.rowName]);
8683 onHeaderChange: function(e)
8685 var state = this.checked;
8686 var view = findRelated(this,"TABLE");
8687 if(!view)
8688 return;
8689 ListView.forEachSelector(view,function(e,rowName) {
8690 e.checked = state;
8695 ListView.columnTypes.Tags = {
8696 createHeader: ListView.columnTypes.String.createHeader,
8697 createItem: function(place,listObject,field,columnTemplate,col,row)
8699 var tags = listObject[field];
8700 createTiddlyText(place,String.encodeTiddlyLinkList(tags));
8704 ListView.columnTypes.Boolean = {
8705 createHeader: ListView.columnTypes.String.createHeader,
8706 createItem: function(place,listObject,field,columnTemplate,col,row)
8708 if(listObject[field] == true)
8709 createTiddlyText(place,columnTemplate.trueText);
8710 if(listObject[field] == false)
8711 createTiddlyText(place,columnTemplate.falseText);
8715 ListView.columnTypes.TagCheckbox = {
8716 createHeader: ListView.columnTypes.String.createHeader,
8717 createItem: function(place,listObject,field,columnTemplate,col,row)
8719 var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
8720 e.setAttribute("tiddler",listObject.title);
8721 e.setAttribute("tag",columnTemplate.tag);
8723 onChange : function(e)
8725 var tag = this.getAttribute("tag");
8726 var tiddler = this.getAttribute("tiddler");
8727 store.setTiddlerTag(tiddler,this.checked,tag);
8731 ListView.columnTypes.TiddlerLink = {
8732 createHeader: ListView.columnTypes.String.createHeader,
8733 createItem: function(place,listObject,field,columnTemplate,col,row)
8735 var v = listObject[field];
8736 if(v != undefined) {
8737 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
8738 createTiddlyText(link,listObject[field]);
8743 //--
8744 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8745 //--
8747 // Clamp a number to a range
8748 Number.prototype.clamp = function(min,max)
8750 var c = this;
8751 if(c < min)
8752 c = min;
8753 if(c > max)
8754 c = max;
8755 return c;
8758 // Add indexOf function if browser does not support it
8759 if(!Array.indexOf) {
8760 Array.prototype.indexOf = function(item,from)
8762 if(!from)
8763 from = 0;
8764 for(var i=from; i<this.length; i++) {
8765 if(this[i] === item)
8766 return i;
8768 return -1;
8771 // Find an entry in a given field of the members of an array
8772 Array.prototype.findByField = function(field,value)
8774 for(var t=0; t<this.length; t++) {
8775 if(this[t][field] == value)
8776 return t;
8778 return null;
8781 // Return whether an entry exists in an array
8782 Array.prototype.contains = function(item)
8784 return this.indexOf(item) != -1;
8787 // Adds, removes or toggles a particular value within an array
8788 // value - value to add
8789 // mode - +1 to add value, -1 to remove value, 0 to toggle it
8790 Array.prototype.setItem = function(value,mode)
8792 var p = this.indexOf(value);
8793 if(mode == 0)
8794 mode = (p == -1) ? +1 : -1;
8795 if(mode == +1) {
8796 if(p == -1)
8797 this.push(value);
8798 } else if(mode == -1) {
8799 if(p != -1)
8800 this.splice(p,1);
8804 // Return whether one of a list of values exists in an array
8805 Array.prototype.containsAny = function(items)
8807 for(var i=0; i<items.length; i++) {
8808 if(this.indexOf(items[i]) != -1)
8809 return true;
8811 return false;
8814 // Return whether all of a list of values exists in an array
8815 Array.prototype.containsAll = function(items)
8817 for(var i = 0; i<items.length; i++) {
8818 if(this.indexOf(items[i]) == -1)
8819 return false;
8821 return true;
8824 // 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
8825 Array.prototype.pushUnique = function(item,unique)
8827 if(unique === false) {
8828 this.push(item);
8829 } else {
8830 if(this.indexOf(item) == -1)
8831 this.push(item);
8835 Array.prototype.remove = function(item)
8837 var p = this.indexOf(item);
8838 if(p != -1)
8839 this.splice(p,1);
8842 if(!Array.prototype.map) {
8843 Array.prototype.map = function(fn,thisObj)
8845 var scope = thisObj || window;
8846 var a = [];
8847 for(var i=0, j=this.length; i < j; ++i) {
8848 a.push(fn.call(scope,this[i],i,this));
8850 return a;
8853 // Get characters from the right end of a string
8854 String.prototype.right = function(n)
8856 return n < this.length ? this.slice(this.length-n) : this;
8859 // Trim whitespace from both ends of a string
8860 String.prototype.trim = function()
8862 return this.replace(/^\s*|\s*$/g,"");
8865 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
8866 String.prototype.unDash = function()
8868 var s = this.split("-");
8869 if(s.length > 1) {
8870 for(var t=1; t<s.length; t++)
8871 s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
8873 return s.join("");
8876 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
8877 String.prototype.format = function(substrings)
8879 var subRegExp = /(?:%(\d+))/mg;
8880 var currPos = 0;
8881 var r = [];
8882 do {
8883 var match = subRegExp.exec(this);
8884 if(match && match[1]) {
8885 if(match.index > currPos)
8886 r.push(this.substring(currPos,match.index));
8887 r.push(substrings[parseInt(match[1])]);
8888 currPos = subRegExp.lastIndex;
8890 } while(match);
8891 if(currPos < this.length)
8892 r.push(this.substring(currPos,this.length));
8893 return r.join("");
8896 // Escape any special RegExp characters with that character preceded by a backslash
8897 String.prototype.escapeRegExp = function()
8899 var s = "\\^$*+?()=!|,{}[].";
8900 var c = this;
8901 for(var t=0; t<s.length; t++)
8902 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
8903 return c;
8906 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
8907 String.prototype.escapeLineBreaks = function()
8909 return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
8912 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
8913 String.prototype.unescapeLineBreaks = function()
8915 return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
8918 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
8919 String.prototype.htmlEncode = function()
8921 return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
8924 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
8925 String.prototype.htmlDecode = function()
8927 return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
8930 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
8931 String.prototype.toJSONString = function()
8933 var m = {
8934 '\b': '\\b',
8935 '\f': '\\f',
8936 '\n': '\\n',
8937 '\r': '\\r',
8938 '\t': '\\t',
8939 '"' : '\\"',
8940 '\\': '\\\\'
8942 var replaceFn = function(a,b) {
8943 var c = m[b];
8944 if(c)
8945 return c;
8946 c = b.charCodeAt();
8947 return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
8949 if(/["\\\x00-\x1f]/.test(this))
8950 return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
8951 return '"' + this + '"';
8954 // Parse a space-separated string of name:value parameters
8955 // The result is an array of objects:
8956 // result[0] = object with a member for each parameter name, value of that member being an array of values
8957 // result[1..n] = one object for each parameter, with 'name' and 'value' members
8958 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
8960 var parseToken = function(match,p) {
8961 var n;
8962 if(match[p]) // Double quoted
8963 n = match[p];
8964 else if(match[p+1]) // Single quoted
8965 n = match[p+1];
8966 else if(match[p+2]) // Double-square-bracket quoted
8967 n = match[p+2];
8968 else if(match[p+3]) // Double-brace quoted
8969 try {
8970 n = match[p+3];
8971 if(allowEval)
8972 n = window.eval(n);
8973 } catch(ex) {
8974 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
8976 else if(match[p+4]) // Unquoted
8977 n = match[p+4];
8978 else if(match[p+5]) // empty quote
8979 n = "";
8980 return n;
8982 var r = [{}];
8983 var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
8984 var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
8985 var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
8986 var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
8987 var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
8988 var emptyQuote = "((?:\"\")|(?:''))";
8989 var skipSpace = "(?:\\s*)";
8990 var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
8991 var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
8992 var params = [];
8993 do {
8994 var match = re.exec(this);
8995 if(match) {
8996 var n = parseToken(match,1);
8997 if(noNames) {
8998 r.push({name:"",value:n});
8999 } else {
9000 var v = parseToken(match,8);
9001 if(v == null && defaultName) {
9002 v = n;
9003 n = defaultName;
9004 } else if(v == null && defaultValue) {
9005 v = defaultValue;
9007 r.push({name:n,value:v});
9008 if(cascadeDefaults) {
9009 defaultName = n;
9010 defaultValue = v;
9014 } while(match);
9015 // Summarise parameters into first element
9016 for(var t=1; t<r.length; t++) {
9017 if(r[0][r[t].name])
9018 r[0][r[t].name].push(r[t].value);
9019 else
9020 r[0][r[t].name] = [r[t].value];
9022 return r;
9025 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
9026 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
9027 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
9028 String.prototype.readMacroParams = function()
9030 var p = this.parseParams("list",null,true,true);
9031 var n = [];
9032 for(var t=1; t<p.length; t++)
9033 n.push(p[t].value);
9034 return n;
9037 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
9038 String.prototype.readBracketedList = function(unique)
9040 var p = this.parseParams("list",null,false,true);
9041 var n = [];
9042 for(var t=1; t<p.length; t++) {
9043 if(p[t].value)
9044 n.pushUnique(p[t].value,unique);
9046 return n;
9049 // Returns array with start and end index of chunk between given start and end marker, or undefined.
9050 String.prototype.getChunkRange = function(start,end)
9052 var s = this.indexOf(start);
9053 if(s != -1) {
9054 s += start.length;
9055 var e = this.indexOf(end,s);
9056 if(e != -1)
9057 return [s,e];
9061 // Replace a chunk of a string given start and end markers
9062 String.prototype.replaceChunk = function(start,end,sub)
9064 var r = this.getChunkRange(start,end);
9065 return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
9068 // Returns a chunk of a string between start and end markers, or undefined
9069 String.prototype.getChunk = function(start,end)
9071 var r = this.getChunkRange(start,end);
9072 if(r)
9073 return this.substring(r[0],r[1]);
9077 // Static method to bracket a string with double square brackets if it contains a space
9078 String.encodeTiddlyLink = function(title)
9080 return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
9083 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
9084 String.encodeTiddlyLinkList = function(list)
9086 if(list) {
9087 var results = [];
9088 for(var t=0; t<list.length; t++)
9089 results.push(String.encodeTiddlyLink(list[t]));
9090 return results.join(" ");
9091 } else {
9092 return "";
9096 // Convert a string as a sequence of name:"value" pairs into a hashmap
9097 String.prototype.decodeHashMap = function()
9099 var fields = this.parseParams("anon","",false);
9100 var r = {};
9101 for(var t=1; t<fields.length; t++)
9102 r[fields[t].name] = fields[t].value;
9103 return r;
9106 // Static method to encode a hashmap into a name:"value"... string
9107 String.encodeHashMap = function(hashmap)
9109 var r = [];
9110 for(var t in hashmap)
9111 r.push(t + ':"' + hashmap[t] + '"');
9112 return r.join(" ");
9115 // Static method to left-pad a string with 0s to a certain width
9116 String.zeroPad = function(n,d)
9118 var s = n.toString();
9119 if(s.length < d)
9120 s = "000000000000000000000000000".substr(0,d-s.length) + s;
9121 return s;
9124 String.prototype.startsWith = function(prefix)
9126 return !prefix || this.substring(0,prefix.length) == prefix;
9129 // Returns the first value of the given named parameter.
9130 function getParam(params,name,defaultValue)
9132 if(!params)
9133 return defaultValue;
9134 var p = params[0][name];
9135 return p ? p[0] : defaultValue;
9138 // Returns the first value of the given boolean named parameter.
9139 function getFlag(params,name,defaultValue)
9141 return !!getParam(params,name,defaultValue);
9144 // Substitute date components into a string
9145 Date.prototype.formatString = function(template)
9147 var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
9148 t = t.replace(/hh12/g,this.getHours12());
9149 t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
9150 t = t.replace(/hh/g,this.getHours());
9151 t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
9152 t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
9153 t = t.replace(/mm/g,this.getMinutes());
9154 t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
9155 t = t.replace(/ss/g,this.getSeconds());
9156 t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
9157 t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
9158 t = t.replace(/wYYYY/g,this.getYearForWeekNo());
9159 t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
9160 t = t.replace(/YYYY/g,this.getFullYear());
9161 t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
9162 t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
9163 t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
9164 t = t.replace(/MM/g,this.getMonth()+1);
9165 t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
9166 t = t.replace(/WW/g,this.getWeek());
9167 t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
9168 t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
9169 t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
9170 t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
9171 t = t.replace(/DD/g,this.getDate());
9172 var tz = this.getTimezoneOffset();
9173 var atz = Math.abs(tz);
9174 t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60),2) + ':' + String.zeroPad(atz % 60,2));
9175 t = t.replace(/\\/g,"");
9176 return t;
9179 Date.prototype.getWeek = function()
9181 var dt = new Date(this.getTime());
9182 var d = dt.getDay();
9183 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
9184 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
9185 var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
9186 return Math.floor(n/7)+1;
9189 Date.prototype.getYearForWeekNo = function()
9191 var dt = new Date(this.getTime());
9192 var d = dt.getDay();
9193 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
9194 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
9195 return dt.getFullYear();
9198 Date.prototype.getHours12 = function()
9200 var h = this.getHours();
9201 return h > 12 ? h-12 : ( h > 0 ? h : 12 );
9204 Date.prototype.getAmPm = function()
9206 return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
9209 Date.prototype.daySuffix = function()
9211 return config.messages.dates.daySuffixes[this.getDate()-1];
9214 // Convert a date to local YYYYMMDDHHMM string format
9215 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
9217 return this.getFullYear() + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
9220 // Convert a date to UTC YYYYMMDDHHMM string format
9221 Date.prototype.convertToYYYYMMDDHHMM = function()
9223 return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
9226 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
9227 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
9229 return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + "." + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2) + String.zeroPad(this.getUTCSeconds(),2) + String.zeroPad(this.getUTCMilliseconds(),4);
9232 // Static method to create a date from a UTC YYYYMMDDHHMM format string
9233 Date.convertFromYYYYMMDDHHMM = function(d)
9235 var hh = d.substr(8,2) || "00";
9236 var mm = d.substr(10,2) || "00";
9237 return new Date(Date.UTC(parseInt(d.substr(0,4),10),
9238 parseInt(d.substr(4,2),10)-1,
9239 parseInt(d.substr(6,2),10),
9240 parseInt(hh,10),
9241 parseInt(mm,10),0,0));
9244 //--
9245 //-- Crypto functions and associated conversion routines
9246 //--
9248 // Crypto 'namespace'
9249 function Crypto() {}
9251 // Convert a string to an array of big-endian 32-bit words
9252 Crypto.strToBe32s = function(str)
9254 var be=[];
9255 var len=Math.floor(str.length/4);
9256 var i, j;
9257 for(i=0, j=0; i<len; i++, j+=4) {
9258 be[i]=((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
9260 while(j<str.length) {
9261 be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
9262 j++;
9264 return be;
9267 // Convert an array of big-endian 32-bit words to a string
9268 Crypto.be32sToStr = function(be)
9270 var str='';
9271 for(var i=0;i<be.length*32;i+=8) {
9272 str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
9274 return str;
9277 // Convert an array of big-endian 32-bit words to a hex string
9278 Crypto.be32sToHex = function(be)
9280 var hex='0123456789ABCDEF';
9281 var str='';
9282 for(var i=0;i<be.length*4;i++) {
9283 str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
9285 return str;
9288 // Return, in hex, the SHA-1 hash of a string
9289 Crypto.hexSha1Str = function(str)
9291 return Crypto.be32sToHex(Crypto.sha1Str(str));
9294 // Return the SHA-1 hash of a string
9295 Crypto.sha1Str = function(str)
9297 return Crypto.sha1(Crypto.strToBe32s(str),str.length);
9300 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
9301 Crypto.sha1 = function(x,blen)
9303 // Add 32-bit integers, wrapping at 32 bits
9304 function add32(a,b)
9306 var lsw=(a&0xFFFF)+(b&0xFFFF);
9307 var msw=(a>>16)+(b>>16)+(lsw>>16);
9308 return (msw<<16)|(lsw&0xFFFF);
9310 function AA(a,b,c,d,e)
9312 b=(b>>>27)|(b<<5);
9313 var lsw=(a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
9314 var msw=(a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
9315 return (msw<<16)|(lsw&0xFFFF);
9317 function RR(w,j)
9319 var n=w[j-3]^w[j-8]^w[j-14]^w[j-16];
9320 return (n>>>31)|(n<<1);
9323 var len=blen*8;
9324 x[len>>5] |= 0x80 << (24-len%32);
9325 x[((len+64>>9)<<4)+15]=len;
9326 var w=new Array(80);
9328 var k1=0x5A827999;
9329 var k2=0x6ED9EBA1;
9330 var k3=0x8F1BBCDC;
9331 var k4=0xCA62C1D6;
9333 var h0=0x67452301;
9334 var h1=0xEFCDAB89;
9335 var h2=0x98BADCFE;
9336 var h3=0x10325476;
9337 var h4=0xC3D2E1F0;
9339 for(var i=0;i<x.length;i+=16) {
9340 var j=0;
9341 var t;
9342 var a=h0;
9343 var b=h1;
9344 var c=h2;
9345 var d=h3;
9346 var e=h4;
9347 while(j<16) {
9348 w[j]=x[i+j];
9349 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9350 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9352 while(j<20) {
9353 w[j]=RR(w,j);
9354 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9355 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9357 while(j<40) {
9358 w[j]=RR(w,j);
9359 t=AA(e,a,b^c^d,w[j],k2);
9360 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9362 while(j<60) {
9363 w[j]=RR(w,j);
9364 t=AA(e,a,(b&c)|(d&(b|c)),w[j],k3);
9365 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9367 while(j<80) {
9368 w[j]=RR(w,j);
9369 t=AA(e,a,b^c^d,w[j],k4);
9370 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9372 h0=add32(h0,a);
9373 h1=add32(h1,b);
9374 h2=add32(h2,c);
9375 h3=add32(h3,d);
9376 h4=add32(h4,e);
9378 return [h0,h1,h2,h3,h4];
9381 //--
9382 //-- RGB colour object
9383 //--
9385 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
9386 function RGB(r,g,b)
9388 this.r = 0;
9389 this.g = 0;
9390 this.b = 0;
9391 if(typeof r == "string") {
9392 if(r.substr(0,1) == "#") {
9393 if(r.length == 7) {
9394 this.r = parseInt(r.substr(1,2),16)/255;
9395 this.g = parseInt(r.substr(3,2),16)/255;
9396 this.b = parseInt(r.substr(5,2),16)/255;
9397 } else {
9398 this.r = parseInt(r.substr(1,1),16)/15;
9399 this.g = parseInt(r.substr(2,1),16)/15;
9400 this.b = parseInt(r.substr(3,1),16)/15;
9402 } else {
9403 var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
9404 var c = r.match(rgbPattern);
9405 if(c) {
9406 this.r = parseInt(c[1],10)/255;
9407 this.g = parseInt(c[2],10)/255;
9408 this.b = parseInt(c[3],10)/255;
9411 } else {
9412 this.r = r;
9413 this.g = g;
9414 this.b = b;
9416 return this;
9419 // Mixes this colour with another in a specified proportion
9420 // c = other colour to mix
9421 // f = 0..1 where 0 is this colour and 1 is the new colour
9422 // Returns an RGB object
9423 RGB.prototype.mix = function(c,f)
9425 return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
9428 // Return an rgb colour as a #rrggbb format hex string
9429 RGB.prototype.toString = function()
9431 return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
9432 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
9433 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
9436 //--
9437 //-- DOM utilities - many derived from www.quirksmode.org
9438 //--
9440 function drawGradient(place,horiz,locolors,hicolors)
9442 if(!hicolors)
9443 hicolors = locolors;
9444 for(var t=0; t<= 100; t+=2) {
9445 var bar = document.createElement("div");
9446 place.appendChild(bar);
9447 bar.style.position = "absolute";
9448 bar.style.left = horiz ? t + "%" : 0;
9449 bar.style.top = horiz ? 0 : t + "%";
9450 bar.style.width = horiz ? (101-t) + "%" : "100%";
9451 bar.style.height = horiz ? "100%" : (101-t) + "%";
9452 bar.style.zIndex = -1;
9453 var p = t/100*(locolors.length-1);
9454 bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)],p-Math.floor(p)).toString();
9458 function createTiddlyText(parent,text)
9460 return parent.appendChild(document.createTextNode(text));
9463 function createTiddlyCheckbox(parent,caption,checked,onChange)
9465 var cb = document.createElement("input");
9466 cb.setAttribute("type","checkbox");
9467 cb.onclick = onChange;
9468 parent.appendChild(cb);
9469 cb.checked = checked;
9470 cb.className = "chkOptionInput";
9471 if(caption)
9472 wikify(caption,parent);
9473 return cb;
9476 function createTiddlyElement(parent,element,id,className,text,attribs)
9478 var e = document.createElement(element);
9479 if(className != null)
9480 e.className = className;
9481 if(id != null)
9482 e.setAttribute("id",id);
9483 if(text != null)
9484 e.appendChild(document.createTextNode(text));
9485 if(attribs) {
9486 for(var n in attribs) {
9487 e.setAttribute(n,attribs[n]);
9490 if(parent != null)
9491 parent.appendChild(e);
9492 return e;
9495 function addEvent(obj,type,fn)
9497 if(obj.attachEvent) {
9498 obj['e'+type+fn] = fn;
9499 obj[type+fn] = function(){obj['e'+type+fn](window.event);};
9500 obj.attachEvent('on'+type,obj[type+fn]);
9501 } else {
9502 obj.addEventListener(type,fn,false);
9506 function removeEvent(obj,type,fn)
9508 if(obj.detachEvent) {
9509 obj.detachEvent('on'+type,obj[type+fn]);
9510 obj[type+fn] = null;
9511 } else {
9512 obj.removeEventListener(type,fn,false);
9516 function addClass(e,className)
9518 var currClass = e.className.split(" ");
9519 if(currClass.indexOf(className) == -1)
9520 e.className += " " + className;
9523 function removeClass(e,className)
9525 var currClass = e.className.split(" ");
9526 var i = currClass.indexOf(className);
9527 while(i != -1) {
9528 currClass.splice(i,1);
9529 i = currClass.indexOf(className);
9531 e.className = currClass.join(" ");
9534 function hasClass(e,className)
9536 if(e.className && e.className.split(" ").indexOf(className) != -1) {
9537 return true;
9539 return false;
9542 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
9543 function findRelated(e,value,name,relative)
9545 name = name || "tagName";
9546 relative = relative || "parentNode";
9547 if(name == "className") {
9548 while(e && !hasClass(e,value)) {
9549 e = e[relative];
9551 } else {
9552 while(e && e[name] != value) {
9553 e = e[relative];
9556 return e;
9559 // Resolve the target object of an event
9560 function resolveTarget(e)
9562 var obj;
9563 if(e.target)
9564 obj = e.target;
9565 else if(e.srcElement)
9566 obj = e.srcElement;
9567 if(obj.nodeType == 3) // defeat Safari bug
9568 obj = obj.parentNode;
9569 return obj;
9572 // Prevent an event from bubbling
9573 function stopEvent(e)
9575 var ev = e || window.event;
9576 ev.cancelBubble = true;
9577 if(ev.stopPropagation) ev.stopPropagation();
9578 return false;
9581 // Return the content of an element as plain text with no formatting
9582 function getPlainText(e)
9584 var text = "";
9585 if(e.innerText)
9586 text = e.innerText;
9587 else if(e.textContent)
9588 text = e.textContent;
9589 return text;
9592 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
9593 function ensureVisible(e)
9595 var posTop = findPosY(e);
9596 var posBot = posTop + e.offsetHeight;
9597 var winTop = findScrollY();
9598 var winHeight = findWindowHeight();
9599 var winBot = winTop + winHeight;
9600 if(posTop < winTop) {
9601 return posTop;
9602 } else if(posBot > winBot) {
9603 if(e.offsetHeight < winHeight)
9604 return posTop - (winHeight - e.offsetHeight);
9605 else
9606 return posTop;
9607 } else {
9608 return winTop;
9612 // Get the current width of the display window
9613 function findWindowWidth()
9615 return window.innerWidth || document.documentElement.clientWidth;
9618 // Get the current height of the display window
9619 function findWindowHeight()
9621 return window.innerHeight || document.documentElement.clientHeight;
9624 // Get the current horizontal page scroll position
9625 function findScrollX()
9627 return window.scrollX || document.documentElement.scrollLeft;
9630 // Get the current vertical page scroll position
9631 function findScrollY()
9633 return window.scrollY || document.documentElement.scrollTop;
9636 function findPosX(obj)
9638 var curleft = 0;
9639 while(obj.offsetParent) {
9640 curleft += obj.offsetLeft;
9641 obj = obj.offsetParent;
9643 return curleft;
9646 function findPosY(obj)
9648 var curtop = 0;
9649 while(obj.offsetParent) {
9650 curtop += obj.offsetTop;
9651 obj = obj.offsetParent;
9653 return curtop;
9656 // Blur a particular element
9657 function blurElement(e)
9659 if(e && e.focus && e.blur) {
9660 e.focus();
9661 e.blur();
9665 // Create a non-breaking space
9666 function insertSpacer(place)
9668 var e = document.createTextNode(String.fromCharCode(160));
9669 if(place)
9670 place.appendChild(e);
9671 return e;
9674 // Remove all children of a node
9675 function removeChildren(e)
9677 while(e && e.hasChildNodes())
9678 removeNode(e.firstChild);
9681 // Remove a node and all it's children
9682 function removeNode(e)
9684 scrubNode(e);
9685 e.parentNode.removeChild(e);
9688 // Remove any event handlers or non-primitve custom attributes
9689 function scrubNode(e)
9691 if(!config.browser.isIE)
9692 return;
9693 var att = e.attributes;
9694 if(att) {
9695 for(var t=0; t<att.length; t++) {
9696 var n = att[t].name;
9697 if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
9698 try {
9699 e[n] = null;
9700 } catch(ex) {
9705 var c = e.firstChild;
9706 while(c) {
9707 scrubNode(c);
9708 c = c.nextSibling;
9712 // Add a stylesheet, replacing any previous custom stylesheet
9713 function setStylesheet(s,id,doc)
9715 if(!id)
9716 id = "customStyleSheet";
9717 if(!doc)
9718 doc = document;
9719 var n = doc.getElementById(id);
9720 if(doc.createStyleSheet) {
9721 // Test for IE's non-standard createStyleSheet method
9722 if(n)
9723 n.parentNode.removeChild(n);
9724 // This failed without the &nbsp;
9725 doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
9726 } else {
9727 if(n) {
9728 n.replaceChild(doc.createTextNode(s),n.firstChild);
9729 } else {
9730 n = doc.createElement("style");
9731 n.type = "text/css";
9732 n.id = id;
9733 n.appendChild(doc.createTextNode(s));
9734 doc.getElementsByTagName("head")[0].appendChild(n);
9739 function removeStyleSheet(id)
9741 var e = document.getElementById(id);
9742 if(e)
9743 e.parentNode.removeChild(e);
9746 // Force the browser to do a document reflow when needed to workaround browser bugs
9747 function forceReflow()
9749 if(config.browser.isGecko) {
9750 setStylesheet("body {top:0px;margin-top:0px;}","forceReflow");
9751 setTimeout(function() {setStylesheet("","forceReflow");},1);
9755 // Replace the current selection of a textarea or text input and scroll it into view
9756 function replaceSelection(e,text)
9758 if(e.setSelectionRange) {
9759 var oldpos = e.selectionStart;
9760 var isRange = e.selectionEnd > e.selectionStart;
9761 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
9762 e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
9763 var linecount = e.value.split('\n').length;
9764 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
9765 e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
9766 } else if(document.selection) {
9767 var range = document.selection.createRange();
9768 if(range.parentElement() == e) {
9769 var isCollapsed = range.text == "";
9770 range.text = text;
9771 if(!isCollapsed) {
9772 range.moveStart('character', -text.length);
9773 range.select();
9779 // Returns the text of the given (text) node, possibly merging subsequent text nodes
9780 function getNodeText(e)
9782 var t = "";
9783 while(e && e.nodeName == "#text") {
9784 t += e.nodeValue;
9785 e = e.nextSibling;
9787 return t;
9790 // Returns true if the element e has a given ancestor element
9791 function isDescendant(e,ancestor)
9793 while(e) {
9794 if(e === ancestor)
9795 return true;
9796 e = e.parentNode;
9798 return false;
9801 //--
9802 //-- LoaderBase and SaverBase
9803 //--
9805 function LoaderBase() {}
9807 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9809 var title = this.getTitle(store,node);
9810 if(safeMode && store.isShadowTiddler(title))
9811 return;
9812 if(title) {
9813 var tiddler = store.createTiddler(title);
9814 this.internalizeTiddler(store,tiddler,title,node);
9815 tiddlers.push(tiddler);
9819 LoaderBase.prototype.loadTiddlers = function(store,nodes)
9821 var tiddlers = [];
9822 for(var t = 0; t < nodes.length; t++) {
9823 try {
9824 this.loadTiddler(store,nodes[t],tiddlers);
9825 } catch(ex) {
9826 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9829 return tiddlers;
9832 function SaverBase() {}
9834 SaverBase.prototype.externalize = function(store)
9836 var results = [];
9837 var tiddlers = store.getTiddlers("title");
9838 for(var t = 0; t < tiddlers.length; t++) {
9839 if(!tiddlers[t].doNotSave())
9840 results.push(this.externalizeTiddler(store, tiddlers[t]));
9842 return results.join("\n");
9845 //--
9846 //-- TW21Loader (inherits from LoaderBase)
9847 //--
9849 function TW21Loader() {}
9851 TW21Loader.prototype = new LoaderBase();
9853 TW21Loader.prototype.getTitle = function(store,node)
9855 var title = null;
9856 if(node.getAttribute) {
9857 title = node.getAttribute("title");
9858 if(!title)
9859 title = node.getAttribute("tiddler");
9861 if(!title && node.id) {
9862 var lenPrefix = store.idPrefix.length;
9863 if(node.id.substr(0,lenPrefix) == store.idPrefix)
9864 title = node.id.substr(lenPrefix);
9866 return title;
9869 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9871 var e = node.firstChild;
9872 var text = null;
9873 if(node.getAttribute("tiddler")) {
9874 text = getNodeText(e).unescapeLineBreaks();
9875 } else {
9876 while(e.nodeName!="PRE" && e.nodeName!="pre") {
9877 e = e.nextSibling;
9879 text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
9881 var modifier = node.getAttribute("modifier");
9882 var c = node.getAttribute("created");
9883 var m = node.getAttribute("modified");
9884 var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
9885 var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
9886 var tags = node.getAttribute("tags");
9887 var fields = {};
9888 var attrs = node.attributes;
9889 for(var i = attrs.length-1; i >= 0; i--) {
9890 var name = attrs[i].name;
9891 if(attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
9892 fields[name] = attrs[i].value.unescapeLineBreaks();
9895 tiddler.assign(title,text,modifier,modified,tags,created,fields);
9896 return tiddler;
9899 //--
9900 //-- TW21Saver (inherits from SaverBase)
9901 //--
9903 function TW21Saver() {}
9905 TW21Saver.prototype = new SaverBase();
9907 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
9909 try {
9910 var extendedAttributes = "";
9911 var usePre = config.options.chkUsePreForStorage;
9912 store.forEachField(tiddler,
9913 function(tiddler,fieldName,value) {
9914 // don't store stuff from the temp namespace
9915 if(typeof value != "string")
9916 value = "";
9917 if(!fieldName.match(/^temp\./))
9918 extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
9919 },true);
9920 var created = tiddler.created;
9921 var modified = tiddler.modified;
9922 var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
9923 attributes += (usePre && created == version.date) ? "" :' created="' + created.convertToYYYYMMDDHHMM() + '"';
9924 attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() +'"';
9925 var tags = tiddler.getTags();
9926 if(!usePre || tags)
9927 attributes += ' tags="' + tags.htmlEncode() + '"';
9928 return ('<div %0="%1"%2%3>%4</'+'div>').format([
9929 usePre ? "title" : "tiddler",
9930 tiddler.title.htmlEncode(),
9931 attributes,
9932 extendedAttributes,
9933 usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9935 } catch (ex) {
9936 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9940 //]]>
9941 </script>
9942 <script type="text/javascript">
9943 //<![CDATA[
9944 if(useJavaSaver)
9945 document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9946 //]]>
9947 </script>
9948 <!--POST-SCRIPT-START-->
9950 <!--POST-SCRIPT-END-->
9951 </body>
9952 </html>