Also made costume menu display incrementally when downloading lots of thumbs
[SauerbratenRemote.git] / P2PMud-sauerbraten / index.html
blob4b8eda2d573c548e91ea1e589688f6ea59fb67f8
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="200810150858" changecount="255">
825 <pre>!Mondo Yith
826 *Detect double nat with upnp
827 *Handle error loading a map from a peer that drops out
828 *camera rotation problem on Linux
829 !Next
830 *if this is a new plexus version, offer to toast the cache
831 *Pastry reconnect business (multiple root peers?)
832 *set CPPFLAGS -DTC in src/Makefile's call to src/enet/configure
833 *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)
834 *make updateMappingDiag() also display results for dumpents
835 *Diag panes for sauer and pastry cmds
836 *plexus profile editor in sauer (prepopulate wizard)
837 *combine swing windows
838 *only show diag tools until the first successful connect. Then make it an option.
839 *Items
840 *Monsters (phantom players)
841 *Brains
842 *responsibility allocation
843 **maybe have a doc for each world that allocates responsibilities among the peers of a world
844 **when responsibilities or peers join or leave, responsibilities shuffle around
845 **we can store responsibility allocation in PAST
846 **might need a world-ping
847 *Create, destroy, and move objects (GUIDs)
848 !Future
850 **Save hook defined in map.groovy
851 **Save menu choice (which calls map's save hook)
852 *World Building
853 **clean up obsolete maps
854 *map/costume export
855 *Simple game
856 **use crosshairs for aiming when they are visible
857 **register sauer cmds for hit effects based on shooter and/or target
858 **show bio when you shoot someone with an examination gun
859 *Tomb files!
860 *Authentication
861 **Groups
862 **Directory entry ownership by digital signatures (peers and groups) -- only an owner can change it
863 *Vehicles
864 **BIG model
865 **specify box with attachment point, vector, and height
866 **specify driving position with attachment point
867 **driver can switch between player view and vehicle view
868 **physics interacts with box
869 **players can move around inside, fire grenades, etc.
870 *groovy libraries in the cloud
871 !Done
872 *DONE Update costume list as thumbs arrive (don't wait for all of them before updating)
873 *DONE constumes getting reset to null (mr fixit) -- seems connected to costume uploading
874 *DONE when costume becomes null, sometimes it doesn't change it
875 *DONE fix case for md2 import
876 *DONE check response values to verify mappings succeed, remove upnp mappings during shutdown, remove cleanup call (make button?)
877 *DONE limbo name not showing up
878 *DONE taunt bug
879 *DONE Map name sometimes shows up as &quot;none&quot; when you are on a map
880 *DONE Blank costume no longer switches to Mr Fixit
881 *DONE players can taunt each other
882 *DONE Filter out Thumbs.db and 0 length files
883 *DONE drag selection with left button
884 *DONE cleanup connecting to existing sauer
885 *DONE remove date from directory properties so that dirs are not mistakenly unique
886 *DONE cloud &quot;power light&quot;
887 *DONE aiming/edit selection problem
888 *DONE Hidden worlds (not advertised, but part of the cloud)
889 *DONE Finish tutorial menus
890 *DONE [replaced by map.groovy] fix bug in saveGroovyData()
891 *DONE [replaced by map.groovy &amp; mapProps] persist JSON object as groovy file in map dir for metadata map development
892 *DONE allow turning off recoil for shots
893 *DONE allow user to restart plexus from sauer
894 *DONE cache player updates for your map so follow can teleport immediately
895 **DONE restore world when you restart sauer
896 *DONE pick random port in range
897 *DONE disable Launch Sauer until connected
898 *DONE test costume uploading
899 *DONE broadcast disconnect with shutting down
900 *DONE check java version on startup
901 *DONE remote guild names not showing
902 *DONE incoming cloud change notification to user
903 *DONE show feedback for unsuccessful port back-connection during ip discovery
904 *DONE Missing player problem -- when loading map, players clear out of sauer but not out of plexus id/name tables
905 *DONE Groovy version of build script
906 *DONE JSON format for cloud properties
907 *DONE (fluke?) on find map, couldn't find map with tume id instead of map id
908 *DONE upload/download progress notification
909 *DONE notification of droppage
910 *DONE testing peer
911 *DONE Clean button in prep gui
912 *DONE options for cleanup on start/completely fresh start
913 *DONE ZLIB licensing
914 *DONE left msg uses nodename instead of player name
915 *DONE switches remote player costume before d/l completed
916 *DONE download into temp dirs and only rename when completed
917 *DONE map member count is always 0
918 *DONE don't subscribe to map until successfully downloaded
919 *DONE Deadlocking?
920 *DONE Rework existing editing menu, much doesn't apply
921 *DONE decouple camera speed from players speed
922 *DONE Make players start off in Limbo
923 *DONE World portals
924 *DONE make LMB detach camera to orbit around the toon
925 *DONE make sauer load models from plexus folder
926 *DONE Global whispers
927 *DONE Map name on load screen
928 *DONE Make push handle regular maps (make manifest that places it in a subdir)
929 *DONE MapStorage
930 *DONE Replace map should broadcast &quot;switchmap&quot; pastry cmd
931 *DONE Message dialog: showmessage title text
932 *DONE make cursors load from plexus/dist
933 *DONE separate backup directory
934 *DONE Use UPnP to discover external IP address
935 *DONE delete player on peer disconnect
936 *DONE clean up players on world connection
937 *DONE Show connection count in world menu
938 *DONE Investigate occasional sauer/groovy lag on linux
939 *DONE Headless peer on Plubble
940 *DONE include UPnP to open external port in firewall
941 *DONE add tc_respawn command to put players back at spawn points
942 *DONE don't show original menu on startup
943 *DONE Hide Gun in First Person mode
944 *DONE Disable weapon swapping, disable shooting
945 *DONE Make Limbo map come up by default
946 *DONE Rename wowmode to tcmode
947 *DONE make cursor float above HUD
948 *DONE load limbo in sauer on startup
949 *DONE Show only filename on map thumbnails when loading
950 *DONE HUD add peer count
951 *DONE launch sauer in sep thread while connecting to pastry
952 *DONE add button to reload sauer
953 *DONE move commands from groovy init into a .cfg file
954 *DONE Storage
955 **DONE Check out out of PAST disk space errors
956 **DONE Global, shared directory for cloud
957 **DONE Costumes
958 *DONE Plexus topic
959 **DONE Presence topic for total online count
960 **DONE available world listing/selection from gui
961 *DONE HUD
962 **DONE Current map
963 **DONE Map connection count
964 *DONE Plubble Peer
965 **DONE Test
967 [[Goals]]
968 [[Completed]]
969 [[Toys]]
970 !Long Term
971 *Game
972 **materials for sensors (use var to set material id) (callback set on material id)
973 **inventory mgmt (+ gui)
974 **stats (gold)
975 **shop keeper
976 **basic monster
977 **dead monsters drop loot
978 **wizard guns (create monster, move, talk, paralyze, destroy)
979 **saved map chunks</pre>
980 </div>
981 <div title="SiteSubtitle" modifier="Zot" created="200806171609" modified="200806191121" changecount="11">
982 <pre>The Killer App of the Future! //[[Reloaded]]//</pre>
983 </div>
984 <div title="SiteTitle" modifier="Zot" created="200806171608" modified="200806191146" changecount="9">
985 <pre>[img[P2PMud|docs/images/p2pmud.gif]]</pre>
986 </div>
987 <div title="StartingOut" modifier="BillBurdick" created="200807142041" modified="200809272113" changecount="12">
988 <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]]
989 *This repository is an Eclipse project, so you'll probably want Eclipse and the CDT feature for C/C++
990 Once you have the stuff, you need to:
991 *Build the project
992 *Copy/link sauer_client and scripts/autoexec.cfg into the top level of the sauerbraten directory (in the same directory as server.bat).
993 *Start the groovy command server in Eclipse (using the Cmd Server launcher)
994 *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)
995 !&lt;html&gt;&lt;span style=&quot;background: red&quot;&gt;HAVING PROBLEMS?&lt;/span&gt;&lt;/html&gt;
996 HowToReallyCleanAnEclipseProject
997 EmergencyWorspaceReconstruction</pre>
998 </div>
999 <div title="StyleSheet" modifier="BillBurdick" created="200809280501" modified="200809280525" changecount="6">
1000 <pre>/*{{{*/
1001 .cthulhu { text-decoration: none;
1002 color: #cccccc;
1003 font-family: &quot;arial&quot;;
1004 font-size: 12pt;
1005 font-weight: medium;
1006 background-color: #282619;
1007 padding-top: 3px;
1008 padding-bottom: 3px;
1010 .cmd.cthulhu { text-decoration: none;
1011 color: #FF9900;
1012 font-family: &quot;arial&quot;;
1013 font-size: 16pt;
1014 font-weight: medium;
1016 a.cthulhu:link { text-decoration: none;
1017 color: #FF9900;
1018 font-family: &quot;arial&quot;;
1019 font-size: 12pt;
1020 font-weight: heavy;
1022 a.cthulhu:visited { text-decoration: none;
1023 color: #FF9900;
1024 font-family: &quot;arial&quot;;
1025 font-size: 12pt;
1026 font-weight: heavy;
1028 /*}}}*/</pre>
1029 </div>
1030 <div title="ToDo" modifier="WRB" created="200806171610" modified="200807192045" changecount="5">
1031 <pre>[[Done]]
1033 Use Scribe
1034 fix orientation
1036 !Update
1037 Update docs to refer to new platform (Groovy/Sauerbraten) instead of old (Javascript/Mozilla)
1039 !Extensions
1040 browser plugin
1041 Drag and drop for browser images?
1043 !Moderation
1044 master player -- invisible/intangible and does not transmit coords to the server
1045 movement modes
1046 puppet -- move according to instructions (default is frozen)
1047 normal -- moves with limitations (movement blockers)
1048 movement blockers
1049 beacons: players can move as long as they remain within the radius of at least one authorized beacon
1050 This allows cell-style hand off
1051 walls: players can pass through authorized walls
1054 !Commands
1055 moveto id x y z -- sets id's movement target to location
1056 gunset id model
1057 contexthook hook -- when active, execute hook on context change for player1
1058 usecontext on/off -- set whether player1's contexthook is active
1059 monstersay command
1060 authorize id blocker
1061 unauthorize id blocker
1062 puppet id -- makes id move toward its movement target and if it's a player unyoke the controls
1063 unpuppet id -- makes id ignore its movement target and if it's a player, yoke the controls
1066 !Guns
1067 talk
1068 move -- shoot destination, then target(s)
1069 authorize -- shoot blocker, then target(s)
1070 unauthorize -- shoot blocker, then target(s)
1071 dirt
1072 blast
1073 paralyze
1074 puppet
1075 die roll
1077 !Network
1078 slaverequest req
1079 mastercommand cmd</pre>
1080 </div>
1081 <div title="Toys" modifier="BillBurdick" created="200808151458" changecount="1">
1082 <pre>launch gun -- sets velocity on opponents
1083 jet pack
1084 Acme world
1085 *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
1086 *cannon golf
1087 might be doable with only triggers and watcher code
1088 Guns and weapons
1089 roman candles/wand of fireballs
1090 physics for wall splat
1091 sensors
1092 monsters control assigned by DHT
1093 map sharing includes map.groovy file in addition to map.ogz and map.cfg
1094 Sharing with Jgit
1095 *Each world gets a branch off the initial starting directory (a dir with a readme)
1096 *Git tree objects are stored in PAST, cached in a local Git repo, and extracted into a local directory</pre>
1097 </div>
1098 <div title="UseCases" modifier="BillBurdick" created="200807300630" modified="200807300650" changecount="3">
1099 <pre>#Items
1100 ##player rolls over an item
1101 ##item adds to inventory
1102 ##player presses &quot;i&quot; key
1103 ##inventory menu pops up
1104 ##player chooses a different item from menu
1105 ##submenu pops up
1106 ##player chooses drop
1107 ##item leaves inventory and appears on map
1108 #Keys and Doors
1109 ##player approaches door
1110 ##door says &quot;locked&quot;
1111 ##player finds key and reapproaches door
1112 ##door opens
1113 #The Shop Keeper
1114 ##player enters shop
1115 ##shop keeper says &quot;welcome to the shop&quot;
1116 ##player switches to shopping gun
1117 ##items are visible on the counter
1118 ##player shoots item
1119 ##menu pops up and offers buy choice
1120 ##player selects &quot;buy&quot; and item gets added to inventory and gold is deducted
1121 ##player shoots shop keeper
1122 ##shop keeper menu pops up and offers to buy items</pre>
1123 </div>
1124 <div title="Zot" modifier="Zot" created="200806171649" changecount="1">
1125 <pre>Zot is one of the founding members of [[TEAM CTHULHU|http://teamcthulhu.com]]</pre>
1126 </div>
1127 </div>
1128 <!--POST-STOREAREA-->
1129 <!--POST-BODY-START-->
1130 <!--POST-BODY-END-->
1131 <script id="jsArea" type="text/javascript">
1132 //<![CDATA[
1134 // Please note:
1136 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
1137 // in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
1139 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
1140 // without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
1143 //--
1144 //-- Configuration repository
1145 //--
1147 // Miscellaneous options
1148 var config = {
1149 numRssItems: 20, // Number of items in the RSS feed
1150 animDuration: 400, // Duration of UI animations in milliseconds
1151 cascadeFast: 20, // Speed for cascade animations (higher == slower)
1152 cascadeSlow: 60, // Speed for EasterEgg cascade animations
1153 cascadeDepth: 5, // Depth of cascade animation
1154 locale: "en" // W3C language tag
1157 // Hashmap of alternative parsers for the wikifier
1158 config.parsers = {};
1160 // Adaptors
1161 config.adaptors = {};
1162 config.defaultAdaptor = null;
1164 // Backstage tasks
1165 config.tasks = {};
1167 // Annotations
1168 config.annotations = {};
1170 // Custom fields to be automatically added to new tiddlers
1171 config.defaultCustomFields = {};
1173 // Messages
1174 config.messages = {
1175 messageClose: {},
1176 dates: {},
1177 tiddlerPopup: {}
1180 // Options that can be set in the options panel and/or cookies
1181 config.options = {
1182 chkRegExpSearch: false,
1183 chkCaseSensitiveSearch: false,
1184 chkIncrementalSearch: true,
1185 chkAnimate: true,
1186 chkSaveBackups: true,
1187 chkAutoSave: false,
1188 chkGenerateAnRssFeed: false,
1189 chkSaveEmptyTemplate: false,
1190 chkOpenInNewWindow: true,
1191 chkToggleLinks: false,
1192 chkHttpReadOnly: true,
1193 chkForceMinorUpdate: false,
1194 chkConfirmDelete: true,
1195 chkInsertTabs: false,
1196 chkUsePreForStorage: true, // Whether to use <pre> format for storage
1197 chkDisplayInstrumentation: false,
1198 txtBackupFolder: "",
1199 txtEditorFocus: "text",
1200 txtMainTab: "tabTimeline",
1201 txtMoreTab: "moreTabAll",
1202 txtMaxEditRows: "30",
1203 txtFileSystemCharSet: "UTF-8",
1204 txtTheme: ""
1206 config.optionsDesc = {};
1208 // Default tiddler templates
1209 var DEFAULT_VIEW_TEMPLATE = 1;
1210 var DEFAULT_EDIT_TEMPLATE = 2;
1211 config.tiddlerTemplates = {
1212 1: "ViewTemplate",
1213 2: "EditTemplate"
1216 // More messages (rather a legacy layout that should not really be like this)
1217 config.views = {
1218 wikified: {
1219 tag: {}
1221 editor: {
1222 tagChooser: {}
1226 // Backstage tasks
1227 config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];
1229 // Macros; each has a 'handler' member that is inserted later
1230 config.macros = {
1231 today: {},
1232 version: {},
1233 search: {sizeTextbox: 15},
1234 tiddler: {},
1235 tag: {},
1236 tags: {},
1237 tagging: {},
1238 timeline: {},
1239 allTags: {},
1240 list: {
1241 all: {},
1242 missing: {},
1243 orphans: {},
1244 shadowed: {},
1245 touched: {},
1246 filter: {}
1248 closeAll: {},
1249 permaview: {},
1250 saveChanges: {},
1251 slider: {},
1252 option: {},
1253 options: {},
1254 newTiddler: {},
1255 newJournal: {},
1256 tabs: {},
1257 gradient: {},
1258 message: {},
1259 view: {defaultView: "text"},
1260 edit: {},
1261 tagChooser: {},
1262 toolbar: {},
1263 plugins: {},
1264 refreshDisplay: {},
1265 importTiddlers: {},
1266 upgrade: {
1267 source: "http://www.tiddlywiki.com/upgrade/",
1268 backupExtension: "pre.core.upgrade"
1270 sync: {},
1271 annotations: {}
1274 // Commands supported by the toolbar macro
1275 config.commands = {
1276 closeTiddler: {},
1277 closeOthers: {},
1278 editTiddler: {},
1279 saveTiddler: {hideReadOnly: true},
1280 cancelTiddler: {},
1281 deleteTiddler: {hideReadOnly: true},
1282 permalink: {},
1283 references: {type: "popup"},
1284 jump: {type: "popup"},
1285 syncing: {type: "popup"},
1286 fields: {type: "popup"}
1289 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
1290 config.userAgent = navigator.userAgent.toLowerCase();
1291 config.browser = {
1292 isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
1293 isGecko: config.userAgent.indexOf("gecko") != -1,
1294 ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
1295 isSafari: config.userAgent.indexOf("applewebkit") != -1,
1296 isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
1297 firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
1298 isOpera: config.userAgent.indexOf("opera") != -1,
1299 isLinux: config.userAgent.indexOf("linux") != -1,
1300 isUnix: config.userAgent.indexOf("x11") != -1,
1301 isMac: config.userAgent.indexOf("mac") != -1,
1302 isWindows: config.userAgent.indexOf("win") != -1
1305 // Basic regular expressions
1306 config.textPrimitives = {
1307 upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
1308 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
1309 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
1310 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
1312 if(config.browser.isBadSafari) {
1313 config.textPrimitives = {
1314 upperLetter: "[A-Z\u00c0-\u00de]",
1315 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
1316 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
1317 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
1320 config.textPrimitives.sliceSeparator = "::";
1321 config.textPrimitives.sectionSeparator = "##";
1322 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
1323 config.textPrimitives.unWikiLink = "~";
1324 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
1325 config.textPrimitives.lowerLetter + "+" +
1326 config.textPrimitives.upperLetter +
1327 config.textPrimitives.anyLetter + "*)|(?:" +
1328 config.textPrimitives.upperLetter + "{2,}" +
1329 config.textPrimitives.lowerLetter + "+))";
1331 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
1332 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
1334 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
1335 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
1336 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
1337 config.textPrimitives.brackettedLink + ")|(?:" +
1338 config.textPrimitives.urlPattern + ")","mg");
1339 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
1340 config.textPrimitives.titledBrackettedLink + ")|(?:" +
1341 config.textPrimitives.brackettedLink + ")|(?:" +
1342 config.textPrimitives.urlPattern + ")","mg");
1344 config.glyphs = {
1345 browsers: [
1346 function() {return config.browser.isIE;},
1347 function() {return true;}
1349 currBrowser: null,
1350 codes: {
1351 downTriangle: ["\u25BC","\u25BE"],
1352 downArrow: ["\u2193","\u2193"],
1353 bentArrowLeft: ["\u2190","\u21A9"],
1354 bentArrowRight: ["\u2192","\u21AA"]
1358 //--
1359 //-- Shadow tiddlers
1360 //--
1362 config.shadowTiddlers = {
1363 StyleSheet: "",
1364 MarkupPreHead: "",
1365 MarkupPostHead: "",
1366 MarkupPreBody: "",
1367 MarkupPostBody: "",
1368 TabTimeline: '<<timeline>>',
1369 TabAll: '<<list all>>',
1370 TabTags: '<<allTags excludeLists>>',
1371 TabMoreMissing: '<<list missing>>',
1372 TabMoreOrphans: '<<list orphans>>',
1373 TabMoreShadowed: '<<list shadowed>>',
1374 AdvancedOptions: '<<options>>',
1375 PluginManager: '<<plugins>>',
1376 ToolbarCommands: '|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|'
1379 //--
1380 //-- Translateable strings
1381 //--
1383 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
1385 merge(config.options,{
1386 txtUserName: "YourName"});
1388 merge(config.tasks,{
1389 save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
1390 sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
1391 importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
1392 tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
1393 upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
1394 plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
1397 // Options that can be set in the options panel and/or cookies
1398 merge(config.optionsDesc,{
1399 txtUserName: "Username for signing your edits",
1400 chkRegExpSearch: "Enable regular expressions for searches",
1401 chkCaseSensitiveSearch: "Case-sensitive searching",
1402 chkIncrementalSearch: "Incremental key-by-key searching",
1403 chkAnimate: "Enable animations",
1404 chkSaveBackups: "Keep backup file when saving changes",
1405 chkAutoSave: "Automatically save changes",
1406 chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
1407 chkSaveEmptyTemplate: "Generate an empty template when saving changes",
1408 chkOpenInNewWindow: "Open external links in a new window",
1409 chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
1410 chkHttpReadOnly: "Hide editing features when viewed over HTTP",
1411 chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
1412 chkConfirmDelete: "Require confirmation before deleting tiddlers",
1413 chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
1414 txtBackupFolder: "Name of folder to use for backups",
1415 txtMaxEditRows: "Maximum number of rows in edit boxes",
1416 txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
1418 merge(config.messages,{
1419 customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
1420 pluginError: "Error: %0",
1421 pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
1422 pluginForced: "Executed because forced via 'systemConfigForce' tag",
1423 pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
1424 nothingSelected: "Nothing is selected. You must select one or more items first",
1425 savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
1426 subtitleUnknown: "(unknown)",
1427 undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
1428 shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
1429 tiddlerLinkTooltip: "%0 - %1, %2",
1430 externalLinkTooltip: "External link to %0",
1431 noTags: "There are no tagged tiddlers",
1432 notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
1433 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",
1434 invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
1435 backupSaved: "Backup saved",
1436 backupFailed: "Failed to save backup file",
1437 rssSaved: "RSS feed saved",
1438 rssFailed: "Failed to save RSS feed file",
1439 emptySaved: "Empty template saved",
1440 emptyFailed: "Failed to save empty template file",
1441 mainSaved: "Main TiddlyWiki file saved",
1442 mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
1443 macroError: "Error in macro <<\%0>>",
1444 macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
1445 missingMacro: "No such macro",
1446 overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
1447 unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
1448 confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
1449 saveInstructions: "SaveChanges",
1450 unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
1451 tiddlerSaveError: "Error when saving tiddler '%0'",
1452 tiddlerLoadError: "Error when loading tiddler '%0'",
1453 wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
1454 invalidFieldName: "Invalid field name %0",
1455 fieldCannotBeChanged: "Field '%0' cannot be changed",
1456 loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
1457 upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});
1459 merge(config.messages.messageClose,{
1460 text: "close",
1461 tooltip: "close this message area"});
1463 config.messages.backstage = {
1464 open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
1465 close: {text: "close", tooltip: "Close the backstage area"},
1466 prompt: "backstage: ",
1467 decal: {
1468 edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
1472 config.messages.listView = {
1473 tiddlerTooltip: "Click for the full text of this tiddler",
1474 previewUnavailable: "(preview not available)"
1477 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
1478 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1479 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1480 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1481 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
1482 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
1483 "th","th","th","th","th","th","th","th","th","th",
1484 "st","nd","rd","th","th","th","th","th","th","th",
1485 "st"];
1486 config.messages.dates.am = "am";
1487 config.messages.dates.pm = "pm";
1489 merge(config.messages.tiddlerPopup,{
1492 merge(config.views.wikified.tag,{
1493 labelNoTags: "no tags",
1494 labelTags: "tags: ",
1495 openTag: "Open tag '%0'",
1496 tooltip: "Show tiddlers tagged with '%0'",
1497 openAllText: "Open all",
1498 openAllTooltip: "Open all of these tiddlers",
1499 popupNone: "No other tiddlers tagged with '%0'"});
1501 merge(config.views.wikified,{
1502 defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
1503 defaultModifier: "(missing)",
1504 shadowModifier: "(built-in shadow tiddler)",
1505 dateFormat: "DD MMM YYYY",
1506 createdPrompt: "created"});
1508 merge(config.views.editor,{
1509 tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
1510 defaultText: "Type the text for '%0'"});
1512 merge(config.views.editor.tagChooser,{
1513 text: "tags",
1514 tooltip: "Choose existing tags to add to this tiddler",
1515 popupNone: "There are no tags defined",
1516 tagTooltip: "Add the tag '%0'"});
1518 merge(config.messages,{
1519 sizeTemplates:
1521 {unit: 1024*1024*1024, template: "%0\u00a0GB"},
1522 {unit: 1024*1024, template: "%0\u00a0MB"},
1523 {unit: 1024, template: "%0\u00a0KB"},
1524 {unit: 1, template: "%0\u00a0B"}
1525 ]});
1527 merge(config.macros.search,{
1528 label: "search",
1529 prompt: "Search this TiddlyWiki",
1530 accessKey: "F",
1531 successMsg: "%0 tiddlers found matching %1",
1532 failureMsg: "No tiddlers found matching %0"});
1534 merge(config.macros.tagging,{
1535 label: "tagging: ",
1536 labelNotTag: "not tagging",
1537 tooltip: "List of tiddlers tagged with '%0'"});
1539 merge(config.macros.timeline,{
1540 dateFormat: "DD MMM YYYY"});
1542 merge(config.macros.allTags,{
1543 tooltip: "Show tiddlers tagged with '%0'",
1544 noTags: "There are no tagged tiddlers"});
1546 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
1547 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
1548 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
1549 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
1550 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
1552 merge(config.macros.closeAll,{
1553 label: "close all",
1554 prompt: "Close all displayed tiddlers (except any that are being edited)"});
1556 merge(config.macros.permaview,{
1557 label: "permaview",
1558 prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
1560 merge(config.macros.saveChanges,{
1561 label: "save changes",
1562 prompt: "Save all tiddlers to create a new TiddlyWiki",
1563 accessKey: "S"});
1565 merge(config.macros.newTiddler,{
1566 label: "new tiddler",
1567 prompt: "Create a new tiddler",
1568 title: "New Tiddler",
1569 accessKey: "N"});
1571 merge(config.macros.newJournal,{
1572 label: "new journal",
1573 prompt: "Create a new tiddler from the current date and time",
1574 accessKey: "J"});
1576 merge(config.macros.options,{
1577 wizardTitle: "Tweak advanced options",
1578 step1Title: "These options are saved in cookies in your browser",
1579 step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
1580 unknownDescription: "//(unknown)//",
1581 listViewTemplate: {
1582 columns: [
1583 {name: 'Option', field: 'option', title: "Option", type: 'String'},
1584 {name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
1585 {name: 'Name', field: 'name', title: "Name", type: 'String'}
1587 rowClasses: [
1588 {className: 'lowlight', field: 'lowlight'}
1592 merge(config.macros.plugins,{
1593 wizardTitle: "Manage plugins",
1594 step1Title: "Currently loaded plugins",
1595 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1596 skippedText: "(This plugin has not been executed because it was added since startup)",
1597 noPluginText: "There are no plugins installed",
1598 confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
1599 removeLabel: "remove systemConfig tag",
1600 removePrompt: "Remove systemConfig tag",
1601 deleteLabel: "delete",
1602 deletePrompt: "Delete these tiddlers forever",
1603 listViewTemplate: {
1604 columns: [
1605 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1606 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1607 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1608 {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
1609 {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
1610 {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
1611 {name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
1612 {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
1613 {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
1615 rowClasses: [
1616 {className: 'error', field: 'error'},
1617 {className: 'warning', field: 'warning'}
1621 merge(config.macros.toolbar,{
1622 moreLabel: "more",
1623 morePrompt: "Reveal further commands"
1626 merge(config.macros.refreshDisplay,{
1627 label: "refresh",
1628 prompt: "Redraw the entire TiddlyWiki display"
1631 merge(config.macros.importTiddlers,{
1632 readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
1633 wizardTitle: "Import tiddlers from another file or server",
1634 step1Title: "Step 1: Locate the server or TiddlyWiki file",
1635 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>",
1636 openLabel: "open",
1637 openPrompt: "Open the connection to this file or server",
1638 openError: "There were problems fetching the tiddlywiki file",
1639 statusOpenHost: "Opening the host",
1640 statusGetWorkspaceList: "Getting the list of available workspaces",
1641 step2Title: "Step 2: Choose the workspace",
1642 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>",
1643 cancelLabel: "cancel",
1644 cancelPrompt: "Cancel this import",
1645 statusOpenWorkspace: "Opening the workspace",
1646 statusGetTiddlerList: "Getting the list of available tiddlers",
1647 errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
1648 step3Title: "Step 3: Choose the tiddlers to import",
1649 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'>",
1650 importLabel: "import",
1651 importPrompt: "Import these tiddlers",
1652 confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
1653 step4Title: "Step 4: Importing %0 tiddler(s)",
1654 step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
1655 doneLabel: "done",
1656 donePrompt: "Close this wizard",
1657 statusDoingImport: "Importing tiddlers",
1658 statusDoneImport: "All tiddlers imported",
1659 systemServerNamePattern: "%2 on %1",
1660 systemServerNamePatternNoWorkspace: "%1",
1661 confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
1662 serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
1663 serverSaveModifier: "(System)",
1664 listViewTemplate: {
1665 columns: [
1666 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1667 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1668 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1669 {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
1671 rowClasses: [
1675 merge(config.macros.upgrade,{
1676 wizardTitle: "Upgrade TiddlyWiki core code",
1677 step1Title: "Update or repair this TiddlyWiki to the latest release",
1678 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>",
1679 errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
1680 errorNotSaved: "You must save changes before you can perform an upgrade",
1681 step2Title: "Confirm the upgrade details",
1682 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",
1683 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",
1684 step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
1685 upgradeLabel: "upgrade",
1686 upgradePrompt: "Prepare for the upgrade process",
1687 statusPreparingBackup: "Preparing backup",
1688 statusSavingBackup: "Saving backup file",
1689 errorSavingBackup: "There was a problem saving the backup file",
1690 statusLoadingCore: "Loading core code",
1691 errorLoadingCore: "Error loading the core code",
1692 errorCoreFormat: "Error with the new core code",
1693 statusSavingCore: "Saving the new core code",
1694 statusReloadingCore: "Reloading the new core code",
1695 startLabel: "start",
1696 startPrompt: "Start the upgrade process",
1697 cancelLabel: "cancel",
1698 cancelPrompt: "Cancel the upgrade process",
1699 step3Title: "Upgrade cancelled",
1700 step3Html: "You have cancelled the upgrade process"
1703 merge(config.macros.sync,{
1704 listViewTemplate: {
1705 columns: [
1706 {name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
1707 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1708 {name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
1709 {name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
1710 {name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
1711 {name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
1712 {name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
1714 rowClasses: [
1716 buttons: [
1717 {caption: "Sync these tiddlers", name: 'sync'}
1719 wizardTitle: "Synchronize with external servers and files",
1720 step1Title: "Choose the tiddlers you want to synchronize",
1721 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1722 syncLabel: "sync",
1723 syncPrompt: "Sync these tiddlers",
1724 hasChanged: "Changed while unplugged",
1725 hasNotChanged: "Unchanged while unplugged",
1726 syncStatusList: {
1727 none: {text: "...", display:null, className:'notChanged'},
1728 changedServer: {text: "Changed on server", display:null, className:'changedServer'},
1729 changedLocally: {text: "Changed while unplugged", display:null, className:'changedLocally'},
1730 changedBoth: {text: "Changed while unplugged and on server", display:null, className:'changedBoth'},
1731 notFound: {text: "Not found on server", display:null, className:'notFound'},
1732 putToServer: {text: "Saved update on server", display:null, className:'putToServer'},
1733 gotFromServer: {text: "Retrieved update from server", display:null, className:'gotFromServer'}
1737 merge(config.macros.annotations,{
1740 merge(config.commands.closeTiddler,{
1741 text: "close",
1742 tooltip: "Close this tiddler"});
1744 merge(config.commands.closeOthers,{
1745 text: "close others",
1746 tooltip: "Close all other tiddlers"});
1748 merge(config.commands.editTiddler,{
1749 text: "edit",
1750 tooltip: "Edit this tiddler",
1751 readOnlyText: "view",
1752 readOnlyTooltip: "View the source of this tiddler"});
1754 merge(config.commands.saveTiddler,{
1755 text: "done",
1756 tooltip: "Save changes to this tiddler"});
1758 merge(config.commands.cancelTiddler,{
1759 text: "cancel",
1760 tooltip: "Undo changes to this tiddler",
1761 warning: "Are you sure you want to abandon your changes to '%0'?",
1762 readOnlyText: "done",
1763 readOnlyTooltip: "View this tiddler normally"});
1765 merge(config.commands.deleteTiddler,{
1766 text: "delete",
1767 tooltip: "Delete this tiddler",
1768 warning: "Are you sure you want to delete '%0'?"});
1770 merge(config.commands.permalink,{
1771 text: "permalink",
1772 tooltip: "Permalink for this tiddler"});
1774 merge(config.commands.references,{
1775 text: "references",
1776 tooltip: "Show tiddlers that link to this one",
1777 popupNone: "No references"});
1779 merge(config.commands.jump,{
1780 text: "jump",
1781 tooltip: "Jump to another open tiddler"});
1783 merge(config.commands.syncing,{
1784 text: "syncing",
1785 tooltip: "Control synchronisation of this tiddler with a server or external file",
1786 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
1787 notCurrentlySyncing: "Not currently syncing",
1788 captionUnSync: "Stop synchronising this tiddler",
1789 chooseServer: "Synchronise this tiddler with another server:",
1790 currServerMarker: "\u25cf ",
1791 notCurrServerMarker: " "});
1793 merge(config.commands.fields,{
1794 text: "fields",
1795 tooltip: "Show the extended fields of this tiddler",
1796 emptyText: "There are no extended fields for this tiddler",
1797 listViewTemplate: {
1798 columns: [
1799 {name: 'Field', field: 'field', title: "Field", type: 'String'},
1800 {name: 'Value', field: 'value', title: "Value", type: 'String'}
1802 rowClasses: [
1804 buttons: [
1805 ]}});
1807 merge(config.shadowTiddlers,{
1808 DefaultTiddlers: "[[GettingStarted]]",
1809 MainMenu: "[[GettingStarted]]",
1810 SiteTitle: "My TiddlyWiki",
1811 SiteSubtitle: "a reusable non-linear personal web notebook",
1812 SiteUrl: "http://www.tiddlywiki.com/",
1813 SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
1814 SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
1815 TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'
1818 merge(config.annotations,{
1819 AdvancedOptions: "This shadow tiddler provides access to several advanced options",
1820 ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
1821 DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
1822 EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
1823 GettingStarted: "This shadow tiddler provides basic usage instructions",
1824 ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
1825 MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
1826 MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
1827 MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
1828 MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
1829 MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
1830 OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
1831 PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
1832 PluginManager: "This shadow tiddler provides access to the plugin manager",
1833 SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
1834 SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
1835 SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
1836 SiteTitle: "This shadow tiddler is used as the first part of the page title",
1837 SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
1838 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",
1839 StyleSheet: "This tiddler can contain custom CSS definitions",
1840 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",
1841 StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
1842 StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
1843 TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
1844 TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
1845 TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
1846 TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
1847 TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
1848 TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
1849 TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
1850 ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
1851 ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
1854 //--
1855 //-- Main
1856 //--
1858 var params = null; // Command line parameters
1859 var store = null; // TiddlyWiki storage
1860 var story = null; // Main story
1861 var formatter = null; // Default formatters for the wikifier
1862 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
1863 var readOnly = false; // Whether we're in readonly mode
1864 var highlightHack = null; // Embarrassing hack department...
1865 var hadConfirmExit = false; // Don't warn more than once
1866 var safeMode = false; // Disable all plugins and cookies
1867 var showBackstage; // Whether to include the backstage area
1868 var installedPlugins = []; // Information filled in when plugins are executed
1869 var startingUp = false; // Whether we're in the process of starting up
1870 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
1872 // Whether to use the JavaSaver applet
1873 var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0,4) != "http");
1875 // Starting up
1876 function main()
1878 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1879 startingUp = true;
1880 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1881 params = getParameters();
1882 if(params)
1883 params = params.parseParams("open",null,false);
1884 store = new TiddlyWiki();
1885 invokeParamifier(params,"oninit");
1886 story = new Story("tiddlerDisplay","tiddler");
1887 addEvent(document,"click",Popup.onDocumentClick);
1888 saveTest();
1889 loadOptionsCookie();
1890 for(var s=0; s<config.notifyTiddlers.length; s++)
1891 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1892 t1 = new Date();
1893 loadShadowTiddlers();
1894 t2 = new Date();
1895 store.loadFromDiv("storeArea","store",true);
1896 t3 = new Date();
1897 invokeParamifier(params,"onload");
1898 t4 = new Date();
1899 readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
1900 var pluginProblem = loadPlugins();
1901 t5 = new Date();
1902 formatter = new Formatter(config.formatters);
1903 invokeParamifier(params,"onconfig");
1904 story.switchTheme(config.options.txtTheme);
1905 showBackstage = !readOnly;
1906 t6 = new Date();
1907 store.notifyAll();
1908 t7 = new Date();
1909 restart();
1910 refreshDisplay();
1911 t8 = new Date();
1912 if(pluginProblem) {
1913 story.displayTiddler(null,"PluginManager");
1914 displayMessage(config.messages.customConfigError);
1916 for(var m in config.macros) {
1917 if(config.macros[m].init)
1918 config.macros[m].init();
1920 t9 = new Date();
1921 if(showBackstage)
1922 backstage.init();
1923 t10 = new Date();
1924 if(config.options.chkDisplayInstrumentation) {
1925 displayMessage("LoadShadows " + (t2-t1) + " ms");
1926 displayMessage("LoadFromDiv " + (t3-t2) + " ms");
1927 displayMessage("LoadPlugins " + (t5-t4) + " ms");
1928 displayMessage("Notify " + (t7-t6) + " ms");
1929 displayMessage("Restart " + (t8-t7) + " ms");
1930 displayMessage("Macro init " + (t9-t8) + " ms");
1931 displayMessage("Total: " + (t10-t0) + " ms");
1933 startingUp = false;
1936 // Restarting
1937 function restart()
1939 invokeParamifier(params,"onstart");
1940 if(story.isEmpty()) {
1941 story.displayDefaultTiddlers();
1943 window.scrollTo(0,0);
1946 function saveTest()
1948 var s = document.getElementById("saveTest");
1949 if(s.hasChildNodes())
1950 alert(config.messages.savedSnapshotError);
1951 s.appendChild(document.createTextNode("savetest"));
1954 function loadShadowTiddlers()
1956 var shadows = new TiddlyWiki();
1957 shadows.loadFromDiv("shadowArea","shadows",true);
1958 shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
1959 delete shadows;
1962 function loadPlugins()
1964 if(safeMode)
1965 return false;
1966 var tiddlers = store.getTaggedTiddlers("systemConfig");
1967 var toLoad = [];
1968 var nLoaded = 0;
1969 var map = {};
1970 var nPlugins = tiddlers.length;
1971 installedPlugins = [];
1972 for(var i=0; i<nPlugins; i++) {
1973 var p = getPluginInfo(tiddlers[i]);
1974 installedPlugins[i] = p;
1975 var n = p.Name;
1976 if(n)
1977 map[n] = p;
1978 n = p.Source;
1979 if(n)
1980 map[n] = p;
1982 var visit = function(p) {
1983 if(!p || p.done)
1984 return;
1985 p.done = 1;
1986 var reqs = p.Requires;
1987 if(reqs) {
1988 reqs = reqs.readBracketedList();
1989 for(var i=0; i<reqs.length; i++)
1990 visit(map[reqs[i]]);
1992 toLoad.push(p);
1994 for(i=0; i<nPlugins; i++)
1995 visit(installedPlugins[i]);
1996 for(i=0; i<toLoad.length; i++) {
1997 p = toLoad[i];
1998 pluginInfo = p;
1999 tiddler = p.tiddler;
2000 if(isPluginExecutable(p)) {
2001 if(isPluginEnabled(p)) {
2002 p.executed = true;
2003 var startTime = new Date();
2004 try {
2005 if(tiddler.text)
2006 window.eval(tiddler.text);
2007 nLoaded++;
2008 } catch(ex) {
2009 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
2010 p.error = true;
2012 pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
2013 } else {
2014 nPlugins--;
2016 } else {
2017 p.warning = true;
2020 return nLoaded != nPlugins;
2023 function getPluginInfo(tiddler)
2025 var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
2026 p.tiddler = tiddler;
2027 p.title = tiddler.title;
2028 p.log = [];
2029 return p;
2032 // Check that a particular plugin is valid for execution
2033 function isPluginExecutable(plugin)
2035 if(plugin.tiddler.isTagged("systemConfigForce")) {
2036 plugin.log.push(config.messages.pluginForced);
2037 return true;
2039 if(plugin["CoreVersion"]) {
2040 var coreVersion = plugin["CoreVersion"].split(".");
2041 var w = parseInt(coreVersion[0],10) - version.major;
2042 if(w == 0 && coreVersion[1])
2043 w = parseInt(coreVersion[1],10) - version.minor;
2044 if(w == 0 && coreVersion[2])
2045 w = parseInt(coreVersion[2],10) - version.revision;
2046 if(w > 0) {
2047 plugin.log.push(config.messages.pluginVersionError);
2048 return false;
2051 return true;
2054 function isPluginEnabled(plugin)
2056 if(plugin.tiddler.isTagged("systemConfigDisable")) {
2057 plugin.log.push(config.messages.pluginDisabled);
2058 return false;
2060 return true;
2063 function invokeMacro(place,macro,params,wikifier,tiddler)
2065 try {
2066 var m = config.macros[macro];
2067 if(m && m.handler)
2068 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
2069 else
2070 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
2071 } catch(ex) {
2072 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
2076 //--
2077 //-- Paramifiers
2078 //--
2080 function getParameters()
2082 var p = null;
2083 if(window.location.hash) {
2084 p = decodeURIComponent(window.location.hash.substr(1));
2085 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
2086 p = convertUTF8ToUnicode(p);
2088 return p;
2091 function invokeParamifier(params,handler)
2093 if(!params || params.length == undefined || params.length <= 1)
2094 return;
2095 for(var t=1; t<params.length; t++) {
2096 var p = config.paramifiers[params[t].name];
2097 if(p && p[handler] instanceof Function)
2098 p[handler](params[t].value);
2102 config.paramifiers = {};
2104 config.paramifiers.start = {
2105 oninit: function(v) {
2106 safeMode = v.toLowerCase() == "safe";
2110 config.paramifiers.open = {
2111 onstart: function(v) {
2112 if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
2113 story.displayTiddler("bottom",v,null,false,null);
2117 config.paramifiers.story = {
2118 onstart: function(v) {
2119 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
2120 invokeParamifier(list,"onstart");
2124 config.paramifiers.search = {
2125 onstart: function(v) {
2126 story.search(v,false,false);
2130 config.paramifiers.searchRegExp = {
2131 onstart: function(v) {
2132 story.prototype.search(v,false,true);
2136 config.paramifiers.tag = {
2137 onstart: function(v) {
2138 story.displayTiddlers(null,store.filterTiddlers("[tag["+v+"]]"),null,false,null);
2142 config.paramifiers.newTiddler = {
2143 onstart: function(v) {
2144 if(!readOnly) {
2145 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
2146 story.focusTiddler(v,"text");
2151 config.paramifiers.newJournal = {
2152 onstart: function(v) {
2153 if(!readOnly) {
2154 var now = new Date();
2155 var title = now.formatString(v.trim());
2156 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
2157 story.focusTiddler(title,"text");
2162 config.paramifiers.readOnly = {
2163 onconfig: function(v) {
2164 var p = v.toLowerCase();
2165 readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
2169 config.paramifiers.theme = {
2170 onconfig: function(v) {
2171 story.switchTheme(v);
2175 config.paramifiers.upgrade = {
2176 onstart: function(v) {
2177 upgradeFrom(v);
2181 config.paramifiers.recent= {
2182 onstart: function(v) {
2183 var titles=[];
2184 var tiddlers=store.getTiddlers("modified","excludeLists").reverse();
2185 for(var i=0; i<v && i<tiddlers.length; i++)
2186 titles.push(tiddlers[i].title);
2187 story.displayTiddlers(null,titles);
2191 config.paramifiers.filter = {
2192 onstart: function(v) {
2193 story.displayTiddlers(null,store.filterTiddlers(v),null,false);
2197 //--
2198 //-- Formatter helpers
2199 //--
2201 function Formatter(formatters)
2203 this.formatters = [];
2204 var pattern = [];
2205 for(var n=0; n<formatters.length; n++) {
2206 pattern.push("(" + formatters[n].match + ")");
2207 this.formatters.push(formatters[n]);
2209 this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
2212 config.formatterHelpers = {
2214 createElementAndWikify: function(w)
2216 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
2219 inlineCssHelper: function(w)
2221 var styles = [];
2222 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2223 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2224 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2225 var s,v;
2226 if(lookaheadMatch[1]) {
2227 s = lookaheadMatch[1].unDash();
2228 v = lookaheadMatch[2];
2229 } else {
2230 s = lookaheadMatch[3].unDash();
2231 v = lookaheadMatch[4];
2233 if(s=="bgcolor")
2234 s = "backgroundColor";
2235 styles.push({style: s, value: v});
2236 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2237 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2238 lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2240 return styles;
2243 applyCssHelper: function(e,styles)
2245 for(var t=0; t< styles.length; t++) {
2246 try {
2247 e.style[styles[t].style] = styles[t].value;
2248 } catch (ex) {
2253 enclosedTextHelper: function(w)
2255 this.lookaheadRegExp.lastIndex = w.matchStart;
2256 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2257 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2258 var text = lookaheadMatch[1];
2259 if(config.browser.isIE)
2260 text = text.replace(/\n/g,"\r");
2261 createTiddlyElement(w.output,this.element,null,null,text);
2262 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2266 isExternalLink: function(link)
2268 if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
2269 return false;
2271 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
2272 if(urlRegExp.exec(link)) {
2273 return true;
2275 if(link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
2276 return true;
2278 return false;
2283 //--
2284 //-- Standard formatters
2285 //--
2287 config.formatters = [
2289 name: "table",
2290 match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
2291 lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
2292 rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
2293 cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
2294 cellTermRegExp: /((?:\x20*)\|)/mg,
2295 rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
2296 handler: function(w)
2298 var table = createTiddlyElement(w.output,"table",null,"twtable");
2299 var prevColumns = [];
2300 var currRowType = null;
2301 var rowContainer;
2302 var rowCount = 0;
2303 w.nextMatch = w.matchStart;
2304 this.lookaheadRegExp.lastIndex = w.nextMatch;
2305 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2306 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2307 var nextRowType = lookaheadMatch[2];
2308 if(nextRowType == "k") {
2309 table.className = lookaheadMatch[1];
2310 w.nextMatch += lookaheadMatch[0].length+1;
2311 } else {
2312 if(nextRowType != currRowType) {
2313 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2314 currRowType = nextRowType;
2316 if(currRowType == "c") {
2317 // Caption
2318 w.nextMatch++;
2319 if(rowContainer != table.firstChild)
2320 table.insertBefore(rowContainer,table.firstChild);
2321 rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
2322 w.subWikifyTerm(rowContainer,this.rowTermRegExp);
2323 } else {
2324 var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
2325 theRow.onmouseover = function() {addClass(this,"hoverRow");};
2326 theRow.onmouseout = function() {removeClass(this,"hoverRow");};
2327 this.rowHandler(w,theRow,prevColumns);
2328 rowCount++;
2331 this.lookaheadRegExp.lastIndex = w.nextMatch;
2332 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2335 rowHandler: function(w,e,prevColumns)
2337 var col = 0;
2338 var colSpanCount = 1;
2339 var prevCell = null;
2340 this.cellRegExp.lastIndex = w.nextMatch;
2341 var cellMatch = this.cellRegExp.exec(w.source);
2342 while(cellMatch && cellMatch.index == w.nextMatch) {
2343 if(cellMatch[1] == "~") {
2344 // Rowspan
2345 var last = prevColumns[col];
2346 if(last) {
2347 last.rowSpanCount++;
2348 last.element.setAttribute("rowspan",last.rowSpanCount);
2349 last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
2350 last.element.valign = "center";
2352 w.nextMatch = this.cellRegExp.lastIndex-1;
2353 } else if(cellMatch[1] == ">") {
2354 // Colspan
2355 colSpanCount++;
2356 w.nextMatch = this.cellRegExp.lastIndex-1;
2357 } else if(cellMatch[2]) {
2358 // End of row
2359 if(prevCell && colSpanCount > 1) {
2360 prevCell.setAttribute("colspan",colSpanCount);
2361 prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
2363 w.nextMatch = this.cellRegExp.lastIndex;
2364 break;
2365 } else {
2366 // Cell
2367 w.nextMatch++;
2368 var styles = config.formatterHelpers.inlineCssHelper(w);
2369 var spaceLeft = false;
2370 var chr = w.source.substr(w.nextMatch,1);
2371 while(chr == " ") {
2372 spaceLeft = true;
2373 w.nextMatch++;
2374 chr = w.source.substr(w.nextMatch,1);
2376 var cell;
2377 if(chr == "!") {
2378 cell = createTiddlyElement(e,"th");
2379 w.nextMatch++;
2380 } else {
2381 cell = createTiddlyElement(e,"td");
2383 prevCell = cell;
2384 prevColumns[col] = {rowSpanCount:1,element:cell};
2385 if(colSpanCount > 1) {
2386 cell.setAttribute("colspan",colSpanCount);
2387 cell.setAttribute("colSpan",colSpanCount); // Needed for IE
2388 colSpanCount = 1;
2390 config.formatterHelpers.applyCssHelper(cell,styles);
2391 w.subWikifyTerm(cell,this.cellTermRegExp);
2392 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
2393 cell.align = spaceLeft ? "center" : "left";
2394 else if(spaceLeft)
2395 cell.align = "right";
2396 w.nextMatch--;
2398 col++;
2399 this.cellRegExp.lastIndex = w.nextMatch;
2400 cellMatch = this.cellRegExp.exec(w.source);
2406 name: "heading",
2407 match: "^!{1,6}",
2408 termRegExp: /(\n)/mg,
2409 handler: function(w)
2411 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
2416 name: "list",
2417 match: "^(?:[\\*#;:]+)",
2418 lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
2419 termRegExp: /(\n)/mg,
2420 handler: function(w)
2422 var stack = [w.output];
2423 var currLevel = 0, currType = null;
2424 var listLevel, listType, itemType, baseType;
2425 w.nextMatch = w.matchStart;
2426 this.lookaheadRegExp.lastIndex = w.nextMatch;
2427 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2428 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2429 if(lookaheadMatch[1]) {
2430 listType = "ul";
2431 itemType = "li";
2432 } else if(lookaheadMatch[2]) {
2433 listType = "ol";
2434 itemType = "li";
2435 } else if(lookaheadMatch[3]) {
2436 listType = "dl";
2437 itemType = "dt";
2438 } else if(lookaheadMatch[4]) {
2439 listType = "dl";
2440 itemType = "dd";
2442 if(!baseType)
2443 baseType = listType;
2444 listLevel = lookaheadMatch[0].length;
2445 w.nextMatch += lookaheadMatch[0].length;
2446 var t;
2447 if(listLevel > currLevel) {
2448 for(t=currLevel; t<listLevel; t++) {
2449 var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
2450 stack.push(createTiddlyElement(target,listType));
2452 } else if(listType!=baseType && listLevel==1) {
2453 w.nextMatch -= lookaheadMatch[0].length;
2454 return;
2455 } else if(listLevel < currLevel) {
2456 for(t=currLevel; t>listLevel; t--)
2457 stack.pop();
2458 } else if(listLevel == currLevel && listType != currType) {
2459 stack.pop();
2460 stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
2462 currLevel = listLevel;
2463 currType = listType;
2464 var e = createTiddlyElement(stack[stack.length-1],itemType);
2465 w.subWikifyTerm(e,this.termRegExp);
2466 this.lookaheadRegExp.lastIndex = w.nextMatch;
2467 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2473 name: "quoteByBlock",
2474 match: "^<<<\\n",
2475 termRegExp: /(^<<<(\n|$))/mg,
2476 element: "blockquote",
2477 handler: config.formatterHelpers.createElementAndWikify
2481 name: "quoteByLine",
2482 match: "^>+",
2483 lookaheadRegExp: /^>+/mg,
2484 termRegExp: /(\n)/mg,
2485 element: "blockquote",
2486 handler: function(w)
2488 var stack = [w.output];
2489 var currLevel = 0;
2490 var newLevel = w.matchLength;
2491 var t;
2492 do {
2493 if(newLevel > currLevel) {
2494 for(t=currLevel; t<newLevel; t++)
2495 stack.push(createTiddlyElement(stack[stack.length-1],this.element));
2496 } else if(newLevel < currLevel) {
2497 for(t=currLevel; t>newLevel; t--)
2498 stack.pop();
2500 currLevel = newLevel;
2501 w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
2502 createTiddlyElement(stack[stack.length-1],"br");
2503 this.lookaheadRegExp.lastIndex = w.nextMatch;
2504 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2505 var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
2506 if(matched) {
2507 newLevel = lookaheadMatch[0].length;
2508 w.nextMatch += lookaheadMatch[0].length;
2510 } while(matched);
2515 name: "rule",
2516 match: "^----+$\\n?",
2517 handler: function(w)
2519 createTiddlyElement(w.output,"hr");
2524 name: "monospacedByLine",
2525 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
2526 element: "pre",
2527 handler: function(w)
2529 switch(w.matchText) {
2530 case "/*{{{*/\n": // CSS
2531 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2532 break;
2533 case "{{{\n": // monospaced block
2534 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2535 break;
2536 case "//{{{\n": // plugin
2537 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2538 break;
2539 case "<!--{{{-->\n": //template
2540 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
2541 break;
2542 default:
2543 break;
2545 config.formatterHelpers.enclosedTextHelper.call(this,w);
2550 name: "wikifyComment",
2551 match: "^(?:/\\*\\*\\*|<!---)\\n",
2552 handler: function(w)
2554 var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
2555 w.subWikifyTerm(w.output,termRegExp);
2560 name: "macro",
2561 match: "<<",
2562 lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
2563 handler: function(w)
2565 this.lookaheadRegExp.lastIndex = w.matchStart;
2566 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2567 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
2568 w.nextMatch = this.lookaheadRegExp.lastIndex;
2569 invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
2575 name: "prettyLink",
2576 match: "\\[\\[",
2577 lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
2578 handler: function(w)
2580 this.lookaheadRegExp.lastIndex = w.matchStart;
2581 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2582 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2583 var e;
2584 var text = lookaheadMatch[1];
2585 if(lookaheadMatch[3]) {
2586 // Pretty bracketted link
2587 var link = lookaheadMatch[3];
2588 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
2589 createExternalLink(w.output,link) : createTiddlyLink(w.output,decodeURIComponent(link),false,null,w.isStatic,w.tiddler);
2590 } else {
2591 // Simple bracketted link
2592 e = createTiddlyLink(w.output,decodeURIComponent(text),false,null,w.isStatic,w.tiddler);
2594 createTiddlyText(e,text);
2595 w.nextMatch = this.lookaheadRegExp.lastIndex;
2601 name: "wikiLink",
2602 match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
2603 handler: function(w)
2605 if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
2606 w.outputText(w.output,w.matchStart+1,w.nextMatch);
2607 return;
2609 if(w.matchStart > 0) {
2610 var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
2611 preRegExp.lastIndex = w.matchStart-1;
2612 var preMatch = preRegExp.exec(w.source);
2613 if(preMatch.index == w.matchStart-1) {
2614 w.outputText(w.output,w.matchStart,w.nextMatch);
2615 return;
2618 if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
2619 var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
2620 w.outputText(link,w.matchStart,w.nextMatch);
2621 } else {
2622 w.outputText(w.output,w.matchStart,w.nextMatch);
2628 name: "urlLink",
2629 match: config.textPrimitives.urlPattern,
2630 handler: function(w)
2632 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
2637 name: "image",
2638 match: "\\[[<>]?[Ii][Mm][Gg]\\[",
2639 lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
2640 handler: function(w)
2642 this.lookaheadRegExp.lastIndex = w.matchStart;
2643 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2644 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2645 var e = w.output;
2646 if(lookaheadMatch[5]) {
2647 var link = lookaheadMatch[5];
2648 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2649 addClass(e,"imageLink");
2651 var img = createTiddlyElement(e,"img");
2652 if(lookaheadMatch[1])
2653 img.align = "left";
2654 else if(lookaheadMatch[2])
2655 img.align = "right";
2656 if(lookaheadMatch[3]) {
2657 img.title = lookaheadMatch[3];
2658 img.setAttribute("alt",lookaheadMatch[3]);
2660 img.src = lookaheadMatch[4];
2661 w.nextMatch = this.lookaheadRegExp.lastIndex;
2667 name: "html",
2668 match: "<[Hh][Tt][Mm][Ll]>",
2669 lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
2670 handler: function(w)
2672 this.lookaheadRegExp.lastIndex = w.matchStart;
2673 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2674 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2675 createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
2676 w.nextMatch = this.lookaheadRegExp.lastIndex;
2682 name: "commentByBlock",
2683 match: "/%",
2684 lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
2685 handler: function(w)
2687 this.lookaheadRegExp.lastIndex = w.matchStart;
2688 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2689 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
2690 w.nextMatch = this.lookaheadRegExp.lastIndex;
2695 name: "characterFormat",
2696 match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
2697 handler: function(w)
2699 switch(w.matchText) {
2700 case "''":
2701 w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
2702 break;
2703 case "//":
2704 w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
2705 break;
2706 case "__":
2707 w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
2708 break;
2709 case "^^":
2710 w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
2711 break;
2712 case "~~":
2713 w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
2714 break;
2715 case "--":
2716 w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
2717 break;
2718 case "{{{":
2719 var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
2720 lookaheadRegExp.lastIndex = w.matchStart;
2721 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2722 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2723 createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
2724 w.nextMatch = lookaheadRegExp.lastIndex;
2726 break;
2732 name: "customFormat",
2733 match: "@@|\\{\\{",
2734 handler: function(w)
2736 switch(w.matchText) {
2737 case "@@":
2738 var e = createTiddlyElement(w.output,"span");
2739 var styles = config.formatterHelpers.inlineCssHelper(w);
2740 if(styles.length == 0)
2741 e.className = "marked";
2742 else
2743 config.formatterHelpers.applyCssHelper(e,styles);
2744 w.subWikifyTerm(e,/(@@)/mg);
2745 break;
2746 case "{{":
2747 var lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
2748 lookaheadRegExp.lastIndex = w.matchStart;
2749 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2750 if(lookaheadMatch) {
2751 w.nextMatch = lookaheadRegExp.lastIndex;
2752 e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
2753 w.subWikifyTerm(e,/(\}\}\})/mg);
2755 break;
2761 name: "mdash",
2762 match: "--",
2763 handler: function(w)
2765 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
2770 name: "lineBreak",
2771 match: "\\n|<br ?/?>",
2772 handler: function(w)
2774 createTiddlyElement(w.output,"br");
2779 name: "rawText",
2780 match: "\\\"{3}|<nowiki>",
2781 lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
2782 handler: function(w)
2784 this.lookaheadRegExp.lastIndex = w.matchStart;
2785 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2786 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2787 createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
2788 w.nextMatch = this.lookaheadRegExp.lastIndex;
2794 name: "htmlEntitiesEncoding",
2795 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};)",
2796 handler: function(w)
2798 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
2804 //--
2805 //-- Wikifier
2806 //--
2808 function getParser(tiddler,format)
2810 if(tiddler) {
2811 if(!format)
2812 format = tiddler.fields["wikiformat"];
2813 var i;
2814 if(format) {
2815 for(i in config.parsers) {
2816 if(format == config.parsers[i].format)
2817 return config.parsers[i];
2819 } else {
2820 for(i in config.parsers) {
2821 if(tiddler.isTagged(config.parsers[i].formatTag))
2822 return config.parsers[i];
2826 return formatter;
2829 function wikify(source,output,highlightRegExp,tiddler)
2831 if(source) {
2832 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
2833 var t0 = new Date();
2834 wikifier.subWikify(output);
2835 if(tiddler && config.options.chkDisplayInstrumentation)
2836 displayMessage("wikify:" +tiddler.title+ " in " + (new Date()-t0) + " ms");
2840 function wikifyStatic(source,highlightRegExp,tiddler,format)
2842 var e = createTiddlyElement(document.body,"pre");
2843 e.style.display = "none";
2844 var html = "";
2845 if(source && source != "") {
2846 if(!tiddler)
2847 tiddler = new Tiddler("temp");
2848 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2849 wikifier.isStatic = true;
2850 wikifier.subWikify(e);
2851 html = e.innerHTML;
2852 removeNode(e);
2854 return html;
2857 function wikifyPlain(title,theStore,limit)
2859 if(!theStore)
2860 theStore = store;
2861 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2862 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2863 } else {
2864 return "";
2868 function wikifyPlainText(text,limit,tiddler)
2870 if(limit > 0)
2871 text = text.substr(0,limit);
2872 var wikifier = new Wikifier(text,formatter,null,tiddler);
2873 return wikifier.wikifyPlain();
2876 function highlightify(source,output,highlightRegExp,tiddler)
2878 if(source) {
2879 var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
2880 wikifier.outputText(output,0,source.length);
2884 function Wikifier(source,formatter,highlightRegExp,tiddler)
2886 this.source = source;
2887 this.output = null;
2888 this.formatter = formatter;
2889 this.nextMatch = 0;
2890 this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
2891 this.highlightRegExp = highlightRegExp;
2892 this.highlightMatch = null;
2893 this.isStatic = false;
2894 if(highlightRegExp) {
2895 highlightRegExp.lastIndex = 0;
2896 this.highlightMatch = highlightRegExp.exec(source);
2898 this.tiddler = tiddler;
2901 Wikifier.prototype.wikifyPlain = function()
2903 var e = createTiddlyElement(document.body,"div");
2904 e.style.display = "none";
2905 this.subWikify(e);
2906 var text = getPlainText(e);
2907 removeNode(e);
2908 return text;
2911 Wikifier.prototype.subWikify = function(output,terminator)
2913 try {
2914 if(terminator)
2915 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
2916 else
2917 this.subWikifyUnterm(output);
2918 } catch(ex) {
2919 showException(ex);
2923 Wikifier.prototype.subWikifyUnterm = function(output)
2925 var oldOutput = this.output;
2926 this.output = output;
2927 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2928 var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2929 while(formatterMatch) {
2930 // Output any text before the match
2931 if(formatterMatch.index > this.nextMatch)
2932 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2933 // Set the match parameters for the handler
2934 this.matchStart = formatterMatch.index;
2935 this.matchLength = formatterMatch[0].length;
2936 this.matchText = formatterMatch[0];
2937 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2938 for(var t=1; t<formatterMatch.length; t++) {
2939 if(formatterMatch[t]) {
2940 this.formatter.formatters[t-1].handler(this);
2941 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2942 break;
2945 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2947 if(this.nextMatch < this.source.length) {
2948 this.outputText(this.output,this.nextMatch,this.source.length);
2949 this.nextMatch = this.source.length;
2951 this.output = oldOutput;
2954 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
2956 var oldOutput = this.output;
2957 this.output = output;
2958 terminatorRegExp.lastIndex = this.nextMatch;
2959 var terminatorMatch = terminatorRegExp.exec(this.source);
2960 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2961 var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2962 while(terminatorMatch || formatterMatch) {
2963 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
2964 if(terminatorMatch.index > this.nextMatch)
2965 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
2966 this.matchText = terminatorMatch[1];
2967 this.matchLength = terminatorMatch[1].length;
2968 this.matchStart = terminatorMatch.index;
2969 this.nextMatch = this.matchStart + this.matchLength;
2970 this.output = oldOutput;
2971 return;
2973 if(formatterMatch.index > this.nextMatch)
2974 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2975 this.matchStart = formatterMatch.index;
2976 this.matchLength = formatterMatch[0].length;
2977 this.matchText = formatterMatch[0];
2978 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2979 for(var t=1; t<formatterMatch.length; t++) {
2980 if(formatterMatch[t]) {
2981 this.formatter.formatters[t-1].handler(this);
2982 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2983 break;
2986 terminatorRegExp.lastIndex = this.nextMatch;
2987 terminatorMatch = terminatorRegExp.exec(this.source);
2988 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2990 if(this.nextMatch < this.source.length) {
2991 this.outputText(this.output,this.nextMatch,this.source.length);
2992 this.nextMatch = this.source.length;
2994 this.output = oldOutput;
2997 Wikifier.prototype.outputText = function(place,startPos,endPos)
2999 while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
3000 if(this.highlightMatch.index > startPos) {
3001 createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
3002 startPos = this.highlightMatch.index;
3004 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
3005 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
3006 startPos = highlightEnd;
3007 if(startPos >= this.highlightRegExp.lastIndex)
3008 this.highlightMatch = this.highlightRegExp.exec(this.source);
3010 if(startPos < endPos) {
3011 createTiddlyText(place,this.source.substring(startPos,endPos));
3015 //--
3016 //-- Macro definitions
3017 //--
3019 config.macros.today.handler = function(place,macroName,params)
3021 var now = new Date();
3022 var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
3023 createTiddlyElement(place,"span",null,null,text);
3026 config.macros.version.handler = function(place)
3028 createTiddlyElement(place,"span",null,null,formatVersion());
3031 config.macros.list.handler = function(place,macroName,params)
3033 var type = params[0] || "all";
3034 var list = document.createElement("ul");
3035 place.appendChild(list);
3036 if(this[type].prompt)
3037 createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
3038 var results;
3039 if(this[type].handler)
3040 results = this[type].handler(params);
3041 for(var t = 0; t < results.length; t++) {
3042 var li = document.createElement("li");
3043 list.appendChild(li);
3044 createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
3048 config.macros.list.all.handler = function(params)
3050 return store.reverseLookup("tags","excludeLists",false,"title");
3053 config.macros.list.missing.handler = function(params)
3055 return store.getMissingLinks();
3058 config.macros.list.orphans.handler = function(params)
3060 return store.getOrphans();
3063 config.macros.list.shadowed.handler = function(params)
3065 return store.getShadowed();
3068 config.macros.list.touched.handler = function(params)
3070 return store.getTouched();
3073 config.macros.list.filter.handler = function(params)
3075 var filter = params[1];
3076 var results = [];
3077 if(filter) {
3078 var tiddlers = store.filterTiddlers(filter);
3079 for(var t=0; t<tiddlers.length; t++)
3080 results.push(tiddlers[t].title);
3082 return results;
3085 config.macros.allTags.handler = function(place,macroName,params)
3087 var tags = store.getTags(params[0]);
3088 var ul = createTiddlyElement(place,"ul");
3089 if(tags.length == 0)
3090 createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
3091 for(var t=0; t<tags.length; t++) {
3092 var title = tags[t][0];
3093 var info = getTiddlyLinkInfo(title);
3094 var li = createTiddlyElement(ul,"li");
3095 var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
3096 btn.setAttribute("tag",title);
3097 btn.setAttribute("refresh","link");
3098 btn.setAttribute("tiddlyLink",title);
3102 config.macros.timeline.handler = function(place,macroName,params)
3104 var field = params[0] || "modified";
3105 var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
3106 var lastDay = "";
3107 var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
3108 var dateFormat = params[2] || this.dateFormat;
3109 for(var t=tiddlers.length-1; t>=last; t--) {
3110 var tiddler = tiddlers[t];
3111 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
3112 if(theDay != lastDay) {
3113 var ul = document.createElement("ul");
3114 place.appendChild(ul);
3115 createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
3116 lastDay = theDay;
3118 createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
3122 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3124 params = paramString.parseParams("name",null,true,false,true);
3125 var names = params[0]["name"];
3126 var tiddlerName = names[0];
3127 var className = names[1] || null;
3128 var args = params[0]["with"];
3129 var wrapper = createTiddlyElement(place,"span",null,className);
3130 if(!args) {
3131 wrapper.setAttribute("refresh","content");
3132 wrapper.setAttribute("tiddler",tiddlerName);
3134 var text = store.getTiddlerText(tiddlerName);
3135 if(text) {
3136 var stack = config.macros.tiddler.tiddlerStack;
3137 if(stack.indexOf(tiddlerName) !== -1)
3138 return;
3139 stack.push(tiddlerName);
3140 try {
3141 var n = args ? Math.min(args.length,9) : 0;
3142 for(var i=0; i<n; i++) {
3143 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
3144 text = text.replace(placeholderRE,args[i]);
3146 config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
3147 } finally {
3148 stack.pop();
3153 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
3155 wikify(text,place,null,store.getTiddler(tiddlerName));
3158 config.macros.tiddler.tiddlerStack = [];
3160 config.macros.tag.handler = function(place,macroName,params)
3162 createTagButton(place,params[0],null,params[1],params[2]);
3165 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3167 params = paramString.parseParams("anon",null,true,false,false);
3168 var ul = createTiddlyElement(place,"ul");
3169 var title = getParam(params,"anon","");
3170 if(title && store.tiddlerExists(title))
3171 tiddler = store.getTiddler(title);
3172 var sep = getParam(params,"sep"," ");
3173 var lingo = config.views.wikified.tag;
3174 var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
3175 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
3176 for(var t=0; t<tiddler.tags.length; t++) {
3177 createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
3178 if(t<tiddler.tags.length-1)
3179 createTiddlyText(ul,sep);
3183 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3185 params = paramString.parseParams("anon",null,true,false,false);
3186 var ul = createTiddlyElement(place,"ul");
3187 var title = getParam(params,"anon","");
3188 if(title == "" && tiddler instanceof Tiddler)
3189 title = tiddler.title;
3190 var sep = getParam(params,"sep"," ");
3191 ul.setAttribute("title",this.tooltip.format([title]));
3192 var tagged = store.getTaggedTiddlers(title);
3193 var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
3194 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
3195 for(var t=0; t<tagged.length; t++) {
3196 createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
3197 if(t<tagged.length-1)
3198 createTiddlyText(ul,sep);
3202 config.macros.closeAll.handler = function(place)
3204 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3207 config.macros.closeAll.onClick = function(e)
3209 story.closeAllTiddlers();
3210 return false;
3213 config.macros.permaview.handler = function(place)
3215 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3218 config.macros.permaview.onClick = function(e)
3220 story.permaView();
3221 return false;
3224 config.macros.saveChanges.handler = function(place,macroName,params)
3226 if(!readOnly)
3227 createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
3230 config.macros.saveChanges.onClick = function(e)
3232 saveChanges();
3233 return false;
3236 config.macros.slider.onClickSlider = function(ev)
3238 var e = ev || window.event;
3239 var n = this.nextSibling;
3240 var cookie = n.getAttribute("cookie");
3241 var isOpen = n.style.display != "none";
3242 if(config.options.chkAnimate && anim && typeof Slider == "function")
3243 anim.startAnimating(new Slider(n,!isOpen,null,"none"));
3244 else
3245 n.style.display = isOpen ? "none" : "block";
3246 config.options[cookie] = !isOpen;
3247 saveOptionCookie(cookie);
3248 return false;
3251 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
3253 var c = cookie || "";
3254 var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
3255 var panel = createTiddlyElement(null,"div",null,"sliderPanel");
3256 panel.setAttribute("cookie",c);
3257 panel.style.display = config.options[c] ? "block" : "none";
3258 place.appendChild(panel);
3259 return panel;
3262 config.macros.slider.handler = function(place,macroName,params)
3264 var panel = this.createSlider(place,params[0],params[2],params[3]);
3265 var text = store.getTiddlerText(params[1]);
3266 panel.setAttribute("refresh","content");
3267 panel.setAttribute("tiddler",params[1]);
3268 if(text)
3269 wikify(text,panel,null,store.getTiddler(params[1]));
3272 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
3273 config.macros.gradient.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3275 var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
3276 panel.style.position = "relative";
3277 panel.style.overflow = "hidden";
3278 panel.style.zIndex = "0";
3279 if(wikifier) {
3280 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
3281 config.formatterHelpers.applyCssHelper(panel,styles);
3283 params = paramString.parseParams("color");
3284 var locolors = [], hicolors = [];
3285 for(var t=2; t<params.length; t++) {
3286 var c = new RGB(params[t].value);
3287 if(params[t].name == "snap") {
3288 hicolors[hicolors.length-1] = c;
3289 } else {
3290 locolors.push(c);
3291 hicolors.push(c);
3294 drawGradient(panel,params[1].value != "vert",locolors,hicolors);
3295 if(wikifier)
3296 wikifier.subWikify(panel,">>");
3297 if(document.all) {
3298 panel.style.height = "100%";
3299 panel.style.width = "100%";
3303 config.macros.message.handler = function(place,macroName,params)
3305 if(params[0]) {
3306 var names = params[0].split(".");
3307 var lookupMessage = function(root,nameIndex) {
3308 if(names[nameIndex] in root) {
3309 if(nameIndex < names.length-1)
3310 return (lookupMessage(root[names[nameIndex]],nameIndex+1));
3311 else
3312 return root[names[nameIndex]];
3313 } else
3314 return null;
3316 var m = lookupMessage(config,0);
3317 if(m == null)
3318 m = lookupMessage(window,0);
3319 createTiddlyText(place,m.toString().format(params.splice(1)));
3324 config.macros.view.views = {
3325 text: function(value,place,params,wikifier,paramString,tiddler) {
3326 highlightify(value,place,highlightHack,tiddler);
3328 link: function(value,place,params,wikifier,paramString,tiddler) {
3329 createTiddlyLink(place,value,true);
3331 wikified: function(value,place,params,wikifier,paramString,tiddler) {
3332 if(params[2])
3333 value=params[2].unescapeLineBreaks().format([value]);
3334 wikify(value,place,highlightHack,tiddler);
3336 date: function(value,place,params,wikifier,paramString,tiddler) {
3337 value = Date.convertFromYYYYMMDDHHMM(value);
3338 createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
3342 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3344 if((tiddler instanceof Tiddler) && params[0]) {
3345 var value = store.getValue(tiddler,params[0]);
3346 if(value) {
3347 var type = params[1] || config.macros.view.defaultView;
3348 var handler = config.macros.view.views[type];
3349 if(handler)
3350 handler(value,place,params,wikifier,paramString,tiddler);
3355 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3357 var field = params[0];
3358 var rows = params[1] || 0;
3359 var defVal = params[2] || '';
3360 if((tiddler instanceof Tiddler) && field) {
3361 story.setDirty(tiddler.title,true);
3362 var e,v;
3363 if(field != "text" && !rows) {
3364 e = createTiddlyElement(null,"input");
3365 if(tiddler.isReadOnly())
3366 e.setAttribute("readOnly","readOnly");
3367 e.setAttribute("edit",field);
3368 e.setAttribute("type","text");
3369 e.value = store.getValue(tiddler,field) || defVal;
3370 e.setAttribute("size","40");
3371 e.setAttribute("autocomplete","off");
3372 place.appendChild(e);
3373 } else {
3374 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
3375 var wrapper2 = createTiddlyElement(wrapper1,"div");
3376 e = createTiddlyElement(wrapper2,"textarea");
3377 if(tiddler.isReadOnly())
3378 e.setAttribute("readOnly","readOnly");
3379 e.value = v = store.getValue(tiddler,field) || defVal;
3380 rows = rows || 10;
3381 var lines = v.match(/\n/mg);
3382 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
3383 if(lines != null && lines.length > rows)
3384 rows = lines.length + 5;
3385 rows = Math.min(rows,maxLines);
3386 e.setAttribute("rows",rows);
3387 e.setAttribute("edit",field);
3388 place.appendChild(wrapper1);
3390 return e;
3394 config.macros.tagChooser.onClick = function(ev)
3396 var e = ev || window.event;
3397 var lingo = config.views.editor.tagChooser;
3398 var popup = Popup.create(this);
3399 var tags = store.getTags("excludeLists");
3400 if(tags.length == 0)
3401 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
3402 for(var t=0; t<tags.length; t++) {
3403 var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
3404 tag.setAttribute("tag",tags[t][0]);
3405 tag.setAttribute("tiddler",this.getAttribute("tiddler"));
3407 Popup.show();
3408 e.cancelBubble = true;
3409 if(e.stopPropagation) e.stopPropagation();
3410 return false;
3413 config.macros.tagChooser.onTagClick = function(ev)
3415 var e = ev || window.event;
3416 if(e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
3417 var tag = this.getAttribute("tag");
3418 var title = this.getAttribute("tiddler");
3419 if(!readOnly)
3420 story.setTiddlerTag(title,tag,0);
3421 return false;
3424 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3426 if(tiddler instanceof Tiddler) {
3427 var lingo = config.views.editor.tagChooser;
3428 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
3429 btn.setAttribute("tiddler",tiddler.title);
3433 config.macros.refreshDisplay.handler = function(place)
3435 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3438 config.macros.refreshDisplay.onClick = function(e)
3440 refreshAll();
3441 return false;
3444 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3446 var title = tiddler ? tiddler.title : null;
3447 var a = title ? config.annotations[title] : null;
3448 if(!tiddler || !title || !a)
3449 return;
3450 var text = a.format([title]);
3451 wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
3454 //--
3455 //-- NewTiddler and NewJournal macros
3456 //--
3458 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
3460 var tags = [];
3461 for(var t=1; t<params.length; t++) {
3462 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
3463 tags.push(params[t].value);
3465 label = getParam(params,"label",label);
3466 prompt = getParam(params,"prompt",prompt);
3467 accessKey = getParam(params,"accessKey",accessKey);
3468 newFocus = getParam(params,"focus",newFocus);
3469 var customFields = getParam(params,"fields","");
3470 if(!customFields && !store.isShadowTiddler(title))
3471 customFields = String.encodeHashMap(config.defaultCustomFields);
3472 var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
3473 btn.setAttribute("newTitle",title);
3474 btn.setAttribute("isJournal",isJournal ? "true" : "false");
3475 if(tags.length > 0)
3476 btn.setAttribute("params",tags.join("|"));
3477 btn.setAttribute("newFocus",newFocus);
3478 btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
3479 if(customFields !== "")
3480 btn.setAttribute("customFields",customFields);
3481 var text = getParam(params,"text");
3482 if(text !== undefined)
3483 btn.setAttribute("newText",text);
3484 return btn;
3487 config.macros.newTiddler.onClickNewTiddler = function()
3489 var title = this.getAttribute("newTitle");
3490 if(this.getAttribute("isJournal") == "true") {
3491 title = new Date().formatString(title.trim());
3493 var params = this.getAttribute("params");
3494 var tags = params ? params.split("|") : [];
3495 var focus = this.getAttribute("newFocus");
3496 var template = this.getAttribute("newTemplate");
3497 var customFields = this.getAttribute("customFields");
3498 if(!customFields && !store.isShadowTiddler(title))
3499 customFields = String.encodeHashMap(config.defaultCustomFields);
3500 story.displayTiddler(null,title,template,false,null,null);
3501 var tiddlerElem = story.getTiddler(title);
3502 if(customFields)
3503 story.addCustomFields(tiddlerElem,customFields);
3504 var text = this.getAttribute("newText");
3505 if(typeof text == "string")
3506 story.getTiddlerField(title,"text").value = text.format([title]);
3507 for(var t=0;t<tags.length;t++)
3508 story.setTiddlerTag(title,tags[t],+1);
3509 story.focusTiddler(title,focus);
3510 return false;
3513 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString)
3515 if(!readOnly) {
3516 params = paramString.parseParams("anon",null,true,false,false);
3517 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
3518 title = getParam(params,"title",title);
3519 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
3523 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString)
3525 if(!readOnly) {
3526 params = paramString.parseParams("anon",null,true,false,false);
3527 var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
3528 title = getParam(params,"title",title);
3529 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
3533 //--
3534 //-- Search macro
3535 //--
3537 config.macros.search.handler = function(place,macroName,params)
3539 var searchTimeout = null;
3540 var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick,"searchButton");
3541 var txt = createTiddlyElement(place,"input",null,"txtOptionInput searchField");
3542 if(params[0])
3543 txt.value = params[0];
3544 txt.onkeyup = this.onKeyPress;
3545 txt.onfocus = this.onFocus;
3546 txt.setAttribute("size",this.sizeTextbox);
3547 txt.setAttribute("accessKey",this.accessKey);
3548 txt.setAttribute("autocomplete","off");
3549 txt.setAttribute("lastSearchText","");
3550 if(config.browser.isSafari) {
3551 txt.setAttribute("type","search");
3552 txt.setAttribute("results","5");
3553 } else {
3554 txt.setAttribute("type","text");
3558 // Global because there's only ever one outstanding incremental search timer
3559 config.macros.search.timeout = null;
3561 config.macros.search.doSearch = function(txt)
3563 if(txt.value.length > 0) {
3564 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
3565 txt.setAttribute("lastSearchText",txt.value);
3569 config.macros.search.onClick = function(e)
3571 config.macros.search.doSearch(this.nextSibling);
3572 return false;
3575 config.macros.search.onKeyPress = function(ev)
3577 var e = ev || window.event;
3578 switch(e.keyCode) {
3579 case 13: // Ctrl-Enter
3580 case 10: // Ctrl-Enter on IE PC
3581 config.macros.search.doSearch(this);
3582 break;
3583 case 27: // Escape
3584 this.value = "";
3585 clearMessage();
3586 break;
3588 if(config.options.chkIncrementalSearch) {
3589 if(this.value.length > 2) {
3590 if(this.value != this.getAttribute("lastSearchText")) {
3591 if(config.macros.search.timeout)
3592 clearTimeout(config.macros.search.timeout);
3593 var txt = this;
3594 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3596 } else {
3597 if(config.macros.search.timeout)
3598 clearTimeout(config.macros.search.timeout);
3603 config.macros.search.onFocus = function(e)
3605 this.select();
3608 //--
3609 //-- Tabs macro
3610 //--
3612 config.macros.tabs.handler = function(place,macroName,params)
3614 var cookie = params[0];
3615 var numTabs = (params.length-1)/3;
3616 var wrapper = createTiddlyElement(null,"div",null,"tabsetWrapper " + cookie);
3617 var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
3618 tabset.setAttribute("cookie",cookie);
3619 var validTab = false;
3620 for(var t=0; t<numTabs; t++) {
3621 var label = params[t*3+1];
3622 var prompt = params[t*3+2];
3623 var content = params[t*3+3];
3624 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
3625 tab.setAttribute("tab",label);
3626 tab.setAttribute("content",content);
3627 tab.title = prompt;
3628 if(config.options[cookie] == label)
3629 validTab = true;
3631 if(!validTab)
3632 config.options[cookie] = params[1];
3633 place.appendChild(wrapper);
3634 this.switchTab(tabset,config.options[cookie]);
3637 config.macros.tabs.onClickTab = function(e)
3639 config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
3640 return false;
3643 config.macros.tabs.switchTab = function(tabset,tab)
3645 var cookie = tabset.getAttribute("cookie");
3646 var theTab = null;
3647 var nodes = tabset.childNodes;
3648 for(var t=0; t<nodes.length; t++) {
3649 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
3650 theTab = nodes[t];
3651 theTab.className = "tab tabSelected";
3652 } else {
3653 nodes[t].className = "tab tabUnselected";
3656 if(theTab) {
3657 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
3658 removeNode(tabset.nextSibling);
3659 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
3660 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
3661 var contentTitle = theTab.getAttribute("content");
3662 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
3663 if(cookie) {
3664 config.options[cookie] = tab;
3665 saveOptionCookie(cookie);
3670 //--
3671 //-- Tiddler toolbar
3672 //--
3674 // Create a toolbar command button
3675 config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
3677 if(typeof commandName != "string") {
3678 var c = null;
3679 for(var t in config.commands) {
3680 if(config.commands[t] == commandName)
3681 c = t;
3683 commandName = c;
3685 if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
3686 var command = config.commands[commandName];
3687 if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
3688 var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
3689 var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
3690 var cmd;
3691 switch(command.type) {
3692 case "popup":
3693 cmd = this.onClickPopup;
3694 break;
3695 case "command":
3696 default:
3697 cmd = this.onClickCommand;
3698 break;
3700 var btn = createTiddlyButton(null,text,tooltip,cmd);
3701 btn.setAttribute("commandName",commandName);
3702 btn.setAttribute("tiddler",tiddler.title);
3703 if(className)
3704 addClass(btn,className);
3705 place.appendChild(btn);
3710 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
3712 var title = tiddler.title;
3713 var ro = tiddler.isReadOnly();
3714 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
3715 return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
3718 config.macros.toolbar.getCommandText = function(command,tiddler)
3720 return tiddler.isReadOnly() && command.readOnlyText || command.text;
3723 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
3725 return tiddler.isReadOnly() && command.readOnlyTooltip || command.tooltip;
3728 config.macros.toolbar.onClickCommand = function(ev)
3730 var e = ev || window.event;
3731 e.cancelBubble = true;
3732 if(e.stopPropagation) e.stopPropagation();
3733 var command = config.commands[this.getAttribute("commandName")];
3734 return command.handler(e,this,this.getAttribute("tiddler"));
3737 config.macros.toolbar.onClickPopup = function(ev)
3739 var e = ev || window.event;
3740 e.cancelBubble = true;
3741 if(e.stopPropagation) e.stopPropagation();
3742 var popup = Popup.create(this);
3743 var command = config.commands[this.getAttribute("commandName")];
3744 var title = this.getAttribute("tiddler");
3745 var tiddler = store.fetchTiddler(title);
3746 popup.setAttribute("tiddler",title);
3747 command.handlePopup(popup,title);
3748 Popup.show();
3749 return false;
3752 // Invoke the first command encountered from a given place that is tagged with a specified class
3753 config.macros.toolbar.invokeCommand = function(place,className,event)
3755 var children = place.getElementsByTagName("a");
3756 for(var t=0; t<children.length; t++) {
3757 var c = children[t];
3758 if(hasClass(c,className) && c.getAttribute && c.getAttribute("commandName")) {
3759 if(c.onclick instanceof Function)
3760 c.onclick.call(c,event);
3761 break;
3766 config.macros.toolbar.onClickMore = function(ev)
3768 var e = this.nextSibling;
3769 e.style.display = "inline";
3770 removeNode(this);
3771 return false;
3774 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3776 for(var t=0; t<params.length; t++) {
3777 var c = params[t];
3778 switch(c) {
3779 case '>':
3780 var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
3781 addClass(btn,"moreCommand");
3782 var e = createTiddlyElement(place,"span",null,"moreCommand");
3783 e.style.display = "none";
3784 place = e;
3785 break;
3786 default:
3787 var className = "";
3788 switch(c.substr(0,1)) {
3789 case "+":
3790 className = "defaultCommand";
3791 c = c.substr(1);
3792 break;
3793 case "-":
3794 className = "cancelCommand";
3795 c = c.substr(1);
3796 break;
3798 if(c in config.commands)
3799 this.createCommand(place,c,tiddler,className);
3800 break;
3805 //--
3806 //-- Menu and toolbar commands
3807 //--
3809 config.commands.closeTiddler.handler = function(event,src,title)
3811 if(story.isDirty(title) && !readOnly) {
3812 if(!confirm(config.commands.cancelTiddler.warning.format([title])))
3813 return false;
3815 story.setDirty(title,false);
3816 story.closeTiddler(title,true);
3817 return false;
3820 config.commands.closeOthers.handler = function(event,src,title)
3822 story.closeAllTiddlers(title);
3823 return false;
3826 config.commands.editTiddler.handler = function(event,src,title)
3828 clearMessage();
3829 var tiddlerElem = story.getTiddler(title);
3830 var fields = tiddlerElem.getAttribute("tiddlyFields");
3831 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
3832 story.focusTiddler(title,config.options.txtEditorFocus||"text");
3833 return false;
3836 config.commands.saveTiddler.handler = function(event,src,title)
3838 var newTitle = story.saveTiddler(title,event.shiftKey);
3839 if(newTitle)
3840 story.displayTiddler(null,newTitle);
3841 return false;
3844 config.commands.cancelTiddler.handler = function(event,src,title)
3846 if(story.hasChanges(title) && !readOnly) {
3847 if(!confirm(this.warning.format([title])))
3848 return false;
3850 story.setDirty(title,false);
3851 story.displayTiddler(null,title);
3852 return false;
3855 config.commands.deleteTiddler.handler = function(event,src,title)
3857 var deleteIt = true;
3858 if(config.options.chkConfirmDelete)
3859 deleteIt = confirm(this.warning.format([title]));
3860 if(deleteIt) {
3861 store.removeTiddler(title);
3862 story.closeTiddler(title,true);
3863 autoSaveChanges();
3865 return false;
3868 config.commands.permalink.handler = function(event,src,title)
3870 var t = encodeURIComponent(String.encodeTiddlyLink(title));
3871 if(window.location.hash != t)
3872 window.location.hash = t;
3873 return false;
3876 config.commands.references.handlePopup = function(popup,title)
3878 var references = store.getReferringTiddlers(title);
3879 var c = false;
3880 for(var r=0; r<references.length; r++) {
3881 if(references[r].title != title && !references[r].isTagged("excludeLists")) {
3882 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
3883 c = true;
3886 if(!c)
3887 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
3890 config.commands.jump.handlePopup = function(popup,title)
3892 story.forEachTiddler(function(title,element) {
3893 createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
3897 config.commands.syncing.handlePopup = function(popup,title)
3899 var tiddler = store.fetchTiddler(title);
3900 if(!tiddler)
3901 return;
3902 var serverType = tiddler.getServerType();
3903 var serverHost = tiddler.fields['server.host'];
3904 var serverWorkspace = tiddler.fields['server.workspace'];
3905 if(!serverWorkspace)
3906 serverWorkspace = "";
3907 if(serverType) {
3908 var e = createTiddlyElement(popup,"li",null,"popupMessage");
3909 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3910 } else {
3911 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
3913 if(serverType) {
3914 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3915 var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
3916 btn.setAttribute("tiddler",title);
3917 btn.setAttribute("server.type","");
3919 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3920 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
3921 var feeds = store.getTaggedTiddlers("systemServer","title");
3922 for(var t=0; t<feeds.length; t++) {
3923 var f = feeds[t];
3924 var feedServerType = store.getTiddlerSlice(f.title,"Type");
3925 if(!feedServerType)
3926 feedServerType = "file";
3927 var feedServerHost = store.getTiddlerSlice(f.title,"URL");
3928 if(!feedServerHost)
3929 feedServerHost = "";
3930 var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
3931 if(!feedServerWorkspace)
3932 feedServerWorkspace = "";
3933 var caption = f.title;
3934 if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
3935 caption = config.commands.syncing.currServerMarker + caption;
3936 } else {
3937 caption = config.commands.syncing.notCurrServerMarker + caption;
3939 btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
3940 btn.setAttribute("tiddler",title);
3941 btn.setAttribute("server.type",feedServerType);
3942 btn.setAttribute("server.host",feedServerHost);
3943 btn.setAttribute("server.workspace",feedServerWorkspace);
3947 config.commands.syncing.onChooseServer = function(e)
3949 var tiddler = this.getAttribute("tiddler");
3950 var serverType = this.getAttribute("server.type");
3951 if(serverType) {
3952 store.addTiddlerFields(tiddler,{
3953 "server.type": serverType,
3954 "server.host": this.getAttribute("server.host"),
3955 "server.workspace": this.getAttribute("server.workspace")
3957 } else {
3958 store.setValue(tiddler,"server",null);
3960 return false;
3963 config.commands.fields.handlePopup = function(popup,title)
3965 var tiddler = store.fetchTiddler(title);
3966 if(!tiddler)
3967 return;
3968 var fields = {};
3969 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
3970 var items = [];
3971 for(var t in fields) {
3972 items.push({field: t,value: fields[t]});
3974 items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
3975 if(items.length > 0)
3976 ListView.create(popup,items,this.listViewTemplate);
3977 else
3978 createTiddlyElement(popup,"div",null,null,this.emptyText);
3981 //--
3982 //-- Tiddler() object
3983 //--
3985 function Tiddler(title)
3987 this.title = title;
3988 this.text = "";
3989 this.modifier = null;
3990 this.created = new Date();
3991 this.modified = this.created;
3992 this.links = [];
3993 this.linksUpdated = false;
3994 this.tags = [];
3995 this.fields = {};
3996 return this;
3999 Tiddler.prototype.getLinks = function()
4001 if(this.linksUpdated==false)
4002 this.changed();
4003 return this.links;
4006 // Returns the fields that are inherited in string field:"value" field2:"value2" format
4007 Tiddler.prototype.getInheritedFields = function()
4009 var f = {};
4010 for(var i in this.fields) {
4011 if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
4012 f[i] = this.fields[i];
4015 return String.encodeHashMap(f);
4018 // Increment the changeCount of a tiddler
4019 Tiddler.prototype.incChangeCount = function()
4021 var c = this.fields['changecount'];
4022 c = c ? parseInt(c,10) : 0;
4023 this.fields['changecount'] = String(c+1);
4026 // Clear the changeCount of a tiddler
4027 Tiddler.prototype.clearChangeCount = function()
4029 if(this.fields['changecount']) {
4030 delete this.fields['changecount'];
4034 Tiddler.prototype.doNotSave = function()
4036 return this.fields['doNotSave'];
4039 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
4040 Tiddler.prototype.isTouched = function()
4042 var changeCount = this.fields['changecount'];
4043 if(changeCount === undefined)
4044 changeCount = 0;
4045 return changeCount > 0;
4048 // Return the tiddler as an RSS item
4049 Tiddler.prototype.toRssItem = function(uri)
4051 var s = [];
4052 s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
4053 s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
4054 for(var t=0; t<this.tags.length; t++)
4055 s.push("<category>" + this.tags[t] + "</category>");
4056 s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
4057 s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
4058 return s.join("\n");
4061 // Format the text for storage in an RSS item
4062 Tiddler.prototype.saveToRss = function(uri)
4064 return "<item>\n" + this.toRssItem(uri) + "\n</item>";
4067 // Change the text and other attributes of a tiddler
4068 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
4070 this.assign(title,text,modifier,modified,tags,created,fields);
4071 this.changed();
4072 return this;
4075 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
4076 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
4078 if(title != undefined)
4079 this.title = title;
4080 if(text != undefined)
4081 this.text = text;
4082 if(modifier != undefined)
4083 this.modifier = modifier;
4084 if(modified != undefined)
4085 this.modified = modified;
4086 if(created != undefined)
4087 this.created = created;
4088 if(fields != undefined)
4089 this.fields = fields;
4090 if(tags != undefined)
4091 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
4092 else if(this.tags == undefined)
4093 this.tags = [];
4094 return this;
4097 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
4098 Tiddler.prototype.getTags = function()
4100 return String.encodeTiddlyLinkList(this.tags);
4103 // Test if a tiddler carries a tag
4104 Tiddler.prototype.isTagged = function(tag)
4106 return this.tags.indexOf(tag) != -1;
4109 // Static method to convert "\n" to newlines, "\s" to "\"
4110 Tiddler.unescapeLineBreaks = function(text)
4112 return text ? text.unescapeLineBreaks() : "";
4115 // Convert newlines to "\n", "\" to "\s"
4116 Tiddler.prototype.escapeLineBreaks = function()
4118 return this.text.escapeLineBreaks();
4121 // Updates the secondary information (like links[] array) after a change to a tiddler
4122 Tiddler.prototype.changed = function()
4124 this.links = [];
4125 var t = this.autoLinkWikiWords() ? 0 : 1;
4126 var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
4127 tiddlerLinkRegExp.lastIndex = 0;
4128 var formatMatch = tiddlerLinkRegExp.exec(this.text);
4129 while(formatMatch) {
4130 var lastIndex = tiddlerLinkRegExp.lastIndex;
4131 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
4132 // wikiWordLink
4133 if(formatMatch.index > 0) {
4134 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
4135 preRegExp.lastIndex = formatMatch.index-1;
4136 var preMatch = preRegExp.exec(this.text);
4137 if(preMatch.index != formatMatch.index-1)
4138 this.links.pushUnique(formatMatch[1]);
4139 } else {
4140 this.links.pushUnique(formatMatch[1]);
4143 else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
4144 this.links.pushUnique(formatMatch[3-t]);
4145 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
4146 this.links.pushUnique(formatMatch[4-t]);
4147 tiddlerLinkRegExp.lastIndex = lastIndex;
4148 formatMatch = tiddlerLinkRegExp.exec(this.text);
4150 this.linksUpdated = true;
4153 Tiddler.prototype.getSubtitle = function()
4155 var modifier = this.modifier;
4156 if(!modifier)
4157 modifier = config.messages.subtitleUnknown;
4158 var modified = this.modified;
4159 if(modified)
4160 modified = modified.toLocaleString();
4161 else
4162 modified = config.messages.subtitleUnknown;
4163 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
4166 Tiddler.prototype.isReadOnly = function()
4168 return readOnly;
4171 Tiddler.prototype.autoLinkWikiWords = function()
4173 return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
4176 Tiddler.prototype.generateFingerprint = function()
4178 return "0x" + Crypto.hexSha1Str(this.text);
4181 Tiddler.prototype.getServerType = function()
4183 var serverType = null;
4184 if(this.fields['server.type'])
4185 serverType = this.fields['server.type'];
4186 if(!serverType)
4187 serverType = this.fields['wikiformat'];
4188 if(serverType && !config.adaptors[serverType])
4189 serverType = null;
4190 return serverType;
4193 Tiddler.prototype.getAdaptor = function()
4195 var serverType = this.getServerType();
4196 return serverType ? new config.adaptors[serverType]() : null;
4199 //--
4200 //-- TiddlyWiki() object contains Tiddler()s
4201 //--
4203 function TiddlyWiki()
4205 var tiddlers = {}; // Hashmap by name of tiddlers
4206 this.tiddlersUpdated = false;
4207 this.namedNotifications = []; // Array of {name:,notify:} of notification functions
4208 this.notificationLevel = 0;
4209 this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
4210 this.clear = function() {
4211 tiddlers = {};
4212 this.setDirty(false);
4214 this.fetchTiddler = function(title) {
4215 var t = tiddlers[title];
4216 return t instanceof Tiddler ? t : null;
4218 this.deleteTiddler = function(title) {
4219 delete this.slices[title];
4220 delete tiddlers[title];
4222 this.addTiddler = function(tiddler) {
4223 delete this.slices[tiddler.title];
4224 tiddlers[tiddler.title] = tiddler;
4226 this.forEachTiddler = function(callback) {
4227 for(var t in tiddlers) {
4228 var tiddler = tiddlers[t];
4229 if(tiddler instanceof Tiddler)
4230 callback.call(this,t,tiddler);
4235 TiddlyWiki.prototype.setDirty = function(dirty)
4237 this.dirty = dirty;
4240 TiddlyWiki.prototype.isDirty = function()
4242 return this.dirty;
4245 TiddlyWiki.prototype.tiddlerExists = function(title)
4247 var t = this.fetchTiddler(title);
4248 return t != undefined;
4251 TiddlyWiki.prototype.isShadowTiddler = function(title)
4253 return typeof config.shadowTiddlers[title] == "string";
4256 TiddlyWiki.prototype.createTiddler = function(title)
4258 var tiddler = this.fetchTiddler(title);
4259 if(!tiddler) {
4260 tiddler = new Tiddler(title);
4261 this.addTiddler(tiddler);
4262 this.setDirty(true);
4264 return tiddler;
4267 TiddlyWiki.prototype.getTiddler = function(title)
4269 var t = this.fetchTiddler(title);
4270 if(t != undefined)
4271 return t;
4272 else
4273 return null;
4276 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
4278 if(!title)
4279 return defaultText;
4280 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
4281 var section = null;
4282 if(pos != -1) {
4283 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
4284 title = title.substr(0,pos);
4286 pos = title.indexOf(config.textPrimitives.sliceSeparator);
4287 if(pos != -1) {
4288 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
4289 if(slice)
4290 return slice;
4292 var tiddler = this.fetchTiddler(title);
4293 if(tiddler) {
4294 if(!section)
4295 return tiddler.text;
4296 var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)","mg");
4297 re.lastIndex = 0;
4298 var match = re.exec(tiddler.text);
4299 if(match) {
4300 var t = tiddler.text.substr(match.index+match[1].length);
4301 var re2 = /^!/mg;
4302 re2.lastIndex = 0;
4303 match = re2.exec(t); //# search for the next heading
4304 if(match)
4305 t = t.substr(0,match.index-1);//# don't include final \n
4306 return t;
4308 return defaultText;
4310 if(this.isShadowTiddler(title))
4311 return config.shadowTiddlers[title];
4312 if(defaultText != undefined)
4313 return defaultText;
4314 return null;
4317 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4319 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
4320 var text = this.getTiddlerText(title,null);
4321 if(text == null)
4322 return defaultText;
4323 var textOut = [];
4324 var lastPos = 0;
4325 do {
4326 var match = bracketRegExp.exec(text);
4327 if(match) {
4328 textOut.push(text.substr(lastPos,match.index-lastPos));
4329 if(match[1]) {
4330 if(depth <= 0)
4331 textOut.push(match[1]);
4332 else
4333 textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
4335 lastPos = match.index + match[0].length;
4336 } else {
4337 textOut.push(text.substr(lastPos));
4339 } while(match);
4340 return textOut.join("");
4343 TiddlyWiki.prototype.slicesRE = /(?:^([\'\/]{0,2})~?([\.\w]+)\:\1\s*([^\n]+)\s*$)|(?:^\|([\'\/]{0,2})~?([\.\w]+)\:?\4\|\s*([^\|\n]+)\s*\|$)/gm;
4345 // @internal
4346 TiddlyWiki.prototype.calcAllSlices = function(title)
4348 var slices = {};
4349 var text = this.getTiddlerText(title,"");
4350 this.slicesRE.lastIndex = 0;
4351 var m = this.slicesRE.exec(text);
4352 while(m) {
4353 if(m[2])
4354 slices[m[2]] = m[3];
4355 else
4356 slices[m[5]] = m[6];
4357 m = this.slicesRE.exec(text);
4359 return slices;
4362 // Returns the slice of text of the given name
4363 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4365 var slices = this.slices[title];
4366 if(!slices) {
4367 slices = this.calcAllSlices(title);
4368 this.slices[title] = slices;
4370 return slices[sliceName];
4373 // Build an hashmap of the specified named slices of a tiddler
4374 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
4376 var r = {};
4377 for(var t=0; t<sliceNames.length; t++) {
4378 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4379 if(slice)
4380 r[sliceNames[t]] = slice;
4382 return r;
4385 TiddlyWiki.prototype.suspendNotifications = function()
4387 this.notificationLevel--;
4390 TiddlyWiki.prototype.resumeNotifications = function()
4392 this.notificationLevel++;
4395 // Invoke the notification handlers for a particular tiddler
4396 TiddlyWiki.prototype.notify = function(title,doBlanket)
4398 if(!this.notificationLevel) {
4399 for(var t=0; t<this.namedNotifications.length; t++) {
4400 var n = this.namedNotifications[t];
4401 if((n.name == null && doBlanket) || (n.name == title))
4402 n.notify(title);
4407 // Invoke the notification handlers for all tiddlers
4408 TiddlyWiki.prototype.notifyAll = function()
4410 if(!this.notificationLevel) {
4411 for(var t=0; t<this.namedNotifications.length; t++) {
4412 var n = this.namedNotifications[t];
4413 if(n.name)
4414 n.notify(n.name);
4419 // Add a notification handler to a tiddler
4420 TiddlyWiki.prototype.addNotification = function(title,fn)
4422 for(var i=0; i<this.namedNotifications.length; i++) {
4423 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
4424 return this;
4426 this.namedNotifications.push({name: title, notify: fn});
4427 return this;
4430 TiddlyWiki.prototype.removeTiddler = function(title)
4432 var tiddler = this.fetchTiddler(title);
4433 if(tiddler) {
4434 this.deleteTiddler(title);
4435 this.notify(title,true);
4436 this.setDirty(true);
4440 // Reset the sync status of a freshly synced tiddler
4441 TiddlyWiki.prototype.resetTiddler = function(title)
4443 var tiddler = this.fetchTiddler(title);
4444 if(tiddler) {
4445 tiddler.clearChangeCount();
4446 this.notify(title,true);
4447 this.setDirty(true);
4451 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4453 var tiddler = this.fetchTiddler(title);
4454 if(tiddler) {
4455 var t = tiddler.tags.indexOf(tag);
4456 if(t != -1)
4457 tiddler.tags.splice(t,1);
4458 if(status)
4459 tiddler.tags.push(tag);
4460 tiddler.changed();
4461 tiddler.incChangeCount(title);
4462 this.notify(title,true);
4463 this.setDirty(true);
4467 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
4469 var tiddler = this.fetchTiddler(title);
4470 if(!tiddler)
4471 return;
4472 merge(tiddler.fields,fields);
4473 tiddler.changed();
4474 tiddler.incChangeCount(title);
4475 this.notify(title,true);
4476 this.setDirty(true);
4479 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
4481 var tiddler = this.fetchTiddler(title);
4482 if(tiddler) {
4483 created = created || tiddler.created; // Preserve created date
4484 this.deleteTiddler(title);
4485 } else {
4486 created = created || modified;
4487 tiddler = new Tiddler();
4489 tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
4490 this.addTiddler(tiddler);
4491 if(clearChangeCount)
4492 tiddler.clearChangeCount();
4493 else
4494 tiddler.incChangeCount();
4495 if(title != newTitle)
4496 this.notify(title,true);
4497 this.notify(newTitle,true);
4498 this.setDirty(true);
4499 return tiddler;
4502 TiddlyWiki.prototype.incChangeCount = function(title)
4504 var tiddler = this.fetchTiddler(title);
4505 if(tiddler)
4506 tiddler.incChangeCount();
4509 TiddlyWiki.prototype.getLoader = function()
4511 if(!this.loader)
4512 this.loader = new TW21Loader();
4513 return this.loader;
4516 TiddlyWiki.prototype.getSaver = function()
4518 if(!this.saver)
4519 this.saver = new TW21Saver();
4520 return this.saver;
4523 // Return all tiddlers formatted as an HTML string
4524 TiddlyWiki.prototype.allTiddlersAsHtml = function()
4526 return this.getSaver().externalize(store);
4529 // Load contents of a TiddlyWiki from an HTML DIV
4530 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
4532 this.idPrefix = idPrefix;
4533 var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
4534 if(!storeElem)
4535 return;
4536 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4537 this.setDirty(false);
4538 if(!noUpdate) {
4539 for(var i = 0;i<tiddlers.length; i++)
4540 tiddlers[i].changed();
4544 // Load contents of a TiddlyWiki from a string
4545 // Returns null if there's an error
4546 TiddlyWiki.prototype.importTiddlyWiki = function(text)
4548 var posDiv = locateStoreArea(text);
4549 if(!posDiv)
4550 return null;
4551 var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
4552 // Create the iframe
4553 var iframe = document.createElement("iframe");
4554 iframe.style.display = "none";
4555 document.body.appendChild(iframe);
4556 var doc = iframe.document;
4557 if(iframe.contentDocument)
4558 doc = iframe.contentDocument; // For NS6
4559 else if(iframe.contentWindow)
4560 doc = iframe.contentWindow.document; // For IE5.5 and IE6
4561 // Put the content in the iframe
4562 doc.open();
4563 doc.writeln(content);
4564 doc.close();
4565 // Load the content into a TiddlyWiki() object
4566 var storeArea = doc.getElementById("storeArea");
4567 this.loadFromDiv(storeArea,"store");
4568 // Get rid of the iframe
4569 iframe.parentNode.removeChild(iframe);
4570 return this;
4573 TiddlyWiki.prototype.updateTiddlers = function()
4575 this.tiddlersUpdated = true;
4576 this.forEachTiddler(function(title,tiddler) {
4577 tiddler.changed();
4581 // Return an array of tiddlers matching a search regular expression
4582 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
4584 var candidates = this.reverseLookup("tags",excludeTag,!!match);
4585 var results = [];
4586 for(var t=0; t<candidates.length; t++) {
4587 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
4588 results.push(candidates[t]);
4590 if(!sortField)
4591 sortField = "title";
4592 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4593 return results;
4596 // Returns a list of all tags in use
4597 // excludeTag - if present, excludes tags that are themselves tagged with excludeTag
4598 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
4599 TiddlyWiki.prototype.getTags = function(excludeTag)
4601 var results = [];
4602 this.forEachTiddler(function(title,tiddler) {
4603 for(var g=0; g<tiddler.tags.length; g++) {
4604 var tag = tiddler.tags[g];
4605 var n = true;
4606 for(var c=0; c<results.length; c++) {
4607 if(results[c][0] == tag) {
4608 n = false;
4609 results[c][1]++;
4612 if(n && excludeTag) {
4613 var t = this.fetchTiddler(tag);
4614 if(t && t.isTagged(excludeTag))
4615 n = false;
4617 if(n)
4618 results.push([tag,1]);
4621 results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
4622 return results;
4625 // Return an array of the tiddlers that are tagged with a given tag
4626 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
4628 return this.reverseLookup("tags",tag,true,sortField);
4631 // Return an array of the tiddlers that link to a given tiddler
4632 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
4634 if(!this.tiddlersUpdated)
4635 this.updateTiddlers();
4636 return this.reverseLookup("links",title,true,sortField);
4639 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
4640 // lookupMatch == true to match tiddlers, false to exclude tiddlers
4641 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
4643 var results = [];
4644 this.forEachTiddler(function(title,tiddler) {
4645 var f = !lookupMatch;
4646 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
4647 if(tiddler[lookupField][lookup] == lookupValue)
4648 f = lookupMatch;
4650 if(f)
4651 results.push(tiddler);
4653 if(!sortField)
4654 sortField = "title";
4655 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4656 return results;
4659 // Return the tiddlers as a sorted array
4660 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4662 var results = [];
4663 this.forEachTiddler(function(title,tiddler) {
4664 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4665 results.push(tiddler);
4667 if(field)
4668 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
4669 return results;
4672 // Return array of names of tiddlers that are referred to but not defined
4673 TiddlyWiki.prototype.getMissingLinks = function(sortField)
4675 if(!this.tiddlersUpdated)
4676 this.updateTiddlers();
4677 var results = [];
4678 this.forEachTiddler(function (title,tiddler) {
4679 if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
4680 return;
4681 for(var n=0; n<tiddler.links.length;n++) {
4682 var link = tiddler.links[n];
4683 if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
4684 results.pushUnique(link);
4687 results.sort();
4688 return results;
4691 // Return an array of names of tiddlers that are defined but not referred to
4692 TiddlyWiki.prototype.getOrphans = function()
4694 var results = [];
4695 this.forEachTiddler(function (title,tiddler) {
4696 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
4697 results.push(title);
4699 results.sort();
4700 return results;
4703 // Return an array of names of all the shadow tiddlers
4704 TiddlyWiki.prototype.getShadowed = function()
4706 var results = [];
4707 for(var t in config.shadowTiddlers) {
4708 if(typeof config.shadowTiddlers[t] == "string")
4709 results.push(t);
4711 results.sort();
4712 return results;
4715 // Return an array of tiddlers that have been touched since they were downloaded or created
4716 TiddlyWiki.prototype.getTouched = function()
4718 var results = [];
4719 this.forEachTiddler(function(title,tiddler) {
4720 if(tiddler.isTouched())
4721 results.push(tiddler);
4723 results.sort();
4724 return results;
4727 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
4728 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
4730 var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
4731 return t instanceof Tiddler ? t : null;
4734 // Filter a list of tiddlers
4735 TiddlyWiki.prototype.filterTiddlers = function(filter)
4737 var results = [];
4738 if(filter) {
4739 var tiddler;
4740 var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4741 var match = re.exec(filter);
4742 while(match) {
4743 if(match[1] || match[4]) {
4744 var title = match[1] || match[4];
4745 tiddler = this.fetchTiddler(title);
4746 if(tiddler) {
4747 results.pushUnique(tiddler);
4748 } else if(this.isShadowTiddler(title)) {
4749 tiddler = new Tiddler();
4750 tiddler.set(title,this.getTiddlerText(title));
4751 results.pushUnique(tiddler);
4753 } else if(match[2]) {
4754 switch(match[2]) {
4755 case "tag":
4756 var matched = this.getTaggedTiddlers(match[3]);
4757 for(var m = 0; m < matched.length; m++)
4758 results.pushUnique(matched[m]);
4759 break;
4760 case "sort":
4761 results = this.sortTiddlers(results,match[3]);
4762 break;
4765 match = re.exec(filter);
4768 return results;
4771 // Sort a list of tiddlers
4772 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4774 var asc = +1;
4775 switch(field.substr(0,1)) {
4776 case "-":
4777 asc = -1;
4778 // Note: this fall-through is intentional
4779 /*jsl:fallthru*/
4780 case "+":
4781 field = field.substr(1);
4782 break;
4784 if(TiddlyWiki.standardFieldAccess[field])
4785 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4786 else
4787 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
4788 return tiddlers;
4791 // Returns true if path is a valid field name (path),
4792 // i.e. a sequence of identifiers, separated by '.'
4793 TiddlyWiki.isValidFieldName = function(name)
4795 var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
4796 return match && (match[0] == name);
4799 // Throws an exception when name is not a valid field name.
4800 TiddlyWiki.checkFieldName = function(name)
4802 if(!TiddlyWiki.isValidFieldName(name))
4803 throw config.messages.invalidFieldName.format([name]);
4806 function StringFieldAccess(n,readOnly)
4808 this.set = readOnly ?
4809 function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
4810 function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
4811 this.get = function(t) {return t[n];};
4814 function DateFieldAccess(n)
4816 this.set = function(t,v) {
4817 var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
4818 if(d != t[n]) {
4819 t[n] = d; return true;
4822 this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
4825 function LinksFieldAccess(n)
4827 this.set = function(t,v) {
4828 var s = (typeof v == "string") ? v.readBracketedList() : v;
4829 if(s.toString() != t[n].toString()) {
4830 t[n] = s; return true;
4833 this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
4836 TiddlyWiki.standardFieldAccess = {
4837 // The set functions return true when setting the data has changed the value.
4838 "title": new StringFieldAccess("title",true),
4839 // Handle the "tiddler" field name as the title
4840 "tiddler": new StringFieldAccess("title",true),
4841 "text": new StringFieldAccess("text"),
4842 "modifier": new StringFieldAccess("modifier"),
4843 "modified": new DateFieldAccess("modified"),
4844 "created": new DateFieldAccess("created"),
4845 "tags": new LinksFieldAccess("tags")
4848 TiddlyWiki.isStandardField = function(name)
4850 return TiddlyWiki.standardFieldAccess[name] != undefined;
4853 // Sets the value of the given field of the tiddler to the value.
4854 // Setting an ExtendedField's value to null or undefined removes the field.
4855 // Setting a namespace to undefined removes all fields of that namespace.
4856 // The fieldName is case-insensitive.
4857 // All values will be converted to a string value.
4858 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
4860 TiddlyWiki.checkFieldName(fieldName);
4861 var t = this.resolveTiddler(tiddler);
4862 if(!t)
4863 return;
4864 fieldName = fieldName.toLowerCase();
4865 var isRemove = (value === undefined) || (value === null);
4866 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4867 if(accessor) {
4868 if(isRemove)
4869 // don't remove StandardFields
4870 return;
4871 var h = TiddlyWiki.standardFieldAccess[fieldName];
4872 if(!h.set(t,value))
4873 return;
4874 } else {
4875 var oldValue = t.fields[fieldName];
4876 if(isRemove) {
4877 if(oldValue !== undefined) {
4878 // deletes a single field
4879 delete t.fields[fieldName];
4880 } else {
4881 // no concrete value is defined for the fieldName
4882 // so we guess this is a namespace path.
4883 // delete all fields in a namespace
4884 var re = new RegExp('^'+fieldName+'\\.');
4885 var dirty = false;
4886 for(var n in t.fields) {
4887 if(n.match(re)) {
4888 delete t.fields[n];
4889 dirty = true;
4892 if(!dirty)
4893 return;
4895 } else {
4896 // the "normal" set case. value is defined (not null/undefined)
4897 // For convenience provide a nicer conversion Date->String
4898 value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
4899 if(oldValue == value)
4900 return;
4901 t.fields[fieldName] = value;
4904 // When we are here the tiddler/store really was changed.
4905 this.notify(t.title,true);
4906 if(!fieldName.match(/^temp\./))
4907 this.setDirty(true);
4910 // Returns the value of the given field of the tiddler.
4911 // The fieldName is case-insensitive.
4912 // Will only return String values (or undefined).
4913 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
4915 var t = this.resolveTiddler(tiddler);
4916 if(!t)
4917 return undefined;
4918 fieldName = fieldName.toLowerCase();
4919 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4920 if(accessor) {
4921 return accessor.get(t);
4923 return t.fields[fieldName];
4926 // Calls the callback function for every field in the tiddler.
4927 // When callback function returns a non-false value the iteration stops
4928 // and that value is returned.
4929 // The order of the fields is not defined.
4930 // @param callback a function(tiddler,fieldName,value).
4931 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
4933 var t = this.resolveTiddler(tiddler);
4934 if(!t)
4935 return undefined;
4936 var n,result;
4937 for(n in t.fields) {
4938 result = callback(t,n,t.fields[n]);
4939 if(result)
4940 return result;
4942 if(onlyExtendedFields)
4943 return undefined;
4944 for(n in TiddlyWiki.standardFieldAccess) {
4945 if(n == "tiddler")
4946 // even though the "title" field can also be referenced through the name "tiddler"
4947 // we only visit this field once.
4948 continue;
4949 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4950 if(result)
4951 return result;
4953 return undefined;
4956 //--
4957 //-- Story functions
4958 //--
4960 function Story(containerId,idPrefix)
4962 this.container = containerId;
4963 this.idPrefix = idPrefix;
4964 this.highlightRegExp = null;
4965 this.tiddlerId = function(title) {
4966 var id = this.idPrefix + title;
4967 return id==this.container ? this.idPrefix + "_" + title : id;
4969 this.containerId = function() {
4970 return this.container;
4974 Story.prototype.getTiddler = function(title)
4976 return document.getElementById(this.tiddlerId(title));
4979 Story.prototype.getContainer = function()
4981 return document.getElementById(this.containerId());
4984 Story.prototype.forEachTiddler = function(fn)
4986 var place = this.getContainer();
4987 if(!place)
4988 return;
4989 var e = place.firstChild;
4990 while(e) {
4991 var n = e.nextSibling;
4992 var title = e.getAttribute("tiddler");
4993 fn.call(this,title,e);
4994 e = n;
4998 Story.prototype.displayDefaultTiddlers = function()
5000 this.displayTiddlers(null,store.filterTiddlers(store.getTiddlerText("DefaultTiddlers")));
5003 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
5005 for(var t = titles.length-1;t>=0;t--)
5006 this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
5009 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle,animationSrc)
5011 var title = (tiddler instanceof Tiddler) ? tiddler.title : tiddler;
5012 var tiddlerElem = this.getTiddler(title);
5013 if(tiddlerElem) {
5014 if(toggle)
5015 this.closeTiddler(title,true);
5016 else
5017 this.refreshTiddler(title,template,false,customFields);
5018 } else {
5019 var place = this.getContainer();
5020 var before = this.positionTiddler(srcElement);
5021 tiddlerElem = this.createTiddler(place,before,title,template,customFields);
5023 if(animationSrc && typeof animationSrc !== "string") {
5024 srcElement = animationSrc;
5026 if(srcElement && typeof srcElement !== "string") {
5027 if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
5028 anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
5029 else
5030 window.scrollTo(0,ensureVisible(tiddlerElem));
5034 Story.prototype.positionTiddler = function(srcElement)
5036 var place = this.getContainer();
5037 var before = null;
5038 if(typeof srcElement == "string") {
5039 switch(srcElement) {
5040 case "top":
5041 before = place.firstChild;
5042 break;
5043 case "bottom":
5044 before = null;
5045 break;
5047 } else {
5048 var after = this.findContainingTiddler(srcElement);
5049 if(after == null) {
5050 before = place.firstChild;
5051 } else if(after.nextSibling) {
5052 before = after.nextSibling;
5053 if(before.nodeType != 1)
5054 before = null;
5057 return before;
5060 Story.prototype.createTiddler = function(place,before,title,template,customFields)
5062 var tiddlerElem = createTiddlyElement(null,"div",this.tiddlerId(title),"tiddler");
5063 tiddlerElem.setAttribute("refresh","tiddler");
5064 if(customFields)
5065 tiddlerElem.setAttribute("tiddlyFields",customFields);
5066 place.insertBefore(tiddlerElem,before);
5067 var defaultText = null;
5068 if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
5069 defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
5070 this.refreshTiddler(title,template,false,customFields,defaultText);
5071 return tiddlerElem;
5074 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
5076 var tiddler = new Tiddler(title);
5077 tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields || {});
5078 var serverType = tiddler.getServerType();
5079 var host = tiddler.fields['server.host'];
5080 var workspace = tiddler.fields['server.workspace'];
5081 if(!serverType || !host)
5082 return null;
5083 var sm = new SyncMachine(serverType,{
5084 start: function() {
5085 return this.openHost(host,"openWorkspace");
5087 openWorkspace: function() {
5088 return this.openWorkspace(workspace,"getTiddler");
5090 getTiddler: function() {
5091 return this.getTiddler(title,"onGetTiddler");
5093 onGetTiddler: function(context) {
5094 var tiddler = context.tiddler;
5095 if(tiddler && tiddler.text) {
5096 var downloaded = new Date();
5097 if(!tiddler.created)
5098 tiddler.created = downloaded;
5099 if(!tiddler.modified)
5100 tiddler.modified = tiddler.created;
5101 store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
5102 autoSaveChanges();
5104 delete this;
5105 return true;
5107 error: function(message) {
5108 displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
5111 sm.go();
5112 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
5115 Story.prototype.chooseTemplateForTiddler = function(title,template)
5117 if(!template)
5118 template = DEFAULT_VIEW_TEMPLATE;
5119 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
5120 template = config.tiddlerTemplates[template];
5121 return template;
5124 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
5126 return store.getRecursiveTiddlerText(template,null,10);
5129 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
5131 var tiddlerElem = this.getTiddler(title);
5132 if(tiddlerElem) {
5133 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
5134 return tiddlerElem;
5135 template = this.chooseTemplateForTiddler(title,template);
5136 var currTemplate = tiddlerElem.getAttribute("template");
5137 if((template != currTemplate) || force) {
5138 var tiddler = store.getTiddler(title);
5139 if(!tiddler) {
5140 tiddler = new Tiddler();
5141 if(store.isShadowTiddler(title)) {
5142 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
5143 } else {
5144 var text = template=="EditTemplate" ?
5145 config.views.editor.defaultText.format([title]) :
5146 config.views.wikified.defaultText.format([title]);
5147 text = defaultText || text;
5148 var fields = customFields ? customFields.decodeHashMap() : null;
5149 tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
5152 tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
5153 tiddlerElem.setAttribute("tiddler",title);
5154 tiddlerElem.setAttribute("template",template);
5155 tiddlerElem.onmouseover = this.onTiddlerMouseOver;
5156 tiddlerElem.onmouseout = this.onTiddlerMouseOut;
5157 tiddlerElem.ondblclick = this.onTiddlerDblClick;
5158 tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
5159 tiddlerElem.innerHTML = this.getTemplateForTiddler(title,template,tiddler);
5160 applyHtmlMacros(tiddlerElem,tiddler);
5161 if(store.getTaggedTiddlers(title).length > 0)
5162 addClass(tiddlerElem,"isTag");
5163 else
5164 removeClass(tiddlerElem,"isTag");
5165 if(store.tiddlerExists(title)) {
5166 removeClass(tiddlerElem,"shadow");
5167 removeClass(tiddlerElem,"missing");
5168 } else {
5169 addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow" : "missing");
5171 if(customFields)
5172 this.addCustomFields(tiddlerElem,customFields);
5173 forceReflow();
5176 return tiddlerElem;
5179 Story.prototype.addCustomFields = function(place,customFields)
5181 var fields = customFields.decodeHashMap();
5182 var w = document.createElement("div");
5183 w.style.display = "none";
5184 place.appendChild(w);
5185 for(var t in fields) {
5186 var e = document.createElement("input");
5187 e.setAttribute("type","text");
5188 e.setAttribute("value",fields[t]);
5189 w.appendChild(e);
5190 e.setAttribute("edit",t);
5194 Story.prototype.refreshAllTiddlers = function(force)
5196 var e = this.getContainer().firstChild;
5197 while(e) {
5198 var template = e.getAttribute("template");
5199 if(template && e.getAttribute("dirty") != "true") {
5200 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : template,true);
5202 e = e.nextSibling;
5206 Story.prototype.onTiddlerMouseOver = function(e)
5208 if(window.addClass instanceof Function)
5209 addClass(this,"selected");
5212 Story.prototype.onTiddlerMouseOut = function(e)
5214 if(window.removeClass instanceof Function)
5215 removeClass(this,"selected");
5218 Story.prototype.onTiddlerDblClick = function(ev)
5220 var e = ev || window.event;
5221 var target = resolveTarget(e);
5222 if(target && target.nodeName.toLowerCase() != "input" && target.nodeName.toLowerCase() != "textarea") {
5223 if(document.selection && document.selection.empty)
5224 document.selection.empty();
5225 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5226 e.cancelBubble = true;
5227 if(e.stopPropagation) e.stopPropagation();
5228 return true;
5230 return false;
5233 Story.prototype.onTiddlerKeyPress = function(ev)
5235 var e = ev || window.event;
5236 clearMessage();
5237 var consume = false;
5238 var title = this.getAttribute("tiddler");
5239 var target = resolveTarget(e);
5240 switch(e.keyCode) {
5241 case 9: // Tab
5242 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
5243 replaceSelection(target,String.fromCharCode(9));
5244 consume = true;
5246 if(config.isOpera) {
5247 target.onblur = function() {
5248 this.focus();
5249 this.onblur = null;
5252 break;
5253 case 13: // Ctrl-Enter
5254 case 10: // Ctrl-Enter on IE PC
5255 case 77: // Ctrl-Enter is "M" on some platforms
5256 if(e.ctrlKey) {
5257 blurElement(this);
5258 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5259 consume = true;
5261 break;
5262 case 27: // Escape
5263 blurElement(this);
5264 config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
5265 consume = true;
5266 break;
5268 e.cancelBubble = consume;
5269 if(consume) {
5270 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
5271 e.returnValue = true; // Cancel The Event in IE
5272 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
5274 return !consume;
5277 Story.prototype.getTiddlerField = function(title,field)
5279 var tiddlerElem = this.getTiddler(title);
5280 var e = null;
5281 if(tiddlerElem ) {
5282 var children = tiddlerElem.getElementsByTagName("*");
5283 for(var t=0; t<children.length; t++) {
5284 var c = children[t];
5285 if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
5286 if(!e)
5287 e = c;
5288 if(c.getAttribute("edit") == field)
5289 e = c;
5293 return e;
5296 Story.prototype.focusTiddler = function(title,field)
5298 var e = this.getTiddlerField(title,field);
5299 if(e) {
5300 e.focus();
5301 e.select();
5305 Story.prototype.blurTiddler = function(title)
5307 var tiddlerElem = this.getTiddler(title);
5308 if(tiddlerElem && tiddlerElem.focus && tiddlerElem.blur) {
5309 tiddlerElem.focus();
5310 tiddlerElem.blur();
5314 Story.prototype.setTiddlerField = function(title,tag,mode,field)
5316 var c = this.getTiddlerField(title,field);
5317 var tags = c.value.readBracketedList();
5318 tags.setItem(tag,mode);
5319 c.value = String.encodeTiddlyLinkList(tags);
5322 Story.prototype.setTiddlerTag = function(title,tag,mode)
5324 this.setTiddlerField(title,tag,mode,"tags");
5327 Story.prototype.closeTiddler = function(title,animate,unused)
5329 var tiddlerElem = this.getTiddler(title);
5330 if(tiddlerElem) {
5331 clearMessage();
5332 this.scrubTiddler(tiddlerElem);
5333 if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
5334 anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
5335 else {
5336 removeNode(tiddlerElem);
5337 forceReflow();
5342 Story.prototype.scrubTiddler = function(tiddlerElem)
5344 tiddlerElem.id = null;
5347 Story.prototype.setDirty = function(title,dirty)
5349 var tiddlerElem = this.getTiddler(title);
5350 if(tiddlerElem)
5351 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
5354 Story.prototype.isDirty = function(title)
5356 var tiddlerElem = this.getTiddler(title);
5357 if(tiddlerElem)
5358 return tiddlerElem.getAttribute("dirty") == "true";
5359 return null;
5362 Story.prototype.areAnyDirty = function()
5364 var r = false;
5365 this.forEachTiddler(function(title,element) {
5366 if(this.isDirty(title))
5367 r = true;
5369 return r;
5372 Story.prototype.closeAllTiddlers = function(exclude)
5374 clearMessage();
5375 this.forEachTiddler(function(title,element) {
5376 if((title != exclude) && element.getAttribute("dirty") != "true")
5377 this.closeTiddler(title);
5379 window.scrollTo(0,ensureVisible(this.container));
5382 Story.prototype.isEmpty = function()
5384 var place = this.getContainer();
5385 return place && place.firstChild == null;
5388 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
5390 this.closeAllTiddlers();
5391 highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
5392 var matches = store.search(highlightHack,"title","excludeSearch");
5393 this.displayTiddlers(null,matches);
5394 highlightHack = null;
5395 var q = useRegExp ? "/" : "'";
5396 if(matches.length > 0)
5397 displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
5398 else
5399 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5402 Story.prototype.findContainingTiddler = function(e)
5404 while(e && !hasClass(e,"tiddler"))
5405 e = e.parentNode;
5406 return e;
5409 Story.prototype.gatherSaveFields = function(e,fields)
5411 if(e && e.getAttribute) {
5412 var f = e.getAttribute("edit");
5413 if(f)
5414 fields[f] = e.value.replace(/\r/mg,"");
5415 if(e.hasChildNodes()) {
5416 var c = e.childNodes;
5417 for(var t=0; t<c.length; t++)
5418 this.gatherSaveFields(c[t],fields);
5423 Story.prototype.hasChanges = function(title)
5425 var e = this.getTiddler(title);
5426 if(e) {
5427 var fields = {};
5428 this.gatherSaveFields(e,fields);
5429 var tiddler = store.fetchTiddler(title);
5430 if(!tiddler)
5431 return false;
5432 for(var n in fields) {
5433 if(store.getValue(title,n) != fields[n])
5434 return true;
5437 return false;
5440 Story.prototype.saveTiddler = function(title,minorUpdate)
5442 var tiddlerElem = this.getTiddler(title);
5443 if(tiddlerElem) {
5444 var fields = {};
5445 this.gatherSaveFields(tiddlerElem,fields);
5446 var newTitle = fields.title || title;
5447 if(!store.tiddlerExists(newTitle))
5448 newTitle = newTitle.trim();
5449 if(store.tiddlerExists(newTitle) && newTitle != title) {
5450 if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
5451 return null;
5453 if(newTitle != title)
5454 this.closeTiddler(newTitle,false);
5455 tiddlerElem.id = this.tiddlerId(newTitle);
5456 tiddlerElem.setAttribute("tiddler",newTitle);
5457 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
5458 tiddlerElem.setAttribute("dirty","false");
5459 if(config.options.chkForceMinorUpdate)
5460 minorUpdate = !minorUpdate;
5461 if(!store.tiddlerExists(newTitle))
5462 minorUpdate = false;
5463 var newDate = new Date();
5464 var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : config.defaultCustomFields);
5465 for(var n in fields) {
5466 if(!TiddlyWiki.isStandardField(n))
5467 extendedFields[n] = fields[n];
5469 var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
5470 autoSaveChanges(null,[tiddler]);
5471 return newTitle;
5473 return null;
5476 Story.prototype.permaView = function()
5478 var links = [];
5479 this.forEachTiddler(function(title,element) {
5480 links.push(String.encodeTiddlyLink(title));
5482 var t = encodeURIComponent(links.join(" "));
5483 if(t == "")
5484 t = "#";
5485 if(window.location.hash != t)
5486 window.location.hash = t;
5489 Story.prototype.switchTheme = function(theme)
5491 if(safeMode)
5492 return;
5494 var isAvailable = function(title) {
5495 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5496 if(s!=-1)
5497 title = title.substr(0,s);
5498 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5501 var getSlice = function(theme,slice) {
5502 var r;
5503 if(readOnly)
5504 r = store.getTiddlerSlice(theme,slice+"ReadOnly") || store.getTiddlerSlice(theme,"Web"+slice);
5505 r = r || store.getTiddlerSlice(theme,slice);
5506 if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
5507 r = theme + r;
5508 return isAvailable(r) ? r : slice;
5511 var replaceNotification = function(i,name,theme,slice) {
5512 var newName = getSlice(theme,slice);
5513 if(name!=newName && store.namedNotifications[i].name==name) {
5514 store.namedNotifications[i].name = newName;
5515 return newName;
5517 return name;
5520 var pt = config.refresherData.pageTemplate;
5521 var vi = DEFAULT_VIEW_TEMPLATE;
5522 var vt = config.tiddlerTemplates[vi];
5523 var ei = DEFAULT_EDIT_TEMPLATE;
5524 var et = config.tiddlerTemplates[ei];
5526 for(var i=0; i<config.notifyTiddlers.length; i++) {
5527 var name = config.notifyTiddlers[i].name;
5528 switch(name) {
5529 case "PageTemplate":
5530 config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
5531 break;
5532 case "StyleSheet":
5533 removeStyleSheet(config.refresherData.styleSheet);
5534 config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
5535 break;
5536 case "ColorPalette":
5537 config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
5538 break;
5539 default:
5540 break;
5543 config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate");
5544 config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate");
5545 if(!startingUp) {
5546 if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
5547 refreshAll();
5548 this.refreshAllTiddlers(true);
5549 } else {
5550 setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet,"",10),config.refreshers.styleSheet);
5552 config.options.txtTheme = theme;
5553 saveOptionCookie("txtTheme");
5557 //--
5558 //-- Backstage
5559 //--
5561 var backstage = {
5562 area: null,
5563 toolbar: null,
5564 button: null,
5565 showButton: null,
5566 hideButton: null,
5567 cloak: null,
5568 panel: null,
5569 panelBody: null,
5570 panelFooter: null,
5571 currTabName: null,
5572 currTabElem: null,
5573 content: null,
5575 init: function() {
5576 var cmb = config.messages.backstage;
5577 this.area = document.getElementById("backstageArea");
5578 this.toolbar = document.getElementById("backstageToolbar");
5579 this.button = document.getElementById("backstageButton");
5580 this.button.style.display = "block";
5581 var t = cmb.open.text + " " + glyph("bentArrowLeft");
5582 this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
5583 function(e) {backstage.show(); return false;},null,"backstageShow");
5584 t = glyph("bentArrowRight") + " " + cmb.close.text;
5585 this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
5586 function(e) {backstage.hide(); return false;},null,"backstageHide");
5587 this.cloak = document.getElementById("backstageCloak");
5588 this.panel = document.getElementById("backstagePanel");
5589 this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
5590 this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
5591 this.cloak.onmousedown = function(e) {backstage.switchTab(null);};
5592 createTiddlyText(this.toolbar,cmb.prompt);
5593 for(t=0; t<config.backstageTasks.length; t++) {
5594 var taskName = config.backstageTasks[t];
5595 var task = config.tasks[taskName];
5596 var handler = task.action ? this.onClickCommand : this.onClickTab;
5597 var text = task.text + (task.action ? "" : glyph("downTriangle"));
5598 var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
5599 btn.setAttribute("task",taskName);
5600 addClass(btn,task.action ? "backstageAction" : "backstageTask");
5602 this.content = document.getElementById("contentWrapper");
5603 if(config.options.chkBackstage)
5604 this.show();
5605 else
5606 this.hide();
5609 isVisible: function() {
5610 return this.area ? this.area.style.display == "block" : false;
5613 show: function() {
5614 this.area.style.display = "block";
5615 if(anim && config.options.chkAnimate) {
5616 backstage.toolbar.style.left = findWindowWidth() + "px";
5617 var p = [{style: "left", start: findWindowWidth(), end: 0, template: "%0px"}];
5618 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
5619 } else {
5620 backstage.area.style.left = "0px";
5622 this.showButton.style.display = "none";
5623 this.hideButton.style.display = "block";
5624 config.options.chkBackstage = true;
5625 saveOptionCookie("chkBackstage");
5626 addClass(this.content,"backstageVisible");
5629 hide: function() {
5630 if(this.currTabElem) {
5631 this.switchTab(null);
5632 } else {
5633 backstage.toolbar.style.left = "0px";
5634 if(anim && config.options.chkAnimate) {
5635 var p = [{style: "left", start: 0, end: findWindowWidth(), template: "%0px"}];
5636 var c = function(element,properties) {backstage.area.style.display = "none";};
5637 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
5638 } else {
5639 this.area.style.display = "none";
5641 this.showButton.style.display = "block";
5642 this.hideButton.style.display = "none";
5643 config.options.chkBackstage = false;
5644 saveOptionCookie("chkBackstage");
5645 removeClass(this.content,"backstageVisible");
5649 onClickCommand: function(e) {
5650 var task = config.tasks[this.getAttribute("task")];
5651 displayMessage(task);
5652 if(task.action) {
5653 backstage.switchTab(null);
5654 task.action();
5656 return false;
5659 onClickTab: function(e) {
5660 backstage.switchTab(this.getAttribute("task"));
5661 return false;
5664 // Switch to a given tab, or none if null is passed
5665 switchTab: function(tabName) {
5666 var tabElem = null;
5667 var e = this.toolbar.firstChild;
5668 while(e)
5670 if(e.getAttribute && e.getAttribute("task") == tabName)
5671 tabElem = e;
5672 e = e.nextSibling;
5674 if(tabName == backstage.currTabName)
5675 return;
5676 if(backstage.currTabElem) {
5677 removeClass(this.currTabElem,"backstageSelTab");
5679 if(tabElem && tabName) {
5680 backstage.preparePanel();
5681 addClass(tabElem,"backstageSelTab");
5682 var task = config.tasks[tabName];
5683 wikify(task.content,backstage.panelBody,null,null);
5684 backstage.showPanel();
5685 } else if(backstage.currTabElem) {
5686 backstage.hidePanel();
5688 backstage.currTabName = tabName;
5689 backstage.currTabElem = tabElem;
5692 isPanelVisible: function() {
5693 return backstage.panel ? backstage.panel.style.display == "block" : false;
5696 preparePanel: function() {
5697 backstage.cloak.style.height = findWindowHeight() + "px";
5698 backstage.cloak.style.display = "block";
5699 removeChildren(backstage.panelBody);
5700 return backstage.panelBody;
5703 showPanel: function() {
5704 backstage.panel.style.display = "block";
5705 if(anim && config.options.chkAnimate) {
5706 backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
5707 var p = [{style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}];
5708 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
5709 } else {
5710 backstage.panel.style.top = "0px";
5712 return backstage.panelBody;
5715 hidePanel: function() {
5716 if(backstage.currTabElem)
5717 removeClass(backstage.currTabElem,"backstageSelTab");
5718 backstage.currTabElem = null;
5719 backstage.currTabName = null;
5720 if(anim && config.options.chkAnimate) {
5721 var p = [
5722 {style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
5723 {style: "display", atEnd: "none"}
5725 var c = function(element,properties) {backstage.cloak.style.display = "none";};
5726 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
5727 } else {
5728 backstage.panel.style.display = "none";
5729 backstage.cloak.style.display = "none";
5734 config.macros.backstage = {};
5736 config.macros.backstage.handler = function(place,macroName,params)
5738 var backstageTask = config.tasks[params[0]];
5739 if(backstageTask)
5740 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5743 //--
5744 //-- ImportTiddlers macro
5745 //--
5747 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5749 if(readOnly) {
5750 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
5751 return;
5753 var w = new Wizard();
5754 w.createWizard(place,this.wizardTitle);
5755 this.restart(w);
5758 config.macros.importTiddlers.onCancel = function(e)
5760 var wizard = new Wizard(this);
5761 var place = wizard.clear();
5762 config.macros.importTiddlers.restart(wizard);
5763 return false;
5766 config.macros.importTiddlers.onClose = function(e)
5768 backstage.hidePanel();
5769 return false;
5772 config.macros.importTiddlers.restart = function(wizard)
5774 wizard.addStep(this.step1Title,this.step1Html);
5775 var s = wizard.getElement("selTypes");
5776 for(var t in config.adaptors) {
5777 var e = createTiddlyElement(s,"option",null,null,config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
5778 e.value = t;
5780 if(config.defaultAdaptor)
5781 s.value = config.defaultAdaptor;
5782 s = wizard.getElement("selFeeds");
5783 var feeds = this.getFeeds();
5784 for(t in feeds) {
5785 e = createTiddlyElement(s,"option",null,null,t);
5786 e.value = t;
5788 wizard.setValue("feeds",feeds);
5789 s.onchange = config.macros.importTiddlers.onFeedChange;
5790 var fileInput = wizard.getElement("txtBrowse");
5791 fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
5792 fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
5793 wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
5794 wizard.formElem.action = "javascript:;";
5795 wizard.formElem.onsubmit = function() {
5796 if(this.txtPath.value.length)
5797 this.lastChild.firstChild.onclick();
5801 config.macros.importTiddlers.getFeeds = function()
5803 var feeds = {};
5804 var tagged = store.getTaggedTiddlers("systemServer","title");
5805 for(var t=0; t<tagged.length; t++) {
5806 var title = tagged[t].title;
5807 var serverType = store.getTiddlerSlice(title,"Type");
5808 if(!serverType)
5809 serverType = "file";
5810 feeds[title] = {title: title,
5811 url: store.getTiddlerSlice(title,"URL"),
5812 workspace: store.getTiddlerSlice(title,"Workspace"),
5813 workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
5814 tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
5815 serverType: serverType,
5816 description: store.getTiddlerSlice(title,"Description")};
5818 return feeds;
5821 config.macros.importTiddlers.onFeedChange = function(e)
5823 var wizard = new Wizard(this);
5824 var selTypes = wizard.getElement("selTypes");
5825 var fileInput = wizard.getElement("txtPath");
5826 var feeds = wizard.getValue("feeds");
5827 var f = feeds[this.value];
5828 if(f) {
5829 selTypes.value = f.serverType;
5830 fileInput.value = f.url;
5831 wizard.setValue("feedName",f.serverType);
5832 wizard.setValue("feedHost",f.url);
5833 wizard.setValue("feedWorkspace",f.workspace);
5834 wizard.setValue("feedWorkspaceList",f.workspaceList);
5835 wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
5837 return false;
5840 config.macros.importTiddlers.onBrowseChange = function(e)
5842 var wizard = new Wizard(this);
5843 var fileInput = wizard.getElement("txtPath");
5844 fileInput.value = config.macros.importTiddlers.getURLFromLocalPath(this.value);
5845 var serverType = wizard.getElement("selTypes");
5846 serverType.value = "file";
5847 return true;
5850 config.macros.importTiddlers.getURLFromLocalPath = function(v)
5852 if(!v||!v.length)
5853 return v;
5854 v = v.replace(/\\/g,"/"); // use "/" for cross-platform consistency
5855 var u;
5856 var t = v.split(":");
5857 var p = t[1]||t[0]; // remove drive letter (if any)
5858 if (t[1] && (t[0]=="http"||t[0]=="https"||t[0]=="file")) {
5859 u = v;
5860 } else if(p.substr(0,1)=="/") {
5861 u = document.location.protocol + "//" + document.location.hostname + (t[1] ? "/" : "") + v;
5862 } else {
5863 var c = document.location.href.replace(/\\/g,"/");
5864 var pos = c.lastIndexOf("/");
5865 if (pos!=-1)
5866 c = c.substr(0,pos); // remove filename
5867 u = c + "/" + p;
5869 return u;
5872 config.macros.importTiddlers.onOpen = function(e)
5874 var wizard = new Wizard(this);
5875 var fileInput = wizard.getElement("txtPath");
5876 var url = fileInput.value;
5877 var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
5878 var adaptor = new config.adaptors[serverType]();
5879 wizard.setValue("adaptor",adaptor);
5880 wizard.setValue("serverType",serverType);
5881 wizard.setValue("host",url);
5882 var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
5883 if(ret !== true)
5884 displayMessage(ret);
5885 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
5886 return false;
5889 config.macros.importTiddlers.onOpenHost = function(context,wizard)
5891 var adaptor = wizard.getValue("adaptor");
5892 if(context.status !== true)
5893 displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
5894 var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
5895 if(ret !== true)
5896 displayMessage(ret);
5897 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
5900 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
5902 if(context.status !== true)
5903 displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
5904 wizard.setValue("context",context);
5905 var workspace = wizard.getValue("feedWorkspace");
5906 if(!workspace && context.workspaces.length==1)
5907 workspace = context.workspaces[0].title;
5908 if(workspace) {
5909 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5910 if(ret !== true)
5911 displayMessage(ret);
5912 wizard.setValue("workspace",workspace);
5913 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5914 return;
5916 wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
5917 var s = wizard.getElement("selWorkspace");
5918 s.onchange = config.macros.importTiddlers.onWorkspaceChange;
5919 for(var t=0; t<context.workspaces.length; t++) {
5920 var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
5921 e.value = context.workspaces[t].title;
5923 var workspaceList = wizard.getValue("feedWorkspaceList");
5924 if(workspaceList) {
5925 var list = workspaceList.parseParams("workspace",null,false,true);
5926 for(var n=1; n<list.length; n++) {
5927 if(context.workspaces.findByField("title",list[n].value) == null) {
5928 e = createTiddlyElement(s,"option",null,null,list[n].value);
5929 e.value = list[n].value;
5933 if(workspace) {
5934 t = wizard.getElement("txtWorkspace");
5935 t.value = workspace;
5937 wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
5940 config.macros.importTiddlers.onWorkspaceChange = function(e)
5942 var wizard = new Wizard(this);
5943 var t = wizard.getElement("txtWorkspace");
5944 t.value = this.value;
5945 this.selectedIndex = 0;
5946 return false;
5949 config.macros.importTiddlers.onChooseWorkspace = function(e)
5951 var wizard = new Wizard(this);
5952 var adaptor = wizard.getValue("adaptor");
5953 var workspace = wizard.getElement("txtWorkspace").value;
5954 wizard.setValue("workspace",workspace);
5955 var context = wizard.getValue("context");
5956 var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5957 if(ret !== true)
5958 displayMessage(ret);
5959 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5960 return false;
5963 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
5965 if(context.status !== true)
5966 displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
5967 var adaptor = wizard.getValue("adaptor");
5968 var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
5969 if(ret !== true)
5970 displayMessage(ret);
5971 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
5974 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
5976 if(context.status !== true) {
5977 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.errorGettingTiddlerList);
5978 return;
5980 // Extract data for the listview
5981 var listedTiddlers = [];
5982 if(context.tiddlers) {
5983 for(var n=0; n<context.tiddlers.length; n++) {
5984 var tiddler = context.tiddlers[n];
5985 listedTiddlers.push({
5986 title: tiddler.title,
5987 modified: tiddler.modified,
5988 modifier: tiddler.modifier,
5989 text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
5990 tags: tiddler.tags,
5991 size: tiddler.text ? tiddler.text.length : 0,
5992 tiddler: tiddler
5996 listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5997 // Display the listview
5998 wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
5999 var markList = wizard.getElement("markList");
6000 var listWrapper = document.createElement("div");
6001 markList.parentNode.insertBefore(listWrapper,markList);
6002 var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
6003 wizard.setValue("listView",listView);
6004 var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
6005 txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
6006 wizard.setButtons([
6007 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
6008 {caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
6012 config.macros.importTiddlers.generateSystemServerName = function(wizard)
6014 var serverType = wizard.getValue("serverType");
6015 var host = wizard.getValue("host");
6016 var workspace = wizard.getValue("workspace");
6017 var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
6018 return pattern.format([serverType,host,workspace]);
6021 config.macros.importTiddlers.saveServerTiddler = function(wizard)
6023 var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
6024 if(store.tiddlerExists(txtSaveTiddler)) {
6025 if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
6026 return;
6027 store.suspendNotifications();
6028 store.removeTiddler(txtSaveTiddler);
6029 store.resumeNotifications();
6031 var serverType = wizard.getValue("serverType");
6032 var host = wizard.getValue("host");
6033 var workspace = wizard.getValue("workspace");
6034 var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
6035 store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
6038 config.macros.importTiddlers.doImport = function(e)
6040 var wizard = new Wizard(this);
6041 if(wizard.getElement("chkSave").checked)
6042 config.macros.importTiddlers.saveServerTiddler(wizard);
6043 var chkSync = wizard.getElement("chkSync").checked;
6044 wizard.setValue("sync",chkSync);
6045 var listView = wizard.getValue("listView");
6046 var rowNames = ListView.getSelectedRows(listView);
6047 var adaptor = wizard.getValue("adaptor");
6048 var overwrite = [];
6049 var t;
6050 for(t=0; t<rowNames.length; t++) {
6051 if(store.tiddlerExists(rowNames[t]))
6052 overwrite.push(rowNames[t]);
6054 if(overwrite.length > 0) {
6055 if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
6056 return false;
6058 wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
6059 for(t=0; t<rowNames.length; t++) {
6060 var link = document.createElement("div");
6061 createTiddlyLink(link,rowNames[t],true);
6062 var place = wizard.getElement("markReport");
6063 place.parentNode.insertBefore(link,place);
6065 wizard.setValue("remainingImports",rowNames.length);
6066 wizard.setButtons([
6067 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
6068 ],config.macros.importTiddlers.statusDoingImport);
6069 for(t=0; t<rowNames.length; t++) {
6070 var context = {};
6071 context.allowSynchronous = true;
6072 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
6074 return false;
6077 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
6079 if(!context.status)
6080 displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
6081 var tiddler = context.tiddler;
6082 store.suspendNotifications();
6083 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
6084 if(!wizard.getValue("sync")) {
6085 store.setValue(tiddler.title,'server',null);
6087 store.resumeNotifications();
6088 if(!context.isSynchronous)
6089 store.notify(tiddler.title,true);
6090 var remainingImports = wizard.getValue("remainingImports")-1;
6091 wizard.setValue("remainingImports",remainingImports);
6092 if(remainingImports == 0) {
6093 if(context.isSynchronous) {
6094 store.notifyAll();
6095 refreshDisplay();
6097 wizard.setButtons([
6098 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
6099 ],config.macros.importTiddlers.statusDoneImport);
6100 autoSaveChanges();
6104 //--
6105 //-- Upgrade macro
6106 //--
6108 config.macros.upgrade.handler = function(place)
6110 var w = new Wizard();
6111 w.createWizard(place,this.wizardTitle);
6112 w.addStep(this.step1Title,this.step1Html.format([this.source,this.source]));
6113 w.setButtons([{caption: this.upgradeLabel, tooltip: this.upgradePrompt, onClick: this.onClickUpgrade}]);
6116 config.macros.upgrade.onClickUpgrade = function(e)
6118 var me = config.macros.upgrade;
6119 var w = new Wizard(this);
6120 if(window.location.protocol != "file:") {
6121 alert(me.errorCantUpgrade);
6122 return false;
6124 if(story.areAnyDirty() || store.isDirty()) {
6125 alert(me.errorNotSaved);
6126 return false;
6128 var localPath = getLocalPath(document.location.toString());
6129 var backupPath = getBackupPath(localPath,me.backupExtension);
6130 w.setValue("backupPath",backupPath);
6131 w.setButtons([],me.statusPreparingBackup);
6132 var original = loadOriginal(localPath);
6133 w.setButtons([],me.statusSavingBackup);
6134 var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
6135 if(backup != true) {
6136 w.setButtons([],me.errorSavingBackup);
6137 alert(me.errorSavingBackup);
6138 return false;
6140 w.setButtons([],me.statusLoadingCore);
6141 var load = loadRemoteFile(me.source,me.onLoadCore,w);
6142 if(typeof load == "string") {
6143 w.setButtons([],me.errorLoadingCore);
6144 alert(me.errorLoadingCore);
6145 return false;
6147 return false;
6150 config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
6152 var me = config.macros.upgrade;
6153 var w = params;
6154 var errMsg;
6155 if(!status)
6156 errMsg = me.errorLoadingCore;
6157 var newVer = me.extractVersion(responseText);
6158 if(!newVer)
6159 errMsg = me.errorCoreFormat;
6160 if(errMsg) {
6161 w.setButtons([],errMsg);
6162 alert(errMsg);
6163 return;
6165 var onStartUpgrade = function(e) {
6166 w.setButtons([],me.statusSavingCore);
6167 var localPath = getLocalPath(document.location.toString());
6168 saveFile(localPath,responseText);
6169 w.setButtons([],me.statusReloadingCore);
6170 var backupPath = w.getValue("backupPath");
6171 var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM() + '#upgrade:[[' + encodeURI(backupPath) + ']]';
6172 window.setTimeout(function () {window.location = newLoc;},10);
6174 var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version,newVer) + 1];
6175 w.addStep(me.step2Title,step2.format([formatVersion(newVer),formatVersion(version)]));
6176 w.setButtons([{caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade},{caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel}]);
6179 config.macros.upgrade.onCancel = function(e)
6181 var me = config.macros.upgrade;
6182 var w = new Wizard(this);
6183 w.addStep(me.step3Title,me.step3Html);
6184 w.setButtons([]);
6185 return false;
6188 config.macros.upgrade.extractVersion = function(upgradeFile)
6190 var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
6191 var m = re.exec(upgradeFile);
6192 return m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
6195 function upgradeFrom(path)
6197 var importStore = new TiddlyWiki();
6198 var tw = loadFile(path);
6199 if(window.netscape !== undefined)
6200 tw = convertUTF8ToUnicode(tw);
6201 importStore.importTiddlyWiki(tw);
6202 importStore.forEachTiddler(function(title,tiddler) {
6203 if(!store.getTiddler(title)) {
6204 store.addTiddler(tiddler);
6207 refreshDisplay();
6208 saveChanges(); //# To create appropriate Markup* sections
6209 alert(config.messages.upgradeDone.format([formatVersion()]));
6210 window.location = window.location.toString().substr(0,window.location.toString().lastIndexOf('?'));
6213 //--
6214 //-- Sync macro
6215 //--
6217 // Synchronisation handlers
6218 config.syncers = {};
6220 // Sync state.
6221 var currSync = null;
6223 // sync macro
6224 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6226 if(!wikifier.isStatic)
6227 this.startSync(place);
6230 config.macros.sync.cancelSync = function()
6232 currSync = null;
6235 config.macros.sync.startSync = function(place)
6237 if(currSync)
6238 config.macros.sync.cancelSync();
6239 currSync = {};
6240 currSync.syncList = this.getSyncableTiddlers();
6241 currSync.syncTasks = this.createSyncTasks(currSync.syncList);
6242 this.preProcessSyncableTiddlers(currSync.syncList);
6243 var wizard = new Wizard();
6244 currSync.wizard = wizard;
6245 wizard.createWizard(place,this.wizardTitle);
6246 wizard.addStep(this.step1Title,this.step1Html);
6247 var markList = wizard.getElement("markList");
6248 var listWrapper = document.createElement("div");
6249 markList.parentNode.insertBefore(listWrapper,markList);
6250 currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
6251 this.processSyncableTiddlers(currSync.syncList);
6252 wizard.setButtons([{caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}]);
6255 config.macros.sync.getSyncableTiddlers = function()
6257 var list = [];
6258 store.forEachTiddler(function(title,tiddler) {
6259 var syncItem = {};
6260 syncItem.serverType = tiddler.getServerType();
6261 syncItem.serverHost = tiddler.fields['server.host'];
6262 if(syncItem.serverType && syncItem.serverHost) {
6263 syncItem.serverWorkspace = tiddler.fields['server.workspace'];
6264 syncItem.tiddler = tiddler;
6265 syncItem.title = tiddler.title;
6266 syncItem.isTouched = tiddler.isTouched();
6267 syncItem.selected = syncItem.isTouched;
6268 syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
6269 syncItem.status = syncItem.syncStatus.text;
6270 list.push(syncItem);
6273 list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
6274 return list;
6277 config.macros.sync.preProcessSyncableTiddlers = function(syncList)
6279 for(var i=0; i<syncList.length; i++) {
6280 var si = syncList[i];
6281 si.serverUrl = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler).uri;
6285 config.macros.sync.processSyncableTiddlers = function(syncList)
6287 for(var i=0; i<syncList.length; i++) {
6288 var si = syncList[i];
6289 si.rowElement.style.display = si.syncStatus.display;
6290 if(si.syncStatus.className)
6291 si.rowElement.className = si.syncStatus.className;
6295 config.macros.sync.createSyncTasks = function(syncList)
6297 var syncTasks = [];
6298 for(var i=0; i<syncList.length; i++) {
6299 var si = syncList[i];
6300 var r = null;
6301 for(var j=0; j<syncTasks.length; j++) {
6302 var cst = syncTasks[j];
6303 if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
6304 r = cst;
6306 if(r) {
6307 si.syncTask = r;
6308 r.syncItems.push(si);
6309 } else {
6310 si.syncTask = this.createSyncTask(si);
6311 syncTasks.push(si.syncTask);
6314 return syncTasks;
6317 config.macros.sync.createSyncTask = function(syncItem)
6319 var st = {};
6320 st.serverType = syncItem.serverType;
6321 st.serverHost = syncItem.serverHost;
6322 st.serverWorkspace = syncItem.serverWorkspace;
6323 st.syncItems = [syncItem];
6324 st.syncMachine = new SyncMachine(st.serverType,{
6325 start: function() {
6326 return this.openHost(st.serverHost,"openWorkspace");
6328 openWorkspace: function() {
6329 return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
6331 getTiddlerList: function() {
6332 return this.getTiddlerList("onGetTiddlerList");
6334 onGetTiddlerList: function(context) {
6335 var tiddlers = context.tiddlers;
6336 for(var i=0; i<st.syncItems.length; i++) {
6337 var si = st.syncItems[i];
6338 var f = tiddlers.findByField("title",si.title);
6339 if(f !== null) {
6340 if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
6341 si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
6343 } else {
6344 si.syncStatus = config.macros.sync.syncStatusList.notFound;
6346 config.macros.sync.updateSyncStatus(si);
6349 getTiddler: function(title) {
6350 return this.getTiddler(title,"onGetTiddler");
6352 onGetTiddler: function(context) {
6353 var tiddler = context.tiddler;
6354 var syncItem = st.syncItems.findByField("title",tiddler.title);
6355 if(syncItem !== null) {
6356 syncItem = st.syncItems[syncItem];
6357 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
6358 syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
6359 config.macros.sync.updateSyncStatus(syncItem);
6362 putTiddler: function(tiddler) {
6363 return this.putTiddler(tiddler,"onPutTiddler");
6365 onPutTiddler: function(context) {
6366 var title = context.title;
6367 var syncItem = st.syncItems.findByField("title",title);
6368 if(syncItem !== null) {
6369 syncItem = st.syncItems[syncItem];
6370 store.resetTiddler(title);
6371 if(context.status) {
6372 syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
6373 config.macros.sync.updateSyncStatus(syncItem);
6378 st.syncMachine.go();
6379 return st;
6382 config.macros.sync.updateSyncStatus = function(syncItem)
6384 var e = syncItem.colElements["status"];
6385 removeChildren(e);
6386 createTiddlyText(e,syncItem.syncStatus.text);
6387 syncItem.rowElement.style.display = syncItem.syncStatus.display;
6388 if(syncItem.syncStatus.className)
6389 syncItem.rowElement.className = syncItem.syncStatus.className;
6392 config.macros.sync.doSync = function(e)
6394 var rowNames = ListView.getSelectedRows(currSync.listView);
6395 var sl = config.macros.sync.syncStatusList;
6396 for(var i=0; i<currSync.syncList.length; i++) {
6397 var si = currSync.syncList[i];
6398 if(rowNames.indexOf(si.title) != -1) {
6399 var r = true;
6400 switch(si.syncStatus) {
6401 case sl.changedServer:
6402 r = si.syncTask.syncMachine.go("getTiddler",si.title);
6403 break;
6404 case sl.notFound:
6405 case sl.changedLocally:
6406 case sl.changedBoth:
6407 r = si.syncTask.syncMachine.go("putTiddler",si.tiddler);
6408 break;
6409 default:
6410 break;
6412 if(!r)
6413 displayMessage("Error in doSync: " + r);
6416 return false;
6419 function SyncMachine(serverType,steps)
6421 this.serverType = serverType;
6422 this.adaptor = new config.adaptors[serverType]();
6423 this.steps = steps;
6426 SyncMachine.prototype.go = function(step,context)
6428 var r = context ? context.status : null;
6429 if(typeof r == "string") {
6430 this.invokeError(r);
6431 return r;
6433 var h = this.steps[step ? step : "start"];
6434 if(!h)
6435 return null;
6436 r = h.call(this,context);
6437 if(typeof r == "string")
6438 this.invokeError(r);
6439 return r;
6442 SyncMachine.prototype.invokeError = function(message)
6444 if(this.steps.error)
6445 this.steps.error(message);
6448 SyncMachine.prototype.openHost = function(host,nextStep)
6450 var me = this;
6451 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
6454 SyncMachine.prototype.getWorkspaceList = function(nextStep)
6456 var me = this;
6457 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
6460 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
6462 var me = this;
6463 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
6466 SyncMachine.prototype.getTiddlerList = function(nextStep)
6468 var me = this;
6469 return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
6472 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
6474 return this.adaptor.generateTiddlerInfo(tiddler);
6477 SyncMachine.prototype.getTiddler = function(title,nextStep)
6479 var me = this;
6480 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
6483 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
6485 var me = this;
6486 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
6489 //--
6490 //-- Manager UI for groups of tiddlers
6491 //--
6493 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString)
6495 var wizard = new Wizard();
6496 wizard.createWizard(place,this.wizardTitle);
6497 wizard.addStep(this.step1Title,this.step1Html);
6498 var markList = wizard.getElement("markList");
6499 var listWrapper = document.createElement("div");
6500 markList.parentNode.insertBefore(listWrapper,markList);
6501 listWrapper.setAttribute("refresh","macro");
6502 listWrapper.setAttribute("macroName","plugins");
6503 listWrapper.setAttribute("params",paramString);
6504 this.refresh(listWrapper,paramString);
6507 config.macros.plugins.refresh = function(listWrapper,params)
6509 var wizard = new Wizard(listWrapper);
6510 var selectedRows = [];
6511 ListView.forEachSelector(listWrapper,function(e,rowName) {
6512 if(e.checked)
6513 selectedRows.push(e.getAttribute("rowName"));
6515 removeChildren(listWrapper);
6516 params = params.parseParams("anon");
6517 var plugins = installedPlugins.slice(0);
6518 var t,tiddler,p;
6519 var configTiddlers = store.getTaggedTiddlers("systemConfig");
6520 for(t=0; t<configTiddlers.length; t++) {
6521 tiddler = configTiddlers[t];
6522 if(plugins.findByField("title",tiddler.title) == null) {
6523 p = getPluginInfo(tiddler);
6524 p.executed = false;
6525 p.log.splice(0,0,this.skippedText);
6526 plugins.push(p);
6529 for(t=0; t<plugins.length; t++) {
6530 p = plugins[t];
6531 p.size = p.tiddler.text ? p.tiddler.text.length : 0;
6532 p.forced = p.tiddler.isTagged("systemConfigForce");
6533 p.disabled = p.tiddler.isTagged("systemConfigDisable");
6534 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
6536 if(plugins.length == 0) {
6537 createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
6538 wizard.setButtons([]);
6539 } else {
6540 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6541 wizard.setValue("listView",listView);
6542 wizard.setButtons([
6543 {caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
6544 {caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
6549 config.macros.plugins.doRemoveTag = function(e)
6551 var wizard = new Wizard(this);
6552 var listView = wizard.getValue("listView");
6553 var rowNames = ListView.getSelectedRows(listView);
6554 if(rowNames.length == 0) {
6555 alert(config.messages.nothingSelected);
6556 } else {
6557 for(var t=0; t<rowNames.length; t++)
6558 store.setTiddlerTag(rowNames[t],false,"systemConfig");
6562 config.macros.plugins.doDelete = function(e)
6564 var wizard = new Wizard(this);
6565 var listView = wizard.getValue("listView");
6566 var rowNames = ListView.getSelectedRows(listView);
6567 if(rowNames.length == 0) {
6568 alert(config.messages.nothingSelected);
6569 } else {
6570 if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
6571 for(var t=0; t<rowNames.length; t++) {
6572 store.removeTiddler(rowNames[t]);
6573 story.closeTiddler(rowNames[t],true);
6579 //--
6580 //-- Message area
6581 //--
6583 function getMessageDiv()
6585 var msgArea = document.getElementById("messageArea");
6586 if(!msgArea)
6587 return null;
6588 if(!msgArea.hasChildNodes())
6589 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
6590 config.messages.messageClose.text,
6591 config.messages.messageClose.tooltip,
6592 clearMessage);
6593 msgArea.style.display = "block";
6594 return createTiddlyElement(msgArea,"div");
6597 function displayMessage(text,linkText)
6599 var e = getMessageDiv();
6600 if(!e) {
6601 alert(text);
6602 return;
6604 if(linkText) {
6605 var link = createTiddlyElement(e,"a",null,null,text);
6606 link.href = linkText;
6607 link.target = "_blank";
6608 } else {
6609 e.appendChild(document.createTextNode(text));
6613 function clearMessage()
6615 var msgArea = document.getElementById("messageArea");
6616 if(msgArea) {
6617 removeChildren(msgArea);
6618 msgArea.style.display = "none";
6620 return false;
6623 //--
6624 //-- Refresh mechanism
6625 //--
6627 config.notifyTiddlers = [
6628 {name: "StyleSheetLayout", notify: refreshStyles},
6629 {name: "StyleSheetColors", notify: refreshStyles},
6630 {name: "StyleSheet", notify: refreshStyles},
6631 {name: "StyleSheetPrint", notify: refreshStyles},
6632 {name: "PageTemplate", notify: refreshPageTemplate},
6633 {name: "SiteTitle", notify: refreshPageTitle},
6634 {name: "SiteSubtitle", notify: refreshPageTitle},
6635 {name: "ColorPalette", notify: refreshColorPalette},
6636 {name: null, notify: refreshDisplay}
6639 config.refreshers = {
6640 link: function(e,changeList)
6642 var title = e.getAttribute("tiddlyLink");
6643 refreshTiddlyLink(e,title);
6644 return true;
6647 tiddler: function(e,changeList)
6649 var title = e.getAttribute("tiddler");
6650 var template = e.getAttribute("template");
6651 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
6652 story.refreshTiddler(title,template,true);
6653 else
6654 refreshElements(e,changeList);
6655 return true;
6658 content: function(e,changeList)
6660 var title = e.getAttribute("tiddler");
6661 var force = e.getAttribute("force");
6662 if(force != null || changeList == null || changeList.indexOf(title) != -1) {
6663 removeChildren(e);
6664 wikify(store.getTiddlerText(title,""),e,null,store.fetchTiddler(title));
6665 return true;
6666 } else
6667 return false;
6670 macro: function(e,changeList)
6672 var macro = e.getAttribute("macroName");
6673 var params = e.getAttribute("params");
6674 if(macro)
6675 macro = config.macros[macro];
6676 if(macro && macro.refresh)
6677 macro.refresh(e,params);
6678 return true;
6682 config.refresherData = {
6683 styleSheet: "StyleSheet",
6684 defaultStyleSheet: "StyleSheet",
6685 pageTemplate: "PageTemplate",
6686 defaultPageTemplate: "PageTemplate",
6687 colorPalette: "ColorPalette",
6688 defaultColorPalette: "ColorPalette"
6691 function refreshElements(root,changeList)
6693 var nodes = root.childNodes;
6694 for(var c=0; c<nodes.length; c++) {
6695 var e = nodes[c], type = null;
6696 if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
6697 type = e.getAttribute("refresh");
6698 var refresher = config.refreshers[type];
6699 var refreshed = false;
6700 if(refresher != undefined)
6701 refreshed = refresher(e,changeList);
6702 if(e.hasChildNodes() && !refreshed)
6703 refreshElements(e,changeList);
6707 function applyHtmlMacros(root,tiddler)
6709 var e = root.firstChild;
6710 while(e) {
6711 var nextChild = e.nextSibling;
6712 if(e.getAttribute) {
6713 var macro = e.getAttribute("macro");
6714 if(macro) {
6715 e.removeAttribute("macro");
6716 var params = "";
6717 var p = macro.indexOf(" ");
6718 if(p != -1) {
6719 params = macro.substr(p+1);
6720 macro = macro.substr(0,p);
6722 invokeMacro(e,macro,params,null,tiddler);
6725 if(e.hasChildNodes())
6726 applyHtmlMacros(e,tiddler);
6727 e = nextChild;
6731 function refreshPageTemplate(title)
6733 var stash = createTiddlyElement(document.body,"div");
6734 stash.style.display = "none";
6735 var display = story.getContainer();
6736 var nodes,t;
6737 if(display) {
6738 nodes = display.childNodes;
6739 for(t=nodes.length-1; t>=0; t--)
6740 stash.appendChild(nodes[t]);
6742 var wrapper = document.getElementById("contentWrapper");
6744 var isAvailable = function(title) {
6745 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
6746 if(s!=-1)
6747 title = title.substr(0,s);
6748 return store.tiddlerExists(title) || store.isShadowTiddler(title);
6750 if(!title || !isAvailable(title))
6751 title = config.refresherData.pageTemplate;
6752 if(!isAvailable(title))
6753 title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
6754 wrapper.innerHTML = store.getRecursiveTiddlerText(title,null,10);
6755 applyHtmlMacros(wrapper);
6756 refreshElements(wrapper);
6757 display = story.getContainer();
6758 removeChildren(display);
6759 if(!display)
6760 display = createTiddlyElement(wrapper,"div",story.containerId());
6761 nodes = stash.childNodes;
6762 for(t=nodes.length-1; t>=0; t--)
6763 display.appendChild(nodes[t]);
6764 removeNode(stash);
6767 function refreshDisplay(hint)
6769 if(typeof hint == "string")
6770 hint = [hint];
6771 var e = document.getElementById("contentWrapper");
6772 refreshElements(e,hint);
6773 if(backstage.isPanelVisible()) {
6774 e = document.getElementById("backstage");
6775 refreshElements(e,hint);
6779 function refreshPageTitle()
6781 document.title = getPageTitle();
6784 function getPageTitle()
6786 var st = wikifyPlain("SiteTitle");
6787 var ss = wikifyPlain("SiteSubtitle");
6788 return st + ((st == "" || ss == "") ? "" : " - ") + ss;
6791 function refreshStyles(title,doc)
6793 setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc || document);
6796 function refreshColorPalette(title)
6798 if(!startingUp)
6799 refreshAll();
6802 function refreshAll()
6804 refreshPageTemplate();
6805 refreshDisplay();
6806 refreshStyles("StyleSheetLayout");
6807 refreshStyles("StyleSheetColors");
6808 refreshStyles(config.refresherData.styleSheet);
6809 refreshStyles("StyleSheetPrint");
6812 //--
6813 //-- Options stuff
6814 //--
6816 config.optionHandlers = {
6817 'txt': {
6818 get: function(name) {return encodeCookie(config.options[name].toString());},
6819 set: function(name,value) {config.options[name] = decodeCookie(value);}
6821 'chk': {
6822 get: function(name) {return config.options[name] ? "true" : "false";},
6823 set: function(name,value) {config.options[name] = value == "true";}
6827 function loadOptionsCookie()
6829 if(safeMode)
6830 return;
6831 var cookies = document.cookie.split(";");
6832 for(var c=0; c<cookies.length; c++) {
6833 var p = cookies[c].indexOf("=");
6834 if(p != -1) {
6835 var name = cookies[c].substr(0,p).trim();
6836 var value = cookies[c].substr(p+1).trim();
6837 var optType = name.substr(0,3);
6838 if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
6839 config.optionHandlers[optType].set(name,value);
6844 function saveOptionCookie(name)
6846 if(safeMode)
6847 return;
6848 var c = name + "=";
6849 var optType = name.substr(0,3);
6850 if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
6851 c += config.optionHandlers[optType].get(name);
6852 c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
6853 document.cookie = c;
6856 function encodeCookie(s)
6858 return escape(convertUnicodeToHtmlEntities(s));
6861 function decodeCookie(s)
6863 s = unescape(s);
6864 var re = /&#[0-9]{1,5};/g;
6865 return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
6869 config.macros.option.genericCreate = function(place,type,opt,className,desc)
6871 var typeInfo = config.macros.option.types[type];
6872 var c = document.createElement(typeInfo.elementType);
6873 if(typeInfo.typeValue)
6874 c.setAttribute("type",typeInfo.typeValue);
6875 c[typeInfo.eventName] = typeInfo.onChange;
6876 c.setAttribute("option",opt);
6877 c.className = className || typeInfo.className;
6878 if(config.optionsDesc[opt])
6879 c.setAttribute("title",config.optionsDesc[opt]);
6880 place.appendChild(c);
6881 if(desc != "no")
6882 createTiddlyText(place,config.optionsDesc[opt] || opt);
6883 c[typeInfo.valueField] = config.options[opt];
6884 return c;
6887 config.macros.option.genericOnChange = function(e)
6889 var opt = this.getAttribute("option");
6890 if(opt) {
6891 var optType = opt.substr(0,3);
6892 var handler = config.macros.option.types[optType];
6893 if(handler.elementType && handler.valueField)
6894 config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType,this);
6896 return true;
6899 config.macros.option.types = {
6900 'txt': {
6901 elementType: "input",
6902 valueField: "value",
6903 eventName: "onchange",
6904 className: "txtOptionInput",
6905 create: config.macros.option.genericCreate,
6906 onChange: config.macros.option.genericOnChange
6908 'chk': {
6909 elementType: "input",
6910 valueField: "checked",
6911 eventName: "onclick",
6912 className: "chkOptionInput",
6913 typeValue: "checkbox",
6914 create: config.macros.option.genericCreate,
6915 onChange: config.macros.option.genericOnChange
6919 config.macros.option.propagateOption = function(opt,valueField,value,elementType,elem)
6921 config.options[opt] = value;
6922 saveOptionCookie(opt);
6923 var nodes = document.getElementsByTagName(elementType);
6924 for(var t=0; t<nodes.length; t++) {
6925 var optNode = nodes[t].getAttribute("option");
6926 if(opt == optNode && nodes[t]!=elem)
6927 nodes[t][valueField] = value;
6931 config.macros.option.handler = function(place,macroName,params,wikifier,paramString)
6933 params = paramString.parseParams("anon",null,true,false,false);
6934 var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
6935 var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
6936 var desc = getParam(params,"desc","no");
6937 var type = opt.substr(0,3);
6938 var h = config.macros.option.types[type];
6939 if(h && h.create)
6940 h.create(place,type,opt,className,desc);
6943 config.macros.options.handler = function(place,macroName,params,wikifier,paramString)
6945 params = paramString.parseParams("anon",null,true,false,false);
6946 var showUnknown = getParam(params,"showUnknown","no");
6947 var wizard = new Wizard();
6948 wizard.createWizard(place,this.wizardTitle);
6949 wizard.addStep(this.step1Title,this.step1Html);
6950 var markList = wizard.getElement("markList");
6951 var chkUnknown = wizard.getElement("chkUnknown");
6952 chkUnknown.checked = showUnknown == "yes";
6953 chkUnknown.onchange = this.onChangeUnknown;
6954 var listWrapper = document.createElement("div");
6955 markList.parentNode.insertBefore(listWrapper,markList);
6956 wizard.setValue("listWrapper",listWrapper);
6957 this.refreshOptions(listWrapper,showUnknown == "yes");
6960 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
6962 var opts = [];
6963 for(var n in config.options) {
6964 var opt = {};
6965 opt.option = "";
6966 opt.name = n;
6967 opt.lowlight = !config.optionsDesc[n];
6968 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6969 if(!opt.lowlight || showUnknown)
6970 opts.push(opt);
6972 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);});
6973 var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
6974 for(n=0; n<opts.length; n++) {
6975 var type = opts[n].name.substr(0,3);
6976 var h = config.macros.option.types[type];
6977 if(h && h.create) {
6978 h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
6983 config.macros.options.onChangeUnknown = function(e)
6985 var wizard = new Wizard(this);
6986 var listWrapper = wizard.getValue("listWrapper");
6987 removeChildren(listWrapper);
6988 config.macros.options.refreshOptions(listWrapper,this.checked);
6989 return false;
6992 //--
6993 //-- Saving
6994 //--
6996 var saveUsingSafari = false;
6998 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
6999 var endSaveArea = '</d' + 'iv>';
7001 // If there are unsaved changes, force the user to confirm before exitting
7002 function confirmExit()
7004 hadConfirmExit = true;
7005 if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
7006 return config.messages.confirmExit;
7009 // Give the user a chance to save changes before exitting
7010 function checkUnsavedChanges()
7012 if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
7013 if(confirm(config.messages.unsavedChangesWarning))
7014 saveChanges();
7018 function updateLanguageAttribute(s)
7020 if(config.locale) {
7021 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
7022 var m = mRE.exec(s);
7023 if(m) {
7024 var t = m[1];
7025 if(m[2])
7026 t += ' xml:lang="' + config.locale + '"';
7027 if(m[3])
7028 t += ' lang="' + config.locale + '"';
7029 t += ">";
7030 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
7033 return s;
7036 function updateMarkupBlock(s,blockName,tiddlerName)
7038 return s.replaceChunk(
7039 "<!--%0-START-->".format([blockName]),
7040 "<!--%0-END-->".format([blockName]),
7041 "\n" + convertUnicodeToFileFormat(store.getRecursiveTiddlerText(tiddlerName,"")) + "\n");
7044 function updateOriginal(original,posDiv,localPath)
7046 if(!posDiv)
7047 posDiv = locateStoreArea(original);
7048 if(!posDiv) {
7049 alert(config.messages.invalidFileError.format([localPath]));
7050 return null;
7052 var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
7053 convertUnicodeToFileFormat(store.allTiddlersAsHtml()) + "\n" +
7054 original.substr(posDiv[1]);
7055 var newSiteTitle = convertUnicodeToFileFormat(getPageTitle()).htmlEncode();
7056 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
7057 revised = updateLanguageAttribute(revised);
7058 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
7059 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
7060 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
7061 revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
7062 return revised;
7065 function locateStoreArea(original)
7067 // Locate the storeArea div's
7068 var posOpeningDiv = original.indexOf(startSaveArea);
7069 var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
7070 if(limitClosingDiv == -1)
7071 limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
7072 var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
7073 return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
7076 function autoSaveChanges(onlyIfDirty,tiddlers)
7078 if(config.options.chkAutoSave)
7079 saveChanges(onlyIfDirty,tiddlers);
7082 function loadOriginal(localPath)
7084 return loadFile(localPath);
7087 // Save this tiddlywiki with the pending changes
7088 function saveChanges(onlyIfDirty,tiddlers)
7090 if(onlyIfDirty && !store.isDirty())
7091 return;
7092 clearMessage();
7093 var t0 = new Date();
7094 var originalPath = document.location.toString();
7095 if(originalPath.substr(0,5) != "file:") {
7096 alert(config.messages.notFileUrlError);
7097 if(store.tiddlerExists(config.messages.saveInstructions))
7098 story.displayTiddler(null,config.messages.saveInstructions);
7099 return;
7101 var localPath = getLocalPath(originalPath);
7102 var original = loadOriginal(localPath);
7103 if(original == null) {
7104 alert(config.messages.cantSaveError);
7105 if(store.tiddlerExists(config.messages.saveInstructions))
7106 story.displayTiddler(null,config.messages.saveInstructions);
7107 return;
7109 var posDiv = locateStoreArea(original);
7110 if(!posDiv) {
7111 alert(config.messages.invalidFileError.format([localPath]));
7112 return;
7114 saveMain(localPath,original,posDiv);
7115 if(config.options.chkSaveBackups)
7116 saveBackup(localPath,original);
7117 if(config.options.chkSaveEmptyTemplate)
7118 saveEmpty(localPath,original,posDiv);
7119 if(config.options.chkGenerateAnRssFeed && saveRss instanceof Function)
7120 saveRss(localPath);
7121 if(config.options.chkDisplayInstrumentation)
7122 displayMessage("saveChanges " + (new Date()-t0) + " ms");
7125 function saveMain(localPath,original,posDiv)
7127 var save;
7128 try {
7129 var revised = updateOriginal(original,posDiv,localPath);
7130 save = saveFile(localPath,revised);
7131 } catch (ex) {
7132 showException(ex);
7134 if(save) {
7135 displayMessage(config.messages.mainSaved,"file://" + localPath);
7136 store.setDirty(false);
7137 } else {
7138 alert(config.messages.mainFailed);
7142 function saveBackup(localPath,original)
7144 var backupPath = getBackupPath(localPath);
7145 var backup = copyFile(backupPath,localPath);
7146 if(!backup)
7147 backup = saveFile(backupPath,original);
7148 if(backup)
7149 displayMessage(config.messages.backupSaved,"file://" + backupPath);
7150 else
7151 alert(config.messages.backupFailed);
7154 function saveEmpty(localPath,original,posDiv)
7156 var emptyPath,p;
7157 if((p = localPath.lastIndexOf("/")) != -1)
7158 emptyPath = localPath.substr(0,p) + "/";
7159 else if((p = localPath.lastIndexOf("\\")) != -1)
7160 emptyPath = localPath.substr(0,p) + "\\";
7161 else
7162 emptyPath = localPath + ".";
7163 emptyPath += "empty.html";
7164 var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
7165 var emptySave = saveFile(emptyPath,empty);
7166 if(emptySave)
7167 displayMessage(config.messages.emptySaved,"file://" + emptyPath);
7168 else
7169 alert(config.messages.emptyFailed);
7172 function getLocalPath(origPath)
7174 var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
7175 // Remove any location or query part of the URL
7176 var argPos = originalPath.indexOf("?");
7177 if(argPos != -1)
7178 originalPath = originalPath.substr(0,argPos);
7179 var hashPos = originalPath.indexOf("#");
7180 if(hashPos != -1)
7181 originalPath = originalPath.substr(0,hashPos);
7182 // Convert file://localhost/ to file:///
7183 if(originalPath.indexOf("file://localhost/") == 0)
7184 originalPath = "file://" + originalPath.substr(16);
7185 // Convert to a native file format
7186 var localPath;
7187 if(originalPath.charAt(9) == ":") // pc local file
7188 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
7189 else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
7190 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
7191 else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
7192 localPath = unescape(originalPath.substr(7));
7193 else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
7194 localPath = unescape(originalPath.substr(5));
7195 else // pc network file
7196 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
7197 return localPath;
7200 function getBackupPath(localPath,title,extension)
7202 var slash = "\\";
7203 var dirPathPos = localPath.lastIndexOf("\\");
7204 if(dirPathPos == -1) {
7205 dirPathPos = localPath.lastIndexOf("/");
7206 slash = "/";
7208 var backupFolder = config.options.txtBackupFolder;
7209 if(!backupFolder || backupFolder == "")
7210 backupFolder = ".";
7211 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
7212 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
7213 if(title)
7214 backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
7215 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension || "html");
7216 return backupPath;
7219 //--
7220 //-- RSS Saving
7221 //--
7223 function saveRss(localPath)
7225 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
7226 if(saveFile(rssPath,convertUnicodeToFileFormat(generateRss())))
7227 displayMessage(config.messages.rssSaved,"file://" + rssPath);
7228 else
7229 alert(config.messages.rssFailed);
7232 function generateRss()
7234 var s = [];
7235 var d = new Date();
7236 var u = store.getTiddlerText("SiteUrl");
7237 // Assemble the header
7238 s.push("<" + "?xml version=\"1.0\"?" + ">");
7239 s.push("<rss version=\"2.0\">");
7240 s.push("<channel>");
7241 s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
7242 if(u)
7243 s.push("<link>" + u.htmlEncode() + "</link>");
7244 s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
7245 s.push("<language>" + config.locale + "</language>");
7246 s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
7247 s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
7248 s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
7249 s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
7250 s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
7251 // The body
7252 var tiddlers = store.getTiddlers("modified","excludeLists");
7253 var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
7254 for(var t=tiddlers.length-1; t>=n; t--) {
7255 s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
7257 // And footer
7258 s.push("</channel>");
7259 s.push("</rss>");
7260 // Save it all
7261 return s.join("\n");
7264 //--
7265 //-- Filesystem code
7266 //--
7268 function convertUTF8ToUnicode(u)
7270 return config.browser.isOpera || !window.netscape ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
7273 function manualConvertUTF8ToUnicode(utf)
7275 var uni = utf;
7276 var src = 0;
7277 var dst = 0;
7278 var b1, b2, b3;
7279 var c;
7280 while(src < utf.length) {
7281 b1 = utf.charCodeAt(src++);
7282 if(b1 < 0x80) {
7283 dst++;
7284 } else if(b1 < 0xE0) {
7285 b2 = utf.charCodeAt(src++);
7286 c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
7287 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7288 } else {
7289 b2 = utf.charCodeAt(src++);
7290 b3 = utf.charCodeAt(src++);
7291 c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
7292 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7295 return uni;
7298 function mozConvertUTF8ToUnicode(u)
7300 try {
7301 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7302 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7303 converter.charset = "UTF-8";
7304 } catch(ex) {
7305 return manualConvertUTF8ToUnicode(u);
7306 } // fallback
7307 var s = converter.ConvertToUnicode(u);
7308 var fin = converter.Finish();
7309 return fin.length > 0 ? s+fin : s;
7312 function convertUnicodeToFileFormat(s)
7314 return config.browser.isOpera || !window.netscape ? convertUnicodeToHtmlEntities(s) : mozConvertUnicodeToUTF8(s);
7317 function convertUnicodeToHtmlEntities(s)
7319 var re = /[^\u0000-\u007F]/g;
7320 return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
7323 function convertUnicodeToUTF8(s)
7325 // return convertUnicodeToFileFormat to allow plugin migration
7326 return convertUnicodeToFileFormat(s);
7329 function manualConvertUnicodeToUTF8(s)
7331 return unescape(encodeURIComponent(s));
7334 function mozConvertUnicodeToUTF8(s)
7336 try {
7337 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7338 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7339 converter.charset = "UTF-8";
7340 } catch(ex) {
7341 return manualConvertUnicodeToUTF8(s);
7342 } // fallback
7343 var u = converter.ConvertFromUnicode(s);
7344 var fin = converter.Finish();
7345 return fin.length > 0 ? u + fin : u;
7348 function convertUriToUTF8(uri,charSet)
7350 if(window.netscape == undefined || charSet == undefined || charSet == "")
7351 return uri;
7352 try {
7353 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7354 var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
7355 } catch(ex) {
7356 return uri;
7358 return converter.convertURISpecToUTF8(uri,charSet);
7361 function copyFile(dest,source)
7363 return config.browser.isIE ? ieCopyFile(dest,source) : false;
7366 function saveFile(fileUrl,content)
7368 var r = mozillaSaveFile(fileUrl,content);
7369 if(!r)
7370 r = ieSaveFile(fileUrl,content);
7371 if(!r)
7372 r = javaSaveFile(fileUrl,content);
7373 return r;
7376 function loadFile(fileUrl)
7378 var r = mozillaLoadFile(fileUrl);
7379 if((r == null) || (r == false))
7380 r = ieLoadFile(fileUrl);
7381 if((r == null) || (r == false))
7382 r = javaLoadFile(fileUrl);
7383 return r;
7386 function ieCreatePath(path)
7388 try {
7389 var fso = new ActiveXObject("Scripting.FileSystemObject");
7390 } catch(ex) {
7391 return null;
7394 var pos = path.lastIndexOf("\\");
7395 if(pos!=-1)
7396 path = path.substring(0,pos+1);
7398 var scan = [path];
7399 var parent = fso.GetParentFolderName(path);
7400 while(parent && !fso.FolderExists(parent)) {
7401 scan.push(parent);
7402 parent = fso.GetParentFolderName(parent);
7405 for(i=scan.length-1;i>=0;i--) {
7406 if(!fso.FolderExists(scan[i])) {
7407 fso.CreateFolder(scan[i]);
7410 return true;
7413 // Returns null if it can't do it, false if there's an error, true if it saved OK
7414 function ieSaveFile(filePath,content)
7416 ieCreatePath(filePath);
7417 try {
7418 var fso = new ActiveXObject("Scripting.FileSystemObject");
7419 } catch(ex) {
7420 return null;
7422 var file = fso.OpenTextFile(filePath,2,-1,0);
7423 file.Write(content);
7424 file.Close();
7425 return true;
7428 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7429 function ieLoadFile(filePath)
7431 try {
7432 var fso = new ActiveXObject("Scripting.FileSystemObject");
7433 var file = fso.OpenTextFile(filePath,1);
7434 var content = file.ReadAll();
7435 file.Close();
7436 } catch(ex) {
7437 return null;
7439 return content;
7442 function ieCopyFile(dest,source)
7444 ieCreatePath(dest);
7445 try {
7446 var fso = new ActiveXObject("Scripting.FileSystemObject");
7447 fso.GetFile(source).Copy(dest);
7448 } catch(ex) {
7449 return false;
7451 return true;
7454 // Returns null if it can't do it, false if there's an error, true if it saved OK
7455 function mozillaSaveFile(filePath,content)
7457 if(window.Components) {
7458 try {
7459 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7460 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7461 file.initWithPath(filePath);
7462 if(!file.exists())
7463 file.create(0,0664);
7464 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
7465 out.init(file,0x20|0x02,00004,null);
7466 out.write(content,content.length);
7467 out.flush();
7468 out.close();
7469 return true;
7470 } catch(ex) {
7471 return false;
7474 return null;
7477 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7478 function mozillaLoadFile(filePath)
7480 if(window.Components) {
7481 try {
7482 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7483 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7484 file.initWithPath(filePath);
7485 if(!file.exists())
7486 return null;
7487 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
7488 inputStream.init(file,0x01,00004,null);
7489 var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
7490 sInputStream.init(inputStream);
7491 var contents = sInputStream.read(sInputStream.available());
7492 sInputStream.close();
7493 inputStream.close();
7494 return contents;
7495 } catch(ex) {
7496 return false;
7499 return null;
7502 function javaUrlToFilename(url)
7504 var f = "//localhost";
7505 if(url.indexOf(f) == 0)
7506 return url.substring(f.length);
7507 var i = url.indexOf(":");
7508 return i > 0 ? url.substring(i-1) : url;
7511 function javaSaveFile(filePath,content)
7513 try {
7514 if(document.applets["TiddlySaver"])
7515 return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
7516 } catch(ex) {
7518 try {
7519 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
7520 s.print(content);
7521 s.close();
7522 } catch(ex) {
7523 return null;
7525 return true;
7528 function javaLoadFile(filePath)
7530 try {
7531 if(document.applets["TiddlySaver"])
7532 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
7533 } catch(ex) {
7535 var content = [];
7536 try {
7537 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
7538 var line;
7539 while((line = r.readLine()) != null)
7540 content.push(new String(line));
7541 r.close();
7542 } catch(ex) {
7543 return null;
7545 return content.join("\n");
7548 //--
7549 //-- Server adaptor base class
7550 //--
7552 function AdaptorBase()
7554 this.host = null;
7555 this.store = null;
7556 return this;
7559 AdaptorBase.prototype.close = function()
7561 return true;
7564 AdaptorBase.prototype.fullHostName = function(host)
7566 if(!host)
7567 return '';
7568 host = host.trim();
7569 if(!host.match(/:\/\//))
7570 host = 'http://' + host;
7571 if(host.substr(host.length-1) == '/')
7572 host = host.substr(0,host.length-1)
7573 return host;
7576 AdaptorBase.minHostName = function(host)
7578 return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
7581 AdaptorBase.prototype.setContext = function(context,userParams,callback)
7583 if(!context) context = {};
7584 context.userParams = userParams;
7585 if(callback) context.callback = callback;
7586 context.adaptor = this;
7587 if(!context.host)
7588 context.host = this.host;
7589 context.host = this.fullHostName(context.host);
7590 if(!context.workspace)
7591 context.workspace = this.workspace;
7592 return context;
7595 // Open the specified host
7596 AdaptorBase.prototype.openHost = function(host,context,userParams,callback)
7598 this.host = host;
7599 context = this.setContext(context,userParams,callback);
7600 context.status = true;
7601 if(callback)
7602 window.setTimeout(function() {context.callback(context,userParams);},10);
7603 return true;
7606 // Open the specified workspace
7607 AdaptorBase.prototype.openWorkspace = function(workspace,context,userParams,callback)
7609 this.workspace = workspace;
7610 context = this.setContext(context,userParams,callback);
7611 context.status = true;
7612 if(callback)
7613 window.setTimeout(function() {callback(context,userParams);},10);
7614 return true;
7617 //--
7618 //-- Server adaptor for talking to static TiddlyWiki files
7619 //--
7621 function FileAdaptor()
7625 FileAdaptor.prototype = new AdaptorBase();
7627 FileAdaptor.serverType = 'file';
7628 FileAdaptor.serverLabel = 'TiddlyWiki';
7630 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7632 context.status = status;
7633 if(!status) {
7634 context.statusText = "Error reading file";
7635 } else {
7636 context.adaptor.store = new TiddlyWiki();
7637 if(!context.adaptor.store.importTiddlyWiki(responseText)) {
7638 context.statusText = config.messages.invalidFileError.format([url]);
7639 context.status = false;
7642 context.complete(context,context.userParams);
7645 // Get the list of workspaces on a given server
7646 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
7648 context = this.setContext(context,userParams,callback);
7649 context.workspaces = [{title:"(default)"}];
7650 context.status = true;
7651 if(callback)
7652 window.setTimeout(function() {callback(context,userParams);},10);
7653 return true;
7656 // Gets the list of tiddlers within a given workspace
7657 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
7659 context = this.setContext(context,userParams,callback);
7660 if(!context.filter)
7661 context.filter = filter;
7662 context.complete = FileAdaptor.getTiddlerListComplete;
7663 if(this.store) {
7664 var ret = context.complete(context,context.userParams);
7665 } else {
7666 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7667 if(typeof ret != "string")
7668 ret = true;
7670 return ret;
7673 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7675 if(context.status) {
7676 if(context.filter) {
7677 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
7678 } else {
7679 context.tiddlers = [];
7680 context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
7682 for(var i=0; i<context.tiddlers.length; i++) {
7683 context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
7684 context.tiddlers[i].fields['server.host'] = AdaptorBase.minHostName(context.host);
7685 context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
7687 context.status = true;
7689 if(context.callback) {
7690 window.setTimeout(function() {context.callback(context,userParams);},10);
7692 return true;
7695 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7697 var info = {};
7698 info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
7699 return info;
7702 // Retrieve a tiddler from a given workspace on a given server
7703 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
7705 context = this.setContext(context,userParams,callback);
7706 context.title = title;
7707 context.complete = FileAdaptor.getTiddlerComplete;
7708 return context.adaptor.store ?
7709 context.complete(context,context.userParams) :
7710 loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7713 FileAdaptor.getTiddlerComplete = function(context,userParams)
7715 var t = context.adaptor.store.fetchTiddler(context.title);
7716 t.fields['server.type'] = FileAdaptor.serverType;
7717 t.fields['server.host'] = AdaptorBase.minHostName(context.host);
7718 t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
7719 context.tiddler = t;
7720 context.status = true;
7721 if(context.allowSynchronous) {
7722 context.isSynchronous = true;
7723 context.callback(context,userParams);
7724 } else {
7725 window.setTimeout(function() {context.callback(context,userParams);},10);
7727 return true;
7730 FileAdaptor.prototype.close = function()
7732 delete this.store;
7733 this.store = null;
7736 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7738 config.defaultAdaptor = FileAdaptor.serverType;
7740 //--
7741 //-- Remote HTTP requests
7742 //--
7744 function loadRemoteFile(url,callback,params)
7746 return httpReq("GET",url,callback,params);
7749 function httpReq(type,url,callback,params,headers,data,contentType,username,password,allowCache)
7751 var x = null;
7752 try {
7753 x = new XMLHttpRequest(); //# Modern
7754 } catch(ex) {
7755 try {
7756 x = new ActiveXObject("Msxml2.XMLHTTP"); //# IE 6
7757 } catch(ex2) {
7760 if(!x)
7761 return "Can't create XMLHttpRequest object";
7762 x.onreadystatechange = function() {
7763 try {
7764 var status = x.status;
7765 } catch(ex) {
7766 status = false;
7768 if(x.readyState == 4 && callback && (status !== undefined)) {
7769 if([0, 200, 201, 204, 207].contains(status))
7770 callback(true,params,x.responseText,url,x);
7771 else
7772 callback(false,params,null,url,x);
7773 x.onreadystatechange = function(){};
7774 x = null;
7777 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
7778 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
7779 try {
7780 if(!allowCache)
7781 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
7782 x.open(type,url,true,username,password);
7783 if(data)
7784 x.setRequestHeader("Content-Type", contentType || "application/x-www-form-urlencoded");
7785 if(x.overrideMimeType)
7786 x.setRequestHeader("Connection", "close");
7787 if(headers) {
7788 for(var n in headers)
7789 x.setRequestHeader(n,headers[n]);
7791 x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
7792 x.send(data);
7793 } catch(ex) {
7794 return exceptionText(ex);
7796 return x;
7799 // included for compatibility
7800 function getXMLHttpRequest()
7802 try {
7803 var x = new XMLHttpRequest(); // Modern
7804 } catch(ex) {
7805 try {
7806 x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
7807 } catch (ex2) {
7808 return null;
7811 return x;
7814 // included for compatibility
7815 function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
7817 return httpReq(type,url,callback,params,headers,data,contentType,username,password,allowCache);
7820 //--
7821 //-- TiddlyWiki-specific utility functions
7822 //--
7824 function formatVersion(v)
7826 v = v || version;
7827 return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
7830 function compareVersions(v1,v2)
7832 var a = ["major","minor","revision"];
7833 for(var i = 0; i<a.length; i++) {
7834 var x1 = v1[a[i]] || 0;
7835 var x2 = v2[a[i]] || 0;
7836 if(x1<x2)
7837 return 1;
7838 if(x1>x2)
7839 return -1;
7841 x1 = v1.beta || 9999;
7842 x2 = v2.beta || 9999;
7843 if(x1<x2)
7844 return 1;
7845 return x1 > x2 ? -1 : 0;
7848 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7850 var btn = document.createElement("a");
7851 if(action) {
7852 btn.onclick = action;
7853 btn.setAttribute("href","javascript:;");
7855 if(tooltip)
7856 btn.setAttribute("title",tooltip);
7857 if(text)
7858 btn.appendChild(document.createTextNode(text));
7859 btn.className = className || "button";
7860 if(id)
7861 btn.id = id;
7862 if(attribs) {
7863 for(var i in attribs) {
7864 btn.setAttribute(i,attribs[i]);
7867 if(parent)
7868 parent.appendChild(btn);
7869 if(accessKey)
7870 btn.setAttribute("accessKey",accessKey);
7871 return btn;
7874 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
7876 var text = includeText ? title : null;
7877 var i = getTiddlyLinkInfo(title,className);
7878 var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
7879 if(isStatic)
7880 btn.className += ' ' + className;
7881 btn.setAttribute("refresh","link");
7882 btn.setAttribute("tiddlyLink",title);
7883 if(noToggle)
7884 btn.setAttribute("noToggle","true");
7885 if(linkedFromTiddler) {
7886 var fields = linkedFromTiddler.getInheritedFields();
7887 if(fields)
7888 btn.setAttribute("tiddlyFields",fields);
7890 return btn;
7893 function refreshTiddlyLink(e,title)
7895 var i = getTiddlyLinkInfo(title,e.className);
7896 e.className = i.classes;
7897 e.title = i.subTitle;
7900 function getTiddlyLinkInfo(title,currClasses)
7902 var classes = currClasses ? currClasses.split(" ") : [];
7903 classes.pushUnique("tiddlyLink");
7904 var tiddler = store.fetchTiddler(title);
7905 var subTitle;
7906 if(tiddler) {
7907 subTitle = tiddler.getSubtitle();
7908 classes.pushUnique("tiddlyLinkExisting");
7909 classes.remove("tiddlyLinkNonExisting");
7910 classes.remove("shadow");
7911 } else {
7912 classes.remove("tiddlyLinkExisting");
7913 classes.pushUnique("tiddlyLinkNonExisting");
7914 if(store.isShadowTiddler(title)) {
7915 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7916 classes.pushUnique("shadow");
7917 } else {
7918 subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
7919 classes.remove("shadow");
7922 if(typeof config.annotations[title]=="string")
7923 subTitle = config.annotations[title];
7924 return {classes: classes.join(" "),subTitle: subTitle};
7927 function createExternalLink(place,url)
7929 var link = document.createElement("a");
7930 link.className = "externalLink";
7931 link.href = url;
7932 link.title = config.messages.externalLinkTooltip.format([url]);
7933 if(config.options.chkOpenInNewWindow)
7934 link.target = "_blank";
7935 place.appendChild(link);
7936 return link;
7939 // Event handler for clicking on a tiddly link
7940 function onClickTiddlerLink(ev)
7942 var e = ev || window.event;
7943 var target = resolveTarget(e);
7944 var link = target;
7945 var title = null;
7946 var fields = null;
7947 var noToggle = null;
7948 do {
7949 title = link.getAttribute("tiddlyLink");
7950 fields = link.getAttribute("tiddlyFields");
7951 noToggle = link.getAttribute("noToggle");
7952 link = link.parentNode;
7953 } while(title == null && link != null);
7954 if(!store.isShadowTiddler(title)) {
7955 var f = fields ? fields.decodeHashMap() : {};
7956 fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
7958 if(title) {
7959 var toggling = e.metaKey || e.ctrlKey;
7960 if(config.options.chkToggleLinks)
7961 toggling = !toggling;
7962 if(noToggle)
7963 toggling = false;
7964 if(store.getTiddler(title))
7965 fields = null;
7966 story.displayTiddler(target,title,null,true,null,fields,toggling);
7968 clearMessage();
7969 return false;
7972 // Create a button for a tag with a popup listing all the tiddlers that it tags
7973 function createTagButton(place,tag,excludeTiddler,title,tooltip)
7975 var btn = createTiddlyButton(place,title||tag,(tooltip||config.views.wikified.tag.tooltip).format([tag]),onClickTag);
7976 btn.setAttribute("tag",tag);
7977 if(excludeTiddler)
7978 btn.setAttribute("tiddler",excludeTiddler);
7979 return btn;
7982 // Event handler for clicking on a tiddler tag
7983 function onClickTag(ev)
7985 var e = ev || window.event;
7986 var popup = Popup.create(this);
7987 var tag = this.getAttribute("tag");
7988 var title = this.getAttribute("tiddler");
7989 if(popup && tag) {
7990 var tagged = store.getTaggedTiddlers(tag);
7991 var titles = [];
7992 var li,r;
7993 for(r=0;r<tagged.length;r++) {
7994 if(tagged[r].title != title)
7995 titles.push(tagged[r].title);
7997 var lingo = config.views.wikified.tag;
7998 if(titles.length > 0) {
7999 var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
8000 openAll.setAttribute("tag",tag);
8001 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
8002 for(r=0; r<titles.length; r++) {
8003 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
8005 } else {
8006 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
8008 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
8009 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
8010 createTiddlyText(h,lingo.openTag.format([tag]));
8012 Popup.show();
8013 e.cancelBubble = true;
8014 if(e.stopPropagation) e.stopPropagation();
8015 return false;
8018 // Event handler for 'open all' on a tiddler popup
8019 function onClickTagOpenAll(ev)
8021 var tiddlers = store.getTaggedTiddlers(this.getAttribute("tag"));
8022 story.displayTiddlers(this,tiddlers);
8023 return false;
8026 function onClickError(ev)
8028 var e = ev || window.event;
8029 var popup = Popup.create(this);
8030 var lines = this.getAttribute("errorText").split("\n");
8031 for(var t=0; t<lines.length; t++)
8032 createTiddlyElement(popup,"li",null,null,lines[t]);
8033 Popup.show();
8034 e.cancelBubble = true;
8035 if(e.stopPropagation) e.stopPropagation();
8036 return false;
8039 function createTiddlyDropDown(place,onchange,options,defaultValue)
8041 var sel = createTiddlyElement(place,"select");
8042 sel.onchange = onchange;
8043 for(var t=0; t<options.length; t++) {
8044 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
8045 e.value = options[t].name;
8046 if(options[t].name == defaultValue)
8047 e.selected = true;
8049 return sel;
8052 function createTiddlyPopup(place,caption,tooltip,tiddler)
8054 if(tiddler.text) {
8055 createTiddlyLink(place,caption,true);
8056 var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
8057 btn.tiddler = tiddler;
8058 } else {
8059 createTiddlyText(place,caption);
8063 function onClickTiddlyPopup(ev)
8065 var e = ev || window.event;
8066 var tiddler = this.tiddler;
8067 if(tiddler.text) {
8068 var popup = Popup.create(this,"div","popupTiddler");
8069 wikify(tiddler.text,popup,null,tiddler);
8070 Popup.show();
8072 if(e) e.cancelBubble = true;
8073 if(e && e.stopPropagation) e.stopPropagation();
8074 return false;
8077 function createTiddlyError(place,title,text)
8079 var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
8080 if(text) btn.setAttribute("errorText",text);
8083 function merge(dst,src,preserveExisting)
8085 for(var i in src) {
8086 if(!preserveExisting || dst[i] === undefined)
8087 dst[i] = src[i];
8089 return dst;
8092 // Returns a string containing the description of an exception, optionally prepended by a message
8093 function exceptionText(e,message)
8095 var s = e.description || e.toString();
8096 return message ? "%0:\n%1".format([message,s]) : s;
8099 // Displays an alert of an exception description with optional message
8100 function showException(e,message)
8102 alert(exceptionText(e,message));
8105 function alertAndThrow(m)
8107 alert(m);
8108 throw(m);
8111 function glyph(name)
8113 var g = config.glyphs;
8114 var b = g.currBrowser;
8115 if(b == null) {
8116 b = 0;
8117 while(!g.browsers[b]() && b < g.browsers.length-1)
8118 b++;
8119 g.currBrowser = b;
8121 if(!g.codes[name])
8122 return "";
8123 return g.codes[name][b];
8126 if(!window.console) {
8127 console = {log:function(message) {displayMessage(message);}};
8131 //- Animation engine
8134 function Animator()
8136 this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
8137 this.timerID = 0; // ID of the timer used for animating
8138 this.animations = []; // List of animations in progress
8139 return this;
8142 // Start animation engine
8143 Animator.prototype.startAnimating = function() //# Variable number of arguments
8145 for(var t=0; t<arguments.length; t++)
8146 this.animations.push(arguments[t]);
8147 if(this.running == 0) {
8148 var me = this;
8149 this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
8151 this.running += arguments.length;
8154 // Perform an animation engine tick, calling each of the known animation modules
8155 Animator.prototype.doAnimate = function(me)
8157 var a = 0;
8158 while(a < me.animations.length) {
8159 var animation = me.animations[a];
8160 if(animation.tick()) {
8161 a++;
8162 } else {
8163 me.animations.splice(a,1);
8164 if(--me.running == 0)
8165 window.clearInterval(me.timerID);
8170 Animator.slowInSlowOut = function(progress)
8172 return(1-((Math.cos(progress * Math.PI)+1)/2));
8175 //--
8176 //-- Morpher animation
8177 //--
8179 // Animate a set of properties of an element
8180 function Morpher(element,duration,properties,callback)
8182 this.element = element;
8183 this.duration = duration;
8184 this.properties = properties;
8185 this.startTime = new Date();
8186 this.endTime = Number(this.startTime) + duration;
8187 this.callback = callback;
8188 this.tick();
8189 return this;
8192 Morpher.prototype.assignStyle = function(element,style,value)
8194 switch(style) {
8195 case "-tw-vertScroll":
8196 window.scrollTo(findScrollX(),value);
8197 break;
8198 case "-tw-horizScroll":
8199 window.scrollTo(value,findScrollY());
8200 break;
8201 default:
8202 element.style[style] = value;
8203 break;
8207 Morpher.prototype.stop = function()
8209 for(var t=0; t<this.properties.length; t++) {
8210 var p = this.properties[t];
8211 if(p.atEnd !== undefined) {
8212 this.assignStyle(this.element,p.style,p.atEnd);
8215 if(this.callback)
8216 this.callback(this.element,this.properties);
8219 Morpher.prototype.tick = function()
8221 var currTime = Number(new Date());
8222 var progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
8223 for(var t=0; t<this.properties.length; t++) {
8224 var p = this.properties[t];
8225 if(p.start !== undefined && p.end !== undefined) {
8226 var template = p.template || "%0";
8227 switch(p.format) {
8228 case undefined:
8229 case "style":
8230 var v = p.start + (p.end-p.start) * progress;
8231 this.assignStyle(this.element,p.style,template.format([v]));
8232 break;
8233 case "color":
8234 break;
8238 if(currTime >= this.endTime) {
8239 this.stop();
8240 return false;
8242 return true;
8245 //--
8246 //-- Zoomer animation
8247 //--
8249 function Zoomer(text,startElement,targetElement,unused)
8251 var e = createTiddlyElement(document.body,"div",null,"zoomer");
8252 createTiddlyElement(e,"div",null,null,text);
8253 var winWidth = findWindowWidth();
8254 var winHeight = findWindowHeight();
8255 var p = [
8256 {style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
8257 {style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
8258 {style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
8259 {style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
8260 {style: 'fontSize', start: 8, end: 24, template: '%0pt'}
8262 var c = function(element,properties) {removeNode(element);};
8263 return new Morpher(e,config.animDuration,p,c);
8266 //--
8267 //-- Scroller animation
8268 //--
8270 function Scroller(targetElement)
8272 var p = [{style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}];
8273 return new Morpher(targetElement,config.animDuration,p);
8276 //--
8277 //-- Slider animation
8278 //--
8280 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
8281 function Slider(element,opening,unused,deleteMode)
8283 element.style.overflow = 'hidden';
8284 if(opening)
8285 element.style.height = '0px'; // Resolves a Firefox flashing bug
8286 element.style.display = 'block';
8287 var left = findPosX(element);
8288 var width = element.scrollWidth;
8289 var height = element.scrollHeight;
8290 var winWidth = findWindowWidth();
8291 var p = [];
8292 var c = null;
8293 if(opening) {
8294 p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
8295 p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
8296 p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
8297 } else {
8298 p.push({style: 'height', start: height, end: 0, template: '%0px'});
8299 p.push({style: 'display', atEnd: 'none'});
8300 p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
8301 p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
8302 switch(deleteMode) {
8303 case "all":
8304 c = function(element,properties) {removeNode(element);};
8305 break;
8306 case "children":
8307 c = function(element,properties) {removeChildren(element);};
8308 break;
8311 return new Morpher(element,config.animDuration,p,c);
8314 //--
8315 //-- Popup menu
8316 //--
8318 var Popup = {
8319 stack: [] // Array of objects with members root: and popup:
8322 Popup.create = function(root,elem,className)
8324 var stackPosition = this.find(root,"popup");
8325 Popup.remove(stackPosition+1);
8326 var popup = createTiddlyElement(document.body,elem || "ol","popup",className || "popup");
8327 popup.stackPosition = stackPosition;
8328 Popup.stack.push({root: root, popup: popup});
8329 return popup;
8332 Popup.onDocumentClick = function(ev)
8334 var e = ev || window.event;
8335 if(e.eventPhase == undefined)
8336 Popup.remove();
8337 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
8338 Popup.remove();
8339 return true;
8342 Popup.show = function(valign,halign,offset)
8344 var curr = Popup.stack[Popup.stack.length-1];
8345 this.place(curr.root,curr.popup,valign,halign,offset);
8346 addClass(curr.root,"highlight");
8347 if(config.options.chkAnimate && anim && typeof Scroller == "function")
8348 anim.startAnimating(new Scroller(curr.popup));
8349 else
8350 window.scrollTo(0,ensureVisible(curr.popup));
8353 Popup.place = function(root,popup,valign,halign,offset)
8355 if(!offset)
8356 var offset = {x:0,y:0};
8357 if(popup.stackPosition >= 0 && !valign && !halign) {
8358 offset.x = offset.x + root.offsetWidth;
8359 } else {
8360 offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
8361 offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
8363 var rootLeft = findPosX(root);
8364 var rootTop = findPosY(root);
8365 var popupLeft = rootLeft + offset.x;
8366 var popupTop = rootTop + offset.y;
8367 var winWidth = findWindowWidth();
8368 if(popup.offsetWidth > winWidth*0.75)
8369 popup.style.width = winWidth*0.75 + "px";
8370 var popupWidth = popup.offsetWidth;
8371 var scrollWidth = winWidth - document.body.offsetWidth;
8372 if(popupLeft + popupWidth > winWidth - scrollWidth - 1) {
8373 if(halign == 'right')
8374 popupLeft = popupLeft - root.offsetWidth - popupWidth;
8375 else
8376 popupLeft = winWidth - popupWidth - scrollWidth - 1;
8378 popup.style.left = popupLeft + "px";
8379 popup.style.top = popupTop + "px";
8380 popup.style.display = "block";
8383 Popup.find = function(e)
8385 var pos = -1;
8386 for (var t=this.stack.length-1; t>=0; t--) {
8387 if(isDescendant(e,this.stack[t].popup))
8388 pos = t;
8390 return pos;
8393 Popup.remove = function(pos)
8395 if(!pos) var pos = 0;
8396 if(Popup.stack.length > pos) {
8397 Popup.removeFrom(pos);
8401 Popup.removeFrom = function(from)
8403 for(var t=Popup.stack.length-1; t>=from; t--) {
8404 var p = Popup.stack[t];
8405 removeClass(p.root,"highlight");
8406 removeNode(p.popup);
8408 Popup.stack = Popup.stack.slice(0,from);
8411 //--
8412 //-- Wizard support
8413 //--
8415 function Wizard(elem)
8417 if(elem) {
8418 this.formElem = findRelated(elem,"wizard","className");
8419 this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
8420 this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
8421 } else {
8422 this.formElem = null;
8423 this.bodyElem = null;
8424 this.footElem = null;
8428 Wizard.prototype.setValue = function(name,value)
8430 if(this.formElem)
8431 this.formElem[name] = value;
8434 Wizard.prototype.getValue = function(name)
8436 return this.formElem ? this.formElem[name] : null;
8439 Wizard.prototype.createWizard = function(place,title)
8441 this.formElem = createTiddlyElement(place,"form",null,"wizard");
8442 createTiddlyElement(this.formElem,"h1",null,null,title);
8443 this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
8444 this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
8447 Wizard.prototype.clear = function()
8449 removeChildren(this.bodyElem);
8452 Wizard.prototype.setButtons = function(buttonInfo,status)
8454 removeChildren(this.footElem);
8455 for(var t=0; t<buttonInfo.length; t++) {
8456 createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
8457 insertSpacer(this.footElem);
8459 if(typeof status == "string") {
8460 createTiddlyElement(this.footElem,"span",null,"status",status);
8464 Wizard.prototype.addStep = function(stepTitle,html)
8466 removeChildren(this.bodyElem);
8467 var w = createTiddlyElement(this.bodyElem,"div");
8468 createTiddlyElement(w,"h2",null,null,stepTitle);
8469 var step = createTiddlyElement(w,"div",null,"wizardStep");
8470 step.innerHTML = html;
8471 applyHtmlMacros(step,tiddler);
8474 Wizard.prototype.getElement = function(name)
8476 return this.formElem.elements[name];
8479 //--
8480 //-- ListView gadget
8481 //--
8483 var ListView = {};
8485 // Create a listview
8486 ListView.create = function(place,listObject,listTemplate,callback,className)
8488 var table = createTiddlyElement(place,"table",null,className || "listView twtable");
8489 var thead = createTiddlyElement(table,"thead");
8490 var r = createTiddlyElement(thead,"tr");
8491 for(var t=0; t<listTemplate.columns.length; t++) {
8492 var columnTemplate = listTemplate.columns[t];
8493 var c = createTiddlyElement(r,"th");
8494 var colType = ListView.columnTypes[columnTemplate.type];
8495 if(colType && colType.createHeader) {
8496 colType.createHeader(c,columnTemplate,t);
8497 if(columnTemplate.className)
8498 addClass(c,columnTemplate.className);
8501 var tbody = createTiddlyElement(table,"tbody");
8502 for(var rc=0; rc<listObject.length; rc++) {
8503 var rowObject = listObject[rc];
8504 r = createTiddlyElement(tbody,"tr");
8505 for(c=0; c<listTemplate.rowClasses.length; c++) {
8506 if(rowObject[listTemplate.rowClasses[c].field])
8507 addClass(r,listTemplate.rowClasses[c].className);
8509 rowObject.rowElement = r;
8510 rowObject.colElements = {};
8511 for(var cc=0; cc<listTemplate.columns.length; cc++) {
8512 c = createTiddlyElement(r,"td");
8513 columnTemplate = listTemplate.columns[cc];
8514 var field = columnTemplate.field;
8515 colType = ListView.columnTypes[columnTemplate.type];
8516 if(colType && colType.createItem) {
8517 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
8518 if(columnTemplate.className)
8519 addClass(c,columnTemplate.className);
8521 rowObject.colElements[field] = c;
8524 if(callback && listTemplate.actions)
8525 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
8526 if(callback && listTemplate.buttons) {
8527 for(t=0; t<listTemplate.buttons.length; t++) {
8528 var a = listTemplate.buttons[t];
8529 if(a && a.name != "")
8530 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
8533 return table;
8536 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
8538 return function(e) {
8539 var view = findRelated(this,"TABLE",null,"previousSibling");
8540 var tiddlers = [];
8541 ListView.forEachSelector(view,function(e,rowName) {
8542 if(e.checked)
8543 tiddlers.push(rowName);
8545 if(tiddlers.length == 0 && !allowEmptySelection) {
8546 alert(config.messages.nothingSelected);
8547 } else {
8548 if(this.nodeName.toLowerCase() == "select") {
8549 callback(view,this.value,tiddlers);
8550 this.selectedIndex = 0;
8551 } else {
8552 callback(view,name,tiddlers);
8558 // Invoke a callback for each selector checkbox in the listview
8559 ListView.forEachSelector = function(view,callback)
8561 var checkboxes = view.getElementsByTagName("input");
8562 var hadOne = false;
8563 for(var t=0; t<checkboxes.length; t++) {
8564 var cb = checkboxes[t];
8565 if(cb.getAttribute("type") == "checkbox") {
8566 var rn = cb.getAttribute("rowName");
8567 if(rn) {
8568 callback(cb,rn);
8569 hadOne = true;
8573 return hadOne;
8576 ListView.getSelectedRows = function(view)
8578 var rowNames = [];
8579 ListView.forEachSelector(view,function(e,rowName) {
8580 if(e.checked)
8581 rowNames.push(rowName);
8583 return rowNames;
8586 ListView.columnTypes = {};
8588 ListView.columnTypes.String = {
8589 createHeader: function(place,columnTemplate,col)
8591 createTiddlyText(place,columnTemplate.title);
8593 createItem: function(place,listObject,field,columnTemplate,col,row)
8595 var v = listObject[field];
8596 if(v != undefined)
8597 createTiddlyText(place,v);
8601 ListView.columnTypes.WikiText = {
8602 createHeader: ListView.columnTypes.String.createHeader,
8603 createItem: function(place,listObject,field,columnTemplate,col,row)
8605 var v = listObject[field];
8606 if(v != undefined)
8607 wikify(v,place,null,null);
8611 ListView.columnTypes.Tiddler = {
8612 createHeader: ListView.columnTypes.String.createHeader,
8613 createItem: function(place,listObject,field,columnTemplate,col,row)
8615 var v = listObject[field];
8616 if(v != undefined && v.title)
8617 createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
8621 ListView.columnTypes.Size = {
8622 createHeader: ListView.columnTypes.String.createHeader,
8623 createItem: function(place,listObject,field,columnTemplate,col,row)
8625 var v = listObject[field];
8626 if(v != undefined) {
8627 var t = 0;
8628 while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
8629 t++;
8630 createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
8635 ListView.columnTypes.Link = {
8636 createHeader: ListView.columnTypes.String.createHeader,
8637 createItem: function(place,listObject,field,columnTemplate,col,row)
8639 var v = listObject[field];
8640 var c = columnTemplate.text;
8641 if(v != undefined)
8642 createTiddlyText(createExternalLink(place,v),c || v);
8646 ListView.columnTypes.Date = {
8647 createHeader: ListView.columnTypes.String.createHeader,
8648 createItem: function(place,listObject,field,columnTemplate,col,row)
8650 var v = listObject[field];
8651 if(v != undefined)
8652 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
8656 ListView.columnTypes.StringList = {
8657 createHeader: ListView.columnTypes.String.createHeader,
8658 createItem: function(place,listObject,field,columnTemplate,col,row)
8660 var v = listObject[field];
8661 if(v != undefined) {
8662 for(var t=0; t<v.length; t++) {
8663 createTiddlyText(place,v[t]);
8664 createTiddlyElement(place,"br");
8670 ListView.columnTypes.Selector = {
8671 createHeader: function(place,columnTemplate,col)
8673 createTiddlyCheckbox(place,null,false,this.onHeaderChange);
8675 createItem: function(place,listObject,field,columnTemplate,col,row)
8677 var e = createTiddlyCheckbox(place,null,listObject[field],null);
8678 e.setAttribute("rowName",listObject[columnTemplate.rowName]);
8680 onHeaderChange: function(e)
8682 var state = this.checked;
8683 var view = findRelated(this,"TABLE");
8684 if(!view)
8685 return;
8686 ListView.forEachSelector(view,function(e,rowName) {
8687 e.checked = state;
8692 ListView.columnTypes.Tags = {
8693 createHeader: ListView.columnTypes.String.createHeader,
8694 createItem: function(place,listObject,field,columnTemplate,col,row)
8696 var tags = listObject[field];
8697 createTiddlyText(place,String.encodeTiddlyLinkList(tags));
8701 ListView.columnTypes.Boolean = {
8702 createHeader: ListView.columnTypes.String.createHeader,
8703 createItem: function(place,listObject,field,columnTemplate,col,row)
8705 if(listObject[field] == true)
8706 createTiddlyText(place,columnTemplate.trueText);
8707 if(listObject[field] == false)
8708 createTiddlyText(place,columnTemplate.falseText);
8712 ListView.columnTypes.TagCheckbox = {
8713 createHeader: ListView.columnTypes.String.createHeader,
8714 createItem: function(place,listObject,field,columnTemplate,col,row)
8716 var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
8717 e.setAttribute("tiddler",listObject.title);
8718 e.setAttribute("tag",columnTemplate.tag);
8720 onChange : function(e)
8722 var tag = this.getAttribute("tag");
8723 var tiddler = this.getAttribute("tiddler");
8724 store.setTiddlerTag(tiddler,this.checked,tag);
8728 ListView.columnTypes.TiddlerLink = {
8729 createHeader: ListView.columnTypes.String.createHeader,
8730 createItem: function(place,listObject,field,columnTemplate,col,row)
8732 var v = listObject[field];
8733 if(v != undefined) {
8734 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
8735 createTiddlyText(link,listObject[field]);
8740 //--
8741 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8742 //--
8744 // Clamp a number to a range
8745 Number.prototype.clamp = function(min,max)
8747 var c = this;
8748 if(c < min)
8749 c = min;
8750 if(c > max)
8751 c = max;
8752 return c;
8755 // Add indexOf function if browser does not support it
8756 if(!Array.indexOf) {
8757 Array.prototype.indexOf = function(item,from)
8759 if(!from)
8760 from = 0;
8761 for(var i=from; i<this.length; i++) {
8762 if(this[i] === item)
8763 return i;
8765 return -1;
8768 // Find an entry in a given field of the members of an array
8769 Array.prototype.findByField = function(field,value)
8771 for(var t=0; t<this.length; t++) {
8772 if(this[t][field] == value)
8773 return t;
8775 return null;
8778 // Return whether an entry exists in an array
8779 Array.prototype.contains = function(item)
8781 return this.indexOf(item) != -1;
8784 // Adds, removes or toggles a particular value within an array
8785 // value - value to add
8786 // mode - +1 to add value, -1 to remove value, 0 to toggle it
8787 Array.prototype.setItem = function(value,mode)
8789 var p = this.indexOf(value);
8790 if(mode == 0)
8791 mode = (p == -1) ? +1 : -1;
8792 if(mode == +1) {
8793 if(p == -1)
8794 this.push(value);
8795 } else if(mode == -1) {
8796 if(p != -1)
8797 this.splice(p,1);
8801 // Return whether one of a list of values exists in an array
8802 Array.prototype.containsAny = function(items)
8804 for(var i=0; i<items.length; i++) {
8805 if(this.indexOf(items[i]) != -1)
8806 return true;
8808 return false;
8811 // Return whether all of a list of values exists in an array
8812 Array.prototype.containsAll = function(items)
8814 for(var i = 0; i<items.length; i++) {
8815 if(this.indexOf(items[i]) == -1)
8816 return false;
8818 return true;
8821 // 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
8822 Array.prototype.pushUnique = function(item,unique)
8824 if(unique === false) {
8825 this.push(item);
8826 } else {
8827 if(this.indexOf(item) == -1)
8828 this.push(item);
8832 Array.prototype.remove = function(item)
8834 var p = this.indexOf(item);
8835 if(p != -1)
8836 this.splice(p,1);
8839 if(!Array.prototype.map) {
8840 Array.prototype.map = function(fn,thisObj)
8842 var scope = thisObj || window;
8843 var a = [];
8844 for(var i=0, j=this.length; i < j; ++i) {
8845 a.push(fn.call(scope,this[i],i,this));
8847 return a;
8850 // Get characters from the right end of a string
8851 String.prototype.right = function(n)
8853 return n < this.length ? this.slice(this.length-n) : this;
8856 // Trim whitespace from both ends of a string
8857 String.prototype.trim = function()
8859 return this.replace(/^\s*|\s*$/g,"");
8862 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
8863 String.prototype.unDash = function()
8865 var s = this.split("-");
8866 if(s.length > 1) {
8867 for(var t=1; t<s.length; t++)
8868 s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
8870 return s.join("");
8873 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
8874 String.prototype.format = function(substrings)
8876 var subRegExp = /(?:%(\d+))/mg;
8877 var currPos = 0;
8878 var r = [];
8879 do {
8880 var match = subRegExp.exec(this);
8881 if(match && match[1]) {
8882 if(match.index > currPos)
8883 r.push(this.substring(currPos,match.index));
8884 r.push(substrings[parseInt(match[1])]);
8885 currPos = subRegExp.lastIndex;
8887 } while(match);
8888 if(currPos < this.length)
8889 r.push(this.substring(currPos,this.length));
8890 return r.join("");
8893 // Escape any special RegExp characters with that character preceded by a backslash
8894 String.prototype.escapeRegExp = function()
8896 var s = "\\^$*+?()=!|,{}[].";
8897 var c = this;
8898 for(var t=0; t<s.length; t++)
8899 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
8900 return c;
8903 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
8904 String.prototype.escapeLineBreaks = function()
8906 return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
8909 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
8910 String.prototype.unescapeLineBreaks = function()
8912 return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
8915 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
8916 String.prototype.htmlEncode = function()
8918 return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
8921 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
8922 String.prototype.htmlDecode = function()
8924 return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
8927 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
8928 String.prototype.toJSONString = function()
8930 var m = {
8931 '\b': '\\b',
8932 '\f': '\\f',
8933 '\n': '\\n',
8934 '\r': '\\r',
8935 '\t': '\\t',
8936 '"' : '\\"',
8937 '\\': '\\\\'
8939 var replaceFn = function(a,b) {
8940 var c = m[b];
8941 if(c)
8942 return c;
8943 c = b.charCodeAt();
8944 return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
8946 if(/["\\\x00-\x1f]/.test(this))
8947 return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
8948 return '"' + this + '"';
8951 // Parse a space-separated string of name:value parameters
8952 // The result is an array of objects:
8953 // result[0] = object with a member for each parameter name, value of that member being an array of values
8954 // result[1..n] = one object for each parameter, with 'name' and 'value' members
8955 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
8957 var parseToken = function(match,p) {
8958 var n;
8959 if(match[p]) // Double quoted
8960 n = match[p];
8961 else if(match[p+1]) // Single quoted
8962 n = match[p+1];
8963 else if(match[p+2]) // Double-square-bracket quoted
8964 n = match[p+2];
8965 else if(match[p+3]) // Double-brace quoted
8966 try {
8967 n = match[p+3];
8968 if(allowEval)
8969 n = window.eval(n);
8970 } catch(ex) {
8971 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
8973 else if(match[p+4]) // Unquoted
8974 n = match[p+4];
8975 else if(match[p+5]) // empty quote
8976 n = "";
8977 return n;
8979 var r = [{}];
8980 var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
8981 var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
8982 var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
8983 var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
8984 var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
8985 var emptyQuote = "((?:\"\")|(?:''))";
8986 var skipSpace = "(?:\\s*)";
8987 var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
8988 var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
8989 var params = [];
8990 do {
8991 var match = re.exec(this);
8992 if(match) {
8993 var n = parseToken(match,1);
8994 if(noNames) {
8995 r.push({name:"",value:n});
8996 } else {
8997 var v = parseToken(match,8);
8998 if(v == null && defaultName) {
8999 v = n;
9000 n = defaultName;
9001 } else if(v == null && defaultValue) {
9002 v = defaultValue;
9004 r.push({name:n,value:v});
9005 if(cascadeDefaults) {
9006 defaultName = n;
9007 defaultValue = v;
9011 } while(match);
9012 // Summarise parameters into first element
9013 for(var t=1; t<r.length; t++) {
9014 if(r[0][r[t].name])
9015 r[0][r[t].name].push(r[t].value);
9016 else
9017 r[0][r[t].name] = [r[t].value];
9019 return r;
9022 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
9023 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
9024 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
9025 String.prototype.readMacroParams = function()
9027 var p = this.parseParams("list",null,true,true);
9028 var n = [];
9029 for(var t=1; t<p.length; t++)
9030 n.push(p[t].value);
9031 return n;
9034 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
9035 String.prototype.readBracketedList = function(unique)
9037 var p = this.parseParams("list",null,false,true);
9038 var n = [];
9039 for(var t=1; t<p.length; t++) {
9040 if(p[t].value)
9041 n.pushUnique(p[t].value,unique);
9043 return n;
9046 // Returns array with start and end index of chunk between given start and end marker, or undefined.
9047 String.prototype.getChunkRange = function(start,end)
9049 var s = this.indexOf(start);
9050 if(s != -1) {
9051 s += start.length;
9052 var e = this.indexOf(end,s);
9053 if(e != -1)
9054 return [s,e];
9058 // Replace a chunk of a string given start and end markers
9059 String.prototype.replaceChunk = function(start,end,sub)
9061 var r = this.getChunkRange(start,end);
9062 return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
9065 // Returns a chunk of a string between start and end markers, or undefined
9066 String.prototype.getChunk = function(start,end)
9068 var r = this.getChunkRange(start,end);
9069 if(r)
9070 return this.substring(r[0],r[1]);
9074 // Static method to bracket a string with double square brackets if it contains a space
9075 String.encodeTiddlyLink = function(title)
9077 return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
9080 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
9081 String.encodeTiddlyLinkList = function(list)
9083 if(list) {
9084 var results = [];
9085 for(var t=0; t<list.length; t++)
9086 results.push(String.encodeTiddlyLink(list[t]));
9087 return results.join(" ");
9088 } else {
9089 return "";
9093 // Convert a string as a sequence of name:"value" pairs into a hashmap
9094 String.prototype.decodeHashMap = function()
9096 var fields = this.parseParams("anon","",false);
9097 var r = {};
9098 for(var t=1; t<fields.length; t++)
9099 r[fields[t].name] = fields[t].value;
9100 return r;
9103 // Static method to encode a hashmap into a name:"value"... string
9104 String.encodeHashMap = function(hashmap)
9106 var r = [];
9107 for(var t in hashmap)
9108 r.push(t + ':"' + hashmap[t] + '"');
9109 return r.join(" ");
9112 // Static method to left-pad a string with 0s to a certain width
9113 String.zeroPad = function(n,d)
9115 var s = n.toString();
9116 if(s.length < d)
9117 s = "000000000000000000000000000".substr(0,d-s.length) + s;
9118 return s;
9121 String.prototype.startsWith = function(prefix)
9123 return !prefix || this.substring(0,prefix.length) == prefix;
9126 // Returns the first value of the given named parameter.
9127 function getParam(params,name,defaultValue)
9129 if(!params)
9130 return defaultValue;
9131 var p = params[0][name];
9132 return p ? p[0] : defaultValue;
9135 // Returns the first value of the given boolean named parameter.
9136 function getFlag(params,name,defaultValue)
9138 return !!getParam(params,name,defaultValue);
9141 // Substitute date components into a string
9142 Date.prototype.formatString = function(template)
9144 var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
9145 t = t.replace(/hh12/g,this.getHours12());
9146 t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
9147 t = t.replace(/hh/g,this.getHours());
9148 t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
9149 t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
9150 t = t.replace(/mm/g,this.getMinutes());
9151 t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
9152 t = t.replace(/ss/g,this.getSeconds());
9153 t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
9154 t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
9155 t = t.replace(/wYYYY/g,this.getYearForWeekNo());
9156 t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
9157 t = t.replace(/YYYY/g,this.getFullYear());
9158 t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
9159 t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
9160 t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
9161 t = t.replace(/MM/g,this.getMonth()+1);
9162 t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
9163 t = t.replace(/WW/g,this.getWeek());
9164 t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
9165 t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
9166 t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
9167 t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
9168 t = t.replace(/DD/g,this.getDate());
9169 var tz = this.getTimezoneOffset();
9170 var atz = Math.abs(tz);
9171 t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60),2) + ':' + String.zeroPad(atz % 60,2));
9172 t = t.replace(/\\/g,"");
9173 return t;
9176 Date.prototype.getWeek = function()
9178 var dt = new Date(this.getTime());
9179 var d = dt.getDay();
9180 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
9181 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
9182 var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
9183 return Math.floor(n/7)+1;
9186 Date.prototype.getYearForWeekNo = function()
9188 var dt = new Date(this.getTime());
9189 var d = dt.getDay();
9190 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
9191 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
9192 return dt.getFullYear();
9195 Date.prototype.getHours12 = function()
9197 var h = this.getHours();
9198 return h > 12 ? h-12 : ( h > 0 ? h : 12 );
9201 Date.prototype.getAmPm = function()
9203 return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
9206 Date.prototype.daySuffix = function()
9208 return config.messages.dates.daySuffixes[this.getDate()-1];
9211 // Convert a date to local YYYYMMDDHHMM string format
9212 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
9214 return this.getFullYear() + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
9217 // Convert a date to UTC YYYYMMDDHHMM string format
9218 Date.prototype.convertToYYYYMMDDHHMM = function()
9220 return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
9223 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
9224 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
9226 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);
9229 // Static method to create a date from a UTC YYYYMMDDHHMM format string
9230 Date.convertFromYYYYMMDDHHMM = function(d)
9232 var hh = d.substr(8,2) || "00";
9233 var mm = d.substr(10,2) || "00";
9234 return new Date(Date.UTC(parseInt(d.substr(0,4),10),
9235 parseInt(d.substr(4,2),10)-1,
9236 parseInt(d.substr(6,2),10),
9237 parseInt(hh,10),
9238 parseInt(mm,10),0,0));
9241 //--
9242 //-- Crypto functions and associated conversion routines
9243 //--
9245 // Crypto 'namespace'
9246 function Crypto() {}
9248 // Convert a string to an array of big-endian 32-bit words
9249 Crypto.strToBe32s = function(str)
9251 var be=[];
9252 var len=Math.floor(str.length/4);
9253 var i, j;
9254 for(i=0, j=0; i<len; i++, j+=4) {
9255 be[i]=((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
9257 while(j<str.length) {
9258 be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
9259 j++;
9261 return be;
9264 // Convert an array of big-endian 32-bit words to a string
9265 Crypto.be32sToStr = function(be)
9267 var str='';
9268 for(var i=0;i<be.length*32;i+=8) {
9269 str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
9271 return str;
9274 // Convert an array of big-endian 32-bit words to a hex string
9275 Crypto.be32sToHex = function(be)
9277 var hex='0123456789ABCDEF';
9278 var str='';
9279 for(var i=0;i<be.length*4;i++) {
9280 str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
9282 return str;
9285 // Return, in hex, the SHA-1 hash of a string
9286 Crypto.hexSha1Str = function(str)
9288 return Crypto.be32sToHex(Crypto.sha1Str(str));
9291 // Return the SHA-1 hash of a string
9292 Crypto.sha1Str = function(str)
9294 return Crypto.sha1(Crypto.strToBe32s(str),str.length);
9297 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
9298 Crypto.sha1 = function(x,blen)
9300 // Add 32-bit integers, wrapping at 32 bits
9301 function add32(a,b)
9303 var lsw=(a&0xFFFF)+(b&0xFFFF);
9304 var msw=(a>>16)+(b>>16)+(lsw>>16);
9305 return (msw<<16)|(lsw&0xFFFF);
9307 function AA(a,b,c,d,e)
9309 b=(b>>>27)|(b<<5);
9310 var lsw=(a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
9311 var msw=(a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
9312 return (msw<<16)|(lsw&0xFFFF);
9314 function RR(w,j)
9316 var n=w[j-3]^w[j-8]^w[j-14]^w[j-16];
9317 return (n>>>31)|(n<<1);
9320 var len=blen*8;
9321 x[len>>5] |= 0x80 << (24-len%32);
9322 x[((len+64>>9)<<4)+15]=len;
9323 var w=new Array(80);
9325 var k1=0x5A827999;
9326 var k2=0x6ED9EBA1;
9327 var k3=0x8F1BBCDC;
9328 var k4=0xCA62C1D6;
9330 var h0=0x67452301;
9331 var h1=0xEFCDAB89;
9332 var h2=0x98BADCFE;
9333 var h3=0x10325476;
9334 var h4=0xC3D2E1F0;
9336 for(var i=0;i<x.length;i+=16) {
9337 var j=0;
9338 var t;
9339 var a=h0;
9340 var b=h1;
9341 var c=h2;
9342 var d=h3;
9343 var e=h4;
9344 while(j<16) {
9345 w[j]=x[i+j];
9346 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9347 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9349 while(j<20) {
9350 w[j]=RR(w,j);
9351 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9352 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9354 while(j<40) {
9355 w[j]=RR(w,j);
9356 t=AA(e,a,b^c^d,w[j],k2);
9357 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9359 while(j<60) {
9360 w[j]=RR(w,j);
9361 t=AA(e,a,(b&c)|(d&(b|c)),w[j],k3);
9362 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9364 while(j<80) {
9365 w[j]=RR(w,j);
9366 t=AA(e,a,b^c^d,w[j],k4);
9367 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9369 h0=add32(h0,a);
9370 h1=add32(h1,b);
9371 h2=add32(h2,c);
9372 h3=add32(h3,d);
9373 h4=add32(h4,e);
9375 return [h0,h1,h2,h3,h4];
9378 //--
9379 //-- RGB colour object
9380 //--
9382 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
9383 function RGB(r,g,b)
9385 this.r = 0;
9386 this.g = 0;
9387 this.b = 0;
9388 if(typeof r == "string") {
9389 if(r.substr(0,1) == "#") {
9390 if(r.length == 7) {
9391 this.r = parseInt(r.substr(1,2),16)/255;
9392 this.g = parseInt(r.substr(3,2),16)/255;
9393 this.b = parseInt(r.substr(5,2),16)/255;
9394 } else {
9395 this.r = parseInt(r.substr(1,1),16)/15;
9396 this.g = parseInt(r.substr(2,1),16)/15;
9397 this.b = parseInt(r.substr(3,1),16)/15;
9399 } else {
9400 var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
9401 var c = r.match(rgbPattern);
9402 if(c) {
9403 this.r = parseInt(c[1],10)/255;
9404 this.g = parseInt(c[2],10)/255;
9405 this.b = parseInt(c[3],10)/255;
9408 } else {
9409 this.r = r;
9410 this.g = g;
9411 this.b = b;
9413 return this;
9416 // Mixes this colour with another in a specified proportion
9417 // c = other colour to mix
9418 // f = 0..1 where 0 is this colour and 1 is the new colour
9419 // Returns an RGB object
9420 RGB.prototype.mix = function(c,f)
9422 return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
9425 // Return an rgb colour as a #rrggbb format hex string
9426 RGB.prototype.toString = function()
9428 return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
9429 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
9430 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
9433 //--
9434 //-- DOM utilities - many derived from www.quirksmode.org
9435 //--
9437 function drawGradient(place,horiz,locolors,hicolors)
9439 if(!hicolors)
9440 hicolors = locolors;
9441 for(var t=0; t<= 100; t+=2) {
9442 var bar = document.createElement("div");
9443 place.appendChild(bar);
9444 bar.style.position = "absolute";
9445 bar.style.left = horiz ? t + "%" : 0;
9446 bar.style.top = horiz ? 0 : t + "%";
9447 bar.style.width = horiz ? (101-t) + "%" : "100%";
9448 bar.style.height = horiz ? "100%" : (101-t) + "%";
9449 bar.style.zIndex = -1;
9450 var p = t/100*(locolors.length-1);
9451 bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)],p-Math.floor(p)).toString();
9455 function createTiddlyText(parent,text)
9457 return parent.appendChild(document.createTextNode(text));
9460 function createTiddlyCheckbox(parent,caption,checked,onChange)
9462 var cb = document.createElement("input");
9463 cb.setAttribute("type","checkbox");
9464 cb.onclick = onChange;
9465 parent.appendChild(cb);
9466 cb.checked = checked;
9467 cb.className = "chkOptionInput";
9468 if(caption)
9469 wikify(caption,parent);
9470 return cb;
9473 function createTiddlyElement(parent,element,id,className,text,attribs)
9475 var e = document.createElement(element);
9476 if(className != null)
9477 e.className = className;
9478 if(id != null)
9479 e.setAttribute("id",id);
9480 if(text != null)
9481 e.appendChild(document.createTextNode(text));
9482 if(attribs) {
9483 for(var n in attribs) {
9484 e.setAttribute(n,attribs[n]);
9487 if(parent != null)
9488 parent.appendChild(e);
9489 return e;
9492 function addEvent(obj,type,fn)
9494 if(obj.attachEvent) {
9495 obj['e'+type+fn] = fn;
9496 obj[type+fn] = function(){obj['e'+type+fn](window.event);};
9497 obj.attachEvent('on'+type,obj[type+fn]);
9498 } else {
9499 obj.addEventListener(type,fn,false);
9503 function removeEvent(obj,type,fn)
9505 if(obj.detachEvent) {
9506 obj.detachEvent('on'+type,obj[type+fn]);
9507 obj[type+fn] = null;
9508 } else {
9509 obj.removeEventListener(type,fn,false);
9513 function addClass(e,className)
9515 var currClass = e.className.split(" ");
9516 if(currClass.indexOf(className) == -1)
9517 e.className += " " + className;
9520 function removeClass(e,className)
9522 var currClass = e.className.split(" ");
9523 var i = currClass.indexOf(className);
9524 while(i != -1) {
9525 currClass.splice(i,1);
9526 i = currClass.indexOf(className);
9528 e.className = currClass.join(" ");
9531 function hasClass(e,className)
9533 if(e.className && e.className.split(" ").indexOf(className) != -1) {
9534 return true;
9536 return false;
9539 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
9540 function findRelated(e,value,name,relative)
9542 name = name || "tagName";
9543 relative = relative || "parentNode";
9544 if(name == "className") {
9545 while(e && !hasClass(e,value)) {
9546 e = e[relative];
9548 } else {
9549 while(e && e[name] != value) {
9550 e = e[relative];
9553 return e;
9556 // Resolve the target object of an event
9557 function resolveTarget(e)
9559 var obj;
9560 if(e.target)
9561 obj = e.target;
9562 else if(e.srcElement)
9563 obj = e.srcElement;
9564 if(obj.nodeType == 3) // defeat Safari bug
9565 obj = obj.parentNode;
9566 return obj;
9569 // Prevent an event from bubbling
9570 function stopEvent(e)
9572 var ev = e || window.event;
9573 ev.cancelBubble = true;
9574 if(ev.stopPropagation) ev.stopPropagation();
9575 return false;
9578 // Return the content of an element as plain text with no formatting
9579 function getPlainText(e)
9581 var text = "";
9582 if(e.innerText)
9583 text = e.innerText;
9584 else if(e.textContent)
9585 text = e.textContent;
9586 return text;
9589 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
9590 function ensureVisible(e)
9592 var posTop = findPosY(e);
9593 var posBot = posTop + e.offsetHeight;
9594 var winTop = findScrollY();
9595 var winHeight = findWindowHeight();
9596 var winBot = winTop + winHeight;
9597 if(posTop < winTop) {
9598 return posTop;
9599 } else if(posBot > winBot) {
9600 if(e.offsetHeight < winHeight)
9601 return posTop - (winHeight - e.offsetHeight);
9602 else
9603 return posTop;
9604 } else {
9605 return winTop;
9609 // Get the current width of the display window
9610 function findWindowWidth()
9612 return window.innerWidth || document.documentElement.clientWidth;
9615 // Get the current height of the display window
9616 function findWindowHeight()
9618 return window.innerHeight || document.documentElement.clientHeight;
9621 // Get the current horizontal page scroll position
9622 function findScrollX()
9624 return window.scrollX || document.documentElement.scrollLeft;
9627 // Get the current vertical page scroll position
9628 function findScrollY()
9630 return window.scrollY || document.documentElement.scrollTop;
9633 function findPosX(obj)
9635 var curleft = 0;
9636 while(obj.offsetParent) {
9637 curleft += obj.offsetLeft;
9638 obj = obj.offsetParent;
9640 return curleft;
9643 function findPosY(obj)
9645 var curtop = 0;
9646 while(obj.offsetParent) {
9647 curtop += obj.offsetTop;
9648 obj = obj.offsetParent;
9650 return curtop;
9653 // Blur a particular element
9654 function blurElement(e)
9656 if(e && e.focus && e.blur) {
9657 e.focus();
9658 e.blur();
9662 // Create a non-breaking space
9663 function insertSpacer(place)
9665 var e = document.createTextNode(String.fromCharCode(160));
9666 if(place)
9667 place.appendChild(e);
9668 return e;
9671 // Remove all children of a node
9672 function removeChildren(e)
9674 while(e && e.hasChildNodes())
9675 removeNode(e.firstChild);
9678 // Remove a node and all it's children
9679 function removeNode(e)
9681 scrubNode(e);
9682 e.parentNode.removeChild(e);
9685 // Remove any event handlers or non-primitve custom attributes
9686 function scrubNode(e)
9688 if(!config.browser.isIE)
9689 return;
9690 var att = e.attributes;
9691 if(att) {
9692 for(var t=0; t<att.length; t++) {
9693 var n = att[t].name;
9694 if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
9695 try {
9696 e[n] = null;
9697 } catch(ex) {
9702 var c = e.firstChild;
9703 while(c) {
9704 scrubNode(c);
9705 c = c.nextSibling;
9709 // Add a stylesheet, replacing any previous custom stylesheet
9710 function setStylesheet(s,id,doc)
9712 if(!id)
9713 id = "customStyleSheet";
9714 if(!doc)
9715 doc = document;
9716 var n = doc.getElementById(id);
9717 if(doc.createStyleSheet) {
9718 // Test for IE's non-standard createStyleSheet method
9719 if(n)
9720 n.parentNode.removeChild(n);
9721 // This failed without the &nbsp;
9722 doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
9723 } else {
9724 if(n) {
9725 n.replaceChild(doc.createTextNode(s),n.firstChild);
9726 } else {
9727 n = doc.createElement("style");
9728 n.type = "text/css";
9729 n.id = id;
9730 n.appendChild(doc.createTextNode(s));
9731 doc.getElementsByTagName("head")[0].appendChild(n);
9736 function removeStyleSheet(id)
9738 var e = document.getElementById(id);
9739 if(e)
9740 e.parentNode.removeChild(e);
9743 // Force the browser to do a document reflow when needed to workaround browser bugs
9744 function forceReflow()
9746 if(config.browser.isGecko) {
9747 setStylesheet("body {top:0px;margin-top:0px;}","forceReflow");
9748 setTimeout(function() {setStylesheet("","forceReflow");},1);
9752 // Replace the current selection of a textarea or text input and scroll it into view
9753 function replaceSelection(e,text)
9755 if(e.setSelectionRange) {
9756 var oldpos = e.selectionStart;
9757 var isRange = e.selectionEnd > e.selectionStart;
9758 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
9759 e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
9760 var linecount = e.value.split('\n').length;
9761 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
9762 e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
9763 } else if(document.selection) {
9764 var range = document.selection.createRange();
9765 if(range.parentElement() == e) {
9766 var isCollapsed = range.text == "";
9767 range.text = text;
9768 if(!isCollapsed) {
9769 range.moveStart('character', -text.length);
9770 range.select();
9776 // Returns the text of the given (text) node, possibly merging subsequent text nodes
9777 function getNodeText(e)
9779 var t = "";
9780 while(e && e.nodeName == "#text") {
9781 t += e.nodeValue;
9782 e = e.nextSibling;
9784 return t;
9787 // Returns true if the element e has a given ancestor element
9788 function isDescendant(e,ancestor)
9790 while(e) {
9791 if(e === ancestor)
9792 return true;
9793 e = e.parentNode;
9795 return false;
9798 //--
9799 //-- LoaderBase and SaverBase
9800 //--
9802 function LoaderBase() {}
9804 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9806 var title = this.getTitle(store,node);
9807 if(safeMode && store.isShadowTiddler(title))
9808 return;
9809 if(title) {
9810 var tiddler = store.createTiddler(title);
9811 this.internalizeTiddler(store,tiddler,title,node);
9812 tiddlers.push(tiddler);
9816 LoaderBase.prototype.loadTiddlers = function(store,nodes)
9818 var tiddlers = [];
9819 for(var t = 0; t < nodes.length; t++) {
9820 try {
9821 this.loadTiddler(store,nodes[t],tiddlers);
9822 } catch(ex) {
9823 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9826 return tiddlers;
9829 function SaverBase() {}
9831 SaverBase.prototype.externalize = function(store)
9833 var results = [];
9834 var tiddlers = store.getTiddlers("title");
9835 for(var t = 0; t < tiddlers.length; t++) {
9836 if(!tiddlers[t].doNotSave())
9837 results.push(this.externalizeTiddler(store, tiddlers[t]));
9839 return results.join("\n");
9842 //--
9843 //-- TW21Loader (inherits from LoaderBase)
9844 //--
9846 function TW21Loader() {}
9848 TW21Loader.prototype = new LoaderBase();
9850 TW21Loader.prototype.getTitle = function(store,node)
9852 var title = null;
9853 if(node.getAttribute) {
9854 title = node.getAttribute("title");
9855 if(!title)
9856 title = node.getAttribute("tiddler");
9858 if(!title && node.id) {
9859 var lenPrefix = store.idPrefix.length;
9860 if(node.id.substr(0,lenPrefix) == store.idPrefix)
9861 title = node.id.substr(lenPrefix);
9863 return title;
9866 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9868 var e = node.firstChild;
9869 var text = null;
9870 if(node.getAttribute("tiddler")) {
9871 text = getNodeText(e).unescapeLineBreaks();
9872 } else {
9873 while(e.nodeName!="PRE" && e.nodeName!="pre") {
9874 e = e.nextSibling;
9876 text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
9878 var modifier = node.getAttribute("modifier");
9879 var c = node.getAttribute("created");
9880 var m = node.getAttribute("modified");
9881 var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
9882 var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
9883 var tags = node.getAttribute("tags");
9884 var fields = {};
9885 var attrs = node.attributes;
9886 for(var i = attrs.length-1; i >= 0; i--) {
9887 var name = attrs[i].name;
9888 if(attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
9889 fields[name] = attrs[i].value.unescapeLineBreaks();
9892 tiddler.assign(title,text,modifier,modified,tags,created,fields);
9893 return tiddler;
9896 //--
9897 //-- TW21Saver (inherits from SaverBase)
9898 //--
9900 function TW21Saver() {}
9902 TW21Saver.prototype = new SaverBase();
9904 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
9906 try {
9907 var extendedAttributes = "";
9908 var usePre = config.options.chkUsePreForStorage;
9909 store.forEachField(tiddler,
9910 function(tiddler,fieldName,value) {
9911 // don't store stuff from the temp namespace
9912 if(typeof value != "string")
9913 value = "";
9914 if(!fieldName.match(/^temp\./))
9915 extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
9916 },true);
9917 var created = tiddler.created;
9918 var modified = tiddler.modified;
9919 var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
9920 attributes += (usePre && created == version.date) ? "" :' created="' + created.convertToYYYYMMDDHHMM() + '"';
9921 attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() +'"';
9922 var tags = tiddler.getTags();
9923 if(!usePre || tags)
9924 attributes += ' tags="' + tags.htmlEncode() + '"';
9925 return ('<div %0="%1"%2%3>%4</'+'div>').format([
9926 usePre ? "title" : "tiddler",
9927 tiddler.title.htmlEncode(),
9928 attributes,
9929 extendedAttributes,
9930 usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9932 } catch (ex) {
9933 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9937 //]]>
9938 </script>
9939 <script type="text/javascript">
9940 //<![CDATA[
9941 if(useJavaSaver)
9942 document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9943 //]]>
9944 </script>
9945 <!--POST-SCRIPT-START-->
9947 <!--POST-SCRIPT-END-->
9948 </body>
9949 </html>