Add taggable-mixin to Melange repository.
[Melange.git] / app / taggable-mixin / taggable.html
blobd0935011d32cf50e3c45355986dc1f750ca0389b
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: 0, date: new Date("May 9, 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> Taggable - a portable mixin class for adding Tags to Google AppEngine Models </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 #messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
155 #messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
157 .popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
159 .popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
160 .popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
161 .popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
162 .popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
163 .popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
164 .popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
165 .popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
166 .listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
168 .tiddler .defaultCommand {font-weight:bold;}
170 .shadow .title {color:[[ColorPalette::TertiaryDark]];}
172 .title {color:[[ColorPalette::SecondaryDark]];}
173 .subtitle {color:[[ColorPalette::TertiaryDark]];}
175 .toolbar {color:[[ColorPalette::PrimaryMid]];}
176 .toolbar a {color:[[ColorPalette::TertiaryLight]];}
177 .selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
178 .selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
180 .tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
181 .selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
182 .tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
183 .tagging .button, .tagged .button {border:none;}
185 .footer {color:[[ColorPalette::TertiaryLight]];}
186 .selected .footer {color:[[ColorPalette::TertiaryMid]];}
188 .sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
189 .sparktick {background:[[ColorPalette::PrimaryDark]];}
191 .error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
192 .warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
193 .lowlight {background:[[ColorPalette::TertiaryLight]];}
195 .zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
197 .imageLink, #displayArea .imageLink {background:transparent;}
199 .annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
201 .viewer .listTitle {list-style-type:none; margin-left:-2em;}
202 .viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
203 .viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
205 .viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
206 .viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
207 .viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
209 .viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
210 .viewer code {color:[[ColorPalette::SecondaryDark]];}
211 .viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
213 .highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
215 .editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
216 .editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
217 .editorFooter {color:[[ColorPalette::TertiaryMid]];}
219 #backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
220 #backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
221 #backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
222 #backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
223 #backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
224 #backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
225 #backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
226 .backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
227 .backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
228 #backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
229 /*}}}*/</pre>
230 </div>
231 <div title="StyleSheetLayout">
232 <pre>/*{{{*/
233 * html .tiddler {height:1%;}
235 body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
237 h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
238 h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
239 h4,h5,h6 {margin-top:1em;}
240 h1 {font-size:1.35em;}
241 h2 {font-size:1.25em;}
242 h3 {font-size:1.1em;}
243 h4 {font-size:1em;}
244 h5 {font-size:.9em;}
246 hr {height:1px;}
248 a {text-decoration:none;}
250 dt {font-weight:bold;}
252 ol {list-style-type:decimal;}
253 ol ol {list-style-type:lower-alpha;}
254 ol ol ol {list-style-type:lower-roman;}
255 ol ol ol ol {list-style-type:decimal;}
256 ol ol ol ol ol {list-style-type:lower-alpha;}
257 ol ol ol ol ol ol {list-style-type:lower-roman;}
258 ol ol ol ol ol ol ol {list-style-type:decimal;}
260 .txtOptionInput {width:11em;}
262 #contentWrapper .chkOptionInput {border:0;}
264 .externalLink {text-decoration:underline;}
266 .indent {margin-left:3em;}
267 .outdent {margin-left:3em; text-indent:-3em;}
268 code.escaped {white-space:nowrap;}
270 .tiddlyLinkExisting {font-weight:bold;}
271 .tiddlyLinkNonExisting {font-style:italic;}
273 /* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
274 a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
276 #mainMenu .tiddlyLinkExisting,
277 #mainMenu .tiddlyLinkNonExisting,
278 #sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
279 #sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
281 .header {position:relative;}
282 .header a:hover {background:transparent;}
283 .headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
284 .headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
286 .siteTitle {font-size:3em;}
287 .siteSubtitle {font-size:1.2em;}
289 #mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
291 #sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
292 #sidebarOptions {padding-top:0.3em;}
293 #sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
294 #sidebarOptions input {margin:0.4em 0.5em;}
295 #sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
296 #sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
297 #sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
298 #sidebarTabs .tabContents {width:15em; overflow:hidden;}
300 .wizard {padding:0.1em 1em 0em 2em;}
301 .wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
302 .wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
303 .wizardStep {padding:1em 1em 1em 1em;}
304 .wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
305 .wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
306 .wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
307 .wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
309 #messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
310 .messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
311 #messageArea a {text-decoration:underline;}
313 .tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
314 .popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
316 .popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
317 .popup .popupMessage {padding:0.4em;}
318 .popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
319 .popup li.disabled {padding:0.4em;}
320 .popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
321 .listBreak {font-size:1px; line-height:1px;}
322 .listBreak div {margin:2px 0;}
324 .tabset {padding:1em 0em 0em 0.5em;}
325 .tab {margin:0em 0em 0em 0.25em; padding:2px;}
326 .tabContents {padding:0.5em;}
327 .tabContents ul, .tabContents ol {margin:0; padding:0;}
328 .txtMainTab .tabContents li {list-style:none;}
329 .tabContents li.listLink { margin-left:.75em;}
331 #contentWrapper {display:block;}
332 #splashScreen {display:none;}
334 #displayArea {margin:1em 17em 0em 14em;}
336 .toolbar {text-align:right; font-size:.9em;}
338 .tiddler {padding:1em 1em 0em 1em;}
340 .missing .viewer,.missing .title {font-style:italic;}
342 .title {font-size:1.6em; font-weight:bold;}
344 .missing .subtitle {display:none;}
345 .subtitle {font-size:1.1em;}
347 .tiddler .button {padding:0.2em 0.4em;}
349 .tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
350 .isTag .tagging {display:block;}
351 .tagged {margin:0.5em; float:right;}
352 .tagging, .tagged {font-size:0.9em; padding:0.25em;}
353 .tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
354 .tagClear {clear:both;}
356 .footer {font-size:.9em;}
357 .footer li {display:inline;}
359 .annotation {padding:0.5em; margin:0.5em;}
361 * html .viewer pre {width:99%; padding:0 0 1em 0;}
362 .viewer {line-height:1.4em; padding-top:0.5em;}
363 .viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
364 .viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
365 .viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
367 .viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
368 .viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
369 table.listView {font-size:0.85em; margin:0.8em 1.0em;}
370 table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
372 .viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
373 .viewer code {font-size:1.2em; line-height:1.4em;}
375 .editor {font-size:1.1em;}
376 .editor input, .editor textarea {display:block; width:100%; font:inherit;}
377 .editorFooter {padding:0.25em 0em; font-size:.9em;}
378 .editorFooter .button {padding-top:0px; padding-bottom:0px;}
380 .fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
382 .sparkline {line-height:1em;}
383 .sparktick {outline:0;}
385 .zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
386 .zoomer div {padding:1em;}
388 * html #backstage {width:99%;}
389 * html #backstageArea {width:99%;}
390 #backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
391 #backstageToolbar {position:relative;}
392 #backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
393 #backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
394 #backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
395 #backstage {position:relative; width:100%; z-index:50;}
396 #backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
397 .backstagePanelFooter {padding-top:0.2em; float:right;}
398 .backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
399 #backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
401 .whenBackstage {display:none;}
402 .backstageVisible .whenBackstage {display:block;}
403 /*}}}*/</pre>
404 </div>
405 <div title="StyleSheetLocale">
406 <pre>/***
407 StyleSheet for use when a translation requires any css style changes.
408 This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
409 ***/
410 /*{{{*/
411 body {font-size:0.8em;}
412 #sidebarOptions {font-size:1.05em;}
413 #sidebarOptions a {font-style:normal;}
414 #sidebarOptions .sliderPanel {font-size:0.95em;}
415 .subtitle {font-size:0.8em;}
416 .viewer table.listView {font-size:0.95em;}
417 /*}}}*/</pre>
418 </div>
419 <div title="StyleSheetPrint">
420 <pre>/*{{{*/
421 @media print {
422 #mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
423 #displayArea {margin: 1em 1em 0em 1em;}
424 /* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
425 noscript {display:none;}
427 /*}}}*/</pre>
428 </div>
429 <div title="PageTemplate">
430 <pre>&lt;!--{{{--&gt;
431 &lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
432 &lt;div class='headerShadow'&gt;
433 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
434 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
435 &lt;/div&gt;
436 &lt;div class='headerForeground'&gt;
437 &lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
438 &lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
439 &lt;/div&gt;
440 &lt;/div&gt;
441 &lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
442 &lt;div id='sidebar'&gt;
443 &lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
444 &lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
445 &lt;/div&gt;
446 &lt;div id='displayArea'&gt;
447 &lt;div id='messageArea'&gt;&lt;/div&gt;
448 &lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
449 &lt;/div&gt;
450 &lt;!--}}}--&gt;</pre>
451 </div>
452 <div title="ViewTemplate">
453 <pre>&lt;!--{{{--&gt;
454 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'&gt;&lt;/div&gt;
455 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
456 &lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
457 &lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
458 &lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
459 &lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
460 &lt;div class='tagClear'&gt;&lt;/div&gt;
461 &lt;!--}}}--&gt;</pre>
462 </div>
463 <div title="EditTemplate">
464 <pre>&lt;!--{{{--&gt;
465 &lt;div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'&gt;&lt;/div&gt;
466 &lt;div class='title' macro='view title'&gt;&lt;/div&gt;
467 &lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
468 &lt;div macro='annotations'&gt;&lt;/div&gt;
469 &lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
470 &lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
471 &lt;!--}}}--&gt;</pre>
472 </div>
473 <div title="GettingStarted">
474 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
475 * SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
476 * MainMenu: The menu (usually on the left)
477 * DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
478 You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
479 </div>
480 <div title="OptionsPanel">
481 <pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser
483 Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)
485 &lt;&lt;option txtUserName&gt;&gt;
486 &lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
487 &lt;&lt;option chkAutoSave&gt;&gt; AutoSave
488 &lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
489 &lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
490 &lt;&lt;option chkAnimate&gt;&gt; EnableAnimations
492 ----
493 Also see AdvancedOptions</pre>
494 </div>
495 <div title="ImportTiddlers">
496 <pre>&lt;&lt;importTiddlers&gt;&gt;</pre>
497 </div>
498 </div>
499 <!--POST-SHADOWAREA-->
500 <div id="storeArea">
501 <div title="AdamCrossland" modifier="AdamCrossland" created="200805201949" modified="200805201951" changecount="2">
502 <pre>Adam is the author of ''Taggable.'' To find out more about him and other software that he has created, visit http://www.adamcrossland.net. You can also visit his blog, http://blog.adamcrossland.net, to see the code in action; he created ''Taggable'' as part of the blogging software project the he wrote as a vehicle to learn about [[Google AppEngine]].</pre>
503 </div>
504 <div title="Apache 2.0 Open Source license" modifier="AdamCrossland" created="200805202001" modified="200805211430" changecount="5">
505 <pre>{{{
506 Copyright 2008 Adam A. Crossland
508 Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
509 you may not use this file except in compliance with the License.
510 You may obtain a copy of the License at
512 http://www.apache.org/licenses/LICENSE-2.0
514 Unless required by applicable law or agreed to in writing, software
515 distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
516 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
517 See the License for the specific language governing permissions and
518 limitations under the License.
519 }}}</pre>
520 </div>
521 <div title="ChangeLog" modifier="AdamCrossland" created="200812041728" modified="200812042045" changecount="9">
522 <pre>''1.0''
523 Initial release
524 ----
525 ''2.0''
526 This release has major changes to both [[Tag|Tag Class]] and [[Taggable|Taggable Class]], reflecting significant and important lessons that [[the author|AdamCrossland]] learned about [[Google AppEngine]]. Specifically, this release's changes should substantially improve the performance of any code that uses tags.
527 *[[Tag|Tag Class]] entities are now stored with a custom key_name that allows them to be much-more-quickly retrieved from the data store.
528 *The [[Tag|Tag Class]] has been rewritten to breakdown Tag/Taggable interactions in to a series of simple, atomic transactions.
529 *[[Tag|Tag Class]] has a new property, [[tagged_count|Tag tagged_count Property]], that records the number of Taggable entities in its [[tagged|Tag tagged Property]] property.
530 *Two new methods, [[get_tags_by_frequency|Tag get_tags_by_frequency Method]] and [[popular_tags|Tag popular_tags Method]], provide access to lists of [[Tags|Tag Class]] based on the number of [[Taggable|Taggable Class]] entities to which they refer.
531 *A new method, [[get_tags_by_name|Tag get_tags_by_name Method]], provides lists of tags in alphabetical order.
532 *The [[Taggable|Taggable Class]] class has been greatly simplified. Multiple methods for getting and setting tags have been replaced with a single property, [[tags|Taggable tags Property]], that handles both getting and setting operations.
533 *A full suite of automated unit tests for both classes has been created.</pre>
534 </div>
535 <div title="Contact" modifier="AdamCrossland" created="200805211444" changecount="1">
536 <pre>If you have any questions or suggestions about ''Taggable'', please feel free to contact the author, AdamCrossland, at adam@adamcrossland.net. Alternatively, you could post the question in &lt;html&gt;&lt;a href=&quot;http://groups.google.com/group/google-appengine/topics&quot;&gt;the Google AppEngine Group.&lt;/a&gt;&lt;/html&gt;</pre>
537 </div>
538 <div title="Contribute" modifier="AdamCrossland" created="200805211441" changecount="1">
539 <pre>If you are using ''Taggable'' and you have some changes, improvements or bug fixes that you'd like to contribute, please contact AdamCrossland at adam@adamcrossland.net.</pre>
540 </div>
541 <div title="Converting from 1.0 to 2.0" modifier="AdamCrossland" created="200812051535" modified="200812051536" changecount="2">
542 <pre>Because the Tag records are stored and queried slightly differently (yet much-more efficiently) in //Taggable-mixin 2.0//, an existing application will have to go through a process of converting all of its existing tags.
544 For AdamCBlog, the application from which //Taggable-mixin// is extracted, I added a new RequestHandler called UpdateTag:
546 class UpdateTag(RequestHandler):
547 def get(self):
548 from taggable import Tag
549 tag_name = self.request.get('tag')
550 tag = db.Query(Tag).filter('tag =', tag_name).fetch(1)[0]
551 if tag is not None:
552 new_tag = Tag.get_or_create(tag_name)
553 for each_tagged in tag.tagged:
554 new_tag.add_tagged(each_tagged)
555 tag.delete()
557 self.redirect('/')
560 Then, I made the request /~UpdateTag?tag=//inserttaghere// for each Tag in the datastore. Yes, it is laborious.
562 Why not just create a RequestHandler that cycled through all of the tags automatically, converting them as it went? I tried that, but there is too much processing involved; the request uses up its time quota and is killed before it can finish.</pre>
563 </div>
564 <div title="DateTimeProperty" modifier="AdamCrossland" created="200812042027" tags="appengine" changecount="1">
565 <pre>A [[Google AppEngine]] datastore property that holds a Python //datetime//.
567 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#DateTimeProperty</pre>
568 </div>
569 <div title="DefaultTiddlers" modifier="AdamCrossland" created="200805202104" changecount="1">
570 <pre>[[Introduction]]</pre>
571 </div>
572 <div title="Google AppEngine" modifier="AdamCrossland" created="200805201811" modified="200805202010" changecount="3">
573 <pre>Google ~AppEngine is Google's platform for developing web applications that run inside Google's computing cloud.
575 Find out more about it at &lt;html&gt;&lt;a href=&quot;http://code.google.com/appengine/&quot;&gt;The Google AppEngine website&lt;/a&gt;&lt;/html&gt;.
577 </pre>
578 </div>
579 <div title="IntegerProperty" modifier="AdamCrossland" created="200812041949" tags="appengine" changecount="1">
580 <pre>A [[Google AppEngine]] datastore property that holds a Python //long//.
582 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#IntegerProperty</pre>
583 </div>
584 <div title="Introduction" modifier="AdamCrossland" created="200805201810" modified="200812051536" changecount="8">
585 <pre>''Taggable'' is a &lt;html&gt;&lt;a href=&quot;http://www.linuxjournal.com/node/4540/print&quot;&gt;mixin&lt;/a&gt;&lt;/html&gt; class that AdamCrossland created in order to add [[tags|Tag Definition]] to his personal blog (http://blog.adamcrossland.net) which is built on [[Google AppEngine]]. The design seemed clean and portable, so he decided to share it with the greater [[AppEngine|Google AppEngine]] community. It is available under [[Apache 2.0 Open Source license]].
587 Users of taggable-mixin 1.0 should note that taggable-mixin 2.0 is not directly compatible with 1.0. While it is possible to upgrade, existing Tag entities will have to be [[converted|Converting from 1.0 to 2.0]].</pre>
588 </div>
589 <div title="Key Class" modifier="AdamCrossland" created="200812042055" tags="appengine" changecount="1">
590 <pre>A [[Google AppEngine]] class that represents a unique key for a datastore entity.
592 http://code.google.com/appengine/docs/datastore/keyclass.html</pre>
593 </div>
594 <div title="ListProperty" modifier="AdamCrossland" created="200812042030" tags="appengine" changecount="1">
595 <pre>A [[Google AppEngine]] datastore property that represents a Python //list//.
597 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#ListProperty</pre>
598 </div>
599 <div title="MainMenu" modifier="AdamCrossland" created="200805201814" modified="200812041913" changecount="10">
600 <pre>[[Introduction|Introduction]]
601 ChangeLog
602 StepByStep
603 &lt;&lt;tag api&gt;&gt;
604 ----
605 [[License|Apache 2.0 Open Source license]]
606 [[Contribute]]
607 [[Contact]]</pre>
608 </div>
609 <div title="RequestHandler" modifier="AdamCrossland" created="200805211437" modified="200805211438" changecount="4">
610 <pre>A //~RequestHandler// is a Python class that inherits from //webapp.~RequestHandler//. It is used to answer HTTP requests that are received by your [[Google AppEngine]] application. For more information on this subject, please consult the &lt;html&gt;&lt;a href=&quot;http://code.google.com/appengine/docs/webapp/requesthandlers.html&quot;&gt;documentation.&lt;/a&gt;&lt;/html&gt;
612 </pre>
613 </div>
614 <div title="SideBarOptions" modifier="AdamCrossland" created="200805211439" changecount="1">
615 <pre>&lt;&lt;search&gt;&gt;&lt;&lt;closeAll&gt;&gt;&lt;&lt;permaview&gt;&gt;&lt;&lt;newTiddler&gt;&gt;&lt;&lt;saveChanges&gt;&gt;&lt;&lt;slider chkSliderOptionsPanel OptionsPanel &quot;options »&quot; &quot;Change TiddlyWiki advanced options&quot;&gt;&gt;</pre>
616 </div>
617 <div title="SiteSubtitle" modifier="AdamCrossland" created="200805201806" modified="200805201807" changecount="2">
618 <pre>a portable mixin class for adding Tags to Google ~AppEngine Models</pre>
619 </div>
620 <div title="SiteTitle" modifier="AdamCrossland" created="200805201805" changecount="1">
621 <pre>Taggable</pre>
622 </div>
623 <div title="StepByStep" modifier="AdamCrossland" created="200805201831" modified="200812041926" tags="help examples code" changecount="14">
624 <pre>Here's a step-by-step guide to adding ''Taggable'' to your [[Google AppEngine]] application. This presents the simplest, most straightforward path to integrating ''Taggable'' as as such, it does not cover all of the options that are available.
626 All of the code examples are modified extracts from the blogging software for which I originally created ''Taggable.'' //''These examples do not represent what the author considers to be complete and secure code; please make sure that you are familiar with best practices for building secure web applications before creating a web application. The author of this document and the accompanying code bears no responsibility whatsoever for any losses or damages that you incur as a result of failing to take the appropriate steps to create a stable, secure, well-written web application.''//
628 *Copy [[taggable.py]] to your application directory.
629 *Import [[taggable.py]] into the python file that defines the Model that you want to make taggable:
631 from taggable import Taggable
633 *Add //Taggable// to the list of classes from which your Model class inherits. Taggable -- and any other mixin classes -- should come before db.Model:
635 class Post(Taggable, db.Model):
637 *Add code to your Model's //init// method to call Taggable's //init// method. If your Model class does not already override //init//, it will have to, and you will have to explicitly call the //init// method of any other superclass -- such as db.Model:
639 def __init__(self, parent=None, key_name=None, app=None, **entity_values):
640 db.Model.__init__(self, parent, key_name, app, **entity_values)
641 Taggable.__init__(self)
643 *Add code to your template to express the tagging information:
645 {% if post.tags %}
646 &lt;div class=&quot;posttags&quot;&gt;tags:&amp;nbsp;
647 {% for each_tag in post.tags %}
648 &lt;a href=&quot;/searchbytag?tag={{ each_tag.tag|escape }}&quot;&gt;{{ each_tag.tag }}&lt;/a&gt;{% if forloop.last %}{% else %}, {% endif %}
649 {% endfor %}
650 &lt;/div&gt;
651 {% endif %}
655 &lt;tr&gt;
656 &lt;td&gt;Tags:&lt;/td&gt;
657 &lt;td&gt;
658 &lt;input type=&quot;TEXT&quot; name=&quot;tags&quot; size=&quot;106&quot; value=&quot;{% if post %}{{ post.tags_string }}{% endif %}&quot; /&gt;
659 &lt;/td&gt;
660 &lt;/tr&gt;
662 *In any RequestHandler method that updates a Model object that has tags associated with it, assign any new value to the ''Taggable'' object's [[tags|TaggableTagsProperty]]:
664 class EditPost(SmartHandler.SmartHandler):
665 def post(self):
666 postid = self.request.get('id')
667 post = Post.get(postid)
668 post.title = self.request.get('title')
669 post.body = self.request.get('body')
670 post.edited = datetime.datetime.now()
671 post.tags = self.request.get('tags')
672 post.put()
674 . do whatever else you need to do in your handler...
676 }}}</pre>
677 </div>
678 <div title="StringProperty" modifier="AdamCrossland" created="200812042024" tags="appengine" changecount="1">
679 <pre>A [[Google AppEngine]] datastore property that holds a Python //string// of 500 characters or less.
681 http://code.google.com/appengine/docs/datastore/typesandpropertyclasses.html#StringProperty</pre>
682 </div>
683 <div title="Tag Class" modifier="AdamCrossland" created="200812041942" modified="200812050051" tags="api" changecount="6">
684 <pre>The //Tag// class is the Model class that holds the data for an individual [[tag|Tag Definition]].
686 It has four properties:
687 *[[tag|Tag tag Property]]
688 *[[added|Tag tag_added Property]]
689 *[[tagged|Tag tagged Property]]
690 *[[tagged_count|Tag tagged_count Property]]
692 and ten methods:
693 *[[remove_tagged|Tag remove_tagged Method]]
694 *[[add_tagged|Tag add_tagged Method]]
695 *[[clear_tagged|Tag clear_tagged Method]]
696 *[[get_by_name|Tag get_by_name Method]]
697 *[[get_tags_for_key|Tag get_tags_for_key Method]]
698 *[[get_or_create|Tag get_or_create Method]]
699 *[[get_tags_by_frequency|Tag get_tags_by_frequency Method]]
700 *[[get_tags_by_name|Tag get_tags_by_name Method]]
701 *[[popular_tags|Tag popular_tags Method]]
702 *[[expire_cached_tags|Tag expire_cached_tags Method]]</pre>
703 </div>
704 <div title="Tag Definition" modifier="AdamCrossland" created="200812050049" changecount="1">
705 <pre>A //Tag// is a word or short phrase that acts as metadata; it describes and increases the searchability and findability of data.
707 http://en.wikipedia.org/wiki/Tag_(metadata)</pre>
708 </div>
709 <div title="Tag add_tagged Method" modifier="AdamCrossland" created="200812042059" modified="200812042332" tags="api" changecount="3">
710 <pre>//tag_instance.add_tagged(key)//
711 * key - a [[Key|Key Class]] for a //Taggable// Model to mark with the [[Tag|Tag Class]]
712 Returns: nothing
714 The //add_tagged// method adds the [[Key|Key Class]] passed in to the Tag's [[tagged|Tag tagged Property]] collection and increments the [[tagged_count|Tag tagged_count Property]]. The operations are performed inside a transaction to ensure data intergity.
716 Under most circumstances, the programmer will not need to call //add_tagged// directly; it is meant to be used internally by the [[Tagged Class]]. However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
717 </div>
718 <div title="Tag clear_tagged Method" modifier="AdamCrossland" created="200812042103" modified="200812042332" tags="api" changecount="2">
719 <pre>//tag_instance.clear_tagged(key)//
720 * key - a [[Key|Key Class]] for a //Taggable// object.
721 Returns: nothing
723 The //clear_tagged// method empties the Tag's [[tagged|Tag tagged Property]] collection and sets the [[tagged_count|Tag tagged_count Property]] to zero. The operations are performed inside a transaction to ensure data intergity.
725 Under most circumstances, the programmer will not need to call //clear_tagged// directly; it is meant to be used internally by the [[Tagged Class]]. However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
726 </div>
727 <div title="Tag expire_cached_tags Method" modifier="AdamCrossland" created="200812050015" tags="api" changecount="1">
728 <pre>//Tag.expire_cached_tags()//
729 Returns: nothing
731 Comments:
732 This method removes from memcache any objects that have been cached by other [[Tag|Tag Class]] methods. Usually, the programmer will not call this method directly, as it is used behind-the-scenes by code that affects the validity of the cached items. It is available, however, in case the programmer wishes to customize existing behavior.</pre>
733 </div>
734 <div title="Tag get_by_name Method" modifier="AdamCrossland" created="200812042151" modified="200812042227" tags="api example" changecount="5">
735 <pre>//Tag.get_by_name(tag_name)//
736 *tag_name - the tag, in text form
737 Returns: a [[Tag|Tag Class]] or //None//
739 Example:
741 class SearchByTag(RequestHandler):
742 def get(self):
743 from post import Post
744 from taggable import Tag
746 requested_tag = self.request.get('tag')
747 if requested_tag is not None and len(requested_tag) &gt; 0:
748 tag = Tag.get_by_name(requested_tag)
749 posts = None
750 if tag is not None:
751 posts = Post.get(tag.tagged)
753 self.template_values['posts'] = posts
755 </pre>
756 </div>
757 <div title="Tag get_or_create Method" modifier="AdamCrossland" created="200812042332" modified="200812042334" tags="api" changecount="2">
758 <pre>//Tag.get_or_create(tag_name)//
759 *tag_name - the name of the tag that is to be retrieved from or created in the datastore
760 Returns: a [[Tag|Tag Class]]
762 Example:
764 for each_tag in tags:
765 each_tag = string.strip(each_tag)
766 if len(each_tag) &gt; 0 and each_tag not in self.__tags:
767 # A tag that was not previously assigned to this entity
768 # is present in the list that is being assigned, so we
769 # associate this entity with the tag.
770 tag = Tag.get_or_create(each_tag)
771 tag.add_tagged(self.key())
772 self.__tags.append(tag)
773 }}}</pre>
774 </div>
775 <div title="Tag get_tags_by_frequency Method" modifier="AdamCrossland" created="200812042356" tags="api" changecount="1">
776 <pre>//Tag.get_tags_by_frequency(limit=1000)//
777 *limit - the number of records to return; the maximum is 1000
778 Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of //Taggable// objects assigned to it.
780 Example:
782 @classmethod
783 def popular_tags(cls, limit=5):
784 from google.appengine.api import memcache
786 tags = memcache.get('popular_tags')
787 if tags is None:
788 tags = Tag.get_tags_by_frequency(limit)
789 memcache.add('popular_tags', tags, 3600)
791 return tags
792 }}}</pre>
793 </div>
794 <div title="Tag get_tags_by_name Method" modifier="AdamCrossland" created="200812050018" tags="api" changecount="1">
795 <pre>//Tag.get_tags_by_name(tag_name)//
796 *tag_name - the string value of a [[Tag|Tag Class]] to be retrieved from the datastore
797 Returns: a [[Tag|Tag Class]] object or None if the given //tag_name// does not exist
799 Example:
801 requested_tag = self.request.get('tag')
802 if requested_tag is not None and len(requested_tag) &gt; 0:
803 tag = Tag.get_by_name(requested_tag)
804 posts = None
805 if tag is not None:
806 posts = Post.get(tag.tagged)
808 self.template_values['posts'] = posts
809 }}}</pre>
810 </div>
811 <div title="Tag get_tags_for_key Method" modifier="AdamCrossland" created="200812042318" modified="200812042355" tags="api example" changecount="2">
812 <pre>//Tag.get_tags_for_key(key)//
813 *key - a [[Key|Key Class]] for a //Taggable// object.
814 Returns: a //list// of [[Tag|Tag Class]] objects
816 Example:
818 def __get_tags(self):
819 &quot;Get a List of Tag objects for all Tags that apply to this object.&quot;
820 if self.__tags is None or len(self.__tags) == 0:
821 self.__tags = Tag.get_tags_for_key(self.key())
822 return self.__tags
823 }}}</pre>
824 </div>
825 <div title="Tag popular_tags Method" modifier="AdamCrossland" created="200812050000" tags="api" changecount="1">
826 <pre>//Tag.popular_tags(limit=5)//
827 *limit - the number of records to return; the default is 5 and the maximum is 1000
828 Returns: a list of [[Tag|Tag Class]] objects, ordered by the number of [[Taggable|Taggable Class]] objects assigned to it.
830 Comments:
831 This method is a thin wrapper around [[get_tags_by_frequency|Tag get_tags_by_frequency]] that caches the returned values.
833 Example:
835 self.template_values['popular_tags'] = Tag.popular_tags()
836 }}}</pre>
837 </div>
838 <div title="Tag remove_tagged Method" modifier="AdamCrossland" created="200812042054" modified="200812042332" tags="api" changecount="5">
839 <pre>//tag_instance.remove_tagged(key)//
840 * key - a [[Key|Key Class]] for a //Taggable// object.
841 Returns: nothing
843 The //remove_tagged// method removes the [[Key|Key Class]] passed in from the Tag's [[tagged|Tag tagged Property]] collection and decrements the [[tagged_count|Tag tagged_count Property]]. The operations are performed inside a transaction to ensure data intergity.
845 Under most circumstances, the programmer will not need to call //remove_tagged// directly; it is meant to be used internally by the [[Tagged Class]]. However, it is available for use by those who wish to customize the behavior of Taggable.</pre>
846 </div>
847 <div title="Tag tag Property" modifier="AdamCrossland" created="200812042022" tags="api" changecount="1">
848 <pre>A StringProperty that holds the name or value of the tag.</pre>
849 </div>
850 <div title="Tag tag_added Property" modifier="AdamCrossland" created="200812042025" modified="200812042026" tags="api" changecount="2">
851 <pre>The DateTimeProperty that records that date and time that the [[Tag|TagClass]] was first added.</pre>
852 </div>
853 <div title="Tag tagged Property" modifier="AdamCrossland" created="200812042028" tags="api" changecount="1">
854 <pre>A ListProperty that holds the Keys of all of the entities that have the given [[Tag|TagClass]] assigned to them.</pre>
855 </div>
856 <div title="Tag tagged_count Property" modifier="AdamCrossland" created="200812041947" modified="200812042018" tags="api" changecount="3">
857 <pre>An IntegerProperty that holds the number of items that have the Tag assigned to them.</pre>
858 </div>
859 <div title="Taggable Class" modifier="AdamCrossland" created="200812050054" modified="200812051431" tags="api" changecount="8">
860 <pre>The //Taggable// class can be mixed-in to any other Python class that inherits from //db.Model// in order to associate [[Tags|Tag Definition]] with that model. For instance, if you were creating blogging software, one of your fundamental objects would be a //Post// that would comprise all of the information about an individual entry in your blog. Usually, a Post can have [[Tags|Tag Definition]] added to it in order to provide easily-digested information to the reader about the content.
862 It has two properties:
863 *[[tags|Taggable tags Property]]
864 *[[tag_separator|Taggable tag_separator Property]]
866 and one method:
867 *[[tags_string|Taggable tags_string Method]]
869 Example:
871 class Post(Taggable, db.Model):
872 index = db.IntegerProperty(required=True, default=0)
873 body = db.TextProperty(required = True)
874 title = db.StringProperty()
875 added = db.DateTimeProperty(auto_now_add=True)
876 added_month = db.IntegerProperty()
877 added_year = db.IntegerProperty()
878 edited = db.DateTimeProperty()
880 def __init__(self, parent=None, key_name=None, app=None, **entity_values):
881 db.Model.__init__(self, parent, key_name, app, **entity_values)
882 Taggable.__init__(self)
884 Notes:
885 *Notice that [[Taggable|Taggable Class]] is placed before db.Model in the inheritance list. While this is not strictly required for //Taggable-mixin// to function correctly, it //is// required for other mixin classes, and it is generally a good practice.
886 *It //is// required that the [[Taggable|Taggable Class]] //init// method is explicitly called, so it should be placed in the inherting class's //init// method. If the class would not otherwise have one, it should be created, and the db.Model //init// must be called as well, as shown.</pre>
887 </div>
888 <div title="Taggable tag_separator Property" modifier="AdamCrossland" created="200805201936" modified="200812042033" tags="api" changecount="6">
889 <pre>The //tag_separator// property is the string that is used to separate individual tags in a string representation of a list of tags. It is used by the [[tags property|TaggableTagsProperty]] to parse a string that may contain one or more tags to be applied to a [[Taggable|TaggableClass]] object. It is also used by the [[tags_string|TaggableTagsStringMethod]] method to construct a string representation of the tags for a [[Taggable|TaggableClass]] object.
891 *By default, it is set to a comma (','), but it can be programatically-set to whatever value the developer desires
892 *It is probably best to avoid using whitespace characters, as that would prevent users from entering multi-word tags
893 *Custom separator values can be set at different levels.
894 **To set one value for all [[Taggable|TaggableClass]] objects, modify the //init// method in the //Taggable// class in the taggable.py file:
896 class Taggable:
897 def __init__(self):
898 self.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma
900 **To set one value for all instances of a particular [[Taggable|TaggableClass]] class, modify the //init// method of that class:
902 class Post(Taggable, db.Model):
903 body = db.TextProperty(required = True)
904 title = db.StringProperty()
905 added = db.DateTimeProperty(auto_now_add=True)
906 edited = db.DateTimeProperty()
908 def __init__(self, parent=None, key_name=None, app=None, **entity_values):
909 db.Model.__init__(self, parent, key_name, app, **entity_values)
910 Taggable.__init__(self)
911 self.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma
913 **To set a value for one particular instance of a [[Taggable|TaggableClass]] class, set the value after creating the instance:
915 newpost = Post(title = self.request.get('title'), body = self.request.get('body'))
916 newpost.tag_separator = &quot;;&quot; # Made it semi-colon instead of comma
917 newpost.set_tags_from_string(&quot;foo; bar; bletch; this is a tag; tags rule&quot;
918 }}}</pre>
919 </div>
920 <div title="Taggable tags Property" modifier="AdamCrossland" created="200812051447" modified="200812051516" tags="api" changecount="2">
921 <pre>The a [[Taggable|Taggable Class]] class's //tags// property is used to assign [[Tags|Tag Class]] to the [[Taggable|Taggable Class]] instance or retrieve a //list// of those already assigned.
923 Assignment Example:
925 class Post(Taggable, db.Model):
926 index = db.IntegerProperty(required=True, default=0)
927 body = db.TextProperty(required = True)
928 title = db.StringProperty()
929 added = db.DateTimeProperty(auto_now_add=True)
930 added_month = db.IntegerProperty()
931 added_year = db.IntegerProperty()
932 edited = db.DateTimeProperty()
934 def __init__(self, parent=None, key_name=None, app=None, **entity_values):
935 db.Model.__init__(self, parent, key_name, app, **entity_values)
936 Taggable.__init__(self)
938 @classmethod
939 def new_post(cls, new_title=None, new_body=None, new_tags=[]):
940 if new_title is not None and new_body is not None:
942 new_post = Post(title = new_title, body = new_body)
943 new_post.put()
945 new_post.tags = new_tags
946 new_post.save()
947 else:
948 raise Exception(&quot;Must supply both new_title and new_body when creating a new Post.&quot;)
951 Retrieval Example:
953 {% if post.tags %}
954 &lt;div class=&quot;posttags&quot;&gt;tags:&amp;nbsp;
955 {% for each_tag in post.tags %}
956 &lt;a href=&quot;/searchbytag?tag={{ each_tag.tag|escape }}&quot;&gt;{{ each_tag.tag }}&lt;/a&gt;{% if forloop.last %}{% else %}, {% endif %}
957 {% endfor %}
958 &lt;/div&gt;
959 {% endif %}
960 }}}</pre>
961 </div>
962 <div title="Taggable tags_string Method" modifier="AdamCrossland" created="200812051441" modified="200812051441" tags="api" changecount="3">
963 <pre>//taggable_instance.tags_string()//
964 Returns: a string representation of the tags assigned to the [[Taggable|Taggable Class]] instance.
966 This method simply joins the string versions of each [[Tag|Tag Class]] in the [[Taggable|Taggable Class]] class's //list//, placing the value stored in [[tag_separator|Taggable tag_separator Property]] inbetween each element.</pre>
967 </div>
968 <div title="TaggableAPI" modifier="AdamCrossland" created="200805201953" modified="200805201956" changecount="6">
969 <pre>&lt;&lt;tagging api&gt;&gt;</pre>
970 </div>
971 <div title="get_tags_as_string" modifier="AdamCrossland" created="200805201952" modified="200812041913" tags="obsolete" changecount="2">
972 <pre>The //get_tags_as_string// method creates a string representation of all of the tags that apply to a ''Taggable'' object.</pre>
973 </div>
974 <div title="set_tags" modifier="AdamCrossland" created="200805201919" modified="200812041848" tags="obsolete" changecount="2">
975 <pre>The //set_tags// method sets the tags for a ''Taggable'' object from a list of strings:
977 tag_list = [&quot;foo&quot;, &quot;bar&quot;, &quot;bletch&quot;,&quot;this is a tag&quot;, &quot;tags rule&quot;]
978 my_taggable_object.set_tags(tag_list)
979 }}}</pre>
980 </div>
981 <div title="set_tags_from_string" modifier="AdamCrossland" created="200805201926" modified="200812041848" tags="obsolete" changecount="3">
982 <pre>The //set_tags_from_string method// sets the tags for a Taggable object from a string that contains one or more tags separated by the character or characters in //self.tag_seperator//:
984 tag_list = &quot;foo, bar, bletch, this is a tag, tags rule&quot;
985 my_taggable_object.set_tags_from_string(tag_list)
988 By default, [[tag_separator|tag_separator]] is set to a comma (','), but it can be anything that the programmer wants.</pre>
989 </div>
990 <div title="taggable.py" modifier="AdamCrossland" created="200805201904" modified="200805201946" changecount="2">
991 <pre>All of the code for the ''Taggable'' mixin class lives in the file taggable.py. Simply copy this file in to your Google ~AppEngine main directory, and it should be available to your code.</pre>
992 </div>
993 </div>
994 <!--POST-STOREAREA-->
995 <!--POST-BODY-START-->
996 <!--POST-BODY-END-->
997 <script id="jsArea" type="text/javascript">
998 //<![CDATA[
1000 // Please note:
1002 // * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
1003 // in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
1005 // * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
1006 // without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
1009 //--
1010 //-- Configuration repository
1011 //--
1013 // Miscellaneous options
1014 var config = {
1015 numRssItems: 20, // Number of items in the RSS feed
1016 animDuration: 400, // Duration of UI animations in milliseconds
1017 cascadeFast: 20, // Speed for cascade animations (higher == slower)
1018 cascadeSlow: 60, // Speed for EasterEgg cascade animations
1019 cascadeDepth: 5, // Depth of cascade animation
1020 locale: "en" // W3C language tag
1023 // Hashmap of alternative parsers for the wikifier
1024 config.parsers = {};
1026 // Adaptors
1027 config.adaptors = {};
1028 config.defaultAdaptor = null;
1030 // Backstage tasks
1031 config.tasks = {};
1033 // Annotations
1034 config.annotations = {};
1036 // Custom fields to be automatically added to new tiddlers
1037 config.defaultCustomFields = {};
1039 // Messages
1040 config.messages = {
1041 messageClose: {},
1042 dates: {},
1043 tiddlerPopup: {}
1046 // Options that can be set in the options panel and/or cookies
1047 config.options = {
1048 chkRegExpSearch: false,
1049 chkCaseSensitiveSearch: false,
1050 chkIncrementalSearch: true,
1051 chkAnimate: true,
1052 chkSaveBackups: true,
1053 chkAutoSave: false,
1054 chkGenerateAnRssFeed: false,
1055 chkSaveEmptyTemplate: false,
1056 chkOpenInNewWindow: true,
1057 chkToggleLinks: false,
1058 chkHttpReadOnly: true,
1059 chkForceMinorUpdate: false,
1060 chkConfirmDelete: true,
1061 chkInsertTabs: false,
1062 chkUsePreForStorage: true, // Whether to use <pre> format for storage
1063 chkDisplayInstrumentation: false,
1064 txtBackupFolder: "",
1065 txtEditorFocus: "text",
1066 txtMainTab: "tabTimeline",
1067 txtMoreTab: "moreTabAll",
1068 txtMaxEditRows: "30",
1069 txtFileSystemCharSet: "UTF-8",
1070 txtTheme: ""
1072 config.optionsDesc = {};
1074 // List of notification functions to be called when certain tiddlers are changed or deleted
1075 config.notifyTiddlers = [
1076 {name: "StyleSheetLayout", notify: refreshStyles},
1077 {name: "StyleSheetColors", notify: refreshStyles},
1078 {name: "StyleSheet", notify: refreshStyles},
1079 {name: "StyleSheetPrint", notify: refreshStyles},
1080 {name: "PageTemplate", notify: refreshPageTemplate},
1081 {name: "SiteTitle", notify: refreshPageTitle},
1082 {name: "SiteSubtitle", notify: refreshPageTitle},
1083 {name: "ColorPalette", notify: refreshColorPalette},
1084 {name: null, notify: refreshDisplay}
1087 // Default tiddler templates
1088 var DEFAULT_VIEW_TEMPLATE = 1;
1089 var DEFAULT_EDIT_TEMPLATE = 2;
1090 config.tiddlerTemplates = {
1091 1: "ViewTemplate",
1092 2: "EditTemplate"
1095 // More messages (rather a legacy layout that shouldn't really be like this)
1096 config.views = {
1097 wikified: {
1098 tag: {}
1100 editor: {
1101 tagChooser: {}
1105 // Backstage tasks
1106 config.backstageTasks = ["save","sync","importTask","tweak","upgrade","plugins"];
1108 // Macros; each has a 'handler' member that is inserted later
1109 config.macros = {
1110 today: {},
1111 version: {},
1112 search: {sizeTextbox: 15},
1113 tiddler: {},
1114 tag: {},
1115 tags: {},
1116 tagging: {},
1117 timeline: {},
1118 allTags: {},
1119 list: {
1120 all: {},
1121 missing: {},
1122 orphans: {},
1123 shadowed: {},
1124 touched: {},
1125 filter: {}
1127 closeAll: {},
1128 permaview: {},
1129 saveChanges: {},
1130 slider: {},
1131 option: {},
1132 options: {},
1133 newTiddler: {},
1134 newJournal: {},
1135 tabs: {},
1136 gradient: {},
1137 message: {},
1138 view: {defaultView: "text"},
1139 edit: {},
1140 tagChooser: {},
1141 toolbar: {},
1142 plugins: {},
1143 refreshDisplay: {},
1144 importTiddlers: {},
1145 upgrade: {
1146 source: "http://www.tiddlywiki.com/upgrade/",
1147 backupExtension: "pre.core.upgrade"
1149 sync: {},
1150 annotations: {}
1153 // Commands supported by the toolbar macro
1154 config.commands = {
1155 closeTiddler: {},
1156 closeOthers: {},
1157 editTiddler: {},
1158 saveTiddler: {hideReadOnly: true},
1159 cancelTiddler: {},
1160 deleteTiddler: {hideReadOnly: true},
1161 permalink: {},
1162 references: {type: "popup"},
1163 jump: {type: "popup"},
1164 syncing: {type: "popup"},
1165 fields: {type: "popup"}
1168 // Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
1169 config.userAgent = navigator.userAgent.toLowerCase();
1170 config.browser = {
1171 isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
1172 isGecko: config.userAgent.indexOf("gecko") != -1,
1173 ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
1174 isSafari: config.userAgent.indexOf("applewebkit") != -1,
1175 isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
1176 firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
1177 isOpera: config.userAgent.indexOf("opera") != -1,
1178 isLinux: config.userAgent.indexOf("linux") != -1,
1179 isUnix: config.userAgent.indexOf("x11") != -1,
1180 isMac: config.userAgent.indexOf("mac") != -1,
1181 isWindows: config.userAgent.indexOf("win") != -1
1184 // Basic regular expressions
1185 config.textPrimitives = {
1186 upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
1187 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
1188 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
1189 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
1191 if(config.browser.isBadSafari) {
1192 config.textPrimitives = {
1193 upperLetter: "[A-Z\u00c0-\u00de]",
1194 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
1195 anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
1196 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
1199 config.textPrimitives.sliceSeparator = "::";
1200 config.textPrimitives.sectionSeparator = "##";
1201 config.textPrimitives.urlPattern = "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)";
1202 config.textPrimitives.unWikiLink = "~";
1203 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
1204 config.textPrimitives.lowerLetter + "+" +
1205 config.textPrimitives.upperLetter +
1206 config.textPrimitives.anyLetter + "*)|(?:" +
1207 config.textPrimitives.upperLetter + "{2,}" +
1208 config.textPrimitives.lowerLetter + "+))";
1210 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
1211 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
1213 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
1214 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
1215 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
1216 config.textPrimitives.brackettedLink + ")|(?:" +
1217 config.textPrimitives.urlPattern + ")","mg");
1218 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
1219 config.textPrimitives.titledBrackettedLink + ")|(?:" +
1220 config.textPrimitives.brackettedLink + ")|(?:" +
1221 config.textPrimitives.urlPattern + ")","mg");
1223 config.glyphs = {
1224 browsers: [
1225 function() {return config.browser.isIE;},
1226 function() {return true;}
1228 currBrowser: null,
1229 codes: {
1230 downTriangle: ["\u25BC","\u25BE"],
1231 downArrow: ["\u2193","\u2193"],
1232 bentArrowLeft: ["\u2190","\u21A9"],
1233 bentArrowRight: ["\u2192","\u21AA"]
1237 //--
1238 //-- Shadow tiddlers
1239 //--
1241 config.shadowTiddlers = {
1242 StyleSheet: "",
1243 MarkupPreHead: "",
1244 MarkupPostHead: "",
1245 MarkupPreBody: "",
1246 MarkupPostBody: "",
1247 TabTimeline: '<<timeline>>',
1248 TabAll: '<<list all>>',
1249 TabTags: '<<allTags excludeLists>>',
1250 TabMoreMissing: '<<list missing>>',
1251 TabMoreOrphans: '<<list orphans>>',
1252 TabMoreShadowed: '<<list shadowed>>',
1253 AdvancedOptions: '<<options>>',
1254 PluginManager: '<<plugins>>'
1257 //--
1258 //-- Translateable strings
1259 //--
1261 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
1263 merge(config.options,{
1264 txtUserName: "YourName"});
1266 merge(config.tasks,{
1267 save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
1268 sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
1269 importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
1270 tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
1271 upgrade: {text: "upgrade", tooltip: "Upgrade TiddlyWiki core code", content: '<<upgrade>>'},
1272 plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
1275 // Options that can be set in the options panel and/or cookies
1276 merge(config.optionsDesc,{
1277 txtUserName: "Username for signing your edits",
1278 chkRegExpSearch: "Enable regular expressions for searches",
1279 chkCaseSensitiveSearch: "Case-sensitive searching",
1280 chkIncrementalSearch: "Incremental key-by-key searching",
1281 chkAnimate: "Enable animations",
1282 chkSaveBackups: "Keep backup file when saving changes",
1283 chkAutoSave: "Automatically save changes",
1284 chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
1285 chkSaveEmptyTemplate: "Generate an empty template when saving changes",
1286 chkOpenInNewWindow: "Open external links in a new window",
1287 chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
1288 chkHttpReadOnly: "Hide editing features when viewed over HTTP",
1289 chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
1290 chkConfirmDelete: "Require confirmation before deleting tiddlers",
1291 chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
1292 txtBackupFolder: "Name of folder to use for backups",
1293 txtMaxEditRows: "Maximum number of rows in edit boxes",
1294 txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
1296 merge(config.messages,{
1297 customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
1298 pluginError: "Error: %0",
1299 pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
1300 pluginForced: "Executed because forced via 'systemConfigForce' tag",
1301 pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
1302 nothingSelected: "Nothing is selected. You must select one or more items first",
1303 savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
1304 subtitleUnknown: "(unknown)",
1305 undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
1306 shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
1307 tiddlerLinkTooltip: "%0 - %1, %2",
1308 externalLinkTooltip: "External link to %0",
1309 noTags: "There are no tagged tiddlers",
1310 notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
1311 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",
1312 invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
1313 backupSaved: "Backup saved",
1314 backupFailed: "Failed to save backup file",
1315 rssSaved: "RSS feed saved",
1316 rssFailed: "Failed to save RSS feed file",
1317 emptySaved: "Empty template saved",
1318 emptyFailed: "Failed to save empty template file",
1319 mainSaved: "Main TiddlyWiki file saved",
1320 mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
1321 macroError: "Error in macro <<\%0>>",
1322 macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
1323 missingMacro: "No such macro",
1324 overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
1325 unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
1326 confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
1327 saveInstructions: "SaveChanges",
1328 unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
1329 tiddlerSaveError: "Error when saving tiddler '%0'",
1330 tiddlerLoadError: "Error when loading tiddler '%0'",
1331 wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
1332 invalidFieldName: "Invalid field name %0",
1333 fieldCannotBeChanged: "Field '%0' cannot be changed",
1334 loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'",
1335 upgradeDone: "The upgrade to version %0 is now complete\n\nClick 'OK' to reload the newly upgraded TiddlyWiki"});
1337 merge(config.messages.messageClose,{
1338 text: "close",
1339 tooltip: "close this message area"});
1341 config.messages.backstage = {
1342 open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
1343 close: {text: "close", tooltip: "Close the backstage area"},
1344 prompt: "backstage: ",
1345 decal: {
1346 edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
1350 config.messages.listView = {
1351 tiddlerTooltip: "Click for the full text of this tiddler",
1352 previewUnavailable: "(preview not available)"
1355 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
1356 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1357 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1358 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1359 // suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
1360 config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
1361 "th","th","th","th","th","th","th","th","th","th",
1362 "st","nd","rd","th","th","th","th","th","th","th",
1363 "st"];
1364 config.messages.dates.am = "am";
1365 config.messages.dates.pm = "pm";
1367 merge(config.messages.tiddlerPopup,{
1370 merge(config.views.wikified.tag,{
1371 labelNoTags: "no tags",
1372 labelTags: "tags: ",
1373 openTag: "Open tag '%0'",
1374 tooltip: "Show tiddlers tagged with '%0'",
1375 openAllText: "Open all",
1376 openAllTooltip: "Open all of these tiddlers",
1377 popupNone: "No other tiddlers tagged with '%0'"});
1379 merge(config.views.wikified,{
1380 defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
1381 defaultModifier: "(missing)",
1382 shadowModifier: "(built-in shadow tiddler)",
1383 dateFormat: "DD MMM YYYY",
1384 createdPrompt: "created"});
1386 merge(config.views.editor,{
1387 tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
1388 defaultText: "Type the text for '%0'"});
1390 merge(config.views.editor.tagChooser,{
1391 text: "tags",
1392 tooltip: "Choose existing tags to add to this tiddler",
1393 popupNone: "There are no tags defined",
1394 tagTooltip: "Add the tag '%0'"});
1396 merge(config.messages,{
1397 sizeTemplates:
1399 {unit: 1024*1024*1024, template: "%0\u00a0GB"},
1400 {unit: 1024*1024, template: "%0\u00a0MB"},
1401 {unit: 1024, template: "%0\u00a0KB"},
1402 {unit: 1, template: "%0\u00a0B"}
1403 ]});
1405 merge(config.macros.search,{
1406 label: "search",
1407 prompt: "Search this TiddlyWiki",
1408 accessKey: "F",
1409 successMsg: "%0 tiddlers found matching %1",
1410 failureMsg: "No tiddlers found matching %0"});
1412 merge(config.macros.tagging,{
1413 label: "tagging: ",
1414 labelNotTag: "not tagging",
1415 tooltip: "List of tiddlers tagged with '%0'"});
1417 merge(config.macros.timeline,{
1418 dateFormat: "DD MMM YYYY"});
1420 merge(config.macros.allTags,{
1421 tooltip: "Show tiddlers tagged with '%0'",
1422 noTags: "There are no tagged tiddlers"});
1424 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
1425 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
1426 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
1427 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
1428 config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
1430 merge(config.macros.closeAll,{
1431 label: "close all",
1432 prompt: "Close all displayed tiddlers (except any that are being edited)"});
1434 merge(config.macros.permaview,{
1435 label: "permaview",
1436 prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
1438 merge(config.macros.saveChanges,{
1439 label: "save changes",
1440 prompt: "Save all tiddlers to create a new TiddlyWiki",
1441 accessKey: "S"});
1443 merge(config.macros.newTiddler,{
1444 label: "new tiddler",
1445 prompt: "Create a new tiddler",
1446 title: "New Tiddler",
1447 accessKey: "N"});
1449 merge(config.macros.newJournal,{
1450 label: "new journal",
1451 prompt: "Create a new tiddler from the current date and time",
1452 accessKey: "J"});
1454 merge(config.macros.options,{
1455 wizardTitle: "Tweak advanced options",
1456 step1Title: "These options are saved in cookies in your browser",
1457 step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
1458 unknownDescription: "//(unknown)//",
1459 listViewTemplate: {
1460 columns: [
1461 {name: 'Option', field: 'option', title: "Option", type: 'String'},
1462 {name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
1463 {name: 'Name', field: 'name', title: "Name", type: 'String'}
1465 rowClasses: [
1466 {className: 'lowlight', field: 'lowlight'}
1470 merge(config.macros.plugins,{
1471 wizardTitle: "Manage plugins",
1472 step1Title: "Currently loaded plugins",
1473 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1474 skippedText: "(This plugin has not been executed because it was added since startup)",
1475 noPluginText: "There are no plugins installed",
1476 confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
1477 removeLabel: "remove systemConfig tag",
1478 removePrompt: "Remove systemConfig tag",
1479 deleteLabel: "delete",
1480 deletePrompt: "Delete these tiddlers forever",
1481 listViewTemplate: {
1482 columns: [
1483 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1484 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1485 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1486 {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
1487 {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
1488 {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
1489 {name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
1490 {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
1491 {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
1493 rowClasses: [
1494 {className: 'error', field: 'error'},
1495 {className: 'warning', field: 'warning'}
1499 merge(config.macros.toolbar,{
1500 moreLabel: "more",
1501 morePrompt: "Reveal further commands"
1504 merge(config.macros.refreshDisplay,{
1505 label: "refresh",
1506 prompt: "Redraw the entire TiddlyWiki display"
1509 merge(config.macros.importTiddlers,{
1510 readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
1511 wizardTitle: "Import tiddlers from another file or server",
1512 step1Title: "Step 1: Locate the server or TiddlyWiki file",
1513 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>",
1514 openLabel: "open",
1515 openPrompt: "Open the connection to this file or server",
1516 openError: "There were problems fetching the tiddlywiki file",
1517 statusOpenHost: "Opening the host",
1518 statusGetWorkspaceList: "Getting the list of available workspaces",
1519 step2Title: "Step 2: Choose the workspace",
1520 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>",
1521 cancelLabel: "cancel",
1522 cancelPrompt: "Cancel this import",
1523 statusOpenWorkspace: "Opening the workspace",
1524 statusGetTiddlerList: "Getting the list of available tiddlers",
1525 errorGettingTiddlerList: "Error getting list of tiddlers, click Cancel to try again",
1526 step3Title: "Step 3: Choose the tiddlers to import",
1527 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'>",
1528 importLabel: "import",
1529 importPrompt: "Import these tiddlers",
1530 confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
1531 step4Title: "Step 4: Importing %0 tiddler(s)",
1532 step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
1533 doneLabel: "done",
1534 donePrompt: "Close this wizard",
1535 statusDoingImport: "Importing tiddlers",
1536 statusDoneImport: "All tiddlers imported",
1537 systemServerNamePattern: "%2 on %1",
1538 systemServerNamePatternNoWorkspace: "%1",
1539 confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
1540 serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
1541 serverSaveModifier: "(System)",
1542 listViewTemplate: {
1543 columns: [
1544 {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
1545 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1546 {name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
1547 {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
1549 rowClasses: [
1553 merge(config.macros.upgrade,{
1554 wizardTitle: "Upgrade TiddlyWiki core code",
1555 step1Title: "Update or repair this TiddlyWiki to the latest release",
1556 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>",
1557 errorCantUpgrade: "Unable to upgrade this TiddlyWiki. You can only perform upgrades on TiddlyWiki files stored locally",
1558 errorNotSaved: "You must save changes before you can perform an upgrade",
1559 step2Title: "Confirm the upgrade details",
1560 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",
1561 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",
1562 step2Html_upgrade: "You are about to upgrade to TiddlyWiki version %0 from %1",
1563 upgradeLabel: "upgrade",
1564 upgradePrompt: "Prepare for the upgrade process",
1565 statusPreparingBackup: "Preparing backup",
1566 statusSavingBackup: "Saving backup file",
1567 errorSavingBackup: "There was a problem saving the backup file",
1568 statusLoadingCore: "Loading core code",
1569 errorLoadingCore: "Error loading the core code",
1570 errorCoreFormat: "Error with the new core code",
1571 statusSavingCore: "Saving the new core code",
1572 statusReloadingCore: "Reloading the new core code",
1573 startLabel: "start",
1574 startPrompt: "Start the upgrade process",
1575 cancelLabel: "cancel",
1576 cancelPrompt: "Cancel the upgrade process",
1577 step3Title: "Upgrade cancelled",
1578 step3Html: "You have cancelled the upgrade process"
1581 merge(config.macros.sync,{
1582 listViewTemplate: {
1583 columns: [
1584 {name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
1585 {name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
1586 {name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
1587 {name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
1588 {name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
1589 {name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
1590 {name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
1592 rowClasses: [
1594 buttons: [
1595 {caption: "Sync these tiddlers", name: 'sync'}
1597 wizardTitle: "Synchronize with external servers and files",
1598 step1Title: "Choose the tiddlers you want to synchronize",
1599 step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
1600 syncLabel: "sync",
1601 syncPrompt: "Sync these tiddlers",
1602 hasChanged: "Changed while unplugged",
1603 hasNotChanged: "Unchanged while unplugged",
1604 syncStatusList: {
1605 none: {text: "...", color: "transparent"},
1606 changedServer: {text: "Changed on server", color: '#80ff80'},
1607 changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
1608 changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
1609 notFound: {text: "Not found on server", color: '#ffff80'},
1610 putToServer: {text: "Saved update on server", color: '#ff80ff'},
1611 gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
1615 merge(config.macros.annotations,{
1618 merge(config.commands.closeTiddler,{
1619 text: "close",
1620 tooltip: "Close this tiddler"});
1622 merge(config.commands.closeOthers,{
1623 text: "close others",
1624 tooltip: "Close all other tiddlers"});
1626 merge(config.commands.editTiddler,{
1627 text: "edit",
1628 tooltip: "Edit this tiddler",
1629 readOnlyText: "view",
1630 readOnlyTooltip: "View the source of this tiddler"});
1632 merge(config.commands.saveTiddler,{
1633 text: "done",
1634 tooltip: "Save changes to this tiddler"});
1636 merge(config.commands.cancelTiddler,{
1637 text: "cancel",
1638 tooltip: "Undo changes to this tiddler",
1639 warning: "Are you sure you want to abandon your changes to '%0'?",
1640 readOnlyText: "done",
1641 readOnlyTooltip: "View this tiddler normally"});
1643 merge(config.commands.deleteTiddler,{
1644 text: "delete",
1645 tooltip: "Delete this tiddler",
1646 warning: "Are you sure you want to delete '%0'?"});
1648 merge(config.commands.permalink,{
1649 text: "permalink",
1650 tooltip: "Permalink for this tiddler"});
1652 merge(config.commands.references,{
1653 text: "references",
1654 tooltip: "Show tiddlers that link to this one",
1655 popupNone: "No references"});
1657 merge(config.commands.jump,{
1658 text: "jump",
1659 tooltip: "Jump to another open tiddler"});
1661 merge(config.commands.syncing,{
1662 text: "syncing",
1663 tooltip: "Control synchronisation of this tiddler with a server or external file",
1664 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
1665 notCurrentlySyncing: "Not currently syncing",
1666 captionUnSync: "Stop synchronising this tiddler",
1667 chooseServer: "Synchronise this tiddler with another server:",
1668 currServerMarker: "\u25cf ",
1669 notCurrServerMarker: " "});
1671 merge(config.commands.fields,{
1672 text: "fields",
1673 tooltip: "Show the extended fields of this tiddler",
1674 emptyText: "There are no extended fields for this tiddler",
1675 listViewTemplate: {
1676 columns: [
1677 {name: 'Field', field: 'field', title: "Field", type: 'String'},
1678 {name: 'Value', field: 'value', title: "Value", type: 'String'}
1680 rowClasses: [
1682 buttons: [
1683 ]}});
1685 merge(config.shadowTiddlers,{
1686 DefaultTiddlers: "GettingStarted",
1687 MainMenu: "GettingStarted",
1688 SiteTitle: "My TiddlyWiki",
1689 SiteSubtitle: "a reusable non-linear personal web notebook",
1690 SiteUrl: "http://www.tiddlywiki.com/",
1691 SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options \u00bb" "Change TiddlyWiki advanced options">>',
1692 SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
1693 TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>',
1694 ToolbarCommands: "|~ViewToolbar|closeTiddler closeOthers +editTiddler > fields syncing permalink references jump|\n|~EditToolbar|+saveTiddler -cancelTiddler deleteTiddler|"});
1696 merge(config.annotations,{
1697 AdvancedOptions: "This shadow tiddler provides access to several advanced options",
1698 ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
1699 DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
1700 EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
1701 GettingStarted: "This shadow tiddler provides basic usage instructions",
1702 ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
1703 MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
1704 MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
1705 MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
1706 MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
1707 MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately after the script block",
1708 OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
1709 PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
1710 PluginManager: "This shadow tiddler provides access to the plugin manager",
1711 SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
1712 SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
1713 SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
1714 SiteTitle: "This shadow tiddler is used as the first part of the page title",
1715 SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
1716 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",
1717 StyleSheet: "This tiddler can contain custom CSS definitions",
1718 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",
1719 StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
1720 StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
1721 TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
1722 TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
1723 TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
1724 TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
1725 TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
1726 TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
1727 TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
1728 ToolbarCommands: "This shadow tiddler determines which commands are shown in tiddler toolbars",
1729 ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
1732 //--
1733 //-- Main
1734 //--
1736 var params = null; // Command line parameters
1737 var store = null; // TiddlyWiki storage
1738 var story = null; // Main story
1739 var formatter = null; // Default formatters for the wikifier
1740 var anim = typeof Animator == "function" ? new Animator() : null; // Animation engine
1741 var readOnly = false; // Whether we're in readonly mode
1742 var highlightHack = null; // Embarrassing hack department...
1743 var hadConfirmExit = false; // Don't warn more than once
1744 var safeMode = false; // Disable all plugins and cookies
1745 var showBackstage; // Whether to include the backstage area
1746 var installedPlugins = []; // Information filled in when plugins are executed
1747 var startingUp = false; // Whether we're in the process of starting up
1748 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
1750 // Whether to use the JavaSaver applet
1751 var useJavaSaver = (config.browser.isSafari || config.browser.isOpera) && (document.location.toString().substr(0,4) != "http");
1753 // Starting up
1754 function main()
1756 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1757 startingUp = true;
1758 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1759 params = getParameters();
1760 if(params)
1761 params = params.parseParams("open",null,false);
1762 store = new TiddlyWiki();
1763 invokeParamifier(params,"oninit");
1764 story = new Story("tiddlerDisplay","tiddler");
1765 addEvent(document,"click",Popup.onDocumentClick);
1766 saveTest();
1767 loadOptionsCookie();
1768 for(var s=0; s<config.notifyTiddlers.length; s++)
1769 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1770 t1 = new Date();
1771 loadShadowTiddlers();
1772 t2 = new Date();
1773 store.loadFromDiv("storeArea","store",true);
1774 t3 = new Date();
1775 invokeParamifier(params,"onload");
1776 t4 = new Date();
1777 readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
1778 showBackstage = !readOnly;
1779 var pluginProblem = loadPlugins();
1780 t5 = new Date();
1781 formatter = new Formatter(config.formatters);
1782 story.switchTheme(config.options.txtTheme);
1783 invokeParamifier(params,"onconfig");
1784 t6 = new Date();
1785 store.notifyAll();
1786 t7 = new Date();
1787 restart();
1788 refreshDisplay();
1789 t8 = new Date();
1790 if(pluginProblem) {
1791 story.displayTiddler(null,"PluginManager");
1792 displayMessage(config.messages.customConfigError);
1794 for(var m in config.macros) {
1795 if(config.macros[m].init)
1796 config.macros[m].init();
1798 t9 = new Date();
1799 if(showBackstage)
1800 backstage.init();
1801 t10 = new Date();
1802 if(config.options.chkDisplayInstrumentation) {
1803 displayMessage("LoadShadows " + (t2-t1) + " ms");
1804 displayMessage("LoadFromDiv " + (t3-t2) + " ms");
1805 displayMessage("LoadPlugins " + (t5-t4) + " ms");
1806 displayMessage("Notify " + (t7-t6) + " ms");
1807 displayMessage("Restart " + (t8-t7) + " ms");
1808 displayMessage("Macro init " + (t9-t8) + " ms");
1809 displayMessage("Total: " + (t10-t0) + " ms");
1811 startingUp = false;
1814 // Restarting
1815 function restart()
1817 invokeParamifier(params,"onstart");
1818 if(story.isEmpty()) {
1819 var tiddlers = store.filterTiddlers(store.getTiddlerText("DefaultTiddlers"));
1820 story.displayTiddlers(null,tiddlers);
1822 window.scrollTo(0,0);
1825 function saveTest()
1827 var s = document.getElementById("saveTest");
1828 if(s.hasChildNodes())
1829 alert(config.messages.savedSnapshotError);
1830 s.appendChild(document.createTextNode("savetest"));
1833 function loadShadowTiddlers()
1835 var shadows = new TiddlyWiki();
1836 shadows.loadFromDiv("shadowArea","shadows",true);
1837 shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
1838 delete shadows;
1841 function loadPlugins()
1843 if(safeMode)
1844 return false;
1845 var tiddlers = store.getTaggedTiddlers("systemConfig");
1846 var toLoad = [];
1847 var nLoaded = 0;
1848 var map = {};
1849 var nPlugins = tiddlers.length;
1850 installedPlugins = [];
1851 for(var i=0; i<nPlugins; i++) {
1852 var p = getPluginInfo(tiddlers[i]);
1853 installedPlugins[i] = p;
1854 var n = p.Name;
1855 if(n)
1856 map[n] = p;
1857 n = p.Source;
1858 if(n)
1859 map[n] = p;
1861 var visit = function(p) {
1862 if(!p || p.done)
1863 return;
1864 p.done = 1;
1865 var reqs = p.Requires;
1866 if(reqs) {
1867 reqs = reqs.readBracketedList();
1868 for(var i=0; i<reqs.length; i++)
1869 visit(map[reqs[i]]);
1871 toLoad.push(p);
1873 for(i=0; i<nPlugins; i++)
1874 visit(installedPlugins[i]);
1875 for(i=0; i<toLoad.length; i++) {
1876 p = toLoad[i];
1877 pluginInfo = p;
1878 tiddler = p.tiddler;
1879 if(isPluginExecutable(p)) {
1880 if(isPluginEnabled(p)) {
1881 p.executed = true;
1882 var startTime = new Date();
1883 try {
1884 if(tiddler.text)
1885 window.eval(tiddler.text);
1886 nLoaded++;
1887 } catch(ex) {
1888 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
1889 p.error = true;
1891 pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
1892 } else {
1893 nPlugins--;
1895 } else {
1896 p.warning = true;
1899 return nLoaded != nPlugins;
1902 function getPluginInfo(tiddler)
1904 var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
1905 p.tiddler = tiddler;
1906 p.title = tiddler.title;
1907 p.log = [];
1908 return p;
1911 // Check that a particular plugin is valid for execution
1912 function isPluginExecutable(plugin)
1914 if(plugin.tiddler.isTagged("systemConfigForce"))
1915 return verifyTail(plugin,true,config.messages.pluginForced);
1916 if(plugin["CoreVersion"]) {
1917 var coreVersion = plugin["CoreVersion"].split(".");
1918 var w = parseInt(coreVersion[0]) - version.major;
1919 if(w == 0 && coreVersion[1])
1920 w = parseInt(coreVersion[1]) - version.minor;
1921 if(w == 0 && coreVersion[2])
1922 w = parseInt(coreVersion[2]) - version.revision;
1923 if(w > 0)
1924 return verifyTail(plugin,false,config.messages.pluginVersionError);
1926 return true;
1929 function isPluginEnabled(plugin)
1931 if(plugin.tiddler.isTagged("systemConfigDisable"))
1932 return verifyTail(plugin,false,config.messages.pluginDisabled);
1933 return true;
1936 function verifyTail(plugin,result,message)
1938 plugin.log.push(message);
1939 return result;
1942 function invokeMacro(place,macro,params,wikifier,tiddler)
1944 try {
1945 var m = config.macros[macro];
1946 if(m && m.handler)
1947 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
1948 else
1949 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
1950 } catch(ex) {
1951 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
1955 //--
1956 //-- Paramifiers
1957 //--
1959 function getParameters()
1961 var p = null;
1962 if(window.location.hash) {
1963 p = decodeURIComponent(window.location.hash.substr(1));
1964 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
1965 p = convertUTF8ToUnicode(p);
1967 return p;
1970 function invokeParamifier(params,handler)
1972 if(!params || params.length == undefined || params.length <= 1)
1973 return;
1974 for(var t=1; t<params.length; t++) {
1975 var p = config.paramifiers[params[t].name];
1976 if(p && p[handler] instanceof Function)
1977 p[handler](params[t].value);
1981 config.paramifiers = {};
1983 config.paramifiers.start = {
1984 oninit: function(v) {
1985 safeMode = v.toLowerCase() == "safe";
1989 config.paramifiers.open = {
1990 onstart: function(v) {
1991 if(!readOnly || store.tiddlerExists(v) || store.isShadowTiddler(v))
1992 story.displayTiddler("bottom",v,null,false,null);
1996 config.paramifiers.story = {
1997 onstart: function(v) {
1998 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
1999 invokeParamifier(list,"onstart");
2003 config.paramifiers.search = {
2004 onstart: function(v) {
2005 story.search(v,false,false);
2009 config.paramifiers.searchRegExp = {
2010 onstart: function(v) {
2011 story.prototype.search(v,false,true);
2015 config.paramifiers.tag = {
2016 onstart: function(v) {
2017 var tagged = store.getTaggedTiddlers(v,"title");
2018 story.displayTiddlers(null,tagged,null,false,null);
2022 config.paramifiers.newTiddler = {
2023 onstart: function(v) {
2024 if(!readOnly) {
2025 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
2026 story.focusTiddler(v,"text");
2031 config.paramifiers.newJournal = {
2032 onstart: function(v) {
2033 if(!readOnly) {
2034 var now = new Date();
2035 var title = now.formatString(v.trim());
2036 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
2037 story.focusTiddler(title,"text");
2042 config.paramifiers.readOnly = {
2043 onconfig: function(v) {
2044 var p = v.toLowerCase();
2045 readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
2049 config.paramifiers.theme = {
2050 onconfig: function(v) {
2051 story.switchTheme(v);
2055 config.paramifiers.upgrade = {
2056 onstart: function(v) {
2057 upgradeFrom(v);
2061 //--
2062 //-- Formatter helpers
2063 //--
2065 function Formatter(formatters)
2067 this.formatters = [];
2068 var pattern = [];
2069 for(var n=0; n<formatters.length; n++) {
2070 pattern.push("(" + formatters[n].match + ")");
2071 this.formatters.push(formatters[n]);
2073 this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
2076 config.formatterHelpers = {
2078 createElementAndWikify: function(w)
2080 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
2083 inlineCssHelper: function(w)
2085 var styles = [];
2086 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2087 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2088 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2089 var s,v;
2090 if(lookaheadMatch[1]) {
2091 s = lookaheadMatch[1].unDash();
2092 v = lookaheadMatch[2];
2093 } else {
2094 s = lookaheadMatch[3].unDash();
2095 v = lookaheadMatch[4];
2097 if(s=="bgcolor")
2098 s = "backgroundColor";
2099 styles.push({style: s, value: v});
2100 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2101 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2102 lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2104 return styles;
2107 applyCssHelper: function(e,styles)
2109 for(var t=0; t< styles.length; t++) {
2110 try {
2111 e.style[styles[t].style] = styles[t].value;
2112 } catch (ex) {
2117 enclosedTextHelper: function(w)
2119 this.lookaheadRegExp.lastIndex = w.matchStart;
2120 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2121 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2122 var text = lookaheadMatch[1];
2123 if(config.browser.isIE)
2124 text = text.replace(/\n/g,"\r");
2125 createTiddlyElement(w.output,this.element,null,null,text);
2126 w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
2130 isExternalLink: function(link)
2132 if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
2133 return false;
2135 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
2136 if(urlRegExp.exec(link)) {
2137 return true;
2139 if(link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1 || link.indexOf("#")!=-1) {
2140 return true;
2142 return false;
2147 //--
2148 //-- Standard formatters
2149 //--
2151 config.formatters = [
2153 name: "table",
2154 match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
2155 lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
2156 rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
2157 cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
2158 cellTermRegExp: /((?:\x20*)\|)/mg,
2159 rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
2160 handler: function(w)
2162 var table = createTiddlyElement(w.output,"table",null,"twtable");
2163 var prevColumns = [];
2164 var currRowType = null;
2165 var rowContainer;
2166 var rowCount = 0;
2167 w.nextMatch = w.matchStart;
2168 this.lookaheadRegExp.lastIndex = w.nextMatch;
2169 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2170 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2171 var nextRowType = lookaheadMatch[2];
2172 if(nextRowType == "k") {
2173 table.className = lookaheadMatch[1];
2174 w.nextMatch += lookaheadMatch[0].length+1;
2175 } else {
2176 if(nextRowType != currRowType) {
2177 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2178 currRowType = nextRowType;
2180 if(currRowType == "c") {
2181 // Caption
2182 w.nextMatch++;
2183 if(rowContainer != table.firstChild)
2184 table.insertBefore(rowContainer,table.firstChild);
2185 rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
2186 w.subWikifyTerm(rowContainer,this.rowTermRegExp);
2187 } else {
2188 var theRow = createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow");
2189 theRow.onmouseover = function() {addClass(this,"hoverRow");};
2190 theRow.onmouseout = function() {removeClass(this,"hoverRow");};
2191 this.rowHandler(w,theRow,prevColumns);
2192 rowCount++;
2195 this.lookaheadRegExp.lastIndex = w.nextMatch;
2196 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2199 rowHandler: function(w,e,prevColumns)
2201 var col = 0;
2202 var colSpanCount = 1;
2203 var prevCell = null;
2204 this.cellRegExp.lastIndex = w.nextMatch;
2205 var cellMatch = this.cellRegExp.exec(w.source);
2206 while(cellMatch && cellMatch.index == w.nextMatch) {
2207 if(cellMatch[1] == "~") {
2208 // Rowspan
2209 var last = prevColumns[col];
2210 if(last) {
2211 last.rowSpanCount++;
2212 last.element.setAttribute("rowspan",last.rowSpanCount);
2213 last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
2214 last.element.valign = "center";
2216 w.nextMatch = this.cellRegExp.lastIndex-1;
2217 } else if(cellMatch[1] == ">") {
2218 // Colspan
2219 colSpanCount++;
2220 w.nextMatch = this.cellRegExp.lastIndex-1;
2221 } else if(cellMatch[2]) {
2222 // End of row
2223 if(prevCell && colSpanCount > 1) {
2224 prevCell.setAttribute("colspan",colSpanCount);
2225 prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
2227 w.nextMatch = this.cellRegExp.lastIndex;
2228 break;
2229 } else {
2230 // Cell
2231 w.nextMatch++;
2232 var styles = config.formatterHelpers.inlineCssHelper(w);
2233 var spaceLeft = false;
2234 var chr = w.source.substr(w.nextMatch,1);
2235 while(chr == " ") {
2236 spaceLeft = true;
2237 w.nextMatch++;
2238 chr = w.source.substr(w.nextMatch,1);
2240 var cell;
2241 if(chr == "!") {
2242 cell = createTiddlyElement(e,"th");
2243 w.nextMatch++;
2244 } else {
2245 cell = createTiddlyElement(e,"td");
2247 prevCell = cell;
2248 prevColumns[col] = {rowSpanCount:1,element:cell};
2249 if(colSpanCount > 1) {
2250 cell.setAttribute("colspan",colSpanCount);
2251 cell.setAttribute("colSpan",colSpanCount); // Needed for IE
2252 colSpanCount = 1;
2254 config.formatterHelpers.applyCssHelper(cell,styles);
2255 w.subWikifyTerm(cell,this.cellTermRegExp);
2256 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
2257 cell.align = spaceLeft ? "center" : "left";
2258 else if(spaceLeft)
2259 cell.align = "right";
2260 w.nextMatch--;
2262 col++;
2263 this.cellRegExp.lastIndex = w.nextMatch;
2264 cellMatch = this.cellRegExp.exec(w.source);
2270 name: "heading",
2271 match: "^!{1,6}",
2272 termRegExp: /(\n)/mg,
2273 handler: function(w)
2275 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
2280 name: "list",
2281 match: "^(?:[\\*#;:]+)",
2282 lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
2283 termRegExp: /(\n)/mg,
2284 handler: function(w)
2286 var stack = [w.output];
2287 var currLevel = 0, currType = null;
2288 var listLevel, listType, itemType, baseType;
2289 w.nextMatch = w.matchStart;
2290 this.lookaheadRegExp.lastIndex = w.nextMatch;
2291 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2292 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2293 if(lookaheadMatch[1]) {
2294 listType = "ul";
2295 itemType = "li";
2296 } else if(lookaheadMatch[2]) {
2297 listType = "ol";
2298 itemType = "li";
2299 } else if(lookaheadMatch[3]) {
2300 listType = "dl";
2301 itemType = "dt";
2302 } else if(lookaheadMatch[4]) {
2303 listType = "dl";
2304 itemType = "dd";
2306 if(!baseType)
2307 baseType = listType;
2308 listLevel = lookaheadMatch[0].length;
2309 w.nextMatch += lookaheadMatch[0].length;
2310 var t;
2311 if(listLevel > currLevel) {
2312 for(t=currLevel; t<listLevel; t++) {
2313 var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
2314 stack.push(createTiddlyElement(target,listType));
2316 } else if(listType!=baseType && listLevel==1) {
2317 w.nextMatch -= lookaheadMatch[0].length;
2318 return;
2319 } else if(listLevel < currLevel) {
2320 for(t=currLevel; t>listLevel; t--)
2321 stack.pop();
2322 } else if(listLevel == currLevel && listType != currType) {
2323 stack.pop();
2324 stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
2326 currLevel = listLevel;
2327 currType = listType;
2328 var e = createTiddlyElement(stack[stack.length-1],itemType);
2329 w.subWikifyTerm(e,this.termRegExp);
2330 this.lookaheadRegExp.lastIndex = w.nextMatch;
2331 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2337 name: "quoteByBlock",
2338 match: "^<<<\\n",
2339 termRegExp: /(^<<<(\n|$))/mg,
2340 element: "blockquote",
2341 handler: config.formatterHelpers.createElementAndWikify
2345 name: "quoteByLine",
2346 match: "^>+",
2347 lookaheadRegExp: /^>+/mg,
2348 termRegExp: /(\n)/mg,
2349 element: "blockquote",
2350 handler: function(w)
2352 var stack = [w.output];
2353 var currLevel = 0;
2354 var newLevel = w.matchLength;
2355 var t;
2356 do {
2357 if(newLevel > currLevel) {
2358 for(t=currLevel; t<newLevel; t++)
2359 stack.push(createTiddlyElement(stack[stack.length-1],this.element));
2360 } else if(newLevel < currLevel) {
2361 for(t=currLevel; t>newLevel; t--)
2362 stack.pop();
2364 currLevel = newLevel;
2365 w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
2366 createTiddlyElement(stack[stack.length-1],"br");
2367 this.lookaheadRegExp.lastIndex = w.nextMatch;
2368 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2369 var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
2370 if(matched) {
2371 newLevel = lookaheadMatch[0].length;
2372 w.nextMatch += lookaheadMatch[0].length;
2374 } while(matched);
2379 name: "rule",
2380 match: "^----+$\\n?",
2381 handler: function(w)
2383 createTiddlyElement(w.output,"hr");
2388 name: "monospacedByLine",
2389 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
2390 element: "pre",
2391 handler: function(w)
2393 switch(w.matchText) {
2394 case "/*{{{*/\n": // CSS
2395 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2396 break;
2397 case "{{{\n": // monospaced block
2398 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2399 break;
2400 case "//{{{\n": // plugin
2401 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2402 break;
2403 case "<!--{{{-->\n": //template
2404 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
2405 break;
2406 default:
2407 break;
2409 config.formatterHelpers.enclosedTextHelper.call(this,w);
2414 name: "wikifyComment",
2415 match: "^(?:/\\*\\*\\*|<!---)\\n",
2416 handler: function(w)
2418 var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
2419 w.subWikifyTerm(w.output,termRegExp);
2424 name: "macro",
2425 match: "<<",
2426 lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
2427 handler: function(w)
2429 this.lookaheadRegExp.lastIndex = w.matchStart;
2430 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2431 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
2432 w.nextMatch = this.lookaheadRegExp.lastIndex;
2433 invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
2439 name: "prettyLink",
2440 match: "\\[\\[",
2441 lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
2442 handler: function(w)
2444 this.lookaheadRegExp.lastIndex = w.matchStart;
2445 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2446 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2447 var e;
2448 var text = lookaheadMatch[1];
2449 if(lookaheadMatch[3]) {
2450 // Pretty bracketted link
2451 var link = lookaheadMatch[3];
2452 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
2453 createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2454 } else {
2455 // Simple bracketted link
2456 e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
2458 createTiddlyText(e,text);
2459 w.nextMatch = this.lookaheadRegExp.lastIndex;
2465 name: "wikiLink",
2466 match: config.textPrimitives.unWikiLink+"?"+config.textPrimitives.wikiLink,
2467 handler: function(w)
2469 if(w.matchText.substr(0,1) == config.textPrimitives.unWikiLink) {
2470 w.outputText(w.output,w.matchStart+1,w.nextMatch);
2471 return;
2473 if(w.matchStart > 0) {
2474 var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
2475 preRegExp.lastIndex = w.matchStart-1;
2476 var preMatch = preRegExp.exec(w.source);
2477 if(preMatch.index == w.matchStart-1) {
2478 w.outputText(w.output,w.matchStart,w.nextMatch);
2479 return;
2482 if(w.autoLinkWikiWords || store.isShadowTiddler(w.matchText)) {
2483 var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
2484 w.outputText(link,w.matchStart,w.nextMatch);
2485 } else {
2486 w.outputText(w.output,w.matchStart,w.nextMatch);
2492 name: "urlLink",
2493 match: config.textPrimitives.urlPattern,
2494 handler: function(w)
2496 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
2501 name: "image",
2502 match: "\\[[<>]?[Ii][Mm][Gg]\\[",
2503 lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
2504 handler: function(w)
2506 this.lookaheadRegExp.lastIndex = w.matchStart;
2507 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2508 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2509 var e = w.output;
2510 if(lookaheadMatch[5]) {
2511 var link = lookaheadMatch[5];
2512 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
2513 addClass(e,"imageLink");
2515 var img = createTiddlyElement(e,"img");
2516 if(lookaheadMatch[1])
2517 img.align = "left";
2518 else if(lookaheadMatch[2])
2519 img.align = "right";
2520 if(lookaheadMatch[3]) {
2521 img.title = lookaheadMatch[3];
2522 img.setAttribute("alt",lookaheadMatch[3]);
2524 img.src = lookaheadMatch[4];
2525 w.nextMatch = this.lookaheadRegExp.lastIndex;
2531 name: "html",
2532 match: "<[Hh][Tt][Mm][Ll]>",
2533 lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
2534 handler: function(w)
2536 this.lookaheadRegExp.lastIndex = w.matchStart;
2537 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2538 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2539 createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
2540 w.nextMatch = this.lookaheadRegExp.lastIndex;
2546 name: "commentByBlock",
2547 match: "/%",
2548 lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
2549 handler: function(w)
2551 this.lookaheadRegExp.lastIndex = w.matchStart;
2552 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2553 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
2554 w.nextMatch = this.lookaheadRegExp.lastIndex;
2559 name: "characterFormat",
2560 match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{",
2561 handler: function(w)
2563 switch(w.matchText) {
2564 case "''":
2565 w.subWikifyTerm(w.output.appendChild(document.createElement("strong")),/('')/mg);
2566 break;
2567 case "//":
2568 w.subWikifyTerm(createTiddlyElement(w.output,"em"),/(\/\/)/mg);
2569 break;
2570 case "__":
2571 w.subWikifyTerm(createTiddlyElement(w.output,"u"),/(__)/mg);
2572 break;
2573 case "^^":
2574 w.subWikifyTerm(createTiddlyElement(w.output,"sup"),/(\^\^)/mg);
2575 break;
2576 case "~~":
2577 w.subWikifyTerm(createTiddlyElement(w.output,"sub"),/(~~)/mg);
2578 break;
2579 case "--":
2580 w.subWikifyTerm(createTiddlyElement(w.output,"strike"),/(--)/mg);
2581 break;
2582 case "{{{":
2583 var lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
2584 lookaheadRegExp.lastIndex = w.matchStart;
2585 var lookaheadMatch = lookaheadRegExp.exec(w.source);
2586 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2587 createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
2588 w.nextMatch = lookaheadRegExp.lastIndex;
2590 break;
2596 name: "customFormat",
2597 match: "@@|\\{\\{",
2598 handler: function(w)
2600 switch(w.matchText) {
2601 case "@@":
2602 var e = createTiddlyElement(w.output,"span");
2603 var styles = config.formatterHelpers.inlineCssHelper(w);
2604 if(styles.length == 0)
2605 e.className = "marked";
2606 else
2607 config.formatterHelpers.applyCssHelper(e,styles);
2608 w.subWikifyTerm(e,/(@@)/mg);
2609 break;
2610 case "{{":
2611 lookaheadRegExp = /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg;
2612 lookaheadRegExp.lastIndex = w.matchStart;
2613 lookaheadMatch = lookaheadRegExp.exec(w.source);
2614 if(lookaheadMatch) {
2615 w.nextMatch = lookaheadRegExp.lastIndex;
2616 e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
2617 w.subWikifyTerm(e,/(\}\}\})/mg);
2619 break;
2625 name: "mdash",
2626 match: "--",
2627 handler: function(w)
2629 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
2634 name: "lineBreak",
2635 match: "\\n|<br ?/?>",
2636 handler: function(w)
2638 createTiddlyElement(w.output,"br");
2643 name: "rawText",
2644 match: "\\\"{3}|<nowiki>",
2645 lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
2646 handler: function(w)
2648 this.lookaheadRegExp.lastIndex = w.matchStart;
2649 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2650 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
2651 createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
2652 w.nextMatch = this.lookaheadRegExp.lastIndex;
2658 name: "htmlEntitiesEncoding",
2659 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};)",
2660 handler: function(w)
2662 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
2668 //--
2669 //-- Wikifier
2670 //--
2672 function getParser(tiddler,format)
2674 if(tiddler) {
2675 if(!format)
2676 format = tiddler.fields["wikiformat"];
2677 var i;
2678 if(format) {
2679 for(i in config.parsers) {
2680 if(format == config.parsers[i].format)
2681 return config.parsers[i];
2683 } else {
2684 for(i in config.parsers) {
2685 if(tiddler.isTagged(config.parsers[i].formatTag))
2686 return config.parsers[i];
2690 return formatter;
2693 function wikify(source,output,highlightRegExp,tiddler)
2695 if(source && source != "") {
2696 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
2697 wikifier.subWikify(output);
2701 function wikifyStatic(source,highlightRegExp,tiddler,format)
2703 var e = createTiddlyElement(document.body,"pre");
2704 e.style.display = "none";
2705 var html = "";
2706 if(source && source != "") {
2707 if(!tiddler)
2708 tiddler = new Tiddler("temp");
2709 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2710 wikifier.isStatic = true;
2711 wikifier.subWikify(e);
2712 html = e.innerHTML;
2713 removeNode(e);
2715 return html;
2718 function wikifyPlain(title,theStore,limit)
2720 if(!theStore)
2721 theStore = store;
2722 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2723 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2724 } else {
2725 return "";
2729 function wikifyPlainText(text,limit,tiddler)
2731 if(limit > 0)
2732 text = text.substr(0,limit);
2733 var wikifier = new Wikifier(text,formatter,null,tiddler);
2734 return wikifier.wikifyPlain();
2737 function highlightify(source,output,highlightRegExp,tiddler)
2739 if(source && source != "") {
2740 var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
2741 wikifier.outputText(output,0,source.length);
2745 function Wikifier(source,formatter,highlightRegExp,tiddler)
2747 this.source = source;
2748 this.output = null;
2749 this.formatter = formatter;
2750 this.nextMatch = 0;
2751 this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
2752 this.highlightRegExp = highlightRegExp;
2753 this.highlightMatch = null;
2754 this.isStatic = false;
2755 if(highlightRegExp) {
2756 highlightRegExp.lastIndex = 0;
2757 this.highlightMatch = highlightRegExp.exec(source);
2759 this.tiddler = tiddler;
2762 Wikifier.prototype.wikifyPlain = function()
2764 var e = createTiddlyElement(document.body,"div");
2765 e.style.display = "none";
2766 this.subWikify(e);
2767 var text = getPlainText(e);
2768 removeNode(e);
2769 return text;
2772 Wikifier.prototype.subWikify = function(output,terminator)
2774 try {
2775 if(terminator)
2776 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
2777 else
2778 this.subWikifyUnterm(output);
2779 } catch(ex) {
2780 showException(ex);
2784 Wikifier.prototype.subWikifyUnterm = function(output)
2786 var oldOutput = this.output;
2787 this.output = output;
2788 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2789 var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2790 while(formatterMatch) {
2791 // Output any text before the match
2792 if(formatterMatch.index > this.nextMatch)
2793 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2794 // Set the match parameters for the handler
2795 this.matchStart = formatterMatch.index;
2796 this.matchLength = formatterMatch[0].length;
2797 this.matchText = formatterMatch[0];
2798 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2799 for(var t=1; t<formatterMatch.length; t++) {
2800 if(formatterMatch[t]) {
2801 this.formatter.formatters[t-1].handler(this);
2802 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2803 break;
2806 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
2808 if(this.nextMatch < this.source.length) {
2809 this.outputText(this.output,this.nextMatch,this.source.length);
2810 this.nextMatch = this.source.length;
2812 this.output = oldOutput;
2815 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
2817 var oldOutput = this.output;
2818 this.output = output;
2819 terminatorRegExp.lastIndex = this.nextMatch;
2820 var terminatorMatch = terminatorRegExp.exec(this.source);
2821 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2822 var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2823 while(terminatorMatch || formatterMatch) {
2824 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
2825 if(terminatorMatch.index > this.nextMatch)
2826 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
2827 this.matchText = terminatorMatch[1];
2828 this.matchLength = terminatorMatch[1].length;
2829 this.matchStart = terminatorMatch.index;
2830 this.nextMatch = this.matchStart + this.matchLength;
2831 this.output = oldOutput;
2832 return;
2834 if(formatterMatch.index > this.nextMatch)
2835 this.outputText(this.output,this.nextMatch,formatterMatch.index);
2836 this.matchStart = formatterMatch.index;
2837 this.matchLength = formatterMatch[0].length;
2838 this.matchText = formatterMatch[0];
2839 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
2840 for(var t=1; t<formatterMatch.length; t++) {
2841 if(formatterMatch[t]) {
2842 this.formatter.formatters[t-1].handler(this);
2843 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
2844 break;
2847 terminatorRegExp.lastIndex = this.nextMatch;
2848 terminatorMatch = terminatorRegExp.exec(this.source);
2849 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
2851 if(this.nextMatch < this.source.length) {
2852 this.outputText(this.output,this.nextMatch,this.source.length);
2853 this.nextMatch = this.source.length;
2855 this.output = oldOutput;
2858 Wikifier.prototype.outputText = function(place,startPos,endPos)
2860 while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
2861 if(this.highlightMatch.index > startPos) {
2862 createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
2863 startPos = this.highlightMatch.index;
2865 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
2866 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
2867 startPos = highlightEnd;
2868 if(startPos >= this.highlightRegExp.lastIndex)
2869 this.highlightMatch = this.highlightRegExp.exec(this.source);
2871 if(startPos < endPos) {
2872 createTiddlyText(place,this.source.substring(startPos,endPos));
2876 //--
2877 //-- Macro definitions
2878 //--
2880 config.macros.today.handler = function(place,macroName,params)
2882 var now = new Date();
2883 var text = params[0] ? now.formatString(params[0].trim()) : now.toLocaleString();
2884 createTiddlyElement(place,"span",null,null,text);
2887 config.macros.version.handler = function(place)
2889 createTiddlyElement(place,"span",null,null,formatVersion());
2892 config.macros.list.handler = function(place,macroName,params)
2894 var type = params[0] ? params[0] : "all";
2895 var list = document.createElement("ul");
2896 place.appendChild(list);
2897 if(this[type].prompt)
2898 createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
2899 var results;
2900 if(this[type].handler)
2901 results = this[type].handler(params);
2902 for(var t = 0; t < results.length; t++) {
2903 var li = document.createElement("li");
2904 list.appendChild(li);
2905 createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
2909 config.macros.list.all.handler = function(params)
2911 return store.reverseLookup("tags","excludeLists",false,"title");
2914 config.macros.list.missing.handler = function(params)
2916 return store.getMissingLinks();
2919 config.macros.list.orphans.handler = function(params)
2921 return store.getOrphans();
2924 config.macros.list.shadowed.handler = function(params)
2926 return store.getShadowed();
2929 config.macros.list.touched.handler = function(params)
2931 return store.getTouched();
2934 config.macros.list.filter.handler = function(params)
2936 var filter = params[1];
2937 var results = [];
2938 if(filter) {
2939 var tiddlers = store.filterTiddlers(filter);
2940 for(var t=0; t<tiddlers.length; t++)
2941 results.push(tiddlers[t].title);
2943 return results;
2946 config.macros.allTags.handler = function(place,macroName,params)
2948 var tags = store.getTags(params[0]);
2949 var ul = createTiddlyElement(place,"ul");
2950 if(tags.length == 0)
2951 createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
2952 for(var t=0; t<tags.length; t++) {
2953 var title = tags[t][0];
2954 var info = getTiddlyLinkInfo(title);
2955 var li = createTiddlyElement(ul,"li");
2956 var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
2957 btn.setAttribute("tag",title);
2958 btn.setAttribute("refresh","link");
2959 btn.setAttribute("tiddlyLink",title);
2963 config.macros.timeline.handler = function(place,macroName,params)
2965 var field = params[0] ? params[0] : "modified";
2966 var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
2967 var lastDay = "";
2968 var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
2969 var dateFormat = params[2] ? params[2] : this.dateFormat;
2970 for(var t=tiddlers.length-1; t>=last; t--) {
2971 var tiddler = tiddlers[t];
2972 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
2973 if(theDay != lastDay) {
2974 var ul = document.createElement("ul");
2975 place.appendChild(ul);
2976 createTiddlyElement(ul,"li",null,"listTitle",tiddler[field].formatString(dateFormat));
2977 lastDay = theDay;
2979 createTiddlyElement(ul,"li",null,"listLink").appendChild(createTiddlyLink(place,tiddler.title,true));
2983 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2985 params = paramString.parseParams("name",null,true,false,true);
2986 var names = params[0]["name"];
2987 var tiddlerName = names[0];
2988 var className = names[1] ? names[1] : null;
2989 var args = params[0]["with"];
2990 var wrapper = createTiddlyElement(place,"span",null,className);
2991 if(!args) {
2992 wrapper.setAttribute("refresh","content");
2993 wrapper.setAttribute("tiddler",tiddlerName);
2995 var text = store.getTiddlerText(tiddlerName);
2996 if(text) {
2997 var stack = config.macros.tiddler.tiddlerStack;
2998 if(stack.indexOf(tiddlerName) !== -1)
2999 return;
3000 stack.push(tiddlerName);
3001 try {
3002 var n = args ? Math.min(args.length,9) : 0;
3003 for(var i=0; i<n; i++) {
3004 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
3005 text = text.replace(placeholderRE,args[i]);
3007 config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
3008 } finally {
3009 stack.pop();
3014 config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
3016 wikify(text,place,null,store.getTiddler(tiddlerName));
3019 config.macros.tiddler.tiddlerStack = [];
3021 config.macros.tag.handler = function(place,macroName,params)
3023 createTagButton(place,params[0],null,params[1],params[2]);
3026 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3028 params = paramString.parseParams("anon",null,true,false,false);
3029 var ul = createTiddlyElement(place,"ul");
3030 var title = getParam(params,"anon","");
3031 if(title && store.tiddlerExists(title))
3032 tiddler = store.getTiddler(title);
3033 var sep = getParam(params,"sep"," ");
3034 var lingo = config.views.wikified.tag;
3035 var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
3036 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([tiddler.title]));
3037 for(var t=0; t<tiddler.tags.length; t++) {
3038 createTagButton(createTiddlyElement(ul,"li"),tiddler.tags[t],tiddler.title);
3039 if(t<tiddler.tags.length-1)
3040 createTiddlyText(ul,sep);
3044 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3046 params = paramString.parseParams("anon",null,true,false,false);
3047 var ul = createTiddlyElement(place,"ul");
3048 var title = getParam(params,"anon","");
3049 if(title == "" && tiddler instanceof Tiddler)
3050 title = tiddler.title;
3051 var sep = getParam(params,"sep"," ");
3052 ul.setAttribute("title",this.tooltip.format([title]));
3053 var tagged = store.getTaggedTiddlers(title);
3054 var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
3055 createTiddlyElement(ul,"li",null,"listTitle",prompt.format([title,tagged.length]));
3056 for(var t=0; t<tagged.length; t++) {
3057 createTiddlyLink(createTiddlyElement(ul,"li"),tagged[t].title,true);
3058 if(t<tagged.length-1)
3059 createTiddlyText(ul,sep);
3063 config.macros.closeAll.handler = function(place)
3065 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3068 config.macros.closeAll.onClick = function(e)
3070 story.closeAllTiddlers();
3071 return false;
3074 config.macros.permaview.handler = function(place)
3076 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3079 config.macros.permaview.onClick = function(e)
3081 story.permaView();
3082 return false;
3085 config.macros.saveChanges.handler = function(place,macroName,params)
3087 if(!readOnly)
3088 createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
3091 config.macros.saveChanges.onClick = function(e)
3093 saveChanges();
3094 return false;
3097 config.macros.slider.onClickSlider = function(ev)
3099 var e = ev ? ev : window.event;
3100 var n = this.nextSibling;
3101 var cookie = n.getAttribute("cookie");
3102 var isOpen = n.style.display != "none";
3103 if(config.options.chkAnimate && anim && typeof Slider == "function")
3104 anim.startAnimating(new Slider(n,!isOpen,null,"none"));
3105 else
3106 n.style.display = isOpen ? "none" : "block";
3107 config.options[cookie] = !isOpen;
3108 saveOptionCookie(cookie);
3109 return false;
3112 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
3114 var c = cookie ? cookie : "";
3115 var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
3116 var panel = createTiddlyElement(null,"div",null,"sliderPanel");
3117 panel.setAttribute("cookie",c);
3118 panel.style.display = config.options[c] ? "block" : "none";
3119 place.appendChild(panel);
3120 return panel;
3123 config.macros.slider.handler = function(place,macroName,params)
3125 var panel = this.createSlider(place,params[0],params[2],params[3]);
3126 var text = store.getTiddlerText(params[1]);
3127 panel.setAttribute("refresh","content");
3128 panel.setAttribute("tiddler",params[1]);
3129 if(text)
3130 wikify(text,panel,null,store.getTiddler(params[1]));
3133 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
3134 config.macros.gradient.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3136 var panel = wikifier ? createTiddlyElement(place,"div",null,"gradient") : place;
3137 panel.style.position = "relative";
3138 panel.style.overflow = "hidden";
3139 panel.style.zIndex = "0";
3140 if(wikifier) {
3141 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
3142 config.formatterHelpers.applyCssHelper(panel,styles);
3144 params = paramString.parseParams("color");
3145 var locolors = [], hicolors = [];
3146 for(var t=2; t<params.length; t++) {
3147 var c = new RGB(params[t].value);
3148 if(params[t].name == "snap") {
3149 hicolors[hicolors.length-1] = c;
3150 } else {
3151 locolors.push(c);
3152 hicolors.push(c);
3155 drawGradient(panel,params[1].value != "vert",locolors,hicolors);
3156 if(wikifier)
3157 wikifier.subWikify(panel,">>");
3158 if(document.all) {
3159 panel.style.height = "100%";
3160 panel.style.width = "100%";
3164 config.macros.message.handler = function(place,macroName,params)
3166 if(params[0]) {
3167 var names = params[0].split(".");
3168 var lookupMessage = function(root,nameIndex) {
3169 if(names[nameIndex] in root) {
3170 if(nameIndex < names.length-1)
3171 return (lookupMessage(root[names[nameIndex]],nameIndex+1));
3172 else
3173 return root[names[nameIndex]];
3174 } else
3175 return null;
3177 var m = lookupMessage(config,0);
3178 if(m == null)
3179 m = lookupMessage(window,0);
3180 createTiddlyText(place,m.toString().format(params.splice(1)));
3185 config.macros.view.views = {
3186 text: function(value,place,params,wikifier,paramString,tiddler) {
3187 highlightify(value,place,highlightHack,tiddler);
3189 link: function(value,place,params,wikifier,paramString,tiddler) {
3190 createTiddlyLink(place,value,true);
3192 wikified: function(value,place,params,wikifier,paramString,tiddler) {
3193 if(params[2])
3194 value=params[2].unescapeLineBreaks().format([value]);
3195 wikify(value,place,highlightHack,tiddler);
3197 date: function(value,place,params,wikifier,paramString,tiddler) {
3198 value = Date.convertFromYYYYMMDDHHMM(value);
3199 createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
3203 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3205 if((tiddler instanceof Tiddler) && params[0]) {
3206 var value = store.getValue(tiddler,params[0]);
3207 if(value) {
3208 var type = params[1] ? params[1] : config.macros.view.defaultView;
3209 var handler = config.macros.view.views[type];
3210 if(handler)
3211 handler(value,place,params,wikifier,paramString,tiddler);
3216 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3218 var field = params[0];
3219 var rows = params[1] || 0;
3220 var defVal = params[2] || '';
3221 if((tiddler instanceof Tiddler) && field) {
3222 story.setDirty(tiddler.title,true);
3223 var e,v;
3224 if(field != "text" && !rows) {
3225 e = createTiddlyElement(null,"input");
3226 if(tiddler.isReadOnly())
3227 e.setAttribute("readOnly","readOnly");
3228 e.setAttribute("edit",field);
3229 e.setAttribute("type","text");
3230 e.value = store.getValue(tiddler,field) || defVal;
3231 e.setAttribute("size","40");
3232 e.setAttribute("autocomplete","off");
3233 place.appendChild(e);
3234 } else {
3235 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
3236 var wrapper2 = createTiddlyElement(wrapper1,"div");
3237 e = createTiddlyElement(wrapper2,"textarea");
3238 if(tiddler.isReadOnly())
3239 e.setAttribute("readOnly","readOnly");
3240 e.value = v = store.getValue(tiddler,field) || defVal;
3241 rows = rows ? rows : 10;
3242 var lines = v.match(/\n/mg);
3243 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
3244 if(lines != null && lines.length > rows)
3245 rows = lines.length + 5;
3246 rows = Math.min(rows,maxLines);
3247 e.setAttribute("rows",rows);
3248 e.setAttribute("edit",field);
3249 place.appendChild(wrapper1);
3251 return e;
3255 config.macros.tagChooser.onClick = function(ev)
3257 var e = ev ? ev : window.event;
3258 if(e.metaKey || e.ctrlKey) stopEvent(e); //# keep popup open on CTRL-click
3259 var lingo = config.views.editor.tagChooser;
3260 var popup = Popup.create(this);
3261 var tags = store.getTags();
3262 if(tags.length == 0)
3263 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
3264 for(var t=0; t<tags.length; t++) {
3265 var tag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
3266 tag.setAttribute("tag",tags[t][0]);
3267 tag.setAttribute("tiddler",this.getAttribute("tiddler"));
3269 Popup.show();
3270 e.cancelBubble = true;
3271 if(e.stopPropagation) e.stopPropagation();
3272 return false;
3275 config.macros.tagChooser.onTagClick = function(ev)
3277 var e = ev ? ev : window.event;
3278 var tag = this.getAttribute("tag");
3279 var title = this.getAttribute("tiddler");
3280 if(!readOnly)
3281 story.setTiddlerTag(title,tag,0);
3282 return false;
3285 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3287 if(tiddler instanceof Tiddler) {
3288 var lingo = config.views.editor.tagChooser;
3289 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
3290 btn.setAttribute("tiddler",tiddler.title);
3294 config.macros.refreshDisplay.handler = function(place)
3296 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3299 config.macros.refreshDisplay.onClick = function(e)
3301 refreshAll();
3302 return false;
3305 config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3307 var title = tiddler ? tiddler.title : null;
3308 var a = title ? config.annotations[title] : null;
3309 if(!tiddler || !title || !a)
3310 return;
3311 var text = a.format([title]);
3312 wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
3315 //--
3316 //-- NewTiddler and NewJournal macros
3317 //--
3319 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
3321 var tags = [];
3322 for(var t=1; t<params.length; t++) {
3323 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
3324 tags.push(params[t].value);
3326 label = getParam(params,"label",label);
3327 prompt = getParam(params,"prompt",prompt);
3328 accessKey = getParam(params,"accessKey",accessKey);
3329 newFocus = getParam(params,"focus",newFocus);
3330 var customFields = getParam(params,"fields","");
3331 if(!customFields && !store.isShadowTiddler(title))
3332 customFields = String.encodeHashMap(config.defaultCustomFields);
3333 var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
3334 btn.setAttribute("newTitle",title);
3335 btn.setAttribute("isJournal",isJournal ? "true" : "false");
3336 if(tags.length > 0)
3337 btn.setAttribute("params",tags.join("|"));
3338 btn.setAttribute("newFocus",newFocus);
3339 btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
3340 if(customFields !== "")
3341 btn.setAttribute("customFields",customFields);
3342 var text = getParam(params,"text");
3343 if(text !== undefined)
3344 btn.setAttribute("newText",text);
3345 return btn;
3348 config.macros.newTiddler.onClickNewTiddler = function()
3350 var title = this.getAttribute("newTitle");
3351 if(this.getAttribute("isJournal") == "true") {
3352 var now = new Date();
3353 title = now.formatString(title.trim());
3355 var params = this.getAttribute("params");
3356 var tags = params ? params.split("|") : [];
3357 var focus = this.getAttribute("newFocus");
3358 var template = this.getAttribute("newTemplate");
3359 var customFields = this.getAttribute("customFields");
3360 story.displayTiddler(null,title,template,false,null,null);
3361 var tiddlerElem = story.getTiddler(title);
3362 if(customFields)
3363 story.addCustomFields(tiddlerElem,customFields);
3364 var text = this.getAttribute("newText");
3365 if(typeof text == "string")
3366 story.getTiddlerField(title,"text").value = text.format([title]);
3367 for(var t=0;t<tags.length;t++)
3368 story.setTiddlerTag(title,tags[t],+1);
3369 story.focusTiddler(title,focus);
3370 return false;
3373 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3375 if(!readOnly) {
3376 params = paramString.parseParams("anon",null,true,false,false);
3377 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
3378 title = getParam(params,"title",title);
3379 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
3383 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3385 if(!readOnly) {
3386 params = paramString.parseParams("anon",null,true,false,false);
3387 var title = params[1] && params[1].name == "anon" ? params[1].value : config.macros.timeline.dateFormat;
3388 title = getParam(params,"title",title);
3389 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
3393 //--
3394 //-- Search macro
3395 //--
3397 config.macros.search.handler = function(place,macroName,params)
3399 var searchTimeout = null;
3400 var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick,"searchButton");
3401 var txt = createTiddlyElement(place,"input",null,"txtOptionInput searchField");
3402 if(params[0])
3403 txt.value = params[0];
3404 txt.onkeyup = this.onKeyPress;
3405 txt.onfocus = this.onFocus;
3406 txt.setAttribute("size",this.sizeTextbox);
3407 txt.setAttribute("accessKey",this.accessKey);
3408 txt.setAttribute("autocomplete","off");
3409 txt.setAttribute("lastSearchText","");
3410 if(config.browser.isSafari) {
3411 txt.setAttribute("type","search");
3412 txt.setAttribute("results","5");
3413 } else {
3414 txt.setAttribute("type","text");
3418 // Global because there's only ever one outstanding incremental search timer
3419 config.macros.search.timeout = null;
3421 config.macros.search.doSearch = function(txt)
3423 if(txt.value.length > 0) {
3424 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
3425 txt.setAttribute("lastSearchText",txt.value);
3429 config.macros.search.onClick = function(e)
3431 config.macros.search.doSearch(this.nextSibling);
3432 return false;
3435 config.macros.search.onKeyPress = function(ev)
3437 var e = ev ? ev : window.event;
3438 switch(e.keyCode) {
3439 case 13: // Ctrl-Enter
3440 case 10: // Ctrl-Enter on IE PC
3441 config.macros.search.doSearch(this);
3442 break;
3443 case 27: // Escape
3444 this.value = "";
3445 clearMessage();
3446 break;
3448 if(config.options.chkIncrementalSearch) {
3449 if(this.value.length > 2) {
3450 if(this.value != this.getAttribute("lastSearchText")) {
3451 if(config.macros.search.timeout)
3452 clearTimeout(config.macros.search.timeout);
3453 var txt = this;
3454 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3456 } else {
3457 if(config.macros.search.timeout)
3458 clearTimeout(config.macros.search.timeout);
3463 config.macros.search.onFocus = function(e)
3465 this.select();
3468 //--
3469 //-- Tabs macro
3470 //--
3472 config.macros.tabs.handler = function(place,macroName,params)
3474 var cookie = params[0];
3475 var numTabs = (params.length-1)/3;
3476 var wrapper = createTiddlyElement(null,"div",null,"tabsetWrapper " + cookie);
3477 var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
3478 tabset.setAttribute("cookie",cookie);
3479 var validTab = false;
3480 for(var t=0; t<numTabs; t++) {
3481 var label = params[t*3+1];
3482 var prompt = params[t*3+2];
3483 var content = params[t*3+3];
3484 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
3485 tab.setAttribute("tab",label);
3486 tab.setAttribute("content",content);
3487 tab.title = prompt;
3488 if(config.options[cookie] == label)
3489 validTab = true;
3491 if(!validTab)
3492 config.options[cookie] = params[1];
3493 place.appendChild(wrapper);
3494 this.switchTab(tabset,config.options[cookie]);
3497 config.macros.tabs.onClickTab = function(e)
3499 config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
3500 return false;
3503 config.macros.tabs.switchTab = function(tabset,tab)
3505 var cookie = tabset.getAttribute("cookie");
3506 var theTab = null;
3507 var nodes = tabset.childNodes;
3508 for(var t=0; t<nodes.length; t++) {
3509 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
3510 theTab = nodes[t];
3511 theTab.className = "tab tabSelected";
3512 } else {
3513 nodes[t].className = "tab tabUnselected";
3516 if(theTab) {
3517 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
3518 removeNode(tabset.nextSibling);
3519 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
3520 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
3521 var contentTitle = theTab.getAttribute("content");
3522 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
3523 if(cookie) {
3524 config.options[cookie] = tab;
3525 saveOptionCookie(cookie);
3530 //--
3531 //-- Tiddler toolbar
3532 //--
3534 // Create a toolbar command button
3535 config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
3537 if(typeof commandName != "string") {
3538 var c = null;
3539 for(var t in config.commands) {
3540 if(config.commands[t] == commandName)
3541 c = t;
3543 commandName = c;
3545 if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
3546 var command = config.commands[commandName];
3547 if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
3548 var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
3549 var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
3550 var cmd;
3551 switch(command.type) {
3552 case "popup":
3553 cmd = this.onClickPopup;
3554 break;
3555 case "command":
3556 default:
3557 cmd = this.onClickCommand;
3558 break;
3560 var btn = createTiddlyButton(null,text,tooltip,cmd);
3561 btn.setAttribute("commandName",commandName);
3562 btn.setAttribute("tiddler",tiddler.title);
3563 if(className)
3564 addClass(btn,className);
3565 place.appendChild(btn);
3570 config.macros.toolbar.isCommandEnabled = function(command,tiddler)
3572 var title = tiddler.title;
3573 var ro = tiddler.isReadOnly();
3574 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
3575 return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
3578 config.macros.toolbar.getCommandText = function(command,tiddler)
3580 return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
3583 config.macros.toolbar.getCommandTooltip = function(command,tiddler)
3585 return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
3588 config.macros.toolbar.onClickCommand = function(ev)
3590 var e = ev ? ev : window.event;
3591 e.cancelBubble = true;
3592 if(e.stopPropagation) e.stopPropagation();
3593 var command = config.commands[this.getAttribute("commandName")];
3594 return command.handler(e,this,this.getAttribute("tiddler"));
3597 config.macros.toolbar.onClickPopup = function(ev)
3599 var e = ev ? ev : window.event;
3600 e.cancelBubble = true;
3601 if(e.stopPropagation) e.stopPropagation();
3602 var popup = Popup.create(this);
3603 var command = config.commands[this.getAttribute("commandName")];
3604 var title = this.getAttribute("tiddler");
3605 var tiddler = store.fetchTiddler(title);
3606 popup.setAttribute("tiddler",title);
3607 command.handlePopup(popup,title);
3608 Popup.show();
3609 return false;
3612 // Invoke the first command encountered from a given place that is tagged with a specified class
3613 config.macros.toolbar.invokeCommand = function(place,className,event)
3615 var children = place.getElementsByTagName("a");
3616 for(var t=0; t<children.length; t++) {
3617 var c = children[t];
3618 if(hasClass(c,className) && c.getAttribute && c.getAttribute("commandName")) {
3619 if(c.onclick instanceof Function)
3620 c.onclick.call(c,event);
3621 break;
3626 config.macros.toolbar.onClickMore = function(ev)
3628 var e = this.nextSibling;
3629 e.style.display = "inline";
3630 removeNode(this);
3631 return false;
3634 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3636 for(var t=0; t<params.length; t++) {
3637 var c = params[t];
3638 switch(c) {
3639 case '>':
3640 var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
3641 addClass(btn,"moreCommand");
3642 var e = createTiddlyElement(place,"span",null,"moreCommand");
3643 e.style.display = "none";
3644 place = e;
3645 break;
3646 default:
3647 var className = "";
3648 switch(c.substr(0,1)) {
3649 case "+":
3650 className = "defaultCommand";
3651 c = c.substr(1);
3652 break;
3653 case "-":
3654 className = "cancelCommand";
3655 c = c.substr(1);
3656 break;
3658 if(c in config.commands)
3659 this.createCommand(place,c,tiddler,className);
3660 break;
3665 //--
3666 //-- Menu and toolbar commands
3667 //--
3669 config.commands.closeTiddler.handler = function(event,src,title)
3671 if(story.isDirty(title) && !readOnly) {
3672 if(!confirm(config.commands.cancelTiddler.warning.format([title])))
3673 return false;
3675 story.setDirty(title,false);
3676 story.closeTiddler(title,true);
3677 return false;
3680 config.commands.closeOthers.handler = function(event,src,title)
3682 story.closeAllTiddlers(title);
3683 return false;
3686 config.commands.editTiddler.handler = function(event,src,title)
3688 clearMessage();
3689 var tiddlerElem = story.getTiddler(title);
3690 var fields = tiddlerElem.getAttribute("tiddlyFields");
3691 story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
3692 story.focusTiddler(title,config.options.txtEditorFocus||"text");
3693 return false;
3696 config.commands.saveTiddler.handler = function(event,src,title)
3698 var newTitle = story.saveTiddler(title,event.shiftKey);
3699 if(newTitle)
3700 story.displayTiddler(null,newTitle);
3701 return false;
3704 config.commands.cancelTiddler.handler = function(event,src,title)
3706 if(story.hasChanges(title) && !readOnly) {
3707 if(!confirm(this.warning.format([title])))
3708 return false;
3710 story.setDirty(title,false);
3711 story.displayTiddler(null,title);
3712 return false;
3715 config.commands.deleteTiddler.handler = function(event,src,title)
3717 var deleteIt = true;
3718 if(config.options.chkConfirmDelete)
3719 deleteIt = confirm(this.warning.format([title]));
3720 if(deleteIt) {
3721 store.removeTiddler(title);
3722 story.closeTiddler(title,true);
3723 autoSaveChanges();
3725 return false;
3728 config.commands.permalink.handler = function(event,src,title)
3730 var t = encodeURIComponent(String.encodeTiddlyLink(title));
3731 if(window.location.hash != t)
3732 window.location.hash = t;
3733 return false;
3736 config.commands.references.handlePopup = function(popup,title)
3738 var references = store.getReferringTiddlers(title);
3739 var c = false;
3740 for(var r=0; r<references.length; r++) {
3741 if(references[r].title != title && !references[r].isTagged("excludeLists")) {
3742 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
3743 c = true;
3746 if(!c)
3747 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
3750 config.commands.jump.handlePopup = function(popup,title)
3752 story.forEachTiddler(function(title,element) {
3753 createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
3757 config.commands.syncing.handlePopup = function(popup,title)
3759 var tiddler = store.fetchTiddler(title);
3760 if(!tiddler)
3761 return;
3762 var serverType = tiddler.getServerType();
3763 var serverHost = tiddler.fields['server.host'];
3764 var serverWorkspace = tiddler.fields['server.workspace'];
3765 if(!serverWorkspace)
3766 serverWorkspace = "";
3767 if(serverType) {
3768 var e = createTiddlyElement(popup,"li",null,"popupMessage");
3769 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3770 } else {
3771 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
3773 if(serverType) {
3774 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3775 var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
3776 btn.setAttribute("tiddler",title);
3777 btn.setAttribute("server.type","");
3779 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
3780 createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
3781 var feeds = store.getTaggedTiddlers("systemServer","title");
3782 for(var t=0; t<feeds.length; t++) {
3783 var f = feeds[t];
3784 var feedServerType = store.getTiddlerSlice(f.title,"Type");
3785 if(!feedServerType)
3786 feedServerType = "file";
3787 var feedServerHost = store.getTiddlerSlice(f.title,"URL");
3788 if(!feedServerHost)
3789 feedServerHost = "";
3790 var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
3791 if(!feedServerWorkspace)
3792 feedServerWorkspace = "";
3793 var caption = f.title;
3794 if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
3795 caption = config.commands.syncing.currServerMarker + caption;
3796 } else {
3797 caption = config.commands.syncing.notCurrServerMarker + caption;
3799 btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
3800 btn.setAttribute("tiddler",title);
3801 btn.setAttribute("server.type",feedServerType);
3802 btn.setAttribute("server.host",feedServerHost);
3803 btn.setAttribute("server.workspace",feedServerWorkspace);
3807 config.commands.syncing.onChooseServer = function(e)
3809 var tiddler = this.getAttribute("tiddler");
3810 var serverType = this.getAttribute("server.type");
3811 if(serverType) {
3812 store.addTiddlerFields(tiddler,{
3813 'server.type': serverType,
3814 'server.host': this.getAttribute("server.host"),
3815 'server.workspace': this.getAttribute("server.workspace")
3817 } else {
3818 store.setValue(tiddler,'server',null);
3820 return false;
3823 config.commands.fields.handlePopup = function(popup,title)
3825 var tiddler = store.fetchTiddler(title);
3826 if(!tiddler)
3827 return;
3828 var fields = {};
3829 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
3830 var items = [];
3831 for(var t in fields) {
3832 items.push({field: t,value: fields[t]});
3834 items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
3835 if(items.length > 0)
3836 ListView.create(popup,items,this.listViewTemplate);
3837 else
3838 createTiddlyElement(popup,"div",null,null,this.emptyText);
3841 //--
3842 //-- Tiddler() object
3843 //--
3845 function Tiddler(title)
3847 this.title = title;
3848 this.text = "";
3849 this.modifier = null;
3850 this.created = new Date();
3851 this.modified = this.created;
3852 this.links = [];
3853 this.linksUpdated = false;
3854 this.tags = [];
3855 this.fields = {};
3856 return this;
3859 Tiddler.prototype.getLinks = function()
3861 if(this.linksUpdated==false)
3862 this.changed();
3863 return this.links;
3866 // Returns the fields that are inherited in string field:"value" field2:"value2" format
3867 Tiddler.prototype.getInheritedFields = function()
3869 var f = {};
3870 for(i in this.fields) {
3871 if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
3872 f[i] = this.fields[i];
3875 return String.encodeHashMap(f);
3878 // Increment the changeCount of a tiddler
3879 Tiddler.prototype.incChangeCount = function()
3881 var c = this.fields['changecount'];
3882 c = c ? parseInt(c) : 0;
3883 this.fields['changecount'] = String(c+1);
3886 // Clear the changeCount of a tiddler
3887 Tiddler.prototype.clearChangeCount = function()
3889 if(this.fields['changecount']) {
3890 delete this.fields['changecount'];
3894 Tiddler.prototype.doNotSave = function()
3896 return this.fields['doNotSave'];
3899 // Returns true if the tiddler has been updated since the tiddler was created or downloaded
3900 Tiddler.prototype.isTouched = function()
3902 var changeCount = this.fields['changecount'];
3903 if(changeCount === undefined)
3904 changeCount = 0;
3905 return changeCount > 0;
3908 // Return the tiddler as an RSS item
3909 Tiddler.prototype.toRssItem = function(uri)
3911 var s = [];
3912 s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
3913 s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
3914 for(var t=0; t<this.tags.length; t++)
3915 s.push("<category>" + this.tags[t] + "</category>");
3916 s.push("<link>" + uri + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
3917 s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
3918 return s.join("\n");
3921 // Format the text for storage in an RSS item
3922 Tiddler.prototype.saveToRss = function(uri)
3924 return "<item>\n" + this.toRssItem(uri) + "\n</item>";
3927 // Change the text and other attributes of a tiddler
3928 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
3930 this.assign(title,text,modifier,modified,tags,created,fields);
3931 this.changed();
3932 return this;
3935 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
3936 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
3938 if(title != undefined)
3939 this.title = title;
3940 if(text != undefined)
3941 this.text = text;
3942 if(modifier != undefined)
3943 this.modifier = modifier;
3944 if(modified != undefined)
3945 this.modified = modified;
3946 if(created != undefined)
3947 this.created = created;
3948 if(fields != undefined)
3949 this.fields = fields;
3950 if(tags != undefined)
3951 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
3952 else if(this.tags == undefined)
3953 this.tags = [];
3954 return this;
3957 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
3958 Tiddler.prototype.getTags = function()
3960 return String.encodeTiddlyLinkList(this.tags);
3963 // Test if a tiddler carries a tag
3964 Tiddler.prototype.isTagged = function(tag)
3966 return this.tags.indexOf(tag) != -1;
3969 // Static method to convert "\n" to newlines, "\s" to "\"
3970 Tiddler.unescapeLineBreaks = function(text)
3972 return text ? text.unescapeLineBreaks() : "";
3975 // Convert newlines to "\n", "\" to "\s"
3976 Tiddler.prototype.escapeLineBreaks = function()
3978 return this.text.escapeLineBreaks();
3981 // Updates the secondary information (like links[] array) after a change to a tiddler
3982 Tiddler.prototype.changed = function()
3984 this.links = [];
3985 var t = this.autoLinkWikiWords() ? 0 : 1;
3986 var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
3987 tiddlerLinkRegExp.lastIndex = 0;
3988 var formatMatch = tiddlerLinkRegExp.exec(this.text);
3989 while(formatMatch) {
3990 var lastIndex = tiddlerLinkRegExp.lastIndex;
3991 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
3992 // wikiWordLink
3993 if(formatMatch.index > 0) {
3994 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
3995 preRegExp.lastIndex = formatMatch.index-1;
3996 var preMatch = preRegExp.exec(this.text);
3997 if(preMatch.index != formatMatch.index-1)
3998 this.links.pushUnique(formatMatch[1]);
3999 } else {
4000 this.links.pushUnique(formatMatch[1]);
4003 else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
4004 this.links.pushUnique(formatMatch[3-t]);
4005 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
4006 this.links.pushUnique(formatMatch[4-t]);
4007 tiddlerLinkRegExp.lastIndex = lastIndex;
4008 formatMatch = tiddlerLinkRegExp.exec(this.text);
4010 this.linksUpdated = true;
4013 Tiddler.prototype.getSubtitle = function()
4015 var modifier = this.modifier;
4016 if(!modifier)
4017 modifier = config.messages.subtitleUnknown;
4018 var modified = this.modified;
4019 if(modified)
4020 modified = modified.toLocaleString();
4021 else
4022 modified = config.messages.subtitleUnknown;
4023 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
4026 Tiddler.prototype.isReadOnly = function()
4028 return readOnly;
4031 Tiddler.prototype.autoLinkWikiWords = function()
4033 return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
4036 Tiddler.prototype.generateFingerprint = function()
4038 return "0x" + Crypto.hexSha1Str(this.text);
4041 Tiddler.prototype.getServerType = function()
4043 var serverType = null;
4044 if(this.fields && this.fields['server.type'])
4045 serverType = this.fields['server.type'];
4046 if(!serverType)
4047 serverType = this.fields['wikiformat'];
4048 if(serverType && !config.adaptors[serverType])
4049 serverType = null;
4050 return serverType;
4053 Tiddler.prototype.getAdaptor = function()
4055 var serverType = this.getServerType();
4056 return serverType ? new config.adaptors[serverType] : null;
4059 //--
4060 //-- TiddlyWiki() object contains Tiddler()s
4061 //--
4063 function TiddlyWiki()
4065 var tiddlers = {}; // Hashmap by name of tiddlers
4066 this.tiddlersUpdated = false;
4067 this.namedNotifications = []; // Array of {name:,notify:} of notification functions
4068 this.notificationLevel = 0;
4069 this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
4070 this.clear = function() {
4071 tiddlers = {};
4072 this.setDirty(false);
4074 this.fetchTiddler = function(title) {
4075 var t = tiddlers[title];
4076 return t instanceof Tiddler ? t : null;
4078 this.deleteTiddler = function(title) {
4079 delete this.slices[title];
4080 delete tiddlers[title];
4082 this.addTiddler = function(tiddler) {
4083 delete this.slices[tiddler.title];
4084 tiddlers[tiddler.title] = tiddler;
4086 this.forEachTiddler = function(callback) {
4087 for(var t in tiddlers) {
4088 var tiddler = tiddlers[t];
4089 if(tiddler instanceof Tiddler)
4090 callback.call(this,t,tiddler);
4095 TiddlyWiki.prototype.setDirty = function(dirty)
4097 this.dirty = dirty;
4100 TiddlyWiki.prototype.isDirty = function()
4102 return this.dirty;
4105 TiddlyWiki.prototype.suspendNotifications = function()
4107 this.notificationLevel--;
4110 TiddlyWiki.prototype.resumeNotifications = function()
4112 this.notificationLevel++;
4115 // Invoke the notification handlers for a particular tiddler
4116 TiddlyWiki.prototype.notify = function(title,doBlanket)
4118 if(!this.notificationLevel) {
4119 for(var t=0; t<this.namedNotifications.length; t++) {
4120 var n = this.namedNotifications[t];
4121 if((n.name == null && doBlanket) || (n.name == title))
4122 n.notify(title);
4127 // Invoke the notification handlers for all tiddlers
4128 TiddlyWiki.prototype.notifyAll = function()
4130 if(!this.notificationLevel) {
4131 for(var t=0; t<this.namedNotifications.length; t++) {
4132 var n = this.namedNotifications[t];
4133 if(n.name)
4134 n.notify(n.name);
4139 // Add a notification handler to a tiddler
4140 TiddlyWiki.prototype.addNotification = function(title,fn)
4142 for(var i=0; i<this.namedNotifications.length; i++) {
4143 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
4144 return this;
4146 this.namedNotifications.push({name: title, notify: fn});
4147 return this;
4150 TiddlyWiki.prototype.removeTiddler = function(title)
4152 var tiddler = this.fetchTiddler(title);
4153 if(tiddler) {
4154 this.deleteTiddler(title);
4155 this.notify(title,true);
4156 this.setDirty(true);
4160 TiddlyWiki.prototype.tiddlerExists = function(title)
4162 var t = this.fetchTiddler(title);
4163 return t != undefined;
4166 TiddlyWiki.prototype.isShadowTiddler = function(title)
4168 return typeof config.shadowTiddlers[title] == "string";
4171 TiddlyWiki.prototype.getTiddler = function(title)
4173 var t = this.fetchTiddler(title);
4174 if(t != undefined)
4175 return t;
4176 else
4177 return null;
4180 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
4182 if(!title)
4183 return defaultText;
4184 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
4185 var section = null;
4186 if(pos != -1) {
4187 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
4188 title = title.substr(0,pos);
4190 pos = title.indexOf(config.textPrimitives.sliceSeparator);
4191 if(pos != -1) {
4192 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
4193 if(slice)
4194 return slice;
4196 var tiddler = this.fetchTiddler(title);
4197 if(tiddler) {
4198 if(!section)
4199 return tiddler.text;
4200 var re = new RegExp("(^!{1,6}" + section.escapeRegExp() + "[ \t]*\n)","mg");
4201 re.lastIndex = 0;
4202 var match = re.exec(tiddler.text);
4203 if(match) {
4204 var t = tiddler.text.substr(match.index+match[1].length);
4205 var re2 = /^!/mg;
4206 re2.lastIndex = 0;
4207 match = re2.exec(t); //# search for the next heading
4208 if(match)
4209 t = t.substr(0,match.index-1);//# don't include final \n
4210 return t;
4212 return defaultText;
4214 if(this.isShadowTiddler(title))
4215 return config.shadowTiddlers[title];
4216 if(defaultText != undefined)
4217 return defaultText;
4218 return null;
4221 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
4223 // @internal
4224 TiddlyWiki.prototype.calcAllSlices = function(title)
4226 var slices = {};
4227 var text = this.getTiddlerText(title,"");
4228 this.slicesRE.lastIndex = 0;
4229 do {
4230 var m = this.slicesRE.exec(text);
4231 if(m) {
4232 if(m[1])
4233 slices[m[1]] = m[2];
4234 else
4235 slices[m[3]] = m[4];
4237 } while(m);
4238 return slices;
4241 // Returns the slice of text of the given name
4242 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4244 var slices = this.slices[title];
4245 if(!slices) {
4246 slices = this.calcAllSlices(title);
4247 this.slices[title] = slices;
4249 return slices[sliceName];
4252 // Build an hashmap of the specified named slices of a tiddler
4253 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
4255 var r = {};
4256 for(var t=0; t<sliceNames.length; t++) {
4257 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4258 if(slice)
4259 r[sliceNames[t]] = slice;
4261 return r;
4264 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4266 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
4267 var text = this.getTiddlerText(title,null);
4268 if(text == null)
4269 return defaultText;
4270 var textOut = [];
4271 var lastPos = 0;
4272 do {
4273 var match = bracketRegExp.exec(text);
4274 if(match) {
4275 textOut.push(text.substr(lastPos,match.index-lastPos));
4276 if(match[1]) {
4277 if(depth <= 0)
4278 textOut.push(match[1]);
4279 else
4280 textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
4282 lastPos = match.index + match[0].length;
4283 } else {
4284 textOut.push(text.substr(lastPos));
4286 } while(match);
4287 return textOut.join("");
4290 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4292 var tiddler = this.fetchTiddler(title);
4293 if(tiddler) {
4294 var t = tiddler.tags.indexOf(tag);
4295 if(t != -1)
4296 tiddler.tags.splice(t,1);
4297 if(status)
4298 tiddler.tags.push(tag);
4299 tiddler.changed();
4300 this.incChangeCount(title);
4301 this.notify(title,true);
4302 this.setDirty(true);
4306 TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
4308 var tiddler = this.fetchTiddler(title);
4309 if(!tiddler)
4310 return;
4311 merge(tiddler.fields,fields);
4312 tiddler.changed();
4313 this.incChangeCount(title);
4314 this.notify(title,true);
4315 this.setDirty(true);
4318 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
4320 var tiddler = this.fetchTiddler(title);
4321 if(tiddler) {
4322 created = created ? created : tiddler.created; // Preserve created date
4323 this.deleteTiddler(title);
4324 } else {
4325 created = created ? created : modified;
4326 tiddler = new Tiddler();
4328 tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
4329 this.addTiddler(tiddler);
4330 if(clearChangeCount)
4331 tiddler.clearChangeCount();
4332 else
4333 tiddler.incChangeCount();
4334 if(title != newTitle)
4335 this.notify(title,true);
4336 this.notify(newTitle,true);
4337 this.setDirty(true);
4338 return tiddler;
4341 // Reset the sync status of a freshly synced tiddler
4342 TiddlyWiki.prototype.resetTiddler = function(title)
4344 var tiddler = this.fetchTiddler(title);
4345 if(tiddler) {
4346 tiddler.clearChangeCount();
4347 this.notify(title,true);
4348 this.setDirty(true);
4352 TiddlyWiki.prototype.incChangeCount = function(title)
4354 var tiddler = this.fetchTiddler(title);
4355 if(tiddler)
4356 tiddler.incChangeCount();
4359 TiddlyWiki.prototype.createTiddler = function(title)
4361 var tiddler = this.fetchTiddler(title);
4362 if(!tiddler) {
4363 tiddler = new Tiddler(title);
4364 this.addTiddler(tiddler);
4365 this.setDirty(true);
4367 return tiddler;
4370 // Load contents of a TiddlyWiki from an HTML DIV
4371 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
4373 this.idPrefix = idPrefix;
4374 var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
4375 if(!storeElem)
4376 return;
4377 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4378 this.setDirty(false);
4379 if(!noUpdate) {
4380 for(var i = 0;i<tiddlers.length; i++)
4381 tiddlers[i].changed();
4385 // Load contents of a TiddlyWiki from a string
4386 // Returns null if there's an error
4387 TiddlyWiki.prototype.importTiddlyWiki = function(text)
4389 var posDiv = locateStoreArea(text);
4390 if(!posDiv)
4391 return null;
4392 var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
4393 // Create the iframe
4394 var iframe = document.createElement("iframe");
4395 iframe.style.display = "none";
4396 document.body.appendChild(iframe);
4397 var doc = iframe.document;
4398 if(iframe.contentDocument)
4399 doc = iframe.contentDocument; // For NS6
4400 else if(iframe.contentWindow)
4401 doc = iframe.contentWindow.document; // For IE5.5 and IE6
4402 // Put the content in the iframe
4403 doc.open();
4404 doc.writeln(content);
4405 doc.close();
4406 // Load the content into a TiddlyWiki() object
4407 var storeArea = doc.getElementById("storeArea");
4408 this.loadFromDiv(storeArea,"store");
4409 // Get rid of the iframe
4410 iframe.parentNode.removeChild(iframe);
4411 return this;
4414 TiddlyWiki.prototype.updateTiddlers = function()
4416 this.tiddlersUpdated = true;
4417 this.forEachTiddler(function(title,tiddler) {
4418 tiddler.changed();
4422 // Return all tiddlers formatted as an HTML string
4423 TiddlyWiki.prototype.allTiddlersAsHtml = function()
4425 return store.getSaver().externalize(store);
4428 // Return an array of tiddlers matching a search regular expression
4429 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
4431 var candidates = this.reverseLookup("tags",excludeTag,!!match);
4432 var results = [];
4433 for(var t=0; t<candidates.length; t++) {
4434 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
4435 results.push(candidates[t]);
4437 if(!sortField)
4438 sortField = "title";
4439 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4440 return results;
4443 // Returns a list of all tags in use
4444 // excludeTag - if present, excludes tags that are themselves tagged with excludeTag
4445 // Returns an array of arrays where [tag][0] is the name of the tag and [tag][1] is the number of occurances
4446 TiddlyWiki.prototype.getTags = function(excludeTag)
4448 var results = [];
4449 this.forEachTiddler(function(title,tiddler) {
4450 for(var g=0; g<tiddler.tags.length; g++) {
4451 var tag = tiddler.tags[g];
4452 var n = true;
4453 for(var c=0; c<results.length; c++) {
4454 if(results[c][0] == tag) {
4455 n = false;
4456 results[c][1]++;
4459 if(n && excludeTag) {
4460 var t = store.fetchTiddler(tag);
4461 if(t && t.isTagged(excludeTag))
4462 n = false;
4464 if(n)
4465 results.push([tag,1]);
4468 results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
4469 return results;
4472 // Return an array of the tiddlers that are tagged with a given tag
4473 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
4475 return this.reverseLookup("tags",tag,true,sortField);
4478 // Return an array of the tiddlers that link to a given tiddler
4479 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
4481 if(!this.tiddlersUpdated)
4482 this.updateTiddlers();
4483 return this.reverseLookup("links",title,true,sortField);
4486 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
4487 // lookupMatch == true to match tiddlers, false to exclude tiddlers
4488 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
4490 var results = [];
4491 this.forEachTiddler(function(title,tiddler) {
4492 var f = !lookupMatch;
4493 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
4494 if(tiddler[lookupField][lookup] == lookupValue)
4495 f = lookupMatch;
4497 if(f)
4498 results.push(tiddler);
4500 if(!sortField)
4501 sortField = "title";
4502 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4503 return results;
4506 // Return the tiddlers as a sorted array
4507 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4509 var results = [];
4510 this.forEachTiddler(function(title,tiddler) {
4511 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4512 results.push(tiddler);
4514 if(field)
4515 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
4516 return results;
4519 // Return array of names of tiddlers that are referred to but not defined
4520 TiddlyWiki.prototype.getMissingLinks = function(sortField)
4522 if(!this.tiddlersUpdated)
4523 this.updateTiddlers();
4524 var results = [];
4525 this.forEachTiddler(function (title,tiddler) {
4526 if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
4527 return;
4528 for(var n=0; n<tiddler.links.length;n++) {
4529 var link = tiddler.links[n];
4530 if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
4531 results.pushUnique(link);
4534 results.sort();
4535 return results;
4538 // Return an array of names of tiddlers that are defined but not referred to
4539 TiddlyWiki.prototype.getOrphans = function()
4541 var results = [];
4542 this.forEachTiddler(function (title,tiddler) {
4543 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
4544 results.push(title);
4546 results.sort();
4547 return results;
4550 // Return an array of names of all the shadow tiddlers
4551 TiddlyWiki.prototype.getShadowed = function()
4553 var results = [];
4554 for(var t in config.shadowTiddlers) {
4555 if(typeof config.shadowTiddlers[t] == "string")
4556 results.push(t);
4558 results.sort();
4559 return results;
4562 // Return an array of tiddlers that have been touched since they were downloaded or created
4563 TiddlyWiki.prototype.getTouched = function()
4565 var results = [];
4566 this.forEachTiddler(function(title,tiddler) {
4567 if(tiddler.isTouched())
4568 results.push(tiddler);
4570 results.sort();
4571 return results;
4574 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
4575 TiddlyWiki.prototype.resolveTiddler = function(tiddler)
4577 var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
4578 return t instanceof Tiddler ? t : null;
4581 TiddlyWiki.prototype.getLoader = function()
4583 if(!this.loader)
4584 this.loader = new TW21Loader();
4585 return this.loader;
4588 TiddlyWiki.prototype.getSaver = function()
4590 if(!this.saver)
4591 this.saver = new TW21Saver();
4592 return this.saver;
4595 // Filter a list of tiddlers
4596 TiddlyWiki.prototype.filterTiddlers = function(filter)
4598 var results = [];
4599 if(filter) {
4600 var tiddler;
4601 var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4602 var match = re.exec(filter);
4603 while(match) {
4604 if(match[1] || match[4]) {
4605 var title = match[1] ? match[1] : match[4];
4606 tiddler = this.fetchTiddler(title);
4607 if(tiddler) {
4608 results.pushUnique(tiddler);
4609 } else if(store.isShadowTiddler(title)) {
4610 tiddler = new Tiddler();
4611 tiddler.set(title,store.getTiddlerText(title));
4612 results.pushUnique(tiddler);
4614 } else if(match[2]) {
4615 switch(match[2]) {
4616 case "tag":
4617 var matched = this.getTaggedTiddlers(match[3]);
4618 for(var m = 0; m < matched.length; m++)
4619 results.pushUnique(matched[m]);
4620 break;
4621 case "sort":
4622 results = this.sortTiddlers(results,match[3]);
4623 break;
4626 match = re.exec(filter);
4629 return results;
4632 // Sort a list of tiddlers
4633 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4635 var asc = +1;
4636 switch(field.substr(0,1)) {
4637 case "-":
4638 asc = -1;
4639 // Note: this fall-through is intentional
4640 case "+":
4641 field = field.substr(1);
4642 break;
4644 if(TiddlyWiki.standardFieldAccess[field])
4645 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4646 else
4647 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
4648 return tiddlers;
4650 // Returns true if path is a valid field name (path),
4651 // i.e. a sequence of identifiers, separated by '.'
4652 TiddlyWiki.isValidFieldName = function(name)
4654 var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
4655 return match && (match[0] == name);
4658 // Throws an exception when name is not a valid field name.
4659 TiddlyWiki.checkFieldName = function(name)
4661 if(!TiddlyWiki.isValidFieldName(name))
4662 throw config.messages.invalidFieldName.format([name]);
4665 function StringFieldAccess(n,readOnly)
4667 this.set = readOnly ?
4668 function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
4669 function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
4670 this.get = function(t) {return t[n];};
4673 function DateFieldAccess(n)
4675 this.set = function(t,v) {
4676 var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
4677 if(d != t[n]) {
4678 t[n] = d; return true;
4681 this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
4684 function LinksFieldAccess(n)
4686 this.set = function(t,v) {
4687 var s = (typeof v == "string") ? v.readBracketedList() : v;
4688 if(s.toString() != t[n].toString()) {
4689 t[n] = s; return true;
4692 this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
4695 TiddlyWiki.standardFieldAccess = {
4696 // The set functions return true when setting the data has changed the value.
4697 "title": new StringFieldAccess("title",true),
4698 // Handle the "tiddler" field name as the title
4699 "tiddler": new StringFieldAccess("title",true),
4700 "text": new StringFieldAccess("text"),
4701 "modifier": new StringFieldAccess("modifier"),
4702 "modified": new DateFieldAccess("modified"),
4703 "created": new DateFieldAccess("created"),
4704 "tags": new LinksFieldAccess("tags")
4707 TiddlyWiki.isStandardField = function(name)
4709 return TiddlyWiki.standardFieldAccess[name] != undefined;
4712 // Sets the value of the given field of the tiddler to the value.
4713 // Setting an ExtendedField's value to null or undefined removes the field.
4714 // Setting a namespace to undefined removes all fields of that namespace.
4715 // The fieldName is case-insensitive.
4716 // All values will be converted to a string value.
4717 TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
4719 TiddlyWiki.checkFieldName(fieldName);
4720 var t = this.resolveTiddler(tiddler);
4721 if(!t)
4722 return;
4723 fieldName = fieldName.toLowerCase();
4724 var isRemove = (value === undefined) || (value === null);
4725 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4726 if(accessor) {
4727 if(isRemove)
4728 // don't remove StandardFields
4729 return;
4730 var h = TiddlyWiki.standardFieldAccess[fieldName];
4731 if(!h.set(t,value))
4732 return;
4733 } else {
4734 var oldValue = t.fields[fieldName];
4735 if(isRemove) {
4736 if(oldValue !== undefined) {
4737 // deletes a single field
4738 delete t.fields[fieldName];
4739 } else {
4740 // no concrete value is defined for the fieldName
4741 // so we guess this is a namespace path.
4742 // delete all fields in a namespace
4743 var re = new RegExp('^'+fieldName+'\\.');
4744 var dirty = false;
4745 for(var n in t.fields) {
4746 if(n.match(re)) {
4747 delete t.fields[n];
4748 dirty = true;
4751 if(!dirty)
4752 return;
4754 } else {
4755 // the "normal" set case. value is defined (not null/undefined)
4756 // For convenience provide a nicer conversion Date->String
4757 value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
4758 if(oldValue == value)
4759 return;
4760 t.fields[fieldName] = value;
4763 // When we are here the tiddler/store really was changed.
4764 this.notify(t.title,true);
4765 if(!fieldName.match(/^temp\./))
4766 this.setDirty(true);
4769 // Returns the value of the given field of the tiddler.
4770 // The fieldName is case-insensitive.
4771 // Will only return String values (or undefined).
4772 TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
4774 var t = this.resolveTiddler(tiddler);
4775 if(!t)
4776 return undefined;
4777 fieldName = fieldName.toLowerCase();
4778 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4779 if(accessor) {
4780 return accessor.get(t);
4782 return t.fields[fieldName];
4785 // Calls the callback function for every field in the tiddler.
4786 // When callback function returns a non-false value the iteration stops
4787 // and that value is returned.
4788 // The order of the fields is not defined.
4789 // @param callback a function(tiddler,fieldName,value).
4790 TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
4792 var t = this.resolveTiddler(tiddler);
4793 if(!t)
4794 return undefined;
4795 var n,result;
4796 for(n in t.fields) {
4797 result = callback(t,n,t.fields[n]);
4798 if(result)
4799 return result;
4801 if(onlyExtendedFields)
4802 return undefined;
4803 for(n in TiddlyWiki.standardFieldAccess) {
4804 if(n == "tiddler")
4805 // even though the "title" field can also be referenced through the name "tiddler"
4806 // we only visit this field once.
4807 continue;
4808 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4809 if(result)
4810 return result;
4812 return undefined;
4815 //--
4816 //-- Story functions
4817 //--
4819 function Story(containerId,idPrefix)
4821 this.container = containerId;
4822 this.idPrefix = idPrefix;
4823 this.highlightRegExp = null;
4824 this.tiddlerId = function(title) {
4825 return this.idPrefix + title;
4827 this.containerId = function() {
4828 return this.container;
4832 Story.prototype.forEachTiddler = function(fn)
4834 var place = this.getContainer();
4835 if(!place)
4836 return;
4837 var e = place.firstChild;
4838 while(e) {
4839 var n = e.nextSibling;
4840 var title = e.getAttribute("tiddler");
4841 fn.call(this,title,e);
4842 e = n;
4846 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
4848 for(var t = titles.length-1;t>=0;t--)
4849 this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
4852 Story.prototype.displayTiddler = function(srcElement,tiddler,template,animate,unused,customFields,toggle)
4854 var title = (tiddler instanceof Tiddler)? tiddler.title : tiddler;
4855 var tiddlerElem = this.getTiddler(title);
4856 if(tiddlerElem) {
4857 if(toggle)
4858 this.closeTiddler(title,true);
4859 else
4860 this.refreshTiddler(title,template,false,customFields);
4861 } else {
4862 var place = this.getContainer();
4863 var before = this.positionTiddler(srcElement);
4864 tiddlerElem = this.createTiddler(place,before,title,template,customFields);
4866 if(srcElement && typeof srcElement !== "string") {
4867 if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
4868 anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
4869 else
4870 window.scrollTo(0,ensureVisible(tiddlerElem));
4874 Story.prototype.positionTiddler = function(srcElement)
4876 var place = this.getContainer();
4877 var before = null;
4878 if(typeof srcElement == "string") {
4879 switch(srcElement) {
4880 case "top":
4881 before = place.firstChild;
4882 break;
4883 case "bottom":
4884 before = null;
4885 break;
4887 } else {
4888 var after = this.findContainingTiddler(srcElement);
4889 if(after == null) {
4890 before = place.firstChild;
4891 } else if(after.nextSibling) {
4892 before = after.nextSibling;
4893 if(before.nodeType != 1)
4894 before = null;
4897 return before;
4900 Story.prototype.createTiddler = function(place,before,title,template,customFields)
4902 var tiddlerElem = createTiddlyElement(null,"div",this.tiddlerId(title),"tiddler");
4903 tiddlerElem.setAttribute("refresh","tiddler");
4904 if(customFields)
4905 tiddlerElem.setAttribute("tiddlyFields",customFields);
4906 place.insertBefore(tiddlerElem,before);
4907 var defaultText = null;
4908 if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
4909 defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
4910 this.refreshTiddler(title,template,false,customFields,defaultText);
4911 return tiddlerElem;
4914 Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
4916 var tiddler = new Tiddler(title);
4917 tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
4918 var serverType = tiddler.getServerType();
4919 var host = tiddler.fields['server.host'];
4920 var workspace = tiddler.fields['server.workspace'];
4921 if(!serverType || !host)
4922 return null;
4923 var sm = new SyncMachine(serverType,{
4924 start: function() {
4925 return this.openHost(host,"openWorkspace");
4927 openWorkspace: function() {
4928 return this.openWorkspace(workspace,"getTiddler");
4930 getTiddler: function() {
4931 return this.getTiddler(title,"onGetTiddler");
4933 onGetTiddler: function(context) {
4934 var tiddler = context.tiddler;
4935 if(tiddler && tiddler.text) {
4936 var downloaded = new Date();
4937 if(!tiddler.created)
4938 tiddler.created = downloaded;
4939 if(!tiddler.modified)
4940 tiddler.modified = tiddler.created;
4941 store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
4942 autoSaveChanges();
4944 delete this;
4945 return true;
4947 error: function(message) {
4948 displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
4951 sm.go();
4952 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
4955 Story.prototype.chooseTemplateForTiddler = function(title,template)
4957 if(!template)
4958 template = DEFAULT_VIEW_TEMPLATE;
4959 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
4960 template = config.tiddlerTemplates[template];
4961 return template;
4964 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
4966 return store.getRecursiveTiddlerText(template,null,10);
4969 Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
4971 var tiddlerElem = this.getTiddler(title);
4972 if(tiddlerElem) {
4973 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
4974 return tiddlerElem;
4975 template = this.chooseTemplateForTiddler(title,template);
4976 var currTemplate = tiddlerElem.getAttribute("template");
4977 if((template != currTemplate) || force) {
4978 var tiddler = store.getTiddler(title);
4979 if(!tiddler) {
4980 tiddler = new Tiddler();
4981 if(store.isShadowTiddler(title)) {
4982 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
4983 } else {
4984 var text = template=="EditTemplate" ?
4985 config.views.editor.defaultText.format([title]) :
4986 config.views.wikified.defaultText.format([title]);
4987 text = defaultText ? defaultText : text;
4988 var fields = customFields ? customFields.decodeHashMap() : null;
4989 tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
4992 tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
4993 tiddlerElem.setAttribute("tiddler",title);
4994 tiddlerElem.setAttribute("template",template);
4995 tiddlerElem.onmouseover = this.onTiddlerMouseOver;
4996 tiddlerElem.onmouseout = this.onTiddlerMouseOut;
4997 tiddlerElem.ondblclick = this.onTiddlerDblClick;
4998 tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
4999 tiddlerElem.innerHTML = this.getTemplateForTiddler(title,template,tiddler);
5000 applyHtmlMacros(tiddlerElem,tiddler);
5001 if(store.getTaggedTiddlers(title).length > 0)
5002 addClass(tiddlerElem,"isTag");
5003 else
5004 removeClass(tiddlerElem,"isTag");
5005 if(store.tiddlerExists(title)) {
5006 removeClass(tiddlerElem,"shadow");
5007 removeClass(tiddlerElem,"missing");
5008 } else {
5009 addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow" : "missing");
5011 if(customFields)
5012 this.addCustomFields(tiddlerElem,customFields);
5013 forceReflow();
5016 return tiddlerElem;
5019 Story.prototype.addCustomFields = function(place,customFields)
5021 var fields = customFields.decodeHashMap();
5022 var w = document.createElement("div");
5023 w.style.display = "none";
5024 place.appendChild(w);
5025 for(var t in fields) {
5026 var e = document.createElement("input");
5027 e.setAttribute("type","text");
5028 e.setAttribute("value",fields[t]);
5029 w.appendChild(e);
5030 e.setAttribute("edit",t);
5034 Story.prototype.refreshAllTiddlers = function(force)
5036 var place = this.getContainer();
5037 var e = place.firstChild;
5038 if(!e)
5039 return;
5040 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
5041 while((e = e.nextSibling) != null)
5042 this.refreshTiddler(e.getAttribute("tiddler"),force ? null : e.getAttribute("template"),true);
5045 Story.prototype.onTiddlerMouseOver = function(e)
5047 if(window.addClass instanceof Function)
5048 addClass(this,"selected");
5051 Story.prototype.onTiddlerMouseOut = function(e)
5053 if(window.removeClass instanceof Function)
5054 removeClass(this,"selected");
5057 Story.prototype.onTiddlerDblClick = function(ev)
5059 var e = ev ? ev : window.event;
5060 var theTarget = resolveTarget(e);
5061 if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
5062 if(document.selection && document.selection.empty)
5063 document.selection.empty();
5064 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5065 e.cancelBubble = true;
5066 if(e.stopPropagation) e.stopPropagation();
5067 return true;
5068 } else {
5069 return false;
5073 Story.prototype.onTiddlerKeyPress = function(ev)
5075 var e = ev ? ev : window.event;
5076 clearMessage();
5077 var consume = false;
5078 var title = this.getAttribute("tiddler");
5079 var target = resolveTarget(e);
5080 switch(e.keyCode) {
5081 case 9: // Tab
5082 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
5083 replaceSelection(target,String.fromCharCode(9));
5084 consume = true;
5086 if(config.isOpera) {
5087 target.onblur = function() {
5088 this.focus();
5089 this.onblur = null;
5092 break;
5093 case 13: // Ctrl-Enter
5094 case 10: // Ctrl-Enter on IE PC
5095 case 77: // Ctrl-Enter is "M" on some platforms
5096 if(e.ctrlKey) {
5097 blurElement(this);
5098 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
5099 consume = true;
5101 break;
5102 case 27: // Escape
5103 blurElement(this);
5104 config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
5105 consume = true;
5106 break;
5108 e.cancelBubble = consume;
5109 if(consume) {
5110 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
5111 e.returnValue = true; // Cancel The Event in IE
5112 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
5114 return !consume;
5117 Story.prototype.getTiddlerField = function(title,field)
5119 var tiddlerElem = this.getTiddler(title);
5120 var e = null;
5121 if(tiddlerElem != null) {
5122 var children = tiddlerElem.getElementsByTagName("*");
5123 for(var t=0; t<children.length; t++) {
5124 var c = children[t];
5125 if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
5126 if(!e)
5127 e = c;
5128 if(c.getAttribute("edit") == field)
5129 e = c;
5133 return e;
5136 Story.prototype.focusTiddler = function(title,field)
5138 var e = this.getTiddlerField(title,field);
5139 if(e) {
5140 e.focus();
5141 e.select();
5145 Story.prototype.blurTiddler = function(title)
5147 var tiddlerElem = this.getTiddler(title);
5148 if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
5149 tiddlerElem.focus();
5150 tiddlerElem.blur();
5154 Story.prototype.setTiddlerField = function(title,tag,mode,field)
5156 var c = story.getTiddlerField(title,field);
5158 var tags = c.value.readBracketedList();
5159 tags.setItem(tag,mode);
5160 c.value = String.encodeTiddlyLinkList(tags);
5163 Story.prototype.setTiddlerTag = function(title,tag,mode)
5165 Story.prototype.setTiddlerField(title,tag,mode,"tags");
5168 Story.prototype.closeTiddler = function(title,animate,unused)
5170 var tiddlerElem = this.getTiddler(title);
5171 if(tiddlerElem != null) {
5172 clearMessage();
5173 this.scrubTiddler(tiddlerElem);
5174 if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
5175 anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
5176 else {
5177 removeNode(tiddlerElem);
5178 forceReflow();
5183 Story.prototype.scrubTiddler = function(tiddlerElem)
5185 tiddlerElem.id = null;
5188 Story.prototype.setDirty = function(title,dirty)
5190 var tiddlerElem = this.getTiddler(title);
5191 if(tiddlerElem != null)
5192 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
5195 Story.prototype.isDirty = function(title)
5197 var tiddlerElem = this.getTiddler(title);
5198 if(tiddlerElem != null)
5199 return tiddlerElem.getAttribute("dirty") == "true";
5200 return null;
5203 Story.prototype.areAnyDirty = function()
5205 var r = false;
5206 this.forEachTiddler(function(title,element) {
5207 if(this.isDirty(title))
5208 r = true;
5210 return r;
5213 Story.prototype.closeAllTiddlers = function(exclude)
5215 clearMessage();
5216 this.forEachTiddler(function(title,element) {
5217 if((title != exclude) && element.getAttribute("dirty") != "true")
5218 this.closeTiddler(title);
5220 window.scrollTo(0,ensureVisible(this.container));
5223 Story.prototype.isEmpty = function()
5225 var place = this.getContainer();
5226 return place && place.firstChild == null;
5229 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
5231 this.closeAllTiddlers();
5232 highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
5233 var matches = store.search(highlightHack,"title","excludeSearch");
5234 this.displayTiddlers(null,matches);
5235 highlightHack = null;
5236 var q = useRegExp ? "/" : "'";
5237 if(matches.length > 0)
5238 displayMessage(config.macros.search.successMsg.format([matches.length.toString(),q + text + q]));
5239 else
5240 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5243 Story.prototype.findContainingTiddler = function(e)
5245 while(e && !hasClass(e,"tiddler"))
5246 e = e.parentNode;
5247 return e;
5250 Story.prototype.gatherSaveFields = function(e,fields)
5252 if(e && e.getAttribute) {
5253 var f = e.getAttribute("edit");
5254 if(f)
5255 fields[f] = e.value.replace(/\r/mg,"");
5256 if(e.hasChildNodes()) {
5257 var c = e.childNodes;
5258 for(var t=0; t<c.length; t++)
5259 this.gatherSaveFields(c[t],fields);
5264 Story.prototype.hasChanges = function(title)
5266 var e = this.getTiddler(title);
5267 if(e != null) {
5268 var fields = {};
5269 this.gatherSaveFields(e,fields);
5270 var tiddler = store.fetchTiddler(title);
5271 if(!tiddler)
5272 return false;
5273 for(var n in fields) {
5274 if(store.getValue(title,n) != fields[n])
5275 return true;
5278 return false;
5281 Story.prototype.saveTiddler = function(title,minorUpdate)
5283 var tiddlerElem = this.getTiddler(title);
5284 if(tiddlerElem != null) {
5285 var fields = {};
5286 this.gatherSaveFields(tiddlerElem,fields);
5287 var newTitle = fields.title ? fields.title : title;
5288 if(!store.tiddlerExists(newTitle))
5289 newTitle = newTitle.trim();
5290 if(store.tiddlerExists(newTitle) && newTitle != title) {
5291 if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
5292 return null;
5294 if(newTitle != title)
5295 this.closeTiddler(newTitle,false);
5296 tiddlerElem.id = this.tiddlerId(newTitle);
5297 tiddlerElem.setAttribute("tiddler",newTitle);
5298 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
5299 tiddlerElem.setAttribute("dirty","false");
5300 if(config.options.chkForceMinorUpdate)
5301 minorUpdate = !minorUpdate;
5302 if(!store.tiddlerExists(newTitle))
5303 minorUpdate = false;
5304 var newDate = new Date();
5305 var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : (newTitle!=title && store.tiddlerExists(title) ? store.fetchTiddler(title).fields : {});
5306 for(var n in fields) {
5307 if(!TiddlyWiki.isStandardField(n))
5308 extendedFields[n] = fields[n];
5310 var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
5311 autoSaveChanges(null,[tiddler]);
5312 return newTitle;
5314 return null;
5317 Story.prototype.permaView = function()
5319 var links = [];
5320 this.forEachTiddler(function(title,element) {
5321 links.push(String.encodeTiddlyLink(title));
5323 var t = encodeURIComponent(links.join(" "));
5324 if(t == "")
5325 t = "#";
5326 if(window.location.hash != t)
5327 window.location.hash = t;
5330 Story.prototype.switchTheme = function(theme)
5332 if(safeMode)
5333 return;
5335 isAvailable = function(title) {
5336 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5337 if(s!=-1)
5338 title = title.substr(0,s);
5339 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5342 getSlice = function(theme,slice) {
5343 var r;
5344 if(readOnly)
5345 r = store.getTiddlerSlice(theme,slice+"ReadOnly") || store.getTiddlerSlice(theme,"Web"+slice);
5346 r = r || store.getTiddlerSlice(theme,slice);
5347 if(r && r.indexOf(config.textPrimitives.sectionSeparator)==0)
5348 r = theme + r;
5349 return isAvailable(r) ? r : slice;
5352 replaceNotification = function(i,name,theme,slice) {
5353 var newName = getSlice(theme,slice);
5354 if(name!=newName && store.namedNotifications[i].name==name) {
5355 store.namedNotifications[i].name = newName;
5356 return newName;
5358 return name;
5361 var pt = config.refresherData.pageTemplate;
5362 var vi = DEFAULT_VIEW_TEMPLATE;
5363 var vt = config.tiddlerTemplates[vi];
5364 var ei = DEFAULT_EDIT_TEMPLATE;
5365 var et = config.tiddlerTemplates[ei];
5367 for(var i=0; i<config.notifyTiddlers.length; i++) {
5368 var name = config.notifyTiddlers[i].name;
5369 switch(name) {
5370 case "PageTemplate":
5371 config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
5372 break;
5373 case "StyleSheet":
5374 removeStyleSheet(config.refresherData.styleSheet);
5375 config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
5376 break;
5377 case "ColorPalette":
5378 config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
5379 break;
5380 default:
5381 break;
5384 config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate");
5385 config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate");
5386 if(!startingUp) {
5387 if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
5388 refreshAll();
5389 story.refreshAllTiddlers(true);
5390 } else {
5391 setStylesheet(store.getRecursiveTiddlerText(config.refresherData.styleSheet,"",10),config.refreshers.styleSheet);
5393 config.options.txtTheme = theme;
5394 saveOptionCookie("txtTheme");
5398 Story.prototype.getTiddler = function(title)
5400 return document.getElementById(this.tiddlerId(title));
5403 Story.prototype.getContainer = function()
5405 return document.getElementById(this.containerId());
5408 //--
5409 //-- Backstage
5410 //--
5412 var backstage = {
5413 area: null,
5414 toolbar: null,
5415 button: null,
5416 showButton: null,
5417 hideButton: null,
5418 cloak: null,
5419 panel: null,
5420 panelBody: null,
5421 panelFooter: null,
5422 currTabName: null,
5423 currTabElem: null,
5424 content: null,
5426 init: function() {
5427 var cmb = config.messages.backstage;
5428 this.area = document.getElementById("backstageArea");
5429 this.toolbar = document.getElementById("backstageToolbar");
5430 this.button = document.getElementById("backstageButton");
5431 this.button.style.display = "block";
5432 var t = cmb.open.text + " " + glyph("bentArrowLeft");
5433 this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
5434 function (e) {backstage.show(); return false;},null,"backstageShow");
5435 t = glyph("bentArrowRight") + " " + cmb.close.text;
5436 this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
5437 function (e) {backstage.hide(); return false;},null,"backstageHide");
5438 this.cloak = document.getElementById("backstageCloak");
5439 this.panel = document.getElementById("backstagePanel");
5440 this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
5441 this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
5442 this.cloak.onmousedown = function(e) {
5443 backstage.switchTab(null);
5445 createTiddlyText(this.toolbar,cmb.prompt);
5446 for(t=0; t<config.backstageTasks.length; t++) {
5447 var taskName = config.backstageTasks[t];
5448 var task = config.tasks[taskName];
5449 var handler = task.action ? this.onClickCommand : this.onClickTab;
5450 var text = task.text + (task.action ? "" : glyph("downTriangle"));
5451 var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
5452 btn.setAttribute("task",taskName);
5453 addClass(btn,task.action ? "backstageAction" : "backstageTask");
5455 this.content = document.getElementById("contentWrapper");
5456 if(config.options.chkBackstage)
5457 this.show();
5458 else
5459 this.hide();
5462 isVisible: function() {
5463 return this.area ? this.area.style.display == "block" : false;
5466 show: function() {
5467 this.area.style.display = "block";
5468 if(anim && config.options.chkAnimate) {
5469 backstage.toolbar.style.left = findWindowWidth() + "px";
5470 var p = [
5471 {style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
5473 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
5474 } else {
5475 backstage.area.style.left = "0px";
5477 this.showButton.style.display = "none";
5478 this.hideButton.style.display = "block";
5479 config.options.chkBackstage = true;
5480 saveOptionCookie("chkBackstage");
5481 addClass(this.content,"backstageVisible");
5484 hide: function() {
5485 if(this.currTabElem) {
5486 this.switchTab(null);
5487 } else {
5488 backstage.toolbar.style.left = "0px";
5489 if(anim && config.options.chkAnimate) {
5490 var p = [
5491 {style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
5493 var c = function(element,properties) {backstage.area.style.display = "none";};
5494 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
5495 } else {
5496 this.area.style.display = "none";
5498 this.showButton.style.display = "block";
5499 this.hideButton.style.display = "none";
5500 config.options.chkBackstage = false;
5501 saveOptionCookie("chkBackstage");
5502 removeClass(this.content,"backstageVisible");
5506 onClickCommand: function(e) {
5507 var task = config.tasks[this.getAttribute("task")];
5508 displayMessage(task);
5509 if(task.action) {
5510 backstage.switchTab(null);
5511 task.action();
5513 return false;
5516 onClickTab: function(e) {
5517 backstage.switchTab(this.getAttribute("task"));
5518 return false;
5521 // Switch to a given tab, or none if null is passed
5522 switchTab: function(tabName) {
5523 var tabElem = null;
5524 var e = this.toolbar.firstChild;
5525 while(e)
5527 if(e.getAttribute && e.getAttribute("task") == tabName)
5528 tabElem = e;
5529 e = e.nextSibling;
5531 if(tabName == backstage.currTabName)
5532 return;
5533 if(backstage.currTabElem) {
5534 removeClass(this.currTabElem,"backstageSelTab");
5536 if(tabElem && tabName) {
5537 backstage.preparePanel();
5538 addClass(tabElem,"backstageSelTab");
5539 var task = config.tasks[tabName];
5540 wikify(task.content,backstage.panelBody,null,null);
5541 backstage.showPanel();
5542 } else if(backstage.currTabElem) {
5543 backstage.hidePanel();
5545 backstage.currTabName = tabName;
5546 backstage.currTabElem = tabElem;
5549 isPanelVisible: function() {
5550 return backstage.panel ? backstage.panel.style.display == "block" : false;
5553 preparePanel: function() {
5554 backstage.cloak.style.height = findWindowHeight() + "px";
5555 backstage.cloak.style.display = "block";
5556 removeChildren(backstage.panelBody);
5557 return backstage.panelBody;
5560 showPanel: function() {
5561 backstage.panel.style.display = "block";
5562 if(anim && config.options.chkAnimate) {
5563 backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
5564 var p = [
5565 {style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
5567 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
5568 } else {
5569 backstage.panel.style.top = "0px";
5571 return backstage.panelBody;
5574 hidePanel: function() {
5575 backstage.currTabName = null;
5576 backstage.currTabElem = null;
5577 if(anim && config.options.chkAnimate) {
5578 var p = [
5579 {style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
5580 {style: "display", atEnd: "none"}
5582 var c = function(element,properties) {backstage.cloak.style.display = "none";};
5583 anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
5584 } else {
5585 backstage.panel.style.display = "none";
5586 backstage.cloak.style.display = "none";
5591 config.macros.backstage = {};
5593 config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5595 var backstageTask = config.tasks[params[0]];
5596 if(backstageTask)
5597 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5600 //--
5601 //-- ImportTiddlers macro
5602 //--
5604 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5606 if(readOnly) {
5607 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
5608 return;
5610 var w = new Wizard();
5611 w.createWizard(place,this.wizardTitle);
5612 this.restart(w);
5615 config.macros.importTiddlers.onCancel = function(e)
5617 var wizard = new Wizard(this);
5618 var place = wizard.clear();
5619 config.macros.importTiddlers.restart(wizard);
5620 return false;
5623 config.macros.importTiddlers.onClose = function(e)
5625 backstage.hidePanel();
5626 return false;
5629 config.macros.importTiddlers.restart = function(wizard)
5631 wizard.addStep(this.step1Title,this.step1Html);
5632 var s = wizard.getElement("selTypes");
5633 for(var t in config.adaptors) {
5634 var e = createTiddlyElement(s,"option",null,null,config.adaptors[t].serverLabel ? config.adaptors[t].serverLabel : t);
5635 e.value = t;
5637 if(config.defaultAdaptor)
5638 s.value = config.defaultAdaptor;
5639 s = wizard.getElement("selFeeds");
5640 var feeds = this.getFeeds();
5641 for(t in feeds) {
5642 e = createTiddlyElement(s,"option",null,null,t);
5643 e.value = t;
5645 wizard.setValue("feeds",feeds);
5646 s.onchange = config.macros.importTiddlers.onFeedChange;
5647 var fileInput = wizard.getElement("txtBrowse");
5648 fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
5649 fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
5650 wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
5653 config.macros.importTiddlers.getFeeds = function()
5655 var feeds = {};
5656 var tagged = store.getTaggedTiddlers("systemServer","title");
5657 for(var t=0; t<tagged.length; t++) {
5658 var title = tagged[t].title;
5659 var serverType = store.getTiddlerSlice(title,"Type");
5660 if(!serverType)
5661 serverType = "file";
5662 feeds[title] = {title: title,
5663 url: store.getTiddlerSlice(title,"URL"),
5664 workspace: store.getTiddlerSlice(title,"Workspace"),
5665 workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
5666 tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
5667 serverType: serverType,
5668 description: store.getTiddlerSlice(title,"Description")};
5670 return feeds;
5673 config.macros.importTiddlers.onFeedChange = function(e)
5675 var wizard = new Wizard(this);
5676 var selTypes = wizard.getElement("selTypes");
5677 var fileInput = wizard.getElement("txtPath");
5678 var feeds = wizard.getValue("feeds");
5679 var f = feeds[this.value];
5680 if(f) {
5681 selTypes.value = f.serverType;
5682 fileInput.value = f.url;
5683 wizard.setValue("feedName",f.serverType);
5684 wizard.setValue("feedHost",f.url);
5685 wizard.setValue("feedWorkspace",f.workspace);
5686 wizard.setValue("feedWorkspaceList",f.workspaceList);
5687 wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
5689 return false;
5692 config.macros.importTiddlers.onBrowseChange = function(e)
5694 var wizard = new Wizard(this);
5695 var fileInput = wizard.getElement("txtPath");
5696 fileInput.value = "file://" + this.value;
5697 var serverType = wizard.getElement("selTypes");
5698 serverType.value = "file";
5699 return false;
5702 config.macros.importTiddlers.onOpen = function(e)
5704 var wizard = new Wizard(this);
5705 var fileInput = wizard.getElement("txtPath");
5706 var url = fileInput.value;
5707 var serverType = wizard.getElement("selTypes").value || config.defaultAdaptor;
5708 var adaptor = new config.adaptors[serverType];
5709 wizard.setValue("adaptor",adaptor);
5710 wizard.setValue("serverType",serverType);
5711 wizard.setValue("host",url);
5712 var ret = adaptor.openHost(url,null,wizard,config.macros.importTiddlers.onOpenHost);
5713 if(ret !== true)
5714 displayMessage(ret);
5715 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
5716 return false;
5719 config.macros.importTiddlers.onOpenHost = function(context,wizard)
5721 var adaptor = wizard.getValue("adaptor");
5722 if(context.status !== true)
5723 displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
5724 var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
5725 if(ret !== true)
5726 displayMessage(ret);
5727 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
5730 config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
5732 if(context.status !== true)
5733 displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
5734 wizard.setValue("context",context);
5735 var workspace = wizard.getValue("feedWorkspace");
5736 if(!workspace && context.workspaces.length==1)
5737 workspace = context.workspaces[0].title;
5738 if(workspace) {
5739 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5740 if(ret !== true)
5741 displayMessage(ret);
5742 wizard.setValue("workspace",workspace);
5743 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5744 return;
5746 wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
5747 var s = wizard.getElement("selWorkspace");
5748 s.onchange = config.macros.importTiddlers.onWorkspaceChange;
5749 for(var t=0; t<context.workspaces.length; t++) {
5750 var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
5751 e.value = context.workspaces[t].title;
5753 var workspaceList = wizard.getValue("feedWorkspaceList");
5754 if(workspaceList) {
5755 var list = workspaceList.parseParams("workspace",null,false,true);
5756 for(var n=1; n<list.length; n++) {
5757 if(context.workspaces.findByField("title",list[n].value) == null) {
5758 e = createTiddlyElement(s,"option",null,null,list[n].value);
5759 e.value = list[n].value;
5763 if(workspace) {
5764 t = wizard.getElement("txtWorkspace");
5765 t.value = workspace;
5767 wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
5770 config.macros.importTiddlers.onWorkspaceChange = function(e)
5772 var wizard = new Wizard(this);
5773 var t = wizard.getElement("txtWorkspace");
5774 t.value = this.value;
5775 this.selectedIndex = 0;
5776 return false;
5779 config.macros.importTiddlers.onChooseWorkspace = function(e)
5781 var wizard = new Wizard(this);
5782 var adaptor = wizard.getValue("adaptor");
5783 var workspace = wizard.getElement("txtWorkspace").value;
5784 wizard.setValue("workspace",workspace);
5785 var context = wizard.getValue("context");
5786 var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
5787 if(ret !== true)
5788 displayMessage(ret);
5789 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
5790 return false;
5793 config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
5795 if(context.status !== true)
5796 displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
5797 var adaptor = wizard.getValue("adaptor");
5798 var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
5799 if(ret !== true)
5800 displayMessage(ret);
5801 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
5804 config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
5806 if(context.status !== true) {
5807 wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.errorGettingTiddlerList);
5808 return;
5810 // Extract data for the listview
5811 var listedTiddlers = [];
5812 if(context.tiddlers) {
5813 for(var n=0; n<context.tiddlers.length; n++) {
5814 var tiddler = context.tiddlers[n];
5815 listedTiddlers.push({
5816 title: tiddler.title,
5817 modified: tiddler.modified,
5818 modifier: tiddler.modifier,
5819 text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
5820 tags: tiddler.tags,
5821 size: tiddler.text ? tiddler.text.length : 0,
5822 tiddler: tiddler
5826 listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
5827 // Display the listview
5828 wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
5829 var markList = wizard.getElement("markList");
5830 var listWrapper = document.createElement("div");
5831 markList.parentNode.insertBefore(listWrapper,markList);
5832 var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
5833 wizard.setValue("listView",listView);
5834 var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
5835 txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
5836 wizard.setButtons([
5837 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
5838 {caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
5842 config.macros.importTiddlers.generateSystemServerName = function(wizard)
5844 var serverType = wizard.getValue("serverType");
5845 var host = wizard.getValue("host");
5846 var workspace = wizard.getValue("workspace");
5847 var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
5848 return pattern.format([serverType,host,workspace]);
5851 config.macros.importTiddlers.saveServerTiddler = function(wizard)
5853 var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
5854 if(store.tiddlerExists(txtSaveTiddler)) {
5855 if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
5856 return;
5857 store.suspendNotifications();
5858 store.removeTiddler(txtSaveTiddler);
5859 store.resumeNotifications();
5861 var serverType = wizard.getValue("serverType");
5862 var host = wizard.getValue("host");
5863 var workspace = wizard.getValue("workspace");
5864 var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
5865 store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
5868 config.macros.importTiddlers.doImport = function(e)
5870 var wizard = new Wizard(this);
5871 if(wizard.getElement("chkSave").checked)
5872 config.macros.importTiddlers.saveServerTiddler(wizard);
5873 var chkSync = wizard.getElement("chkSync").checked;
5874 wizard.setValue("sync",chkSync);
5875 var listView = wizard.getValue("listView");
5876 var rowNames = ListView.getSelectedRows(listView);
5877 var adaptor = wizard.getValue("adaptor");
5878 var overwrite = new Array();
5879 var t;
5880 for(t=0; t<rowNames.length; t++) {
5881 if(store.tiddlerExists(rowNames[t]))
5882 overwrite.push(rowNames[t]);
5884 if(overwrite.length > 0) {
5885 if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
5886 return false;
5888 wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
5889 for(t=0; t<rowNames.length; t++) {
5890 var link = document.createElement("div");
5891 createTiddlyLink(link,rowNames[t],true);
5892 var place = wizard.getElement("markReport");
5893 place.parentNode.insertBefore(link,place);
5895 wizard.setValue("remainingImports",rowNames.length);
5896 wizard.setButtons([
5897 {caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
5898 ],config.macros.importTiddlers.statusDoingImport);
5899 for(t=0; t<rowNames.length; t++) {
5900 var context = {};
5901 context.allowSynchronous = true;
5902 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
5904 return false;
5907 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
5909 if(!context.status)
5910 displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
5911 var tiddler = context.tiddler;
5912 store.suspendNotifications();
5913 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
5914 if(!wizard.getValue("sync")) {
5915 store.setValue(tiddler.title,'server',null);
5917 store.resumeNotifications();
5918 if(!context.isSynchronous)
5919 store.notify(tiddler.title,true);
5920 var remainingImports = wizard.getValue("remainingImports")-1;
5921 wizard.setValue("remainingImports",remainingImports);
5922 if(remainingImports == 0) {
5923 if(context.isSynchronous) {
5924 store.notifyAll();
5925 refreshDisplay();
5927 wizard.setButtons([
5928 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
5929 ],config.macros.importTiddlers.statusDoneImport);
5930 autoSaveChanges();
5934 //--
5935 //-- Upgrade macro
5936 //--
5938 config.macros.upgrade.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5940 var w = new Wizard();
5941 w.createWizard(place,this.wizardTitle);
5942 w.addStep(this.step1Title,this.step1Html.format([this.source,this.source]));
5943 w.setButtons([{caption: this.upgradeLabel, tooltip: this.upgradePrompt, onClick: this.onClickUpgrade}]);
5946 config.macros.upgrade.onClickUpgrade = function(e)
5948 var me = config.macros.upgrade;
5949 var w = new Wizard(this);
5950 if(window.location.protocol != "file:") {
5951 alert(me.errorCantUpgrade);
5952 return false;
5954 if(story.areAnyDirty() || store.isDirty()) {
5955 alert(me.errorNotSaved);
5956 return false;
5958 var localPath = getLocalPath(document.location.toString());
5959 var backupPath = getBackupPath(localPath,me.backupExtension);
5960 w.setValue("backupPath",backupPath);
5961 w.setButtons([],me.statusPreparingBackup);
5962 var original = loadOriginal(localPath);
5963 w.setButtons([],me.statusSavingBackup);
5964 var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
5965 if(backup != true) {
5966 w.setButtons([],me.errorSavingBackup);
5967 alert(me.errorSavingBackup);
5968 return false;
5970 w.setButtons([],me.statusLoadingCore);
5971 var load = loadRemoteFile(me.source,me.onLoadCore,w);
5972 if(typeof load == "string") {
5973 w.setButtons([],me.errorLoadingCore);
5974 alert(me.errorLoadingCore);
5975 return false;
5977 return false;
5980 config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
5982 var me = config.macros.upgrade;
5983 var w = params;
5984 var errMsg;
5985 if(!status)
5986 errMsg = me.errorLoadingCore;
5987 var newVer = me.extractVersion(responseText);
5988 if(!newVer)
5989 errMsg = me.errorCoreFormat;
5990 if(errMsg) {
5991 w.setButtons([],errMsg);
5992 alert(errMsg);
5993 return;
5995 var onStartUpgrade = function(e) {
5996 w.setButtons([],me.statusSavingCore);
5997 var localPath = getLocalPath(document.location.toString());
5998 saveFile(localPath,responseText);
5999 w.setButtons([],me.statusReloadingCore);
6000 var backupPath = w.getValue("backupPath");
6001 var newLoc = document.location.toString() + '?time=' + new Date().convertToYYYYMMDDHHMM() + '#upgrade:[[' + encodeURI(backupPath) + ']]';
6002 window.setTimeout(function () {window.location = newLoc;},10)
6004 var step2 = [me.step2Html_downgrade,me.step2Html_restore,me.step2Html_upgrade][compareVersions(version,newVer) + 1];
6005 w.addStep(me.step2Title,step2.format([formatVersion(newVer),formatVersion(version)]));
6006 w.setButtons([{caption: me.startLabel, tooltip: me.startPrompt, onClick: onStartUpgrade},{caption: me.cancelLabel, tooltip: me.cancelPrompt, onClick: me.onCancel}]);
6009 config.macros.upgrade.onCancel = function(e)
6011 var me = config.macros.upgrade;
6012 var w = new Wizard(this);
6013 w.addStep(me.step3Title,me.step3Html);
6014 w.setButtons([]);
6015 return false;
6018 config.macros.upgrade.extractVersion = function(upgradeFile)
6020 var re = /^var version = \{title: "([^"]+)", major: (\d+), minor: (\d+), revision: (\d+)(, beta: (\d+)){0,1}, date: new Date\("([^"]+)"\)/mg;
6021 var m = re.exec(upgradeFile);
6022 return m ? {title: m[1], major: m[2], minor: m[3], revision: m[4], beta: m[6], date: new Date(m[7])} : null;
6025 function upgradeFrom(path)
6027 var importStore = new TiddlyWiki();
6028 var tw = loadFile(path);
6029 if(window.netscape !== undefined)
6030 tw = convertUTF8ToUnicode(tw);
6031 importStore.importTiddlyWiki(tw);
6032 importStore.forEachTiddler(function(title,tiddler) {
6033 if(!store.getTiddler(title)) {
6034 store.addTiddler(tiddler);
6037 refreshDisplay();
6038 saveChanges(); //# To create appropriate Markup* sections
6039 alert(config.messages.upgradeDone.format([formatVersion()]));
6040 window.location = window.location.toString().substr(0,window.location.toString().lastIndexOf('?'));
6043 //--
6044 //-- Sync macro
6045 //--
6047 // Synchronisation handlers
6048 config.syncers = {};
6050 // Sync state.
6051 var currSync = null;
6053 // sync macro
6054 config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6056 if(!wikifier.isStatic)
6057 this.startSync(place);
6060 config.macros.sync.startSync = function(place)
6062 if(currSync)
6063 config.macros.sync.cancelSync();
6064 currSync = {};
6065 currSync.syncList = this.getSyncableTiddlers();
6066 this.createSyncTasks();
6067 this.preProcessSyncableTiddlers();
6068 var wizard = new Wizard();
6069 currSync.wizard = wizard;
6070 wizard.createWizard(place,this.wizardTitle);
6071 wizard.addStep(this.step1Title,this.step1Html);
6072 var markList = wizard.getElement("markList");
6073 var listWrapper = document.createElement("div");
6074 markList.parentNode.insertBefore(listWrapper,markList);
6075 currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
6076 this.processSyncableTiddlers();
6077 wizard.setButtons([
6078 {caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
6082 config.macros.sync.getSyncableTiddlers = function()
6084 var list = [];
6085 store.forEachTiddler(function(title,tiddler) {
6086 var syncItem = {};
6087 syncItem.serverType = tiddler.getServerType();
6088 syncItem.serverHost = tiddler.fields['server.host'];
6089 syncItem.serverWorkspace = tiddler.fields['server.workspace'];
6090 syncItem.tiddler = tiddler;
6091 syncItem.title = tiddler.title;
6092 syncItem.isTouched = tiddler.isTouched();
6093 syncItem.selected = syncItem.isTouched;
6094 syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
6095 syncItem.status = syncItem.syncStatus.text;
6096 if(syncItem.serverType && syncItem.serverHost)
6097 list.push(syncItem);
6099 list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
6100 return list;
6103 config.macros.sync.preProcessSyncableTiddlers = function()
6105 for(var t=0; t<currSync.syncList.length; t++) {
6106 si = currSync.syncList[t];
6107 var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
6108 si.serverUrl = ti.uri;
6112 config.macros.sync.processSyncableTiddlers = function()
6114 for(var t=0; t<currSync.syncList.length; t++) {
6115 si = currSync.syncList[t];
6116 si.rowElement.style.backgroundColor = si.syncStatus.color;
6120 config.macros.sync.createSyncTasks = function()
6122 currSync.syncTasks = [];
6123 for(var t=0; t<currSync.syncList.length; t++) {
6124 var si = currSync.syncList[t];
6125 var r = null;
6126 for(var st=0; st<currSync.syncTasks.length; st++) {
6127 var cst = currSync.syncTasks[st];
6128 if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
6129 r = cst;
6131 if(r == null) {
6132 si.syncTask = this.createSyncTask(si);
6133 currSync.syncTasks.push(si.syncTask);
6134 } else {
6135 si.syncTask = r;
6136 r.syncItems.push(si);
6141 config.macros.sync.createSyncTask = function(syncItem)
6143 var st = {};
6144 st.serverType = syncItem.serverType;
6145 st.serverHost = syncItem.serverHost;
6146 st.serverWorkspace = syncItem.serverWorkspace;
6147 st.syncItems = [syncItem];
6148 st.syncMachine = new SyncMachine(st.serverType,{
6149 start: function() {
6150 return this.openHost(st.serverHost,"openWorkspace");
6152 openWorkspace: function() {
6153 return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
6155 getTiddlerList: function() {
6156 return this.getTiddlerList("onGetTiddlerList");
6158 onGetTiddlerList: function(context) {
6159 var tiddlers = context.tiddlers;
6160 for(var t=0; t<st.syncItems.length; t++) {
6161 var si = st.syncItems[t];
6162 var f = tiddlers.findByField("title",si.title);
6163 if(f !== null) {
6164 if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
6165 si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
6167 } else {
6168 si.syncStatus = config.macros.sync.syncStatusList.notFound;
6170 config.macros.sync.updateSyncStatus(si);
6173 getTiddler: function(title) {
6174 return this.getTiddler(title,"onGetTiddler");
6176 onGetTiddler: function(context) {
6177 var tiddler = context.tiddler;
6178 var syncItem = st.syncItems.findByField("title",tiddler.title);
6179 if(syncItem !== null) {
6180 syncItem = st.syncItems[syncItem];
6181 store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
6182 syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
6183 config.macros.sync.updateSyncStatus(syncItem);
6186 putTiddler: function(tiddler) {
6187 return this.putTiddler(tiddler,"onPutTiddler");
6189 onPutTiddler: function(context) {
6190 var title = context.title;
6191 var syncItem = st.syncItems.findByField("title",title);
6192 if(syncItem !== null) {
6193 syncItem = st.syncItems[syncItem];
6194 store.resetTiddler(title);
6195 syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
6196 config.macros.sync.updateSyncStatus(syncItem);
6200 st.syncMachine.go();
6201 return st;
6204 config.macros.sync.updateSyncStatus = function(syncItem)
6206 var e = syncItem.colElements["status"];
6207 removeChildren(e);
6208 createTiddlyText(e,syncItem.syncStatus.text);
6209 syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
6212 config.macros.sync.doSync = function(e)
6214 var rowNames = ListView.getSelectedRows(currSync.listView);
6215 for(var t=0; t<currSync.syncList.length; t++) {
6216 var si = currSync.syncList[t];
6217 if(rowNames.indexOf(si.title) != -1) {
6218 config.macros.sync.doSyncItem(si);
6221 return false;
6224 config.macros.sync.doSyncItem = function(syncItem)
6226 var r = true;
6227 var sl = config.macros.sync.syncStatusList;
6228 switch(syncItem.syncStatus) {
6229 case sl.changedServer:
6230 r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
6231 break;
6232 case sl.notFound:
6233 case sl.changedLocally:
6234 case sl.changedBoth:
6235 r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
6236 break;
6237 default:
6238 break;
6240 if(r !== true)
6241 displayMessage("Error in doSyncItem: " + r);
6244 config.macros.sync.cancelSync = function()
6246 currSync = null;
6249 function SyncMachine(serverType,steps)
6251 this.serverType = serverType;
6252 this.adaptor = new config.adaptors[serverType];
6253 this.steps = steps;
6256 SyncMachine.prototype.go = function(step,context)
6258 var r = context ? context.status : null;
6259 if(typeof r == "string") {
6260 this.invokeError(r);
6261 return r;
6263 var h = this.steps[step ? step : "start"];
6264 if(!h)
6265 return null;
6266 r = h.call(this,context);
6267 if(typeof r == "string")
6268 this.invokeError(r);
6269 return r;
6272 SyncMachine.prototype.invokeError = function(message)
6274 if(this.steps.error)
6275 this.steps.error(message);
6278 SyncMachine.prototype.openHost = function(host,nextStep)
6280 var me = this;
6281 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
6284 SyncMachine.prototype.getWorkspaceList = function(nextStep)
6286 var me = this;
6287 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
6290 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
6292 var me = this;
6293 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
6296 SyncMachine.prototype.getTiddlerList = function(nextStep)
6298 var me = this;
6299 return me.adaptor.getTiddlerList(null,null,function(context) {me.go(nextStep,context);});
6302 SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
6304 return this.adaptor.generateTiddlerInfo(tiddler);
6307 SyncMachine.prototype.getTiddler = function(title,nextStep)
6309 var me = this;
6310 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
6313 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
6315 var me = this;
6316 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
6319 //--
6320 //-- Manager UI for groups of tiddlers
6321 //--
6323 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6325 var wizard = new Wizard();
6326 wizard.createWizard(place,this.wizardTitle);
6327 wizard.addStep(this.step1Title,this.step1Html);
6328 var markList = wizard.getElement("markList");
6329 var listWrapper = document.createElement("div");
6330 markList.parentNode.insertBefore(listWrapper,markList);
6331 listWrapper.setAttribute("refresh","macro");
6332 listWrapper.setAttribute("macroName","plugins");
6333 listWrapper.setAttribute("params",paramString);
6334 this.refresh(listWrapper,paramString);
6337 config.macros.plugins.refresh = function(listWrapper,params)
6339 var wizard = new Wizard(listWrapper);
6340 var selectedRows = [];
6341 ListView.forEachSelector(listWrapper,function(e,rowName) {
6342 if(e.checked)
6343 selectedRows.push(e.getAttribute("rowName"));
6345 removeChildren(listWrapper);
6346 params = params.parseParams("anon");
6347 var plugins = installedPlugins.slice(0);
6348 var t,tiddler,p;
6349 var configTiddlers = store.getTaggedTiddlers("systemConfig");
6350 for(t=0; t<configTiddlers.length; t++) {
6351 tiddler = configTiddlers[t];
6352 if(plugins.findByField("title",tiddler.title) == null) {
6353 p = getPluginInfo(tiddler);
6354 p.executed = false;
6355 p.log.splice(0,0,this.skippedText);
6356 plugins.push(p);
6359 for(t=0; t<plugins.length; t++) {
6360 p = plugins[t];
6361 p.size = p.tiddler.text ? p.tiddler.text.length : 0;
6362 p.forced = p.tiddler.isTagged("systemConfigForce");
6363 p.disabled = p.tiddler.isTagged("systemConfigDisable");
6364 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
6366 if(plugins.length == 0) {
6367 createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
6368 wizard.setButtons([]);
6369 } else {
6370 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6371 wizard.setValue("listView",listView);
6372 wizard.setButtons([
6373 {caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
6374 {caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
6379 config.macros.plugins.doRemoveTag = function(e)
6381 var wizard = new Wizard(this);
6382 var listView = wizard.getValue("listView");
6383 var rowNames = ListView.getSelectedRows(listView);
6384 if(rowNames.length == 0) {
6385 alert(config.messages.nothingSelected);
6386 } else {
6387 for(var t=0; t<rowNames.length; t++)
6388 store.setTiddlerTag(rowNames[t],false,"systemConfig");
6392 config.macros.plugins.doDelete = function(e)
6394 var wizard = new Wizard(this);
6395 var listView = wizard.getValue("listView");
6396 var rowNames = ListView.getSelectedRows(listView);
6397 if(rowNames.length == 0) {
6398 alert(config.messages.nothingSelected);
6399 } else {
6400 if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
6401 for(t=0; t<rowNames.length; t++) {
6402 store.removeTiddler(rowNames[t]);
6403 story.closeTiddler(rowNames[t],true);
6409 //--
6410 //-- Message area
6411 //--
6413 function getMessageDiv()
6415 var msgArea = document.getElementById("messageArea");
6416 if(!msgArea)
6417 return null;
6418 if(!msgArea.hasChildNodes())
6419 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
6420 config.messages.messageClose.text,
6421 config.messages.messageClose.tooltip,
6422 clearMessage);
6423 msgArea.style.display = "block";
6424 return createTiddlyElement(msgArea,"div");
6427 function displayMessage(text,linkText)
6429 var e = getMessageDiv();
6430 if(!e) {
6431 alert(text);
6432 return;
6434 if(linkText) {
6435 var link = createTiddlyElement(e,"a",null,null,text);
6436 link.href = linkText;
6437 link.target = "_blank";
6438 } else {
6439 e.appendChild(document.createTextNode(text));
6443 function clearMessage()
6445 var msgArea = document.getElementById("messageArea");
6446 if(msgArea) {
6447 removeChildren(msgArea);
6448 msgArea.style.display = "none";
6450 return false;
6453 //--
6454 //-- Refresh mechanism
6455 //--
6457 config.refreshers = {
6458 link: function(e,changeList)
6460 var title = e.getAttribute("tiddlyLink");
6461 refreshTiddlyLink(e,title);
6462 return true;
6465 tiddler: function(e,changeList)
6467 var title = e.getAttribute("tiddler");
6468 var template = e.getAttribute("template");
6469 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
6470 story.refreshTiddler(title,template,true);
6471 else
6472 refreshElements(e,changeList);
6473 return true;
6476 content: function(e,changeList)
6478 var title = e.getAttribute("tiddler");
6479 var force = e.getAttribute("force");
6480 if(force != null || changeList == null || changeList.indexOf(title) != -1) {
6481 removeChildren(e);
6482 wikify(store.getTiddlerText(title,title),e,null,store.fetchTiddler(title));
6483 return true;
6484 } else
6485 return false;
6488 macro: function(e,changeList)
6490 var macro = e.getAttribute("macroName");
6491 var params = e.getAttribute("params");
6492 if(macro)
6493 macro = config.macros[macro];
6494 if(macro && macro.refresh)
6495 macro.refresh(e,params);
6496 return true;
6500 config.refresherData = {
6501 styleSheet: "StyleSheet",
6502 defaultStyleSheet: "StyleSheet",
6503 pageTemplate: "PageTemplate",
6504 defaultPageTemplate: "PageTemplate",
6505 colorPalette: "ColorPalette",
6506 defaultColorPalette: "ColorPalette"
6509 function refreshElements(root,changeList)
6511 var nodes = root.childNodes;
6512 for(var c=0; c<nodes.length; c++) {
6513 var e = nodes[c], type = null;
6514 if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
6515 type = e.getAttribute("refresh");
6516 var refresher = config.refreshers[type];
6517 var refreshed = false;
6518 if(refresher != undefined)
6519 refreshed = refresher(e,changeList);
6520 if(e.hasChildNodes() && !refreshed)
6521 refreshElements(e,changeList);
6525 function applyHtmlMacros(root,tiddler)
6527 var e = root.firstChild;
6528 while(e) {
6529 var nextChild = e.nextSibling;
6530 if(e.getAttribute) {
6531 var macro = e.getAttribute("macro");
6532 if(macro) {
6533 var params = "";
6534 var p = macro.indexOf(" ");
6535 if(p != -1) {
6536 params = macro.substr(p+1);
6537 macro = macro.substr(0,p);
6539 invokeMacro(e,macro,params,null,tiddler);
6542 if(e.hasChildNodes())
6543 applyHtmlMacros(e,tiddler);
6544 e = nextChild;
6548 function refreshPageTemplate(title)
6550 var stash = createTiddlyElement(document.body,"div");
6551 stash.style.display = "none";
6552 var display = story.getContainer();
6553 var nodes,t;
6554 if(display) {
6555 nodes = display.childNodes;
6556 for(t=nodes.length-1; t>=0; t--)
6557 stash.appendChild(nodes[t]);
6559 var wrapper = document.getElementById("contentWrapper");
6561 isAvailable = function(title) {
6562 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
6563 if(s!=-1)
6564 title = title.substr(0,s);
6565 return store.tiddlerExists(title) || store.isShadowTiddler(title);
6567 if(!title || !isAvailable(title))
6568 title = config.refresherData.pageTemplate;
6569 if(!isAvailable(title))
6570 title = config.refresherData.defaultPageTemplate; //# this one is always avaialable
6571 html = store.getRecursiveTiddlerText(title,null,10);
6572 wrapper.innerHTML = html;
6573 applyHtmlMacros(wrapper);
6574 refreshElements(wrapper);
6575 display = story.getContainer();
6576 removeChildren(display);
6577 if(!display)
6578 display = createTiddlyElement(wrapper,"div",story.containerId());
6579 nodes = stash.childNodes;
6580 for(t=nodes.length-1; t>=0; t--)
6581 display.appendChild(nodes[t]);
6582 removeNode(stash);
6585 function refreshDisplay(hint)
6587 if(typeof hint == "string")
6588 hint = [hint];
6589 var e = document.getElementById("contentWrapper");
6590 refreshElements(e,hint);
6591 if(backstage.isPanelVisible()) {
6592 e = document.getElementById("backstage");
6593 refreshElements(e,hint);
6597 function refreshPageTitle()
6599 document.title = getPageTitle();
6602 function getPageTitle()
6604 var st = wikifyPlain("SiteTitle");
6605 var ss = wikifyPlain("SiteSubtitle");
6606 return st + ((st == "" || ss == "") ? "" : " - ") + ss;
6609 function refreshStyles(title,doc)
6611 setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc ? doc : document);
6614 function refreshColorPalette(title)
6616 if(!startingUp)
6617 refreshAll();
6620 function refreshAll()
6622 refreshPageTemplate();
6623 refreshDisplay();
6624 refreshStyles("StyleSheetLayout");
6625 refreshStyles("StyleSheetColors");
6626 refreshStyles(config.refresherData.styleSheet);
6627 refreshStyles("StyleSheetPrint");
6630 //--
6631 //-- Options stuff
6632 //--
6634 config.optionHandlers = {
6635 'txt': {
6636 get: function(name) {return encodeCookie(config.options[name].toString());},
6637 set: function(name,value) {config.options[name] = decodeCookie(value);}
6639 'chk': {
6640 get: function(name) {return config.options[name] ? "true" : "false";},
6641 set: function(name,value) {config.options[name] = value == "true";}
6645 function loadOptionsCookie()
6647 if(safeMode)
6648 return;
6649 var cookies = document.cookie.split(";");
6650 for(var c=0; c<cookies.length; c++) {
6651 var p = cookies[c].indexOf("=");
6652 if(p != -1) {
6653 var name = cookies[c].substr(0,p).trim();
6654 var value = cookies[c].substr(p+1).trim();
6655 var optType = name.substr(0,3);
6656 if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
6657 config.optionHandlers[optType].set(name,value);
6662 function saveOptionCookie(name)
6664 if(safeMode)
6665 return;
6666 var c = name + "=";
6667 var optType = name.substr(0,3);
6668 if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
6669 c += config.optionHandlers[optType].get(name);
6670 c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
6671 document.cookie = c;
6674 function encodeCookie(s)
6676 return escape(manualConvertUnicodeToUTF8(s));
6679 function decodeCookie(s)
6681 s = unescape(s);
6682 var re = /&#[0-9]{1,5};/g;
6683 return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
6687 config.macros.option.genericCreate = function(place,type,opt,className,desc)
6689 var typeInfo = config.macros.option.types[type];
6690 var c = document.createElement(typeInfo.elementType);
6691 if(typeInfo.typeValue)
6692 c.setAttribute("type",typeInfo.typeValue);
6693 c[typeInfo.eventName] = typeInfo.onChange;
6694 c.setAttribute("option",opt);
6695 if(className)
6696 c.className = className;
6697 else
6698 c.className = typeInfo.className;
6699 if(config.optionsDesc[opt])
6700 c.setAttribute("title",config.optionsDesc[opt]);
6701 place.appendChild(c);
6702 if(desc != "no")
6703 createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
6704 c[typeInfo.valueField] = config.options[opt];
6705 return c;
6708 config.macros.option.genericOnChange = function(e)
6710 var opt = this.getAttribute("option");
6711 if(opt) {
6712 var optType = opt.substr(0,3);
6713 var handler = config.macros.option.types[optType];
6714 if(handler.elementType && handler.valueField)
6715 config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
6717 return true;
6720 config.macros.option.types = {
6721 'txt': {
6722 elementType: "input",
6723 valueField: "value",
6724 eventName: "onkeyup",
6725 className: "txtOptionInput",
6726 create: config.macros.option.genericCreate,
6727 onChange: config.macros.option.genericOnChange
6729 'chk': {
6730 elementType: "input",
6731 valueField: "checked",
6732 eventName: "onclick",
6733 className: "chkOptionInput",
6734 typeValue: "checkbox",
6735 create: config.macros.option.genericCreate,
6736 onChange: config.macros.option.genericOnChange
6740 config.macros.option.propagateOption = function(opt,valueField,value,elementType)
6742 config.options[opt] = value;
6743 saveOptionCookie(opt);
6744 var nodes = document.getElementsByTagName(elementType);
6745 for(var t=0; t<nodes.length; t++) {
6746 var optNode = nodes[t].getAttribute("option");
6747 if(opt == optNode)
6748 nodes[t][valueField] = value;
6752 config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6754 params = paramString.parseParams("anon",null,true,false,false);
6755 var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
6756 var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
6757 var desc = getParam(params,"desc","no");
6758 var type = opt.substr(0,3);
6759 var h = config.macros.option.types[type];
6760 if(h && h.create)
6761 h.create(place,type,opt,className,desc);
6764 config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
6766 params = paramString.parseParams("anon",null,true,false,false);
6767 var showUnknown = getParam(params,"showUnknown","no");
6768 var wizard = new Wizard();
6769 wizard.createWizard(place,this.wizardTitle);
6770 wizard.addStep(this.step1Title,this.step1Html);
6771 var markList = wizard.getElement("markList");
6772 var chkUnknown = wizard.getElement("chkUnknown");
6773 chkUnknown.checked = showUnknown == "yes";
6774 chkUnknown.onchange = this.onChangeUnknown;
6775 var listWrapper = document.createElement("div");
6776 markList.parentNode.insertBefore(listWrapper,markList);
6777 wizard.setValue("listWrapper",listWrapper);
6778 this.refreshOptions(listWrapper,showUnknown == "yes");
6781 config.macros.options.refreshOptions = function(listWrapper,showUnknown)
6783 var opts = [];
6784 for(var n in config.options) {
6785 var opt = {};
6786 opt.option = "";
6787 opt.name = n;
6788 opt.lowlight = !config.optionsDesc[n];
6789 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6790 if(!opt.lowlight || showUnknown)
6791 opts.push(opt);
6793 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);});
6794 var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
6795 for(n=0; n<opts.length; n++) {
6796 var type = opts[n].name.substr(0,3);
6797 var h = config.macros.option.types[type];
6798 if(h && h.create) {
6799 h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
6804 config.macros.options.onChangeUnknown = function(e)
6806 var wizard = new Wizard(this);
6807 var listWrapper = wizard.getValue("listWrapper");
6808 removeChildren(listWrapper);
6809 config.macros.options.refreshOptions(listWrapper,this.checked);
6810 return false;
6813 //--
6814 //-- Saving
6815 //--
6817 var saveUsingSafari = false;
6819 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
6820 var endSaveArea = '</d' + 'iv>';
6822 // If there are unsaved changes, force the user to confirm before exitting
6823 function confirmExit()
6825 hadConfirmExit = true;
6826 if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
6827 return config.messages.confirmExit;
6830 // Give the user a chance to save changes before exitting
6831 function checkUnsavedChanges()
6833 if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
6834 if(confirm(config.messages.unsavedChangesWarning))
6835 saveChanges();
6839 function updateLanguageAttribute(s)
6841 if(config.locale) {
6842 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
6843 var m = mRE.exec(s);
6844 if(m) {
6845 var t = m[1];
6846 if(m[2])
6847 t += ' xml:lang="' + config.locale + '"';
6848 if(m[3])
6849 t += ' lang="' + config.locale + '"';
6850 t += ">";
6851 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
6854 return s;
6857 function updateMarkupBlock(s,blockName,tiddlerName)
6859 return s.replaceChunk(
6860 "<!--%0-START-->".format([blockName]),
6861 "<!--%0-END-->".format([blockName]),
6862 "\n" + convertUnicodeToUTF8(store.getRecursiveTiddlerText(tiddlerName,"")) + "\n");
6865 function updateOriginal(original,posDiv)
6867 if(!posDiv)
6868 posDiv = locateStoreArea(original);
6869 if(!posDiv) {
6870 alert(config.messages.invalidFileError.format([localPath]));
6871 return null;
6873 var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
6874 convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
6875 original.substr(posDiv[1]);
6876 var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
6877 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
6878 revised = updateLanguageAttribute(revised);
6879 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
6880 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
6881 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
6882 revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
6883 return revised;
6886 function locateStoreArea(original)
6888 // Locate the storeArea div's
6889 var posOpeningDiv = original.indexOf(startSaveArea);
6890 var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
6891 if(limitClosingDiv == -1)
6892 limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
6893 var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
6894 return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
6897 function autoSaveChanges(onlyIfDirty,tiddlers)
6899 if(config.options.chkAutoSave)
6900 saveChanges(onlyIfDirty,tiddlers);
6903 function loadOriginal(localPath)
6905 return loadFile(localPath);
6908 // Save this tiddlywiki with the pending changes
6909 function saveChanges(onlyIfDirty,tiddlers)
6911 if(onlyIfDirty && !store.isDirty())
6912 return;
6913 clearMessage();
6914 var t0 = new Date();
6915 var originalPath = document.location.toString();
6916 if(originalPath.substr(0,5) != "file:") {
6917 alert(config.messages.notFileUrlError);
6918 if(store.tiddlerExists(config.messages.saveInstructions))
6919 story.displayTiddler(null,config.messages.saveInstructions);
6920 return;
6922 var localPath = getLocalPath(originalPath);
6923 var original = loadOriginal(localPath);
6924 if(original == null) {
6925 alert(config.messages.cantSaveError);
6926 if(store.tiddlerExists(config.messages.saveInstructions))
6927 story.displayTiddler(null,config.messages.saveInstructions);
6928 return;
6930 var posDiv = locateStoreArea(original);
6931 if(!posDiv) {
6932 alert(config.messages.invalidFileError.format([localPath]));
6933 return;
6935 saveMain(localPath,original,posDiv);
6936 if(config.options.chkSaveBackups)
6937 saveBackup(localPath,original);
6938 if(config.options.chkSaveEmptyTemplate)
6939 saveEmpty(localPath,original,posDiv);
6940 if(config.options.chkGenerateAnRssFeed)
6941 saveRss(localPath);
6942 if(config.options.chkDisplayInstrumentation)
6943 displayMessage("saveChanges " + (new Date()-t0) + " ms");
6946 function saveMain(localPath,original,posDiv)
6948 var save;
6949 try {
6950 var revised = updateOriginal(original,posDiv);
6951 save = saveFile(localPath,revised);
6952 } catch (ex) {
6953 showException(ex);
6955 if(save) {
6956 displayMessage(config.messages.mainSaved,"file://" + localPath);
6957 store.setDirty(false);
6958 } else {
6959 alert(config.messages.mainFailed);
6963 function saveBackup(localPath,original)
6965 var backupPath = getBackupPath(localPath);
6966 var backup = copyFile(backupPath,localPath);
6967 if(!backup)
6968 backup = saveFile(backupPath,original);
6969 if(backup)
6970 displayMessage(config.messages.backupSaved,"file://" + backupPath);
6971 else
6972 alert(config.messages.backupFailed);
6975 function saveEmpty(localPath,original,posDiv)
6977 var emptyPath,p;
6978 if((p = localPath.lastIndexOf("/")) != -1)
6979 emptyPath = localPath.substr(0,p) + "/";
6980 else if((p = localPath.lastIndexOf("\\")) != -1)
6981 emptyPath = localPath.substr(0,p) + "\\";
6982 else
6983 emptyPath = localPath + ".";
6984 emptyPath += "empty.html";
6985 var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
6986 var emptySave = saveFile(emptyPath,empty);
6987 if(emptySave)
6988 displayMessage(config.messages.emptySaved,"file://" + emptyPath);
6989 else
6990 alert(config.messages.emptyFailed);
6993 function saveRss(localPath)
6995 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
6996 if(saveFile(rssPath,convertUnicodeToUTF8(generateRss())))
6997 displayMessage(config.messages.rssSaved,"file://" + rssPath);
6998 else
6999 alert(config.messages.rssFailed);
7002 function getLocalPath(origPath)
7004 var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
7005 // Remove any location or query part of the URL
7006 var argPos = originalPath.indexOf("?");
7007 if(argPos != -1)
7008 originalPath = originalPath.substr(0,argPos);
7009 var hashPos = originalPath.indexOf("#");
7010 if(hashPos != -1)
7011 originalPath = originalPath.substr(0,hashPos);
7012 // Convert file://localhost/ to file:///
7013 if(originalPath.indexOf("file://localhost/") == 0)
7014 originalPath = "file://" + originalPath.substr(16);
7015 // Convert to a native file format
7016 var localPath;
7017 if(originalPath.charAt(9) == ":") // pc local file
7018 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
7019 else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
7020 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
7021 else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
7022 localPath = unescape(originalPath.substr(7));
7023 else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
7024 localPath = unescape(originalPath.substr(5));
7025 else // pc network file
7026 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
7027 return localPath;
7030 function getBackupPath(localPath,title,extension)
7032 var slash = "\\";
7033 var dirPathPos = localPath.lastIndexOf("\\");
7034 if(dirPathPos == -1) {
7035 dirPathPos = localPath.lastIndexOf("/");
7036 slash = "/";
7038 var backupFolder = config.options.txtBackupFolder;
7039 if(!backupFolder || backupFolder == "")
7040 backupFolder = ".";
7041 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
7042 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + ".";
7043 if(title)
7044 backupPath += title.replace(/[\\\/\*\?\":<> ]/g,"_") + ".";
7045 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() + "." + (extension ? extension : "html");
7046 return backupPath;
7049 function generateRss()
7051 var s = [];
7052 var d = new Date();
7053 var u = store.getTiddlerText("SiteUrl");
7054 // Assemble the header
7055 s.push("<" + "?xml version=\"1.0\"?" + ">");
7056 s.push("<rss version=\"2.0\">");
7057 s.push("<channel>");
7058 s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
7059 if(u)
7060 s.push("<link>" + u.htmlEncode() + "</link>");
7061 s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
7062 s.push("<language>en-us</language>");
7063 s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
7064 s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
7065 s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
7066 s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
7067 s.push("<generator>TiddlyWiki " + formatVersion() + "</generator>");
7068 // The body
7069 var tiddlers = store.getTiddlers("modified","excludeLists");
7070 var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
7071 for(var t=tiddlers.length-1; t>=n; t--) {
7072 s.push("<item>\n" + tiddlers[t].toRssItem(u) + "\n</item>");
7074 // And footer
7075 s.push("</channel>");
7076 s.push("</rss>");
7077 // Save it all
7078 return s.join("\n");
7082 //--
7083 //-- Filesystem code
7084 //--
7086 function convertUTF8ToUnicode(u)
7088 return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
7091 function manualConvertUTF8ToUnicode(utf)
7093 var uni = utf;
7094 var src = 0;
7095 var dst = 0;
7096 var b1, b2, b3;
7097 var c;
7098 while(src < utf.length) {
7099 b1 = utf.charCodeAt(src++);
7100 if(b1 < 0x80) {
7101 dst++;
7102 } else if(b1 < 0xE0) {
7103 b2 = utf.charCodeAt(src++);
7104 c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
7105 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7106 } else {
7107 b2 = utf.charCodeAt(src++);
7108 b3 = utf.charCodeAt(src++);
7109 c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
7110 uni = uni.substring(0,dst++).concat(c,utf.substr(src));
7113 return uni;
7116 function mozConvertUTF8ToUnicode(u)
7118 try {
7119 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7120 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7121 converter.charset = "UTF-8";
7122 } catch(ex) {
7123 return manualConvertUTF8ToUnicode(u);
7124 } // fallback
7125 var s = converter.ConvertToUnicode(u);
7126 var fin = converter.Finish();
7127 return (fin.length > 0) ? s+fin : s;
7130 function convertUnicodeToUTF8(s)
7132 if(window.netscape == undefined)
7133 return manualConvertUnicodeToUTF8(s);
7134 else
7135 return mozConvertUnicodeToUTF8(s);
7138 function manualConvertUnicodeToUTF8(s)
7140 var re = /[^\u0000-\u007F]/g ;
7141 return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
7144 function mozConvertUnicodeToUTF8(s)
7146 try {
7147 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7148 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7149 converter.charset = "UTF-8";
7150 } catch(ex) {
7151 return manualConvertUnicodeToUTF8(s);
7152 } // fallback
7153 var u = converter.ConvertFromUnicode(s);
7154 var fin = converter.Finish();
7155 return fin.length > 0 ? u + fin : u;
7158 function convertUriToUTF8(uri,charSet)
7160 if(window.netscape == undefined || charSet == undefined || charSet == "")
7161 return uri;
7162 try {
7163 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7164 var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
7165 } catch(ex) {
7166 return uri;
7168 return converter.convertURISpecToUTF8(uri,charSet);
7171 function copyFile(dest,source)
7173 return config.browser.isIE ? ieCopyFile(dest,source) : false;
7176 function saveFile(fileUrl,content)
7178 var r = mozillaSaveFile(fileUrl,content);
7179 if(!r)
7180 r = ieSaveFile(fileUrl,content);
7181 if(!r)
7182 r = javaSaveFile(fileUrl,content);
7183 return r;
7186 function loadFile(fileUrl)
7188 var r = mozillaLoadFile(fileUrl);
7189 if((r == null) || (r == false))
7190 r = ieLoadFile(fileUrl);
7191 if((r == null) || (r == false))
7192 r = javaLoadFile(fileUrl);
7193 return r;
7196 function ieCreatePath(path)
7198 try {
7199 var fso = new ActiveXObject("Scripting.FileSystemObject");
7200 } catch(ex) {
7201 return null;
7204 var pos = path.lastIndexOf("\\");
7205 if(pos!=-1)
7206 path = path.substring(0, pos+1);
7208 var scan = [];
7209 scan.push(path);
7210 var i = 0;
7211 do {
7212 var parent = fso.GetParentFolderName(scan[i++]);
7213 if(fso.FolderExists(parent))
7214 break;
7215 scan.push(parent);
7216 } while(true);
7218 for(i=scan.length-1;i>=0;i--) {
7219 if(!fso.FolderExists(scan[i]))
7220 fso.CreateFolder(scan[i]);
7222 return true;
7225 // Returns null if it can't do it, false if there's an error, true if it saved OK
7226 function ieSaveFile(filePath,content)
7228 ieCreatePath(filePath);
7229 try {
7230 var fso = new ActiveXObject("Scripting.FileSystemObject");
7231 } catch(ex) {
7232 return null;
7234 var file = fso.OpenTextFile(filePath,2,-1,0);
7235 file.Write(content);
7236 file.Close();
7237 return true;
7240 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7241 function ieLoadFile(filePath)
7243 try {
7244 var fso = new ActiveXObject("Scripting.FileSystemObject");
7245 var file = fso.OpenTextFile(filePath,1);
7246 var content = file.ReadAll();
7247 file.Close();
7248 } catch(ex) {
7249 return null;
7251 return content;
7254 function ieCopyFile(dest,source)
7256 ieCreatePath(dest);
7257 try {
7258 var fso = new ActiveXObject("Scripting.FileSystemObject");
7259 fso.GetFile(source).Copy(dest);
7260 } catch(ex) {
7261 return false;
7263 return true;
7266 // Returns null if it can't do it, false if there's an error, true if it saved OK
7267 function mozillaSaveFile(filePath,content)
7269 if(window.Components) {
7270 try {
7271 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7272 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7273 file.initWithPath(filePath);
7274 if(!file.exists())
7275 file.create(0,0664);
7276 var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
7277 out.init(file,0x20|0x02,00004,null);
7278 out.write(content,content.length);
7279 out.flush();
7280 out.close();
7281 return true;
7282 } catch(ex) {
7283 return false;
7286 return null;
7289 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
7290 function mozillaLoadFile(filePath)
7292 if(window.Components) {
7293 try {
7294 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
7295 var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7296 file.initWithPath(filePath);
7297 if(!file.exists())
7298 return null;
7299 var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
7300 inputStream.init(file,0x01,00004,null);
7301 var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
7302 sInputStream.init(inputStream);
7303 var contents = sInputStream.read(sInputStream.available());
7304 sInputStream.close();
7305 inputStream.close();
7306 return contents;
7307 } catch(ex) {
7308 return false;
7311 return null;
7314 function javaUrlToFilename(url)
7316 var f = "//localhost";
7317 if(url.indexOf(f) == 0)
7318 return url.substring(f.length);
7319 var i = url.indexOf(":");
7320 if(i > 0)
7321 return url.substring(i-1);
7322 return url;
7325 function javaSaveFile(filePath,content)
7327 try {
7328 if(document.applets["TiddlySaver"])
7329 return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
7330 } catch(ex) {
7332 try {
7333 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
7334 s.print(content);
7335 s.close();
7336 } catch(ex) {
7337 return null;
7339 return true;
7342 function javaLoadFile(filePath)
7344 try {
7345 if(document.applets["TiddlySaver"])
7346 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
7347 } catch(ex) {
7349 var content = [];
7350 try {
7351 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
7352 var line;
7353 while((line = r.readLine()) != null)
7354 content.push(new String(line));
7355 r.close();
7356 } catch(ex) {
7357 return null;
7359 return content.join("\n");
7362 //--
7363 //-- Server adaptor for talking to static TiddlyWiki files
7364 //--
7366 function FileAdaptor()
7368 this.host = null;
7369 this.store = null;
7370 return this;
7373 FileAdaptor.serverType = 'file';
7374 FileAdaptor.serverLabel = 'TiddlyWiki';
7376 FileAdaptor.prototype.setContext = function(context,userParams,callback)
7378 if(!context) context = {};
7379 context.userParams = userParams;
7380 if(callback) context.callback = callback;
7381 context.adaptor = this;
7382 if(!context.host)
7383 context.host = this.host;
7384 context.host = FileAdaptor.fullHostName(context.host);
7385 if(!context.workspace)
7386 context.workspace = this.workspace;
7387 return context;
7390 FileAdaptor.fullHostName = function(host)
7392 if(!host)
7393 return '';
7394 if(!host.match(/:\/\//))
7395 host = 'http://' + host;
7396 return host;
7399 FileAdaptor.minHostName = function(host)
7401 return host ? host.replace(/^http:\/\//,'').replace(/\/$/,'') : '';
7404 // Open the specified host
7405 FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
7407 this.host = host;
7408 context = this.setContext(context,userParams,callback);
7409 context.status = true;
7410 if(callback)
7411 window.setTimeout(function() {context.callback(context,userParams);},10);
7412 return true;
7415 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7417 context.status = status;
7418 if(!status) {
7419 context.statusText = "Error reading file";
7420 } else {
7421 context.adaptor.store = new TiddlyWiki();
7422 if(!context.adaptor.store.importTiddlyWiki(responseText))
7423 context.statusText = config.messages.invalidFileError.format([url]);
7425 context.complete(context,context.userParams);
7428 // Get the list of workspaces on a given server
7429 FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
7431 context = this.setContext(context,userParams,callback);
7432 context.workspaces = [{title:"(default)"}];
7433 context.status = true;
7434 if(callback)
7435 window.setTimeout(function() {callback(context,userParams);},10);
7436 return true;
7439 // Open the specified workspace
7440 FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
7442 this.workspace = workspace;
7443 context = this.setContext(context,userParams,callback);
7444 context.status = true;
7445 if(callback)
7446 window.setTimeout(function() {callback(context,userParams);},10);
7447 return true;
7450 // Gets the list of tiddlers within a given workspace
7451 FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback,filter)
7453 context = this.setContext(context,userParams,callback);
7454 if(!context.filter)
7455 context.filter = filter;
7456 context.complete = FileAdaptor.getTiddlerListComplete;
7457 if(this.store) {
7458 var ret = context.complete(context,context.userParams);
7459 } else {
7460 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7461 if(typeof ret != "string")
7462 ret = true;
7464 return ret;
7467 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7469 if(context.status) {
7470 if(context.filter) {
7471 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
7472 } else {
7473 context.tiddlers = [];
7474 context.adaptor.store.forEachTiddler(function(title,tiddler) {context.tiddlers.push(tiddler);});
7476 for(var i=0; i<context.tiddlers.length; i++) {
7477 context.tiddlers[i].fields['server.type'] = FileAdaptor.serverType;
7478 context.tiddlers[i].fields['server.host'] = FileAdaptor.minHostName(context.host);
7479 context.tiddlers[i].fields['server.page.revision'] = context.tiddlers[i].modified.convertToYYYYMMDDHHMM();
7481 context.status = true;
7483 if(context.callback) {
7484 window.setTimeout(function() {context.callback(context,userParams);},10);
7486 return true;
7489 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7491 var info = {};
7492 info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
7493 return info;
7496 // Retrieve a tiddler from a given workspace on a given server
7497 FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
7499 context = this.setContext(context,userParams,callback);
7500 context.title = title;
7501 context.complete = FileAdaptor.getTiddlerComplete;
7502 return context.adaptor.store ?
7503 context.complete(context,context.userParams) :
7504 loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7507 FileAdaptor.getTiddlerComplete = function(context,userParams)
7509 var t = context.adaptor.store.fetchTiddler(context.title);
7510 t.fields['server.type'] = FileAdaptor.serverType;
7511 t.fields['server.host'] = FileAdaptor.minHostName(context.host);
7512 t.fields['server.page.revision'] = t.modified.convertToYYYYMMDDHHMM();
7513 context.tiddler = t;
7514 context.status = true;
7515 if(context.allowSynchronous) {
7516 context.isSynchronous = true;
7517 context.callback(context,userParams);
7518 } else {
7519 window.setTimeout(function() {context.callback(context,userParams);},10);
7521 return true;
7524 FileAdaptor.prototype.close = function()
7526 delete this.store;
7527 this.store = null;
7530 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7532 config.defaultAdaptor = FileAdaptor.serverType;
7534 //--
7535 //-- Remote HTTP requests
7536 //--
7538 function loadRemoteFile(url,callback,params)
7540 return doHttp("GET",url,null,null,null,null,callback,params,null);
7543 // HTTP status codes
7544 var httpStatus = {
7545 OK: 200,
7546 ContentCreated: 201,
7547 NoContent: 204,
7548 MultiStatus: 207,
7549 Unauthorized: 401,
7550 Forbidden: 403,
7551 NotFound: 404,
7552 MethodNotAllowed: 405
7555 function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
7557 var x = getXMLHttpRequest();
7558 if(!x)
7559 return "Can't create XMLHttpRequest object";
7560 x.onreadystatechange = function() {
7561 try {
7562 var status = x.status;
7563 } catch(ex) {
7564 status = false;
7566 if(x.readyState == 4 && callback && (status !== undefined)) {
7567 if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent, httpStatus.MultiStatus].contains(status))
7568 callback(true,params,x.responseText,url,x);
7569 else
7570 callback(false,params,null,url,x);
7571 x.onreadystatechange = function(){};
7572 x = null;
7575 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
7576 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
7577 try {
7578 if(!allowCache)
7579 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
7580 x.open(type,url,true,username,password);
7581 if(data)
7582 x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
7583 if(x.overrideMimeType)
7584 x.setRequestHeader("Connection", "close");
7585 if(headers) {
7586 for(var n in headers)
7587 x.setRequestHeader(n,headers[n]);
7589 x.setRequestHeader("X-Requested-With", "TiddlyWiki " + formatVersion());
7590 x.send(data);
7591 } catch(ex) {
7592 return exceptionText(ex);
7594 return x;
7597 function getXMLHttpRequest()
7599 try {
7600 var x = new XMLHttpRequest(); // Modern
7601 } catch(ex) {
7602 try {
7603 x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
7604 } catch (ex2) {
7605 return null;
7608 return x;
7611 //--
7612 //-- TiddlyWiki-specific utility functions
7613 //--
7615 formatVersion = function(v)
7617 v = v || version;
7618 return v.major + "." + v.minor + "." + v.revision + (v.beta ? " (beta " + v.beta + ")" : "");
7621 compareVersions = function(v1,v2)
7623 var a = ["major","minor","revision"];
7624 for(var i = 0; i<a.length; i++) {
7625 var x1 = v1[a[i]] || 0;
7626 var x2 = v2[a[i]] || 0;
7627 if(x1<x2)
7628 return 1;
7629 if(x1>x2)
7630 return -1;
7632 x1 = v1.beta || 9999;
7633 x2 = v2.beta || 9999;
7634 if(x1<x2)
7635 return 1;
7636 return x1 > x2 ? -1 : 0;
7639 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7641 var btn = document.createElement("a");
7642 if(action) {
7643 btn.onclick = action;
7644 btn.setAttribute("href","javascript:;");
7646 if(tooltip)
7647 btn.setAttribute("title",tooltip);
7648 if(text)
7649 btn.appendChild(document.createTextNode(text));
7650 btn.className = className ? className : "button";
7651 if(id)
7652 btn.id = id;
7653 if(attribs) {
7654 for(var n in attribs) {
7655 btn.setAttribute(n,attribs[n]);
7658 if(parent)
7659 parent.appendChild(btn);
7660 if(accessKey)
7661 btn.setAttribute("accessKey",accessKey);
7662 return btn;
7665 function createTiddlyLink(place,title,includeText,className,isStatic,linkedFromTiddler,noToggle)
7667 var text = includeText ? title : null;
7668 var i = getTiddlyLinkInfo(title,className);
7669 var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
7670 btn.setAttribute("refresh","link");
7671 btn.setAttribute("tiddlyLink",title);
7672 if(noToggle)
7673 btn.setAttribute("noToggle","true");
7674 if(linkedFromTiddler) {
7675 var fields = linkedFromTiddler.getInheritedFields();
7676 if(fields)
7677 btn.setAttribute("tiddlyFields",fields);
7679 return btn;
7682 function refreshTiddlyLink(e,title)
7684 var i = getTiddlyLinkInfo(title,e.className);
7685 e.className = i.classes;
7686 e.title = i.subTitle;
7689 function getTiddlyLinkInfo(title,currClasses)
7691 var classes = currClasses ? currClasses.split(" ") : [];
7692 classes.pushUnique("tiddlyLink");
7693 var tiddler = store.fetchTiddler(title);
7694 var subTitle;
7695 if(tiddler) {
7696 subTitle = tiddler.getSubtitle();
7697 classes.pushUnique("tiddlyLinkExisting");
7698 classes.remove("tiddlyLinkNonExisting");
7699 classes.remove("shadow");
7700 } else {
7701 classes.remove("tiddlyLinkExisting");
7702 classes.pushUnique("tiddlyLinkNonExisting");
7703 if(store.isShadowTiddler(title)) {
7704 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7705 classes.pushUnique("shadow");
7706 } else {
7707 subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
7708 classes.remove("shadow");
7711 if(typeof config.annotations[title]=="string")
7712 subTitle = config.annotations[title];
7713 return {classes: classes.join(" "),subTitle: subTitle};
7716 function createExternalLink(place,url)
7718 var link = document.createElement("a");
7719 link.className = "externalLink";
7720 link.href = url;
7721 link.title = config.messages.externalLinkTooltip.format([url]);
7722 if(config.options.chkOpenInNewWindow)
7723 link.target = "_blank";
7724 place.appendChild(link);
7725 return link;
7728 // Event handler for clicking on a tiddly link
7729 function onClickTiddlerLink(ev)
7731 var e = ev ? ev : window.event;
7732 var target = resolveTarget(e);
7733 var link = target;
7734 var title = null;
7735 var fields = null;
7736 var noToggle = null;
7737 do {
7738 title = link.getAttribute("tiddlyLink");
7739 fields = link.getAttribute("tiddlyFields");
7740 noToggle = link.getAttribute("noToggle");
7741 link = link.parentNode;
7742 } while(title == null && link != null);
7743 if(!store.isShadowTiddler(title)) {
7744 var f = fields ? fields.decodeHashMap() : {};
7745 fields = String.encodeHashMap(merge(f,config.defaultCustomFields,true));
7747 if(title) {
7748 var toggling = e.metaKey || e.ctrlKey;
7749 if(config.options.chkToggleLinks)
7750 toggling = !toggling;
7751 if(noToggle)
7752 toggling = false;
7753 if(store.getTiddler(title))
7754 fields = null;
7755 story.displayTiddler(target,title,null,true,null,fields,toggling);
7757 clearMessage();
7758 return false;
7761 // Create a button for a tag with a popup listing all the tiddlers that it tags
7762 function createTagButton(place,tag,excludeTiddler,title,tooltip)
7764 var btn = createTiddlyButton(place,title||tag,(tooltip||config.views.wikified.tag.tooltip).format([tag]),onClickTag);
7765 btn.setAttribute("tag",tag);
7766 if(excludeTiddler)
7767 btn.setAttribute("tiddler",excludeTiddler);
7768 return btn;
7771 // Event handler for clicking on a tiddler tag
7772 function onClickTag(ev)
7774 var e = ev ? ev : window.event;
7775 var popup = Popup.create(this);
7776 var tag = this.getAttribute("tag");
7777 var title = this.getAttribute("tiddler");
7778 if(popup && tag) {
7779 var tagged = store.getTaggedTiddlers(tag);
7780 var titles = [];
7781 var li,r;
7782 for(r=0;r<tagged.length;r++) {
7783 if(tagged[r].title != title)
7784 titles.push(tagged[r].title);
7786 var lingo = config.views.wikified.tag;
7787 if(titles.length > 0) {
7788 var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
7789 openAll.setAttribute("tag",tag);
7790 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7791 for(r=0; r<titles.length; r++) {
7792 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
7794 } else {
7795 createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
7797 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
7798 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
7799 createTiddlyText(h,lingo.openTag.format([tag]));
7801 Popup.show();
7802 e.cancelBubble = true;
7803 if(e.stopPropagation) e.stopPropagation();
7804 return false;
7807 // Event handler for 'open all' on a tiddler popup
7808 function onClickTagOpenAll(ev)
7810 var e = ev ? ev : window.event;
7811 var tag = this.getAttribute("tag");
7812 var tagged = store.getTaggedTiddlers(tag);
7813 story.displayTiddlers(this,tagged);
7814 return false;
7817 function onClickError(ev)
7819 var e = ev ? ev : window.event;
7820 var popup = Popup.create(this);
7821 var lines = this.getAttribute("errorText").split("\n");
7822 for(var t=0; t<lines.length; t++)
7823 createTiddlyElement(popup,"li",null,null,lines[t]);
7824 Popup.show();
7825 e.cancelBubble = true;
7826 if(e.stopPropagation) e.stopPropagation();
7827 return false;
7830 function createTiddlyDropDown(place,onchange,options,defaultValue)
7832 var sel = createTiddlyElement(place,"select");
7833 sel.onchange = onchange;
7834 for(var t=0; t<options.length; t++) {
7835 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
7836 e.value = options[t].name;
7837 if(options[t].name == defaultValue)
7838 e.selected = true;
7840 return sel;
7843 function createTiddlyPopup(place,caption,tooltip,tiddler)
7845 if(tiddler.text) {
7846 createTiddlyLink(place,caption,true);
7847 var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
7848 btn.tiddler = tiddler;
7849 } else {
7850 createTiddlyText(place,caption);
7854 function onClickTiddlyPopup(ev)
7856 var e = ev ? ev : window.event;
7857 var tiddler = this.tiddler;
7858 if(tiddler.text) {
7859 var popup = Popup.create(this,"div","popupTiddler");
7860 wikify(tiddler.text,popup,null,tiddler);
7861 Popup.show();
7863 if(e) e.cancelBubble = true;
7864 if(e && e.stopPropagation) e.stopPropagation();
7865 return false;
7868 function createTiddlyError(place,title,text)
7870 var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
7871 if(text) btn.setAttribute("errorText",text);
7874 function merge(dst,src,preserveExisting)
7876 for(p in src) {
7877 if(!preserveExisting || dst[p] === undefined)
7878 dst[p] = src[p];
7880 return dst;
7883 // Returns a string containing the description of an exception, optionally prepended by a message
7884 function exceptionText(e,message)
7886 var s = e.description ? e.description : e.toString();
7887 return message ? "%0:\n%1".format([message,s]) : s;
7890 // Displays an alert of an exception description with optional message
7891 function showException(e,message)
7893 alert(exceptionText(e,message));
7896 function alertAndThrow(m)
7898 alert(m);
7899 throw(m);
7902 function glyph(name)
7904 var g = config.glyphs;
7905 var b = g.currBrowser;
7906 if(b == null) {
7907 b = 0;
7908 while(!g.browsers[b]() && b < g.browsers.length-1)
7909 b++;
7910 g.currBrowser = b;
7912 if(!g.codes[name])
7913 return "";
7914 return g.codes[name][b];
7919 //- Animation engine
7922 function Animator()
7924 this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
7925 this.timerID = 0; // ID of the timer used for animating
7926 this.animations = []; // List of animations in progress
7927 return this;
7930 // Start animation engine
7931 Animator.prototype.startAnimating = function() //# Variable number of arguments
7933 for(var t=0; t<arguments.length; t++)
7934 this.animations.push(arguments[t]);
7935 if(this.running == 0) {
7936 var me = this;
7937 this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
7939 this.running += arguments.length;
7942 // Perform an animation engine tick, calling each of the known animation modules
7943 Animator.prototype.doAnimate = function(me)
7945 var a = 0;
7946 while(a < me.animations.length) {
7947 var animation = me.animations[a];
7948 if(animation.tick()) {
7949 a++;
7950 } else {
7951 me.animations.splice(a,1);
7952 if(--me.running == 0)
7953 window.clearInterval(me.timerID);
7958 Animator.slowInSlowOut = function(progress)
7960 return(1-((Math.cos(progress * Math.PI)+1)/2));
7963 //--
7964 //-- Morpher animation
7965 //--
7967 // Animate a set of properties of an element
7968 function Morpher(element,duration,properties,callback)
7970 this.element = element;
7971 this.duration = duration;
7972 this.properties = properties;
7973 this.startTime = new Date();
7974 this.endTime = Number(this.startTime) + duration;
7975 this.callback = callback;
7976 this.tick();
7977 return this;
7980 Morpher.prototype.assignStyle = function(element,style,value)
7982 switch(style) {
7983 case "-tw-vertScroll":
7984 window.scrollTo(findScrollX(),value);
7985 break;
7986 case "-tw-horizScroll":
7987 window.scrollTo(value,findScrollY());
7988 break;
7989 default:
7990 element.style[style] = value;
7991 break;
7995 Morpher.prototype.stop = function()
7997 for(var t=0; t<this.properties.length; t++) {
7998 var p = this.properties[t];
7999 if(p.atEnd !== undefined) {
8000 this.assignStyle(this.element,p.style,p.atEnd);
8003 if(this.callback)
8004 this.callback(this.element,this.properties);
8007 Morpher.prototype.tick = function()
8009 var currTime = Number(new Date());
8010 progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
8011 for(var t=0; t<this.properties.length; t++) {
8012 var p = this.properties[t];
8013 if(p.start !== undefined && p.end !== undefined) {
8014 var template = p.template ? p.template : "%0";
8015 switch(p.format) {
8016 case undefined:
8017 case "style":
8018 var v = p.start + (p.end-p.start) * progress;
8019 this.assignStyle(this.element,p.style,template.format([v]));
8020 break;
8021 case "color":
8022 break;
8026 if(currTime >= this.endTime) {
8027 this.stop();
8028 return false;
8030 return true;
8033 //--
8034 //-- Zoomer animation
8035 //--
8037 function Zoomer(text,startElement,targetElement,unused)
8039 var e = createTiddlyElement(document.body,"div",null,"zoomer");
8040 createTiddlyElement(e,"div",null,null,text);
8041 var winWidth = findWindowWidth();
8042 var winHeight = findWindowHeight();
8043 var p = [
8044 {style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
8045 {style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
8046 {style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
8047 {style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
8048 {style: 'fontSize', start: 8, end: 24, template: '%0pt'}
8050 var c = function(element,properties) {removeNode(element);};
8051 return new Morpher(e,config.animDuration,p,c);
8054 //--
8055 //-- Scroller animation
8056 //--
8058 function Scroller(targetElement,unused)
8060 var p = [
8061 {style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
8063 return new Morpher(targetElement,config.animDuration,p);
8066 //--
8067 //-- Slider animation
8068 //--
8070 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
8071 function Slider(element,opening,unused,deleteMode)
8073 element.style.overflow = 'hidden';
8074 if(opening)
8075 element.style.height = '0px'; // Resolves a Firefox flashing bug
8076 element.style.display = 'block';
8077 var left = findPosX(element);
8078 var width = element.scrollWidth;
8079 var height = element.scrollHeight;
8080 var winWidth = findWindowWidth();
8081 var p = [];
8082 var c = null;
8083 if(opening) {
8084 p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
8085 p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
8086 p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
8087 } else {
8088 p.push({style: 'height', start: height, end: 0, template: '%0px'});
8089 p.push({style: 'display', atEnd: 'none'});
8090 p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
8091 p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
8092 switch(deleteMode) {
8093 case "all":
8094 c = function(element,properties) {removeNode(element);};
8095 break;
8096 case "children":
8097 c = function(element,properties) {removeChildren(element);};
8098 break;
8101 return new Morpher(element,config.animDuration,p,c);
8104 //--
8105 //-- Popup menu
8106 //--
8108 var Popup = {
8109 stack: [] // Array of objects with members root: and popup:
8112 Popup.create = function(root,elem,theClass)
8114 var stackPosition = this.find(root,"popup");
8115 Popup.remove(stackPosition+1);
8116 var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
8117 popup.stackPosition = stackPosition;
8118 Popup.stack.push({root: root, popup: popup});
8119 return popup;
8122 Popup.onDocumentClick = function(ev)
8124 var e = ev ? ev : window.event;
8125 if(e.eventPhase == undefined)
8126 Popup.remove();
8127 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
8128 Popup.remove();
8129 return true;
8132 Popup.show = function(valign,halign,offset)
8134 var curr = Popup.stack[Popup.stack.length-1];
8135 this.place(curr.root,curr.popup,valign,halign,offset);
8136 addClass(curr.root,"highlight");
8137 if(config.options.chkAnimate && anim && typeof Scroller == "function")
8138 anim.startAnimating(new Scroller(curr.popup));
8139 else
8140 window.scrollTo(0,ensureVisible(curr.popup));
8143 Popup.place = function(root,popup,valign,halign,offset)
8145 if(!offset)
8146 var offset = {x:0,y:0};
8147 if(popup.stackPosition >= 0 && !valign && !halign) {
8148 offset.x = offset.x + root.offsetWidth;
8149 } else {
8150 offset.x = (halign == 'right') ? offset.x + root.offsetWidth : offset.x;
8151 offset.y = (valign == 'top') ? offset.y : offset.y + root.offsetHeight;
8153 var rootLeft = findPosX(root);
8154 var rootTop = findPosY(root);
8155 var popupLeft = rootLeft + offset.x;
8156 var popupTop = rootTop + offset.y;
8157 var winWidth = findWindowWidth();
8158 if(popup.offsetWidth > winWidth*0.75)
8159 popup.style.width = winWidth*0.75 + "px";
8160 var popupWidth = popup.offsetWidth;
8161 var scrollWidth = winWidth - document.body.offsetWidth;
8162 if(popupLeft + popupWidth > winWidth - scrollWidth - 1) {
8163 if(halign == 'right')
8164 popupLeft = popupLeft - root.offsetWidth - popupWidth;
8165 else
8166 popupLeft = winWidth - popupWidth - scrollWidth - 1;
8168 popup.style.left = popupLeft + "px";
8169 popup.style.top = popupTop + "px";
8170 popup.style.display = "block";
8173 Popup.find = function(e)
8175 var pos = -1;
8176 for (var t=this.stack.length-1; t>=0; t--) {
8177 if(isDescendant(e,this.stack[t].popup))
8178 pos = i;
8180 return pos;
8183 Popup.remove = function(pos)
8185 if(!pos) var pos = 0;
8186 if(Popup.stack.length > pos) {
8187 Popup.removeFrom(pos);
8191 Popup.removeFrom = function(from)
8193 for(var t=Popup.stack.length-1; t>=from; t--) {
8194 var p = Popup.stack[t];
8195 removeClass(p.root,"highlight");
8196 removeNode(p.popup);
8198 Popup.stack = Popup.stack.slice(0,from);
8201 //--
8202 //-- Wizard support
8203 //--
8205 function Wizard(elem)
8207 if(elem) {
8208 this.formElem = findRelated(elem,"wizard","className");
8209 this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
8210 this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
8211 } else {
8212 this.formElem = null;
8213 this.bodyElem = null;
8214 this.footElem = null;
8218 Wizard.prototype.setValue = function(name,value)
8220 if(this.formElem)
8221 this.formElem[name] = value;
8224 Wizard.prototype.getValue = function(name)
8226 return this.formElem ? this.formElem[name] : null;
8229 Wizard.prototype.createWizard = function(place,title)
8231 this.formElem = createTiddlyElement(place,"form",null,"wizard");
8232 createTiddlyElement(this.formElem,"h1",null,null,title);
8233 this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
8234 this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
8237 Wizard.prototype.clear = function()
8239 removeChildren(this.bodyElem);
8242 Wizard.prototype.setButtons = function(buttonInfo,status)
8244 removeChildren(this.footElem);
8245 for(var t=0; t<buttonInfo.length; t++) {
8246 createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
8247 insertSpacer(this.footElem);
8249 if(typeof status == "string") {
8250 createTiddlyElement(this.footElem,"span",null,"status",status);
8254 Wizard.prototype.addStep = function(stepTitle,html)
8256 removeChildren(this.bodyElem);
8257 var w = createTiddlyElement(this.bodyElem,"div");
8258 createTiddlyElement(w,"h2",null,null,stepTitle);
8259 var step = createTiddlyElement(w,"div",null,"wizardStep");
8260 step.innerHTML = html;
8261 applyHtmlMacros(step,tiddler);
8264 Wizard.prototype.getElement = function(name)
8266 return this.formElem.elements[name];
8269 //--
8270 //-- ListView gadget
8271 //--
8273 var ListView = {};
8275 // Create a listview
8276 ListView.create = function(place,listObject,listTemplate,callback,className)
8278 var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
8279 var thead = createTiddlyElement(table,"thead");
8280 var r = createTiddlyElement(thead,"tr");
8281 for(var t=0; t<listTemplate.columns.length; t++) {
8282 var columnTemplate = listTemplate.columns[t];
8283 var c = createTiddlyElement(r,"th");
8284 var colType = ListView.columnTypes[columnTemplate.type];
8285 if(colType && colType.createHeader)
8286 colType.createHeader(c,columnTemplate,t);
8288 var tbody = createTiddlyElement(table,"tbody");
8289 for(var rc=0; rc<listObject.length; rc++) {
8290 rowObject = listObject[rc];
8291 r = createTiddlyElement(tbody,"tr");
8292 for(c=0; c<listTemplate.rowClasses.length; c++) {
8293 if(rowObject[listTemplate.rowClasses[c].field])
8294 addClass(r,listTemplate.rowClasses[c].className);
8296 rowObject.rowElement = r;
8297 rowObject.colElements = {};
8298 for(var cc=0; cc<listTemplate.columns.length; cc++) {
8299 c = createTiddlyElement(r,"td");
8300 columnTemplate = listTemplate.columns[cc];
8301 var field = columnTemplate.field;
8302 colType = ListView.columnTypes[columnTemplate.type];
8303 if(colType && colType.createItem)
8304 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
8305 rowObject.colElements[field] = c;
8308 if(callback && listTemplate.actions)
8309 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
8310 if(callback && listTemplate.buttons) {
8311 for(t=0; t<listTemplate.buttons.length; t++) {
8312 var a = listTemplate.buttons[t];
8313 if(a && a.name != "")
8314 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
8317 return table;
8320 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
8322 return function(e) {
8323 var view = findRelated(this,"TABLE",null,"previousSibling");
8324 var tiddlers = [];
8325 ListView.forEachSelector(view,function(e,rowName) {
8326 if(e.checked)
8327 tiddlers.push(rowName);
8329 if(tiddlers.length == 0 && !allowEmptySelection) {
8330 alert(config.messages.nothingSelected);
8331 } else {
8332 if(this.nodeName.toLowerCase() == "select") {
8333 callback(view,this.value,tiddlers);
8334 this.selectedIndex = 0;
8335 } else {
8336 callback(view,name,tiddlers);
8342 // Invoke a callback for each selector checkbox in the listview
8343 ListView.forEachSelector = function(view,callback)
8345 var checkboxes = view.getElementsByTagName("input");
8346 var hadOne = false;
8347 for(var t=0; t<checkboxes.length; t++) {
8348 var cb = checkboxes[t];
8349 if(cb.getAttribute("type") == "checkbox") {
8350 var rn = cb.getAttribute("rowName");
8351 if(rn) {
8352 callback(cb,rn);
8353 hadOne = true;
8357 return hadOne;
8360 ListView.getSelectedRows = function(view)
8362 var rowNames = [];
8363 ListView.forEachSelector(view,function(e,rowName) {
8364 if(e.checked)
8365 rowNames.push(rowName);
8367 return rowNames;
8370 ListView.columnTypes = {};
8372 ListView.columnTypes.String = {
8373 createHeader: function(place,columnTemplate,col)
8375 createTiddlyText(place,columnTemplate.title);
8377 createItem: function(place,listObject,field,columnTemplate,col,row)
8379 var v = listObject[field];
8380 if(v != undefined)
8381 createTiddlyText(place,v);
8385 ListView.columnTypes.WikiText = {
8386 createHeader: ListView.columnTypes.String.createHeader,
8387 createItem: function(place,listObject,field,columnTemplate,col,row)
8389 var v = listObject[field];
8390 if(v != undefined)
8391 wikify(v,place,null,null);
8395 ListView.columnTypes.Tiddler = {
8396 createHeader: ListView.columnTypes.String.createHeader,
8397 createItem: function(place,listObject,field,columnTemplate,col,row)
8399 var v = listObject[field];
8400 if(v != undefined && v.title)
8401 createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
8405 ListView.columnTypes.Size = {
8406 createHeader: ListView.columnTypes.String.createHeader,
8407 createItem: function(place,listObject,field,columnTemplate,col,row)
8409 var v = listObject[field];
8410 if(v != undefined) {
8411 var t = 0;
8412 while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
8413 t++;
8414 createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
8419 ListView.columnTypes.Link = {
8420 createHeader: ListView.columnTypes.String.createHeader,
8421 createItem: function(place,listObject,field,columnTemplate,col,row)
8423 var v = listObject[field];
8424 var c = columnTemplate.text;
8425 if(v != undefined)
8426 createTiddlyText(createExternalLink(place,v),c ? c : v);
8430 ListView.columnTypes.Date = {
8431 createHeader: ListView.columnTypes.String.createHeader,
8432 createItem: function(place,listObject,field,columnTemplate,col,row)
8434 var v = listObject[field];
8435 if(v != undefined)
8436 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
8440 ListView.columnTypes.StringList = {
8441 createHeader: ListView.columnTypes.String.createHeader,
8442 createItem: function(place,listObject,field,columnTemplate,col,row)
8444 var v = listObject[field];
8445 if(v != undefined) {
8446 for(var t=0; t<v.length; t++) {
8447 createTiddlyText(place,v[t]);
8448 createTiddlyElement(place,"br");
8454 ListView.columnTypes.Selector = {
8455 createHeader: function(place,columnTemplate,col)
8457 createTiddlyCheckbox(place,null,false,this.onHeaderChange);
8459 createItem: function(place,listObject,field,columnTemplate,col,row)
8461 var e = createTiddlyCheckbox(place,null,listObject[field],null);
8462 e.setAttribute("rowName",listObject[columnTemplate.rowName]);
8464 onHeaderChange: function(e)
8466 var state = this.checked;
8467 var view = findRelated(this,"TABLE");
8468 if(!view)
8469 return;
8470 ListView.forEachSelector(view,function(e,rowName) {
8471 e.checked = state;
8476 ListView.columnTypes.Tags = {
8477 createHeader: ListView.columnTypes.String.createHeader,
8478 createItem: function(place,listObject,field,columnTemplate,col,row)
8480 var tags = listObject[field];
8481 createTiddlyText(place,String.encodeTiddlyLinkList(tags));
8485 ListView.columnTypes.Boolean = {
8486 createHeader: ListView.columnTypes.String.createHeader,
8487 createItem: function(place,listObject,field,columnTemplate,col,row)
8489 if(listObject[field] == true)
8490 createTiddlyText(place,columnTemplate.trueText);
8491 if(listObject[field] == false)
8492 createTiddlyText(place,columnTemplate.falseText);
8496 ListView.columnTypes.TagCheckbox = {
8497 createHeader: ListView.columnTypes.String.createHeader,
8498 createItem: function(place,listObject,field,columnTemplate,col,row)
8500 var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
8501 e.setAttribute("tiddler",listObject.title);
8502 e.setAttribute("tag",columnTemplate.tag);
8504 onChange : function(e)
8506 var tag = this.getAttribute("tag");
8507 var tiddler = this.getAttribute("tiddler");
8508 store.setTiddlerTag(tiddler,this.checked,tag);
8512 ListView.columnTypes.TiddlerLink = {
8513 createHeader: ListView.columnTypes.String.createHeader,
8514 createItem: function(place,listObject,field,columnTemplate,col,row)
8516 var v = listObject[field];
8517 if(v != undefined) {
8518 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
8519 createTiddlyText(link,listObject[field]);
8524 //--
8525 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8526 //--
8528 // Clamp a number to a range
8529 Number.prototype.clamp = function(min,max)
8531 var c = this;
8532 if(c < min)
8533 c = min;
8534 if(c > max)
8535 c = max;
8536 return c;
8539 // Add indexOf function if browser does not support it
8540 if(!Array.indexOf) {
8541 Array.prototype.indexOf = function(item,from)
8543 if(!from)
8544 from = 0;
8545 for(var i=from; i<this.length; i++) {
8546 if(this[i] === item)
8547 return i;
8549 return -1;
8552 // Find an entry in a given field of the members of an array
8553 Array.prototype.findByField = function(field,value)
8555 for(var t=0; t<this.length; t++) {
8556 if(this[t][field] == value)
8557 return t;
8559 return null;
8562 // Return whether an entry exists in an array
8563 Array.prototype.contains = function(item)
8565 return this.indexOf(item) != -1;
8568 // Adds, removes or toggles a particular value within an array
8569 // value - value to add
8570 // mode - +1 to add value, -1 to remove value, 0 to toggle it
8571 Array.prototype.setItem = function(value,mode)
8573 var p = this.indexOf(value);
8574 if(mode == 0)
8575 mode = (p == -1) ? +1 : -1;
8576 if(mode == +1) {
8577 if(p == -1)
8578 this.push(value);
8579 } else if(mode == -1) {
8580 if(p != -1)
8581 this.splice(p,1);
8585 // Return whether one of a list of values exists in an array
8586 Array.prototype.containsAny = function(items)
8588 for(var i=0; i<items.length; i++) {
8589 if(this.indexOf(items[i]) != -1)
8590 return true;
8592 return false;
8595 // Return whether all of a list of values exists in an array
8596 Array.prototype.containsAll = function(items)
8598 for(var i = 0; i<items.length; i++) {
8599 if(this.indexOf(items[i]) == -1)
8600 return false;
8602 return true;
8605 // 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
8606 Array.prototype.pushUnique = function(item,unique)
8608 if(unique === false) {
8609 this.push(item);
8610 } else {
8611 if(this.indexOf(item) == -1)
8612 this.push(item);
8616 Array.prototype.remove = function(item)
8618 var p = this.indexOf(item);
8619 if(p != -1)
8620 this.splice(p,1);
8623 if(!Array.prototype.map) {
8624 Array.prototype.map = function(fn,thisObj)
8626 var scope = thisObj || window;
8627 var a = [];
8628 for(var i=0, j=this.length; i < j; ++i) {
8629 a.push(fn.call(scope,this[i],i,this));
8631 return a;
8634 // Get characters from the right end of a string
8635 String.prototype.right = function(n)
8637 return n < this.length ? this.slice(this.length-n) : this;
8640 // Trim whitespace from both ends of a string
8641 String.prototype.trim = function()
8643 return this.replace(/^\s*|\s*$/g,"");
8646 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
8647 String.prototype.unDash = function()
8649 var s = this.split("-");
8650 if(s.length > 1) {
8651 for(var t=1; t<s.length; t++)
8652 s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
8654 return s.join("");
8657 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
8658 String.prototype.format = function(substrings)
8660 var subRegExp = /(?:%(\d+))/mg;
8661 var currPos = 0;
8662 var r = [];
8663 do {
8664 var match = subRegExp.exec(this);
8665 if(match && match[1]) {
8666 if(match.index > currPos)
8667 r.push(this.substring(currPos,match.index));
8668 r.push(substrings[parseInt(match[1])]);
8669 currPos = subRegExp.lastIndex;
8671 } while(match);
8672 if(currPos < this.length)
8673 r.push(this.substring(currPos,this.length));
8674 return r.join("");
8677 // Escape any special RegExp characters with that character preceded by a backslash
8678 String.prototype.escapeRegExp = function()
8680 var s = "\\^$*+?()=!|,{}[].";
8681 var c = this;
8682 for(var t=0; t<s.length; t++)
8683 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
8684 return c;
8687 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
8688 String.prototype.escapeLineBreaks = function()
8690 return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
8693 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
8694 String.prototype.unescapeLineBreaks = function()
8696 return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
8699 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
8700 String.prototype.htmlEncode = function()
8702 return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
8705 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
8706 String.prototype.htmlDecode = function()
8708 return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
8711 // Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
8712 String.prototype.toJSONString = function()
8714 var m = {
8715 '\b': '\\b',
8716 '\f': '\\f',
8717 '\n': '\\n',
8718 '\r': '\\r',
8719 '\t': '\\t',
8720 '"' : '\\"',
8721 '\\': '\\\\'
8723 var replaceFn = function(a,b) {
8724 var c = m[b];
8725 if(c)
8726 return c;
8727 c = b.charCodeAt();
8728 return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
8730 if(/["\\\x00-\x1f]/.test(this))
8731 return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
8732 return '"' + this + '"';
8735 // Parse a space-separated string of name:value parameters
8736 // The result is an array of objects:
8737 // result[0] = object with a member for each parameter name, value of that member being an array of values
8738 // result[1..n] = one object for each parameter, with 'name' and 'value' members
8739 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
8741 var parseToken = function(match,p) {
8742 var n;
8743 if(match[p]) // Double quoted
8744 n = match[p];
8745 else if(match[p+1]) // Single quoted
8746 n = match[p+1];
8747 else if(match[p+2]) // Double-square-bracket quoted
8748 n = match[p+2];
8749 else if(match[p+3]) // Double-brace quoted
8750 try {
8751 n = match[p+3];
8752 if(allowEval)
8753 n = window.eval(n);
8754 } catch(ex) {
8755 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
8757 else if(match[p+4]) // Unquoted
8758 n = match[p+4];
8759 else if(match[p+5]) // empty quote
8760 n = "";
8761 return n;
8763 var r = [{}];
8764 var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
8765 var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
8766 var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
8767 var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
8768 var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
8769 var emptyQuote = "((?:\"\")|(?:''))";
8770 var skipSpace = "(?:\\s*)";
8771 var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
8772 var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
8773 var params = [];
8774 do {
8775 var match = re.exec(this);
8776 if(match) {
8777 var n = parseToken(match,1);
8778 if(noNames) {
8779 r.push({name:"",value:n});
8780 } else {
8781 var v = parseToken(match,8);
8782 if(v == null && defaultName) {
8783 v = n;
8784 n = defaultName;
8785 } else if(v == null && defaultValue) {
8786 v = defaultValue;
8788 r.push({name:n,value:v});
8789 if(cascadeDefaults) {
8790 defaultName = n;
8791 defaultValue = v;
8795 } while(match);
8796 // Summarise parameters into first element
8797 for(var t=1; t<r.length; t++) {
8798 if(r[0][r[t].name])
8799 r[0][r[t].name].push(r[t].value);
8800 else
8801 r[0][r[t].name] = [r[t].value];
8803 return r;
8806 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
8807 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
8808 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
8809 String.prototype.readMacroParams = function()
8811 var p = this.parseParams("list",null,true,true);
8812 var n = [];
8813 for(var t=1; t<p.length; t++)
8814 n.push(p[t].value);
8815 return n;
8818 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
8819 String.prototype.readBracketedList = function(unique)
8821 var p = this.parseParams("list",null,false,true);
8822 var n = [];
8823 for(var t=1; t<p.length; t++) {
8824 if(p[t].value)
8825 n.pushUnique(p[t].value,unique);
8827 return n;
8830 // Returns array with start and end index of chunk between given start and end marker, or undefined.
8831 String.prototype.getChunkRange = function(start,end)
8833 var s = this.indexOf(start);
8834 if(s != -1) {
8835 s += start.length;
8836 var e = this.indexOf(end,s);
8837 if(e != -1)
8838 return [s,e];
8842 // Replace a chunk of a string given start and end markers
8843 String.prototype.replaceChunk = function(start,end,sub)
8845 var r = this.getChunkRange(start,end);
8846 return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
8849 // Returns a chunk of a string between start and end markers, or undefined
8850 String.prototype.getChunk = function(start,end)
8852 var r = this.getChunkRange(start,end);
8853 if(r)
8854 return this.substring(r[0],r[1]);
8858 // Static method to bracket a string with double square brackets if it contains a space
8859 String.encodeTiddlyLink = function(title)
8861 return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
8864 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
8865 String.encodeTiddlyLinkList = function(list)
8867 if(list) {
8868 var results = [];
8869 for(var t=0; t<list.length; t++)
8870 results.push(String.encodeTiddlyLink(list[t]));
8871 return results.join(" ");
8872 } else {
8873 return "";
8877 // Convert a string as a sequence of name:"value" pairs into a hashmap
8878 String.prototype.decodeHashMap = function()
8880 var fields = this.parseParams("anon","",false);
8881 var r = {};
8882 for(var t=1; t<fields.length; t++)
8883 r[fields[t].name] = fields[t].value;
8884 return r;
8887 // Static method to encode a hashmap into a name:"value"... string
8888 String.encodeHashMap = function(hashmap)
8890 var r = [];
8891 for(var t in hashmap)
8892 r.push(t + ':"' + hashmap[t] + '"');
8893 return r.join(" ");
8896 // Static method to left-pad a string with 0s to a certain width
8897 String.zeroPad = function(n,d)
8899 var s = n.toString();
8900 if(s.length < d)
8901 s = "000000000000000000000000000".substr(0,d-s.length) + s;
8902 return s;
8905 String.prototype.startsWith = function(prefix)
8907 return !prefix || this.substring(0,prefix.length) == prefix;
8910 // Returns the first value of the given named parameter.
8911 function getParam(params,name,defaultValue)
8913 if(!params)
8914 return defaultValue;
8915 var p = params[0][name];
8916 return p ? p[0] : defaultValue;
8919 // Returns the first value of the given boolean named parameter.
8920 function getFlag(params,name,defaultValue)
8922 return !!getParam(params,name,defaultValue);
8925 // Substitute date components into a string
8926 Date.prototype.formatString = function(template)
8928 var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
8929 t = t.replace(/hh12/g,this.getHours12());
8930 t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
8931 t = t.replace(/hh/g,this.getHours());
8932 t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
8933 t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
8934 t = t.replace(/mm/g,this.getMinutes());
8935 t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
8936 t = t.replace(/ss/g,this.getSeconds());
8937 t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
8938 t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
8939 t = t.replace(/wYYYY/g,this.getYearForWeekNo());
8940 t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
8941 t = t.replace(/YYYY/g,this.getFullYear());
8942 t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
8943 t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
8944 t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
8945 t = t.replace(/MM/g,this.getMonth()+1);
8946 t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
8947 t = t.replace(/WW/g,this.getWeek());
8948 t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
8949 t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
8950 t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
8951 t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
8952 t = t.replace(/DD/g,this.getDate());
8953 var tz = this.getTimezoneOffset();
8954 var atz = Math.abs(tz);
8955 t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + String.zeroPad(Math.floor(atz / 60),2) + ':' + String.zeroPad(atz % 60,2));
8956 return t;
8959 Date.prototype.getWeek = function()
8961 var dt = new Date(this.getTime());
8962 var d = dt.getDay();
8963 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8964 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
8965 var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
8966 return Math.floor(n/7)+1;
8969 Date.prototype.getYearForWeekNo = function()
8971 var dt = new Date(this.getTime());
8972 var d = dt.getDay();
8973 if(d==0) d=7;// JavaScript Sun=0, ISO Sun=7
8974 dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
8975 return dt.getFullYear();
8978 Date.prototype.getHours12 = function()
8980 var h = this.getHours();
8981 return h > 12 ? h-12 : ( h > 0 ? h : 12 );
8984 Date.prototype.getAmPm = function()
8986 return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
8989 Date.prototype.daySuffix = function()
8991 return config.messages.dates.daySuffixes[this.getDate()-1];
8994 // Convert a date to local YYYYMMDDHHMM string format
8995 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
8997 return this.getFullYear() + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
9000 // Convert a date to UTC YYYYMMDDHHMM string format
9001 Date.prototype.convertToYYYYMMDDHHMM = function()
9003 return this.getUTCFullYear() + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
9006 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
9007 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
9009 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);
9012 // Static method to create a date from a UTC YYYYMMDDHHMM format string
9013 Date.convertFromYYYYMMDDHHMM = function(d)
9015 var hh = d.substr(8,2) || "00";
9016 var mm = d.substr(10,2) || "00";
9017 return new Date(Date.UTC(parseInt(d.substr(0,4),10),
9018 parseInt(d.substr(4,2),10)-1,
9019 parseInt(d.substr(6,2),10),
9020 parseInt(hh,10),
9021 parseInt(mm,10),0,0));
9025 //--
9026 //-- Crypto functions and associated conversion routines
9027 //--
9029 // Crypto 'namespace'
9030 function Crypto() {}
9032 // Convert a string to an array of big-endian 32-bit words
9033 Crypto.strToBe32s = function(str)
9035 var be=Array();
9036 var len=Math.floor(str.length/4);
9037 var i, j;
9038 for(i=0, j=0; i<len; i++, j+=4) {
9039 be[i]=((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
9041 while(j<str.length) {
9042 be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
9043 j++;
9045 return be;
9048 // Convert an array of big-endian 32-bit words to a string
9049 Crypto.be32sToStr = function(be)
9051 var str='';
9052 for(var i=0;i<be.length*32;i+=8)
9053 str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
9054 return str;
9057 // Convert an array of big-endian 32-bit words to a hex string
9058 Crypto.be32sToHex = function(be)
9060 var hex='0123456789ABCDEF';
9061 var str='';
9062 for(var i=0;i<be.length*4;i++)
9063 str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
9064 return str;
9067 // Return, in hex, the SHA-1 hash of a string
9068 Crypto.hexSha1Str = function(str)
9070 return Crypto.be32sToHex(Crypto.sha1Str(str));
9073 // Return the SHA-1 hash of a string
9074 Crypto.sha1Str = function(str)
9076 return Crypto.sha1(Crypto.strToBe32s(str),str.length);
9079 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
9080 Crypto.sha1 = function(x,blen)
9082 // Add 32-bit integers, wrapping at 32 bits
9083 function add32(a,b)
9085 var lsw=(a&0xFFFF)+(b&0xFFFF);
9086 var msw=(a>>16)+(b>>16)+(lsw>>16);
9087 return (msw<<16)|(lsw&0xFFFF);
9089 function AA(a,b,c,d,e)
9091 b=(b>>>27)|(b<<5);
9092 var lsw=(a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
9093 var msw=(a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
9094 return (msw<<16)|(lsw&0xFFFF);
9096 function RR(w,j)
9098 var n=w[j-3]^w[j-8]^w[j-14]^w[j-16];
9099 return (n>>>31)|(n<<1);
9102 var len=blen*8;
9103 x[len>>5] |= 0x80 << (24-len%32);
9104 x[((len+64>>9)<<4)+15]=len;
9105 var w=Array(80);
9107 var k1=0x5A827999;
9108 var k2=0x6ED9EBA1;
9109 var k3=0x8F1BBCDC;
9110 var k4=0xCA62C1D6;
9112 var h0=0x67452301;
9113 var h1=0xEFCDAB89;
9114 var h2=0x98BADCFE;
9115 var h3=0x10325476;
9116 var h4=0xC3D2E1F0;
9118 for(var i=0;i<x.length;i+=16) {
9119 var j=0;
9120 var t;
9121 var a=h0;
9122 var b=h1;
9123 var c=h2;
9124 var d=h3;
9125 var e=h4;
9126 while(j<16) {
9127 w[j]=x[i+j];
9128 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9129 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9131 while(j<20) {
9132 w[j]=RR(w,j);
9133 t=AA(e,a,d^(b&(c^d)),w[j],k1);
9134 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9136 while(j<40) {
9137 w[j]=RR(w,j);
9138 t=AA(e,a,b^c^d,w[j],k2);
9139 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9141 while(j<60) {
9142 w[j]=RR(w,j);
9143 t=AA(e,a,(b&c)|(d&(b|c)),w[j],k3);
9144 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9146 while(j<80) {
9147 w[j]=RR(w,j);
9148 t=AA(e,a,b^c^d,w[j],k4);
9149 e=d; d=c; c=(b>>>2)|(b<<30); b=a; a=t; j++;
9151 h0=add32(h0,a);
9152 h1=add32(h1,b);
9153 h2=add32(h2,c);
9154 h3=add32(h3,d);
9155 h4=add32(h4,e);
9157 return [h0,h1,h2,h3,h4];
9160 //--
9161 //-- RGB colour object
9162 //--
9164 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
9165 function RGB(r,g,b)
9167 this.r = 0;
9168 this.g = 0;
9169 this.b = 0;
9170 if(typeof r == "string") {
9171 if(r.substr(0,1) == "#") {
9172 if(r.length == 7) {
9173 this.r = parseInt(r.substr(1,2),16)/255;
9174 this.g = parseInt(r.substr(3,2),16)/255;
9175 this.b = parseInt(r.substr(5,2),16)/255;
9176 } else {
9177 this.r = parseInt(r.substr(1,1),16)/15;
9178 this.g = parseInt(r.substr(2,1),16)/15;
9179 this.b = parseInt(r.substr(3,1),16)/15;
9181 } else {
9182 var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
9183 var c = r.match(rgbPattern);
9184 if(c) {
9185 this.r = parseInt(c[1],10)/255;
9186 this.g = parseInt(c[2],10)/255;
9187 this.b = parseInt(c[3],10)/255;
9190 } else {
9191 this.r = r;
9192 this.g = g;
9193 this.b = b;
9195 return this;
9198 // Mixes this colour with another in a specified proportion
9199 // c = other colour to mix
9200 // f = 0..1 where 0 is this colour and 1 is the new colour
9201 // Returns an RGB object
9202 RGB.prototype.mix = function(c,f)
9204 return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
9207 // Return an rgb colour as a #rrggbb format hex string
9208 RGB.prototype.toString = function()
9210 return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
9211 ("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
9212 ("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
9215 //--
9216 //-- DOM utilities - many derived from www.quirksmode.org
9217 //--
9219 function drawGradient(place,horiz,locolors,hicolors)
9221 if(!hicolors)
9222 hicolors = locolors;
9223 for(var t=0; t<= 100; t+=2) {
9224 var bar = document.createElement("div");
9225 place.appendChild(bar);
9226 bar.style.position = "absolute";
9227 bar.style.left = horiz ? t + "%" : 0;
9228 bar.style.top = horiz ? 0 : t + "%";
9229 bar.style.width = horiz ? (101-t) + "%" : "100%";
9230 bar.style.height = horiz ? "100%" : (101-t) + "%";
9231 bar.style.zIndex = -1;
9232 var p = t/100*(locolors.length-1);
9233 bar.style.backgroundColor = hicolors[Math.floor(p)].mix(locolors[Math.ceil(p)],p-Math.floor(p)).toString();
9237 function createTiddlyText(parent,text)
9239 return parent.appendChild(document.createTextNode(text));
9242 function createTiddlyCheckbox(parent,caption,checked,onChange)
9244 var cb = document.createElement("input");
9245 cb.setAttribute("type","checkbox");
9246 cb.onclick = onChange;
9247 parent.appendChild(cb);
9248 cb.checked = checked;
9249 cb.className = "chkOptionInput";
9250 if(caption)
9251 wikify(caption,parent);
9252 return cb;
9255 function createTiddlyElement(parent,element,id,className,text,attribs)
9257 var e = document.createElement(element);
9258 if(className != null)
9259 e.className = className;
9260 if(id != null)
9261 e.setAttribute("id",id);
9262 if(text != null)
9263 e.appendChild(document.createTextNode(text));
9264 if(attribs) {
9265 for(var n in attribs) {
9266 e.setAttribute(n,attribs[n]);
9269 if(parent != null)
9270 parent.appendChild(e);
9271 return e;
9274 function addEvent(obj,type,fn)
9276 if(obj.attachEvent) {
9277 obj['e'+type+fn] = fn;
9278 obj[type+fn] = function(){obj['e'+type+fn](window.event);};
9279 obj.attachEvent('on'+type,obj[type+fn]);
9280 } else {
9281 obj.addEventListener(type,fn,false);
9285 function removeEvent(obj,type,fn)
9287 if(obj.detachEvent) {
9288 obj.detachEvent('on'+type,obj[type+fn]);
9289 obj[type+fn] = null;
9290 } else {
9291 obj.removeEventListener(type,fn,false);
9295 function addClass(e,className)
9297 var currClass = e.className.split(" ");
9298 if(currClass.indexOf(className) == -1)
9299 e.className += " " + className;
9302 function removeClass(e,className)
9304 var currClass = e.className.split(" ");
9305 var i = currClass.indexOf(className);
9306 while(i != -1) {
9307 currClass.splice(i,1);
9308 i = currClass.indexOf(className);
9310 e.className = currClass.join(" ");
9313 function hasClass(e,className)
9315 if(e.className) {
9316 if(e.className.split(" ").indexOf(className) != -1)
9317 return true;
9319 return false;
9322 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
9323 function findRelated(e,value,name,relative)
9325 name = name ? name : "tagName";
9326 relative = relative ? relative : "parentNode";
9327 if(name == "className") {
9328 while(e && !hasClass(e,value)) {
9329 e = e[relative];
9331 } else {
9332 while(e && e[name] != value) {
9333 e = e[relative];
9336 return e;
9339 // Resolve the target object of an event
9340 function resolveTarget(e)
9342 var obj;
9343 if(e.target)
9344 obj = e.target;
9345 else if(e.srcElement)
9346 obj = e.srcElement;
9347 if(obj.nodeType == 3) // defeat Safari bug
9348 obj = obj.parentNode;
9349 return obj;
9352 // Prevent an event from bubbling
9353 function stopEvent(e)
9355 var ev = e ? e : window.event;
9356 ev.cancelBubble = true;
9357 if(ev.stopPropagation) ev.stopPropagation();
9358 return false;
9361 // Return the content of an element as plain text with no formatting
9362 function getPlainText(e)
9364 var text = "";
9365 if(e.innerText)
9366 text = e.innerText;
9367 else if(e.textContent)
9368 text = e.textContent;
9369 return text;
9372 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
9373 function ensureVisible(e)
9375 var posTop = findPosY(e);
9376 var posBot = posTop + e.offsetHeight;
9377 var winTop = findScrollY();
9378 var winHeight = findWindowHeight();
9379 var winBot = winTop + winHeight;
9380 if(posTop < winTop) {
9381 return posTop;
9382 } else if(posBot > winBot) {
9383 if(e.offsetHeight < winHeight)
9384 return posTop - (winHeight - e.offsetHeight);
9385 else
9386 return posTop;
9387 } else {
9388 return winTop;
9392 // Get the current width of the display window
9393 function findWindowWidth()
9395 return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
9398 // Get the current height of the display window
9399 function findWindowHeight()
9401 return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
9404 // Get the current horizontal page scroll position
9405 function findScrollX()
9407 return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
9410 // Get the current vertical page scroll position
9411 function findScrollY()
9413 return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
9416 function findPosX(obj)
9418 var curleft = 0;
9419 while(obj.offsetParent) {
9420 curleft += obj.offsetLeft;
9421 obj = obj.offsetParent;
9423 return curleft;
9426 function findPosY(obj)
9428 var curtop = 0;
9429 while(obj.offsetParent) {
9430 curtop += obj.offsetTop;
9431 obj = obj.offsetParent;
9433 return curtop;
9436 // Blur a particular element
9437 function blurElement(e)
9439 if(e != null && e.focus && e.blur) {
9440 e.focus();
9441 e.blur();
9445 // Create a non-breaking space
9446 function insertSpacer(place)
9448 var e = document.createTextNode(String.fromCharCode(160));
9449 if(place)
9450 place.appendChild(e);
9451 return e;
9454 // Remove all children of a node
9455 function removeChildren(e)
9457 while(e && e.hasChildNodes())
9458 removeNode(e.firstChild);
9461 // Remove a node and all it's children
9462 function removeNode(e)
9464 scrubNode(e);
9465 e.parentNode.removeChild(e);
9468 // Remove any event handlers or non-primitve custom attributes
9469 function scrubNode(e)
9471 if(!config.browser.isIE)
9472 return;
9473 var att = e.attributes;
9474 if(att) {
9475 for(var t=0; t<att.length; t++) {
9476 var n = att[t].name;
9477 if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
9478 try {
9479 e[n] = null;
9480 } catch(ex) {
9485 var c = e.firstChild;
9486 while(c) {
9487 scrubNode(c);
9488 c = c.nextSibling;
9492 // Add a stylesheet, replacing any previous custom stylesheet
9493 function setStylesheet(s,id,doc)
9495 if(!id)
9496 id = "customStyleSheet";
9497 if(!doc)
9498 doc = document;
9499 var n = doc.getElementById(id);
9500 if(doc.createStyleSheet) {
9501 // Test for IE's non-standard createStyleSheet method
9502 if(n)
9503 n.parentNode.removeChild(n);
9504 // This failed without the &nbsp;
9505 doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
9506 } else {
9507 if(n) {
9508 n.replaceChild(doc.createTextNode(s),n.firstChild);
9509 } else {
9510 n = doc.createElement("style");
9511 n.type = "text/css";
9512 n.id = id;
9513 n.appendChild(doc.createTextNode(s));
9514 doc.getElementsByTagName("head")[0].appendChild(n);
9519 function removeStyleSheet(id)
9521 var e = document.getElementById(id);
9522 if(e)
9523 e.parentNode.removeChild(e);
9526 // Force the browser to do a document reflow when needed to workaround browser bugs
9527 function forceReflow()
9529 if(config.browser.isGecko) {
9530 setStylesheet("body {top:0px;margin-top:0px;}","forceReflow");
9531 setTimeout(function() {setStylesheet("","forceReflow");},1);
9535 // Replace the current selection of a textarea or text input and scroll it into view
9536 function replaceSelection(e,text)
9538 if(e.setSelectionRange) {
9539 var oldpos = e.selectionStart;
9540 var isRange = e.selectionEnd > e.selectionStart;
9541 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
9542 e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
9543 var linecount = e.value.split('\n').length;
9544 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
9545 e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
9546 } else if(document.selection) {
9547 var range = document.selection.createRange();
9548 if(range.parentElement() == e) {
9549 var isCollapsed = range.text == "";
9550 range.text = text;
9551 if(!isCollapsed) {
9552 range.moveStart('character', -text.length);
9553 range.select();
9559 // Returns the text of the given (text) node, possibly merging subsequent text nodes
9560 function getNodeText(e)
9562 var t = "";
9563 while(e && e.nodeName == "#text") {
9564 t += e.nodeValue;
9565 e = e.nextSibling;
9567 return t;
9570 // Returns true if the element e has a given ancestor element
9571 function isDescendant(e,ancestor)
9573 while(e) {
9574 if(e === ancestor)
9575 return true;
9576 e = e.parentNode;
9578 return false;
9581 //--
9582 //-- LoaderBase and SaverBase
9583 //--
9585 function LoaderBase() {}
9587 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9589 var title = this.getTitle(store,node);
9590 if(safeMode && store.isShadowTiddler(title))
9591 return;
9592 if(title) {
9593 var tiddler = store.createTiddler(title);
9594 this.internalizeTiddler(store,tiddler,title,node);
9595 tiddlers.push(tiddler);
9599 LoaderBase.prototype.loadTiddlers = function(store,nodes)
9601 var tiddlers = [];
9602 for(var t = 0; t < nodes.length; t++) {
9603 try {
9604 this.loadTiddler(store,nodes[t],tiddlers);
9605 } catch(ex) {
9606 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9609 return tiddlers;
9612 function SaverBase() {}
9614 SaverBase.prototype.externalize = function(store)
9616 var results = [];
9617 var tiddlers = store.getTiddlers("title");
9618 for(var t = 0; t < tiddlers.length; t++) {
9619 if(!tiddlers[t].doNotSave())
9620 results.push(this.externalizeTiddler(store, tiddlers[t]));
9622 return results.join("\n");
9625 //--
9626 //-- TW21Loader (inherits from LoaderBase)
9627 //--
9629 function TW21Loader() {}
9631 TW21Loader.prototype = new LoaderBase();
9633 TW21Loader.prototype.getTitle = function(store,node)
9635 var title = null;
9636 if(node.getAttribute) {
9637 title = node.getAttribute("title");
9638 if(!title)
9639 title = node.getAttribute("tiddler");
9641 if(!title && node.id) {
9642 var lenPrefix = store.idPrefix.length;
9643 if(node.id.substr(0,lenPrefix) == store.idPrefix)
9644 title = node.id.substr(lenPrefix);
9646 return title;
9649 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9651 var e = node.firstChild;
9652 var text = null;
9653 if(node.getAttribute("tiddler")) {
9654 text = getNodeText(e).unescapeLineBreaks();
9655 } else {
9656 while(e.nodeName!="PRE" && e.nodeName!="pre") {
9657 e = e.nextSibling;
9659 text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
9661 var modifier = node.getAttribute("modifier");
9662 var c = node.getAttribute("created");
9663 var m = node.getAttribute("modified");
9664 var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
9665 var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
9666 var tags = node.getAttribute("tags");
9667 var fields = {};
9668 var attrs = node.attributes;
9669 for(var i = attrs.length-1; i >= 0; i--) {
9670 var name = attrs[i].name;
9671 if(attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
9672 fields[name] = attrs[i].value.unescapeLineBreaks();
9675 tiddler.assign(title,text,modifier,modified,tags,created,fields);
9676 return tiddler;
9679 //--
9680 //-- TW21Saver (inherits from SaverBase)
9681 //--
9683 function TW21Saver() {}
9685 TW21Saver.prototype = new SaverBase();
9687 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
9689 try {
9690 var extendedAttributes = "";
9691 var usePre = config.options.chkUsePreForStorage;
9692 store.forEachField(tiddler,
9693 function(tiddler,fieldName,value) {
9694 // don't store stuff from the temp namespace
9695 if(typeof value != "string")
9696 value = "";
9697 if(!fieldName.match(/^temp\./))
9698 extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
9699 },true);
9700 var created = tiddler.created;
9701 var modified = tiddler.modified;
9702 var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
9703 attributes += (usePre && created == version.date) ? "" :' created="' + created.convertToYYYYMMDDHHMM() + '"';
9704 attributes += (usePre && modified == created) ? "" : ' modified="' + modified.convertToYYYYMMDDHHMM() +'"';
9705 var tags = tiddler.getTags();
9706 if(!usePre || tags)
9707 attributes += ' tags="' + tags.htmlEncode() + '"';
9708 return ('<div %0="%1"%2%3>%4</'+'div>').format([
9709 usePre ? "title" : "tiddler",
9710 tiddler.title.htmlEncode(),
9711 attributes,
9712 extendedAttributes,
9713 usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9715 } catch (ex) {
9716 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9720 //]]>
9721 </script>
9722 <script type="text/javascript">
9723 //<![CDATA[
9724 if(useJavaSaver)
9725 document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9726 //]]>
9727 </script>
9728 <!--POST-SCRIPT-START-->
9730 <!--POST-SCRIPT-END-->
9731 </body>
9732 </html>