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">
4 <script id=
"versionArea" type=
"text/javascript">
6 var version = {title:
"TiddlyWiki", major:
2, minor:
4, revision:
0, date: new Date(
"May 9, 2008"), extensions: {}};
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
42 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'
/>
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;}
55 <!--POST-HEAD-START-->
59 <body onload=
"main();" onunload=
"if(window.checkUnsavedChanges) checkUnsavedChanges(); if(window.scrubNodes) scrubNodes(document.body);">
64 Welcome to TiddlyWiki created by Jeremy Ruston, Copyright
© 2007 UnaMesa Association
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>
69 <div id=
"saveTest"></div>
70 <div id=
"backstageCloak"></div>
71 <div id=
"backstageButton"></div>
72 <div id=
"backstageArea"><div id=
"backstageToolbar"></div></div>
74 <div id=
"backstagePanel"></div>
76 <div id=
"contentWrapper"></div>
77 <div id=
"contentStash"></div>
79 <div title=
"MarkupPreHead">
81 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/
>
82 <!--}}}--
></pre>
84 <div title=
"ColorPalette">
101 <div title=
"StyleSheetColors">
103 body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
105 a {color:[[ColorPalette::PrimaryMid]];}
106 a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
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;}
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)';}
231 <div title=
"StyleSheetLayout">
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;}
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;}
405 <div title=
"StyleSheetLocale">
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.
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;}
419 <div title=
"StyleSheetPrint">
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;}
429 <div title=
"PageTemplate">
430 <pre><!--{{{--
>
431 <div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'
>
432 <div class='headerShadow'
>
433 <span class='siteTitle' refresh='content' tiddler='SiteTitle'
></span
>&nbsp;
434 <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'
></span
>
436 <div class='headerForeground'
>
437 <span class='siteTitle' refresh='content' tiddler='SiteTitle'
></span
>&nbsp;
438 <span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'
></span
>
441 <div id='mainMenu' refresh='content' tiddler='MainMenu'
></div
>
442 <div id='sidebar'
>
443 <div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'
></div
>
444 <div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'
></div
>
446 <div id='displayArea'
>
447 <div id='messageArea'
></div
>
448 <div id='tiddlerDisplay'
></div
>
450 <!--}}}--
></pre>
452 <div title=
"ViewTemplate">
453 <pre><!--{{{--
>
454 <div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'
></div
>
455 <div class='title' macro='view title'
></div
>
456 <div class='subtitle'
><span macro='view modifier link'
></span
>,
<span macro='view modified date'
></span
> (
<span macro='message views.wikified.createdPrompt'
></span
> <span macro='view created date'
></span
>)
</div
>
457 <div class='tagging' macro='tagging'
></div
>
458 <div class='tagged' macro='tags'
></div
>
459 <div class='viewer' macro='view text wikified'
></div
>
460 <div class='tagClear'
></div
>
461 <!--}}}--
></pre>
463 <div title=
"EditTemplate">
464 <pre><!--{{{--
>
465 <div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'
></div
>
466 <div class='title' macro='view title'
></div
>
467 <div class='editor' macro='edit title'
></div
>
468 <div macro='annotations'
></div
>
469 <div class='editor' macro='edit text'
></div
>
470 <div class='editor' macro='edit tags'
></div
><div class='editorFooter'
><span macro='message views.editor.tagPrompt'
></span
><span macro='tagChooser'
></span
></div
>
471 <!--}}}--
></pre>
473 <div title=
"GettingStarted">
474 <pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
475 * SiteTitle
& 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:
<<option txtUserName
>></pre>
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 <<option txtUserName
>>
486 <<option chkSaveBackups
>> SaveBackups
487 <<option chkAutoSave
>> AutoSave
488 <<option chkRegExpSearch
>> RegExpSearch
489 <<option chkCaseSensitiveSearch
>> CaseSensitiveSearch
490 <<option chkAnimate
>> EnableAnimations
493 Also see AdvancedOptions
</pre>
495 <div title=
"ImportTiddlers">
496 <pre><<importTiddlers
>></pre>
499 <!--POST-SHADOWAREA-->
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>
504 <div title=
"Apache 2.0 Open Source license" modifier=
"AdamCrossland" created=
"200805202001" modified=
"200805211430" changecount=
"5">
506 Copyright
2008 Adam A. Crossland
508 Licensed under the Apache License, Version
2.0 (the
"License
");
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
"AS IS
" 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.
521 <div title=
"ChangeLog" modifier=
"AdamCrossland" created=
"200812041728" modified=
"200812042045" changecount=
"9">
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>
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
<html
><a href=
"http://groups.google.com/group/google-appengine/topics
">the Google AppEngine Group.
</a
></html
></pre>
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>
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):
548 from taggable import Tag
549 tag_name = self.request.get('tag')
550 tag = db.Query(Tag).filter('tag =', tag_name).fetch(
1)[
0]
552 new_tag = Tag.get_or_create(tag_name)
553 for each_tagged in tag.tagged:
554 new_tag.add_tagged(each_tagged)
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>
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>
569 <div title=
"DefaultTiddlers" modifier=
"AdamCrossland" created=
"200805202104" changecount=
"1">
570 <pre>[[Introduction]]
</pre>
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
<html
><a href=
"http://code.google.com/appengine/
">The Google AppEngine website
</a
></html
>.
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>
584 <div title=
"Introduction" modifier=
"AdamCrossland" created=
"200805201810" modified=
"200812051536" changecount=
"8">
585 <pre>''Taggable'' is a
<html
><a href=
"http://www.linuxjournal.com/node/
4540/print
">mixin
</a
></html
> 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>
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>
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>
599 <div title=
"MainMenu" modifier=
"AdamCrossland" created=
"200805201814" modified=
"200812041913" changecount=
"10">
600 <pre>[[Introduction|Introduction]]
603 <<tag api
>>
605 [[License|Apache
2.0 Open Source license]]
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
<html
><a href=
"http://code.google.com/appengine/docs/webapp/requesthandlers.html
">documentation.
</a
></html
>
614 <div title=
"SideBarOptions" modifier=
"AdamCrossland" created=
"200805211439" changecount=
"1">
615 <pre><<search
>><<closeAll
>><<permaview
>><<newTiddler
>><<saveChanges
>><<slider chkSliderOptionsPanel OptionsPanel
"options »
" "Change TiddlyWiki advanced options
">></pre>
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>
620 <div title=
"SiteTitle" modifier=
"AdamCrossland" created=
"200805201805" changecount=
"1">
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:
646 <div class=
"posttags
">tags:
&nbsp;
647 {% for each_tag in post.tags %}
648 <a href=
"/searchbytag?tag={{ each_tag.tag|escape }}
">{{ each_tag.tag }}
</a
>{% if forloop.last %}{% else %}, {% endif %}
656 <td
>Tags:
</td
>
658 <input type=
"TEXT
" name=
"tags
" size=
"106" value=
"{% if post %}{{ post.tags_string }}{% endif %}
" /
>
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):
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')
674 . do whatever else you need to do in your handler...
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>
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]]
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>
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>
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]]
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>
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.
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>
727 <div title=
"Tag expire_cached_tags Method" modifier=
"AdamCrossland" created=
"200812050015" tags=
"api" changecount=
"1">
728 <pre>//Tag.expire_cached_tags()//
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>
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//
741 class SearchByTag(RequestHandler):
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)
> 0:
748 tag = Tag.get_by_name(requested_tag)
751 posts = Post.get(tag.tagged)
753 self.template_values['posts'] = posts
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]]
764 for each_tag in tags:
765 each_tag = string.strip(each_tag)
766 if len(each_tag)
> 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)
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.
783 def popular_tags(cls, limit=
5):
784 from google.appengine.api import memcache
786 tags = memcache.get('popular_tags')
788 tags = Tag.get_tags_by_frequency(limit)
789 memcache.add('popular_tags', tags,
3600)
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
801 requested_tag = self.request.get('tag')
802 if requested_tag is not None and len(requested_tag)
> 0:
803 tag = Tag.get_by_name(requested_tag)
806 posts = Post.get(tag.tagged)
808 self.template_values['posts'] = posts
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
818 def __get_tags(self):
819 "Get a List of Tag objects for all Tags that apply to this object.
"
820 if self.__tags is None or len(self.__tags) ==
0:
821 self.__tags = Tag.get_tags_for_key(self.key())
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.
831 This method is a thin wrapper around [[get_tags_by_frequency|Tag get_tags_by_frequency]] that caches the returned values.
835 self.template_values['popular_tags'] = Tag.popular_tags()
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.
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>
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>
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>
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>
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>
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]]
867 *[[tags_string|Taggable tags_string Method]]
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)
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>
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:
898 self.tag_separator =
";
" # 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 =
";
" # 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 =
";
" # Made it semi-colon instead of comma
917 newpost.set_tags_from_string(
"foo; bar; bletch; this is a tag; tags rule
"
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.
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)
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)
945 new_post.tags = new_tags
948 raise Exception(
"Must supply both new_title and new_body when creating a new Post.
")
954 <div class=
"posttags
">tags:
&nbsp;
955 {% for each_tag in post.tags %}
956 <a href=
"/searchbytag?tag={{ each_tag.tag|escape }}
">{{ each_tag.tag }}
</a
>{% if forloop.last %}{% else %}, {% endif %}
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>
968 <div title=
"TaggableAPI" modifier=
"AdamCrossland" created=
"200805201953" modified=
"200805201956" changecount=
"6">
969 <pre><<tagging api
>></pre>
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>
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 = [
"foo
",
"bar
",
"bletch
",
"this is a tag
",
"tags rule
"]
978 my_taggable_object.set_tags(tag_list)
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 =
"foo, bar, bletch, this is a tag, tags rule
"
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>
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>
994 <!--POST-STOREAREA-->
995 <!--POST-BODY-START-->
997 <script id=
"jsArea" type=
"text/javascript">
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
1010 //-- Configuration repository
1013 // Miscellaneous options
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 = {};
1027 config.adaptors = {};
1028 config.defaultAdaptor = null;
1034 config.annotations = {};
1036 // Custom fields to be automatically added to new tiddlers
1037 config.defaultCustomFields = {};
1046 // Options that can be set in the options panel and/or cookies
1048 chkRegExpSearch: false,
1049 chkCaseSensitiveSearch: false,
1050 chkIncrementalSearch: true,
1052 chkSaveBackups: true,
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",
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 = {
1095 // More messages (rather a legacy layout that shouldn't really be like this)
1106 config.backstageTasks = [
"save",
"sync",
"importTask",
"tweak",
"upgrade",
"plugins"];
1108 // Macros; each has a 'handler' member that is inserted later
1112 search: {sizeTextbox:
15},
1138 view: {defaultView:
"text"},
1146 source:
"http://www.tiddlywiki.com/upgrade/",
1147 backupExtension:
"pre.core.upgrade"
1153 // Commands supported by the toolbar macro
1158 saveTiddler: {hideReadOnly: true},
1160 deleteTiddler: {hideReadOnly: true},
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();
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
");
1225 function() {return config.browser.isIE;},
1226 function() {return true;}
1230 downTriangle: ["\u25BC
","\u25BE
"],
1231 downArrow: ["\u2193
","\u2193
"],
1232 bentArrowLeft: ["\u2190
","\u21A9
"],
1233 bentArrowRight: ["\u2192
","\u21AA
"]
1238 //-- Shadow tiddlers
1241 config.shadowTiddlers = {
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>>'
1258 //-- Translateable strings
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,{
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:
",
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
",
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,{
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,{
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
"}
1405 merge(config.macros.search,{
1407 prompt: "Search this TiddlyWiki
",
1409 successMsg: "%
0 tiddlers found matching %
1",
1410 failureMsg: "No tiddlers found matching %
0"});
1412 merge(config.macros.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,{
1432 prompt: "Close all displayed tiddlers (except any that are being edited)
"});
1434 merge(config.macros.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
",
1443 merge(config.macros.newTiddler,{
1444 label: "new tiddler
",
1445 prompt: "Create a new tiddler
",
1446 title: "New Tiddler
",
1449 merge(config.macros.newJournal,{
1450 label: "new journal
",
1451 prompt: "Create a new tiddler from the current date and time
",
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)//
",
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'}
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
",
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'}
1494 {className: 'error', field: 'error'},
1495 {className: 'warning', field: 'warning'}
1499 merge(config.macros.toolbar,{
1501 morePrompt: "Reveal further commands
"
1504 merge(config.macros.refreshDisplay,{
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>",
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
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)
",
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'}
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,{
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'}
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
1601 syncPrompt: "Sync these tiddlers
",
1602 hasChanged: "Changed while unplugged
",
1603 hasNotChanged: "Unchanged while unplugged
",
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,{
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,{
1628 tooltip: "Edit this tiddler
",
1629 readOnlyText: "view
",
1630 readOnlyTooltip: "View the source of this tiddler
"});
1632 merge(config.commands.saveTiddler,{
1634 tooltip: "Save changes to this tiddler
"});
1636 merge(config.commands.cancelTiddler,{
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,{
1645 tooltip: "Delete this tiddler
",
1646 warning: "Are you sure you want to delete '%
0'?
"});
1648 merge(config.commands.permalink,{
1650 tooltip: "Permalink for this tiddler
"});
1652 merge(config.commands.references,{
1654 tooltip: "Show tiddlers that link to this one
",
1655 popupNone: "No references
"});
1657 merge(config.commands.jump,{
1659 tooltip: "Jump to another open tiddler
"});
1661 merge(config.commands.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,{
1673 tooltip: "Show the extended fields of this tiddler
",
1674 emptyText: "There are no extended fields for this tiddler
",
1677 {name: 'Field', field: 'field', title: "Field
", type: 'String'},
1678 {name: 'Value', field: 'value', title: "Value
", type: 'String'}
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
"
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
");
1756 var t10,t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
1758 window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
1759 params = getParameters();
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);
1767 loadOptionsCookie();
1768 for(var s=0; s<config.notifyTiddlers.length; s++)
1769 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
1771 loadShadowTiddlers();
1773 store.loadFromDiv("storeArea
","store
",true);
1775 invokeParamifier(params,"onload
");
1777 readOnly = (window.location.protocol == "file:
") ? false : config.options.chkHttpReadOnly;
1778 showBackstage = !readOnly;
1779 var pluginProblem = loadPlugins();
1781 formatter = new Formatter(config.formatters);
1782 story.switchTheme(config.options.txtTheme);
1783 invokeParamifier(params,"onconfig
");
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();
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
");
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);
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;});
1841 function loadPlugins()
1845 var tiddlers = store.getTaggedTiddlers("systemConfig
");
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;
1861 var visit = function(p) {
1865 var reqs = p.Requires;
1867 reqs = reqs.readBracketedList();
1868 for(var i=0; i<reqs.length; i++)
1869 visit(map[reqs[i]]);
1873 for(i=0; i<nPlugins; i++)
1874 visit(installedPlugins[i]);
1875 for(i=0; i<toLoad.length; i++) {
1878 tiddler = p.tiddler;
1879 if(isPluginExecutable(p)) {
1880 if(isPluginEnabled(p)) {
1882 var startTime = new Date();
1885 window.eval(tiddler.text);
1888 p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
1891 pluginInfo.startupTime = String((new Date()) - startTime) + "ms
";
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;
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;
1924 return verifyTail(plugin,false,config.messages.pluginVersionError);
1929 function isPluginEnabled(plugin)
1931 if(plugin.tiddler.isTagged("systemConfigDisable
"))
1932 return verifyTail(plugin,false,config.messages.pluginDisabled);
1936 function verifyTail(plugin,result,message)
1938 plugin.log.push(message);
1942 function invokeMacro(place,macro,params,wikifier,tiddler)
1945 var m = config.macros[macro];
1947 m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
1949 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
1951 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
1959 function getParameters()
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);
1970 function invokeParamifier(params,handler)
1972 if(!params || params.length == undefined || params.length <= 1)
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) {
2025 story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
2026 story.focusTiddler(v,"text
");
2031 config.paramifiers.newJournal = {
2032 onstart: function(v) {
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) {
2062 //-- Formatter helpers
2065 function Formatter(formatters)
2067 this.formatters = [];
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)
2086 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
2087 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
2088 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
2090 if(lookaheadMatch[1]) {
2091 s = lookaheadMatch[1].unDash();
2092 v = lookaheadMatch[2];
2094 s = lookaheadMatch[3].unDash();
2095 v = lookaheadMatch[4];
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);
2107 applyCssHelper: function(e,styles)
2109 for(var t=0; t< styles.length; t++) {
2111 e.style[styles[t].style] = styles[t].value;
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)) {
2135 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg
");
2136 if(urlRegExp.exec(link)) {
2139 if(link.indexOf(".
")!=-1 || link.indexOf("\\
")!=-1 || link.indexOf("/
")!=-1 || link.indexOf("#
")!=-1) {
2148 //-- Standard formatters
2151 config.formatters = [
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;
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;
2176 if(nextRowType != currRowType) {
2177 rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
2178 currRowType = nextRowType;
2180 if(currRowType == "c
") {
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);
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);
2195 this.lookaheadRegExp.lastIndex = w.nextMatch;
2196 lookaheadMatch = this.lookaheadRegExp.exec(w.source);
2199 rowHandler: function(w,e,prevColumns)
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] == "~
") {
2209 var last = prevColumns[col];
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] == ">") {
2220 w.nextMatch = this.cellRegExp.lastIndex-1;
2221 } else if(cellMatch[2]) {
2223 if(prevCell && colSpanCount > 1) {
2224 prevCell.setAttribute("colspan
",colSpanCount);
2225 prevCell.setAttribute("colSpan
",colSpanCount); // Needed for IE
2227 w.nextMatch = this.cellRegExp.lastIndex;
2232 var styles = config.formatterHelpers.inlineCssHelper(w);
2233 var spaceLeft = false;
2234 var chr = w.source.substr(w.nextMatch,1);
2238 chr = w.source.substr(w.nextMatch,1);
2242 cell = createTiddlyElement(e,"th
");
2245 cell = createTiddlyElement(e,"td
");
2248 prevColumns[col] = {rowSpanCount:1,element:cell};
2249 if(colSpanCount > 1) {
2250 cell.setAttribute("colspan
",colSpanCount);
2251 cell.setAttribute("colSpan
",colSpanCount); // Needed for IE
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
";
2259 cell.align = "right
";
2263 this.cellRegExp.lastIndex = w.nextMatch;
2264 cellMatch = this.cellRegExp.exec(w.source);
2272 termRegExp: /(\n)/mg,
2273 handler: function(w)
2275 w.subWikifyTerm(createTiddlyElement(w.output,"h
" + w.matchLength),this.termRegExp);
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]) {
2296 } else if(lookaheadMatch[2]) {
2299 } else if(lookaheadMatch[3]) {
2302 } else if(lookaheadMatch[4]) {
2307 baseType = listType;
2308 listLevel = lookaheadMatch[0].length;
2309 w.nextMatch += lookaheadMatch[0].length;
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;
2319 } else if(listLevel < currLevel) {
2320 for(t=currLevel; t>listLevel; t--)
2322 } else if(listLevel == currLevel && listType != currType) {
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
",
2339 termRegExp: /(^<<<(\n|$))/mg,
2340 element: "blockquote
",
2341 handler: config.formatterHelpers.createElementAndWikify
2345 name: "quoteByLine
",
2347 lookaheadRegExp: /^>+/mg,
2348 termRegExp: /(\n)/mg,
2349 element: "blockquote
",
2350 handler: function(w)
2352 var stack = [w.output];
2354 var newLevel = w.matchLength;
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--)
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;
2371 newLevel = lookaheadMatch[0].length;
2372 w.nextMatch += lookaheadMatch[0].length;
2380 match: "^----+$\\n?
",
2381 handler: function(w)
2383 createTiddlyElement(w.output,"hr
");
2388 name: "monospacedByLine
",
2389 match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|
<!--\\{\\{\\{-->)\\n
",
2391 handler: function(w)
2393 switch(w.matchText) {
2394 case "/*{{{*/\n
": // CSS
2395 this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg;
2397 case "{{{\n
": // monospaced block
2398 this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg;
2400 case "//{{{\n
": // plugin
2401 this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg;
2403 case "<!--{{{-->\n
": //template
2404 this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg;
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);
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);
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) {
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);
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;
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);
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);
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);
2486 w.outputText(w.output,w.matchStart,w.nextMatch);
2493 match: config.textPrimitives.urlPattern,
2494 handler: function(w)
2496 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
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) {
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])
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;
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",
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) {
2565 w.subWikifyTerm(w.output.appendChild(document.createElement(
"strong")),/('')/mg);
2568 w.subWikifyTerm(createTiddlyElement(w.output,
"em"),/(\/\/)/mg);
2571 w.subWikifyTerm(createTiddlyElement(w.output,
"u"),/(__)/mg);
2574 w.subWikifyTerm(createTiddlyElement(w.output,
"sup"),/(\^\^)/mg);
2577 w.subWikifyTerm(createTiddlyElement(w.output,
"sub"),/(~~)/mg);
2580 w.subWikifyTerm(createTiddlyElement(w.output,
"strike"),/(--)/mg);
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;
2596 name:
"customFormat",
2598 handler: function(w)
2600 switch(w.matchText) {
2602 var e = createTiddlyElement(w.output,
"span");
2603 var styles = config.formatterHelpers.inlineCssHelper(w);
2604 if(styles.length ==
0)
2605 e.className =
"marked";
2607 config.formatterHelpers.applyCssHelper(e,styles);
2608 w.subWikifyTerm(e,/(@@)/mg);
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);
2627 handler: function(w)
2629 createTiddlyElement(w.output,
"span").innerHTML =
"—";
2635 match:
"\\n|<br ?/?>",
2636 handler: function(w)
2638 createTiddlyElement(w.output,
"br");
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;
2672 function getParser(tiddler,format)
2676 format = tiddler.fields["wikiformat
"];
2679 for(i in config.parsers) {
2680 if(format == config.parsers[i].format)
2681 return config.parsers[i];
2684 for(i in config.parsers) {
2685 if(tiddler.isTagged(config.parsers[i].formatTag))
2686 return config.parsers[i];
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
";
2706 if(source && source != "") {
2708 tiddler = new Tiddler("temp
");
2709 var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
2710 wikifier.isStatic = true;
2711 wikifier.subWikify(e);
2718 function wikifyPlain(title,theStore,limit)
2722 if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
2723 return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
2729 function wikifyPlainText(text,limit,tiddler)
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;
2749 this.formatter = formatter;
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
";
2767 var text = getPlainText(e);
2772 Wikifier.prototype.subWikify = function(output,terminator)
2776 this.subWikifyTerm(output,new RegExp("(
" + terminator + ")
","mg
"));
2778 this.subWikifyUnterm(output);
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;
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;
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;
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));
2877 //-- Macro definitions
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);
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];
2939 var tiddlers = store.filterTiddlers(filter);
2940 for(var t=0; t<tiddlers.length; t++)
2941 results.push(tiddlers[t].title);
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);
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));
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);
2992 wrapper.setAttribute("refresh
","content
");
2993 wrapper.setAttribute("tiddler
",tiddlerName);
2995 var text = store.getTiddlerText(tiddlerName);
2997 var stack = config.macros.tiddler.tiddlerStack;
2998 if(stack.indexOf(tiddlerName) !== -1)
3000 stack.push(tiddlerName);
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);
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();
3074 config.macros.permaview.handler = function(place)
3076 createTiddlyButton(place,this.label,this.prompt,this.onClick);
3079 config.macros.permaview.onClick = function(e)
3085 config.macros.saveChanges.handler = function(place,macroName,params)
3088 createTiddlyButton(place,params[0] || this.label,params[1] || this.prompt,this.onClick,null,null,this.accessKey);
3091 config.macros.saveChanges.onClick = function(e)
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
"));
3106 n.style.display = isOpen ? "none
" : "block
";
3107 config.options[cookie] = !isOpen;
3108 saveOptionCookie(cookie);
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);
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]);
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";
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;
3155 drawGradient(panel,params[1].value != "vert
",locolors,hicolors);
3157 wikifier.subWikify(panel,">>");
3159 panel.style.height = "100%
";
3160 panel.style.width = "100%
";
3164 config.macros.message.handler = function(place,macroName,params)
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));
3173 return root[names[nameIndex]];
3177 var m = lookupMessage(config,0);
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) {
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]);
3208 var type = params[1] ? params[1] : config.macros.view.defaultView;
3209 var handler = config.macros.view.views[type];
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);
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);
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);
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
"));
3270 e.cancelBubble = true;
3271 if(e.stopPropagation) e.stopPropagation();
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
");
3281 story.setTiddlerTag(title,tag,0);
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)
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)
3311 var text = a.format([title]);
3312 wikify(text,createTiddlyElement(place,"div
",null,"annotation
"),null,tiddler);
3316 //-- NewTiddler and NewJournal macros
3319 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
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
");
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);
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);
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);
3373 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
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)
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);
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
");
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");
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);
3435 config.macros.search.onKeyPress = function(ev)
3437 var e = ev ? ev : window.event;
3439 case 13: // Ctrl-Enter
3440 case 10: // Ctrl-Enter on IE PC
3441 config.macros.search.doSearch(this);
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);
3454 config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
3457 if(config.macros.search.timeout)
3458 clearTimeout(config.macros.search.timeout);
3463 config.macros.search.onFocus = function(e)
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);
3488 if(config.options[cookie] == label)
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
"));
3503 config.macros.tabs.switchTab = function(tabset,tab)
3505 var cookie = tabset.getAttribute("cookie
");
3507 var nodes = tabset.childNodes;
3508 for(var t=0; t<nodes.length; t++) {
3509 if(nodes[t].getAttribute && nodes[t].getAttribute("tab
") == tab) {
3511 theTab.className = "tab tabSelected
";
3513 nodes[t].className = "tab tabUnselected
";
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));
3524 config.options[cookie] = tab;
3525 saveOptionCookie(cookie);
3531 //-- Tiddler toolbar
3534 // Create a toolbar command button
3535 config.macros.toolbar.createCommand = function(place,commandName,tiddler,className)
3537 if(typeof commandName != "string
") {
3539 for(var t in config.commands) {
3540 if(config.commands[t] == commandName)
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);
3551 switch(command.type) {
3553 cmd = this.onClickPopup;
3557 cmd = this.onClickCommand;
3560 var btn = createTiddlyButton(null,text,tooltip,cmd);
3561 btn.setAttribute("commandName
",commandName);
3562 btn.setAttribute("tiddler
",tiddler.title);
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);
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);
3626 config.macros.toolbar.onClickMore = function(ev)
3628 var e = this.nextSibling;
3629 e.style.display = "inline
";
3634 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
3636 for(var t=0; t<params.length; t++) {
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
";
3648 switch(c.substr(0,1)) {
3650 className = "defaultCommand
";
3654 className = "cancelCommand
";
3658 if(c in config.commands)
3659 this.createCommand(place,c,tiddler,className);
3666 //-- Menu and toolbar commands
3669 config.commands.closeTiddler.handler = function(event,src,title)
3671 if(story.isDirty(title) && !readOnly) {
3672 if(!confirm(config.commands.cancelTiddler.warning.format([title])))
3675 story.setDirty(title,false);
3676 story.closeTiddler(title,true);
3680 config.commands.closeOthers.handler = function(event,src,title)
3682 story.closeAllTiddlers(title);
3686 config.commands.editTiddler.handler = function(event,src,title)
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
");
3696 config.commands.saveTiddler.handler = function(event,src,title)
3698 var newTitle = story.saveTiddler(title,event.shiftKey);
3700 story.displayTiddler(null,newTitle);
3704 config.commands.cancelTiddler.handler = function(event,src,title)
3706 if(story.hasChanges(title) && !readOnly) {
3707 if(!confirm(this.warning.format([title])))
3710 story.setDirty(title,false);
3711 story.displayTiddler(null,title);
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]));
3721 store.removeTiddler(title);
3722 story.closeTiddler(title,true);
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;
3736 config.commands.references.handlePopup = function(popup,title)
3738 var references = store.getReferringTiddlers(title);
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);
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);
3762 var serverType = tiddler.getServerType();
3763 var serverHost = tiddler.fields['server.host'];
3764 var serverWorkspace = tiddler.fields['server.workspace'];
3765 if(!serverWorkspace)
3766 serverWorkspace = "";
3768 var e = createTiddlyElement(popup,"li
",null,"popupMessage
");
3769 e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
3771 createTiddlyElement(popup,"li
",null,"popupMessage
",config.commands.syncing.notCurrentlySyncing);
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++) {
3784 var feedServerType = store.getTiddlerSlice(f.title,"Type
");
3786 feedServerType = "file
";
3787 var feedServerHost = store.getTiddlerSlice(f.title,"URL
");
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;
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
");
3812 store.addTiddlerFields(tiddler,{
3813 'server.type': serverType,
3814 'server.host': this.getAttribute("server.host
"),
3815 'server.workspace': this.getAttribute("server.workspace
")
3818 store.setValue(tiddler,'server',null);
3823 config.commands.fields.handlePopup = function(popup,title)
3825 var tiddler = store.fetchTiddler(title);
3829 store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
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);
3838 createTiddlyElement(popup,"div
",null,null,this.emptyText);
3842 //-- Tiddler() object
3845 function Tiddler(title)
3849 this.modifier = null;
3850 this.created = new Date();
3851 this.modified = this.created;
3853 this.linksUpdated = false;
3859 Tiddler.prototype.getLinks = function()
3861 if(this.linksUpdated==false)
3866 // Returns the fields that are inherited in string field:"value
" field2:"value2
" format
3867 Tiddler.prototype.getInheritedFields = function()
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)
3905 return changeCount > 0;
3908 // Return the tiddler as an RSS item
3909 Tiddler.prototype.toRssItem = function(uri)
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);
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)
3940 if(text != undefined)
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)
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()
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) {
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]);
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;
4017 modifier = config.messages.subtitleUnknown;
4018 var modified = this.modified;
4020 modified = modified.toLocaleString();
4022 modified = config.messages.subtitleUnknown;
4023 return config.messages.tiddlerLinkTooltip.format([this.title,modifier,modified]);
4026 Tiddler.prototype.isReadOnly = function()
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'];
4047 serverType = this.fields['wikiformat'];
4048 if(serverType && !config.adaptors[serverType])
4053 Tiddler.prototype.getAdaptor = function()
4055 var serverType = this.getServerType();
4056 return serverType ? new config.adaptors[serverType] : null;
4060 //-- TiddlyWiki() object contains Tiddler()s
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() {
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)
4100 TiddlyWiki.prototype.isDirty = function()
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))
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];
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))
4146 this.namedNotifications.push({name: title, notify: fn});
4150 TiddlyWiki.prototype.removeTiddler = function(title)
4152 var tiddler = this.fetchTiddler(title);
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);
4180 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
4184 var pos = title.indexOf(config.textPrimitives.sectionSeparator);
4187 section = title.substr(pos + config.textPrimitives.sectionSeparator.length);
4188 title = title.substr(0,pos);
4190 pos = title.indexOf(config.textPrimitives.sliceSeparator);
4192 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
4196 var tiddler = this.fetchTiddler(title);
4199 return tiddler.text;
4200 var re = new RegExp("(^!{
1,
6}
" + section.escapeRegExp() + "[ \t]*\n)
","mg
");
4202 var match = re.exec(tiddler.text);
4204 var t = tiddler.text.substr(match.index+match[1].length);
4207 match = re2.exec(t); //# search for the next heading
4209 t = t.substr(0,match.index-1);//# don't include final \n
4214 if(this.isShadowTiddler(title))
4215 return config.shadowTiddlers[title];
4216 if(defaultText != undefined)
4221 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
4224 TiddlyWiki.prototype.calcAllSlices = function(title)
4227 var text = this.getTiddlerText(title,"");
4228 this.slicesRE.lastIndex = 0;
4230 var m = this.slicesRE.exec(text);
4233 slices[m[1]] = m[2];
4235 slices[m[3]] = m[4];
4241 // Returns the slice of text of the given name
4242 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
4244 var slices = this.slices[title];
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)
4256 for(var t=0; t<sliceNames.length; t++) {
4257 var slice = this.getTiddlerSlice(title,sliceNames[t]);
4259 r[sliceNames[t]] = slice;
4264 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
4266 var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])
","mg
");
4267 var text = this.getTiddlerText(title,null);
4273 var match = bracketRegExp.exec(text);
4275 textOut.push(text.substr(lastPos,match.index-lastPos));
4278 textOut.push(match[1]);
4280 textOut.push(this.getRecursiveTiddlerText(match[1],"[[
" + match[1] + "]]
",depth-1));
4282 lastPos = match.index + match[0].length;
4284 textOut.push(text.substr(lastPos));
4287 return textOut.join("");
4290 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
4292 var tiddler = this.fetchTiddler(title);
4294 var t = tiddler.tags.indexOf(tag);
4296 tiddler.tags.splice(t,1);
4298 tiddler.tags.push(tag);
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);
4311 merge(tiddler.fields,fields);
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);
4322 created = created ? created : tiddler.created; // Preserve created date
4323 this.deleteTiddler(title);
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();
4333 tiddler.incChangeCount();
4334 if(title != newTitle)
4335 this.notify(title,true);
4336 this.notify(newTitle,true);
4337 this.setDirty(true);
4341 // Reset the sync status of a freshly synced tiddler
4342 TiddlyWiki.prototype.resetTiddler = function(title)
4344 var tiddler = this.fetchTiddler(title);
4346 tiddler.clearChangeCount();
4347 this.notify(title,true);
4348 this.setDirty(true);
4352 TiddlyWiki.prototype.incChangeCount = function(title)
4354 var tiddler = this.fetchTiddler(title);
4356 tiddler.incChangeCount();
4359 TiddlyWiki.prototype.createTiddler = function(title)
4361 var tiddler = this.fetchTiddler(title);
4363 tiddler = new Tiddler(title);
4364 this.addTiddler(tiddler);
4365 this.setDirty(true);
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;
4377 var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
4378 this.setDirty(false);
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);
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
4404 doc.writeln(content);
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);
4414 TiddlyWiki.prototype.updateTiddlers = function()
4416 this.tiddlersUpdated = true;
4417 this.forEachTiddler(function(title,tiddler) {
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);
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]);
4438 sortField = "title
";
4439 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
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)
4449 this.forEachTiddler(function(title,tiddler) {
4450 for(var g=0; g<tiddler.tags.length; g++) {
4451 var tag = tiddler.tags[g];
4453 for(var c=0; c<results.length; c++) {
4454 if(results[c][0] == tag) {
4459 if(n && excludeTag) {
4460 var t = store.fetchTiddler(tag);
4461 if(t && t.isTagged(excludeTag))
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);});
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)
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)
4498 results.push(tiddler);
4501 sortField = "title
";
4502 results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
4506 // Return the tiddlers as a sorted array
4507 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
4510 this.forEachTiddler(function(title,tiddler) {
4511 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
4512 results.push(tiddler);
4515 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
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();
4525 this.forEachTiddler(function (title,tiddler) {
4526 if(tiddler.isTagged("excludeMissing
") || tiddler.isTagged("systemConfig
"))
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);
4538 // Return an array of names of tiddlers that are defined but not referred to
4539 TiddlyWiki.prototype.getOrphans = function()
4542 this.forEachTiddler(function (title,tiddler) {
4543 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists
"))
4544 results.push(title);
4550 // Return an array of names of all the shadow tiddlers
4551 TiddlyWiki.prototype.getShadowed = function()
4554 for(var t in config.shadowTiddlers) {
4555 if(typeof config.shadowTiddlers[t] == "string
")
4562 // Return an array of tiddlers that have been touched since they were downloaded or created
4563 TiddlyWiki.prototype.getTouched = function()
4566 this.forEachTiddler(function(title,tiddler) {
4567 if(tiddler.isTouched())
4568 results.push(tiddler);
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()
4584 this.loader = new TW21Loader();
4588 TiddlyWiki.prototype.getSaver = function()
4591 this.saver = new TW21Saver();
4595 // Filter a list of tiddlers
4596 TiddlyWiki.prototype.filterTiddlers = function(filter)
4601 var re = /([^\s\[\]]+)|(?:\[([ \w]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
4602 var match = re.exec(filter);
4604 if(match[1] || match[4]) {
4605 var title = match[1] ? match[1] : match[4];
4606 tiddler = this.fetchTiddler(title);
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]) {
4617 var matched = this.getTaggedTiddlers(match[3]);
4618 for(var m = 0; m < matched.length; m++)
4619 results.pushUnique(matched[m]);
4622 results = this.sortTiddlers(results,match[3]);
4626 match = re.exec(filter);
4632 // Sort a list of tiddlers
4633 TiddlyWiki.prototype.sortTiddlers = function(tiddlers,field)
4636 switch(field.substr(0,1)) {
4639 // Note: this fall-through is intentional
4641 field = field.substr(1);
4644 if(TiddlyWiki.standardFieldAccess[field])
4645 tiddlers.sort(function(a,b) {return a[field] < b[field] ? -asc : (a[field] == b[field] ? 0 : asc);});
4647 tiddlers.sort(function(a,b) {return a.fields[field] < b.fields[field] ? -asc : (a.fields[field] == b.fields[field] ? 0 : +asc);});
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);
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);
4723 fieldName = fieldName.toLowerCase();
4724 var isRemove = (value === undefined) || (value === null);
4725 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
4728 // don't remove StandardFields
4730 var h = TiddlyWiki.standardFieldAccess[fieldName];
4734 var oldValue = t.fields[fieldName];
4736 if(oldValue !== undefined) {
4737 // deletes a single field
4738 delete t.fields[fieldName];
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+'\\.');
4745 for(var n in t.fields) {
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)
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);
4777 fieldName = fieldName.toLowerCase();
4778 var accessor = TiddlyWiki.standardFieldAccess[fieldName];
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);
4796 for(n in t.fields) {
4797 result = callback(t,n,t.fields[n]);
4801 if(onlyExtendedFields)
4803 for(n in TiddlyWiki.standardFieldAccess) {
4805 // even though the "title
" field can also be referenced through the name "tiddler
"
4806 // we only visit this field once.
4808 result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
4816 //-- Story functions
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();
4837 var e = place.firstChild;
4839 var n = e.nextSibling;
4840 var title = e.getAttribute("tiddler
");
4841 fn.call(this,title,e);
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);
4858 this.closeTiddler(title,true);
4860 this.refreshTiddler(title,template,false,customFields);
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));
4870 window.scrollTo(0,ensureVisible(tiddlerElem));
4874 Story.prototype.positionTiddler = function(srcElement)
4876 var place = this.getContainer();
4878 if(typeof srcElement == "string
") {
4879 switch(srcElement) {
4881 before = place.firstChild;
4888 var after = this.findContainingTiddler(srcElement);
4890 before = place.firstChild;
4891 } else if(after.nextSibling) {
4892 before = after.nextSibling;
4893 if(before.nodeType != 1)
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
");
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);
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)
4923 var sm = new SyncMachine(serverType,{
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);
4947 error: function(message) {
4948 displayMessage("Error loading missing tiddler from %
0: %
1".format([host,message]));
4952 return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
4955 Story.prototype.chooseTemplateForTiddler = function(title,template)
4958 template = DEFAULT_VIEW_TEMPLATE;
4959 if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
4960 template = config.tiddlerTemplates[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);
4973 if(tiddlerElem.getAttribute("dirty
") == "true
" && !force)
4975 template = this.chooseTemplateForTiddler(title,template);
4976 var currTemplate = tiddlerElem.getAttribute("template
");
4977 if((template != currTemplate) || force) {
4978 var tiddler = store.getTiddler(title);
4980 tiddler = new Tiddler();
4981 if(store.isShadowTiddler(title)) {
4982 tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
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
");
5004 removeClass(tiddlerElem,"isTag
");
5005 if(store.tiddlerExists(title)) {
5006 removeClass(tiddlerElem,"shadow
");
5007 removeClass(tiddlerElem,"missing
");
5009 addClass(tiddlerElem,store.isShadowTiddler(title) ? "shadow
" : "missing
");
5012 this.addCustomFields(tiddlerElem,customFields);
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]);
5030 e.setAttribute("edit
",t);
5034 Story.prototype.refreshAllTiddlers = function(force)
5036 var place = this.getContainer();
5037 var e = place.firstChild;
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();
5073 Story.prototype.onTiddlerKeyPress = function(ev)
5075 var e = ev ? ev : window.event;
5077 var consume = false;
5078 var title = this.getAttribute("tiddler
");
5079 var target = resolveTarget(e);
5082 if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea
") {
5083 replaceSelection(target,String.fromCharCode(9));
5086 if(config.isOpera) {
5087 target.onblur = function() {
5093 case 13: // Ctrl-Enter
5094 case 10: // Ctrl-Enter on IE PC
5095 case 77: // Ctrl-Enter is "M
" on some platforms
5098 config.macros.toolbar.invokeCommand(this,"defaultCommand
",e);
5104 config.macros.toolbar.invokeCommand(this,"cancelCommand
",e);
5108 e.cancelBubble = 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
5117 Story.prototype.getTiddlerField = function(title,field)
5119 var tiddlerElem = this.getTiddler(title);
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
") {
5128 if(c.getAttribute("edit
") == field)
5136 Story.prototype.focusTiddler = function(title,field)
5138 var e = this.getTiddlerField(title,field);
5145 Story.prototype.blurTiddler = function(title)
5147 var tiddlerElem = this.getTiddler(title);
5148 if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
5149 tiddlerElem.focus();
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) {
5173 this.scrubTiddler(tiddlerElem);
5174 if(config.options.chkAnimate && animate && anim && typeof Slider == "function
")
5175 anim.startAnimating(new Slider(tiddlerElem,false,null,"all
"));
5177 removeNode(tiddlerElem);
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
";
5203 Story.prototype.areAnyDirty = function()
5206 this.forEachTiddler(function(title,element) {
5207 if(this.isDirty(title))
5213 Story.prototype.closeAllTiddlers = function(exclude)
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]));
5240 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
5243 Story.prototype.findContainingTiddler = function(e)
5245 while(e && !hasClass(e,"tiddler
"))
5250 Story.prototype.gatherSaveFields = function(e,fields)
5252 if(e && e.getAttribute) {
5253 var f = e.getAttribute("edit
");
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);
5269 this.gatherSaveFields(e,fields);
5270 var tiddler = store.fetchTiddler(title);
5273 for(var n in fields) {
5274 if(store.getValue(title,n) != fields[n])
5281 Story.prototype.saveTiddler = function(title,minorUpdate)
5283 var tiddlerElem = this.getTiddler(title);
5284 if(tiddlerElem != null) {
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()])))
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]);
5317 Story.prototype.permaView = function()
5320 this.forEachTiddler(function(title,element) {
5321 links.push(String.encodeTiddlyLink(title));
5323 var t = encodeURIComponent(links.join(" "));
5326 if(window.location.hash != t)
5327 window.location.hash = t;
5330 Story.prototype.switchTheme = function(theme)
5335 isAvailable = function(title) {
5336 var s = title ? title.indexOf(config.textPrimitives.sectionSeparator) : -1;
5338 title = title.substr(0,s);
5339 return store.tiddlerExists(title) || store.isShadowTiddler(title);
5342 getSlice = function(theme,slice) {
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)
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;
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;
5370 case "PageTemplate
":
5371 config.refresherData.pageTemplate = replaceNotification(i,config.refresherData.pageTemplate,theme,name);
5374 removeStyleSheet(config.refresherData.styleSheet);
5375 config.refresherData.styleSheet = replaceNotification(i,config.refresherData.styleSheet,theme,name);
5377 case "ColorPalette
":
5378 config.refresherData.colorPalette = replaceNotification(i,config.refresherData.colorPalette,theme,name);
5384 config.tiddlerTemplates[vi] = getSlice(theme,"ViewTemplate
");
5385 config.tiddlerTemplates[ei] = getSlice(theme,"EditTemplate
");
5387 if(config.refresherData.pageTemplate!=pt || config.tiddlerTemplates[vi]!=vt || config.tiddlerTemplates[ei]!=et) {
5389 story.refreshAllTiddlers(true);
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());
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)
5462 isVisible: function() {
5463 return this.area ? this.area.style.display == "block
" : false;
5467 this.area.style.display = "block
";
5468 if(anim && config.options.chkAnimate) {
5469 backstage.toolbar.style.left = findWindowWidth() + "px
";
5471 {style: "left
", start: findWindowWidth(), end: 0, template: "%
0px
"}
5473 anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
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
");
5485 if(this.currTabElem) {
5486 this.switchTab(null);
5488 backstage.toolbar.style.left = "0px
";
5489 if(anim && config.options.chkAnimate) {
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));
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);
5510 backstage.switchTab(null);
5516 onClickTab: function(e) {
5517 backstage.switchTab(this.getAttribute("task
"));
5521 // Switch to a given tab, or none if null is passed
5522 switchTab: function(tabName) {
5524 var e = this.toolbar.firstChild;
5527 if(e.getAttribute && e.getAttribute("task
") == tabName)
5531 if(tabName == backstage.currTabName)
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
";
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));
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) {
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));
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]];
5597 createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
5601 //-- ImportTiddlers macro
5604 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
5607 createTiddlyElement(place,"div
",null,"marked
",this.readOnlyWarning);
5610 var w = new Wizard();
5611 w.createWizard(place,this.wizardTitle);
5615 config.macros.importTiddlers.onCancel = function(e)
5617 var wizard = new Wizard(this);
5618 var place = wizard.clear();
5619 config.macros.importTiddlers.restart(wizard);
5623 config.macros.importTiddlers.onClose = function(e)
5625 backstage.hidePanel();
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);
5637 if(config.defaultAdaptor)
5638 s.value = config.defaultAdaptor;
5639 s = wizard.getElement("selFeeds
");
5640 var feeds = this.getFeeds();
5642 e = createTiddlyElement(s,"option
",null,null,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()
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
");
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
")};
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];
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);
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
";
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);
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);
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);
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;
5739 var ret = context.adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
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);
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
");
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;
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;
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);
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);
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
"));
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);
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) : "",
5821 size: tiddler.text ? tiddler.text.length : 0,
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);
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])))
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();
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(",
")])))
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);
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++) {
5901 context.allowSynchronous = true;
5902 var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
5907 config.macros.importTiddlers.onGetTiddler = function(context,wizard)
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) {
5928 {caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onClose}
5929 ],config.macros.importTiddlers.statusDoneImport);
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);
5954 if(story.areAnyDirty() || store.isDirty()) {
5955 alert(me.errorNotSaved);
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);
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);
5980 config.macros.upgrade.onLoadCore = function(status,params,responseText,url,xhr)
5982 var me = config.macros.upgrade;
5986 errMsg = me.errorLoadingCore;
5987 var newVer = me.extractVersion(responseText);
5989 errMsg = me.errorCoreFormat;
5991 w.setButtons([],errMsg);
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);
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);
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('?'));
6047 // Synchronisation handlers
6048 config.syncers = {};
6051 var currSync = null;
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)
6063 config.macros.sync.cancelSync();
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();
6078 {caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
6082 config.macros.sync.getSyncableTiddlers = function()
6085 store.forEachTiddler(function(title,tiddler) {
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);});
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];
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)
6132 si.syncTask = this.createSyncTask(si);
6133 currSync.syncTasks.push(si.syncTask);
6136 r.syncItems.push(si);
6141 config.macros.sync.createSyncTask = function(syncItem)
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,{
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);
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'];
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();
6204 config.macros.sync.updateSyncStatus = function(syncItem)
6206 var e = syncItem.colElements["status
"];
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);
6224 config.macros.sync.doSyncItem = function(syncItem)
6227 var sl = config.macros.sync.syncStatusList;
6228 switch(syncItem.syncStatus) {
6229 case sl.changedServer:
6230 r = syncItem.syncTask.syncMachine.go("getTiddler
",syncItem.title);
6233 case sl.changedLocally:
6234 case sl.changedBoth:
6235 r = syncItem.syncTask.syncMachine.go("putTiddler
",syncItem.tiddler);
6241 displayMessage("Error in doSyncItem:
" + r);
6244 config.macros.sync.cancelSync = function()
6249 function SyncMachine(serverType,steps)
6251 this.serverType = serverType;
6252 this.adaptor = new config.adaptors[serverType];
6256 SyncMachine.prototype.go = function(step,context)
6258 var r = context ? context.status : null;
6259 if(typeof r == "string
") {
6260 this.invokeError(r);
6263 var h = this.steps[step ? step : "start
"];
6266 r = h.call(this,context);
6267 if(typeof r == "string
")
6268 this.invokeError(r);
6272 SyncMachine.prototype.invokeError = function(message)
6274 if(this.steps.error)
6275 this.steps.error(message);
6278 SyncMachine.prototype.openHost = function(host,nextStep)
6281 return me.adaptor.openHost(host,null,null,function(context) {me.go(nextStep,context);});
6284 SyncMachine.prototype.getWorkspaceList = function(nextStep)
6287 return me.adaptor.getWorkspaceList(null,null,function(context) {me.go(nextStep,context);});
6290 SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
6293 return me.adaptor.openWorkspace(workspace,null,null,function(context) {me.go(nextStep,context);});
6296 SyncMachine.prototype.getTiddlerList = function(nextStep)
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)
6310 return me.adaptor.getTiddler(title,null,null,function(context) {me.go(nextStep,context);});
6313 SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
6316 return me.adaptor.putTiddler(tiddler,null,null,function(context) {me.go(nextStep,context);});
6320 //-- Manager UI for groups of tiddlers
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) {
6343 selectedRows.push(e.getAttribute("rowName
"));
6345 removeChildren(listWrapper);
6346 params = params.parseParams("anon
");
6347 var plugins = installedPlugins.slice(0);
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);
6355 p.log.splice(0,0,this.skippedText);
6359 for(t=0; t<plugins.length; 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([]);
6370 var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
6371 wizard.setValue("listView
",listView);
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);
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);
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);
6413 function getMessageDiv()
6415 var msgArea = document.getElementById("messageArea
");
6418 if(!msgArea.hasChildNodes())
6419 createTiddlyButton(createTiddlyElement(msgArea,"div
",null,"messageToolbar
"),
6420 config.messages.messageClose.text,
6421 config.messages.messageClose.tooltip,
6423 msgArea.style.display = "block
";
6424 return createTiddlyElement(msgArea,"div
");
6427 function displayMessage(text,linkText)
6429 var e = getMessageDiv();
6435 var link = createTiddlyElement(e,"a
",null,null,text);
6436 link.href = linkText;
6437 link.target = "_blank
";
6439 e.appendChild(document.createTextNode(text));
6443 function clearMessage()
6445 var msgArea = document.getElementById("messageArea
");
6447 removeChildren(msgArea);
6448 msgArea.style.display = "none
";
6454 //-- Refresh mechanism
6457 config.refreshers = {
6458 link: function(e,changeList)
6460 var title = e.getAttribute("tiddlyLink
");
6461 refreshTiddlyLink(e,title);
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);
6472 refreshElements(e,changeList);
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) {
6482 wikify(store.getTiddlerText(title,title),e,null,store.fetchTiddler(title));
6488 macro: function(e,changeList)
6490 var macro = e.getAttribute("macroName
");
6491 var params = e.getAttribute("params
");
6493 macro = config.macros[macro];
6494 if(macro && macro.refresh)
6495 macro.refresh(e,params);
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;
6529 var nextChild = e.nextSibling;
6530 if(e.getAttribute) {
6531 var macro = e.getAttribute("macro
");
6534 var p = macro.indexOf(" ");
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);
6548 function refreshPageTemplate(title)
6550 var stash = createTiddlyElement(document.body,"div
");
6551 stash.style.display = "none
";
6552 var display = story.getContainer();
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;
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);
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]);
6585 function refreshDisplay(hint)
6587 if(typeof hint == "string
")
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)
6620 function refreshAll()
6622 refreshPageTemplate();
6624 refreshStyles("StyleSheetLayout
");
6625 refreshStyles("StyleSheetColors
");
6626 refreshStyles(config.refresherData.styleSheet);
6627 refreshStyles("StyleSheetPrint
");
6634 config.optionHandlers = {
6636 get: function(name) {return encodeCookie(config.options[name].toString());},
6637 set: function(name,value) {config.options[name] = decodeCookie(value);}
6640 get: function(name) {return config.options[name] ? "true
" : "false
";},
6641 set: function(name,value) {config.options[name] = value == "true
";}
6645 function loadOptionsCookie()
6649 var cookies = document.cookie.split(";
");
6650 for(var c=0; c<cookies.length; c++) {
6651 var p = cookies[c].indexOf("=
");
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)
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)
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);
6696 c.className = className;
6698 c.className = typeInfo.className;
6699 if(config.optionsDesc[opt])
6700 c.setAttribute("title
",config.optionsDesc[opt]);
6701 place.appendChild(c);
6703 createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
6704 c[typeInfo.valueField] = config.options[opt];
6708 config.macros.option.genericOnChange = function(e)
6710 var opt = this.getAttribute("option
");
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);
6720 config.macros.option.types = {
6722 elementType: "input
",
6723 valueField: "value
",
6724 eventName: "onkeyup
",
6725 className: "txtOptionInput
",
6726 create: config.macros.option.genericCreate,
6727 onChange: config.macros.option.genericOnChange
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
");
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];
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)
6784 for(var n in config.options) {
6788 opt.lowlight = !config.optionsDesc[n];
6789 opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
6790 if(!opt.lowlight || showUnknown)
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];
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);
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))
6839 function updateLanguageAttribute(s)
6842 var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)
")?(?: lang\="([a-z]+)
")?>/;
6843 var m = mRE.exec(s);
6847 t += ' xml:lang="' + config.locale + '
"';
6849 t += ' lang="' + config.locale + '
"';
6851 s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
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)
6868 posDiv = locateStoreArea(original);
6870 alert(config.messages.invalidFileError.format([localPath]));
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
");
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())
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);
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);
6930 var posDiv = locateStoreArea(original);
6932 alert(config.messages.invalidFileError.format([localPath]));
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)
6942 if(config.options.chkDisplayInstrumentation)
6943 displayMessage("saveChanges
" + (new Date()-t0) + " ms
");
6946 function saveMain(localPath,original,posDiv)
6950 var revised = updateOriginal(original,posDiv);
6951 save = saveFile(localPath,revised);
6956 displayMessage(config.messages.mainSaved,"file://
" + localPath);
6957 store.setDirty(false);
6959 alert(config.messages.mainFailed);
6963 function saveBackup(localPath,original)
6965 var backupPath = getBackupPath(localPath);
6966 var backup = copyFile(backupPath,localPath);
6968 backup = saveFile(backupPath,original);
6970 displayMessage(config.messages.backupSaved,"file://
" + backupPath);
6972 alert(config.messages.backupFailed);
6975 function saveEmpty(localPath,original,posDiv)
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) + "\\
";
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);
6988 displayMessage(config.messages.emptySaved,"file://
" + emptyPath);
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);
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("?
");
7008 originalPath = originalPath.substr(0,argPos);
7009 var hashPos = originalPath.indexOf("#
");
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
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
"),"\\
");
7030 function getBackupPath(localPath,title,extension)
7033 var dirPathPos = localPath.lastIndexOf("\\
");
7034 if(dirPathPos == -1) {
7035 dirPathPos = localPath.lastIndexOf("/
");
7038 var backupFolder = config.options.txtBackupFolder;
7039 if(!backupFolder || backupFolder == "")
7041 var backupPath = localPath.substr(0,dirPathPos) + slash + backupFolder + localPath.substr(dirPathPos);
7042 backupPath = backupPath.substr(0,backupPath.lastIndexOf(".
")) + ".
";
7044 backupPath += title.replace(/[\\\/\*\?\":<
> ]/g,
"_") +
".";
7045 backupPath += (new Date()).convertToYYYYMMDDHHMMSSMMM() +
"." + (extension ? extension :
"html");
7049 function generateRss()
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" +
">");
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>");
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>");
7075 s.push(
"</channel>");
7078 return s.join(
"\n");
7083 //-- Filesystem code
7086 function convertUTF8ToUnicode(u)
7088 return window.netscape == undefined ? manualConvertUTF8ToUnicode(u) : mozConvertUTF8ToUnicode(u);
7091 function manualConvertUTF8ToUnicode(utf)
7098 while(src < utf.length) {
7099 b1 = utf.charCodeAt(src++);
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));
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));
7116 function mozConvertUTF8ToUnicode(u)
7119 netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect");
7120 var converter = Components.classes[
"@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7121 converter.charset =
"UTF-8";
7123 return manualConvertUTF8ToUnicode(u);
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);
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)
7147 netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect");
7148 var converter = Components.classes[
"@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
7149 converter.charset =
"UTF-8";
7151 return manualConvertUnicodeToUTF8(s);
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 ==
"")
7163 netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect");
7164 var converter = Components.classes[
"@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
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);
7180 r = ieSaveFile(fileUrl,content);
7182 r = javaSaveFile(fileUrl,content);
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);
7196 function ieCreatePath(path)
7199 var fso = new ActiveXObject(
"Scripting.FileSystemObject");
7204 var pos = path.lastIndexOf(
"\\");
7206 path = path.substring(
0, pos+
1);
7212 var parent = fso.GetParentFolderName(scan[i++]);
7213 if(fso.FolderExists(parent))
7218 for(i=scan.length-
1;i
>=
0;i--) {
7219 if(!fso.FolderExists(scan[i]))
7220 fso.CreateFolder(scan[i]);
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);
7230 var fso = new ActiveXObject(
"Scripting.FileSystemObject");
7234 var file = fso.OpenTextFile(filePath,
2,-
1,
0);
7235 file.Write(content);
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)
7244 var fso = new ActiveXObject(
"Scripting.FileSystemObject");
7245 var file = fso.OpenTextFile(filePath,
1);
7246 var content = file.ReadAll();
7254 function ieCopyFile(dest,source)
7258 var fso = new ActiveXObject(
"Scripting.FileSystemObject");
7259 fso.GetFile(source).Copy(dest);
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) {
7271 netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect");
7272 var file = Components.classes[
"@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7273 file.initWithPath(filePath);
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);
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) {
7294 netscape.security.PrivilegeManager.enablePrivilege(
"UniversalXPConnect");
7295 var file = Components.classes[
"@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
7296 file.initWithPath(filePath);
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();
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(
":");
7321 return url.substring(i-
1);
7325 function javaSaveFile(filePath,content)
7328 if(document.applets[
"TiddlySaver"])
7329 return document.applets[
"TiddlySaver"].saveFile(javaUrlToFilename(filePath),
"UTF-8",content);
7333 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
7342 function javaLoadFile(filePath)
7345 if(document.applets[
"TiddlySaver"])
7346 return String(document.applets[
"TiddlySaver"].loadFile(javaUrlToFilename(filePath),
"UTF-8"));
7351 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
7353 while((line = r.readLine()) != null)
7354 content.push(new String(line));
7359 return content.join(
"\n");
7363 //-- Server adaptor for talking to static TiddlyWiki files
7366 function FileAdaptor()
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;
7383 context.host = this.host;
7384 context.host = FileAdaptor.fullHostName(context.host);
7385 if(!context.workspace)
7386 context.workspace = this.workspace;
7390 FileAdaptor.fullHostName = function(host)
7394 if(!host.match(/:\/\//))
7395 host = 'http://' + 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)
7408 context = this.setContext(context,userParams,callback);
7409 context.status = true;
7411 window.setTimeout(function() {context.callback(context,userParams);},
10);
7415 FileAdaptor.loadTiddlyWikiCallback = function(status,context,responseText,url,xhr)
7417 context.status = status;
7419 context.statusText =
"Error reading file";
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;
7435 window.setTimeout(function() {callback(context,userParams);},
10);
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;
7446 window.setTimeout(function() {callback(context,userParams);},
10);
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);
7455 context.filter = filter;
7456 context.complete = FileAdaptor.getTiddlerListComplete;
7458 var ret = context.complete(context,context.userParams);
7460 ret = loadRemoteFile(context.host,FileAdaptor.loadTiddlyWikiCallback,context);
7461 if(typeof ret !=
"string")
7467 FileAdaptor.getTiddlerListComplete = function(context,userParams)
7469 if(context.status) {
7470 if(context.filter) {
7471 context.tiddlers = context.adaptor.store.filterTiddlers(context.filter);
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);
7489 FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
7492 info.uri = tiddler.fields['server.host'] +
"#" + tiddler.title;
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);
7519 window.setTimeout(function() {context.callback(context,userParams);},
10);
7524 FileAdaptor.prototype.close = function()
7530 config.adaptors[FileAdaptor.serverType] = FileAdaptor;
7532 config.defaultAdaptor = FileAdaptor.serverType;
7535 //-- Remote HTTP requests
7538 function loadRemoteFile(url,callback,params)
7540 return doHttp(
"GET",url,null,null,null,null,callback,params,null);
7543 // HTTP status codes
7546 ContentCreated:
201,
7552 MethodNotAllowed:
405
7555 function doHttp(type,url,data,contentType,username,password,callback,params,headers,allowCache)
7557 var x = getXMLHttpRequest();
7559 return
"Can't create XMLHttpRequest object";
7560 x.onreadystatechange = function() {
7562 var status = x.status;
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);
7570 callback(false,params,null,url,x);
7571 x.onreadystatechange = function(){};
7575 if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf(
"http") == -
1)
7576 window.netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserRead");
7579 url = url + (url.indexOf(
"?") <
0 ?
"?" :
"&") +
"nocache=" + Math.random();
7580 x.open(type,url,true,username,password);
7582 x.setRequestHeader(
"Content-Type", contentType ? contentType :
"application/x-www-form-urlencoded");
7583 if(x.overrideMimeType)
7584 x.setRequestHeader(
"Connection",
"close");
7586 for(var n in headers)
7587 x.setRequestHeader(n,headers[n]);
7589 x.setRequestHeader(
"X-Requested-With",
"TiddlyWiki " + formatVersion());
7592 return exceptionText(ex);
7597 function getXMLHttpRequest()
7600 var x = new XMLHttpRequest(); // Modern
7603 x = new ActiveXObject(
"Msxml2.XMLHTTP"); // IE
6
7612 //-- TiddlyWiki-specific utility functions
7615 formatVersion = function(v)
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;
7632 x1 = v1.beta ||
9999;
7633 x2 = v2.beta ||
9999;
7636 return x1
> x2 ? -
1 :
0;
7639 function createTiddlyButton(parent,text,tooltip,action,className,id,accessKey,attribs)
7641 var btn = document.createElement(
"a");
7643 btn.onclick = action;
7644 btn.setAttribute(
"href",
"javascript:;");
7647 btn.setAttribute(
"title",tooltip);
7649 btn.appendChild(document.createTextNode(text));
7650 btn.className = className ? className :
"button";
7654 for(var n in attribs) {
7655 btn.setAttribute(n,attribs[n]);
7659 parent.appendChild(btn);
7661 btn.setAttribute(
"accessKey",accessKey);
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);
7673 btn.setAttribute(
"noToggle",
"true");
7674 if(linkedFromTiddler) {
7675 var fields = linkedFromTiddler.getInheritedFields();
7677 btn.setAttribute(
"tiddlyFields",fields);
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);
7696 subTitle = tiddler.getSubtitle();
7697 classes.pushUnique(
"tiddlyLinkExisting");
7698 classes.remove(
"tiddlyLinkNonExisting");
7699 classes.remove(
"shadow");
7701 classes.remove(
"tiddlyLinkExisting");
7702 classes.pushUnique(
"tiddlyLinkNonExisting");
7703 if(store.isShadowTiddler(title)) {
7704 subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
7705 classes.pushUnique(
"shadow");
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";
7721 link.title = config.messages.externalLinkTooltip.format([url]);
7722 if(config.options.chkOpenInNewWindow)
7723 link.target =
"_blank";
7724 place.appendChild(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);
7736 var noToggle = null;
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));
7748 var toggling = e.metaKey || e.ctrlKey;
7749 if(config.options.chkToggleLinks)
7750 toggling = !toggling;
7753 if(store.getTiddler(title))
7755 story.displayTiddler(target,title,null,true,null,fields,toggling);
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);
7767 btn.setAttribute(
"tiddler",excludeTiddler);
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");
7779 var tagged = store.getTaggedTiddlers(tag);
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);
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]));
7802 e.cancelBubble = true;
7803 if(e.stopPropagation) e.stopPropagation();
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);
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]);
7825 e.cancelBubble = true;
7826 if(e.stopPropagation) e.stopPropagation();
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)
7843 function createTiddlyPopup(place,caption,tooltip,tiddler)
7846 createTiddlyLink(place,caption,true);
7847 var btn = createTiddlyButton(place,glyph(
"downArrow"),tooltip,onClickTiddlyPopup,
"tiddlerPopupButton");
7848 btn.tiddler = tiddler;
7850 createTiddlyText(place,caption);
7854 function onClickTiddlyPopup(ev)
7856 var e = ev ? ev : window.event;
7857 var tiddler = this.tiddler;
7859 var popup = Popup.create(this,
"div",
"popupTiddler");
7860 wikify(tiddler.text,popup,null,tiddler);
7863 if(e) e.cancelBubble = true;
7864 if(e && e.stopPropagation) e.stopPropagation();
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)
7877 if(!preserveExisting || dst[p] === undefined)
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)
7902 function glyph(name)
7904 var g = config.glyphs;
7905 var b = g.currBrowser;
7908 while(!g.browsers[b]() && b < g.browsers.length-
1)
7914 return g.codes[name][b];
7919 //- Animation engine
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
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) {
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)
7946 while(a < me.animations.length) {
7947 var animation = me.animations[a];
7948 if(animation.tick()) {
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));
7964 //-- Morpher animation
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;
7980 Morpher.prototype.assignStyle = function(element,style,value)
7983 case
"-tw-vertScroll":
7984 window.scrollTo(findScrollX(),value);
7986 case
"-tw-horizScroll":
7987 window.scrollTo(value,findScrollY());
7990 element.style[style] = value;
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);
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";
8018 var v = p.start + (p.end-p.start) * progress;
8019 this.assignStyle(this.element,p.style,template.format([v]));
8026 if(currTime
>= this.endTime) {
8034 //-- Zoomer animation
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();
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);
8055 //-- Scroller animation
8058 function Scroller(targetElement,unused)
8061 {style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
8063 return new Morpher(targetElement,config.animDuration,p);
8067 //-- Slider animation
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';
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();
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)'});
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) {
8094 c = function(element,properties) {removeNode(element);};
8097 c = function(element,properties) {removeChildren(element);};
8101 return new Morpher(element,config.animDuration,p,c);
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});
8122 Popup.onDocumentClick = function(ev)
8124 var e = ev ? ev : window.event;
8125 if(e.eventPhase == undefined)
8127 else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
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));
8140 window.scrollTo(
0,ensureVisible(curr.popup));
8143 Popup.place = function(root,popup,valign,halign,offset)
8146 var offset = {x:
0,y:
0};
8147 if(popup.stackPosition
>=
0 && !valign && !halign) {
8148 offset.x = offset.x + root.offsetWidth;
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;
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)
8176 for (var t=this.stack.length-
1; t
>=
0; t--) {
8177 if(isDescendant(e,this.stack[t].popup))
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);
8205 function Wizard(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");
8212 this.formElem = null;
8213 this.bodyElem = null;
8214 this.footElem = null;
8218 Wizard.prototype.setValue = function(name,value)
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];
8270 //-- ListView gadget
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));
8320 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
8322 return function(e) {
8323 var view = findRelated(this,
"TABLE",null,
"previousSibling");
8325 ListView.forEachSelector(view,function(e,rowName) {
8327 tiddlers.push(rowName);
8329 if(tiddlers.length ==
0 && !allowEmptySelection) {
8330 alert(config.messages.nothingSelected);
8332 if(this.nodeName.toLowerCase() ==
"select") {
8333 callback(view,this.value,tiddlers);
8334 this.selectedIndex =
0;
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");
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");
8360 ListView.getSelectedRows = function(view)
8363 ListView.forEachSelector(view,function(e,rowName) {
8365 rowNames.push(rowName);
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];
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];
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) {
8412 while(t
<config.messages.sizeTemplates.length-
1 && v
<config.messages.sizeTemplates[t].unit)
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;
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];
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");
8470 ListView.forEachSelector(view,function(e,rowName) {
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]);
8525 //-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
8528 // Clamp a number to a range
8529 Number.prototype.clamp = function(min,max)
8539 // Add indexOf function if browser does not support it
8540 if(!Array.indexOf) {
8541 Array.prototype.indexOf = function(item,from)
8545 for(var i=from; i
<this.length; i++) {
8546 if(this[i] === item)
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)
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);
8575 mode = (p == -
1) ? +
1 : -
1;
8579 } else if(mode == -
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)
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)
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) {
8611 if(this.indexOf(item) == -
1)
8616 Array.prototype.remove = function(item)
8618 var p = this.indexOf(item);
8623 if(!Array.prototype.map) {
8624 Array.prototype.map = function(fn,thisObj)
8626 var scope = thisObj || window;
8628 for(var i=
0, j=this.length; i < j; ++i) {
8629 a.push(fn.call(scope,this[i],i,this));
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(
"-");
8651 for(var t=
1; t
<s.length; t++)
8652 s[t] = s[t].substr(
0,
1).toUpperCase() + s[t].substr(
1);
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;
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;
8672 if(currPos < this.length)
8673 r.push(this.substring(currPos,this.length));
8677 // Escape any special RegExp characters with that character preceded by a backslash
8678 String.prototype.escapeRegExp = function()
8680 var s =
"\\^$*+?()=!|,{}[].";
8682 for(var t=
0; t
<s.length; t++)
8683 c = c.replace(new RegExp(
"\\" + s.substr(t,
1),
"g"),
"\\" + s.substr(t,
1));
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
"&", < to
"<",
> to
">" and
" to """
8700 String.prototype.htmlEncode = function()
8702 return this.replace(/&/mg,"&").replace(/</mg,"<").replace(/>/mg,">").replace(/\"/mg,
""");
8705 // Convert
"&" to &,
"<" to <,
">" to
> and
""" to
"
8706 String.prototype.htmlDecode = function()
8708 return this.replace(/</mg,"<
").replace(/>/mg,">").replace(/"/mg,"\
"").replace(/
&/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()
8723 var replaceFn = function(a,b) {
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) {
8743 if(match[p]) // Double quoted
8745 else if(match[p+
1]) // Single quoted
8747 else if(match[p+
2]) // Double-square-bracket quoted
8749 else if(match[p+
3]) // Double-brace quoted
8755 throw
"Unable to evaluate {{" + match[p+
3] +
"}}: " + exceptionText(ex);
8757 else if(match[p+
4]) // Unquoted
8759 else if(match[p+
5]) // empty quote
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");
8775 var match = re.exec(this);
8777 var n = parseToken(match,
1);
8779 r.push({name:
"",value:n});
8781 var v = parseToken(match,
8);
8782 if(v == null && defaultName) {
8785 } else if(v == null && defaultValue) {
8788 r.push({name:n,value:v});
8789 if(cascadeDefaults) {
8796 // Summarise parameters into first element
8797 for(var t=
1; t
<r.length; t++) {
8799 r[
0][r[t].name].push(r[t].value);
8801 r[
0][r[t].name] = [r[t].value];
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);
8813 for(var t=
1; t
<p.length; t++)
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);
8823 for(var t=
1; t
<p.length; t++) {
8825 n.pushUnique(p[t].value,unique);
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);
8836 var e = this.indexOf(end,s);
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);
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)
8869 for(var t=
0; t
<list.length; t++)
8870 results.push(String.encodeTiddlyLink(list[t]));
8871 return results.join(
" ");
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);
8882 for(var t=
1; t
<fields.length; t++)
8883 r[fields[t].name] = fields[t].value;
8887 // Static method to encode a hashmap into a name:
"value"... string
8888 String.encodeHashMap = function(hashmap)
8891 for(var t in hashmap)
8892 r.push(t + ':
"' + hashmap[t] + '"');
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();
8901 s =
"000000000000000000000000000".substr(
0,d-s.length) + 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)
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));
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),
9021 parseInt(mm,
10),
0,
0));
9026 //-- Crypto functions and associated conversion routines
9029 // Crypto 'namespace'
9030 function Crypto() {}
9032 // Convert a string to an array of big-endian
32-bit words
9033 Crypto.strToBe32s = function(str)
9036 var len=Math.floor(str.length/
4);
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);
9048 // Convert an array of big-endian
32-bit words to a string
9049 Crypto.be32sToStr = function(be)
9052 for(var i=
0;i
<be.length*
32;i+=
8)
9053 str += String.fromCharCode((be[i
>>5]
>>>(
24-i%
32)) &
0xff);
9057 // Convert an array of big-endian
32-bit words to a hex string
9058 Crypto.be32sToHex = function(be)
9060 var hex='
0123456789ABCDEF';
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);
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
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)
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);
9098 var n=w[j-
3]^w[j-
8]^w[j-
14]^w[j-
16];
9099 return (n
>>>31)|(n<
<1);
9103 x[len
>>5] |=
0x80 << (
24-len%
32);
9104 x[((len+
64>>9)<
<4)+
15]=len;
9118 for(var i=
0;i
<x.length;i+=
16) {
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++;
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++;
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++;
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++;
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++;
9157 return [h0,h1,h2,h3,h4];
9161 //-- RGB colour object
9164 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
9170 if(typeof r ==
"string") {
9171 if(r.substr(
0,
1) ==
"#") {
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;
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;
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);
9185 this.r = parseInt(c[
1],
10)/
255;
9186 this.g = parseInt(c[
2],
10)/
255;
9187 this.b = parseInt(c[
3],
10)/
255;
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);
9216 //-- DOM utilities - many derived from www.quirksmode.org
9219 function drawGradient(place,horiz,locolors,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";
9251 wikify(caption,parent);
9255 function createTiddlyElement(parent,element,id,className,text,attribs)
9257 var e = document.createElement(element);
9258 if(className != null)
9259 e.className = className;
9261 e.setAttribute(
"id",id);
9263 e.appendChild(document.createTextNode(text));
9265 for(var n in attribs) {
9266 e.setAttribute(n,attribs[n]);
9270 parent.appendChild(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]);
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;
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);
9307 currClass.splice(i,
1);
9308 i = currClass.indexOf(className);
9310 e.className = currClass.join(
" ");
9313 function hasClass(e,className)
9316 if(e.className.split(
" ").indexOf(className) != -
1)
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)) {
9332 while(e && e[name] != value) {
9339 // Resolve the target object of an event
9340 function resolveTarget(e)
9345 else if(e.srcElement)
9347 if(obj.nodeType ==
3) // defeat Safari bug
9348 obj = obj.parentNode;
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();
9361 // Return the content of an element as plain text with no formatting
9362 function getPlainText(e)
9367 else if(e.textContent)
9368 text = e.textContent;
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) {
9382 } else if(posBot
> winBot) {
9383 if(e.offsetHeight < winHeight)
9384 return posTop - (winHeight - e.offsetHeight);
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)
9419 while(obj.offsetParent) {
9420 curleft += obj.offsetLeft;
9421 obj = obj.offsetParent;
9426 function findPosY(obj)
9429 while(obj.offsetParent) {
9430 curtop += obj.offsetTop;
9431 obj = obj.offsetParent;
9436 // Blur a particular element
9437 function blurElement(e)
9439 if(e != null && e.focus && e.blur) {
9445 // Create a non-breaking space
9446 function insertSpacer(place)
9448 var e = document.createTextNode(String.fromCharCode(
160));
9450 place.appendChild(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)
9465 e.parentNode.removeChild(e);
9468 // Remove any event handlers or non-primitve custom attributes
9469 function scrubNode(e)
9471 if(!config.browser.isIE)
9473 var att = e.attributes;
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))) {
9485 var c = e.firstChild;
9492 // Add a stylesheet, replacing any previous custom stylesheet
9493 function setStylesheet(s,id,doc)
9496 id =
"customStyleSheet";
9499 var n = doc.getElementById(id);
9500 if(doc.createStyleSheet) {
9501 // Test for IE's non-standard createStyleSheet method
9503 n.parentNode.removeChild(n);
9504 // This failed without the
9505 doc.getElementsByTagName(
"head")[
0].insertAdjacentHTML(
"beforeEnd",
" <style id='" + id +
"'>" + s +
"</style>");
9508 n.replaceChild(doc.createTextNode(s),n.firstChild);
9510 n = doc.createElement(
"style");
9511 n.type =
"text/css";
9513 n.appendChild(doc.createTextNode(s));
9514 doc.getElementsByTagName(
"head")[
0].appendChild(n);
9519 function removeStyleSheet(id)
9521 var e = document.getElementById(id);
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 ==
"";
9552 range.moveStart('character', -text.length);
9559 // Returns the text of the given (text) node, possibly merging subsequent text nodes
9560 function getNodeText(e)
9563 while(e && e.nodeName ==
"#text") {
9570 // Returns true if the element e has a given ancestor element
9571 function isDescendant(e,ancestor)
9582 //-- LoaderBase and SaverBase
9585 function LoaderBase() {}
9587 LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
9589 var title = this.getTitle(store,node);
9590 if(safeMode && store.isShadowTiddler(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)
9602 for(var t =
0; t < nodes.length; t++) {
9604 this.loadTiddler(store,nodes[t],tiddlers);
9606 showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
9612 function SaverBase() {}
9614 SaverBase.prototype.externalize = function(store)
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");
9626 //-- TW21Loader (inherits from LoaderBase)
9629 function TW21Loader() {}
9631 TW21Loader.prototype = new LoaderBase();
9633 TW21Loader.prototype.getTitle = function(store,node)
9636 if(node.getAttribute) {
9637 title = node.getAttribute(
"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);
9649 TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
9651 var e = node.firstChild;
9653 if(node.getAttribute(
"tiddler")) {
9654 text = getNodeText(e).unescapeLineBreaks();
9656 while(e.nodeName!=
"PRE" && e.nodeName!=
"pre") {
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");
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);
9680 //-- TW21Saver (inherits from SaverBase)
9683 function TW21Saver() {}
9685 TW21Saver.prototype = new SaverBase();
9687 TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
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")
9697 if(!fieldName.match(/^temp\./))
9698 extendedAttributes += ' %
0=
"%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
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();
9707 attributes += '
tags=
"' + tags.htmlEncode() + '"';
9708 return ('
<div %
0=
"%1"%
2%
3>%
4</'+'div
>').format([
9709 usePre ?
"title" :
"tiddler",
9710 tiddler.title.htmlEncode(),
9713 usePre ?
"\n<pre>" + tiddler.text.htmlEncode() +
"</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
9716 throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
9722 <script type=
"text/javascript">
9725 document
.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
9728 <!--POST-SCRIPT-START-->
9730 <!--POST-SCRIPT-END-->