Add axTLS sourcecode
[project/luci.git] / libs / nixio / axTLS / www / index.html
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3 <head>
4 <script type="text/javascript">
5 //<![CDATA[
6 var version = {major: 2, minor: 1, revision: 3, date: new Date("Nov 3, 2006"), extensions: {}};
7 //]]>
8 </script>
9 <!--
10 TiddlyWiki 2.1.3 by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
11
12 Copyright (c) Osmosoft Limited 2004-2006
13
14 Redistribution and use in source and binary forms, with or without modification,
15 are permitted provided that the following conditions are met:
16
17 Redistributions of source code must retain the above copyright notice, this
18 list of conditions and the following disclaimer.
19
20 Redistributions in binary form must reproduce the above copyright notice, this
21 list of conditions and the following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24 Neither the name of the Osmosoft Limited nor the names of its contributors may be
25 used to endorse or promote products derived from this software without specific
26 prior written permission.
27
28 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
29 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
31 SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
33 TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
37 DAMAGE.
38 -->
39 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
40 <!--PRE-HEAD-START-->
41 <!--{{{-->
42 <link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
43 <!--}}}-->
44 <!--PRE-HEAD-END-->
45 <title> axTLS Embedded SSL - changes, notes and errata </title>
46 <script type="text/javascript">
47 //<![CDATA[
48 // ---------------------------------------------------------------------------------
49 // Configuration repository
50 // ---------------------------------------------------------------------------------
51
52 // Miscellaneous options
53 var config = {
54         numRssItems: 20, // Number of items in the RSS feed
55         animFast: 0.12, // Speed for animations (lower == slower)
56         animSlow: 0.01, // Speed for EasterEgg animations
57         cascadeFast: 20, // Speed for cascade animations (higher == slower)
58         cascadeSlow: 60, // Speed for EasterEgg cascade animations
59         cascadeDepth: 5, // Depth of cascade animation
60         displayStartupTime: false // Whether to display startup time
61         };
62
63 // Messages
64 config.messages = {
65         messageClose: {},
66         dates: {}
67 };
68
69 // Options that can be set in the options panel and/or cookies
70 config.options = {
71         chkRegExpSearch: false,
72         chkCaseSensitiveSearch: false,
73         chkAnimate: true,
74         chkSaveBackups: true,
75         chkAutoSave: false,
76         chkGenerateAnRssFeed: false,
77         chkSaveEmptyTemplate: false,
78         chkOpenInNewWindow: true,
79         chkToggleLinks: false,
80         chkHttpReadOnly: true,
81         chkForceMinorUpdate: false,
82         chkConfirmDelete: true,
83         chkInsertTabs: false,
84         txtBackupFolder: "",
85         txtMainTab: "tabTimeline",
86         txtMoreTab: "moreTabAll",
87         txtMaxEditRows: "30"
88         };
89         
90 // List of notification functions to be called when certain tiddlers are changed or deleted
91 config.notifyTiddlers = [
92         {name: "StyleSheetLayout", notify: refreshStyles},
93         {name: "StyleSheetColors", notify: refreshStyles},
94         {name: "StyleSheet", notify: refreshStyles},
95         {name: "StyleSheetPrint", notify: refreshStyles},
96         {name: "PageTemplate", notify: refreshPageTemplate},
97         {name: "SiteTitle", notify: refreshPageTitle},
98         {name: "SiteSubtitle", notify: refreshPageTitle},
99         {name: "ColorPalette", notify: refreshColorPalette},
100         {name: null, notify: refreshDisplay}
101         ];
102
103 // Default tiddler templates
104 var DEFAULT_VIEW_TEMPLATE = 1;
105 var DEFAULT_EDIT_TEMPLATE = 2;
106 config.tiddlerTemplates = {
107         1: "ViewTemplate",
108         2: "EditTemplate"
109         };
110
111 // More messages (rather a legacy layout that shouldn't really be like this)
112 config.views = {
113         wikified: {
114                 tag: {}
115                 },
116         editor: {
117                 tagChooser: {}
118                 }
119         };
120
121 // Macros; each has a 'handler' member that is inserted later
122 config.macros = {
123         today: {},
124         version: {},
125         search: {sizeTextbox: 15},
126         tiddler: {},
127         tag: {},
128         tags: {},
129         tagging: {},
130         timeline: {},
131         allTags: {},
132         list: {
133                 all: {},
134                 missing: {},
135                 orphans: {},
136                 shadowed: {}
137                 },
138         closeAll: {},
139         permaview: {},
140         saveChanges: {},
141         slider: {},
142         option: {},
143         newTiddler: {},
144         newJournal: {},
145         sparkline: {},
146         tabs: {},
147         gradient: {},
148         message: {},
149         view: {},
150         edit: {},
151         tagChooser: {},
152         toolbar: {},
153         br: {},
154         plugins: {},
155         refreshDisplay: {},
156         importTiddlers: {}
157         };
158
159 // Commands supported by the toolbar macro
160 config.commands = {
161         closeTiddler: {},
162         closeOthers: {},
163         editTiddler: {},
164         saveTiddler: {hideReadOnly: true},
165         cancelTiddler: {},
166         deleteTiddler: {hideReadOnly: true},
167         permalink: {},
168         references: {},
169         jump: {}
170         };
171
172 // Browser detection... In a very few places, there's nothing else for it but to
173 // know what browser we're using.
174 config.userAgent = navigator.userAgent.toLowerCase();
175 config.browser = {
176         isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
177         ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
178         isSafari: config.userAgent.indexOf("applewebkit") != -1,
179         isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
180         firefoxDate: /Gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
181         isOpera: config.userAgent.indexOf("opera") != -1,
182         isLinux: config.userAgent.indexOf("linux") != -1,
183         isUnix: config.userAgent.indexOf("x11") != -1,
184         isMac: config.userAgent.indexOf("mac") != -1,
185         isWindows: config.userAgent.indexOf("win") != -1
186         };
187
188 // Basic regular expressions
189 config.textPrimitives = {
190         upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
191         lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
192         anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
193         anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
194         };
195 if(config.browser.isBadSafari)
196         config.textPrimitives = {
197                 upperLetter: "[A-Z\u00c0-\u00de]",
198                 lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
199                 anyLetter:   "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
200                 anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
201                 }
202 config.textPrimitives.sliceSeparator = "::";
203 config.textPrimitives.urlPattern = "[a-z]{3,8}:[^\\s:'\"][^\\s'\"]*(?:/|\\b)";
204 config.textPrimitives.unWikiLink = "~";
205 config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
206                                                                                                 config.textPrimitives.lowerLetter + "+" +
207                                                                                                 config.textPrimitives.upperLetter +
208                                                                                                 config.textPrimitives.anyLetter + "*)|(?:" +
209                                                                                                 config.textPrimitives.upperLetter + "{2,}" +
210                                                                                                 config.textPrimitives.lowerLetter + "+))";
211
212 config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
213 config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
214
215 config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
216 config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
217 config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
218         config.textPrimitives.brackettedLink + ")|(?:" + 
219         config.textPrimitives.urlPattern + ")","mg");
220 config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
221         config.textPrimitives.titledBrackettedLink + ")|(?:" +
222         config.textPrimitives.brackettedLink + ")|(?:" +
223         config.textPrimitives.urlPattern + ")","mg");
224
225 // ---------------------------------------------------------------------------------
226 // Shadow tiddlers
227 // ---------------------------------------------------------------------------------
228
229 config.shadowTiddlers = {
230         ColorPalette: "Background: #fff\n" + 
231                                   "Foreground: #000\n" +
232                                   "PrimaryPale: #8cf\n" +
233                                   "PrimaryLight: #18f\n" +
234                                   "PrimaryMid: #04b\n" +
235                                   "PrimaryDark: #014\n" +
236                                   "SecondaryPale: #ffc\n" +
237                                   "SecondaryLight: #fe8\n" +
238                                   "SecondaryMid: #db4\n" +
239                                   "SecondaryDark: #841\n" +
240                                   "TertiaryPale: #eee\n" +
241                                   "TertiaryLight: #ccc\n" +
242                                   "TertiaryMid: #999\n" +
243                                   "TertiaryDark: #666\n" +
244                                   "Error: #f88\n",
245         StyleSheet: "",
246         StyleSheetColors: "/*{{{*/\nbody {\n    background: [[ColorPalette::Background]];\n     color: [[ColorPalette::Foreground]];\n}\n\na{\n color: [[ColorPalette::PrimaryMid]];\n}\n\na:hover{\n   background: [[ColorPalette::PrimaryMid]];\n     color: [[ColorPalette::Background]];\n}\n\na img{\n     border: 0;\n}\n\nh1,h2,h3,h4,h5 {\n     color: [[ColorPalette::SecondaryDark]];\n       background: [[ColorPalette::PrimaryPale]];\n}\n\n.button {\n    color: [[ColorPalette::PrimaryDark]];\n border: 1px solid [[ColorPalette::Background]];\n}\n\n.button:hover {\n color: [[ColorPalette::PrimaryDark]];\n background: [[ColorPalette::SecondaryLight]];\n border-color: [[ColorPalette::SecondaryMid]];\n}\n\n.button:active {\n  color: [[ColorPalette::Background]];\n  background: [[ColorPalette::SecondaryMid]];\n   border: 1px solid [[ColorPalette::SecondaryDark]];\n}\n\n.header {\n    background: [[ColorPalette::PrimaryMid]];\n}\n\n.headerShadow {\n       color: [[ColorPalette::Foreground]];\n}\n\n.headerShadow a {\n  font-weight: normal;\n  color: [[ColorPalette::Foreground]];\n}\n\n.headerForeground {\n        color: [[ColorPalette::Background]];\n}\n\n.headerForeground a {\n      font-weight: normal;\n  color: [[ColorPalette::PrimaryPale]];\n}\n\n.tabSelected{\n     color: [[ColorPalette::PrimaryDark]];\n background: [[ColorPalette::TertiaryPale]];\n   border-left: 1px solid [[ColorPalette::TertiaryLight]];\n       border-top: 1px solid [[ColorPalette::TertiaryLight]];\n        border-right: 1px solid [[ColorPalette::TertiaryLight]];\n}\n\n.tabUnselected {\n       color: [[ColorPalette::Background]];\n  background: [[ColorPalette::TertiaryMid]];\n}\n\n.tabContents {\n       color: [[ColorPalette::PrimaryDark]];\n background: [[ColorPalette::TertiaryPale]];\n   border: 1px solid [[ColorPalette::TertiaryLight]];\n}\n\n.tabContents .button {\n        border: 0;}\n\n#sidebar {\n}\n\n#sidebarOptions input {\n      border: 1px solid [[ColorPalette::PrimaryMid]];\n}\n\n#sidebarOptions .sliderPanel {\n  background: [[ColorPalette::PrimaryPale]];\n}\n\n#sidebarOptions .sliderPanel a {\n     border: none;\n color: [[ColorPalette::PrimaryMid]];\n}\n\n#sidebarOptions .sliderPanel a:hover {\n     color: [[ColorPalette::Background]];\n  background: [[ColorPalette::PrimaryMid]];\n}\n\n#sidebarOptions .sliderPanel a:active {\n       color: [[ColorPalette::PrimaryMid]];\n  background: [[ColorPalette::Background]];\n}\n\n.wizard {\n     background: [[ColorPalette::SecondaryLight]];\n border-top: 1px solid [[ColorPalette::SecondaryMid]];\n border-left: 1px solid [[ColorPalette::SecondaryMid]];\n}\n\n.wizard h1 {\n     color: [[ColorPalette::SecondaryDark]];\n}\n\n.wizard h2 {\n    color: [[ColorPalette::Foreground]];\n}\n\n.wizardStep {\n      background: [[ColorPalette::Background]];\n     border-top: 1px solid [[ColorPalette::SecondaryMid]];\n border-bottom: 1px solid [[ColorPalette::SecondaryMid]];\n      border-left: 1px solid [[ColorPalette::SecondaryMid]];\n}\n\n.wizard .button {\n        color: [[ColorPalette::Background]];\n  background: [[ColorPalette::PrimaryMid]];\n     border-top: 1px solid [[ColorPalette::PrimaryLight]];\n border-right: 1px solid [[ColorPalette::PrimaryDark]];\n        border-bottom: 1px solid [[ColorPalette::PrimaryDark]];\n       border-left: 1px solid [[ColorPalette::PrimaryLight]];\n}\n\n.wizard .button:hover {\n  color: [[ColorPalette::PrimaryLight]];\n        background: [[ColorPalette::PrimaryDark]];\n    border-color: [[ColorPalette::PrimaryLight]];\n}\n\n.wizard .button:active {\n  color: [[ColorPalette::Background]];\n  background: [[ColorPalette::PrimaryMid]];\n     border-top: 1px solid [[ColorPalette::PrimaryLight]];\n border-right: 1px solid [[ColorPalette::PrimaryDark]];\n        border-bottom: 1px solid [[ColorPalette::PrimaryDark]];\n       border-left: 1px solid [[ColorPalette::PrimaryLight]];\n}\n\n#messageArea {\n   border: 1px solid [[ColorPalette::SecondaryDark]];\n    background: [[ColorPalette::SecondaryMid]];\n   color: [[ColorPalette::PrimaryDark]];\n}\n\n#messageArea .button {\n    padding: 0.2em 0.2em 0.2em 0.2em;\n     color: [[ColorPalette::PrimaryDark]];\n background: [[ColorPalette::Background]];\n}\n\n.popup {\n      background: [[ColorPalette::PrimaryLight]];\n   border: 1px solid [[ColorPalette::PrimaryMid]];\n}\n\n.popup hr {\n     color: [[ColorPalette::PrimaryDark]];\n background: [[ColorPalette::PrimaryDark]];\n    border-bottom: 1px;\n}\n\n.listBreak div{\n     border-bottom: 1px solid [[ColorPalette::PrimaryDark]];\n}\n\n.popup li.disabled {\n    color: [[ColorPalette::PrimaryMid]];\n}\n\n.popup li a, .popup li a:visited {\n color: [[ColorPalette::TertiaryPale]];\n        border: none;\n}\n\n.popup li a:hover {\n       background: [[ColorPalette::PrimaryDark]];\n    color: [[ColorPalette::Background]];\n  border: none;\n}\n\n.tiddler .defaultCommand {\n font-weight: bold;\n}\n\n.shadow .title {\n    color: [[ColorPalette::TertiaryDark]];\n}\n\n.title {\n color: [[ColorPalette::SecondaryDark]];\n}\n\n.subtitle {\n     color: [[ColorPalette::TertiaryDark]];\n}\n\n.toolbar {\n       color: [[ColorPalette::PrimaryMid]];\n}\n\n.tagging, .tagged {\n        border: 1px solid [[ColorPalette::TertiaryPale]];\n     background-color: [[ColorPalette::TertiaryPale]];\n}\n\n.selected .tagging, .selected .tagged {\n       background-color: [[ColorPalette::TertiaryLight]];\n    border: 1px solid [[ColorPalette::TertiaryMid]];\n}\n\n.tagging .listTitle, .tagged .listTitle {\n      color: [[ColorPalette::PrimaryDark]];\n}\n\n.tagging .button, .tagged .button {\n               border: none;\n}\n\n.footer {\n color: [[ColorPalette::TertiaryLight]];\n}\n\n.selected .footer {\n     color: [[ColorPalette::TertiaryMid]];\n}\n\n.sparkline {\n      background: [[ColorPalette::PrimaryPale]];\n    border: 0;\n}\n\n.sparktick {\n background: [[ColorPalette::PrimaryDark]];\n}\n\n.error, .errorButton {\n       color: [[ColorPalette::Foreground]];\n  background: [[ColorPalette::Error]];\n}\n\n.warning {\n color: [[ColorPalette::Foreground]];\n  background: [[ColorPalette::SecondaryPale]];\n}\n\n.cascade {\n background: [[ColorPalette::TertiaryPale]];\n   color: [[ColorPalette::TertiaryMid]];\n border: 1px solid [[ColorPalette::TertiaryMid]];\n}\n\n.imageLink, #displayArea .imageLink {\n  background: transparent;\n}\n\n.viewer .listTitle {list-style-type: none; margin-left: -2em;}\n\n.viewer .button {\n    border: 1px solid [[ColorPalette::SecondaryMid]];\n}\n\n.viewer blockquote {\n  border-left: 3px solid [[ColorPalette::TertiaryDark]];\n}\n\n.viewer table {\n  border: 2px solid [[ColorPalette::TertiaryDark]];\n}\n\n.viewer th, thead td {\n        background: [[ColorPalette::SecondaryMid]];\n   border: 1px solid [[ColorPalette::TertiaryDark]];\n     color: [[ColorPalette::Background]];\n}\n\n.viewer td, .viewer tr {\n   border: 1px solid [[ColorPalette::TertiaryDark]];\n}\n\n.viewer pre {\n border: 1px solid [[ColorPalette::SecondaryLight]];\n   background: [[ColorPalette::SecondaryPale]];\n}\n\n.viewer code {\n     color: [[ColorPalette::SecondaryDark]];\n}\n\n.viewer hr {\n    border: 0;\n    border-top: dashed 1px [[ColorPalette::TertiaryDark]];\n        color: [[ColorPalette::TertiaryDark]];\n}\n\n.highlight, .marked {\n    background: [[ColorPalette::SecondaryLight]];\n}\n\n.editor input {\n   border: 1px solid [[ColorPalette::PrimaryMid]];\n}\n\n.editor textarea {\n      border: 1px solid [[ColorPalette::PrimaryMid]];\n       width: 100%;\n}\n\n.editorFooter {\n    color: [[ColorPalette::TertiaryMid]];\n}\n\n/*}}}*/",
247         StyleSheetLayout: "/*{{{*/\n* html .tiddler {\n    height: 1%;\n}\n\nbody {\n   font-size: .75em;\n     font-family: arial,helvetica;\n margin: 0;\n    padding: 0;\n}\n\nh1,h2,h3,h4,h5 {\n    font-weight: bold;\n    text-decoration: none;\n        padding-left: 0.4em;\n}\n\nh1 {font-size: 1.35em;}\nh2 {font-size: 1.25em;}\nh3 {font-size: 1.1em;}\nh4 {font-size: 1em;}\nh5 {font-size: .9em;}\n\nhr {\n      height: 1px;\n}\n\na{\n text-decoration: none;\n}\n\ndt {font-weight: bold;}\n\nol { list-style-type: decimal }\nol ol { list-style-type: lower-alpha }\nol ol ol { list-style-type: lower-roman }\nol ol ol ol { list-style-type: decimal }\nol ol ol ol ol { list-style-type: lower-alpha }\nol ol ol ol ol ol { list-style-type: lower-roman }\nol ol ol ol ol ol ol { list-style-type: decimal }\n\n.txtOptionInput {\n     width: 11em;\n}\n\n#contentWrapper .chkOptionInput {\n  border: 0;\n}\n\n.externalLink {\n      text-decoration: underline;\n}\n\n.indent {margin-left:3em;}\n.outdent {margin-left:3em; text-indent:-3em;}\ncode.escaped {white-space:nowrap;}\n\n.tiddlyLinkExisting {\n      font-weight: bold;\n}\n\n.tiddlyLinkNonExisting {\n     font-style: italic;\n}\n\n/* the 'a' is required for IE, otherwise it renders the whole tiddler a bold */\na.tiddlyLinkNonExisting.shadow {\n   font-weight: bold;\n}\n\n#mainMenu .tiddlyLinkExisting, \n#mainMenu .tiddlyLinkNonExisting,\n#sidebarTabs .tiddlyLinkNonExisting{\n font-weight: normal;\n font-style: normal;\n}\n\n#sidebarTabs .tiddlyLinkExisting {\n font-weight: bold;\n font-style: normal;\n}\n\n.header {\n            position: relative;\n}\n\n.header a:hover {\n   background: transparent;\n}\n\n.headerShadow {\n        position: relative;\n   padding: 4.5em 0em 1em 1em;\n   left: -1px;\n   top: -1px;\n}\n\n.headerForeground {\n  position: absolute;\n   padding: 4.5em 0em 1em 1em;\n   left: 0px;\n    top: 0px;\n}\n\n.siteTitle {\n  font-size: 3em;\n}\n\n.siteSubtitle {\n font-size: 1.2em;\n}\n\n#mainMenu {\n   position: absolute;\n   left: 0;\n      width: 10em;\n  text-align: right;\n    line-height: 1.6em;\n   padding: 1.5em 0.5em 0.5em 0.5em;\n     font-size: 1.1em;\n}\n\n#sidebar {\n    position: absolute;\n   right: 3px;\n   width: 16em;\n  font-size: .9em;\n}\n\n#sidebarOptions {\n      padding-top: 0.3em;\n}\n\n#sidebarOptions a {\n margin: 0em 0.2em;\n    padding: 0.2em 0.3em;\n display: block;\n}\n\n#sidebarOptions input {\n margin: 0.4em 0.5em;\n}\n\n#sidebarOptions .sliderPanel {\n     margin-left: 1em;\n     padding: 0.5em;\n       font-size: .85em;\n}\n\n#sidebarOptions .sliderPanel a {\n      font-weight: bold;\n    display: inline;\n      padding: 0;\n}\n\n#sidebarOptions .sliderPanel input {\n        margin: 0 0 .3em 0;\n}\n\n#sidebarTabs .tabContents {\n width: 15em;\n  overflow: hidden;\n}\n\n.wizard {\n     padding: 0.1em 0em 0em 2em;\n}\n\n.wizard h1 {\n        font-size: 2em;\n       font-weight: bold;\n    background: none;\n     padding: 0em 0em 0em 0em;\n     margin: 0.4em 0em 0.2em 0em;\n}\n\n.wizard h2 {\n       font-size: 1.2em;\n     font-weight: bold;\n    background: none;\n     padding: 0em 0em 0em 0em;\n     margin: 0.2em 0em 0.2em 0em;\n}\n\n.wizardStep {\n      padding: 1em 1em 1em 1em;\n}\n\n.wizard .button {\n     margin: 0.5em 0em 0em 0em;\n    font-size: 1.2em;\n}\n\n#messageArea {\nposition:absolute; top:0; right:0; margin: 0.5em; padding: 0.5em;\n}\n\n*[id='messageArea'] {\nposition:fixed !important; z-index:99;}\n\n.messageToolbar {\ndisplay: block;\ntext-align: right;\n}\n\n#messageArea a{\n        text-decoration: underline;\n}\n\n.popup {\n    font-size: .9em;\n      padding: 0.2em;\n       list-style: none;\n     margin: 0;\n}\n\n.popup hr {\n  display: block;\n       height: 1px;\n  width: auto;\n  padding: 0;\n   margin: 0.2em 0em;\n}\n\n.listBreak {\n font-size: 1px;\n       line-height: 1px;\n}\n\n.listBreak div {\n      margin: 2px 0;\n}\n\n.popup li.disabled {\n     padding: 0.2em;\n}\n\n.popup li a{\n    display: block;\n       padding: 0.2em;\n}\n\n.tabset {\n       padding: 1em 0em 0em 0.5em;\n}\n\n.tab {\n      margin: 0em 0em 0em 0.25em;\n   padding: 2px;\n}\n\n.tabContents {\n    padding: 0.5em;\n}\n\n.tabContents ul, .tabContents ol {\n      margin: 0;\n    padding: 0;\n}\n\n.txtMainTab .tabContents li {\n       list-style: none;\n}\n\n.tabContents li.listLink {\n     margin-left: .75em;\n}\n\n#displayArea {\n     margin: 1em 17em 0em 14em;\n}\n\n\n.toolbar {\n text-align: right;\n    font-size: .9em;\n      visibility: hidden;\n}\n\n.selected .toolbar {\n        visibility: visible;\n}\n\n.tiddler {\n padding: 1em 1em 0em 1em;\n}\n\n.missing .viewer,.missing .title {\n    font-style: italic;\n}\n\n.title {\n    font-size: 1.6em;\n     font-weight: bold;\n}\n\n.missing .subtitle {\n display: none;\n}\n\n.subtitle {\n      font-size: 1.1em;\n}\n\n.tiddler .button {\n    padding: 0.2em 0.4em;\n}\n\n.tagging {\nmargin: 0.5em 0.5em 0.5em 0;\nfloat: left;\ndisplay: none;\n}\n\n.isTag .tagging {\ndisplay: block;\n}\n\n.tagged {\nmargin: 0.5em;\nfloat: right;\n}\n\n.tagging, .tagged {\nfont-size: 0.9em;\npadding: 0.25em;\n}\n\n.tagging ul, .tagged ul {\nlist-style: none;margin: 0.25em;\npadding: 0;\n}\n\n.tagClear {\nclear: both;\n}\n\n.footer {\n      font-size: .9em;\n}\n\n.footer li {\ndisplay: inline;\n}\n\n* html .viewer pre {\n      width: 99%;\n   padding: 0 0 1em 0;\n}\n\n.viewer {\n   line-height: 1.4em;\n   padding-top: 0.5em;\n}\n\n.viewer .button {\n   margin: 0em 0.25em;\n   padding: 0em 0.25em;\n}\n\n.viewer blockquote {\n       line-height: 1.5em;\n   padding-left: 0.8em;\n  margin-left: 2.5em;\n}\n\n.viewer ul, .viewer ol{\n     margin-left: 0.5em;\n   padding-left: 1.5em;\n}\n\n.viewer table {\n    border-collapse: collapse;\n    margin: 0.8em 1.0em;\n}\n\n.viewer th, .viewer td, .viewer tr,.viewer caption{\n        padding: 3px;\n}\n\n.viewer table.listView {\n  font-size: 0.85em;\n    margin: 0.8em 1.0em;\n}\n\n.viewer table.listView th, .viewer table.listView td, .viewer table.listView tr {\n  padding: 0px 3px 0px 3px;\n}\n\n.viewer pre {\n padding: 0.5em;\n       margin-left: 0.5em;\n   font-size: 1.2em;\n     line-height: 1.4em;\n   overflow: auto;\n}\n\n.viewer code {\n  font-size: 1.2em;\n     line-height: 1.4em;\n}\n\n.editor {\nfont-size: 1.1em;\n}\n\n.editor input, .editor textarea {\n        display: block;\n       width: 100%;\n  font: inherit;\n}\n\n.editorFooter {\n  padding: 0.25em 0em;\n  font-size: .9em;\n}\n\n.editorFooter .button {\npadding-top: 0px; padding-bottom: 0px;}\n\n.fieldsetFix {border: 0;\npadding: 0;\nmargin: 1px 0px 1px 0px;\n}\n\n.sparkline {\n line-height: 1em;\n}\n\n.sparktick {\n  outline: 0;\n}\n\n.zoomer {\n   font-size: 1.1em;\n     position: absolute;\n   padding: 1em;\n}\n\n.cascade {\n        font-size: 1.1em;\n     position: absolute;\n   overflow: hidden;\n}\n/*}}}*/",
248         StyleSheetPrint: "/*{{{*/\n@media print {\n#mainMenu, #sidebar, #messageArea, .toolbar {display: none ! important;}\n#displayArea {margin: 1em 1em 0em 1em;}\n/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */\nnoscript {display:none;}\n}\n/*}}}*/",
249         PageTemplate: "<!--{{{-->\n<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>\n<div class='headerShadow'>\n<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;\n<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>\n</div>\n<div class='headerForeground'>\n<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;\n<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>\n</div>\n</div>\n<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>\n<div id='sidebar'>\n<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>\n<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>\n</div>\n<div id='displayArea'>\n<div id='messageArea'></div>\n<div id='tiddlerDisplay'></div>\n</div>\n<!--}}}-->",
250         ViewTemplate: "<!--{{{-->\n<div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler permalink references jump'></div>\n<div class='title' macro='view title'></div>\n<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date [[DD MMM YYYY]]'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date [[DD MMM YYYY]]'></span>)</div>\n<div class='tagging' macro='tagging'></div>\n<div class='tagged' macro='tags'></div>\n<div class='viewer' macro='view text wikified'></div>\n<div class='tagClear'></div>\n<!--}}}-->",
251         EditTemplate: "<!--{{{-->\n<div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'></div>\n<div class='title' macro='view title'></div>\n<div class='editor' macro='edit title'></div>\n<div class='editor' macro='edit text'></div>\n<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>\n<!--}}}-->",
252         MarkupPreHead: "<!--{{{-->\n<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>\n<!--}}}-->",
253         MarkupPostHead: "",
254         MarkupPreBody: "",
255         MarkupPostBody: ""
256         };
257
258 // ---------------------------------------------------------------------------------
259 // Translateable strings
260 // ---------------------------------------------------------------------------------
261
262 // Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
263
264 merge(config.options,{
265         txtUserName: "YourName"});
266
267 merge(config.messages,{
268         customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
269         pluginError: "Error: %0",
270         pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
271         pluginForced: "Executed because forced via 'systemConfigForce' tag",
272         pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
273         nothingSelected: "Nothing is selected. You must select one or more items first",
274         savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
275         subtitleUnknown: "(unknown)",
276         undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
277         shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
278         tiddlerLinkTooltip: "%0 - %1, %2",
279         externalLinkTooltip: "External link to %0",
280         noTags: "There are no tagged tiddlers",
281         notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
282         cantSaveError: "It's not possible to save changes. This could be because your browser doesn't support saving (instead, use FireFox if you can), or because the pathname to your TiddlyWiki file contains illegal characters",
283         invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
284         backupSaved: "Backup saved",
285         backupFailed: "Failed to save backup file",
286         rssSaved: "RSS feed saved",
287         rssFailed: "Failed to save RSS feed file",
288         emptySaved: "Empty template saved",
289         emptyFailed: "Failed to save empty template file",
290         mainSaved: "Main TiddlyWiki file saved",
291         mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
292         macroError: "Error in macro <<%0>>",
293         macroErrorDetails: "Error while executing macro <<%0>>:\n%1",
294         missingMacro: "No such macro",
295         overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
296         unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
297         confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
298         saveInstructions: "SaveChanges",
299         unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
300         tiddlerSaveError: "Error when saving tiddler '%0'",
301         tiddlerLoadError: "Error when loading tiddler '%0'",
302         wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
303         invalidFieldName: "Invalid field name %0",
304         fieldCannotBeChanged: "Field '%0' cannot be changed"});
305
306 merge(config.messages.messageClose,{
307         text: "close",
308         tooltip: "close this message area"});
309
310 config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
311 config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
312 config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
313 config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
314
315 merge(config.views.wikified.tag,{
316         labelNoTags: "no tags",
317         labelTags: "tags: ",
318         openTag: "Open tag '%0'",
319         tooltip: "Show tiddlers tagged with '%0'",
320         openAllText: "Open all",
321         openAllTooltip: "Open all of these tiddlers",
322         popupNone: "No other tiddlers tagged with '%0'"});
323
324 merge(config.views.wikified,{
325         defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
326         defaultModifier: "(missing)",
327         shadowModifier: "(built-in shadow tiddler)",
328         createdPrompt: "created"});
329
330 merge(config.views.editor,{
331         tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
332         defaultText: "Type the text for '%0'"});
333
334 merge(config.views.editor.tagChooser,{
335         text: "tags",
336         tooltip: "Choose existing tags to add to this tiddler",
337         popupNone: "There are no tags defined",
338         tagTooltip: "Add the tag '%0'"});
339
340 merge(config.macros.search,{
341         label: "search",
342         prompt: "Search this TiddlyWiki",
343         accessKey: "F",
344         successMsg: "%0 tiddlers found matching %1",
345         failureMsg: "No tiddlers found matching %0"});
346
347 merge(config.macros.tagging,{
348         label: "tagging: ",
349         labelNotTag: "not tagging",
350         tooltip: "List of tiddlers tagged with '%0'"});
351
352 merge(config.macros.timeline,{
353         dateFormat: "DD MMM YYYY"});
354
355 merge(config.macros.allTags,{
356         tooltip: "Show tiddlers tagged with '%0'",
357         noTags: "There are no tagged tiddlers"});
358
359 config.macros.list.all.prompt = "All tiddlers in alphabetical order";
360 config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
361 config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
362 config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
363
364 merge(config.macros.closeAll,{
365         label: "close all",
366         prompt: "Close all displayed tiddlers (except any that are being edited)"});
367
368 merge(config.macros.permaview,{
369         label: "permaview",
370         prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
371
372 merge(config.macros.saveChanges,{
373         label: "save changes",
374         prompt: "Save all tiddlers to create a new TiddlyWiki",
375         accessKey: "S"});
376
377 merge(config.macros.newTiddler,{
378         label: "new tiddler",
379         prompt: "Create a new tiddler",
380         title: "New Tiddler",
381         accessKey: "N"});
382
383 merge(config.macros.newJournal,{
384         label: "new journal",
385         prompt: "Create a new tiddler from the current date and time",
386         accessKey: "J"});
387
388 merge(config.macros.plugins,{
389         skippedText: "(This plugin has not been executed because it was added since startup)",
390         noPluginText: "There are no plugins installed",
391         confirmDeleteText: "Are you sure you want to delete these tiddlers:\n\n%0",
392         listViewTemplate : {
393                 columns: [
394                         {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
395                         {name: 'Title', field: 'title', tiddlerLink: 'title', title: "Title", type: 'TiddlerLink'},
396                         {name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
397                         {name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
398                         {name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
399                         {name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
400                         {name: 'Log', field: 'log', title: "Log", type: 'StringList'}
401                         ],
402                 rowClasses: [
403                         {className: 'error', field: 'error'},
404                         {className: 'warning', field: 'warning'}
405                         ],
406                 actions: [
407                         {caption: "More actions...", name: ''},
408                         {caption: "Remove systemConfig tag", name: 'remove'},
409                         {caption: "Delete these tiddlers forever", name: 'delete'}
410                         ]}
411         });
412
413 merge(config.macros.refreshDisplay,{
414         label: "refresh",
415         prompt: "Redraw the entire TiddlyWiki display"
416         });
417
418 merge(config.macros.importTiddlers,{
419         readOnlyWarning: "You cannot import tiddlers into a read-only TiddlyWiki. Try opening the TiddlyWiki file from a file:// URL",
420         defaultPath: "http://www.tiddlywiki.com/index.html",
421         fetchLabel: "fetch",
422         fetchPrompt: "Fetch the tiddlywiki file",
423         fetchError: "There were problems fetching the tiddlywiki file",
424         confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
425         wizardTitle: "Import tiddlers from another TiddlyWiki file",
426         step1: "Step 1: Locate the TiddlyWiki file",
427         step1prompt: "Enter the URL or pathname here: ",
428         step1promptFile: "...or browse for a file: ",
429         step1promptFeeds: "...or select a pre-defined feed: ",
430         step1feedPrompt: "Choose...",
431         step2: "Step 2: Loading TiddlyWiki file",
432         step2Text: "Please wait while the file is loaded from: %0",
433         step3: "Step 3: Choose the tiddlers to import",
434         step4: "%0 tiddler(s) imported",
435         step5: "Done",
436         listViewTemplate: {
437                 columns: [
438                         {name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
439                         {name: 'Title', field: 'title', title: "Title", type: 'String'},
440                         {name: 'Snippet', field: 'text', title: "Snippet", type: 'String'},
441                         {name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
442                         ],
443                 rowClasses: [
444                         ],
445                 actions: [
446                         {caption: "More actions...", name: ''},
447                         {caption: "Import these tiddlers", name: 'import'}
448                         ]}
449         });
450
451 merge(config.commands.closeTiddler,{
452         text: "close",
453         tooltip: "Close this tiddler"});
454
455 merge(config.commands.closeOthers,{
456         text: "close others",
457         tooltip: "Close all other tiddlers"});
458
459 merge(config.commands.editTiddler,{
460         text: "edit",
461         tooltip: "Edit this tiddler",
462         readOnlyText: "view",
463         readOnlyTooltip: "View the source of this tiddler"});
464
465 merge(config.commands.saveTiddler,{
466         text: "done",
467         tooltip: "Save changes to this tiddler"});
468
469 merge(config.commands.cancelTiddler,{
470         text: "cancel",
471         tooltip: "Undo changes to this tiddler",
472         warning: "Are you sure you want to abandon your changes to '%0'?",
473         readOnlyText: "done",
474         readOnlyTooltip: "View this tiddler normally"});
475
476 merge(config.commands.deleteTiddler,{
477         text: "delete",
478         tooltip: "Delete this tiddler",
479         warning: "Are you sure you want to delete '%0'?"});
480
481 merge(config.commands.permalink,{
482         text: "permalink",
483         tooltip: "Permalink for this tiddler"});
484
485 merge(config.commands.references,{
486         text: "references",
487         tooltip: "Show tiddlers that link to this one",
488         popupNone: "No references"});
489
490 merge(config.commands.jump,{
491         text: "jump",
492         tooltip: "Jump to another open tiddler"});
493
494 merge(config.shadowTiddlers,{
495         DefaultTiddlers: "GettingStarted",
496         MainMenu: "GettingStarted",
497         SiteTitle: "My TiddlyWiki",
498         SiteSubtitle: "a reusable non-linear personal web notebook",
499         SiteUrl: "http://www.tiddlywiki.com/",
500         GettingStarted: "To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:\n* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)\n* MainMenu: The menu (usually on the left)\n* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened\nYou'll also need to enter your username for signing your edits: <<option txtUserName>>",
501         SideBarOptions: "<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal 'DD MMM YYYY'>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel 'options Â»' 'Change TiddlyWiki advanced options'>>",
502         OptionsPanel: "These InterfaceOptions for customising TiddlyWiki are saved in your browser\n\nYour username for signing your edits. Write it as a WikiWord (eg JoeBloggs)\n\n<<option txtUserName>>\n<<option chkSaveBackups>> SaveBackups\n<<option chkAutoSave>> AutoSave\n<<option chkRegExpSearch>> RegExpSearch\n<<option chkCaseSensitiveSearch>> CaseSensitiveSearch\n<<option chkAnimate>> EnableAnimations\n\n----\nAdvancedOptions\nPluginManager\nImportTiddlers",
503         AdvancedOptions: "<<option chkGenerateAnRssFeed>> GenerateAnRssFeed\n<<option chkOpenInNewWindow>> OpenLinksInNewWindow\n<<option chkSaveEmptyTemplate>> SaveEmptyTemplate\n<<option chkToggleLinks>> Clicking on links to tiddlers that are already open causes them to close\n^^(override with Control or other modifier key)^^\n<<option chkHttpReadOnly>> HideEditingFeatures when viewed over HTTP\n<<option chkForceMinorUpdate>> Treat edits as MinorChanges by preserving date and time\n^^(override with Shift key when clicking 'done' or by pressing Ctrl-Shift-Enter^^\n<<option chkConfirmDelete>> ConfirmBeforeDeleting\nMaximum number of lines in a tiddler edit box: <<option txtMaxEditRows>>\nFolder name for backup files: <<option txtBackupFolder>>\n<<option chkInsertTabs>> Use tab key to insert tab characters instead of jumping to next field",
504         SideBarTabs: "<<tabs txtMainTab Timeline Timeline TabTimeline All 'All tiddlers' TabAll Tags 'All tags' TabTags More 'More lists' TabMore>>",
505         TabTimeline: "<<timeline>>",
506         TabAll: "<<list all>>",
507         TabTags: "<<allTags>>",
508         TabMore: "<<tabs txtMoreTab Missing 'Missing tiddlers' TabMoreMissing Orphans 'Orphaned tiddlers' TabMoreOrphans Shadowed 'Shadowed tiddlers' TabMoreShadowed>>",
509         TabMoreMissing: "<<list missing>>",
510         TabMoreOrphans: "<<list orphans>>",
511         TabMoreShadowed: "<<list shadowed>>",
512         PluginManager: "<<plugins>>",
513         ImportTiddlers: "<<importTiddlers>>"});
514
515 // ---------------------------------------------------------------------------------
516 // Main
517 // ---------------------------------------------------------------------------------
518
519 var params = null; // Command line parameters
520 var store = null; // TiddlyWiki storage
521 var story = null; // Main story
522 var formatter = null; // Default formatters for the wikifier
523 config.parsers = {}; // Hashmap of alternative parsers for the wikifier
524 var anim = new Animator(); // Animation engine
525 var readOnly = false; // Whether we're in readonly mode
526 var highlightHack = null; // Embarrassing hack department...
527 var hadConfirmExit = false; // Don't warn more than once
528 var safeMode = false; // Disable all plugins and cookies
529 var installedPlugins = []; // Information filled in when plugins are executed
530 var startingUp = false; // Whether we're in the process of starting up
531 var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
532
533 // Whether to use the JavaSaver applet
534 var useJavaSaver = config.browser.isSafari || config.browser.isOpera;
535
536 // Starting up
537 function main()
538 {
539         var now, then = new Date();
540         startingUp = true;
541         window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
542         params = getParameters();
543         if(params)
544                 params = params.parseParams("open",null,false);
545         store = new TiddlyWiki();
546         invokeParamifier(params,"oninit");
547         story = new Story("tiddlerDisplay","tiddler");
548         addEvent(document,"click",Popup.onDocumentClick);
549         saveTest();
550         loadOptionsCookie();
551         for(var s=0; s<config.notifyTiddlers.length; s++)
552                 store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
553         store.loadFromDiv("storeArea","store",true);
554         invokeParamifier(params,"onload");
555         var pluginProblem = loadPlugins();
556         formatter = new Formatter(config.formatters);
557         readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
558         invokeParamifier(params,"onconfig");
559         store.notifyAll();
560         restart();
561         if(pluginProblem)
562                 {
563                 story.displayTiddler(null,"PluginManager");
564                 displayMessage(config.messages.customConfigError);
565                 }
566         now = new Date();
567         if(config.displayStartupTime)
568                 displayMessage("TiddlyWiki startup in " + (now-then)/1000 + " seconds");
569         startingUp = false;
570 }
571
572 // Restarting
573 function restart()
574 {
575         invokeParamifier(params,"onstart");
576         if(story.isEmpty())
577                 {
578                 var defaultParams = store.getTiddlerText("DefaultTiddlers").parseParams("open",null,false);
579                 invokeParamifier(defaultParams,"onstart");
580                 }
581         window.scrollTo(0,0);
582 }
583
584 function saveTest()
585 {
586         var saveTest = document.getElementById("saveTest");
587         if(saveTest.hasChildNodes())
588                 alert(config.messages.savedSnapshotError);
589         saveTest.appendChild(document.createTextNode("savetest"));
590 }
591
592 function loadPlugins()
593 {
594         if(safeMode)
595                 return false;
596         var configTiddlers = store.getTaggedTiddlers("systemConfig");
597         installedPlugins = [];
598         var hadProblem = false;
599         for(var t=0; t<configTiddlers.length; t++)
600                 {
601                 tiddler = configTiddlers[t];
602                 pluginInfo = getPluginInfo(tiddler);
603                 if(isPluginExecutable(pluginInfo))
604                         {
605                         pluginInfo.executed = true;
606                         pluginInfo.error = false;
607                         try
608                                 {
609                                 if(tiddler.text && tiddler.text != "")
610                                         window.eval(tiddler.text);
611                                 }
612                         catch(e)
613                                 {
614                                 pluginInfo.log.push(config.messages.pluginError.format([exceptionText(e)]));
615                                 pluginInfo.error = true;
616                                 hadProblem = true;
617                                 }
618                         }
619                 else
620                         pluginInfo.warning = true;
621                 installedPlugins.push(pluginInfo);
622                 }
623         return hadProblem;
624 }
625
626 function getPluginInfo(tiddler)
627 {
628         var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","CoreVersion","Date","Source","Author","License","Browsers"]);
629         p.tiddler = tiddler;
630         p.title = tiddler.title;
631         p.log = [];
632         return p;
633 }
634
635 // Check that a particular plugin is valid for execution
636 function isPluginExecutable(plugin)
637 {
638         if(plugin.tiddler.isTagged("systemConfigDisable"))
639                 return verifyTail(plugin,false,config.messages.pluginDisabled);
640         if(plugin.tiddler.isTagged("systemConfigForce"))
641                 return verifyTail(plugin,true,config.messages.pluginForced);
642         if(plugin["CoreVersion"])
643                 {
644                 var coreVersion = plugin["CoreVersion"].split(".");
645                 var w = parseInt(coreVersion[0]) - version.major;
646                 if(w == 0 && coreVersion[1])
647                         w = parseInt(coreVersion[1]) - version.minor;
648                 if(w == 0 && coreVersion[2])
649                         w = parseInt(coreVersion[2]) - version.revision;
650                 if(w > 0)
651                         return verifyTail(plugin,false,config.messages.pluginVersionError);
652                 }
653         return true;
654 }
655
656 function verifyTail(plugin,result,message)
657 {
658         plugin.log.push(message);
659         return result;
660 }
661
662 function invokeMacro(place,macro,params,wikifier,tiddler)
663 {
664         try
665                 {
666                 var m = config.macros[macro];
667                 if(m && m.handler)
668                         m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
669                 else
670                         createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
671                 }
672         catch(ex)
673                 {
674                 createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
675                 }
676 }
677
678 // ---------------------------------------------------------------------------------
679 // Paramifiers
680 // ---------------------------------------------------------------------------------
681
682 function getParameters()
683 {
684         var p = null;
685         if(window.location.hash)
686                 {
687                 p = decodeURI(window.location.hash.substr(1));
688                 if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
689                         p = convertUTF8ToUnicode(p);
690                 }
691         return p;
692 }
693
694 function invokeParamifier(params,handler)
695 {
696         if(!params || params.length == undefined || params.length <= 1)
697                 return;
698         for(var t=1; t<params.length; t++)
699                 {
700                 var p = config.paramifiers[params[t].name];
701                 if(p && p[handler] instanceof Function)
702                         p[handler](params[t].value);
703                 }
704 }
705
706 config.paramifiers = {};
707
708 config.paramifiers.start = {
709         oninit: function(v) {
710                 safeMode = v.toLowerCase() == "safe";
711                 }
712 };
713
714 config.paramifiers.open = {
715         onstart: function(v) {
716                 story.displayTiddler("bottom",v,null,false,false);
717                 }
718 };
719
720 config.paramifiers.story = {
721         onstart: function(v) {
722                 var list = store.getTiddlerText(v,"").parseParams("open",null,false);
723                 invokeParamifier(list,"onstart");
724                 }
725 };
726
727 config.paramifiers.search = {
728         onstart: function(v) {
729                 story.search(v,false,false);
730                 }
731 };
732
733 config.paramifiers.searchRegExp = {
734         onstart: function(v) {
735                 story.prototype.search(v,false,true);
736                 }
737 };
738
739 config.paramifiers.tag = {
740         onstart: function(v) {
741                 var tagged = store.getTaggedTiddlers(v,"title");
742                 for(var t=0; t<tagged.length; t++)
743                         story.displayTiddler("bottom",tagged[t].title,null,false,false);
744                 }
745 };
746
747 config.paramifiers.newTiddler = {
748         onstart: function(v) {
749                 if(!readOnly)
750                         {
751                         story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
752                         story.focusTiddler(v,"text");
753                         }
754                 }
755 };
756
757 config.paramifiers.newJournal = {
758         onstart: function(v) {
759                 if(!readOnly)
760                         {
761                         var now = new Date();
762                         var title = now.formatString(v.trim());
763                         story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
764                         story.focusTiddler(title,"text");
765                         }
766                 }
767 };
768
769 // ---------------------------------------------------------------------------------
770 // Formatter helpers
771 // ---------------------------------------------------------------------------------
772
773 function Formatter(formatters)
774 {
775         this.formatters = [];
776         var pattern = [];
777         for(var n=0; n<formatters.length; n++)
778                 {
779                 pattern.push("(" + formatters[n].match + ")");
780                 this.formatters.push(formatters[n]);
781                 }
782         this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
783 }
784
785 config.formatterHelpers = {
786
787         createElementAndWikify: function(w)
788         {
789                 w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
790         },
791         
792         inlineCssHelper: function(w)
793         {
794                 var styles = [];
795                 config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
796                 var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
797                 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch)
798                         {
799                         var s,v;
800                         if(lookaheadMatch[1])
801                                 {
802                                 s = lookaheadMatch[1].unDash();
803                                 v = lookaheadMatch[2];
804                                 }
805                         else
806                                 {
807                                 s = lookaheadMatch[3].unDash();
808                                 v = lookaheadMatch[4];
809                                 }
810                         if (s=="bgcolor")
811                                 s = "backgroundColor";
812                         styles.push({style: s, value: v});
813                         w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
814                         config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
815                         lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
816                         }
817                 return styles;
818         },
819
820         applyCssHelper: function(e,styles)
821         {
822                 for(var t=0; t< styles.length; t++)
823                         {
824                         try
825                                 {
826                                 e.style[styles[t].style] = styles[t].value;
827                                 }
828                         catch (ex)
829                                 {
830                                 }
831                         }
832         },
833
834         enclosedTextHelper: function(w)
835         {
836                 this.lookaheadRegExp.lastIndex = w.matchStart;
837                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
838                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
839                         {
840                         var text = lookaheadMatch[1];
841                         if(config.browser.isIE)
842                                 text = text.replace(/\n/g,"\r");
843                         createTiddlyElement(w.output,this.element,null,null,text);
844                         w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
845                         }
846         },
847
848         isExternalLink: function(link)
849         {
850                 if(store.tiddlerExists(link) || store.isShadowTiddler(link))
851                         {
852                         //# Definitely not an external link
853                         return false;
854                         }
855                 var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
856                 if(urlRegExp.exec(link))
857                         {
858                         // Definitely an external link
859                         return true;
860                         }
861                 if (link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1)
862                         {
863                         //# Link contains . / or \ so is probably an external link
864                         return true;
865                         }
866                 //# Otherwise assume it is not an external link
867                 return false;
868         }
869
870 };
871
872 // ---------------------------------------------------------------------------------
873 // Standard formatters
874 // ---------------------------------------------------------------------------------
875
876 config.formatters = [
877 {
878         name: "table",
879         match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
880         lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
881         rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
882         cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
883         cellTermRegExp: /((?:\x20*)\|)/mg,
884         rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
885
886         handler: function(w)
887         {
888                 var table = createTiddlyElement(w.output,"table");
889                 var prevColumns = [];
890                 var currRowType = null;
891                 var rowContainer;
892                 var rowCount = 0;
893                 w.nextMatch = w.matchStart;
894                 this.lookaheadRegExp.lastIndex = w.nextMatch;
895                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
896                 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch)
897                         {
898                         var nextRowType = lookaheadMatch[2];
899                         if(nextRowType == "k")
900                                 {
901                                 table.className = lookaheadMatch[1];
902                                 w.nextMatch += lookaheadMatch[0].length+1;
903                                 }
904                         else
905                                 {
906                                 if(nextRowType != currRowType)
907                                         {
908                                         rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
909                                         currRowType = nextRowType;
910                                         }
911                                 if(currRowType == "c")
912                                         {
913                                         // Caption
914                                         w.nextMatch++;
915                                         if(rowContainer != table.firstChild)
916                                                 table.insertBefore(rowContainer,table.firstChild);
917                                         rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
918                                         w.subWikifyTerm(rowContainer,this.rowTermRegExp);
919                                         }
920                                 else
921                                         {
922                                         this.rowHandler(w,createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow"),prevColumns);
923                                         rowCount++;
924                                         }
925                                 }
926                         this.lookaheadRegExp.lastIndex = w.nextMatch;
927                         lookaheadMatch = this.lookaheadRegExp.exec(w.source);
928                         }
929         },
930         rowHandler: function(w,e,prevColumns)
931         {
932                 var col = 0;
933                 var colSpanCount = 1;
934                 var prevCell = null;
935                 this.cellRegExp.lastIndex = w.nextMatch;
936                 var cellMatch = this.cellRegExp.exec(w.source);
937                 while(cellMatch && cellMatch.index == w.nextMatch)
938                         {
939                         if(cellMatch[1] == "~")
940                                 {
941                                 // Rowspan
942                                 var last = prevColumns[col];
943                                 if(last)
944                                         {
945                                         last.rowSpanCount++;
946                                         last.element.setAttribute("rowspan",last.rowSpanCount);
947                                         last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
948                                         last.element.valign = "center";
949                                         }
950                                 w.nextMatch = this.cellRegExp.lastIndex-1;
951                                 }
952                         else if(cellMatch[1] == ">")
953                                 {
954                                 // Colspan
955                                 colSpanCount++;
956                                 w.nextMatch = this.cellRegExp.lastIndex-1;
957                                 }
958                         else if(cellMatch[2])
959                                 {
960                                 // End of row
961                                 if(prevCell && colSpanCount > 1)
962                                         {
963                                         prevCell.setAttribute("colspan",colSpanCount);
964                                         prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
965                                         }
966                                 w.nextMatch = this.cellRegExp.lastIndex;
967                                 break;
968                                 }
969                         else
970                                 {
971                                 // Cell
972                                 w.nextMatch++;
973                                 var styles = config.formatterHelpers.inlineCssHelper(w);
974                                 var spaceLeft = false;
975                                 var chr = w.source.substr(w.nextMatch,1);
976                                 while(chr == " ")
977                                         {
978                                         spaceLeft = true;
979                                         w.nextMatch++;
980                                         chr = w.source.substr(w.nextMatch,1);
981                                         }
982                                 var cell;
983                                 if(chr == "!")
984                                         {
985                                         cell = createTiddlyElement(e,"th");
986                                         w.nextMatch++;
987                                         }
988                                 else
989                                         cell = createTiddlyElement(e,"td");
990                                 prevCell = cell;
991                                 prevColumns[col] = {rowSpanCount:1, element:cell};
992                                 if(colSpanCount > 1)
993                                         {
994                                         cell.setAttribute("colspan",colSpanCount);
995                                         cell.setAttribute("colSpan",colSpanCount); // Needed for IE
996                                         colSpanCount = 1;
997                                         }
998                                 config.formatterHelpers.applyCssHelper(cell,styles);
999                                 w.subWikifyTerm(cell,this.cellTermRegExp);
1000                                 if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
1001                                         cell.align = spaceLeft ? "center" : "left";
1002                                 else if(spaceLeft)
1003                                         cell.align = "right";
1004                                 w.nextMatch--;
1005                                 }
1006                         col++;
1007                         this.cellRegExp.lastIndex = w.nextMatch;
1008                         cellMatch = this.cellRegExp.exec(w.source);
1009                         }
1010         }
1011 },
1012
1013 {
1014         name: "heading",
1015         match: "^!{1,5}",
1016         termRegExp: /(\n)/mg,
1017         handler: function(w)
1018         {
1019                 w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
1020         }
1021 },
1022
1023 {
1024         name: "list",
1025         match: "^(?:(?:(?:\\*)|(?:#)|(?:;)|(?::))+)",
1026         lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
1027         termRegExp: /(\n)/mg,
1028         handler: function(w)
1029         {
1030                 var placeStack = [w.output];
1031                 var currLevel = 0, currType = null;
1032                 var listLevel, listType, itemType;
1033                 w.nextMatch = w.matchStart;
1034                 this.lookaheadRegExp.lastIndex = w.nextMatch;
1035                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1036                 while(lookaheadMatch && lookaheadMatch.index == w.nextMatch)
1037                         {
1038                         if(lookaheadMatch[1])
1039                                 {
1040                                 listType = "ul";
1041                                 itemType = "li";
1042                                 }
1043                         else if(lookaheadMatch[2])
1044                                 {
1045                                 listType = "ol";
1046                                 itemType = "li";
1047                                 }
1048                         else if(lookaheadMatch[3])
1049                                 {
1050                                 listType = "dl";
1051                                 itemType = "dt";
1052                                 }
1053                         else if(lookaheadMatch[4])
1054                                 {
1055                                 listType = "dl";
1056                                 itemType = "dd";
1057                                 }
1058                         listLevel = lookaheadMatch[0].length;
1059                         w.nextMatch += lookaheadMatch[0].length;
1060                         if(listLevel > currLevel)
1061                                 {
1062                                 for(var t=currLevel; t<listLevel; t++)
1063                                         placeStack.push(createTiddlyElement(placeStack[placeStack.length-1],listType));
1064                                 }
1065                         else if(listLevel < currLevel)
1066                                 {
1067                                 for(var t=currLevel; t>listLevel; t--)
1068                                         placeStack.pop();
1069                                 }
1070                         else if(listLevel == currLevel && listType != currType)
1071                                 {
1072                                 placeStack.pop();
1073                                 placeStack.push(createTiddlyElement(placeStack[placeStack.length-1],listType));
1074                                 }
1075                         currLevel = listLevel;
1076                         currType = listType;
1077                         var e = createTiddlyElement(placeStack[placeStack.length-1],itemType);
1078                         w.subWikifyTerm(e,this.termRegExp);
1079                         this.lookaheadRegExp.lastIndex = w.nextMatch;
1080                         lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1081                 }
1082         }
1083 },
1084
1085 {
1086         name: "quoteByBlock",
1087         match: "^<<<\\n",
1088         termRegExp: /(^<<<(\n|$))/mg,
1089         element: "blockquote",
1090         handler: config.formatterHelpers.createElementAndWikify
1091 },
1092
1093 {
1094         name: "quoteByLine",
1095         match: "^>+",
1096         lookaheadRegExp: /^>+/mg,
1097         termRegExp: /(\n)/mg,
1098         element: "blockquote",
1099         handler: function(w)
1100         {
1101                 var placeStack = [w.output];
1102                 var currLevel = 0;
1103                 var newLevel = w.matchLength;
1104                 var t;
1105                 do {
1106                         if(newLevel > currLevel)
1107                                 {
1108                                 for(t=currLevel; t<newLevel; t++)
1109                                         placeStack.push(createTiddlyElement(placeStack[placeStack.length-1],this.element));
1110                                 }
1111                         else if(newLevel < currLevel)
1112                                 {
1113                                 for(t=currLevel; t>newLevel; t--)
1114                                         placeStack.pop();
1115                                 }
1116                         currLevel = newLevel;
1117                         w.subWikifyTerm(placeStack[placeStack.length-1],this.termRegExp);
1118                         createTiddlyElement(placeStack[placeStack.length-1],"br");
1119                         this.lookaheadRegExp.lastIndex = w.nextMatch;
1120                         var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1121                         var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
1122                         if(matched)
1123                                 {
1124                                 newLevel = lookaheadMatch[0].length;
1125                                 w.nextMatch += lookaheadMatch[0].length;
1126                                 }
1127                 } while(matched);
1128         }
1129 },
1130
1131 {
1132         name: "rule",
1133         match: "^----+$\\n?",
1134         handler: function(w)
1135         {
1136                 createTiddlyElement(w.output,"hr");
1137         }
1138 },
1139
1140 {
1141         name: "monospacedByLine",
1142         match: "^\\{\\{\\{\\n",
1143         lookaheadRegExp: /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg,
1144         element: "pre",
1145         handler: config.formatterHelpers.enclosedTextHelper
1146 },
1147
1148 {
1149         name: "monospacedByLineForCSS",
1150         match: "^/\\*[\\{]{3}\\*/\\n",
1151         lookaheadRegExp: /\/\*[\{]{3}\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*[\}]{3}\*\/$\n?)/mg,
1152         element: "pre",
1153         handler: config.formatterHelpers.enclosedTextHelper
1154 },
1155
1156 {
1157         name: "monospacedByLineForPlugin",
1158         match: "^//\\{\\{\\{\\n",
1159         lookaheadRegExp: /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg,
1160         element: "pre",
1161         handler: config.formatterHelpers.enclosedTextHelper
1162 },
1163
1164 {
1165         name: "monospacedByLineForTemplate",
1166         match: "^<!--[\\{]{3}-->\\n",
1167         lookaheadRegExp: /<!--[\{]{3}-->\n*((?:^[^\n]*\n)+?)(\n*^<!--[\}]{3}-->$\n?)/mg, 
1168         element: "pre",
1169         handler: config.formatterHelpers.enclosedTextHelper
1170 },
1171
1172 {
1173         name: "wikifyCommentForPlugin", 
1174         match: "^/\\*\\*\\*\\n",
1175         termRegExp: /(^\*\*\*\/\n)/mg,
1176         handler: function(w)
1177         {
1178                 w.subWikifyTerm(w.output,this.termRegExp);
1179         }
1180 },
1181
1182 {
1183         name: "wikifyCommentForTemplate", 
1184         match: "^<!---\\n",
1185         termRegExp: /(^--->\n)/mg,
1186         handler: function(w) 
1187         {
1188                 w.subWikifyTerm(w.output,this.termRegExp);
1189         }
1190 },
1191
1192 {
1193         name: "macro",
1194         match: "<<",
1195         lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
1196         handler: function(w)
1197         {
1198                 this.lookaheadRegExp.lastIndex = w.matchStart;
1199                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1200                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1])
1201                         {
1202                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1203                         invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
1204                         }
1205         }
1206 },
1207
1208 {
1209         name: "prettyLink",
1210         match: "\\[\\[",
1211         lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
1212         handler: function(w)
1213         {
1214                 this.lookaheadRegExp.lastIndex = w.matchStart;
1215                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1216                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
1217                         {
1218                         var e;
1219                         var text = lookaheadMatch[1];
1220                         if(lookaheadMatch[3])
1221                                 {
1222                                 // Pretty bracketted link
1223                                 var link = lookaheadMatch[3];
1224                                 e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link))
1225                                                 ? createExternalLink(w.output,link)
1226                                                 : createTiddlyLink(w.output,link,false,null,w.isStatic);
1227                                 }
1228                         else
1229                                 {
1230                                 // Simple bracketted link
1231                                 e = createTiddlyLink(w.output,text,false,null,w.isStatic);
1232                                 }
1233                         createTiddlyText(e,text);
1234                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1235                         }
1236         }
1237 },
1238
1239 {
1240         name: "unWikiLink",
1241         match: config.textPrimitives.unWikiLink+config.textPrimitives.wikiLink,
1242         handler: function(w)
1243         {
1244                 w.outputText(w.output,w.matchStart+1,w.nextMatch);
1245         }
1246 },
1247
1248 {
1249         name: "wikiLink",
1250         match: config.textPrimitives.wikiLink,
1251         handler: function(w)
1252         {
1253                 if(w.matchStart > 0)
1254                         {
1255                         var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
1256                         preRegExp.lastIndex = w.matchStart-1;
1257                         var preMatch = preRegExp.exec(w.source);
1258                         if(preMatch.index == w.matchStart-1)
1259                                 {
1260                                 w.outputText(w.output,w.matchStart,w.nextMatch);
1261                                 return;
1262                                 }
1263                         }
1264                 if(w.autoLinkWikiWords == true || store.isShadowTiddler(w.matchText))
1265                         {
1266                         var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic);
1267                         w.outputText(link,w.matchStart,w.nextMatch);
1268                         }
1269                 else
1270                         {
1271                         w.outputText(w.output,w.matchStart,w.nextMatch);
1272                         }
1273         }
1274 },
1275
1276 {
1277         name: "urlLink",
1278         match: config.textPrimitives.urlPattern,
1279         handler: function(w)
1280         {
1281                 w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
1282         }
1283 },
1284
1285 {
1286         name: "image",
1287         match: "\\[[<>]?[Ii][Mm][Gg]\\[",
1288         lookaheadRegExp: /\[(<?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
1289         handler: function(w)
1290         {
1291                 this.lookaheadRegExp.lastIndex = w.matchStart;
1292                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1293                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart) // Simple bracketted link
1294                         {
1295                         var e = w.output;
1296                         if(lookaheadMatch[5])
1297                                 {
1298                                 var link = lookaheadMatch[5];
1299                                 e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic);
1300                                 addClass(e,"imageLink");
1301                                 }
1302                         var img = createTiddlyElement(e,"img");
1303                         if(lookaheadMatch[1])
1304                                 img.align = "left";
1305                         else if(lookaheadMatch[2])
1306                                 img.align = "right";
1307                         if(lookaheadMatch[3])
1308                                 img.title = lookaheadMatch[3];
1309                         img.src = lookaheadMatch[4];
1310                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1311                         }
1312         }
1313 },
1314
1315 {
1316         name: "html",
1317         match: "<[Hh][Tt][Mm][Ll]>",
1318         lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
1319         handler: function(w)
1320         {
1321                 this.lookaheadRegExp.lastIndex = w.matchStart;
1322                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
1323                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
1324                         {
1325                         createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
1326                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1327                         }
1328         }
1329 },
1330
1331 {
1332         name: "commentByBlock",
1333         match: "/%",
1334         lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
1335         handler: function(w)
1336         {
1337                 this.lookaheadRegExp.lastIndex = w.matchStart;
1338                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
1339                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
1340                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1341         }
1342 },
1343
1344 {
1345         name: "boldByChar",
1346         match: "''",
1347         termRegExp: /('')/mg,
1348         element: "strong",
1349         handler: config.formatterHelpers.createElementAndWikify
1350 },
1351
1352 {
1353         name: "italicByChar",
1354         match: "//",
1355         termRegExp: /(\/\/)/mg,
1356         element: "em",
1357         handler: config.formatterHelpers.createElementAndWikify
1358 },
1359
1360 {
1361         name: "underlineByChar",
1362         match: "__",
1363         termRegExp: /(__)/mg,
1364         element: "u",
1365         handler: config.formatterHelpers.createElementAndWikify
1366 },
1367
1368 {
1369         name: "strikeByChar",
1370         match: "--(?!\\s|$)",
1371         termRegExp: /((?!\s)--|(?=\n\n))/mg,
1372         element: "strike",
1373         handler: config.formatterHelpers.createElementAndWikify
1374 },
1375
1376 {
1377         name: "superscriptByChar",
1378         match: "\\^\\^",
1379         termRegExp: /(\^\^)/mg,
1380         element: "sup",
1381         handler: config.formatterHelpers.createElementAndWikify
1382 },
1383
1384 {
1385         name: "subscriptByChar",
1386         match: "~~",
1387         termRegExp: /(~~)/mg,
1388         element: "sub",
1389         handler: config.formatterHelpers.createElementAndWikify
1390 },
1391
1392 {
1393         name: "monospacedByChar",
1394         match: "\\{\\{\\{",
1395         lookaheadRegExp: /\{\{\{((?:.|\n)*?)\}\}\}/mg,
1396         handler: function(w)
1397         {
1398                 this.lookaheadRegExp.lastIndex = w.matchStart;
1399                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
1400                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
1401                         {
1402                         createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
1403                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1404                         }
1405         }
1406 },
1407
1408 {
1409         name: "styleByChar",
1410         match: "@@",
1411         termRegExp: /(@@)/mg,
1412         handler:  function(w)
1413         {
1414                 var e = createTiddlyElement(w.output,"span");
1415                 var styles = config.formatterHelpers.inlineCssHelper(w);
1416                 if(styles.length == 0)
1417                         e.className = "marked";
1418                 else
1419                         config.formatterHelpers.applyCssHelper(e,styles);
1420                 w.subWikifyTerm(e,this.termRegExp);
1421         }
1422 },
1423
1424 {
1425         name: "lineBreak",
1426         match: "\\n|<br ?/?>",
1427         handler: function(w)
1428         {
1429                 createTiddlyElement(w.output,"br");
1430         }
1431 },
1432
1433 {
1434         name: "rawText",
1435         match: "\\\"{3}|<nowiki>",
1436         lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
1437         handler: function(w)
1438         {
1439                 this.lookaheadRegExp.lastIndex = w.matchStart;
1440                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
1441                 if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
1442                         {
1443                         createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
1444                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1445                         }
1446         }
1447 },
1448
1449 {
1450         name: "mdash",
1451         match: "--",
1452         handler: function(w)
1453                 {
1454                 createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
1455                 }
1456 },
1457
1458 {
1459         name: "htmlEntitiesEncoding",
1460         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};)",
1461         handler: function(w)
1462                 {
1463                 createTiddlyElement(w.output,"span").innerHTML = w.matchText;
1464                 }
1465 },
1466
1467 {
1468         name: "customClasses",
1469         match: "\\{\\{",
1470         termRegExp: /(\}\}\})/mg,
1471         lookaheadRegExp: /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg,
1472         handler: function(w)
1473         {
1474                 this.lookaheadRegExp.lastIndex = w.matchStart;
1475                 var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
1476                 if(lookaheadMatch)
1477                         {
1478                         var e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
1479                         w.nextMatch = this.lookaheadRegExp.lastIndex;
1480                         w.subWikifyTerm(e,this.termRegExp);
1481                         }
1482         }
1483 }
1484
1485 ];
1486
1487 // ---------------------------------------------------------------------------------
1488 // Wikifier
1489 // ---------------------------------------------------------------------------------
1490
1491 function getParser(tiddler)
1492 {
1493         var f = formatter;
1494         if(tiddler!=null)
1495                 {
1496                 for(var i in config.parsers)
1497                         {
1498                         if(tiddler.isTagged(config.parsers[i].formatTag))
1499                                 {
1500                                 f = config.parsers[i];
1501                                 break;
1502                                 }
1503                         }
1504                 }
1505         return f;
1506 }
1507
1508 function wikify(source,output,highlightRegExp,tiddler)
1509 {
1510         if(source && source != "")
1511                 {
1512                 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
1513                 wikifier.subWikifyUnterm(output);
1514                 }
1515 }
1516
1517 function wikifyStatic(source,highlightRegExp,tiddler)
1518 {
1519         var e = createTiddlyElement(document.body,"div");
1520         e.style.display = "none";
1521         var html = "";
1522         if(source && source != "")
1523                 {
1524                 var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
1525                 wikifier.isStatic = true;
1526                 wikifier.subWikifyUnterm(e);
1527                 html = e.innerHTML;
1528                 e.parentNode.removeChild(e);
1529                 }
1530         return html;
1531 }
1532
1533 // Wikify a named tiddler to plain text
1534 function wikifyPlain(title)
1535 {
1536         if(store.tiddlerExists(title) || store.isShadowTiddler(title))
1537                 {
1538                 var wikifier = new Wikifier(store.getTiddlerText(title),formatter,null,store.getTiddler(title));
1539                 return wikifier.wikifyPlain();
1540                 }
1541         else
1542                 return "";
1543 }
1544
1545 // Highlight plain text into an element
1546 function highlightify(source,output,highlightRegExp)
1547 {
1548         if(source && source != "")
1549                 {
1550                 var wikifier = new Wikifier(source,formatter,highlightRegExp);
1551                 wikifier.outputText(output,0,source.length);
1552                 }
1553 }
1554
1555 // Construct a wikifier object
1556 // source - source string that's going to be wikified
1557 // formatter - Formatter() object containing the list of formatters to be used
1558 // highlightRegExp - regular expression of the text string to highlight
1559 // tiddler - reference to the tiddler that's taken to be the container for this wikification
1560 function Wikifier(source,formatter,highlightRegExp,tiddler)
1561 {
1562         this.source = source;
1563         this.output = null;
1564         this.formatter = formatter;
1565         this.nextMatch = 0;
1566         this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
1567         this.highlightRegExp = highlightRegExp;
1568         this.highlightMatch = null;
1569         this.isStatic = false;
1570         if(highlightRegExp)
1571                 {
1572                 highlightRegExp.lastIndex = 0;
1573                 this.highlightMatch = highlightRegExp.exec(source);
1574                 }
1575         this.tiddler = tiddler;
1576 }
1577
1578 Wikifier.prototype.wikifyPlain = function()
1579 {
1580         var e = createTiddlyElement(document.body,"div");
1581         e.style.display = "none";
1582         this.subWikify(e);
1583         var text = getPlainText(e);
1584         e.parentNode.removeChild(e);
1585         return text;
1586 }
1587
1588 Wikifier.prototype.subWikify = function(output,terminator)
1589 {
1590         // Handle the terminated and unterminated cases separately
1591         if (terminator)
1592                 this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
1593         else
1594                 this.subWikifyUnterm(output);
1595 }
1596
1597 Wikifier.prototype.subWikifyUnterm = function(output)
1598 {
1599         // subWikify() can be indirectly recursive, so we need to save the old output pointer
1600         var oldOutput = this.output;
1601         this.output = output;
1602         // Get the first match
1603         this.formatter.formatterRegExp.lastIndex = this.nextMatch;
1604         var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
1605         while(formatterMatch)
1606                 {
1607                 // Output any text before the match
1608                 if(formatterMatch.index > this.nextMatch)
1609                         this.outputText(this.output,this.nextMatch,formatterMatch.index);
1610                 // Set the match parameters for the handler
1611                 this.matchStart = formatterMatch.index;
1612                 this.matchLength = formatterMatch[0].length;
1613                 this.matchText = formatterMatch[0];
1614                 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
1615                 // Figure out which formatter matched and call its handler
1616                 for(var t=1; t<formatterMatch.length; t++)
1617                         {
1618                         if(formatterMatch[t])
1619                                 {
1620                                 this.formatter.formatters[t-1].handler(this);
1621                                 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
1622                                 break;
1623                                 }
1624                         }
1625                 // Get the next match
1626                 formatterMatch = this.formatter.formatterRegExp.exec(this.source);
1627                 }
1628         // Output any text after the last match
1629         if(this.nextMatch < this.source.length)
1630                 {
1631                 this.outputText(this.output,this.nextMatch,this.source.length);
1632                 this.nextMatch = this.source.length;
1633                 }
1634         // Restore the output pointer
1635         this.output = oldOutput;
1636 }
1637
1638 Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
1639 {
1640         // subWikify() can be indirectly recursive, so we need to save the old output pointer
1641         var oldOutput = this.output;
1642         this.output = output;
1643         // Get the first matches for the formatter and terminator RegExps
1644         terminatorRegExp.lastIndex = this.nextMatch;
1645         var terminatorMatch = terminatorRegExp.exec(this.source);
1646         this.formatter.formatterRegExp.lastIndex = this.nextMatch;
1647         var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
1648         while(terminatorMatch || formatterMatch)
1649                 {
1650                 // Check for a terminator match  before the next formatter match
1651                 if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index))
1652                         {
1653                         // Output any text before the match
1654                         if(terminatorMatch.index > this.nextMatch)
1655                                 this.outputText(this.output,this.nextMatch,terminatorMatch.index);
1656                         // Set the match parameters
1657                         this.matchText = terminatorMatch[1];
1658                         this.matchLength = terminatorMatch[1].length;
1659                         this.matchStart = terminatorMatch.index;
1660                         this.nextMatch = this.matchStart + this.matchLength;
1661                         // Restore the output pointer
1662                         this.output = oldOutput;
1663                         return;
1664                         }
1665                 // It must be a formatter match; output any text before the match
1666                 if(formatterMatch.index > this.nextMatch)
1667                         this.outputText(this.output,this.nextMatch,formatterMatch.index);
1668                 // Set the match parameters
1669                 this.matchStart = formatterMatch.index;
1670                 this.matchLength = formatterMatch[0].length;
1671                 this.matchText = formatterMatch[0];
1672                 this.nextMatch = this.formatter.formatterRegExp.lastIndex;
1673                 // Figure out which formatter matched and call its handler
1674                 for(var t=1; t<formatterMatch.length; t++)
1675                         {
1676                         if(formatterMatch[t])
1677                                 {
1678                                 this.formatter.formatters[t-1].handler(this);
1679                                 this.formatter.formatterRegExp.lastIndex = this.nextMatch;
1680                                 break;
1681                                 }
1682                         }
1683                 // Get the next match
1684                 terminatorRegExp.lastIndex = this.nextMatch;
1685                 terminatorMatch = terminatorRegExp.exec(this.source);
1686                 formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
1687                 }
1688         // Output any text after the last match
1689         if(this.nextMatch < this.source.length)
1690                 {
1691                 this.outputText(this.output,this.nextMatch,this.source.length);
1692                 this.nextMatch = this.source.length;
1693                 }
1694         // Restore the output pointer
1695         this.output = oldOutput;
1696 }
1697
1698 Wikifier.prototype.outputText = function(place,startPos,endPos)
1699 {
1700         // Check for highlights
1701         while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos))
1702                 {
1703                 // Deal with any plain text before the highlight
1704                 if(this.highlightMatch.index > startPos)
1705                         {
1706                         createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
1707                         startPos = this.highlightMatch.index;
1708                         }
1709                 // Deal with the highlight
1710                 var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
1711                 var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
1712                 startPos = highlightEnd;
1713                 // Nudge along to the next highlight if we're done with this one
1714                 if(startPos >= this.highlightRegExp.lastIndex)
1715                         this.highlightMatch = this.highlightRegExp.exec(this.source);
1716                 }
1717         // Do the unhighlighted text left over
1718         if(startPos < endPos)
1719                 {
1720                 createTiddlyText(place,this.source.substring(startPos,endPos));
1721                 }
1722 }
1723
1724 // ---------------------------------------------------------------------------------
1725 // Macro definitions
1726 // ---------------------------------------------------------------------------------
1727
1728 config.macros.today.handler = function(place,macroName,params)
1729 {
1730         var now = new Date();
1731         var text;
1732         if(params[0])
1733                 text = now.formatString(params[0].trim());
1734         else
1735                 text = now.toLocaleString();
1736         createTiddlyElement(place,"span",null,null,text);
1737 }
1738
1739 config.macros.version.handler = function(place)
1740 {
1741         createTiddlyElement(place,"span",null,null,version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
1742 }
1743
1744 config.macros.list.handler = function(place,macroName,params)
1745 {
1746         var type = params[0] ? params[0] : "all";
1747         var theList = document.createElement("ul");
1748         place.appendChild(theList);
1749         if(this[type].prompt)
1750                 createTiddlyElement(theList,"li",null,"listTitle",this[type].prompt);
1751         var results;
1752         if(this[type].handler)
1753                 results = this[type].handler(params);
1754         for(var t = 0; t < results.length; t++)
1755                 {
1756                 var theListItem = document.createElement("li")
1757                 theList.appendChild(theListItem);
1758                 if(typeof results[t] == "string")
1759                         createTiddlyLink(theListItem,results[t],true);
1760                 else
1761                         createTiddlyLink(theListItem,results[t].title,true);
1762                 }
1763 }
1764
1765 config.macros.list.all.handler = function(params)
1766 {
1767         return store.reverseLookup("tags","excludeLists",false,"title");
1768 }
1769
1770 config.macros.list.missing.handler = function(params)
1771 {
1772         return store.getMissingLinks();
1773 }
1774
1775 config.macros.list.orphans.handler = function(params)
1776 {
1777         return store.getOrphans();
1778 }
1779
1780 config.macros.list.shadowed.handler = function(params)
1781 {
1782         return store.getShadowed();
1783 }
1784
1785 config.macros.allTags.handler = function(place,macroName,params)
1786 {
1787         var tags = store.getTags();
1788         var theDateList = createTiddlyElement(place,"ul");
1789         if(tags.length == 0)
1790                 createTiddlyElement(theDateList,"li",null,"listTitle",this.noTags);
1791         for(var t=0; t<tags.length; t++)
1792                 {
1793                 var theListItem =createTiddlyElement(theDateList,"li");
1794                 var theTag = createTiddlyButton(theListItem,tags[t][0] + " (" + tags[t][1] + ")",this.tooltip.format([tags[t][0]]),onClickTag);
1795                 theTag.setAttribute("tag",tags[t][0]);
1796                 }
1797 }
1798
1799 config.macros.timeline.handler = function(place,macroName,params)
1800 {
1801         var field = params[0] ? params[0] : "modified";
1802         var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
1803         var lastDay = "";
1804         var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
1805         for(var t=tiddlers.length-1; t>=last; t--)
1806                 {
1807                 var tiddler = tiddlers[t];
1808                 var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
1809                 if(theDay != lastDay)
1810                         {
1811                         var theDateList = document.createElement("ul");
1812                         place.appendChild(theDateList);
1813                         createTiddlyElement(theDateList,"li",null,"listTitle",tiddler[field].formatString(this.dateFormat));
1814                         lastDay = theDay;
1815                         }
1816                 var theDateListItem = createTiddlyElement(theDateList,"li",null,"listLink");
1817                 theDateListItem.appendChild(createTiddlyLink(place,tiddler.title,true));
1818                 }
1819 }
1820
1821 config.macros.search.handler = function(place,macroName,params)
1822 {
1823         var searchTimeout = null;
1824         var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick);
1825         var txt = createTiddlyElement(place,"input",null,"txtOptionInput");
1826         if(params[0])
1827                 txt.value = params[0];
1828         txt.onkeyup = this.onKeyPress;
1829         txt.onfocus = this.onFocus;
1830         txt.setAttribute("size",this.sizeTextbox);
1831         txt.setAttribute("accessKey",this.accessKey);
1832         txt.setAttribute("autocomplete","off");
1833         txt.setAttribute("lastSearchText","");
1834         if(config.browser.isSafari)
1835                 {
1836                 txt.setAttribute("type","search");
1837                 txt.setAttribute("results","5");
1838                 }
1839         else
1840                 txt.setAttribute("type","text");
1841 }
1842
1843 // Global because there's only ever one outstanding incremental search timer
1844 config.macros.search.timeout = null;
1845
1846 config.macros.search.doSearch = function(txt)
1847 {
1848         if(txt.value.length > 0)
1849                 {
1850                 story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
1851                 txt.setAttribute("lastSearchText",txt.value);
1852                 }
1853 }
1854
1855 config.macros.search.onClick = function(e)
1856 {
1857         config.macros.search.doSearch(this.nextSibling);
1858         return false;
1859 }
1860
1861 config.macros.search.onKeyPress = function(e)
1862 {
1863         if(!e) var e = window.event;
1864         switch(e.keyCode)
1865                 {
1866                 case 13: // Ctrl-Enter
1867                 case 10: // Ctrl-Enter on IE PC
1868                         config.macros.search.doSearch(this);
1869                         break;
1870                 case 27: // Escape
1871                         this.value = "";
1872                         clearMessage();
1873                         break;
1874                 }
1875         if(this.value.length > 2)
1876                 {
1877                 if(this.value != this.getAttribute("lastSearchText"))
1878                         {
1879                         if(config.macros.search.timeout)
1880                                 clearTimeout(config.macros.search.timeout);
1881                         var txt = this;
1882                         config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
1883                         }
1884                 }
1885         else
1886                 {
1887                 if(config.macros.search.timeout)
1888                         clearTimeout(config.macros.search.timeout);
1889                 }
1890 }
1891
1892 config.macros.search.onFocus = function(e)
1893 {
1894         this.select();
1895 }
1896
1897 config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
1898 {
1899         params = paramString.parseParams("name",null,true,false,true);
1900         var names = params[0]["name"];
1901         var tiddlerName = names[0];
1902         var className = names[1] ? names[1] : null;
1903         var args = params[0]["with"];
1904         var wrapper = createTiddlyElement(place,"span",null,className);
1905         if(!args)
1906                 {
1907                 wrapper.setAttribute("refresh","content");
1908                 wrapper.setAttribute("tiddler",tiddlerName);
1909                 }
1910         var text = store.getTiddlerText(tiddlerName);
1911         if(text)
1912                 {
1913                 var stack = config.macros.tiddler.tiddlerStack;
1914                 if(stack.indexOf(tiddlerName) !== -1)
1915                         return;
1916                 stack.push(tiddlerName);
1917                 try
1918                         {
1919                         var n = args ? Math.min(args.length,9) : 0;
1920                         for(var i=0; i<n; i++) 
1921                                 {
1922                                 var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
1923                                 text = text.replace(placeholderRE,args[i]);
1924                                 }
1925                         config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
1926                         }
1927                 finally
1928                         {
1929                         stack.pop();
1930                         }
1931                 }
1932 }
1933
1934 config.macros.tiddler.renderText = function(place,text,tiddlerName,params) 
1935 {
1936         wikify(text,place,null,store.getTiddler(tiddlerName));
1937 }
1938
1939 config.macros.tiddler.tiddlerStack = [];
1940
1941 config.macros.tag.handler = function(place,macroName,params)
1942 {
1943         createTagButton(place,params[0]);
1944 }
1945
1946 config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
1947 {
1948         params = paramString.parseParams("anon",null,true,false,false);
1949         var theList = createTiddlyElement(place,"ul");
1950         var title = getParam(params,"anon","");
1951         if(title && store.tiddlerExists(title))
1952                 tiddler = store.getTiddler(title);
1953         var sep = getParam(params,"sep"," ");
1954         var lingo = config.views.wikified.tag;
1955         var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
1956         createTiddlyElement(theList,"li",null,"listTitle",prompt.format([tiddler.title]));
1957         for(var t=0; t<tiddler.tags.length; t++)
1958                 {
1959                 createTagButton(createTiddlyElement(theList,"li"),tiddler.tags[t],tiddler.title);
1960                 if(t<tiddler.tags.length-1)
1961                         createTiddlyText(theList,sep);
1962                 }
1963 }
1964
1965 config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
1966 {
1967         params = paramString.parseParams("anon",null,true,false,false);
1968         var theList = createTiddlyElement(place,"ul");
1969         var title = getParam(params,"anon","");
1970         if(title == "" && tiddler instanceof Tiddler)
1971                 title = tiddler.title;
1972         var sep = getParam(params,"sep"," ");
1973         theList.setAttribute("title",this.tooltip.format([title]));
1974         var tagged = store.getTaggedTiddlers(title);
1975         var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
1976         createTiddlyElement(theList,"li",null,"listTitle",prompt.format([title,tagged.length]));
1977         for(var t=0; t<tagged.length; t++)
1978                 {
1979                 createTiddlyLink(createTiddlyElement(theList,"li"),tagged[t].title,true);
1980                 if(t<tagged.length-1)
1981                         createTiddlyText(theList,sep);
1982                 }
1983 }
1984
1985 config.macros.closeAll.handler = function(place)
1986 {
1987         createTiddlyButton(place,this.label,this.prompt,this.onClick);
1988 }
1989
1990 config.macros.closeAll.onClick = function(e)
1991 {
1992         story.closeAllTiddlers();
1993         return false;
1994 }
1995
1996 config.macros.permaview.handler = function(place)
1997 {
1998         createTiddlyButton(place,this.label,this.prompt,this.onClick);
1999 }
2000
2001 config.macros.permaview.onClick = function(e)
2002 {
2003         story.permaView();
2004         return false;
2005 }
2006
2007 config.macros.saveChanges.handler = function(place)
2008 {
2009         if(!readOnly)
2010                 createTiddlyButton(place,this.label,this.prompt,this.onClick,null,null,this.accessKey);
2011 }
2012
2013 config.macros.saveChanges.onClick = function(e)
2014 {
2015         saveChanges();
2016         return false;
2017 }
2018
2019 config.macros.slider.onClickSlider = function(e)
2020 {
2021         if(!e) var e = window.event;
2022         var n = this.nextSibling;
2023         var cookie = n.getAttribute("cookie");
2024         var isOpen = n.style.display != "none";
2025         if(anim && config.options.chkAnimate)
2026                 anim.startAnimating(new Slider(n,!isOpen,e.shiftKey || e.altKey,"none"));
2027         else
2028                 n.style.display = isOpen ? "none" : "block";
2029         config.options[cookie] = !isOpen;
2030         saveOptionCookie(cookie);
2031         return false;
2032 }
2033
2034 config.macros.slider.createSlider = function(place,cookie,title,tooltip)
2035 {
2036         var cookie = cookie ? cookie : "";
2037         var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
2038         var panel = createTiddlyElement(null,"div",null,"sliderPanel");
2039         panel.setAttribute("cookie",cookie);
2040         panel.style.display = config.options[cookie] ? "block" : "none";
2041         place.appendChild(panel);
2042         return panel;
2043 }
2044
2045 config.macros.slider.handler = function(place,macroName,params)
2046 {
2047         var panel = this.createSlider(place,params[0],params[2],params[3]);
2048         var text = store.getTiddlerText(params[1]);
2049         panel.setAttribute("refresh", "content");
2050         panel.setAttribute("tiddler", params[1]);
2051         if(text)
2052                 wikify(text,panel,null,store.getTiddler(params[1]));
2053 }
2054
2055 config.macros.option.onChangeOption = function(e)
2056 {
2057         var opt = this.getAttribute("option");
2058         var elementType,valueField;
2059         if(opt)
2060                 {
2061                 switch(opt.substr(0,3))
2062                         {
2063                         case "txt":
2064                                 elementType = "input";
2065                                 valueField = "value";
2066                                 break;
2067                         case "chk":
2068                                 elementType = "input";
2069                                 valueField = "checked";
2070                                 break;
2071                         }
2072                 config.options[opt] = this[valueField];
2073                 saveOptionCookie(opt);
2074                 var nodes = document.getElementsByTagName(elementType);
2075                 for(var t=0; t<nodes.length; t++)
2076                         {
2077                         var optNode = nodes[t].getAttribute("option");
2078                         if(opt == optNode)
2079                                 nodes[t][valueField] = this[valueField];
2080                         }
2081                 }
2082         return(true);
2083 }
2084
2085 config.macros.option.handler = function(place,macroName,params)
2086 {
2087         var opt = params[0];
2088         if(config.options[opt] == undefined)
2089                 return;
2090         var c;
2091         switch(opt.substr(0,3))
2092                 {
2093                 case "txt":
2094                         c = document.createElement("input");
2095                         c.onkeyup = this.onChangeOption;
2096                         c.setAttribute("option",opt);
2097                         c.className = "txtOptionInput";
2098                         place.appendChild(c);
2099                         c.value = config.options[opt];
2100                         break;
2101                 case "chk":
2102                         c = document.createElement("input");
2103                         c.setAttribute("type","checkbox");
2104                         c.onclick = this.onChangeOption;
2105                         c.setAttribute("option",opt);
2106                         c.className = "chkOptionInput";
2107                         place.appendChild(c);
2108                         c.checked = config.options[opt];
2109                         break;
2110                 }
2111 }
2112
2113
2114
2115 config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
2116 {
2117         var tags = [];
2118         for(var t=1; t<params.length; t++)
2119                 if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
2120                         tags.push(params[t].value);
2121         label = getParam(params,"label",label);
2122         prompt = getParam(params,"prompt",prompt);
2123         accessKey = getParam(params,"accessKey",accessKey);
2124         newFocus = getParam(params,"focus",newFocus);
2125         var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
2126         btn.setAttribute("newTitle",title);
2127         btn.setAttribute("isJournal",isJournal);
2128         btn.setAttribute("params",tags.join("|"));
2129         btn.setAttribute("newFocus",newFocus);
2130         btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
2131         var text = getParam(params,"text");
2132         if(text !== undefined) 
2133                 btn.setAttribute("newText",text);
2134         return btn;
2135 }
2136
2137 config.macros.newTiddler.onClickNewTiddler = function()
2138 {
2139         var title = this.getAttribute("newTitle");
2140         if(this.getAttribute("isJournal"))
2141                 {
2142                 var now = new Date();
2143                 title = now.formatString(title.trim());
2144                 }
2145         var params = this.getAttribute("params").split("|");
2146         var focus = this.getAttribute("newFocus");
2147         var template = this.getAttribute("newTemplate");
2148         story.displayTiddler(null,title,template);
2149         var text = this.getAttribute("newText");
2150         if(typeof text == "string")
2151                 story.getTiddlerField(title,"text").value = text.format([title]);
2152         for(var t=0;t<params.length;t++)
2153                 story.setTiddlerTag(title,params[t],+1);
2154         story.focusTiddler(title,focus);
2155         return false;
2156 }
2157
2158 config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2159 {
2160         if(!readOnly)
2161                 {
2162                 params = paramString.parseParams("anon",null,true,false,false);
2163                 var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
2164                 title = getParam(params,"title",title);
2165                 this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
2166                 }
2167 }
2168
2169 config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2170 {
2171         if(!readOnly)
2172                 {
2173                 params = paramString.parseParams("anon",null,true,false,false);
2174                 var title = params[1] && params[1].name == "anon" ? params[1].value : "";
2175                 title = getParam(params,"title",title);
2176                 config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
2177                 }
2178 }
2179
2180 config.macros.sparkline.handler = function(place,macroName,params)
2181 {
2182         var data = [];
2183         var min = 0;
2184         var max = 0;
2185         for(var t=0; t<params.length; t++)
2186                 {
2187                 var v = parseInt(params[t]);
2188                 if(v < min)
2189                         min = v;
2190                 if(v > max)
2191                         max = v;
2192                 data.push(v);
2193                 }
2194         if(data.length < 1)
2195                 return;
2196         var box = createTiddlyElement(place,"span",null,"sparkline",String.fromCharCode(160));
2197         box.title = data.join(",");
2198         var w = box.offsetWidth;
2199         var h = box.offsetHeight;
2200         box.style.paddingRight = (data.length * 2 - w) + "px";
2201         box.style.position = "relative";
2202         for(var d=0; d<data.length; d++)
2203                 {
2204                 var tick = document.createElement("img");
2205                 tick.border = 0;
2206                 tick.className = "sparktick";
2207                 tick.style.position = "absolute";
2208                 tick.src = "data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B";
2209                 tick.style.left = d*2 + "px";
2210                 tick.style.width = "2px";
2211                 var v = Math.floor(((data[d] - min)/(max-min)) * h);
2212                 tick.style.top = (h-v) + "px";
2213                 tick.style.height = v + "px";
2214                 box.appendChild(tick);
2215                 }
2216 }
2217
2218 config.macros.tabs.handler = function(place,macroName,params)
2219 {
2220         var cookie = params[0];
2221         var numTabs = (params.length-1)/3;
2222         var wrapper = createTiddlyElement(null,"div",null,cookie);
2223         var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
2224         tabset.setAttribute("cookie",cookie);
2225         var validTab = false;
2226         for(var t=0; t<numTabs; t++)
2227                 {
2228                 var label = params[t*3+1];
2229                 var prompt = params[t*3+2];
2230                 var content = params[t*3+3];
2231                 var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
2232                 tab.setAttribute("tab",label);
2233                 tab.setAttribute("content",content);
2234                 tab.title = prompt;
2235                 if(config.options[cookie] == label)
2236                         validTab = true;
2237                 }
2238         if(!validTab)
2239                 config.options[cookie] = params[1];
2240         place.appendChild(wrapper);
2241         this.switchTab(tabset,config.options[cookie]);
2242 }
2243
2244 config.macros.tabs.onClickTab = function(e)
2245 {
2246         config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
2247         return false;
2248 }
2249
2250 config.macros.tabs.switchTab = function(tabset,tab)
2251 {
2252         var cookie = tabset.getAttribute("cookie");
2253         var theTab = null
2254         var nodes = tabset.childNodes;
2255         for(var t=0; t<nodes.length; t++)
2256                 if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab)
2257                         {
2258                         theTab = nodes[t];
2259                         theTab.className = "tab tabSelected";
2260                         }
2261                 else
2262                         nodes[t].className = "tab tabUnselected"
2263         if(theTab)
2264                 {
2265                 if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
2266                         tabset.parentNode.removeChild(tabset.nextSibling);
2267                 var tabContent = createTiddlyElement(null,"div",null,"tabContents");
2268                 tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
2269                 var contentTitle = theTab.getAttribute("content");
2270                 wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
2271                 if(cookie)
2272                         {
2273                         config.options[cookie] = tab;
2274                         saveOptionCookie(cookie);
2275                         }
2276                 }
2277 }
2278
2279 // <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
2280 config.macros.gradient.handler = function(place,macroName,params,wikifier)
2281 {
2282         var terminator = ">>";
2283         var panel;
2284         if(wikifier)
2285                 panel = createTiddlyElement(place,"div",null,"gradient");
2286         else
2287                 panel = place;
2288         panel.style.position = "relative";
2289         panel.style.overflow = "hidden";
2290         panel.style.zIndex = "0";
2291         var t;
2292         if(wikifier)
2293                 {
2294                 var styles = config.formatterHelpers.inlineCssHelper(wikifier);
2295                 config.formatterHelpers.applyCssHelper(panel,styles);
2296                 }
2297         var colours = [];
2298         for(t=1; t<params.length; t++)
2299                 {
2300                 var c = new RGB(params[t]);
2301                 if(c)
2302                         colours.push(c);
2303                 }
2304         drawGradient(panel,params[0] != "vert",colours);
2305         if(wikifier)
2306                 wikifier.subWikify(panel,terminator);
2307         if(document.all)
2308                 {
2309                 panel.style.height = "100%";
2310                 panel.style.width = "100%";
2311                 }
2312 }
2313
2314 config.macros.message.handler = function(place,macroName,params)
2315 {
2316         if(params[0])
2317                 {
2318                 var m = config;
2319                 var p = params[0].split(".");
2320                 for(var t=0; t<p.length; t++)
2321                         {
2322                         if(p[t] in m)
2323                                 m = m[p[t]];
2324                         else
2325                                 break;
2326                         }
2327                 createTiddlyText(place,m.toString().format(params.splice(1)));
2328                 }
2329 }
2330
2331 config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2332 {
2333         if((tiddler instanceof Tiddler) && params[0])
2334                 {
2335                 var value = store.getValue(tiddler,params[0]);
2336                 if(value != undefined)
2337                         switch(params[1])
2338                                 {
2339                                 case undefined:
2340                                         highlightify(value,place,highlightHack);
2341                                         break;
2342                                 case "link":
2343                                         createTiddlyLink(place,value,true);
2344                                         break;
2345                                 case "wikified":
2346                                         wikify(value,place,highlightHack,tiddler);
2347                                         break;
2348                                 case "date":
2349                                         value = Date.convertFromYYYYMMDDHHMM(value);
2350                                         if(params[2])
2351                                                 createTiddlyText(place,value.formatString(params[2]));
2352                                         else
2353                                                 createTiddlyText(place,value);
2354                                         break;
2355                                 }
2356                 }
2357 }
2358
2359 config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2360 {
2361         var field = params[0];
2362         if((tiddler instanceof Tiddler) && field)
2363                 {
2364                 story.setDirty(tiddler.title,true);
2365                 if(field != "text")
2366                         {
2367                                 var e = createTiddlyElement(null,"input");
2368                                 if(tiddler.isReadOnly())
2369                                         e.setAttribute("readOnly","readOnly");
2370                                 e.setAttribute("edit",field);
2371                                 e.setAttribute("type","text");
2372                                 var v = store.getValue(tiddler,field);
2373                                 if(!v) 
2374                                         v = "";
2375                                 e.value = v;
2376                                 e.setAttribute("size","40");
2377                                 e.setAttribute("autocomplete","off");
2378                                 place.appendChild(e);
2379                         }
2380                 else
2381                         {
2382                                 var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
2383                                 var wrapper2 = createTiddlyElement(wrapper1,"div");
2384                                 var e = createTiddlyElement(wrapper2,"textarea");
2385                                 if(tiddler.isReadOnly())
2386                                         e.setAttribute("readOnly","readOnly");
2387                                 var v = store.getValue(tiddler,field);
2388                                 if(!v) 
2389                                         v = "";
2390                                 e.value = v;
2391                                 var rows = 10;
2392                                 var lines = v.match(/\n/mg);
2393                                 var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
2394                                 if(lines != null && lines.length > rows)
2395                                         rows = lines.length + 5;
2396                                 rows = Math.min(rows,maxLines);
2397                                 e.setAttribute("rows",rows);
2398                                 e.setAttribute("edit",field);
2399                                 place.appendChild(wrapper1);
2400                         }
2401                 }
2402 }
2403
2404 config.macros.tagChooser.onClick = function(e)
2405 {
2406         if(!e) var e = window.event;
2407         var lingo = config.views.editor.tagChooser;
2408         var popup = Popup.create(this);
2409         var tags = store.getTags();
2410         if(tags.length == 0)
2411                 createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
2412         for(var t=0; t<tags.length; t++)
2413                 {
2414                 var theTag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
2415                 theTag.setAttribute("tag",tags[t][0]);
2416                 theTag.setAttribute("tiddler", this.getAttribute("tiddler"));
2417                 }
2418         Popup.show(popup,false);
2419         e.cancelBubble = true;
2420         if(e.stopPropagation) e.stopPropagation();
2421         return(false);
2422 }
2423
2424 config.macros.tagChooser.onTagClick = function(e)
2425 {
2426         if(!e) var e = window.event;
2427         var tag = this.getAttribute("tag");
2428         var title = this.getAttribute("tiddler");
2429         if(!readOnly)
2430                 story.setTiddlerTag(title,tag,0);
2431         return(false);
2432 }
2433
2434 config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2435 {
2436         if(tiddler instanceof Tiddler)
2437                 {
2438                 var title = tiddler.title;
2439                 var lingo = config.views.editor.tagChooser;
2440                 var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
2441                 btn.setAttribute("tiddler", title);
2442                 }
2443 }
2444
2445 // Create a toolbar command button
2446 // place - parent DOM element
2447 // command - reference to config.commands[] member -or- name of member
2448 // tiddler - reference to tiddler that toolbar applies to
2449 // theClass - the class to give the button
2450 config.macros.toolbar.createCommand = function(place,commandName,tiddler,theClass)
2451 {
2452         if(typeof commandName != "string")
2453                 {
2454                 var c = null;
2455                 for(var t in config.commands)
2456                         if(config.commands[t] == commandName)
2457                                 c = t;
2458                 commandName = c;
2459                 }
2460         if((tiddler instanceof Tiddler) && (typeof commandName == "string"))
2461                 {
2462                 var title = tiddler.title;
2463                 var command = config.commands[commandName];
2464                 var ro = tiddler.isReadOnly();
2465                 var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
2466                 var text = ro && command.readOnlyText ? command.readOnlyText : command.text;
2467                 var tooltip = ro && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
2468                 if((!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow))
2469
2470                         {
2471                         var btn = createTiddlyButton(null,text,tooltip,this.onClickCommand);
2472                         btn.setAttribute("commandName", commandName);
2473                         btn.setAttribute("tiddler", title);
2474                         if(theClass)
2475                                 addClass(btn,theClass);
2476                         place.appendChild(btn);
2477                         }
2478                 }
2479 }
2480
2481 config.macros.toolbar.onClickCommand = function(e)
2482 {
2483         if(!e) var e = window.event;
2484         var command = config.commands[this.getAttribute("commandName")];
2485         return command.handler(e,this,this.getAttribute("tiddler"));
2486 }
2487
2488 // Invoke the first command encountered from a given place that is tagged with a specified class
2489 config.macros.toolbar.invokeCommand = function(place,theClass,event)
2490 {
2491         var children = place.getElementsByTagName("a")
2492         for(var t=0; t<children.length; t++)
2493                 {
2494                 var c = children[t];
2495                 if(hasClass(c,theClass) && c.getAttribute && c.getAttribute("commandName"))
2496                         {
2497                         if(c.onclick instanceof Function)
2498                                 c.onclick.call(c,event);
2499                         break;
2500                         }
2501                 }
2502 }
2503
2504 config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2505 {
2506         for(var t=0; t<params.length; t++)
2507                 {
2508                 var c = params[t];
2509                 var theClass = "";
2510                 switch(c.substr(0,1))
2511                         {
2512                         case "+":
2513                                 theClass = "defaultCommand";
2514                                 c = c.substr(1);
2515                                 break;
2516                         case "-":
2517                                 theClass = "cancelCommand";
2518                                 c = c.substr(1);
2519                                 break;
2520                         }
2521                 if(c in config.commands)
2522                         this.createCommand(place,c,tiddler,theClass);
2523                 }
2524 }
2525
2526 config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2527 {
2528         var e = createTiddlyElement(place,"div");
2529         e.setAttribute("refresh","macro");
2530         e.setAttribute("macroName","plugins");
2531         e.setAttribute("params",paramString);
2532         this.refresh(e,paramString);
2533 }
2534
2535 config.macros.plugins.refresh = function(place,params)
2536 {
2537         var selectedRows = [];
2538         ListView.forEachSelector(place,function(e,rowName) {
2539                         if(e.checked)
2540                                 selectedRows.push(e.getAttribute("rowName"));
2541                 });
2542         removeChildren(place);
2543         params = params.parseParams("anon");
2544         var plugins = installedPlugins.slice(0);
2545         var t,tiddler,p;
2546         var configTiddlers = store.getTaggedTiddlers("systemConfig");
2547         for(t=0; t<configTiddlers.length; t++)
2548                 {
2549                 tiddler = configTiddlers[t];
2550                 if(plugins.findByField("title",tiddler.title) == null)
2551                         {
2552                         p = getPluginInfo(tiddler);
2553                         p.executed = false;
2554                         p.log.splice(0,0,this.skippedText);
2555                         plugins.push(p);
2556                         }
2557                 }
2558         for(t=0; t<plugins.length; t++)
2559                 {
2560                 var p = plugins[t];
2561                 p.forced = p.tiddler.isTagged("systemConfigForce");
2562                 p.disabled = p.tiddler.isTagged("systemConfigDisable");
2563                 p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
2564                 }
2565         if(plugins.length == 0)
2566                 createTiddlyElement(place,"em",null,null,this.noPluginText);
2567         else
2568                 ListView.create(place,plugins,this.listViewTemplate,this.onSelectCommand);
2569 }
2570
2571 config.macros.plugins.onSelectCommand = function(command,rowNames)
2572 {
2573         var t;
2574         switch(command)
2575                 {
2576                 case "remove":
2577                         for(t=0; t<rowNames.length; t++)
2578                                 store.setTiddlerTag(rowNames[t],false,"systemConfig");
2579                         break;
2580                 case "delete":
2581                         if(rowNames.length > 0 && confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")])))
2582                                 {
2583                                 for(t=0; t<rowNames.length; t++)
2584                                         {
2585                                         store.removeTiddler(rowNames[t]);
2586                                         story.closeTiddler(rowNames[t],true,false);
2587                                         }
2588                                 }
2589                         break;
2590                 }
2591         if(config.options.chkAutoSave)
2592                 saveChanges(true);
2593 }
2594
2595 config.macros.refreshDisplay.handler = function(place)
2596 {
2597         createTiddlyButton(place,this.label,this.prompt,this.onClick);
2598 }
2599
2600 config.macros.refreshDisplay.onClick = function(e)
2601 {
2602         refreshAll();
2603         return false;
2604 }
2605
2606 config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
2607 {
2608         if(readOnly)
2609                 {
2610                 createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
2611                 return;
2612                 }
2613         var importer = createTiddlyElement(null,"div",null,"importTiddler wizard");
2614         createTiddlyElement(importer,"h1",null,null,this.wizardTitle);
2615         createTiddlyElement(importer,"h2",null,"step1",this.step1);
2616         var step = createTiddlyElement(importer,"div",null,"wizardStep");
2617         createTiddlyText(step,this.step1prompt);
2618         var input = createTiddlyElement(null,"input",null,"txtOptionInput");
2619         input.type = "text";
2620         input.size = 50;
2621         step.appendChild(input);
2622         importer.inputBox = input;
2623         createTiddlyElement(step,"br");
2624         createTiddlyText(step,this.step1promptFile);
2625         var fileInput = createTiddlyElement(null,"input",null,"txtOptionInput");
2626         fileInput.type = "file";
2627         fileInput.size = 50;
2628         fileInput.onchange = this.onBrowseChange;
2629         fileInput.onkeyup = this.onBrowseChange;
2630         step.appendChild(fileInput);
2631         createTiddlyElement(step,"br");
2632         createTiddlyText(step,this.step1promptFeeds);
2633         var feeds = this.getFeeds([{caption: this.step1feedPrompt, name: ""}]);
2634         createTiddlyDropDown(step,this.onFeedChange,feeds);
2635         createTiddlyElement(step,"br");
2636         createTiddlyButton(step,this.fetchLabel,this.fetchPrompt,this.onFetch,null,null,null);
2637         place.appendChild(importer);
2638 }
2639
2640 config.macros.importTiddlers.getFeeds = function(feeds)
2641 {
2642         var tagged = store.getTaggedTiddlers("contentPublisher","title");
2643         for(var t=0; t<tagged.length; t++)
2644                 feeds.push({caption: tagged[t].title, name: store.getTiddlerSlice(tagged[t].title,"URL")});
2645         return feeds;
2646 }
2647
2648 config.macros.importTiddlers.onFeedChange = function(e)
2649 {
2650         var importer = findRelated(this,"importTiddler","className","parentNode");
2651         importer.inputBox.value = this.value;
2652         this.selectedIndex = 0;
2653 }
2654
2655 config.macros.importTiddlers.onBrowseChange = function(e)
2656 {
2657         var importer = findRelated(this,"importTiddler","className","parentNode");
2658         importer.inputBox.value = "file://" + this.value;
2659 }
2660
2661 config.macros.importTiddlers.onFetch = function(e)
2662 {
2663         var importer = findRelated(this,"importTiddler","className","parentNode");
2664         var url = importer.inputBox.value;
2665         var cutoff = findRelated(importer.firstChild,"step2","className","nextSibling");
2666         while(cutoff)
2667                 {
2668                 var temp = cutoff.nextSibling;
2669                 cutoff.parentNode.removeChild(cutoff);
2670                 cutoff = temp;
2671                 }
2672         createTiddlyElement(importer,"h2",null,"step2",config.macros.importTiddlers.step2);
2673         var step = createTiddlyElement(importer,"div",null,"wizardStep",config.macros.importTiddlers.step2Text.format([url]));
2674         loadRemoteFile(url,config.macros.importTiddlers.onLoad,importer);
2675 }
2676
2677 config.macros.importTiddlers.onLoad = function(status,params,responseText,url,xhr)
2678 {
2679         if(!status)
2680                 {
2681                 displayMessage(this.fetchError);
2682                 return;
2683                 }
2684         var importer = params;
2685         // Check that the tiddler we're in hasn't been closed - doesn't work on IE
2686 //      var p = importer;
2687 //      while(p.parentNode)
2688 //              p = p.parentNode;
2689 //      if(!(p instanceof HTMLDocument))
2690 //              return;
2691         // Crack out the content - (should be refactored)
2692         var posOpeningDiv = responseText.indexOf(startSaveArea);
2693         var limitClosingDiv = responseText.indexOf("<!--POST-BODY-START--"+">");
2694         var posClosingDiv = responseText.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? responseText.length : limitClosingDiv);
2695         if((posOpeningDiv == -1) || (posClosingDiv == -1))
2696                 {
2697                 alert(config.messages.invalidFileError.format([url]));
2698                 return;
2699                 }
2700         var content = "<html><body>" + responseText.substring(posOpeningDiv,posClosingDiv + endSaveArea.length) + "</body></html>";
2701         // Create the iframe
2702         var iframe = document.createElement("iframe");
2703         iframe.style.display = "none";
2704         importer.insertBefore(iframe,importer.firstChild);
2705         var doc = iframe.document;
2706         if(iframe.contentDocument)
2707                 doc = iframe.contentDocument; // For NS6
2708         else if(iframe.contentWindow)
2709                 doc = iframe.contentWindow.document; // For IE5.5 and IE6
2710         // Put the content in the iframe
2711         doc.open();
2712         doc.writeln(content);
2713         doc.close();
2714         // Load the content into a TiddlyWiki() object
2715         var storeArea = doc.getElementById("storeArea");
2716         var importStore = new TiddlyWiki();
2717         importStore.loadFromDiv(storeArea,"store");
2718         // Get rid of the iframe
2719         iframe.parentNode.removeChild(iframe);
2720         // Extract data for the listview
2721         var tiddlers = [];
2722         importStore.forEachTiddler(function(title,tiddler)
2723                 {
2724                 var t = {};
2725                 t.title = title;
2726                 t.modified = tiddler.modified;
2727                 t.modifier = tiddler.modifier;
2728                 t.text = tiddler.text.substr(0,50);
2729                 t.tags = tiddler.tags;
2730                 tiddlers.push(t);
2731                 });
2732         // Display the listview
2733         createTiddlyElement(importer,"h2",null,"step3",config.macros.importTiddlers.step3);
2734         var step = createTiddlyElement(importer,"div",null,"wizardStep");
2735         ListView.create(step,tiddlers,config.macros.importTiddlers.listViewTemplate,config.macros.importTiddlers.onSelectCommand);
2736         // Save the importer
2737         importer.store = importStore;
2738 }
2739
2740 config.macros.importTiddlers.onSelectCommand = function(listView,command,rowNames)
2741 {
2742         var importer = findRelated(listView,"importTiddler","className","parentNode");
2743         switch(command)
2744                 {
2745                 case "import":
2746                         config.macros.importTiddlers.doImport(importer,rowNames);
2747                         break;
2748                 }
2749         if(config.options.chkAutoSave)
2750                 saveChanges(true);
2751 }
2752
2753 config.macros.importTiddlers.doImport = function(importer,rowNames)
2754 {
2755         var theStore = importer.store;
2756         var overwrite = new Array();
2757         var t;
2758         for(t=0; t<rowNames.length; t++)
2759                 {
2760                 if(store.tiddlerExists(rowNames[t]))
2761                         overwrite.push(rowNames[t]);
2762         }
2763         if(overwrite.length > 0)
2764                 if(!confirm(this.confirmOverwriteText.format([overwrite.join(", ")])))
2765                         return;
2766         for(t=0; t<rowNames.length; t++)
2767                 {
2768                 var inbound = theStore.fetchTiddler(rowNames[t]);
2769                 store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, inbound.tags);
2770                 store.fetchTiddler(inbound.title).created = inbound.created;
2771                 store.notify(rowNames[t],false);
2772                 }
2773         store.notifyAll();
2774         store.setDirty(true);
2775         createTiddlyElement(importer,"h2",null,"step4",this.step4.format([rowNames.length]));
2776         var step = createTiddlyElement(importer,"div",null,"wizardStep");
2777         for(t=0; t<rowNames.length; t++)
2778                 {
2779                 createTiddlyLink(step,rowNames[t],true);
2780                 createTiddlyElement(step,"br");
2781                 }
2782         createTiddlyElement(importer,"h2",null,"step5",this.step5);
2783 }
2784 // ---------------------------------------------------------------------------------
2785 // Menu and toolbar commands
2786 // ---------------------------------------------------------------------------------
2787
2788 config.commands.closeTiddler.handler = function(event,src,title)
2789 {
2790         story.closeTiddler(title,true,event.shiftKey || event.altKey);
2791         return false;
2792 }
2793
2794 config.commands.closeOthers.handler = function(event,src,title)
2795 {
2796         story.closeAllTiddlers(title);
2797         return false;
2798 }
2799
2800 config.commands.editTiddler.handler = function(event,src,title)
2801 {
2802         clearMessage();
2803         story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
2804         story.focusTiddler(title,"text");
2805         return false;
2806 }
2807
2808 config.commands.saveTiddler.handler = function(event,src,title)
2809 {
2810         var newTitle = story.saveTiddler(title,event.shiftKey);
2811         if(newTitle)
2812            story.displayTiddler(null,newTitle);
2813         return false;
2814 }
2815
2816 config.commands.cancelTiddler.handler = function(event,src,title)
2817 {
2818         if(story.hasChanges(title) && !readOnly)
2819                 if(!confirm(this.warning.format([title])))
2820                         return false;
2821         story.setDirty(title,false);
2822         story.displayTiddler(null,title);
2823         return false;
2824 }
2825
2826 config.commands.deleteTiddler.handler = function(event,src,title)
2827 {
2828         var deleteIt = true;
2829         if (config.options.chkConfirmDelete)
2830                 deleteIt = confirm(this.warning.format([title]));
2831         if (deleteIt)
2832                 {
2833                 store.removeTiddler(title);
2834                 story.closeTiddler(title,true,event.shiftKey || event.altKey);
2835                 if(config.options.chkAutoSave)
2836                         saveChanges();
2837                 }
2838         return false;
2839 }
2840
2841 config.commands.permalink.handler = function(event,src,title)
2842 {
2843         var t = encodeURIComponent(String.encodeTiddlyLink(title));
2844         if(window.location.hash != t)
2845                 window.location.hash = t;
2846         return false;
2847 }
2848
2849 config.commands.references.handler = function(event,src,title)
2850 {
2851         var popup = Popup.create(src);
2852         if(popup)
2853                 {
2854                 var references = store.getReferringTiddlers(title);
2855                 var c = false;
2856                 for(var r=0; r<references.length; r++)
2857                         if(references[r].title != title && !references[r].isTagged("excludeLists"))
2858                                 {
2859                                 createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
2860                                 c = true;
2861                                 }
2862                 if(!c)
2863                         createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
2864                 }
2865         Popup.show(popup,false);
2866         event.cancelBubble = true;
2867         if (event.stopPropagation) event.stopPropagation();
2868         return false;
2869 }
2870
2871 config.commands.jump.handler = function(event,src,title)
2872 {
2873         var popup = Popup.create(src);
2874         if(popup)
2875                 {
2876                 story.forEachTiddler(function(title,element) {
2877                         createTiddlyLink(createTiddlyElement(popup,"li"),title,true);
2878                         });
2879                 }
2880         Popup.show(popup,false);
2881         event.cancelBubble = true;
2882         if (event.stopPropagation) event.stopPropagation();
2883         return false;
2884 }
2885
2886 // ---------------------------------------------------------------------------------
2887 // Tiddler() object
2888 // ---------------------------------------------------------------------------------
2889
2890 function Tiddler()
2891 {
2892         this.title = null;
2893         this.text = null;
2894         this.modifier = null;
2895         this.modified = new Date();
2896         this.created = new Date();
2897         this.links = [];
2898         this.linksUpdated = false;
2899         this.tags = [];
2900         return this;
2901 }
2902
2903 Tiddler.prototype.getLinks = function()
2904 {
2905         if(this.linksUpdated==false)
2906                 this.changed();
2907         return this.links;
2908 }
2909
2910 // Format the text for storage in an RSS item
2911 Tiddler.prototype.saveToRss = function(url)
2912 {
2913         var s = [];
2914         s.push("<item>");
2915         s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
2916         s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
2917         for(var t=0; t<this.tags.length; t++)
2918                 s.push("<category>" + this.tags[t] + "</category>");
2919         s.push("<link>" + url + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
2920         s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
2921         s.push("</item>");
2922         return(s.join("\n"));
2923 }
2924
2925 // Change the text and other attributes of a tiddler
2926 Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
2927 {
2928         this.assign(title,text,modifier,modified,tags,created,fields);
2929         this.changed();
2930         return this;
2931 }
2932
2933 // Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
2934 Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
2935 {
2936         if(title != undefined)
2937                 this.title = title;
2938         if(text != undefined)
2939                 this.text = text;
2940         if(modifier != undefined)
2941                 this.modifier = modifier;
2942         if(modified != undefined)
2943                 this.modified = modified;
2944         if(created != undefined)
2945                 this.created = created;
2946         if(fields != undefined)
2947                 this.fields = fields;
2948         if(tags != undefined)
2949                 this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
2950         else if(this.tags == undefined)
2951                 this.tags = [];
2952         return this;
2953 }
2954
2955 // Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
2956 Tiddler.prototype.getTags = function()
2957 {
2958         return String.encodeTiddlyLinkList(this.tags);
2959 }
2960
2961 // Test if a tiddler carries a tag
2962 Tiddler.prototype.isTagged = function(tag)
2963 {
2964         return this.tags.indexOf(tag) != -1;
2965 }
2966
2967 // Static method to convert "\n" to newlines, "\s" to "\"
2968 Tiddler.unescapeLineBreaks = function(text)
2969 {
2970         return text ? text.unescapeLineBreaks() : "";
2971 }
2972
2973 // Convert newlines to "\n", "\" to "\s"
2974 Tiddler.prototype.escapeLineBreaks = function()
2975 {
2976         return this.text.escapeLineBreaks();
2977 }
2978
2979 // Updates the secondary information (like links[] array) after a change to a tiddler
2980 Tiddler.prototype.changed = function()
2981 {
2982         this.links = [];
2983         var t = this.autoLinkWikiWords() ? 0 : 1;
2984         var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
2985         tiddlerLinkRegExp.lastIndex = 0;
2986         var formatMatch = tiddlerLinkRegExp.exec(this.text);
2987         while(formatMatch)
2988                 {
2989                 if(t==0 && formatMatch[1] && formatMatch[1] != this.title) // wikiWordLink
2990                         {
2991                         if(formatMatch.index > 0)
2992                                 {
2993                                 var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
2994                                 preRegExp.lastIndex = formatMatch.index-1;
2995                                 var preMatch = preRegExp.exec(this.text);
2996                                 if(preMatch.index != formatMatch.index-1)
2997                                         this.links.pushUnique(formatMatch[1]);
2998                                 }
2999                         else
3000                                 this.links.pushUnique(formatMatch[1]);
3001                         }
3002                 else if(formatMatch[2-t] && (store.tiddlerExists(formatMatch[3-t]) || store.isShadowTiddler(formatMatch[3-t]))) // titledBrackettedLink
3003                         this.links.pushUnique(formatMatch[3-t]);
3004                 else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
3005                         this.links.pushUnique(formatMatch[4-t]);
3006                 // Do not add link if match urlPattern (formatMatch[5-t])
3007                 formatMatch = tiddlerLinkRegExp.exec(this.text);
3008                 }
3009         this.linksUpdated = true;
3010         return;
3011 }
3012
3013 Tiddler.prototype.getSubtitle = function()
3014 {
3015         var theModifier = this.modifier;
3016         if(!theModifier)
3017                 theModifier = config.messages.subtitleUnknown;
3018         var theModified = this.modified;
3019         if(theModified)
3020                 theModified = theModified.toLocaleString();
3021         else
3022                 theModified = config.messages.subtitleUnknown;
3023         return(config.messages.tiddlerLinkTooltip.format([this.title,theModifier,theModified]));
3024 }
3025
3026 Tiddler.prototype.isReadOnly = function()
3027 {
3028         return readOnly;
3029 }
3030
3031 Tiddler.prototype.autoLinkWikiWords = function()
3032 {
3033         return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
3034 }
3035
3036 Tiddler.prototype.generateFingerprint = function()
3037 {
3038         return "0x" + Crypto.hexSha1Str(this.text);
3039 }
3040
3041 // ---------------------------------------------------------------------------------
3042 // TiddlyWiki() object contains Tiddler()s
3043 // ---------------------------------------------------------------------------------
3044
3045 function TiddlyWiki()
3046 {
3047         var tiddlers = {}; // Hashmap by name of tiddlers
3048         this.tiddlersUpdated = false;
3049         this.namedNotifications = []; // Array of {name:,notify:} of notification functions
3050         this.notificationLevel = 0;
3051         this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
3052         this.clear = function() {
3053                 tiddlers = {};
3054                 this.setDirty(false);
3055                 };
3056         this.fetchTiddler = function(title) {
3057                 return tiddlers[title];
3058                 };
3059         this.deleteTiddler = function(title) {
3060                 delete this.slices[title];
3061                 delete tiddlers[title];
3062                 };
3063         this.addTiddler = function(tiddler) {
3064                 delete this.slices[tiddler.title];
3065                 tiddlers[tiddler.title] = tiddler;
3066                 };
3067         this.forEachTiddler = function(callback) {
3068                 for(var t in tiddlers)
3069                         {
3070                         var tiddler = tiddlers[t];
3071                         if(tiddler instanceof Tiddler)
3072                                 callback.call(this,t,tiddler);
3073                         }
3074                 };
3075 }
3076
3077 // Set the dirty flag
3078 TiddlyWiki.prototype.setDirty = function(dirty)
3079 {
3080         this.dirty = dirty;
3081 }
3082
3083 TiddlyWiki.prototype.isDirty = function()
3084 {
3085         return this.dirty;
3086 }
3087
3088 TiddlyWiki.prototype.suspendNotifications = function()
3089 {
3090         this.notificationLevel--;
3091 }
3092
3093 TiddlyWiki.prototype.resumeNotifications = function()
3094 {
3095         this.notificationLevel++;
3096 }
3097
3098 // Invoke the notification handlers for a particular tiddler
3099 TiddlyWiki.prototype.notify = function(title,doBlanket)
3100 {
3101         if(!this.notificationLevel)
3102                 for(var t=0; t<this.namedNotifications.length; t++)
3103                         {
3104                         var n = this.namedNotifications[t];
3105                         if((n.name == null && doBlanket) || (n.name == title))
3106                                 n.notify(title);
3107                         }
3108 }
3109
3110 // Invoke the notification handlers for all tiddlers
3111 TiddlyWiki.prototype.notifyAll = function()
3112 {
3113         if(!this.notificationLevel)
3114                 for(var t=0; t<this.namedNotifications.length; t++)
3115                         {
3116                         var n = this.namedNotifications[t];
3117                         if(n.name)
3118                                 n.notify(n.name);
3119                         }
3120 }
3121
3122 // Add a notification handler to a tiddler
3123 TiddlyWiki.prototype.addNotification = function(title,fn)
3124 {
3125         for (var i=0; i<this.namedNotifications.length; i++)
3126                 if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
3127                         return this;
3128         this.namedNotifications.push({name: title, notify: fn});
3129         return this;
3130 }
3131
3132 TiddlyWiki.prototype.removeTiddler = function(title)
3133 {
3134         var tiddler = this.fetchTiddler(title);
3135         if(tiddler)
3136                 {
3137                 this.deleteTiddler(title);
3138                 this.notify(title,true);
3139                 this.setDirty(true);
3140                 }
3141 }
3142
3143 TiddlyWiki.prototype.tiddlerExists = function(title)
3144 {
3145         var t = this.fetchTiddler(title);
3146         return (t != undefined);
3147 }
3148
3149 TiddlyWiki.prototype.isShadowTiddler = function(title)
3150 {
3151         return typeof config.shadowTiddlers[title] == "string";
3152 }
3153
3154 TiddlyWiki.prototype.getTiddler = function(title)
3155 {
3156         var t = this.fetchTiddler(title);
3157         if(t != undefined)
3158                 return t;
3159         else
3160                 return null;
3161 }
3162
3163 TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
3164 {
3165         var tiddler = this.fetchTiddler(title);
3166         if(tiddler)
3167                 return tiddler.text;
3168         if(!title)
3169                 return defaultText;
3170         var pos = title.indexOf(config.textPrimitives.sliceSeparator);
3171         if(pos != -1)
3172                 {
3173                 var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
3174                 if(slice)
3175                         return slice;
3176                 }
3177         if(this.isShadowTiddler(title))
3178                 return config.shadowTiddlers[title];
3179         if(defaultText != undefined)
3180                 return defaultText;
3181         return null;
3182 }
3183
3184 TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?(\w+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?(\w+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm
3185
3186 // @internal
3187 TiddlyWiki.prototype.calcAllSlices = function(title) 
3188 {
3189         var slices = {};
3190         var text = this.getTiddlerText(title,"");
3191         this.slicesRE.lastIndex = 0;
3192         do 
3193                 {
3194                         var m = this.slicesRE.exec(text);
3195                         if (m) 
3196                                 {
3197                                         if (m[1])
3198                                                 slices[m[1]] = m[2];
3199                                         else
3200                                                 slices[m[3]] = m[4];
3201                                 }
3202                 }
3203         while(m);
3204         return slices;
3205 }
3206
3207 // Returns the slice of text of the given name
3208 //#
3209 //# A text slice is a substring in the tiddler's text that is defined
3210 //# either like this
3211 //#    aName:  textSlice
3212 //# or
3213 //#    |aName:| textSlice |
3214 //# or
3215 //#    |aName| textSlice |
3216 //#
3217 //# In the text the name (or name:) may be decorated with '' or //. I.e.
3218 //# this would also a possible text slice:
3219 //#
3220 //#    |''aName:''| textSlice |
3221 //#
3222 //# @param name should only contain "word characters" (i.e. "a-ZA-Z_0-9")
3223 //# @return [may be undefined] the (trimmed) text of the specified slice.
3224 TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
3225 {
3226         var slices = this.slices[title];
3227         if (!slices) {
3228                 slices = this.calcAllSlices(title);
3229                 this.slices[title] = slices;
3230         }
3231         return slices[sliceName];
3232 }
3233
3234 // Build an hashmap of the specified named slices of a tiddler
3235 TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
3236 {
3237         var r = {};
3238         for(var t=0; t<sliceNames.length; t++)
3239                 {
3240                 var slice = this.getTiddlerSlice(title,sliceNames[t]);
3241                 if(slice)
3242                         r[sliceNames[t]] = slice;
3243                 }
3244         return r;
3245 }
3246
3247 TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
3248 {
3249         var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
3250         var text = this.getTiddlerText(title,null);
3251         if(text == null)
3252                 return defaultText;
3253         var textOut = [];
3254         var lastPos = 0;
3255         do {
3256                 var match = bracketRegExp.exec(text);
3257                 if(match)
3258                         {
3259                         textOut.push(text.substr(lastPos,match.index-lastPos));
3260                         if(match[1])
3261                                 {
3262                                 if(depth <= 0)
3263                                         textOut.push(match[1]);
3264                                 else
3265                                         textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
3266                                 }
3267                         lastPos = match.index + match[0].length;
3268                         }
3269                 else
3270                         textOut.push(text.substr(lastPos));
3271         } while(match);
3272         return(textOut.join(""));
3273 }
3274
3275 TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
3276 {
3277         var tiddler = this.fetchTiddler(title);
3278         if(tiddler)
3279                 {
3280                 var t = tiddler.tags.indexOf(tag);
3281                 if(t != -1)
3282                         tiddler.tags.splice(t,1);
3283                 if(status)
3284                         tiddler.tags.push(tag);
3285                 tiddler.changed();
3286                 this.notify(title,true);
3287                 this.setDirty(true);
3288                 }
3289 }
3290
3291 TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields)
3292 {
3293         var tiddler = this.fetchTiddler(title);
3294         var created;
3295         if(tiddler)
3296                 {
3297                 created = tiddler.created; // Preserve created date
3298                 this.deleteTiddler(title);
3299                 }
3300         else
3301                 {
3302                 tiddler = new Tiddler();
3303                 created = modified;
3304                 }
3305         tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
3306         this.addTiddler(tiddler);
3307         if(title != newTitle)
3308                 this.notify(title,true);
3309         this.notify(newTitle,true);
3310         this.setDirty(true);
3311         return tiddler;
3312 }
3313
3314 TiddlyWiki.prototype.createTiddler = function(title)
3315 {
3316         var tiddler = this.fetchTiddler(title);
3317         if(!tiddler)
3318                 {
3319                 tiddler = new Tiddler();
3320                 tiddler.title = title;
3321                 this.addTiddler(tiddler);
3322                 this.setDirty(true);
3323                 }
3324         return tiddler;
3325 }
3326
3327 // Load contents of a tiddlywiki from an HTML DIV
3328 TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
3329 {
3330         this.idPrefix = idPrefix;
3331         var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
3332         var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
3333         this.setDirty(false);
3334         if(!noUpdate)
3335                 {
3336                 for(var i = 0;i<tiddlers.length; i++)
3337                         tiddlers[i].changed();
3338                 }
3339 }
3340
3341 TiddlyWiki.prototype.updateTiddlers = function()
3342 {
3343         this.tiddlersUpdated = true;
3344         this.forEachTiddler(function(title,tiddler) {
3345                 tiddler.changed();
3346                 });
3347 }
3348
3349 // Return all tiddlers formatted as an HTML string
3350 TiddlyWiki.prototype.allTiddlersAsHtml = function()
3351 {
3352         return store.getSaver().externalize(store);
3353 }
3354
3355 // Return an array of tiddlers matching a search regular expression
3356 TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag)
3357 {
3358         var candidates = this.reverseLookup("tags",excludeTag,false);
3359         var results = [];
3360         for(var t=0; t<candidates.length; t++)
3361                 {
3362                 if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
3363                         results.push(candidates[t]);
3364                 }
3365         if(!sortField)
3366                 sortField = "title";
3367         results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
3368         return results;
3369 }
3370
3371 // Return an array of all the tags in use. Each member of the array is another array where [0] is the name of the tag and [1] is the number of occurances
3372 TiddlyWiki.prototype.getTags = function()
3373 {
3374         var results = [];
3375         this.forEachTiddler(function(title,tiddler) {
3376                 for(var g=0; g<tiddler.tags.length; g++)
3377                         {
3378                         var tag = tiddler.tags[g];
3379                         var f = false;
3380                         for(var c=0; c<results.length; c++)
3381                                 if(results[c][0] == tag)
3382                                         {
3383                                         f = true;
3384                                         results[c][1]++;
3385                                         }
3386                         if(!f)
3387                                 results.push([tag,1]);
3388                         }
3389                 });
3390         results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
3391         return results;
3392 }
3393
3394 // Return an array of the tiddlers that are tagged with a given tag
3395 TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
3396 {
3397         return this.reverseLookup("tags",tag,true,sortField);
3398 }
3399
3400 // Return an array of the tiddlers that link to a given tiddler
3401 TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
3402 {
3403         if(!this.tiddlersUpdated)
3404                 this.updateTiddlers();
3405         return this.reverseLookup("links",title,true,sortField);
3406 }
3407
3408 // Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
3409 // lookupMatch == true to match tiddlers, false to exclude tiddlers
3410 TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
3411 {
3412         var results = [];
3413         this.forEachTiddler(function(title,tiddler) {
3414                 var f = !lookupMatch;
3415                 for(var lookup=0; lookup<tiddler[lookupField].length; lookup++)
3416                         if(tiddler[lookupField][lookup] == lookupValue)
3417                                 f = lookupMatch;
3418                 if(f)
3419                         results.push(tiddler);
3420                 });
3421         if(!sortField)
3422                 sortField = "title";
3423         results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
3424         return results;
3425 }
3426
3427 // Return the tiddlers as a sorted array
3428 TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
3429 {
3430         var results = [];
3431         this.forEachTiddler(function(title,tiddler) {
3432                 if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
3433                         results.push(tiddler);
3434                 });
3435         if(field)
3436                 results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
3437         return results;
3438 }
3439
3440 // Return array of names of tiddlers that are referred to but not defined
3441 TiddlyWiki.prototype.getMissingLinks = function(sortField)
3442 {
3443         if(!this.tiddlersUpdated)
3444                 this.updateTiddlers();
3445         var results = [];
3446         this.forEachTiddler(function (title,tiddler) {
3447                 for(var n=0; n<tiddler.links.length;n++)
3448                         {
3449                         var link = tiddler.links[n];
3450                         if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
3451                                 results.pushUnique(link);
3452                         }
3453                 });
3454         results.sort();
3455         return results;
3456 }
3457
3458 // Return an array of names of tiddlers that are defined but not referred to
3459 TiddlyWiki.prototype.getOrphans = function()
3460 {
3461         var results = [];
3462         this.forEachTiddler(function (title,tiddler) {
3463                 if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
3464                         results.push(title);
3465                 });
3466         results.sort();
3467         return results;
3468 }
3469
3470 // Return an array of names of all the shadow tiddlers
3471 TiddlyWiki.prototype.getShadowed = function()
3472 {
3473         var results = [];
3474         for(var t in config.shadowTiddlers)
3475                 if(typeof config.shadowTiddlers[t] == "string")
3476                         results.push(t);
3477         results.sort();
3478         return results;
3479 }
3480
3481 // Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
3482 TiddlyWiki.prototype.resolveTiddler = function(tiddler) 
3483 {
3484         var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
3485         return t instanceof Tiddler ? t : null;
3486 }
3487
3488 TiddlyWiki.prototype.getLoader = function() 
3489 {
3490         if (!this.loader) 
3491                 this.loader = new TW21Loader();
3492         return this.loader;
3493 }
3494  
3495 TiddlyWiki.prototype.getSaver = function() 
3496 {
3497         if (!this.saver) 
3498                 this.saver = new TW21Saver();
3499         return this.saver;
3500 }
3501
3502 // Returns true if path is a valid field name (path),
3503 // i.e. a sequence of identifiers, separated by '.'
3504 TiddlyWiki.isValidFieldName = function (name) {
3505         var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
3506         return match && (match[0] == name);
3507 }
3508
3509 // Throws an exception when name is not a valid field name.
3510 TiddlyWiki.checkFieldName = function(name) {
3511         if (!TiddlyWiki.isValidFieldName(name))
3512                 throw config.messages.invalidFieldName.format([name]);
3513 }
3514
3515 function StringFieldAccess(n, readOnly) {
3516         this.set = readOnly 
3517                 ? function(t,v) {if (v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);}
3518                 : function(t,v) {if (v != t[n]) {t[n] = v; return true;}};
3519         this.get = function(t) {return t[n];};
3520 }
3521
3522 function DateFieldAccess(n) {
3523         this.set = function(t,v) {
3524                         var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v); 
3525                         if (d != t[n]) {
3526                                 t[n] = d; return true;
3527                         }
3528                 };
3529         this.get = function(t)   {return t[n].convertToYYYYMMDDHHMM();}
3530 }
3531
3532 function LinksFieldAccess(n) {
3533         this.set = function(t,v) {
3534                         var s = (typeof v == "string") ? v.readBracketedList() : v; 
3535                         if (s.toString() != t[n].toString()) {
3536                                 t[n] = s; return true;
3537                         }
3538                 };
3539         this.get = function(t)   {return String.encodeTiddlyLinkList(t[n]);}
3540 }
3541
3542 TiddlyWiki.standardFieldAccess = {
3543         // The set functions return true when setting the data has changed the value.
3544         
3545         "title":    new StringFieldAccess("title", true),
3546         // Handle the "tiddler" field name as the title
3547         "tiddler":  new StringFieldAccess("title", true),
3548         
3549         "text":     new StringFieldAccess("text"),
3550         "modifier": new StringFieldAccess("modifier"),
3551         "modified": new DateFieldAccess("modified"),
3552         "created":  new DateFieldAccess("created"),
3553         "tags":     new LinksFieldAccess("tags")
3554 };
3555
3556 TiddlyWiki.isStandardField = function(name) {
3557         return TiddlyWiki.standardFieldAccess[name] != undefined;
3558 }
3559
3560 // Sets the value of the given field of the tiddler to the value. 
3561 // Setting an ExtendedField's value to null or undefined removes the field. 
3562 // Setting a namespace to undefined removes all fields of that namespace.
3563 // The fieldName is case-insensitive.
3564 // All values will be converted to a string value.
3565 TiddlyWiki.prototype.setValue = function(tiddler, fieldName, value) {
3566         TiddlyWiki.checkFieldName(fieldName);
3567         var t = this.resolveTiddler(tiddler);
3568         if (!t)
3569                 return;
3570                 
3571         fieldName = fieldName.toLowerCase();
3572
3573         var isRemove = (value === undefined) || (value === null);
3574
3575         if (!t.fields) 
3576                 t.fields = {};
3577                 
3578         var accessor = TiddlyWiki.standardFieldAccess[fieldName];
3579         if (accessor) {
3580                 if (isRemove)
3581                         // don't remove StandardFields
3582                         return;
3583                 var h = TiddlyWiki.standardFieldAccess[fieldName];
3584                 if (!h.set(t, value))
3585                         return;
3586
3587         } else {
3588                 var oldValue = t.fields[fieldName];
3589                 
3590                 if (isRemove) {
3591                         if (oldValue !== undefined) {
3592                                 // deletes a single field
3593                                 delete t.fields[fieldName];
3594                         } else {
3595                                 // no concrete value is defined for the fieldName
3596                                 // so we guess this is a namespace path.
3597                                 
3598                                 // delete all fields in a namespace
3599                                 var re = new RegExp('^'+fieldName+'\\.');
3600                                 var dirty = false;
3601                                 for (var n in t.fields) {
3602                                         if (n.match(re)) {
3603                                                 delete t.fields[n];
3604                                                 dirty = true;
3605                                         }
3606                                 }
3607                                 if (!dirty)
3608                                         return
3609                         }
3610                                 
3611                 } else {
3612                         // the "normal" set case. value is defined (not null/undefined)
3613                         // For convenience provide a nicer conversion Date->String
3614                         value = value instanceof Date 
3615                                 ? value.convertToYYYYMMDDHHMMSSMMM() 
3616                                 : String(value);
3617                         if (oldValue == value) 
3618                                 return;
3619                         t.fields[fieldName] = value;
3620                 }
3621         }
3622         
3623         // When we are here the tiddler/store really was changed.
3624         this.notify(t.title,true);
3625         if (!fieldName.match(/^temp\./))
3626                 this.setDirty(true);
3627 }
3628
3629 // Returns the value of the given field of the tiddler. 
3630 // The fieldName is case-insensitive.
3631 // Will only return String values (or undefined).
3632 TiddlyWiki.prototype.getValue = function(tiddler, fieldName) {
3633         var t = this.resolveTiddler(tiddler);
3634         if (!t)
3635                 return undefined;
3636
3637         fieldName = fieldName.toLowerCase();
3638
3639         var accessor = TiddlyWiki.standardFieldAccess[fieldName];
3640         if (accessor) {
3641                 return accessor.get(t);
3642         }
3643         
3644         return t.fields ? t.fields[fieldName] : undefined;
3645 }
3646
3647 // Calls the callback function for every field in the tiddler.
3648 //
3649 // When callback function returns a non-false value the iteration stops 
3650 // and that value is returned. 
3651 //
3652 // The order of the fields is not defined.
3653 // 
3654 // @param callback a function(tiddler, fieldName, value). 
3655 // 
3656 TiddlyWiki.prototype.forEachField = function(tiddler, callback, onlyExtendedFields) {
3657         var t = this.resolveTiddler(tiddler);
3658         if (!t)
3659                 return undefined;
3660         
3661         if (t.fields) {
3662                 for (var n in t.fields) {
3663                         var result = callback(t, n, t.fields[n]);
3664                         if (result)
3665                                 return result;
3666                 }
3667         }
3668         
3669         if (onlyExtendedFields)
3670                 return undefined;
3671
3672         for (var n in TiddlyWiki.standardFieldAccess) {
3673                 if (n == "tiddler")
3674                         // even though the "title" field can also be referenced through the name "tiddler"
3675                         // we only visit this field once.
3676                         continue;
3677                         
3678                 var result = callback(t, n, TiddlyWiki.standardFieldAccess[n].get(t));
3679                 if (result)
3680                         return result;
3681         }
3682
3683         return undefined;
3684 };
3685 // ---------------------------------------------------------------------------------
3686 // Story functions
3687 // ---------------------------------------------------------------------------------
3688
3689 // A story is a HTML div containing a sequence of tiddlers that can be manipulated
3690 // container - id of containing element
3691 // idPrefix - string prefix prepended to title to make ids for tiddlers in this story
3692 function Story(container,idPrefix)
3693 {
3694         this.container = container;
3695         this.idPrefix = idPrefix;
3696         this.highlightRegExp = null;
3697 }
3698
3699 // Iterate through all the tiddlers in a story
3700 // fn - callback function to be called for each tiddler. Arguments are:
3701 //              tiddler - reference to Tiddler object
3702 //              element - reference to tiddler display element
3703 Story.prototype.forEachTiddler = function(fn)
3704 {
3705         var place = document.getElementById(this.container);
3706         if(!place)
3707                 return;
3708         var e = place.firstChild;
3709         while(e)
3710                 {
3711                 var n = e.nextSibling;
3712                 var title = e.getAttribute("tiddler");
3713                 fn.call(this,title,e);
3714                 e = n;
3715                 }
3716 }
3717
3718 // Display several tiddlers given their titles in an array. Parameters same as displayTiddler(), except:
3719 // titles - array of string titles
3720 Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,slowly)
3721 {
3722         for(var t = titles.length-1;t>=0;t--)
3723                 this.displayTiddler(srcElement,titles[t],template,animate,slowly);
3724 }
3725
3726 // Display a given tiddler with a given template. If the tiddler is already displayed but with a different
3727 // template, it is switched to the specified template
3728 // srcElement - reference to element from which this one is being opened -or-
3729 //              special positions "top", "bottom"
3730 // title - title of tiddler to display
3731 // template - the name of the tiddler containing the template -or-
3732 //                        one of the constants DEFAULT_VIEW_TEMPLATE and DEFAULT_EDIT_TEMPLATE -or-
3733 //                        null or undefined to indicate the current template if there is one, DEFAULT_VIEW_TEMPLATE if not
3734 // animate - whether to perform animations
3735 // slowly - whether to perform animations in slomo
3736 Story.prototype.displayTiddler = function(srcElement,title,template,animate,slowly)
3737 {
3738         var place = document.getElementById(this.container);
3739         var tiddlerElem = document.getElementById(this.idPrefix + title);
3740         if(tiddlerElem)
3741                 this.refreshTiddler(title,template);
3742         else
3743                 {
3744                 var before = this.positionTiddler(srcElement);
3745                 tiddlerElem = this.createTiddler(place,before,title,template);
3746                 }
3747         if(srcElement && typeof srcElement !== "string")
3748                 {
3749                 if(anim && config.options.chkAnimate && (animate == undefined || animate == true))
3750                         anim.startAnimating(new Cascade(title,srcElement,tiddlerElem,slowly),new Scroller(tiddlerElem,slowly));
3751                 else
3752                         window.scrollTo(0,ensureVisible(tiddlerElem));
3753                 }
3754 }
3755
3756 // Figure out the appropriate position for a newly opened tiddler
3757 // srcElement - reference to the element containing the link to the tiddler -or-
3758 //              special positions "top", "bottom"
3759 // returns - reference to the tiddler that the new one should appear before (null for the bottom of the story)
3760 Story.prototype.positionTiddler = function(srcElement)
3761 {
3762         var place = document.getElementById(this.container);
3763         var before;
3764         if(typeof srcElement == "string")
3765                 {
3766                 switch(srcElement)
3767                         {
3768                         case "top":
3769                                 before = place.firstChild;
3770                                 break;
3771                         case "bottom":
3772                                 before = null;
3773                                 break;
3774                         }
3775                 }
3776         else
3777                 {
3778                 var after = this.findContainingTiddler(srcElement);
3779                 if(after == null)
3780                         before = place.firstChild;
3781                 else if(after.nextSibling)
3782                         before = after.nextSibling;
3783                 else
3784                         before = null;
3785                 }
3786         return before;
3787 }
3788
3789 // Create a tiddler frame at the appropriate place in a story column
3790 // place - reference to parent element
3791 // before - null, or reference to element before which to insert new tiddler
3792 // title - title of new tiddler
3793 // template - the name of the tiddler containing the template or one of the constants DEFAULT_VIEW_TEMPLATE and DEFAULT_EDIT_TEMPLATE
3794 Story.prototype.createTiddler = function(place,before,title,template)
3795 {
3796         var tiddlerElem = createTiddlyElement(null,"div",this.idPrefix + title,"tiddler");
3797         tiddlerElem.setAttribute("refresh","tiddler");
3798         place.insertBefore(tiddlerElem,before);
3799         this.refreshTiddler(title,template);
3800         return tiddlerElem;
3801 }
3802
3803 // Overridable for choosing the name of the template to apply for a tiddler
3804 Story.prototype.chooseTemplateForTiddler = function(title,template)
3805 {
3806         if(!template)
3807                 template = DEFAULT_VIEW_TEMPLATE;
3808         if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
3809                 template = config.tiddlerTemplates[template];
3810         return template;
3811 }
3812
3813 // Overridable for extracting the text of a template from a tiddler
3814 Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
3815 {
3816         return store.getRecursiveTiddlerText(template,null,10);
3817 }
3818
3819 // Apply a template to an existing tiddler if it is not already displayed using that template
3820 // title - title of tiddler to update
3821 // template - the name of the tiddler containing the template or one of the constants DEFAULT_VIEW_TEMPLATE and DEFAULT_EDIT_TEMPLATE
3822 // force - if true, forces the refresh even if the template hasn't changedd
3823 Story.prototype.refreshTiddler = function(title,template,force)
3824 {
3825         var tiddlerElem = document.getElementById(this.idPrefix + title);
3826         if(tiddlerElem)
3827                 {
3828                 if(tiddlerElem.getAttribute("dirty") == "true" && !force)
3829                         return tiddlerElem;
3830                 template = this.chooseTemplateForTiddler(title,template);
3831                 var currTemplate = tiddlerElem.getAttribute("template");
3832                 if((template != currTemplate) || force)
3833                         {
3834                         var tiddler = store.getTiddler(title);
3835                         if(!tiddler)
3836                                 {
3837                                 tiddler = new Tiddler();
3838                                 if(store.isShadowTiddler(title))
3839                                         tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
3840                                 else
3841                                         {
3842                                         var text = template=="EditTemplate"
3843                                                                 ? config.views.editor.defaultText.format([title])
3844                                                                 : config.views.wikified.defaultText.format([title]);
3845                                         tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date);
3846                                         }
3847                                 }
3848                         tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
3849                         tiddlerElem.setAttribute("tiddler",title);
3850                         tiddlerElem.setAttribute("template",template);
3851                         var me = this;
3852                         tiddlerElem.onmouseover = this.onTiddlerMouseOver;
3853                         tiddlerElem.onmouseout = this.onTiddlerMouseOut;
3854                         tiddlerElem.ondblclick = this.onTiddlerDblClick;
3855                         tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
3856                         var html = this.getTemplateForTiddler(title,template,tiddler);
3857                         tiddlerElem.innerHTML = html;
3858                         applyHtmlMacros(tiddlerElem,tiddler);
3859                         if(store.getTaggedTiddlers(title).length > 0)
3860                                 addClass(tiddlerElem,"isTag");
3861                         else
3862                                 removeClass(tiddlerElem,"isTag");
3863                         if(!store.tiddlerExists(title))
3864                                 {
3865                                 if(store.isShadowTiddler(title))
3866                                         addClass(tiddlerElem,"shadow");
3867                                 else
3868                                         addClass(tiddlerElem,"missing");
3869                                 }
3870                         else
3871                                 {
3872                                 removeClass(tiddlerElem,"shadow");
3873                                 removeClass(tiddlerElem,"missing");
3874                                 }
3875                         }
3876                 }
3877         return tiddlerElem;
3878 }
3879
3880 // Refresh all tiddlers in the Story
3881 Story.prototype.refreshAllTiddlers = function() 
3882 {
3883         var place = document.getElementById(this.container);
3884         var e = place.firstChild;
3885         if(!e)
3886                 return;
3887         this.refreshTiddler(e.getAttribute("tiddler"),e.getAttribute("template"),true);
3888         while((e = e.nextSibling) != null) 
3889                 this.refreshTiddler(e.getAttribute("tiddler"),e.getAttribute("template"),true);
3890 }
3891
3892 // Default tiddler onmouseover/out event handlers
3893 Story.prototype.onTiddlerMouseOver = function(e)
3894 {
3895         if(window.addClass instanceof Function)
3896                 addClass(this,"selected");
3897 }
3898
3899 Story.prototype.onTiddlerMouseOut = function(e)
3900 {
3901         if(window.removeClass instanceof Function)
3902                 removeClass(this,"selected");
3903 }
3904
3905 // Default tiddler ondblclick event handler
3906 Story.prototype.onTiddlerDblClick = function(e)
3907 {
3908         if(!e) var e = window.event;
3909         var theTarget = resolveTarget(e);
3910         if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea")
3911                 {
3912                 if(document.selection && document.selection.empty)
3913                         document.selection.empty();
3914                 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
3915                 e.cancelBubble = true;
3916                 if (e.stopPropagation) e.stopPropagation();
3917                 return true;
3918                 }
3919         else
3920                 return false;
3921 }
3922
3923 Story.prototype.onTiddlerKeyPress = function(e)
3924 {
3925         if(!e) var e = window.event;
3926         clearMessage();
3927         var consume = false; 
3928         var title = this.getAttribute("tiddler");
3929         var target = resolveTarget(e);
3930         switch(e.keyCode)
3931                 {
3932                 case 9: // Tab
3933                         if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea")
3934                                 {
3935                                 replaceSelection(target,String.fromCharCode(9));
3936                                 consume = true; 
3937                                 }
3938                         if(config.isOpera)
3939                                 {
3940                                 target.onblur = function()
3941                                         {
3942                                         this.focus();
3943                                         this.onblur = null;
3944                                         }
3945                                 }
3946                         break;
3947                 case 13: // Ctrl-Enter
3948                 case 10: // Ctrl-Enter on IE PC
3949                 case 77: // Ctrl-Enter is "M" on some platforms
3950                         if(e.ctrlKey)
3951                                 {
3952                                 blurElement(this);
3953                                 config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
3954                                 consume = true;
3955                                 }
3956                         break; 
3957                 case 27: // Escape
3958                         blurElement(this);
3959                         config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
3960                         consume = true;
3961                         break;
3962                 }
3963         e.cancelBubble = consume;
3964         if(consume)
3965                 {
3966                 if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
3967                 e.returnValue = true; // Cancel The Event in IE
3968                 if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
3969                 }
3970         return(!consume);
3971 };
3972
3973 // Returns the specified field (input or textarea element) in a tiddler, otherwise the first edit field it finds
3974 // or null if it found no edit field at all
3975 Story.prototype.getTiddlerField = function(title,field)
3976 {
3977         var tiddlerElem = document.getElementById(this.idPrefix + title);
3978         var e = null;
3979         if(tiddlerElem != null)
3980                 {
3981                 var children = tiddlerElem.getElementsByTagName("*");
3982                 for (var t=0; t<children.length; t++)
3983                         {
3984                         var c = children[t];
3985                         if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea")
3986                                 {
3987                                 if(!e)
3988                                         e = c;
3989                                 if(c.getAttribute("edit") == field)
3990                                         e = c;
3991                                 }
3992                         }
3993                 }
3994         return e;
3995 }
3996
3997 // Focus a specified tiddler. Attempts to focus the specified field, otherwise the first edit field it finds
3998 Story.prototype.focusTiddler = function(title,field)
3999 {
4000         var e = this.getTiddlerField(title,field);
4001         if(e)
4002                 {
4003                 e.focus();
4004                 e.select();
4005                 }
4006 }
4007
4008 // Ensures that a specified tiddler does not have the focus
4009 Story.prototype.blurTiddler = function(title)
4010 {
4011         var tiddlerElem = document.getElementById(this.idPrefix + title);
4012         if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur)
4013                 {
4014                 tiddlerElem.focus();
4015                 tiddlerElem.blur();
4016                 }
4017 }
4018
4019 // Adds a specified value to the edit controls (if any) of a particular
4020 // array-formatted field of a particular tiddler (eg "tags")
4021 //  title - name of tiddler
4022 //  tag - value of field, without any [[brackets]]
4023 //  mode - +1 to add the tag, -1 to remove it, 0 to toggle it
4024 //  field - name of field (eg "tags")
4025 Story.prototype.setTiddlerField = function(title,tag,mode,field)
4026 {
4027         var c = story.getTiddlerField(title,field);
4028
4029         var tags = c.value.readBracketedList();
4030         tags.setItem(tag,mode);
4031         c.value = String.encodeTiddlyLinkList(tags);
4032 }
4033
4034 // The same as setTiddlerField but preset to the "tags" field
4035 Story.prototype.setTiddlerTag = function(title,tag,mode)
4036 {
4037         Story.prototype.setTiddlerField(title,tag,mode,"tags");
4038 }
4039
4040 // Close a specified tiddler
4041 // title - name of tiddler to close
4042 // animate - whether to perform animations
4043 // slowly - whether to perform animations in slomo
4044 Story.prototype.closeTiddler = function(title,animate,slowly)
4045 {
4046         var tiddlerElem = document.getElementById(this.idPrefix + title);
4047         if(tiddlerElem != null)
4048                 {
4049                 clearMessage();
4050                 this.scrubTiddler(tiddlerElem);
4051                 if(anim && config.options.chkAnimate && animate)
4052                         anim.startAnimating(new Slider(tiddlerElem,false,slowly,"all"));
4053                 else
4054                         tiddlerElem.parentNode.removeChild(tiddlerElem);
4055                 }
4056 }
4057
4058 // Scrub IDs from a tiddler. This is so that the 'ghost' of a tiddler while it is being closed
4059 // does not interfere with things
4060 // tiddler - reference to the tiddler element
4061 Story.prototype.scrubTiddler = function(tiddlerElem)
4062 {
4063         tiddlerElem.id = null;
4064 }
4065
4066 // Set the 'dirty' flag of a tiddler
4067 // title - title of tiddler to change
4068 // dirty - new boolean status of flag
4069 Story.prototype.setDirty = function(title,dirty)
4070 {
4071         var tiddlerElem = document.getElementById(this.idPrefix + title);
4072         if(tiddlerElem != null)
4073                 tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
4074 }
4075
4076 // Is a particular tiddler dirty (with unsaved changes)?
4077 Story.prototype.isDirty = function(title)
4078 {
4079         var tiddlerElem = document.getElementById(this.idPrefix + title);
4080         if(tiddlerElem != null)
4081                 return tiddlerElem.getAttribute("dirty") == "true";
4082         return null;
4083 }
4084
4085 // Determine whether any open tiddler are dirty
4086 Story.prototype.areAnyDirty = function()
4087 {
4088         var r = false;
4089         this.forEachTiddler(function(title,element) {
4090                 if(this.isDirty(title))
4091                         r = true;
4092                 });
4093         return r;
4094 }
4095
4096 // Close all tiddlers in the story
4097 Story.prototype.closeAllTiddlers = function(exclude)
4098 {
4099         clearMessage();
4100         this.forEachTiddler(function(title,element) {
4101                 if((title != exclude) && element.getAttribute("dirty") != "true")
4102                         this.closeTiddler(title);
4103                 });
4104         window.scrollTo(0,0);
4105 }
4106
4107 // Check if there are any tiddlers in the story
4108 Story.prototype.isEmpty = function()
4109 {
4110         var place = document.getElementById(this.container);
4111         return(place && place.firstChild == null);
4112 }
4113
4114 // Perform a search and display the result
4115 // text - text to search for
4116 // useCaseSensitive - true for case sensitive matching
4117 // useRegExp - true to interpret text as a RegExp
4118 Story.prototype.search = function(text,useCaseSensitive,useRegExp)
4119 {
4120         this.closeAllTiddlers();
4121         highlightHack = new RegExp(useRegExp ?   text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
4122         var matches = store.search(highlightHack,"title","excludeSearch");
4123         var titles = [];
4124         for(var t=matches.length-1; t>=0; t--)
4125                 titles.push(matches[t].title);
4126         this.displayTiddlers(null,titles);
4127         highlightHack = null;
4128         var q = useRegExp ? "/" : "'";
4129         if(matches.length > 0)
4130                 displayMessage(config.macros.search.successMsg.format([titles.length.toString(),q + text + q]));
4131         else
4132                 displayMessage(config.macros.search.failureMsg.format([q + text + q]));
4133 }
4134
4135 // Determine if the specified element is within a tiddler in this story
4136 // e - reference to an element
4137 // returns: reference to a tiddler element or null if none
4138 Story.prototype.findContainingTiddler = function(e)
4139 {
4140         while(e && !hasClass(e,"tiddler"))
4141                 e = e.parentNode;
4142         return(e);
4143 }
4144
4145 // Gather any saveable fields from a tiddler element
4146 // e - reference to an element to scan recursively
4147 // fields - object to contain gathered field values
4148 Story.prototype.gatherSaveFields = function(e,fields)
4149 {
4150         if(e && e.getAttribute)
4151                 {
4152                 var f = e.getAttribute("edit");
4153                 if(f)
4154                         fields[f] = e.value.replace(/\r/mg,"");;
4155                 if(e.hasChildNodes())
4156                         {
4157                         var c = e.childNodes;
4158                         for(var t=0; t<c.length; t++)
4159                                 this.gatherSaveFields(c[t],fields)
4160                         }
4161                 }
4162 }
4163
4164 // Determine whether a tiddler has any edit fields, and if so if their values have been changed
4165 // title - name of tiddler
4166 Story.prototype.hasChanges = function(title)
4167 {
4168         var e = document.getElementById(this.idPrefix + title);
4169         if(e != null)
4170                 {
4171                 var fields = {};
4172                 this.gatherSaveFields(e,fields);
4173                 var tiddler = store.fetchTiddler(title);
4174                 if (!tiddler)
4175                         return false;
4176                 for(var n in fields)
4177                         if (store.getValue(title,n) != fields[n])
4178                                 return true;
4179                 }
4180         return false;
4181 }
4182
4183 // Save any open edit fields of a tiddler and updates the display as necessary
4184 // title - name of tiddler
4185 // minorUpdate - true if the modified date shouldn't be updated
4186 // returns: title of saved tiddler, or null if not saved
4187 Story.prototype.saveTiddler = function(title,minorUpdate)
4188 {
4189         var tiddlerElem = document.getElementById(this.idPrefix + title);
4190         if(tiddlerElem != null)
4191                 {
4192                 var fields = {};
4193                 this.gatherSaveFields(tiddlerElem,fields);
4194                 var newTitle = fields.title ? fields.title : title;
4195                 if(store.tiddlerExists(newTitle) && newTitle != title)
4196                         {
4197                         if(confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
4198                                 this.closeTiddler(newTitle,false,false);
4199                         else
4200                                 return null;
4201                         }
4202                 tiddlerElem.id = this.idPrefix + newTitle;
4203                 tiddlerElem.setAttribute("tiddler",newTitle);
4204                 tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
4205                 tiddlerElem.setAttribute("dirty","false");
4206                 if(config.options.chkForceMinorUpdate)
4207                         minorUpdate = !minorUpdate;
4208                 var newDate = new Date();
4209                 store.saveTiddler(title,newTitle,fields.text,config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags);
4210                 for (var n in fields) 
4211                         if (!TiddlyWiki.isStandardField(n))
4212                                 store.setValue(newTitle,n,fields[n]);
4213                 if(config.options.chkAutoSave)
4214                         saveChanges();
4215                 return newTitle;
4216                 }
4217         return null;
4218 }
4219
4220 Story.prototype.permaView = function()
4221 {
4222         var links = [];
4223         this.forEachTiddler(function(title,element) {
4224                 links.push(String.encodeTiddlyLink(title));
4225                 });
4226         var t = encodeURIComponent(links.join(" "));
4227         if(t == "")
4228                 t = "#";
4229         if(window.location.hash != t)
4230                 window.location.hash = t;
4231 }
4232
4233 // ---------------------------------------------------------------------------------
4234 // Message area
4235 // ---------------------------------------------------------------------------------
4236
4237 function getMessageDiv()
4238 {
4239         var msgArea = document.getElementById("messageArea");
4240         if(!msgArea)
4241                 return null;
4242         if(!msgArea.hasChildNodes())
4243                 createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
4244                         config.messages.messageClose.text,
4245                         config.messages.messageClose.tooltip,
4246                         clearMessage);
4247         msgArea.style.display = "block";
4248         return createTiddlyElement(msgArea,"div");
4249 }
4250
4251 function displayMessage(text,linkText)
4252 {
4253         var e = getMessageDiv();
4254         if(!e)
4255                 {
4256                 alert(text);
4257                 return;
4258                 }
4259         if(linkText)
4260                 {
4261                 var link = createTiddlyElement(e,"a",null,null,text);
4262                 link.href = linkText;
4263                 link.target = "_blank";
4264                 }
4265         else
4266                 e.appendChild(document.createTextNode(text));
4267 }
4268
4269 function clearMessage()
4270 {
4271         var msgArea = document.getElementById("messageArea");
4272         if(msgArea)
4273                 {
4274                 removeChildren(msgArea);
4275                 msgArea.style.display = "none";
4276                 }
4277         return false;
4278 }
4279
4280 // ---------------------------------------------------------------------------------
4281 // Refresh mechanism
4282 // ---------------------------------------------------------------------------------
4283
4284 config.refreshers = {
4285         link: function(e,changeList)
4286                 {
4287                 var title = e.getAttribute("tiddlyLink");
4288                 refreshTiddlyLink(e,title);
4289                 return true;
4290                 },
4291         
4292         tiddler: function(e,changeList)
4293                 {
4294                 var title = e.getAttribute("tiddler");
4295                 var template = e.getAttribute("template");
4296                 if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
4297                         story.refreshTiddler(title,template,true);
4298                 else
4299                         refreshElements(e,changeList);
4300                 return true;
4301                 },
4302
4303         content: function(e,changeList)
4304                 {
4305                 var title = e.getAttribute("tiddler");
4306                 var force = e.getAttribute("force");
4307                 if(force != null || changeList == null || changeList.indexOf(title) != -1)
4308                         {
4309                         removeChildren(e);
4310                         wikify(store.getTiddlerText(title,title),e);
4311                         return true;
4312                         }
4313                 else
4314                         return false;
4315                 },
4316
4317         macro: function(e,changeList)
4318                 {
4319                 var macro = e.getAttribute("macroName");
4320                 var params = e.getAttribute("params");
4321                 if(macro)
4322                         macro = config.macros[macro];
4323                 if(macro && macro.refresh)
4324                         macro.refresh(e,params);
4325                 return true;
4326                 }
4327 };
4328
4329 function refreshElements(root,changeList)
4330 {
4331         var nodes = root.childNodes;
4332         for(var c=0; c<nodes.length; c++)
4333                 {
4334                 var e = nodes[c],type;
4335                 if(e.getAttribute)
4336                         type = e.getAttribute("refresh");
4337                 else
4338                         type = null;
4339                 var refresher = config.refreshers[type];
4340                 var refreshed = false;
4341                 if(refresher != undefined)
4342                         refreshed = refresher(e,changeList);
4343                 if(e.hasChildNodes() && !refreshed)
4344                         refreshElements(e,changeList);
4345                 }
4346 }
4347
4348 function applyHtmlMacros(root,tiddler)
4349 {
4350         var e = root.firstChild;
4351         while(e)
4352                 {
4353                 var nextChild = e.nextSibling;
4354                 if(e.getAttribute)
4355                         {
4356                         var macro = e.getAttribute("macro");
4357                         if(macro)
4358                                 {
4359                                 var params = "";
4360                                 var p = macro.indexOf(" ");
4361                                 if(p != -1)
4362                                         {
4363                                         params = macro.substr(p+1);
4364                                         macro = macro.substr(0,p);
4365                                         }
4366                                 invokeMacro(e,macro,params,null,tiddler);
4367                                 }
4368                         }
4369                 if(e.hasChildNodes())
4370                         applyHtmlMacros(e,tiddler);
4371                 e = nextChild;
4372                 }
4373 }
4374
4375 function refreshPageTemplate(title)
4376 {
4377         var stash = createTiddlyElement(document.body,"div");
4378         stash.style.display = "none";
4379         var display = document.getElementById("tiddlerDisplay");
4380         var nodes,t;
4381         if(display)
4382                 {
4383                 nodes = display.childNodes;
4384                 for(t=nodes.length-1; t>=0; t--)
4385                         stash.appendChild(nodes[t]);
4386                 }
4387         var wrapper = document.getElementById("contentWrapper");
4388         if(!title)
4389                 title = "PageTemplate";
4390         var html = store.getRecursiveTiddlerText(title,null,10);
4391         wrapper.innerHTML = html;
4392         applyHtmlMacros(wrapper);
4393         refreshElements(wrapper);
4394         display = document.getElementById("tiddlerDisplay");
4395         removeChildren(display);
4396         if(!display)
4397                 display = createTiddlyElement(wrapper,"div","tiddlerDisplay");
4398         nodes = stash.childNodes;
4399         for(t=nodes.length-1; t>=0; t--)
4400                 display.appendChild(nodes[t]);
4401         stash.parentNode.removeChild(stash);
4402 }
4403
4404 function refreshDisplay(hint)
4405 {
4406         var e = document.getElementById("contentWrapper");
4407         if(typeof hint == "string")
4408                 hint = [hint];
4409         refreshElements(e,hint);
4410 }
4411
4412 function refreshPageTitle()
4413 {
4414         document.title = wikifyPlain("SiteTitle") + " - " + wikifyPlain("SiteSubtitle");
4415 }
4416
4417 function refreshStyles(title)
4418 {
4419         setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title);
4420 }
4421
4422 function refreshColorPalette(title)
4423 {
4424         if(!startingUp)
4425                 refreshAll();
4426 }
4427
4428 function refreshAll()
4429 {
4430         refreshPageTemplate();
4431         refreshDisplay();
4432         refreshStyles("StyleSheetLayout");
4433         refreshStyles("StyleSheetColors");
4434         refreshStyles("StyleSheet");
4435         refreshStyles("StyleSheetPrint");
4436 }
4437
4438 // ---------------------------------------------------------------------------------
4439 // Options cookie stuff
4440 // ---------------------------------------------------------------------------------
4441
4442 function loadOptionsCookie()
4443 {
4444         if(safeMode)
4445                 return;
4446         var cookies = document.cookie.split(";");
4447         for(var c=0; c<cookies.length; c++)
4448                 {
4449                 var p = cookies[c].indexOf("=");
4450                 if(p != -1)
4451                         {
4452                         var name = cookies[c].substr(0,p).trim();
4453                         var value = cookies[c].substr(p+1).trim();
4454                         switch(name.substr(0,3))
4455                                 {
4456                                 case "txt":
4457                                         config.options[name] = unescape(value);
4458                                         break;
4459                                 case "chk":
4460                                         config.options[name] = value == "true";
4461                                         break;
4462                                 }
4463                         }
4464                 }
4465 }
4466
4467 function saveOptionCookie(name)
4468 {
4469         if(safeMode)
4470                 return;
4471         var c = name + "=";
4472         switch(name.substr(0,3))
4473                 {
4474                 case "txt":
4475                         c += escape(config.options[name].toString());
4476                         break;
4477                 case "chk":
4478                         c += config.options[name] ? "true" : "false";
4479                         break;
4480                 }
4481         c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
4482         document.cookie = c;
4483 }
4484
4485 // ---------------------------------------------------------------------------------
4486 // Saving
4487 // ---------------------------------------------------------------------------------
4488
4489 var saveUsingSafari = false;
4490
4491 var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
4492 var endSaveArea = '</d' + 'iv>';
4493
4494 // If there are unsaved changes, force the user to confirm before exitting
4495 function confirmExit()
4496 {
4497         hadConfirmExit = true;
4498         if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
4499                 return config.messages.confirmExit;
4500 }
4501
4502 // Give the user a chance to save changes before exitting
4503 function checkUnsavedChanges()
4504 {
4505         if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false)
4506                 {
4507                 if(confirm(config.messages.unsavedChangesWarning))
4508                         saveChanges();
4509                 }
4510 }
4511
4512 function updateMarkupBlock(s,blockName,tiddlerName)
4513 {
4514         return s.replaceChunk(
4515                         "<!--%0-START-->".format([blockName]),
4516                         "<!--%0-END-->".format([blockName]),
4517                         "\n" + store.getRecursiveTiddlerText(tiddlerName,"") + "\n");
4518 }
4519
4520 // Save this tiddlywiki with the pending changes
4521 function saveChanges(onlyIfDirty)
4522 {
4523         if(onlyIfDirty && !store.isDirty())
4524                 return;
4525         clearMessage();
4526         // Get the URL of the document
4527         var originalPath = document.location.toString();
4528         // Check we were loaded from a file URL
4529         if(originalPath.substr(0,5) != "file:")
4530                 {
4531                 alert(config.messages.notFileUrlError);
4532                 if(store.tiddlerExists(config.messages.saveInstructions))
4533                         story.displayTiddler(null,config.messages.saveInstructions);
4534                 return;
4535                 }
4536         var localPath = getLocalPath(originalPath);
4537         // Load the original file
4538         var original = loadFile(localPath);
4539         if(original == null)
4540                 {
4541                 alert(config.messages.cantSaveError);
4542                 if(store.tiddlerExists(config.messages.saveInstructions))
4543                         story.displayTiddler(null,config.messages.saveInstructions);
4544                 return;
4545                 }
4546         // Locate the storeArea div's
4547         var posOpeningDiv = original.indexOf(startSaveArea);
4548         var limitClosingDiv = original.indexOf("<!--POST-BODY-START--"+">");
4549         var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
4550         if((posOpeningDiv == -1) || (posClosingDiv == -1))
4551                 {
4552                 alert(config.messages.invalidFileError.format([localPath]));
4553                 return;
4554                 }
4555         // Save the backup
4556         if(config.options.chkSaveBackups)
4557                 {
4558                 var backupPath = getBackupPath(localPath);
4559                 var backup = saveFile(backupPath,original);
4560                 if(backup)
4561                         displayMessage(config.messages.backupSaved,"file://" + backupPath);
4562                 else
4563                         alert(config.messages.backupFailed);
4564                 }
4565         // Save Rss
4566         if(config.options.chkGenerateAnRssFeed)
4567                 {
4568                 var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
4569                 var rssSave = saveFile(rssPath,convertUnicodeToUTF8(generateRss()));
4570                 if(rssSave)
4571                         displayMessage(config.messages.rssSaved,"file://" + rssPath);
4572                 else
4573                         alert(config.messages.rssFailed);
4574                 }
4575         // Save empty template
4576         if(config.options.chkSaveEmptyTemplate)
4577                 {
4578                 var emptyPath,p;
4579                 if((p = localPath.lastIndexOf("/")) != -1)
4580                         emptyPath = localPath.substr(0,p) + "/empty.html";
4581                 else if((p = localPath.lastIndexOf("\\")) != -1)
4582                         emptyPath = localPath.substr(0,p) + "\\empty.html";
4583                 else
4584                         emptyPath = localPath + ".empty.html";
4585                 var empty = original.substr(0,posOpeningDiv + startSaveArea.length) + original.substr(posClosingDiv);
4586                 var emptySave = saveFile(emptyPath,empty);
4587                 if(emptySave)
4588                         displayMessage(config.messages.emptySaved,"file://" + emptyPath);
4589                 else
4590                         alert(config.messages.emptyFailed);
4591                 }
4592         var save;
4593         try 
4594                 {
4595                 // Save new file
4596                 var revised = original.substr(0,posOpeningDiv + startSaveArea.length) + "\n" +
4597                                         convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
4598                                         original.substr(posClosingDiv);
4599                 var newSiteTitle = convertUnicodeToUTF8((wikifyPlain("SiteTitle") + " - " + wikifyPlain("SiteSubtitle")).htmlEncode());
4600                 revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
4601                 revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
4602                 revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
4603                 revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
4604                 revised = updateMarkupBlock(revised,"POST-BODY","MarkupPostBody");
4605                 save = saveFile(localPath,revised);
4606                 }
4607         catch (e) 
4608                 {
4609                 showException(e);
4610                 }
4611         if(save)
4612                 {
4613                 displayMessage(config.messages.mainSaved,"file://" + localPath);
4614                 store.setDirty(false);
4615                 }
4616         else
4617                 alert(config.messages.mainFailed);
4618 }
4619
4620 function getLocalPath(originalPath)
4621 {
4622         // Remove any location or query part of the URL
4623         var argPos = originalPath.indexOf("?");
4624         if(argPos != -1)
4625                 originalPath = originalPath.substr(0,argPos);
4626         var hashPos = originalPath.indexOf("#");
4627         if(hashPos != -1)
4628                 originalPath = originalPath.substr(0,hashPos);
4629         // Convert file://localhost/ to file:///
4630         if(originalPath.indexOf("file://localhost/") == 0)
4631                 originalPath = "file://" + originalPath.substr(16);
4632         // Convert to a native file format assuming
4633         // "file:///x:/path/path/path..." - pc local file --> "x:\path\path\path..."
4634         // "file://///server/share/path/path/path..." - FireFox pc network file --> "\\server\share\path\path\path..."
4635         // "file:///path/path/path..." - mac/unix local file --> "/path/path/path..."
4636         // "file://server/share/path/path/path..." - pc network file --> "\\server\share\path\path\path..."
4637         var localPath;
4638         if(originalPath.charAt(9) == ":") // pc local file
4639                 localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
4640         else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
4641                 localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
4642         else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
4643                 localPath = unescape(originalPath.substr(7));
4644         else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
4645                 localPath = unescape(originalPath.substr(5));
4646         else // pc network file
4647                 localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
4648         return localPath;
4649 }
4650
4651 function getBackupPath(localPath)
4652 {
4653         var backSlash = true;
4654         var dirPathPos = localPath.lastIndexOf("\\");
4655         if(dirPathPos == -1)
4656                 {
4657                 dirPathPos = localPath.lastIndexOf("/");
4658                 backSlash = false;
4659                 }
4660         var backupFolder = config.options.txtBackupFolder;
4661         if(!backupFolder || backupFolder == "")
4662                 backupFolder = ".";
4663         var backupPath = localPath.substr(0,dirPathPos) + (backSlash ? "\\" : "/") + backupFolder + localPath.substr(dirPathPos);
4664         backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + "." + (new Date()).convertToYYYYMMDDHHMMSSMMM() + ".html";
4665         return backupPath;
4666 }
4667
4668 function generateRss()
4669 {
4670         var s = [];
4671         var d = new Date();
4672         var u = store.getTiddlerText("SiteUrl");
4673         // Assemble the header
4674         s.push("<" + "?xml version=\"1.0\"?" + ">");
4675         s.push("<rss version=\"2.0\">");
4676         s.push("<channel>");
4677         s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
4678         if(u)
4679                 s.push("<link>" + u.htmlEncode() + "</link>");
4680         s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
4681         s.push("<language>en-us</language>");
4682         s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
4683         s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
4684         s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
4685         s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
4686         s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
4687         // The body
4688         var tiddlers = store.getTiddlers("modified","excludeLists");
4689         var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
4690         for (var t=tiddlers.length-1; t>=n; t--)
4691                 s.push(tiddlers[t].saveToRss(u));
4692         // And footer
4693         s.push("</channel>");
4694         s.push("</rss>");
4695         // Save it all
4696         return s.join("\n");
4697 }
4698
4699
4700 // UTF-8 encoding rules:
4701 // 0x0000 - 0x007F:     0xxxxxxx
4702 // 0x0080 - 0x07FF:     110xxxxx 10xxxxxx
4703 // 0x0800 - 0xFFFF:     1110xxxx 10xxxxxx 10xxxxxx
4704
4705 function convertUTF8ToUnicode(u)
4706 {
4707         if(window.netscape == undefined)
4708                 return manualConvertUTF8ToUnicode(u);
4709         else
4710                 return mozConvertUTF8ToUnicode(u);
4711 }
4712
4713 function manualConvertUTF8ToUnicode(utf)
4714 {
4715         var uni = utf;
4716         var src = 0;
4717         var dst = 0;
4718         var b1, b2, b3;
4719         var c;
4720         while(src < utf.length)
4721                 {
4722                 b1 = utf.charCodeAt(src++);
4723                 if(b1 < 0x80)
4724                         dst++;
4725                 else if(b1 < 0xE0)
4726                         {
4727                         b2 = utf.charCodeAt(src++);
4728                         c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
4729                         uni = uni.substring(0,dst++).concat(c,utf.substr(src));
4730                         }
4731                 else
4732                         {
4733                         b2 = utf.charCodeAt(src++);
4734                         b3 = utf.charCodeAt(src++);
4735                         c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
4736                         uni = uni.substring(0,dst++).concat(c,utf.substr(src));
4737                         }
4738         }
4739         return(uni);
4740 }
4741
4742 function mozConvertUTF8ToUnicode(u)
4743 {
4744         try
4745                 {
4746                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
4747                 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
4748                 converter.charset = "UTF-8";
4749                 }
4750         catch(e)
4751                 {
4752                 return manualConvertUTF8ToUnicode(u);
4753                 } // fallback
4754         var s = converter.ConvertToUnicode(u);
4755         var fin = converter.Finish();
4756         return (fin.length > 0) ? s+fin : s;
4757 }
4758
4759 function convertUnicodeToUTF8(s)
4760 {
4761         if(window.netscape == undefined)
4762                 return manualConvertUnicodeToUTF8(s);
4763         else
4764                 return mozConvertUnicodeToUTF8(s);
4765 }
4766
4767 function manualConvertUnicodeToUTF8(s)
4768 {
4769         var re = /[^\u0000-\u007F]/g ;
4770         return s.replace(re, function($0) {return("&#" + $0.charCodeAt(0).toString() + ";");})
4771 }
4772
4773 function mozConvertUnicodeToUTF8(s)
4774 {
4775         try
4776                 {
4777                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
4778                 var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
4779                 converter.charset = "UTF-8";
4780                 }
4781         catch(e)
4782                 {
4783                 return manualConvertUnicodeToUTF8(s);
4784                 } // fallback
4785         var u = converter.ConvertFromUnicode(s);
4786         var fin = converter.Finish();
4787         if(fin.length > 0)
4788                 return u + fin;
4789         else
4790                 return u;
4791 }
4792
4793 function saveFile(fileUrl, content)
4794 {
4795         var r = null;
4796         if((r == null) || (r == false))
4797                 r = mozillaSaveFile(fileUrl, content);
4798         if((r == null) || (r == false))
4799                 r = ieSaveFile(fileUrl, content);
4800         if((r == null) || (r == false))
4801                 r = javaSaveFile(fileUrl, content);
4802         return(r);
4803 }
4804
4805 function loadFile(fileUrl)
4806 {
4807         var r = null;
4808         if((r == null) || (r == false))
4809                 r = mozillaLoadFile(fileUrl);
4810         if((r == null) || (r == false))
4811                 r = ieLoadFile(fileUrl);
4812         if((r == null) || (r == false))
4813                 r = javaLoadFile(fileUrl);
4814         return(r);
4815 }
4816
4817 // Returns null if it can't do it, false if there's an error, true if it saved OK
4818 function ieSaveFile(filePath, content)
4819 {
4820         try
4821                 {
4822                 var fso = new ActiveXObject("Scripting.FileSystemObject");
4823                 }
4824         catch(e)
4825                 {
4826                 //alert("Exception while attempting to save\n\n" + e.toString());
4827                 return(null);
4828                 }
4829         var file = fso.OpenTextFile(filePath,2,-1,0);
4830         file.Write(content);
4831         file.Close();
4832         return(true);
4833 }
4834
4835 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
4836 function ieLoadFile(filePath)
4837 {
4838         try
4839                 {
4840                 var fso = new ActiveXObject("Scripting.FileSystemObject");
4841                 var file = fso.OpenTextFile(filePath,1);
4842                 var content = file.ReadAll();
4843                 file.Close();
4844                 }
4845         catch(e)
4846                 {
4847                 //alert("Exception while attempting to load\n\n" + e.toString());
4848                 return(null);
4849                 }
4850         return(content);
4851 }
4852
4853 // Returns null if it can't do it, false if there's an error, true if it saved OK
4854 function mozillaSaveFile(filePath, content)
4855 {
4856         if(window.Components)
4857                 try
4858                         {
4859                         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
4860                         var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
4861                         file.initWithPath(filePath);
4862                         if (!file.exists())
4863                                 file.create(0, 0664);
4864                         var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
4865                         out.init(file, 0x20 | 0x02, 00004,null);
4866                         out.write(content, content.length);
4867                         out.flush();
4868                         out.close();
4869                         return(true);
4870                         }
4871                 catch(e)
4872                         {
4873                         //alert("Exception while attempting to save\n\n" + e);
4874                         return(false);
4875                         }
4876         return(null);
4877 }
4878
4879 // Returns null if it can't do it, false if there's an error, or a string of the content if successful
4880 function mozillaLoadFile(filePath)
4881 {
4882         if(window.Components)
4883                 try
4884                         {
4885                         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
4886                         var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
4887                         file.initWithPath(filePath);
4888                         if (!file.exists())
4889                                 return(null);
4890                         var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
4891                         inputStream.init(file, 0x01, 00004, null);
4892                         var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
4893                         sInputStream.init(inputStream);
4894                         return(sInputStream.read(sInputStream.available()));
4895                         }
4896                 catch(e)
4897                         {
4898                         //alert("Exception while attempting to load\n\n" + e);
4899                         return(false);
4900                         }
4901         return(null);
4902 }
4903
4904 function javaUrlToFilename(url)
4905 {
4906         var f = "//localhost";
4907         if(url.indexOf(f) == 0)
4908                 return url.substring(f.length);
4909         var i = url.indexOf(":");
4910         if(i > 0)
4911                 return url.substring(i-1);
4912         return url;
4913 }
4914
4915 function javaSaveFile(filePath, content)
4916 {
4917         try
4918                 {
4919                 if(document.applets["TiddlySaver"])
4920                         return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
4921                 }
4922         catch(e)
4923                 {
4924                 }
4925         try
4926                 {
4927                 var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
4928                 s.print(content);
4929                 s.close();
4930                 }
4931         catch(e)
4932                 {
4933                 return null;
4934                 }
4935         return true;
4936 }
4937
4938 function javaLoadFile(filePath)
4939 {
4940         try
4941                 {
4942         if(document.applets["TiddlySaver"])
4943                 return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
4944                 }
4945         catch(e)
4946                 {
4947                 }
4948         var content = [];
4949         try
4950                 {
4951                 var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
4952                 var line;
4953                 while ((line = r.readLine()) != null)
4954                         content.push(new String(line));
4955                 r.close();
4956                 }
4957         catch(e)
4958                 {
4959                 return null;
4960                 }
4961         return content.join("\n");
4962 }
4963
4964
4965 // ---------------------------------------------------------------------------------
4966 // Remote HTTP requests
4967 // ---------------------------------------------------------------------------------
4968
4969 // Load a file over http
4970 //   url - the source url
4971 //   callback - function to call when there's a response
4972 //   params - parameter object that gets passed to the callback for storing it's state
4973 // Return value is the underlying XMLHttpRequest object, or 'null' if there was an error
4974 // Callback function is called like this:
4975 //   callback(status,params,responseText,xhr)
4976 //     status - true if OK, false if error
4977 //     params - the parameter object provided to loadRemoteFile()
4978 //     responseText - the text of the file
4979 //     xhr - the underlying XMLHttpRequest object
4980 function loadRemoteFile(url,callback,params)
4981 {
4982         // Get an xhr object
4983         var x;
4984         try
4985                 {
4986                 x = new XMLHttpRequest(); // Modern
4987                 }
4988         catch(e)
4989                 {
4990                 try
4991                         {
4992                         x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
4993                         }
4994                 catch (e)
4995                         {
4996                         return null;
4997                         }
4998                 }
4999         // Install callback
5000         x.onreadystatechange = function()
5001                 {
5002                 if (x.readyState == 4)
5003                         {
5004                         if ((x.status == 0 || x.status == 200) && callback)
5005                                 {
5006                                 callback(true,params,x.responseText,url,x);
5007                         }
5008                         else
5009                                 callback(false,params,null,url,x);
5010                         }
5011                 }
5012         // Send request
5013         if(window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
5014                 window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
5015         try
5016                 {
5017                 url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
5018                 x.open("GET",url,true);
5019                 if (x.overrideMimeType)
5020                         x.overrideMimeType("text/html");
5021                 x.send(null);
5022                 }
5023         catch (e)
5024                 {
5025                 alert("Error in send " + e);
5026                 return null;
5027                 }
5028         return x;
5029 }
5030 // ---------------------------------------------------------------------------------
5031 // TiddlyWiki-specific utility functions
5032 // ---------------------------------------------------------------------------------
5033
5034 function createTiddlyButton(theParent,theText,theTooltip,theAction,theClass,theId,theAccessKey)
5035 {
5036         var theButton = document.createElement("a");
5037         if(theAction)
5038                 {
5039                 theButton.onclick = theAction;
5040                 theButton.setAttribute("href","javascript:;");
5041                 }
5042         if(theTooltip)
5043                 theButton.setAttribute("title",theTooltip);
5044         if(theText)
5045                 theButton.appendChild(document.createTextNode(theText));
5046         if(theClass)
5047                 theButton.className = theClass;
5048         else
5049                 theButton.className = "button";
5050         if(theId)
5051                 theButton.id = theId;
5052         if(theParent)
5053                 theParent.appendChild(theButton);
5054         if(theAccessKey)
5055                 theButton.setAttribute("accessKey",theAccessKey);
5056         return(theButton);
5057 }
5058
5059 function createTiddlyLink(place,title,includeText,theClass,isStatic)
5060 {
5061         var text = includeText ? title : null;
5062         var i = getTiddlyLinkInfo(title,theClass)
5063         var btn;
5064         if(isStatic)
5065                 btn = createExternalLink(place,"#" + title);
5066         else
5067                 btn = createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
5068         btn.setAttribute("refresh","link");
5069         btn.setAttribute("tiddlyLink",title);
5070         return(btn);
5071 }
5072
5073 function refreshTiddlyLink(e,title)
5074 {
5075         var i = getTiddlyLinkInfo(title,e.className);
5076         e.className = i.classes;
5077         e.title = i.subTitle;
5078 }
5079
5080 function getTiddlyLinkInfo(title,currClasses)
5081 {
5082         var classes = currClasses ? currClasses.split(" ") : [];
5083         classes.pushUnique("tiddlyLink");
5084         var tiddler = store.fetchTiddler(title);
5085         var subTitle;
5086         if(tiddler)
5087                 {
5088                 subTitle = tiddler.getSubtitle();
5089                 classes.pushUnique("tiddlyLinkExisting");
5090                 classes.remove("tiddlyLinkNonExisting");
5091                 classes.remove("shadow");
5092                 }
5093         else
5094                 {
5095                 classes.remove("tiddlyLinkExisting");
5096                 classes.pushUnique("tiddlyLinkNonExisting");
5097                 if(store.isShadowTiddler(title))
5098                         {
5099                         subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
5100                         classes.pushUnique("shadow");
5101                         }
5102                 else
5103                         {
5104                         subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
5105                         classes.remove("shadow");
5106                         }
5107                 }
5108         return {classes: classes.join(" "), subTitle: subTitle};
5109 }
5110
5111 function createExternalLink(place,url)
5112 {
5113         var theLink = document.createElement("a");
5114         theLink.className = "externalLink";
5115         theLink.href = url;
5116         theLink.title = config.messages.externalLinkTooltip.format([url]);
5117         if(config.options.chkOpenInNewWindow)
5118                 theLink.target = "_blank";
5119         place.appendChild(theLink);
5120         return(theLink);
5121 }
5122
5123 // Event handler for clicking on a tiddly link
5124 function onClickTiddlerLink(e)
5125 {
5126         if (!e) var e = window.event;
5127         var theTarget = resolveTarget(e);
5128         var theLink = theTarget;
5129         var title = null;
5130         do {
5131                 title = theLink.getAttribute("tiddlyLink");
5132                 theLink = theLink.parentNode;
5133         } while(title == null && theLink != null);
5134         if(title)
5135                 {
5136                 var toggling = e.metaKey || e.ctrlKey;
5137                 if(config.options.chkToggleLinks)
5138                         toggling = !toggling;
5139                 var opening;
5140                 if(toggling && document.getElementById("tiddler" + title))
5141                         story.closeTiddler(title,true,e.shiftKey || e.altKey);
5142                 else
5143                         story.displayTiddler(theTarget,title,null,true,e.shiftKey || e.altKey);
5144                 }
5145         clearMessage();
5146         return(false);
5147 }
5148
5149 // Create a button for a tag with a popup listing all the tiddlers that it tags
5150 function createTagButton(place,tag,excludeTiddler)
5151 {
5152         var theTag = createTiddlyButton(place,tag,config.views.wikified.tag.tooltip.format([tag]),onClickTag);
5153         theTag.setAttribute("tag",tag);
5154         if(excludeTiddler)
5155                 theTag.setAttribute("tiddler",excludeTiddler);
5156         return(theTag);
5157 }
5158
5159 // Event handler for clicking on a tiddler tag
5160 function onClickTag(e)
5161 {
5162         if (!e) var e = window.event;
5163         var theTarget = resolveTarget(e);
5164         var popup = Popup.create(this);
5165         var tag = this.getAttribute("tag");
5166         var title = this.getAttribute("tiddler");
5167         if(popup && tag)
5168                 {
5169                 var tagged = store.getTaggedTiddlers(tag);
5170                 var titles = [];
5171                 var li,r;
5172                 for(r=0;r<tagged.length;r++)
5173                         if(tagged[r].title != title)
5174                                 titles.push(tagged[r].title);
5175                 var lingo = config.views.wikified.tag;
5176                 if(titles.length > 0)
5177                         {
5178                         var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
5179                         openAll.setAttribute("tag",tag);
5180                         createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
5181                         for(r=0; r<titles.length; r++)
5182                                 {
5183                                 createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
5184                                 }
5185                         }
5186                 else
5187                         createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
5188                 createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
5189                 var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
5190                 createTiddlyText(h,lingo.openTag.format([tag]));
5191                 }
5192         Popup.show(popup,false);
5193         e.cancelBubble = true;
5194         if (e.stopPropagation) e.stopPropagation();
5195         return(false);
5196 }
5197
5198 // Event handler for 'open all' on a tiddler popup
5199 function onClickTagOpenAll(e)
5200 {
5201         if (!e) var e = window.event;
5202         var tag = this.getAttribute("tag");
5203         var tagged = store.getTaggedTiddlers(tag);
5204         var titles = [];
5205         for(var t=0; t<tagged.length; t++)
5206                 titles.push(tagged[t].title);
5207         story.displayTiddlers(this,titles);
5208         return(false);
5209 }
5210
5211 function onClickError(e)
5212 {
5213         if (!e) var e = window.event;
5214         var popup = Popup.create(this);
5215         var lines = this.getAttribute("errorText").split("\n");
5216         for(var t=0; t<lines.length; t++)
5217                 createTiddlyElement(popup,"li",null,null,lines[t]);
5218         Popup.show(popup,false);
5219         e.cancelBubble = true;
5220         if (e.stopPropagation) e.stopPropagation();
5221         return false;
5222 }
5223
5224 function createTiddlyDropDown(place,onchange,options)
5225 {
5226         var sel = createTiddlyElement(place,"select");
5227         sel.onchange = onchange;
5228         for(var t=0; t<options.length; t++)
5229                 {
5230                 var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
5231                 e.value = options[t].name;
5232                 }
5233 }
5234
5235 function createTiddlyError(place,title,text)
5236 {
5237         var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
5238         if (text) btn.setAttribute("errorText",text);
5239 }
5240
5241 function merge(dst,src,preserveExisting)
5242 {
5243         for (p in src)
5244                 if (!preserveExisting || dst[p] === undefined)
5245                         dst[p] = src[p];
5246         return dst;
5247 }
5248
5249 // Returns a string containing the description of an exception, optionally prepended by a message
5250 function exceptionText(e, message)
5251 {
5252         var s = e.description ? e.description : e.toString();
5253         return message ? "%0:\n%1".format([message, s]) : s;
5254 }
5255
5256 // Displays an alert of an exception description with optional message
5257 function showException(e, message)
5258 {
5259         alert(exceptionText(e, message));
5260 }
5261
5262 // ---------------------------------------------------------------------------------
5263 // Animation engine
5264 // ---------------------------------------------------------------------------------
5265
5266 function Animator()
5267 {
5268         this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
5269         this.timerID = 0; // ID of the timer used for animating
5270         this.animations = []; // List of animations in progress
5271         return this;
5272 }
5273
5274 // Start animation engine
5275 Animator.prototype.startAnimating = function() // Variable number of arguments
5276 {
5277         for(var t=0; t<arguments.length; t++)
5278                 this.animations.push(arguments[t]);
5279         if(this.running == 0)
5280                 {
5281                 var me = this;
5282                 this.timerID = window.setInterval(function() {me.doAnimate(me);},5);
5283                 }
5284         this.running += arguments.length;
5285 }
5286
5287 // Perform an animation engine tick, calling each of the known animation modules
5288 Animator.prototype.doAnimate = function(me)
5289 {
5290         var a = 0;
5291         while(a < me.animations.length)
5292                 {
5293                 var animation = me.animations[a];
5294                 if(animation.tick())
5295                         a++;
5296                 else
5297                         {
5298                         me.animations.splice(a,1);
5299                         if(--me.running == 0)
5300                                 window.clearInterval(me.timerID);
5301                         }
5302                 }
5303 }
5304
5305 // Map a 0..1 value to 0..1, but slow down at the start and end
5306 Animator.slowInSlowOut = function(progress)
5307 {
5308         return(1-((Math.cos(progress * Math.PI)+1)/2));
5309 }
5310
5311 // ---------------------------------------------------------------------------------
5312 // Cascade animation
5313 // ---------------------------------------------------------------------------------
5314
5315 function Cascade(text,startElement,targetElement,slowly)
5316 {
5317         var winWidth = findWindowWidth();
5318         var winHeight = findWindowHeight();
5319         this.elements = [];
5320         this.startElement = startElement;
5321         this.startLeft = findPosX(this.startElement);
5322         this.startTop = findPosY(this.startElement);
5323         this.startWidth = Math.min(this.startElement.offsetWidth,winWidth);
5324         this.startHeight = Math.min(this.startElement.offsetHeight,winHeight);
5325         this.targetElement = targetElement;
5326         targetElement.style.position = "relative";
5327         targetElement.style.zIndex = 2;
5328         this.targetLeft = findPosX(this.targetElement);
5329         this.targetTop = findPosY(this.targetElement);
5330         this.targetWidth = Math.min(this.targetElement.offsetWidth,winWidth);
5331         this.targetHeight = Math.min(this.targetElement.offsetHeight,winHeight);
5332         this.progress = -1;
5333         this.steps = slowly ? config.cascadeSlow : config.cascadeFast;
5334         this.text = text;
5335         this.tick();
5336         return this;
5337 }
5338
5339 Cascade.prototype.tick = function()
5340 {
5341         this.progress++;
5342         if(this.progress >= this.steps)
5343                 {
5344                 while(this.elements.length > 0)
5345                         this.removeTail();
5346                 this.targetElement.style.position = "static";
5347                 this.targetElement.style.zIndex = "";
5348                 return false;
5349                 }
5350         else
5351                 {
5352                 if(this.elements.length > 0 && this.progress > config.cascadeDepth)
5353                         this.removeTail();
5354                 if(this.progress < (this.steps - config.cascadeDepth))
5355                         {
5356                         var f = Animator.slowInSlowOut(this.progress/(this.steps - config.cascadeDepth - 1));
5357                         var e = createTiddlyElement(document.body,"div",null,"cascade",this.text);
5358                         e.style.zIndex = 1;
5359                         e.style.left = this.startLeft + (this.targetLeft-this.startLeft) * f + "px";
5360                         e.style.top = this.startTop + (this.targetTop-this.startTop) * f + "px";
5361                         e.style.width = this.startWidth + (this.targetWidth-this.startWidth) * f + "px";
5362                         e.style.height = this.startHeight + (this.targetHeight-this.startHeight) * f + "px";
5363                         e.style.display = "block";
5364                         this.elements.push(e);
5365                         }
5366                 return true;
5367                 }
5368 }
5369
5370 Cascade.prototype.removeTail = function()
5371 {
5372         var e = this.elements[0];
5373         e.parentNode.removeChild(e);
5374         this.elements.shift();
5375 }
5376
5377 // ---------------------------------------------------------------------------------
5378 // Scroller animation
5379 // ---------------------------------------------------------------------------------
5380
5381 function Scroller(targetElement,slowly)
5382 {
5383         this.targetElement = targetElement;
5384         this.startScroll = findScrollY();
5385         this.targetScroll = ensureVisible(targetElement);
5386         this.progress = 0;
5387         this.step = slowly ? config.animSlow : config.animFast;
5388         return this;
5389 }
5390
5391 Scroller.prototype.tick = function()
5392 {
5393         this.progress += this.step;
5394         if(this.progress > 1)
5395                 {
5396                 window.scrollTo(0,this.targetScroll);
5397                 return false;
5398                 }
5399         else
5400                 {
5401                 var f = Animator.slowInSlowOut(this.progress);
5402                 window.scrollTo(0,this.startScroll + (this.targetScroll-this.startScroll) * f);
5403                 return true;
5404                 }
5405 }
5406
5407 // ---------------------------------------------------------------------------------
5408 // Slider animation
5409 // ---------------------------------------------------------------------------------
5410
5411 // deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
5412 function Slider(element,opening,slowly,deleteMode)
5413 {
5414         this.element = element;
5415         element.style.display = "block";
5416         this.deleteMode = deleteMode;
5417         this.element.style.height = "auto";
5418         this.realHeight = element.offsetHeight;
5419         this.opening = opening;
5420         this.step = slowly ? config.animSlow : config.animFast;
5421         if(opening)
5422                 {
5423                 this.progress = 0;
5424                 element.style.height = "0px";
5425                 element.style.display = "block";
5426                 }
5427         else
5428                 {
5429                 this.progress = 1;
5430                 this.step = -this.step;
5431                 }
5432         element.style.overflow = "hidden";
5433         return this;
5434 }
5435
5436 Slider.prototype.stop = function()
5437 {
5438         if(this.opening)
5439                 {
5440                 this.element.style.height = "auto";
5441                 this.element.style.opacity = 1;
5442                 this.element.style.filter = "alpha(opacity:100)";
5443                 }
5444         else
5445                 {
5446                 switch(this.deleteMode)
5447                         {
5448                         case "none":
5449                                 this.element.style.display = "none";
5450                                 break;
5451                         case "all":
5452                                 this.element.parentNode.removeChild(this.element);
5453                                 break;
5454                         case "children":
5455                                 removeChildren(this.element);
5456                                 break;
5457                         }
5458                 }
5459 }
5460
5461 Slider.prototype.tick = function()
5462 {
5463         this.progress += this.step;
5464         if(this.progress < 0 || this.progress > 1)
5465                 {
5466                 this.stop();
5467                 return false;
5468                 }
5469         else
5470                 {
5471                 var f = Animator.slowInSlowOut(this.progress);
5472                 var h = this.realHeight * f;
5473                 this.element.style.height = h + "px";
5474                 this.element.style.opacity = f;
5475                 this.element.style.filter = "alpha(opacity:" + f * 100 +")";
5476                 return true;
5477                 }
5478 }
5479
5480 // ---------------------------------------------------------------------------------
5481 // Popup menu
5482 // ---------------------------------------------------------------------------------
5483
5484 var Popup = {
5485         stack: [] // Array of objects with members root: and popup:
5486         };
5487
5488 Popup.create = function(root)
5489 {
5490         Popup.remove();
5491         var popup = createTiddlyElement(document.body,"ol","popup","popup");
5492         Popup.stack.push({root: root, popup: popup});
5493         return popup;
5494 }
5495
5496 Popup.onDocumentClick = function(e)
5497 {
5498         if (!e) var e = window.event;
5499         var target = resolveTarget(e);
5500         if(e.eventPhase == undefined)
5501                 Popup.remove();
5502         else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
5503                 Popup.remove();
5504         return true;
5505 }
5506
5507 Popup.show = function(unused,slowly)
5508 {
5509         var curr = Popup.stack[Popup.stack.length-1];
5510         var rootLeft = findPosX(curr.root);
5511         var rootTop = findPosY(curr.root);
5512         var rootHeight = curr.root.offsetHeight;
5513         var popupLeft = rootLeft;
5514         var popupTop = rootTop + rootHeight;
5515         var popupWidth = curr.popup.offsetWidth;
5516         var winWidth = findWindowWidth();
5517         if(popupLeft + popupWidth > winWidth)
5518                 popupLeft = winWidth - popupWidth;
5519         curr.popup.style.left = popupLeft + "px";
5520         curr.popup.style.top = popupTop + "px";
5521         curr.popup.style.display = "block";
5522         addClass(curr.root,"highlight");
5523         if(anim && config.options.chkAnimate)
5524                 anim.startAnimating(new Scroller(curr.popup,slowly));
5525         else
5526                 window.scrollTo(0,ensureVisible(curr.popup));
5527 }
5528
5529 Popup.remove = function()
5530 {
5531         if(Popup.stack.length > 0)
5532                 {
5533                 Popup.removeFrom(0);
5534                 }
5535 }
5536
5537 Popup.removeFrom = function(from)
5538 {
5539         for(var t=Popup.stack.length-1; t>=from; t--)
5540                 {
5541                 var p = Popup.stack[t];
5542                 removeClass(p.root,"highlight");
5543                 p.popup.parentNode.removeChild(p.popup);
5544                 }
5545         Popup.stack = Popup.stack.slice(0,from);
5546 }
5547
5548 // ---------------------------------------------------------------------------------
5549 // ListView gadget
5550 // ---------------------------------------------------------------------------------
5551
5552 var ListView = {};
5553
5554 // Create a listview
5555 //   place - where in the DOM tree to insert the listview
5556 //   listObject - array of objects to be included in the listview
5557 //   listTemplate - template for the listview
5558 //   callback - callback for a command being selected
5559 //   className - optional classname for the <table> element
5560 ListView.create = function(place,listObject,listTemplate,callback,className)
5561 {
5562         var table = createTiddlyElement(place,"table",null,className ? className : "listView");
5563         var thead = createTiddlyElement(table,"thead");
5564         var r = createTiddlyElement(thead,"tr");
5565         for(var t=0; t<listTemplate.columns.length; t++)
5566                 {
5567                 var columnTemplate = listTemplate.columns[t];
5568                 var c = createTiddlyElement(r,"th");
5569                 var colType = ListView.columnTypes[columnTemplate.type];
5570                 if(colType && colType.createHeader)
5571                         colType.createHeader(c,columnTemplate,t);
5572                 }
5573         var tbody = createTiddlyElement(table,"tbody");
5574         for(var rc=0; rc<listObject.length; rc++)
5575                 {
5576                 rowObject = listObject[rc];
5577                 r = createTiddlyElement(tbody,"tr");
5578                 for(var c=0; c<listTemplate.rowClasses.length; c++)
5579                         {
5580                         if(rowObject[listTemplate.rowClasses[c].field])
5581                                 addClass(r,listTemplate.rowClasses[c].className);
5582                         }
5583                 rowObject.rowElement = rowObject;
5584                 rowObject.colElements = {};
5585                 for(var cc=0; cc<listTemplate.columns.length; cc++)
5586                         {
5587                         var c = createTiddlyElement(r,"td");
5588                         var columnTemplate = listTemplate.columns[cc];
5589                         var field = columnTemplate.field;
5590                         var colType = ListView.columnTypes[columnTemplate.type];
5591                         if(colType && colType.createItem)
5592                                 colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
5593                         rowObject.colElements[field] = c;
5594                         }
5595                 }
5596         if(callback && listTemplate.actions)
5597                 createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
5598         if(callback && listTemplate.buttons)
5599                 {
5600                 for(t=0; t<listTemplate.buttons.length; t++)
5601                         {
5602                         var a = listTemplate.buttons[t];
5603                         if(a && a.name != "")
5604                                 createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
5605                         }
5606                 }
5607         return table;
5608 }
5609
5610 ListView.getCommandHandler = function(callback,name,allowEmptySelection)
5611 {
5612         return function(e)
5613                 {
5614                 var view = findRelated(this,"TABLE",null,"previousSibling");
5615                 var tiddlers = [];
5616                 ListView.forEachSelector(view,function(e,rowName) {
5617                                         if(e.checked)
5618                                                 tiddlers.push(rowName);
5619                                         });
5620                 if(tiddlers.length == 0 && !allowEmptySelection)
5621                         alert(config.messages.nothingSelected);
5622                 else
5623                         {
5624                         if(this.nodeName.toLowerCase() == "select")
5625                                 {
5626                                 callback(view,this.value,tiddlers);
5627                                 this.selectedIndex = 0;
5628                                 }
5629                         else
5630                                 callback(view,name,tiddlers);
5631                         }
5632                 };
5633 }
5634
5635 // Invoke a callback for each selector checkbox in the listview
5636 //   view - <table> element of listView
5637 //   callback(checkboxElement,rowName)
5638 //     where
5639 //       checkboxElement - DOM element of checkbox
5640 //       rowName - name of this row as assigned by the column template
5641 //   result: true if at least one selector was checked
5642 ListView.forEachSelector = function(view,callback)
5643 {
5644         var checkboxes = view.getElementsByTagName("input");
5645         var hadOne = false;
5646         for(var t=0; t<checkboxes.length; t++)
5647                 {
5648                 var cb = checkboxes[t];
5649                 if(cb.getAttribute("type") == "checkbox")
5650                         {
5651                         var rn = cb.getAttribute("rowName");
5652                         if(rn)
5653                                 {
5654                                 callback(cb,rn);
5655                                 hadOne = true;
5656                                 }
5657                         }
5658                 }
5659         return hadOne;
5660 }
5661
5662 ListView.columnTypes = {};
5663
5664 ListView.columnTypes.String = {
5665         createHeader: function(place,columnTemplate,col)
5666                 {
5667                         createTiddlyText(place,columnTemplate.title);
5668                 },
5669         createItem: function(place,listObject,field,columnTemplate,col,row)
5670                 {
5671                         var v = listObject[field];
5672                         if(v != undefined)
5673                                 createTiddlyText(place,v);
5674                 }
5675 };
5676
5677 ListView.columnTypes.Date = {
5678         createHeader: ListView.columnTypes.String.createHeader,
5679         createItem: function(place,listObject,field,columnTemplate,col,row)
5680                 {
5681                         var v = listObject[field];
5682                         if(v != undefined)
5683                                 createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
5684                 }
5685 };
5686
5687 ListView.columnTypes.StringList = {
5688         createHeader: ListView.columnTypes.String.createHeader,
5689         createItem: function(place,listObject,field,columnTemplate,col,row)
5690                 {
5691                         var v = listObject[field];
5692                         if(v != undefined)
5693                                 {
5694                                 for(var t=0; t<v.length; t++)
5695                                         {
5696                                         createTiddlyText(place,v[t]);
5697                                         createTiddlyElement(place,"br");
5698                                         }
5699                                 }
5700                 }
5701 };
5702
5703 ListView.columnTypes.Selector = {
5704         createHeader: function(place,columnTemplate,col)
5705                 {
5706                         createTiddlyCheckbox(place,null,false,this.onHeaderChange);
5707                 },
5708         createItem: function(place,listObject,field,columnTemplate,col,row)
5709                 {
5710                         var e = createTiddlyCheckbox(place,null,listObject[field],null);
5711                         e.setAttribute("rowName",listObject[columnTemplate.rowName]);
5712                 },
5713         onHeaderChange: function(e)
5714                 {
5715                         var state = this.checked;
5716                         var view = findRelated(this,"TABLE");
5717                         if(!view)
5718                                 return;
5719                         ListView.forEachSelector(view,function(e,rowName) {
5720                                                                 e.checked = state;
5721                                                         });
5722                 }
5723 };
5724
5725 ListView.columnTypes.Tags = {
5726         createHeader: ListView.columnTypes.String.createHeader,
5727         createItem: function(place,listObject,field,columnTemplate,col,row)
5728                 {
5729                         var tags = listObject[field];
5730                         createTiddlyText(place,String.encodeTiddlyLinkList(tags));
5731                 }
5732 };
5733
5734 ListView.columnTypes.Boolean = {
5735         createHeader: ListView.columnTypes.String.createHeader,
5736         createItem: function(place,listObject,field,columnTemplate,col,row)
5737                 {
5738                         if(listObject[field] == true)
5739                                 createTiddlyText(place,columnTemplate.trueText);
5740                         if(listObject[field] == false)
5741                                 createTiddlyText(place,columnTemplate.falseText);
5742                 }
5743 };
5744
5745 ListView.columnTypes.TagCheckbox = {
5746         createHeader: ListView.columnTypes.String.createHeader,
5747         createItem: function(place,listObject,field,columnTemplate,col,row)
5748                 {
5749                         var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
5750                         e.setAttribute("tiddler",listObject.title);
5751                         e.setAttribute("tag",columnTemplate.tag);
5752                 },
5753         onChange : function(e)
5754                 {
5755                         var tag = this.getAttribute("tag");
5756                         var tiddler = this.getAttribute("tiddler");
5757                         store.setTiddlerTag(tiddler,this.checked,tag);
5758                 }
5759 };
5760
5761 ListView.columnTypes.TiddlerLink = {
5762         createHeader: ListView.columnTypes.String.createHeader,
5763         createItem: function(place,listObject,field,columnTemplate,col,row)
5764                 {
5765                         var v = listObject[field];
5766                         if(v != undefined)
5767                                 {
5768                                 var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
5769                                 createTiddlyText(link,listObject[field]);
5770                                 }
5771                 }
5772 };
5773 // ---------------------------------------------------------------------------------
5774 // Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
5775 // ---------------------------------------------------------------------------------
5776
5777 // Clamp a number to a range
5778 Number.prototype.clamp = function(min,max)
5779 {
5780         var c = this;
5781         if(c < min)
5782                 c = min;
5783         if(c > max)
5784                 c = max;
5785         return c;
5786 }
5787
5788 // Add indexOf function if browser does not support it
5789 if(!Array.indexOf) {
5790 Array.prototype.indexOf = function(item,from)
5791 {
5792         if(!from)
5793                 from = 0;
5794         for(var i=from; i<this.length; i++)
5795                 if(this[i] === item)
5796                         return i;
5797         return -1;
5798 }}
5799
5800 // Find an entry in a given field of the members of an array
5801 Array.prototype.findByField = function(field,value)
5802 {
5803         for(var t=0; t<this.length; t++)
5804                 if(this[t][field] == value)
5805                         return t;
5806         return null;
5807 }
5808
5809 // Return whether an entry exists in an array
5810 Array.prototype.contains = function(item)
5811 {
5812         return this.indexOf(item) != -1;
5813 };
5814
5815 // Adds, removes or toggles a particular value within an array
5816 //  value - value to add
5817 //  mode - +1 to add value, -1 to remove value, 0 to toggle it
5818 Array.prototype.setItem = function(value,mode)
5819 {
5820         var p = this.indexOf(value);
5821         if(mode == 0)
5822                 mode = (p == -1) ? +1 : -1;
5823         if(mode == +1)
5824                 {
5825                 if(p == -1)
5826                         this.push(value);
5827                 }
5828         else if(mode == -1)
5829                 {
5830                 if(p != -1)
5831                         this.splice(p,1);
5832                 }
5833 }
5834
5835 // Return whether one of a list of values exists in an array
5836 Array.prototype.containsAny = function(items)
5837 {
5838         for(var i=0; i<items.length; i++)
5839                 if (this.indexOf(items[i]) != -1)
5840                         return true;
5841         return false;
5842 };
5843
5844 // Return whether all of a list of values exists in an array
5845 Array.prototype.containsAll = function(items)
5846 {
5847         for (var i = 0; i<items.length; i++)
5848                 if (this.indexOf(items[i]) == -1)
5849                         return false;
5850         return true;
5851 };
5852
5853 // 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
5854 Array.prototype.pushUnique = function(item,unique)
5855 {
5856         if(unique != undefined && unique == false)
5857                 this.push(item);
5858         else
5859                 {
5860                 if(this.indexOf(item) == -1)
5861                         this.push(item);
5862                 }
5863 }
5864
5865 Array.prototype.remove = function(item)
5866 {
5867         var p = this.indexOf(item);
5868         if(p != -1)
5869                 this.splice(p,1);
5870 }
5871
5872 // Get characters from the right end of a string
5873 String.prototype.right = function(n)
5874 {
5875         if(n < this.length)
5876                 return this.slice(this.length-n);
5877         else
5878                 return this;
5879 }
5880
5881 // Trim whitespace from both ends of a string
5882 String.prototype.trim = function()
5883 {
5884         return this.replace(/^\s*|\s*$/g,"");
5885 }
5886
5887 // Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
5888 String.prototype.unDash = function()
5889 {
5890         var s = this.split("-");
5891         if(s.length > 1)
5892                 for(var t=1; t<s.length; t++)
5893                         s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
5894         return s.join("");
5895 }
5896
5897 // Substitute substrings from an array into a format string that includes '%1'-type specifiers
5898 String.prototype.format = function(substrings)
5899 {
5900         var subRegExp = /(?:%(\d+))/mg;
5901         var currPos = 0;
5902         var r = [];
5903         do {
5904                 var match = subRegExp.exec(this);
5905                 if(match && match[1])
5906                         {
5907                         if(match.index > currPos)
5908                                 r.push(this.substring(currPos,match.index));
5909                         r.push(substrings[parseInt(match[1])]);
5910                         currPos = subRegExp.lastIndex;
5911                         }
5912         } while(match);
5913         if(currPos < this.length)
5914                 r.push(this.substring(currPos,this.length));
5915         return r.join("");
5916 }
5917
5918 // Escape any special RegExp characters with that character preceded by a backslash
5919 String.prototype.escapeRegExp = function()
5920 {
5921         var s = "\\^$*+?()=!|,{}[].";
5922         var c = this;
5923         for(var t=0; t<s.length; t++)
5924                 c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
5925         return c;
5926 }
5927
5928 // Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
5929 String.prototype.escapeLineBreaks = function()
5930 {
5931         return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
5932 }
5933
5934 // Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
5935 String.prototype.unescapeLineBreaks = function()
5936 {
5937         return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
5938 }
5939
5940 // Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
5941 String.prototype.htmlEncode = function()
5942 {
5943         return(this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;"));
5944 }
5945
5946 // Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
5947 String.prototype.htmlDecode = function()
5948 {
5949         return(this.replace(/&amp;/mg,"&").replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\""));
5950 }
5951
5952 // Parse a space-separated string of name:value parameters where:
5953 //   - the name or the value can be optional (in which case separate defaults are used instead)
5954 //     - in case of ambiguity, a lone word is taken to be a value
5955 //     - if 'cascadeDefaults' is set to true, then the defaults are modified by updated by each specified name or value
5956 //     - name prefixes are not allowed if the 'noNames' parameter is true
5957 //   - if both the name and value are present they must be separated by a colon
5958 //   - the name and the value may both be quoted with single- or double-quotes, double-square brackets
5959 //   - names or values quoted with {{double-curly braces}} are evaluated as a JavaScript expression
5960 //     - as long as the 'allowEval' parameter is true
5961 // The result is an array of objects:
5962 //   result[0] = object with a member for each parameter name, value of that member being an array of values
5963 //   result[1..n] = one object for each parameter, with 'name' and 'value' members
5964 String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
5965 {
5966         var parseToken = function(match,p)
5967                 {
5968                 var n;
5969                 if(match[p]) // Double quoted
5970                         n = match[p];
5971                 else if(match[p+1]) // Single quoted
5972                         n = match[p+1];
5973                 else if(match[p+2]) // Double-square-bracket quoted
5974                         n = match[p+2];
5975                 else if(match[p+3]) // Double-brace quoted
5976                         try
5977                                 {
5978                                 n = match[p+3];
5979                                 if(allowEval)
5980                                         n = window.eval(n);
5981                                 }
5982                         catch(e)
5983                                 {
5984                                 throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(e);
5985                                 }
5986                 else if(match[p+4]) // Unquoted
5987                         n = match[p+4];
5988                 else if(match[p+5]) // empty quote
5989                         n = "";
5990                 return n;
5991                 };
5992         var r = [{}];
5993         var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
5994         var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
5995         var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
5996         var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
5997         var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
5998         var emptyQuote = "((?:\"\")|(?:''))";
5999         var skipSpace = "(?:\\s*)";
6000         var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
6001         var re = noNames
6002                 ? new RegExp(token,"mg")
6003                 : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
6004         var params = [];
6005         do {
6006                 var match = re.exec(this);
6007                 if(match)
6008                         {
6009                         var n = parseToken(match,1);
6010                         if(noNames)
6011                                 r.push({name: "", value: n});
6012                         else
6013                                 {
6014                                 var v = parseToken(match,8);
6015                                 if(v == null && defaultName)
6016                                         {
6017                                         v = n;
6018                                         n = defaultName;
6019                                         }
6020                                 else if(v == null && defaultValue)
6021                                         v = defaultValue;
6022                                 r.push({name: n, value: v});
6023                                 if(cascadeDefaults)
6024                                         {
6025                                         defaultName = n;
6026                                         defaultValue = v;
6027                                         }
6028                                 }
6029                         }
6030         } while(match);
6031         // Summarise parameters into first element
6032         for(var t=1; t<r.length; t++)
6033                 {
6034                 if(r[0][r[t].name])
6035                         r[0][r[t].name].push(r[t].value);
6036                 else
6037                         r[0][r[t].name] = [r[t].value];
6038                 }
6039         return r;
6040 }
6041
6042 // Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
6043 // [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
6044 // an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
6045 String.prototype.readMacroParams = function()
6046 {
6047         var p = this.parseParams("list",null,true,true);
6048         var n = [];
6049         for(var t=1; t<p.length; t++)
6050                 n.push(p[t].value);
6051         return n;
6052 }
6053
6054 // Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
6055 String.prototype.readBracketedList = function(unique)
6056 {
6057         var p = this.parseParams("list",null,false,true);
6058         var n = [];
6059         for(var t=1; t<p.length; t++)
6060                 n.pushUnique(p[t].value,unique);
6061         return n;
6062 }
6063
6064 // Returns array with start and end index of chunk between given start and end marker, or undefined.
6065 String.prototype.getChunkRange = function(start,end) 
6066 {
6067         var s = this.indexOf(start);
6068         if(s != -1)
6069                 {
6070                 s += start.length;
6071                 var e = this.indexOf(end,s);
6072                 if(e != -1)
6073                         return [s, e];
6074                 }
6075 }
6076
6077 // Replace a chunk of a string given start and end markers
6078 String.prototype.replaceChunk = function(start,end,sub)
6079 {
6080         var r = this.getChunkRange(start,end);
6081         return r 
6082                 ? this.substring(0,r[0]) + sub + this.substring(r[1])
6083                 : this;
6084 }
6085
6086 // Returns a chunk of a string between start and end markers, or undefined
6087 String.prototype.getChunk = function(start,end)
6088 {
6089         var r = this.getChunkRange(start,end);
6090         if (r)
6091                 return this.substring(r[0],r[1]);
6092 }
6093
6094
6095 // Static method to bracket a string with double square brackets if it contains a space
6096 String.encodeTiddlyLink = function(title)
6097 {
6098         if(title.indexOf(" ") == -1)
6099                 return(title);
6100         else
6101                 return("[[" + title + "]]");
6102 }
6103
6104 // Static method to encodeTiddlyLink for every item in an array and join them with spaces
6105 String.encodeTiddlyLinkList = function(list)
6106 {
6107         if(list)
6108                 {
6109                 var results = [];
6110                 for(var t=0; t<list.length; t++)
6111                         results.push(String.encodeTiddlyLink(list[t]));
6112                 return results.join(" ");
6113                 }
6114         else
6115                 return "";
6116 }
6117
6118 // Static method to left-pad a string with 0s to a certain width
6119 String.zeroPad = function(n,d)
6120 {
6121         var s = n.toString();
6122         if(s.length < d)
6123                 s = "000000000000000000000000000".substr(0,d-s.length) + s;
6124         return(s);
6125 }
6126
6127 String.prototype.startsWith = function(prefix) 
6128 {
6129         return !prefix || this.substring(0,prefix.length) == prefix;
6130 }
6131
6132 // Returns the first value of the given named parameter.
6133 //#
6134 //# @param params
6135 //#         as returned by parseParams or null/undefined
6136 //# @return [may be null/undefined]
6137 //#
6138 function getParam(params, name, defaultValue) {
6139         if (!params)
6140                 return defaultValue;
6141         var p = params[0][name];
6142         return p ? p[0] : defaultValue;
6143 }
6144
6145 // Returns the first value of the given boolean named parameter.
6146 //#
6147 //# @param params
6148 //#         as returned by parseParams or null/undefined
6149 //#
6150 function getFlag(params, name, defaultValue) {
6151         return !!getParam(params, name, defaultValue);
6152
6153         
6154 // Substitute date components into a string
6155 Date.prototype.formatString = function(template)
6156 {
6157         var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
6158         t = t.replace(/hh12/g,this.getHours12());
6159         t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
6160         t = t.replace(/hh/g,this.getHours());
6161         t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
6162         t = t.replace(/mm/g,this.getMinutes());
6163         t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
6164         t = t.replace(/ss/g,this.getSeconds());
6165         t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
6166         t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
6167         t = t.replace(/wYYYY/g,this.getYearForWeekNo());
6168         t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
6169         t = t.replace(/YYYY/g,this.getFullYear());
6170         t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
6171         t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
6172         t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
6173         t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
6174         t = t.replace(/MM/g,this.getMonth()+1);
6175         t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
6176         t = t.replace(/WW/g,this.getWeek());
6177         t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
6178         t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
6179         t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
6180         t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
6181         t = t.replace(/DD/g,this.getDate());
6182         return t;
6183 }
6184
6185 Date.prototype.getWeek = function()
6186 {
6187         var dt = new Date(this.getTime());
6188         var d = dt.getDay();
6189         if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
6190         dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
6191         var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000); 
6192         return Math.floor(n/7)+1;
6193 }
6194
6195 Date.prototype.getYearForWeekNo = function()
6196 {
6197         var dt = new Date(this.getTime());
6198         var d = dt.getDay();
6199         if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
6200         dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
6201         return dt.getFullYear();
6202 }
6203
6204 Date.prototype.getHours12 = function()
6205 {
6206         var h = this.getHours();
6207         return h > 12 ? h-12 : ( h > 0 ? h : 12 );
6208 }
6209
6210 Date.prototype.getAmPm = function()
6211 {
6212         return this.getHours() >= 12 ? "pm" : "am";
6213 }
6214
6215 Date.prototype.daySuffix = function()
6216 {
6217         var num = this.getDate();
6218         if (num >= 11 && num <= 13) return "th";
6219         else if (num.toString().substr(-1)=="1") return "st";
6220         else if (num.toString().substr(-1)=="2") return "nd";
6221         else if (num.toString().substr(-1)=="3") return "rd";
6222         return "th";
6223 }
6224
6225 // Convert a date to local YYYYMMDDHHMM string format
6226 Date.prototype.convertToLocalYYYYMMDDHHMM = function()
6227 {
6228         return(String.zeroPad(this.getFullYear(),4) + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2));
6229 }
6230
6231 // Convert a date to UTC YYYYMMDDHHMM string format
6232 Date.prototype.convertToYYYYMMDDHHMM = function()
6233 {
6234         return(String.zeroPad(this.getUTCFullYear(),4) + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2));
6235 }
6236
6237 // Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
6238 Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
6239 {
6240         return(String.zeroPad(this.getUTCFullYear(),4) + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + "." + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2) + String.zeroPad(this.getUTCSeconds(),2) + String.zeroPad(this.getUTCMilliseconds(),4));
6241 }
6242
6243 // Static method to create a date from a UTC YYYYMMDDHHMM format string
6244 Date.convertFromYYYYMMDDHHMM = function(d)
6245 {
6246         var theDate = new Date(Date.UTC(parseInt(d.substr(0,4),10),
6247                                                         parseInt(d.substr(4,2),10)-1,
6248                                                         parseInt(d.substr(6,2),10),
6249                                                         parseInt(d.substr(8,2),10),
6250                                                         parseInt(d.substr(10,2),10),0,0));
6251         return(theDate);
6252 }
6253
6254 // ---------------------------------------------------------------------------------
6255 // Crypto functions and associated conversion routines
6256 // ---------------------------------------------------------------------------------
6257
6258 // Crypto "namespace"
6259 function Crypto() {}
6260
6261 // Convert a string to an array of big-endian 32-bit words
6262 Crypto.strToBe32s = function(str)
6263 {
6264         var be = Array();
6265         var len = Math.floor(str.length/4);
6266         var i, j;
6267         for(i=0, j=0; i<len; i++, j+=4)
6268                 {
6269                 be[i] = ((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
6270                 }
6271         while (j<str.length)
6272                 {
6273                 be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
6274                 j++;
6275                 }
6276         return be;
6277 }
6278
6279 // Convert an array of big-endian 32-bit words to a string
6280 Crypto.be32sToStr = function(be)
6281 {
6282         var str = "";
6283         for(var i=0;i<be.length*32;i+=8)
6284                 str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
6285         return str;
6286 }
6287
6288 // Convert an array of big-endian 32-bit words to a hex string
6289 Crypto.be32sToHex = function(be)
6290 {
6291         var hex = "0123456789ABCDEF";
6292         var str = "";
6293         for(var i=0;i<be.length*4;i++)
6294                 str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
6295         return str;
6296 }
6297
6298 // Return, in hex, the SHA-1 hash of a string
6299 Crypto.hexSha1Str = function(str)
6300 {
6301         return Crypto.be32sToHex(Crypto.sha1Str(str));
6302 }
6303
6304 // Return the SHA-1 hash of a string
6305 Crypto.sha1Str = function(str)
6306 {
6307         return Crypto.sha1(Crypto.strToBe32s(str),str.length);
6308 }
6309
6310 // Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
6311 Crypto.sha1 = function(x,blen)
6312 {
6313         // Add 32-bit integers, wrapping at 32 bits
6314         //# Uses 16-bit operations internally to work around bugs in some JavaScript interpreters.
6315         add32 = function(a,b)
6316         {
6317                 var lsw = (a&0xFFFF)+(b&0xFFFF);
6318                 var msw = (a>>16)+(b>>16)+(lsw>>16);
6319                 return (msw<<16)|(lsw&0xFFFF);
6320         };
6321         // Add five 32-bit integers, wrapping at 32 bits
6322         //# Uses 16-bit operations internally to work around bugs in some JavaScript interpreters.
6323         add32x5 = function(a,b,c,d,e)
6324         {
6325                 var lsw = (a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
6326                 var msw = (a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
6327                 return (msw<<16)|(lsw&0xFFFF);
6328         };
6329         // Bitwise rotate left a 32-bit integer by 1 bit
6330         rol32 = function(n)
6331         {
6332                 return (n>>>31)|(n<<1);
6333         };
6334
6335         var len = blen*8;
6336         // Append padding so length in bits is 448 mod 512
6337         x[len>>5] |= 0x80 << (24-len%32);
6338         // Append length
6339         x[((len+64>>9)<<4)+15] = len;
6340         var w = Array(80);
6341
6342         var k1 = 0x5A827999;
6343         var k2 = 0x6ED9EBA1;
6344         var k3 = 0x8F1BBCDC;
6345         var k4 = 0xCA62C1D6;
6346
6347         var h0 = 0x67452301;
6348         var h1 = 0xEFCDAB89;
6349         var h2 = 0x98BADCFE;
6350         var h3 = 0x10325476;
6351         var h4 = 0xC3D2E1F0;
6352
6353         for(var i=0;i<x.length;i+=16)
6354                 {
6355                 var j,t;
6356                 var a = h0;
6357                 var b = h1;
6358                 var c = h2;
6359                 var d = h3;
6360                 var e = h4;
6361                 for(j = 0;j<16;j++)
6362                         {
6363                         w[j] = x[i+j];
6364                         t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
6365                         e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
6366                         }
6367                 for(j=16;j<20;j++)
6368                         {
6369                         w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
6370                         t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
6371                         e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
6372                         }
6373                 for(j=20;j<40;j++)
6374                         {
6375                         w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
6376                         t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k2);
6377                         e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
6378                         }
6379                 for(j=40;j<60;j++)
6380                         {
6381                         w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
6382                         t = add32x5(e,(a>>>27)|(a<<5),(b&c)|(d&(b|c)),w[j],k3);
6383                         e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
6384                         }
6385                 for(j=60;j<80;j++)
6386                         {
6387                         w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
6388                         t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k4);
6389                         e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
6390                         }
6391
6392                 h0 = add32(h0,a);
6393                 h1 = add32(h1,b);
6394                 h2 = add32(h2,c);
6395                 h3 = add32(h3,d);
6396                 h4 = add32(h4,e);
6397                 }
6398         return Array(h0,h1,h2,h3,h4);
6399 }
6400
6401 // ---------------------------------------------------------------------------------
6402 // RGB colour object
6403 // ---------------------------------------------------------------------------------
6404
6405 // Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
6406 function RGB(r,g,b)
6407 {
6408         this.r = 0;
6409         this.g = 0;
6410         this.b = 0;
6411         if(typeof r == "string")
6412                 {
6413                 if(r.substr(0,1) == "#")
6414                         {
6415                         if(r.length == 7)
6416                                 {
6417                                 this.r = parseInt(r.substr(1,2),16)/255;
6418                                 this.g = parseInt(r.substr(3,2),16)/255;
6419                                 this.b = parseInt(r.substr(5,2),16)/255;
6420                                 }
6421                         else
6422                                 {
6423                                 this.r = parseInt(r.substr(1,1),16)/15;
6424                                 this.g = parseInt(r.substr(2,1),16)/15;
6425                                 this.b = parseInt(r.substr(3,1),16)/15;
6426                                 }
6427                         }
6428                 else
6429                         {
6430                         var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/ ;
6431                         var c = r.match(rgbPattern);
6432                         if (c)
6433                                 {
6434                                 this.r = parseInt(c[1],10)/255;
6435                                 this.g = parseInt(c[2],10)/255;
6436                                 this.b = parseInt(c[3],10)/255;
6437                                 }
6438                         }
6439                 }
6440         else
6441                 {
6442                 this.r = r;
6443                 this.g = g;
6444                 this.b = b;
6445                 }
6446         return this;
6447 }
6448
6449 // Mixes this colour with another in a specified proportion
6450 // c = other colour to mix
6451 // f = 0..1 where 0 is this colour and 1 is the new colour
6452 // Returns an RGB object
6453 RGB.prototype.mix = function(c,f)
6454 {
6455         return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
6456 }
6457
6458 // Return an rgb colour as a #rrggbb format hex string
6459 RGB.prototype.toString = function()
6460 {
6461         var r = this.r.clamp(0,1);
6462         var g = this.g.clamp(0,1);
6463         var b = this.b.clamp(0,1);
6464         return("#" + ("0" + Math.floor(r * 255).toString(16)).right(2) +
6465                                  ("0" + Math.floor(g * 255).toString(16)).right(2) +
6466                                  ("0" + Math.floor(b * 255).toString(16)).right(2));
6467 }
6468
6469 // ---------------------------------------------------------------------------------
6470 // DOM utilities - many derived from www.quirksmode.org
6471 // ---------------------------------------------------------------------------------
6472
6473 function drawGradient(place,horiz,colours)
6474 {
6475         for(var t=0; t<= 100; t+=2)
6476                 {
6477                 var bar = document.createElement("div");
6478                 place.appendChild(bar);
6479                 bar.style.position = "absolute";
6480                 bar.style.left = horiz ? t + "%" : 0;
6481                 bar.style.top = horiz ? 0 : t + "%";
6482                 bar.style.width = horiz ? (101-t) + "%" : "100%";
6483                 bar.style.height = horiz ? "100%" : (101-t) + "%";
6484                 bar.style.zIndex = -1;
6485                 var f = t/100;
6486                 var p = f*(colours.length-1);
6487                 bar.style.backgroundColor = colours[Math.floor(p)].mix(colours[Math.ceil(p)],p-Math.floor(p)).toString();
6488                 }
6489 }
6490
6491 function createTiddlyText(theParent,theText)
6492 {
6493         return theParent.appendChild(document.createTextNode(theText));
6494 }
6495
6496 function createTiddlyCheckbox(theParent,caption,checked,onChange)
6497 {
6498         var cb = document.createElement("input");
6499         cb.setAttribute("type","checkbox");
6500         cb.onclick = onChange;
6501         theParent.appendChild(cb);
6502         cb.checked = checked;
6503         cb.className = "chkOptionInput";
6504         if(caption)
6505                 wikify(caption,theParent);
6506         return cb;
6507 }
6508
6509 function createTiddlyElement(theParent,theElement,theID,theClass,theText)
6510 {
6511         var e = document.createElement(theElement);
6512         if(theClass != null)
6513                 e.className = theClass;
6514         if(theID != null)
6515                 e.setAttribute("id",theID);
6516         if(theText != null)
6517                 e.appendChild(document.createTextNode(theText));
6518         if(theParent != null)
6519                 theParent.appendChild(e);
6520         return(e);
6521 }
6522
6523 // Add an event handler
6524 // Thanks to John Resig, via QuirksMode
6525 function addEvent(obj,type,fn)
6526 {
6527         if(obj.attachEvent)
6528                 {
6529                 obj['e'+type+fn] = fn;
6530                 obj[type+fn] = function(){obj['e'+type+fn](window.event);}
6531                 obj.attachEvent('on'+type,obj[type+fn]);
6532                 }
6533         else
6534                 obj.addEventListener(type,fn,false);
6535 }
6536
6537 // Remove  an event handler
6538 // Thanks to John Resig, via QuirksMode
6539 function removeEvent(obj,type,fn)
6540 {
6541         if(obj.detachEvent)
6542                 {
6543                 obj.detachEvent('on'+type,obj[type+fn]);
6544                 obj[type+fn] = null;
6545                 }
6546         else
6547                 obj.removeEventListener(type,fn,false);
6548 }
6549
6550 function addClass(e,theClass)
6551 {
6552         var currClass = e.className.split(" ");
6553         if(currClass.indexOf(theClass) == -1)
6554                 e.className += " " + theClass;
6555 }
6556
6557 function removeClass(e,theClass)
6558 {
6559         var currClass = e.className.split(" ");
6560         var i = currClass.indexOf(theClass);
6561         while(i != -1)
6562                 {
6563                 currClass.splice(i,1);
6564                 i = currClass.indexOf(theClass);
6565                 }
6566         e.className = currClass.join(" ");
6567 }
6568
6569 function hasClass(e,theClass)
6570 {
6571         if(e.className)
6572                 {
6573                 if(e.className.split(" ").indexOf(theClass) != -1)
6574                         return true;
6575                 }
6576         return false;
6577 }
6578
6579 // Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
6580 function findRelated(e,value,name,relative)
6581 {
6582         name = name ? name : "tagName";
6583         relative = relative ? relative : "parentNode";
6584         if(name == "className")
6585                 {
6586                 while(e && !hasClass(e,value))
6587                         {
6588                         e = e[relative];
6589                         }
6590                 }
6591         else
6592                 {
6593                 while(e && e[name] != value)
6594                         {
6595                         e = e[relative];
6596                         }
6597                 }
6598         return e;
6599 }
6600
6601 // Resolve the target object of an event
6602 function resolveTarget(e)
6603 {
6604         var obj;
6605         if (e.target)
6606                 obj = e.target;
6607         else if (e.srcElement)
6608                 obj = e.srcElement;
6609         if (obj.nodeType == 3) // defeat Safari bug
6610                 obj = obj.parentNode;
6611         return(obj);
6612 }
6613
6614 // Return the content of an element as plain text with no formatting
6615 function getPlainText(e)
6616 {
6617         var text = "";
6618         if(e.innerText)
6619                 text = e.innerText;
6620         else if(e.textContent)
6621                 text = e.textContent;
6622         return text;
6623 }
6624
6625 // Get the scroll position for window.scrollTo necessary to scroll a given element into view
6626 function ensureVisible(e)
6627 {
6628         var posTop = findPosY(e);
6629         var posBot = posTop + e.offsetHeight;
6630         var winTop = findScrollY();
6631         var winHeight = findWindowHeight();
6632         var winBot = winTop + winHeight;
6633         if(posTop < winTop)
6634                 return(posTop);
6635         else if(posBot > winBot)
6636                 {
6637                 if(e.offsetHeight < winHeight)
6638                         return(posTop - (winHeight - e.offsetHeight));
6639                 else
6640                         return(posTop);
6641                 }
6642         else
6643                 return(winTop);
6644 }
6645
6646 // Get the current width of the display window
6647 function findWindowWidth()
6648 {
6649         return(window.innerWidth ? window.innerWidth : document.documentElement.clientWidth);
6650 }
6651
6652 // Get the current height of the display window
6653 function findWindowHeight()
6654 {
6655         return(window.innerHeight ? window.innerHeight : document.documentElement.clientHeight);
6656 }
6657
6658 // Get the current horizontal page scroll position
6659 function findScrollX()
6660 {
6661         return(window.scrollX ? window.scrollX : document.documentElement.scrollLeft);
6662 }
6663
6664 // Get the current vertical page scroll position
6665 function findScrollY()
6666 {
6667         return(window.scrollY ? window.scrollY : document.documentElement.scrollTop);
6668 }
6669
6670 function findPosX(obj)
6671 {
6672         var curleft = 0;
6673         while (obj.offsetParent)
6674                 {
6675                 curleft += obj.offsetLeft;
6676                 obj = obj.offsetParent;
6677                 }
6678         return curleft;
6679 }
6680
6681 function findPosY(obj)
6682 {
6683         var curtop = 0;
6684         while (obj.offsetParent)
6685                 {
6686                 curtop += obj.offsetTop;
6687                 obj = obj.offsetParent;
6688                 }
6689         return curtop;
6690 }
6691
6692 // Blur a particular element
6693 function blurElement(e)
6694 {
6695         if(e != null && e.focus && e.blur)
6696                 {
6697                 e.focus();
6698                 e.blur();
6699                 }
6700 }
6701
6702 // Create a non-breaking space
6703 function insertSpacer(place)
6704 {
6705         var e = document.createTextNode(String.fromCharCode(160));
6706         if(place)
6707                 place.appendChild(e);
6708         return e;
6709 }
6710
6711 // Remove all children of a node
6712 function removeChildren(e)
6713 {
6714         while(e.hasChildNodes())
6715                 e.removeChild(e.firstChild);
6716 }
6717
6718 // Add a stylesheet, replacing any previous custom stylesheet
6719 function setStylesheet(s,id)
6720 {
6721         if(!id)
6722                 id = "customStyleSheet";
6723         var n = document.getElementById(id);
6724         if(document.createStyleSheet) // Test for IE's non-standard createStyleSheet method
6725                 {
6726                 if(n)
6727                         n.parentNode.removeChild(n);
6728                 // This failed without the &nbsp;
6729                 document.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
6730                 }
6731         else
6732                 {
6733                 if(n)
6734                         n.replaceChild(document.createTextNode(s),n.firstChild);
6735                 else
6736                         {
6737                         var n = document.createElement("style");
6738                         n.type = "text/css";
6739                         n.id = id;
6740                         n.appendChild(document.createTextNode(s));
6741                         document.getElementsByTagName("head")[0].appendChild(n);
6742                         }
6743                 }
6744 }
6745
6746 // Replace the current selection of a textarea or text input and scroll it into view
6747
6748 function replaceSelection(e,text)
6749 {
6750         if (e.setSelectionRange)
6751                 {
6752                 var oldpos = e.selectionStart + 1;
6753                 e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionStart);
6754                 e.setSelectionRange( oldpos, oldpos);
6755                 var linecount = e.value.split('\n').length;
6756                 var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
6757                 e.scrollTop = Math.floor((thisline-e.rows/2)*e.scrollHeight/linecount);
6758                 }
6759         else if (document.selection)
6760                 {
6761                 var range = document.selection.createRange();
6762                 if (range.parentElement() == e)
6763                         {
6764                         var isCollapsed = range.text == "";
6765                         range.text = text;
6766                          if (!isCollapsed)
6767                                 {
6768                                 range.moveStart('character', -text.length);
6769                                 range.select();
6770                                 }
6771                         }
6772                 }
6773 }
6774
6775 // Returns the text of the given (text) node, possibly merging subsequent text nodes
6776 function getNodeText(e)
6777 {
6778         var t = ""; 
6779         while (e && e.nodeName == "#text")
6780                 {
6781                 t += e.nodeValue;
6782                 e = e.nextSibling;
6783                 }
6784         return t;
6785 }
6786 //# -------------------------
6787 //# LoaderBase: A (abstract) storage loader that loads the tiddlers from a list of HTML elements.
6788 //# The format of the elements is defined by subclasses of this loader through the internalizeTiddler implementation.
6789 //# Subclasses must implement:
6790 //#                     function getTitle(store, e)
6791 //#
6792 //# store must implement:
6793 //#                     function createTiddler(title).
6794 //#
6795
6796 function LoaderBase()
6797 {
6798 }
6799
6800 LoaderBase.prototype.loadTiddler = function(store,e,tiddlers)
6801 {
6802         var title = this.getTitle(store, e);
6803         if (title)
6804                 {
6805                 var tiddler = store.createTiddler(title);
6806                 this.internalizeTiddler(store, tiddler, title, e);
6807                 tiddlers.push(tiddler);
6808                 }
6809 }
6810
6811 LoaderBase.prototype.loadTiddlers = function(store,nodes)
6812 {
6813         var tiddlers = [];
6814         for (var t = 0; t < nodes.length; t++)
6815                 {
6816                 try
6817                         {
6818                         this.loadTiddler(store, nodes[t], tiddlers);
6819                         }
6820                 catch(e)
6821                         {
6822                         showException(e, config.messages.tiddlerLoadError.format([this.getTitle(store, nodes[t])]));
6823                         }
6824                 }
6825         return tiddlers;
6826 }
6827         
6828 //# -------------------------
6829 //# SaverBase: a (abstract) storage saver that externalizes all tiddlers into a string, 
6830 //# with every tiddler individually externalized (using this.externalizeTiddler) and joined with newlines 
6831 //# Subclasses must implement:
6832 //#                     function externalizeTiddler(store, tiddler)
6833 //#
6834 //# store must implement:
6835 //#                     function getTiddlers(sortByFieldName)
6836 //#
6837
6838 function SaverBase()
6839 {
6840 }
6841
6842 SaverBase.prototype.externalize = function(store) 
6843 {
6844         var results = [];
6845         var tiddlers = store.getTiddlers("title");
6846         for (var t = 0; t < tiddlers.length; t++)
6847                 results.push(this.externalizeTiddler(store, tiddlers[t]));
6848         return results.join("\n");
6849 }
6850 //--------------------------------
6851 // TW21Loader (inherits from LoaderBase)
6852
6853 function TW21Loader() {};
6854
6855 TW21Loader.prototype = new LoaderBase();
6856
6857 TW21Loader.prototype.getTitle = function(store, e) {
6858         var title = null;
6859         if(e.getAttribute)
6860                 title = e.getAttribute("tiddler");
6861         if(!title && e.id) {    
6862                 var lenPrefix = store.idPrefix.length;
6863                 if (e.id.substr(0,lenPrefix) == store.idPrefix)
6864                         title = e.id.substr(lenPrefix);
6865         }
6866         return title;
6867 }
6868
6869 TW21Loader.prototype.internalizeTiddler = function(store, tiddler, title, data) {
6870         var text = getNodeText(data.firstChild).unescapeLineBreaks();
6871         var modifier = data.getAttribute("modifier");
6872         var modified = Date.convertFromYYYYMMDDHHMM(data.getAttribute("modified"));
6873         var c = data.getAttribute("created");
6874         var created = c ? Date.convertFromYYYYMMDDHHMM(c) : modified;
6875         var tags = data.getAttribute("tags");
6876         var fields = {};
6877         var attrs = data.attributes;
6878         for(var i = attrs.length-1; i >= 0; i--) {
6879                 var name = attrs[i].name;
6880                 if (attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
6881                         fields[name] = attrs[i].value.unescapeLineBreaks();
6882                 }
6883         }
6884         tiddler.assign(title,text,modifier,modified,tags,created, fields);
6885         return tiddler;
6886 };
6887
6888 //--------------------------------
6889 // TW21Saver (inherits from SaverBase)
6890
6891 function TW21Saver() {};
6892
6893 TW21Saver.prototype = new SaverBase();
6894
6895 TW21Saver.prototype.externalizeTiddler = function(store, tiddler) 
6896 {
6897         try {
6898                 var extendedFieldAttributes = "";
6899                 store.forEachField(tiddler, 
6900                         function(tiddler, fieldName, value) {
6901                                 // don't store stuff from the temp namespace
6902                                 if (!fieldName.match(/^temp\./))
6903                                         extendedFieldAttributes += ' %0="%1"'.format([fieldName, value.escapeLineBreaks().htmlEncode()]);
6904                         }, true);
6905                 return '<div tiddler="%0" modifier="%1" modified="%2" created="%3" tags="%4"%6>%5</div>'.format([
6906                                 tiddler.title.htmlEncode(),
6907                                 tiddler.modifier.htmlEncode(),
6908                                 tiddler.modified.convertToYYYYMMDDHHMM(),
6909                                 tiddler.created.convertToYYYYMMDDHHMM(),
6910                                 tiddler.getTags().htmlEncode(),
6911                                 tiddler.escapeLineBreaks().htmlEncode(),
6912                                 extendedFieldAttributes
6913                         ]);
6914         } catch (e) {
6915                 throw exceptionText(e, config.messages.tiddlerSaveError.format([tiddler.title]));
6916         }
6917 }
6918
6919 // ---------------------------------------------------------------------------------
6920 // Deprecated code
6921 // ---------------------------------------------------------------------------------
6922
6923 // @Deprecated: Use createElementAndWikify and this.termRegExp instead
6924 config.formatterHelpers.charFormatHelper = function(w)
6925 {
6926         w.subWikify(createTiddlyElement(w.output,this.element),this.terminator);
6927 }
6928
6929 // @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
6930 config.formatterHelpers.monospacedByLineHelper = function(w)
6931 {
6932         var lookaheadRegExp = new RegExp(this.lookahead,"mg");
6933         lookaheadRegExp.lastIndex = w.matchStart;
6934         var lookaheadMatch = lookaheadRegExp.exec(w.source);
6935         if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
6936                 {
6937                 var text = lookaheadMatch[1];
6938                 if(config.browser.isIE)
6939                         text = text.replace(/\n/g,"\r");
6940                 createTiddlyElement(w.output,"pre",null,null,text);
6941                 w.nextMatch = lookaheadRegExp.lastIndex;
6942                 }
6943 }
6944
6945 // @Deprecated: Use <br> or <br /> instead of <<br>>
6946 config.macros.br.handler = function(place)
6947 {
6948         createTiddlyElement(place,"br");
6949 }
6950
6951 // Find an entry in an array. Returns the array index or null
6952 // @Deprecated: Use indexOf instead
6953 Array.prototype.find = function(item)
6954 {
6955         var i = this.indexOf(item);
6956         return i == -1 ? null : i;
6957 }
6958
6959 // Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
6960 // @Deprecated: Use store.getLoader().internalizeTiddler instead
6961 Tiddler.prototype.loadFromDiv = function(divRef,title)
6962 {
6963         return store.getLoader().internalizeTiddler(store,this,title,divRef);
6964 }
6965
6966 // Format the text for storage in an HTML DIV
6967 // @Deprecated Use store.getSaver().externalizeTiddler instead.
6968 Tiddler.prototype.saveToDiv = function()
6969 {
6970         return store.getSaver().externalizeTiddler(store,this);
6971 }
6972
6973 // @Deprecated: Use store.allTiddlersAsHtml() instead
6974 function allTiddlersAsHtml()
6975 {
6976         return store.allTiddlersAsHtml();
6977 }
6978
6979 // @Deprecated: Use refreshPageTemplate instead
6980 function applyPageTemplate(title)
6981 {
6982         refreshPageTemplate(title);
6983 }
6984
6985 // @Deprecated: Use story.displayTiddlers instead
6986 function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,slowly)
6987 {
6988         story.displayTiddlers(srcElement,titles,template,animate,slowly);
6989 }
6990
6991 // @Deprecated: Use story.displayTiddler instead
6992 function displayTiddler(srcElement,title,template,unused1,unused2,animate,slowly)
6993 {
6994         story.displayTiddler(srcElement,title,template,animate,slowly);
6995 }
6996
6997 // @Deprecated: Use functions on right hand side directly instead
6998 var createTiddlerPopup = Popup.create;
6999 var scrollToTiddlerPopup = Popup.show;
7000 var hideTiddlerPopup = Popup.remove;
7001
7002 // @Deprecated: Use right hand side directly instead
7003 var regexpBackSlashEn = new RegExp("\\\\n","mg");
7004 var regexpBackSlash = new RegExp("\\\\","mg");
7005 var regexpBackSlashEss = new RegExp("\\\\s","mg");
7006 var regexpNewLine = new RegExp("\n","mg");
7007 var regexpCarriageReturn = new RegExp("\r","mg");
7008 // ---------------------------------------------------------------------------------
7009 // End of scripts
7010 merge(config.shadowTiddlers,{SiteTitle:'DevFire'});
7011 merge(config.shadowTiddlers,{MainMenu:"PageTemplate\nStyleSheet\nMainMenu\nDefaultTiddlers"});
7012 merge(config.shadowTiddlers,{SiteSubtitle:"a theme for ~TiddlyWiki"});
7013 merge(config.shadowTiddlers,{DefaultTiddlers:"LorumIpsum"});
7014 merge(config.shadowTiddlers,{LorumIpsum:"Aenean eros arcu, condimentum nec, dapibus ut, tincidunt sit amet, urna. Quisque viverra, eros sed imperdiet iaculis, est risus facilisis quam, id malesuada arcu nulla luctus urna. Nullam et est. Vestibulum velit sem, faucibus cursus, dapibus vestibulum, pellentesque et, urna. Donec luctus. Donec lectus. Aliquam eget eros facilisis tortor feugiat sollicitudin. Integer lobortis vulputate sapien. Sed iaculis erat ac nunc. Etiam eu enim. Mauris ipsum urna, rhoncus at, bibendum sit amet, euismod eget, dolor. Mauris fermentum quam vitae ligula. Vestibulum in libero feugiat justo dictum consectetuer. Vestibulum euismod purus eget elit. Nunc sed massa porta elit bibendum posuere. Nunc pulvinar justo sit amet odio. In sed est. Phasellus ornare elementum nulla. Nulla ipsum neque, cursus a, viverra a, imperdiet at, enim. Quisque facilisis, diam sed accumsan suscipit, odio arcu hendrerit dolor, quis aliquet massa nulla nec sem.\n!heading 1\n!!heading 2\n!!!heading3\n----\n<<tag button>>\nThis is a link to a [[StyleSheet]] tiddler.\n\n> This is a blockquote\n> This is a blockquote\n> This is a blockquote\n|>|>| !This is a header |h\n|column1|column2|column3|\n|row2| row2 |row2|\n|column1|column2|column3|\n|row2| row2 |row2|\n|column1|column2|column3|\n|row2| row2 |row2|"});
7015 // ---------------------------------------------------------------------------------
7016 //]]>
7017 </script>
7018 <style type="text/css">
7019
7020 #saveTest {
7021         display: none;
7022 }
7023
7024 .zoomer {
7025         display: none;
7026 }
7027
7028 #messageArea {
7029         display: none;
7030 }
7031
7032 #copyright {
7033         display: none;
7034 }
7035
7036 .popup {
7037         position: absolute;
7038 }
7039
7040 #storeArea {
7041         display: none;
7042         margin: 4em 10em 3em;
7043 }
7044
7045 #storeArea div {
7046  padding: 0.5em;
7047  margin: 1em 0em 0em 0em;
7048  border-color: #f0f0f0 #606060 #404040 #d0d0d0; 
7049  border-style: solid; 
7050  border-width: 2px;
7051  overflow: auto;
7052 }
7053
7054 #javascriptWarning {
7055         width: 100%;
7056         text-align: center;
7057         font-weight: bold;
7058         background-color: #dd1100;
7059         color: #fff;
7060         padding:1em 0em; 
7061 }
7062
7063 </style>
7064 <!--POST-HEAD-START-->
7065
7066 <!--POST-HEAD-END-->
7067 </head>
7068 <body onload="main();" onunload="if(window.checkUnsavedChanges) checkUnsavedChanges();">
7069 <!--PRE-BODY-START-->
7070
7071 <!--PRE-BODY-END-->
7072         <script type="text/javascript">
7073 //<![CDATA[
7074 if (useJavaSaver)
7075         document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
7076 //]]>
7077         </script>
7078         <div id="copyright">
7079         Welcome to TiddlyWiki by Jeremy Ruston, Copyright &copy; 2006 Osmosoft Limited
7080         </div>
7081         <noscript>
7082                 <div id="javascriptWarning">This page requires JavaScript to function properly</div>
7083         </noscript>
7084         <div id="saveTest"></div>
7085         <div id="contentWrapper"></div>
7086         <div id="contentStash"></div>
7087         <div id="storeArea">
7088 <div tiddler="(built-in shadow tiddler)" modifier="CameronRich" modified="200702240024" created="200702240024" tags="">changes, notes and errata</div>
7089 <div tiddler="Cam" modifier="YourName" modified="200804011313" created="200804011313" tags="">Type the text for 'YourName'</div>
7090 <div tiddler="Changelog" modifier="YourName" modified="200901301233" created="200702240022" tags="">@@bgcolor(#ff0000):color(#ffffff):Changes for 1.2.1@@\n\n!!__SSL Library__\n* Certificate verification now works for Firefox.\n* Extended the openssl API.\n\n@@bgcolor(#ff0000):color(#ffffff):Changes for 1.2.0@@\n\n!!__SSL Library__\n* A self-signed certificate will be verified as ok provided that that it is on the certificate authority list.\n* Certificates are not verified when added as certificate authorities (since self-signed and expired certificates can be added to browsers etc)\n\n@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.9@@\n\n!!__SSL Library__\n* Now support MS IIS resource kit certificates (thanks to Carsten Sørensen).\n* Fixed a memory leak when freeing more than one CA certificate.\n* The bigint library had a problem with squaring which affected classical reduction (thanks to Manuel Klimek).\n\n!!__axhttpd__\n* Brought back setuid()/setgid() as an option.\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.8@@\n\n!!__SSL Library__\n* Now using a BSD style license.\n* Self-signed certificates can now be automatically generated (the keys still need to be provided).\n* A new API call //ssl_x509_create()// can be used to programatically  create the certificate.\n* Certificate/keys can be loaded automatically given a file location.\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.7@@\n\n!!__SSL Library__\n\n* Variable sized session id's is now better handled for session caching. It has meant a new API call //ssl_get_session_id_size()// and a change to //ssl_client_new()// to define the session id size.\n* Muliple records with a single header are now better supported (thanks to HervĂ© Sibert).\n* ~MD2 added for Verisign root cert verification (thanks to Byron Rakitzis).\n* The ~MD5/~SHA1 digests are calculated incrementally to reduce memory (thanks to Byron Rakitzis).\n* The bigint cache is now cleared regularly to reduce memory.\n\n!!__axhttpd__\n\n* Improved the POST handling (thanks to Christian Melki).\n* CSS files now work properly.\n* Lua's CGI launcher location is configurable.\n* //vfork()// is now used for CGI for performance reasons.\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.6@@\n\n!!__SSL Library__\n\n* ~RC4 speed improvements\n* Lua samples/bindings now work properly\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.5@@\n\n!!__SSL Library__\n\n* Session id's can now be variable lengths in server hello messages.\n* 0 length client certificates are now supported.\n* ssl_version() now returns just the version and not the date.\n* ssl_write() was not sending complete packets under load.\n\n!!__axhttpd__\n\n* Completely updated the CGI code.\n* Lua now integrated - Lua scripts and Lua Pages now run.\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.4@@\n\n!!__SSL Library__\n\n* Fixed a Win32 crypto library issue with non-Administrator users\n* Removed compiler warnings that showed up in ~FC6.\n* GNU TLS certificates are now accepted.\n* Separated the send/receive headers for HMAC calculations.\n* Fixed a compilation problem with swig/perl/~FC6.\n* Fixed an issue with loading PEM CA certificates.\n\n!!__axhttpd__\n\n* Made //setuid()/setgid()// call an mconf option.\n* Made //chroot()// an mconf option. Default to //chdir()// instead.\n* Removed optional permissions checking.\n\n!@@bgcolor(#ff0000):color(#ffffff):Changes for 1.1.1@@\n\n!!__SSL Library__\n\n* AES should now work on 16bit processors (there was an alignment problem).\n* Various freed objects are cleared before freeing.\n* Header files now installed in ///usr/local/include/axTLS//.\n* -DCYGWIN replaced with -~DCONFIG_PLATFORM_CYGWIN (and the same for Solaris).\n* removed &quot;-noextern&quot; option in Swig. Fixed some other warnings in Win32.\n* SSLCTX changed to ~SSL_CTX (to be consistent with openssl). SSLCTX still exists for backwards compatibility.\n* malloc() and friends call abort() on failure.\n* Fixed a memory leak in directory listings.\n* Added openssl() compatibility functions.\n* Fixed Cygwin 'make install' issue.\n\n!!__axhttpd__\n\n* main.c now becomes axhttpd.c.\n* Header file issue fixed (in mime_types.c).\n* //chroot()// now used for better security.\n* Basic authentication implemented (via .htpasswd).\n* SSL access/denial protection implemented (via .htaccess).\n* Directory access protection implemented (via .htaccess).\n* Can now have more than one CGI file extension in mconf.\n* &quot;~If-Modified-Since&quot; request now handled properly.\n* Performance tweaks to remove //ssl_find()//.</div>
7091 <div tiddler="DefaultTiddlers" modifier="CameronRich" modified="200702240019" created="200702240019" tags="">[[Read Me]]</div>
7092 <div tiddler="License" modifier="YourName" modified="200804011309" created="200702240022" tags="">axTLS uses a BSD style license:\n\nCopyright (c) 2008, Cameron Rich All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\nRedistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer. Redistributions in binary\nform must reproduce the above copyright notice, this list of conditions and\nthe following disclaimer in the documentation and/or other materials\nprovided with the distribution. Neither the name of the axTLS Project nor\nthe names of its contributors may be used to endorse or promote products\nderived from this software without specific prior written permission. \n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot;\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\nLIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\nOUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGE.</div>
7093 <div tiddler="MainMenu" modifier="CameronRich" modified="200702250353" created="200702240021" tags="">[[Read Me]] \n[[Changelog]]\n[[axhttpd]]\n[[License]]</div>
7094 <div tiddler="PageTemplate" modifier="YourName" modified="200701122313" created="200701122350" tags="DevFireTheme">&lt;div class='header' macro='gradient vert #390108 #900'&gt;\n&lt;div class='headerShadow'&gt;\n&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;\n&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;\n&lt;/div&gt;\n&lt;div class='headerForeground'&gt;\n&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;\n&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;\n&lt;/div&gt;\n&lt;/div&gt;\n&lt;div id='mainMenu'&gt;\n&lt;div refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;\n&lt;/div&gt;\n&lt;div id='sidebar'&gt;\n&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;\n&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;\n&lt;/div&gt;\n&lt;div id='displayArea'&gt;\n&lt;div id='messageArea'&gt;&lt;/div&gt;\n&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;\n&lt;/div&gt;</div>
7095 <div tiddler="Read Me" modifier="YourName" modified="200804011313" created="200702240020" tags="">!@@bgcolor(#ff0000):color(#ffffff):axTLS Quick Start Guide@@\n\nThis is a guide to get a small SSL web-server up and running quickly.\n\n!!__Introduction__\n\nThe axTLS project is an SSL client/server library using the ~TLSv1 protocol. It is designed to be small and fast, and is suited to embedded projects. A web server is included.\n\nThe basic web server + SSL library is around 60-70kB and is configurable for features or size.\n\n!!__Compilation__\n\nAll platforms require GNU make. This means on Win32 that Cygwin needs to be installed with &quot;make&quot; and various developer options selected.\n\nConfiguration now uses a tool called &quot;mconf&quot; which gives a nice way to configure options (similar to what is used in ~BusyBox and the Linux kernel).\n\nYou should be able to compile axTLS simply by extracting it, change into the extracted directory and typing:\n\n{{indent{{{{&gt;  make}}}\n\nSelect your platform type, save the configuration, exit, and then type &quot;make&quot; again.\n\nIf all goes well, you should end up with an executable called &quot;axhttpd&quot; (or axhttpd.exe) in the //_stage// directory.\n\nTo play with all the various axTLS options, type:\n\n{{indent{{{{&gt;  make menuconfig}}}\n\nSave the new configuration and rebuild.\n\n!!__Running it__\n\nTo run it, go to the //_stage// directory, and type (as superuser):\n\n{{indent{{{{&gt; axhttpd}}}\n\nNote: you may have to set your ~LD_LIBRARY_PATH - e.g. go to //_stage// and type //export ~LD_LIBRARY_PATH=`pwd`//\n\nAnd then point your browser at https://127.0.0.1 And you should see a this html page with a padlock appearing on your browser. or type http://127.0.0.1 to see the same page unencrypted.\n\n!!__The axssl utilities__\n\nThe axssl suite of tools are the SSL test tools in the various language bindings. They are:\n\n* axssl              - C sample\n* axssl.csharp    - C# sample\n* axssl.vbnet      - VB.NET sample\n* axtls.jar           - Java sample\n* axssl.pl           - Perl sample\n* axssl.lua         - Lua sample\n\nAll the tools have identical command-line parameters. e.g. to run something interesting:\n\n{{indent{{{{&gt; axssl s_server -verify -CAfile ../ssl/test/axTLS.ca_x509}}}\n\nand\n\n{{indent{{{{&gt; axssl s_client -cert ../ssl/test/axTLS.x509_1024 -key ../ssl/test/axTLS.key_1024 -reconnect}}}\n\n!!!!C#\n\nIf building under Linux or other non-Win32 platforms, Mono must be installed and the executable is run as:\n\n{{indent{{{{&gt; mono axssl.csharp.exe ...}}}\n\n!!!!Java\n\nThe java version is run as:\n\n{{indent{{{{&gt; java -jar axtls.jar &lt;options&gt;}}}\n\n!!!!Perl\n\n{{indent{{{{&gt; [perl] ./axssl.pl &lt;options&gt;}}}\n\nIf running under Win32, be sure to use the correct version of Perl (i.e. ~ActiveState's version works ok).\n\n!!!!Lua\n\n{{indent{{{{&gt; [lua] ./axssl.lua &lt;options&gt;}}}\n\n!__Known Issues__\n\n* Firefox doesn't handle legacy ~SSLv2 at all well. Disabling ~SSLv2 still initiates a ~SSLv23 handshake (v1.5). And continuous pressing of the &quot;Reload&quot; page instigates a change to ~SSLv3 for some reason (even though the TLS 1.0 option is selected). This will cause a &quot;Firefox and &lt;server&gt; cannot communicate securely because they have no common encryption algorithms&quot; (v1.5), or &quot;Firefox can't connect to &lt;server&gt; because the site uses a security protocol which isn't enabled&quot; (v2.0). See bugzilla issues 343543 and 359484 (Comment #7). It's all broken (hopefully fixed soon).\n* Perl/Java bindings don't work on 64 bit Linux machines. I can't even compile the latest version of Perl on an ~AMD64 box (using ~FC3).\n* Java 1.4 or better is required for the Java interfaces.\n* Processes that fork can't use session resumption unless some form of IPC is used.\n* Ensure libperl.so and libaxtls.so are in the shared library path when running with the perl bindings. A way to do this is with:\n\n{{indent{{{{&gt; export LD_LIBRARY_PATH=`perl -e 'use Config; print $Config{archlib};'`/CORE:.}}}\n* The lua sample requires the luabit library from http://luaforge.net/projects/bit.\n\n!!!!Win32 issues\n\n* Be careful about doing .NET executions on network drives - .NET complains with security exceptions on the binary. //TODO: Add a manifest file to prevent this.//\n* CGI has been removed from Win32 - it needs a lot more work to get it right.\n* The default Microsoft .NET SDK is v2.0.50727. Download from: http://msdn.microsoft.com/netframework/downloads/updates/default.aspx.\n\n!!!!Solaris issues\n\n* mconf doesn't work well - some manual tweaking is required for string values.\n* GNU make is required and needs to be in $PATH.\n* To get swig's library dependencies to work (and for the C library to be found), I needed to type:\n\n{{indent{{{{&gt; export LD_LIBRARY_PATH=/usr/local/gcc-3.3.1/lib:.}}}\n\n!!!!Cygwin issues\n\n* The bindings all compile but don't run under Cygwin with the exception of Perl. This is due to win32 executables being incompatible with Cygwin libraries.\n\n</div>
7096 <div tiddler="SiteSubtitle" modifier="CameronRich" modified="200702240025" created="200702240025" tags="">changes, notes and errata</div>
7097 <div tiddler="SiteTitle" modifier="CameronRich" modified="200702240023" created="200702240023" tags="">axTLS Embedded SSL</div>
7098 <div tiddler="SiteUrl" modifier="CameronRich" modified="200702240025" created="200702240025" tags="">http://axtls.cerocclub.com.au</div>
7099 <div tiddler="StyleSheet" modifier="CameronRich" modified="200702250600" created="200701122350" tags="DevFireTheme">/***\nhttp://tiddlystyles.com/#theme:DevFire\nAuthor: Clint Checketts\n***/\n\n/*{{{*/\nbody {\nbackground: #000;\n}\n/*}}}*/\n/***\n!Link styles /% ============================================================= %/\n***/\n/*{{{*/\na,\na.button,\n#mainMenu a.button,\n#sidebarOptions .sliderPanel a{\n color: #ffbf00;\n border: 0;\n background: transparent;\n}\n\na:hover,\na.button:hover,\n#mainMenu a.button:hover,\n#sidebarOptions .sliderPanel a:hover\n#sidebarOptions .sliderPanel a:active{\n color: #ff7f00;\n border: 0;\n border-bottom: #ff7f00 1px dashed;\n background: transparent;\n text-decoration: none;\n}\n\n#displayArea .button.highlight{\n color: #ffbf00;\n background: #4c4c4c;\n}\n/*}}}*/\n/***\n!Header styles /% ============================================================= %/\n***/\n/*{{{*/\n.header{\n border-bottom: 2px solid #ffbf00;\n color: #fff;\n}\n\n.headerForeground a {\n color: #fff;\n}\n\n.header a:hover {\n border-bottom: 1px dashed #fff;\n}\n/*}}}*/\n/***\n!Main menu styles /% ============================================================= %/\n***/\n/*{{{*/\n#mainMenu {color: #fff;}\n#mainMenu h1{\n font-size: 1.1em;\n}\n#mainMenu li,#mainMenu ul{\n list-style: none;\n margin: 0;\n padding: 0;\n}\n/*}}}*/\n/***\n!Sidebar styles /% ============================================================= %/\n***/\n/*{{{*/\n#sidebar {\n right: 0;\n color: #fff;\n border: 2px solid #ffbf00;\n border-width: 0 0 2px 2px;\n}\n#sidebarOptions {\n background-color: #4c4c4c;\n padding: 0;\n}\n\n#sidebarOptions a{\n margin: 0;\n color: #ffbf00;\n border: 0;\n}\n#sidebarOptions a:hover {\n color: #4c4c4c;\n background-color: #ffbf00;\n\n}\n\n#sidebarOptions a:active {\n color: #ffbf00;\n background-color: transparent;\n}\n\n#sidebarOptions .sliderPanel {\n background-color: #333;\n margin: 0;\n}\n\n#sidebarTabs {background-color: #4c4c4c;}\n#sidebarTabs .tabSelected {\n padding: 3px 3px;\n cursor: default;\n color: #ffbf00;\n background-color: #666;\n}\n#sidebarTabs .tabUnselected {\n color: #ffbf00;\n background-color: #5f5f5f;\n padding: 0 4px;\n}\n\n#sidebarTabs .tabUnselected:hover,\n#sidebarTabs .tabContents {\n background-color: #666;\n}\n\n.listTitle{color: #FFF;}\n#sidebarTabs .tabContents a{\n color: #ffbf00;\n}\n\n#sidebarTabs .tabContents a:hover{\n color: #ff7f00;\n background: transparent;\n}\n\n#sidebarTabs .txtMoreTab .tabSelected,\n#sidebarTabs .txtMoreTab .tab:hover,\n#sidebarTabs .txtMoreTab .tabContents{\n color: #ffbf00;\n background: #4c4c4c;\n}\n\n#sidebarTabs .txtMoreTab .tabUnselected {\n color: #ffbf00;\n background: #5f5f5f;\n}\n\n.tab.tabSelected, .tab.tabSelected:hover{color: #ffbf00; border: 0; background-color: #4c4c4c;cursor:default;}\n.tab.tabUnselected {background-color: #666;}\n.tab.tabUnselected:hover{color:#ffbf00; border: 0;background-color: #4c4c4c;}\n.tabContents {\n background-color: #4c4c4c;\n border: 0;\n}\n.tabContents .tabContents{background: #666;}\n.tabContents .tabSelected{background: #666;}\n.tabContents .tabUnselected{background: #5f5f5f;}\n.tabContents .tab:hover{background: #666;}\n/*}}}*/\n/***\n!Message area styles /% ============================================================= %/\n***/\n/*{{{*/\n#messageArea {background-color: #666; color: #fff; border: 2px solid #ffbf00;}\n#messageArea a:link, #messageArea a:visited {color: #ffbf00; text-decoration:none;}\n#messageArea a:hover {color: #ff7f00;}\n#messageArea a:active {color: #ff7f00;}\n#messageArea .messageToolbar a{\n border: 1px solid #ffbf00;\n background: #4c4c4c;\n}\n/*}}}*/\n/***\n!Popup styles /% ============================================================= %/\n***/\n/*{{{*/\n.popup {color: #fff; background-color: #4c4c4c; border: 1px solid #ffbf00;}\n.popup li.disabled{color: #fff;}\n.popup a {color: #ffbf00; }\n.popup a:hover { background: transparent; color: #ff7f00; border: 0;}\n.popup hr {color: #ffbf00; background: #ffbf00;}\n/*}}}*/\n/***\n!Tiddler Display styles /% ============================================================= %/\n***/\n/*{{{*/\n.title{color: #fff;}\nh1, h2, h3, h4, h5 {\n color: #fff;\n background-color: transparent;\n border-bottom: 1px solid #333;\n}\n\n.subtitle{\n color: #666;\n}\n\n.viewer {color: #fff; }\n\n.viewer table{background: #666; color: #fff;}\n\n.viewer th {background-color: #996; color: #fff;}\n\n.viewer pre, .viewer code {color: #ddd; background-color: #4c4c4c; border: 1px solid #ffbf00;}\n\n.viewer hr {color: #666;}\n\n.tiddler .button {color: #4c4c4c;}\n.tiddler .button:hover { color: #ffbf00; background-color: #4c4c4c;}\n.tiddler .button:active {color: #ffbf00; background-color: #4c4c4c;}\n\n.toolbar {\n color: #4c4c4c;\n}\n\n.toolbar a.button,\n.toolbar a.button:hover,\n.toolbar a.button:active,\n.editorFooter a{\n border: 0;\n}\n\n.footer {\n color: #ddd;\n}\n\n.selected .footer {\n color: #888;\n}\n\n.highlight, .marked {\n color: #000;\n background-color: #ffe72f;\n}\n.editorFooter {\n color: #aaa;\n}\n\n.tab{\n-moz-border-radius-topleft: 3px;\n-moz-border-radius-topright: 3px;\n}\n\n.tagging,\n.tagged{\n background: #4c4c4c;\n border: 1px solid #4c4c4c; \n}\n\n.selected .tagging,\n.selected .tagged{\n background-color: #333;\n border: 1px solid #ffbf00;\n}\n\n.tagging .listTitle,\n.tagged .listTitle{\n color: #fff;\n}\n\n.tagging .button,\n.tagged .button{\n color: #ffbf00;\n border: 0;\n padding: 0;\n}\n\n.tagging .button:hover,\n.tagged .button:hover{\nbackground: transparent;\n}\n\n.selected .isTag .tagging.simple,\n.selected .tagged.simple,\n.isTag .tagging.simple,\n.tagged.simple {\n float: none;\n display: inline;\n border: 0;\n background: transparent;\n color: #fff;\n margin: 0;\n}\n\n.cascade {\n background: #4c4c4c;\n color: #ddd;\n border: 1px solid #ffbf00;\n}\n/*}}}*/</div>
7100 <div tiddler="axhttpd" modifier="YourName" modified="200804011308" created="200702242231" tags="">axhttpd is a small embedded web server using the axTLS library. It is based originally on the web server written by Doug Currie which is at http://www.hcsw.org/awhttpd.\n\n!@@bgcolor(#ff0000):color(#ffffff):axhttpd Features@@ \n\n!!__Basic Authentication__\n\nBasic Authentication uses a password file called  &quot;.htpasswd&quot;, in the directory to be  protected. This file is formatted as the familiar colon-separated username/encrypted-password pair, records delimited by newlines. The protection does not carry over to subdirectories. The utility program htpasswd is included to help manually edit .htpasswd files.\n\nThe encryption of this password uses a proprietary algorithm due to the dependency of many crypt libraries on DES. An example is in [[/test_dir/no_http|https://127.0.0.1/test_dir/no_http]]  (username 'abcd', password is '1234').\n\n//Note: This is an mconf enabled configuration option.//\n\n!!__SSL Protection__\n\nDirectories/files can be accessed using the 'http' or 'https' uri prefix. If normal http access for a directory needs to be disabled, then put &quot;~SSLRequireSSL&quot; into a '.htaccess' file in the directory to be protected. \n\nConversely, use &quot;~SSLDenySSL&quot; to deny access to directories via SSL.\n\nAn example is in [[/test_dir/no_http|http://127.0.0.1/test_dir/no_http]] and [[/test_dir/no_ssl|https://127.0.0.1/test_dir/no_ssl]].\n\nEntire directories can be denied access with a &quot;Deny all&quot; directive (regardless of SSL or authentication). An example is in [[/test_dir/bin|http://127.0.0.1/test_dir/bin]]\n\n!!__CGI__\n\nMost of the CGI 1.1 variables are now placed into the script environment and should work as normal.\n\n!!__Lua and Lua Pages__\n\nThis is a small scripting language gaining popularity in embedded applications due to its small footprint and fast speed.\n\nLua has been incorporated into the build, so simply select it and it will automatically install. Try pointing your browser at [[test_main.html]|http://127.0.0.1/lua/test_main.html]] to see an example of Lua Pages.\n\n//Note: This is an mconf enabled configuration option.//\n\n!!__Directory Listing__\n\nAn mconf option. Allow the files in directories to be displayed. An example is in [[/test_dir|http://127.0.0.1/test_dir]]\n\n!!__Other Features__\n\n* Timeout - HTTP 1.1 allows for persistent connections. This is the time allowed for this connection in seconds.\n* Daemon - Puts the process in daemon mode. \n* SSL  session cache size - The size of the session cache (a heavily loaded server should maintain many sessions). A session will save on expensive SSL handshaking.\n\n</div>
7101 </div>
7102 <!--POST-BODY-START-->
7103
7104 <!--POST-BODY-END-->
7105         </body>
7106 </html>