/***\n|''Name:''|DC3.Ajax|\n|''Description:''|General purpose Ajax class|\n|''Date:''|Oct 10, 2006|\n|''Source:''|http://solo.dc3.com/tw/index.html#DC3.Ajax|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.0.5|\n|''~CoreVersion:''|2.0.11, 2.1.0|\n|''Browser:''|Firefox 1.5; Internet Explorer 6.0; Safari|\n|''Require:''|//none//|\n!!Description\nAjax library\n!!Usage\nTBD\n!!Revision History\n<<<\n''2006.09.16 [1.0.1]'' Initial creation, loosely patterned after BidiX Ajax\n''2006.09.20 [1.0.2]'' Add POST to core, refactor massively, now object can contain multiple methods. Add postForm() method.\n''2006.09.22 [1.0.3]'' Support posting hidden fields in postForm()\n''2006.09.26 [1.0.4]'' Change error reporting to popups. Include status code, status text and any body text in error popup.\n''2006.10.03 [1.0.5]'' Refactor post code, lowercase element type strings for tests, check out on TW 2.1.0\n<<<\n!!Code\n***/\n//{{{\nif (!window.DC3) window.DC3 = {};\nDC3.Ajax = \n{\n sendRequest: function(method, url, postData, onComplete, clientParams)\n {\n var xmlhttp;\n \n try { \n xmlhttp = new XMLHttpRequest(); \n } catch (e) {\n try { \n xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); \n } catch (e) {\n try { \n xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); \n } catch (e) { \n alert(e.description?e.description:e.toString()); \n }\n }\n }\n if (!xmlhttp) {\n alert("Can't support AJAX: Update your browser!");\n return;\n }\n if (window.netscape) {\n try {\n if (document.location.protocol.indexOf("http") == -1) {\n netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");\n }\n } catch (e) { \n alert(e.description ? e.description : e.toString()); \n }\n }\n \n xmlhttp.onreadystatechange = function () // Completion event\n {\n if (xmlhttp.readyState == 4) {\n if (xmlhttp.status == 200 || xmlhttp.status === 0) {\n onComplete(xmlhttp.responseText, clientParams);\n } else {\n alert(xmlhttp.status + " " + xmlhttp.statusText + ":\sn" + xmlhttp.responseText);\n }\n }\n };\n \n try {\n xmlhttp.open(method, url, true);\n if(method.toLowerCase() == 'post') {\n xmlhttp.setRequestHeader("Content-Type",\n "application/x-www-form-urlencoded");\n xmlhttp.send(postData);\n } else if (config.browser.isIE) { // Pathetic!\n xmlhttp.send(); \n } else { \n xmlhttp.send(null); \n }\n } catch (e) { \n alert(url + "failed: " + e.toString()); \n }\n },\n \n postForm: function(form, url, onComplete, clientParams)\n {\n var encode = function(name, val, last) {\n return encodeURIComponent(name) + "=" + encodeURIComponent(val) + (last ? "" : "&");\n };\n var nElem = form.elements.length;\n var fData = "";\n // Must hand-construct query string according to rules\n for(var i = 0; i < nElem; i++) {\n var elem = form.elements[i];\n var last = (i == nElem - 1);\n switch(elem.type.toLowerCase()) {\n case "text":\n case "password":\n case "textarea":\n case "select-one":\n case "hidden":\n fData += encode(elem.name, elem.value, last); // Encode even if empty\n break;\n case "checkbox":\n case "radio":\n if(elem.checked)\n fData += encode(elem.name, elem.value, last);\n break;\n case "select-multiple":\n for(var j = 0; j < elem.options.length; j++) {\n if(elem.options[j].selected) // Encode only selected (may be none!)\n fData += encode(elem.name, elem.options[j].value);\n }\n break;\n default: // Others unsupported \n displayMessage("Ajax.postForm: " + elem.type + " form elements not supported.");\n return; // BAIL OUT!\n }\n }\n this.sendRequest("POST", url, fData, onComplete, clientParams);\n }\n};\n//}}}\n \n
/***\n|''Name:''|~DC3.LightBox|\n|''Description:''|LightBox support library|\n|''Date:''|Dec 25, 2006|\n|''Source:''|http://solo.dc3.com/tw/#LightBoxPlugin|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.0.1|\n|''~CoreVersion:''|2.1.x|\n|''Browser:''|Firefox 1.5/2.0; Internet Explorer 6.0/7.0; Safari|\n|''Require:''|LightBoxCSS (see below), support HTML in MarkupPreHead (see below), access to icon images in subdir ''im'' (showAlert() only)|\n!Description\nThis plugin implements a lightbox widget for ~TiddlyWiki. Via Javascript, you can display any HTML div in the lightbox, or use "canned" divs for displaying HTML message in a box or an alert with icon. The lightbox is closed by either clicking the X-icon or anywhere outside the lightbox. Only one lightbox can be active at a time. See the usage section below.\n!!Usage\nThis plugin is a __library__, not a macro. Thus, it must be tagged {{{systemConfig}}}, but it does not support macro invocation. It is callable only from Javascript so the [[InlineJavascriptPlugin|http://www.tiddlytools.com/#InlineJavascriptPlugin]] is a virtual necessity!\n|!Usage|!Sample Javascript|\n|Display HTML message|{{{DC3.LightBox.showContent("Some <i>message</i>");}}}|\n|Display alert|{{{DC3.LightBox.showAlert("ok", "All is well");}}}|\n|Display any DIV in a lightbox|{{{DC3.LightBox.showBox("myLightBox");}}}|\n|Close current lightbox|{{{DC3.LightBox.hideBox()}}}|\n*The frame for the showContent() and showAlert() methods should expand to enclose text, but this happens only on IE and not FireFox. To be safe, just keep your messages short and use showBox() and your own HTML div for "big" messages etc.\n*The first parameter to showAlert() is the icon name. This is simply translated to {{{im/icon.png}}}. The standard icon image files (below) are used with icon strings of "error, "info", "ok", "question", and "warning". \n*The generalized showBox() method can be used to display images, media players, whatever you want! Just make up the HTML div, give it an id, then pass that ID to showBox().\n!!Advanced Usage - onClose\nAll three of the above methods support an optional parameter onClose, supplied as the last parameter in a call. It must evaluate to a function which is called (with no parameters) when the lightbox is about to close. If the onClose() function returns false, the lightbox will not be closed. For example \n{{{\nDC3.LightBox.showAlert("warning", "Something <em>bad</em> is about to happen", soundBuzzer);\n}}}\n!Installation\n#Paste this entire tiddler into a tiddler called DC3.LightBox and tag it {{{systemConfig}}}.\n#Paste the Required CSS (below) into a tiddler called LightBoxCSS and tag it {{{systemContent}}}.\n#Paste the content for MarkupPreHead (below) into MarkupPreHead.\n#Put the image files (below) into a subfolder ''im'' relative to the location of the TiddlyWiki. \n!!!Required CSS\nPaste into a tiddler called LightBoxCSS.\n{{{\n#lightBoxOverlay {\n position:absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 90; \n background-color: #000;\n -moz-opacity: 0.6;\n opacity: .60;\n filter: alpha(opacity=60);\n}\n#lightBoxOverlay[id]{ \n position: fixed;\n}\n\ndiv.lightBox {\n background: #2d2d2d;\n color: #fff;\n border: 2px solid #eee;\n}\n\nimg.lightBoxClose {\n position: absolute;\n top: -5px;\n right: -5px;\n margin: 0px;\n cursor: pointer;\n}\n\ndiv.lightBoxAlert {\n width: 300px;\n height: 64px;\n background: #2d2d2d;\n color: #fff;\n padding: 10px;\n border: 2px solid #eee;\n}\n\ndiv.lightBoxAlertIcon {\n position: absolute;\n top: 8px;\n left: 8px;\n width: 48px;\n height: 48px;\n}\n\ndiv.lightBoxAlertMessage {\n margin-left: 56px;\n margin-top: 16px;\n}\n}}}\n!!!Content for MarkupPreHead\n{{{\n<!-- LightBox translucent overlay -->\n<div id="lightBoxOverlay" onclick="DC3.LightBox.hideBox()" style="display:none"></div>\n<!-- General use simple LightBox -->\n<div class="lightBox" id="lightBox" style="display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n <div id="lightBoxContent"></div>\n</div>\n<!-- General use Alert LightBox -->\n<div class="lightBoxAlert" id="lightBoxAlert" style="display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n <div class="lightBoxAlertIcon"><img id="lightBoxAlertIcon" src="runtime" alt="runtime" title="runtime"></div>\n <div class="lightBoxAlertMessage" id="lightBoxAlertMessage">runtime</div>\n</div>\n<!-- End of LightBox -->\n}}}\n!!!Images (close box and alert icons)\nThese must be in a subfolder ''im'' below the ~TiddlyWiki. You can get the images by right clicking on the links and //save target/link//, or right clicking on the images and //save image//. @@Note: The images below will display ugly in IE6, but they will display nice (with transparency) in the lightbox alerts, owing to the use of the DXFilters for transparency in the code.@@\n|[[close.gif|im/close.gif]]|[img[im/close.gif]]|[[error.png|im/error.png]]|[img[im/error.png]]|\n|[[info.png|im/info.png]]|[img[im/info.png]]|[[ok.png|im/ok.png]]|[img[im/ok.png]]|\n|[[question.png|im/question.png]]|[img[im/question.png]]|[[warning.png|im/warning.png]]|[img[im/warning.png]]|\n!!Credits\nThis TiddlyWiki library and CSS is an amalgamation of the techniques and code described in the following: \n* [[Original LightBox|http://www.huddletogether.com/projects/lightbox/]] by Lokesh Dhakar\n* [[Lightweight LightBox|http://www.pjhyett.com/posts/190-the-lightbox-effect-without-lightbox]] that can show any DIV by PJ Hyett\n* [[Better Modal Windows with LightBox|http://blog.feedmarker.com/2006/02/12/how-to-make-better-modal-windows-with-lightbox/]] by Bruno\nBruno's CSS for the overlay is much better than the first two, it is independent of any PNG image(s) and does not have CSS quirk-hacks for IE, nor does it use IE's DXFilters for PNG transparency. Of course for IE6, the DXFilters are used in the Javascript!\n!!Revision History\n<<<\n''2006.12.02 [1.0.1]'' Initial creation\n''2006.12.03 [1.0.1]'' hideBox() no longer takes //id// just closes currently open box. Needed for overlay click/close.\n''2006.12.03 [1.0.1]'' Add support for special Alert type LightBox with switchable icon. Hack IE for alpha transparency. See inline comments. Add showContent(html), showAlert(icon, message)\n''2006.12.04 [1.0.1]'' Ignore show calls if box is already displayed. Optional callback for hideBox(), can prevent hiding. Allows modal box to be set up by caller.\n''2006.12.25 [1.0.1]'' Documentation and installation instructions\n<<<\n!!Code\n***/\n//{{{\n\n// Initialize style sheet from tiddler\nrefreshStyles("LightBoxCSS");\n\nif (!window.DC3) window.DC3 = {};\nwindow.DC3.LightBox = \n{\n //\n //Internal proterties\n //\n _curBox: null, // [sentinel]\n _onClose: null, // [sentinel]\n _alertImgDiv: null, // [sentinel]\n _alertImgHTML: null, // [sentinel]\n \n //\n // Public interface\n //\n showContent: function(content, onClose) { // Uses generic LightBox in MarkupPreBody\n if(this._curBox) return; // Ignore if box already showing (typ.)\n document.getElementById("lightBoxContent").innerHTML = content;\n this.showBox("lightBox", onClose);\n },\n \n showAlert: function(icon, message, onClose) { // Uses standard alert LightBox in MarkupPreBody\n if(this._curBox) return;\n var icoElem = document.getElementById("lightBoxAlertIcon");\n icoElem.src = "im/" + icon + ".png"; // Requires icon.png (48 x 48)\n icoElem.title = icon;\n icoElem.alt = icon;\n document.getElementById("lightBoxAlertMessage").innerHTML = message;\n DC3.LightBox.showBox("lightBoxAlert", onClose);\n },\n \n showBox: function(id, onClose) \n {\n if(this._curBox) return;\n this._onClose = onClose; // If valid, call this in hideBox. See comments there!\n //\n // Surprise! In IE, the height:100% in the #overlay CSS definition does\n // not honor the z-order, and calculates the height to be the top margin!\n // So, for IE, I have added this imperfect hack which ,forces the overlay\n // size to the scroll size. This causes funny scrollbar behavior, but the\n // alternatives I tried were really complex. \n //\n // Surprise #2! IE6 doesn't support alpha transparency in PNG images, and \n // I use same for the icons in the LightBox Alert. Another hack needed.\n // We can't just change the DIV from containing an IMG tag to using the\n // bloody MS AlphaImageLoader, we also have to save the original IMG tag\n // because the alert is multi-use: the image to be shown can be changed \n // dynamically. When closing the Lightbox, we restore the original inner\n // IMG tag and clear the filter style. On showing the box, we grab the path\n // to the image file then zap the IMG tag, using the image file path in\n // the filter/AlphaImageLoader. Egad!!!\n //\n var ovly = document.getElementById('lightBoxOverlay');\n if(config.browser.isIE) {\n var h1 = document.documentElement.scrollHeight;\n var h2 = document.documentElement.offsetHeight;\n ovly.style.height = Math.max(h1, h2);\n ovly.style.width = document.documentElement.scrollWidth;\n // Change icon div for IE proprietary\n var alertDivs = document.getElementById(id).getElementsByTagName("div");\n this._alertImgDiv = null;\n for(var i in alertDivs) {\n if(alertDivs[i].className && alertDivs[i].className == "lightBoxAlertIcon") {\n this._alertImgDiv = alertDivs[i];\n break;\n }\n }\n if(this._alertImgDiv) {\n var imgFile = this._alertImgDiv.firstChild.src;\n this._alertImgHTML = this._alertImgDiv.innerHTML; // Saved to allow dynamic change of image file\n this._alertImgDiv.innerHTML = "";\n this._alertImgDiv.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\s"" + imgFile + "\s", sizingMethod=\s"scale\s")";\n }\n }\n ovly.style.display = 'block';\n this._center(id);\n this._curBox = id;\n return false;\n },\n \n hideBox: function()\n {\n if(!this._curBox) return;\n if(this._onClose && this._onClose() === false) // If onClose() returns false, refuse to close\n return false;\n document.getElementById(this._curBox).style.display = 'none';\n document.getElementById('lightBoxOverlay').style.display = 'none';\n this._curBox = null; // Allow show calls once again\n // Restore original non-IE image. Code may dynamically change image file!\n if(this._alertImgDiv) {\n this._alertImgDiv.innerHTML = this._alertImgHTML;\n this._alertImgDiv.style.filter = "";\n }\n return false;\n },\n \n //\n // Internal methods\n //\n _getDimensions: function(elem) // Lifted from Prototype and made independent\n {\n if(elem.style.display != 'none')\n return { width: elem.offsetWidth, height: elem.offsetHeight};\n \n // All *Width and *Height properties give 0 on elements with display none,\n // so enable the elem temporarily\n var els = elem.style;\n var origVis = els.visibility;\n var origPos = els.position;\n els.visibility = 'hidden';\n els.position = 'absolute';\n els.display = '';\n var origW = elem.clientWidth;\n var origH = elem.clientHeight;\n els.display = 'none';\n els.position = origPos;\n els.visibility = origVis;\n return {width: origW, height: origH};\n },\n \n //\n // This is rather big. I'll have to look at more elegant way(s)\n // of doing this... some day! :-)\n //\n _center: function(elem)\n {\n try{\n elem = document.getElementById(elem);\n }catch(e){\n return;\n }\n \n var my_width = 0;\n var my_height = 0;\n \n if ( typeof( window.innerWidth ) == 'number' ){\n my_width = window.innerWidth;\n my_height = window.innerHeight;\n } else if ( document.documentElement && \n ( document.documentElement.clientWidth ||\n document.documentElement.clientHeight ) ){\n my_width = document.documentElement.clientWidth;\n my_height = document.documentElement.clientHeight;\n }\n else if ( document.body &&\n ( document.body.clientWidth || document.body.clientHeight ) ){\n my_width = document.body.clientWidth;\n my_height = document.body.clientHeight;\n }\n \n elem.style.position = 'absolute';\n elem.style.zIndex = 99;\n \n var scrollY = 0;\n \n if ( document.documentElement && document.documentElement.scrollTop ){\n scrollY = document.documentElement.scrollTop;\n }else if ( document.body && document.body.scrollTop ){\n scrollY = document.body.scrollTop;\n }else if ( window.pageYOffset ){\n scrollY = window.pageYOffset;\n }else if ( window.scrollY ){\n scrollY = window.scrollY;\n }\n \n var elementDimensions = this._getDimensions(elem);\n \n var setX = ( my_width - elementDimensions.width ) / 2;\n var setY = ( my_height - elementDimensions.height ) / 2 + scrollY;\n \n setX = ( setX < 0 ) ? 0 : setX;\n setY = ( setY < 0 ) ? 0 : setY;\n \n elem.style.left = setX + "px";\n elem.style.top = setY + "px";\n \n elem.style.display = 'block';\n }\n};\n//}}}\n
Here are some examples of using [[DC3.LightBox]] which you can use to get the idea. Everyone wants to show pictures (well, and maybe videos), so here are some examples of those:\n\n<html>\n<div id="myImageBox" style="background:black;color:yellow;border:2px solid white;display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n<div><img src="gemini-sm.jpg"><br><div align="center">Gemini-IX Lifts Off</div></div>\n</div>\n<a href="javascript:;" onclick="DC3.LightBox.showBox('myImageBox')">Gemini-9 Lifts Off</a></html>\n<html><div id="myYVideo" style="display:none">\n<div><object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/uVq2EqTC3yY"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/uVq2EqTC3yY" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed></object></div>\n</div>\n<a href="javascript:;" onclick="DC3.LightBox.showBox('myYVideo')">Some YouTube junk (short, no close box)</a></html>\n\nNext, a couple of usages of the 'standard' lightboxes for showing text/HTML and alerts:\n\n<html><a href="javascript:;" onclick="DC3.LightBox.showContent('<div style=\s'padding:10px;\s'>This uses the <b>showContent()</b> method.</div>');">Here is some simple text in a div with 10px padding</a></html>\n<html><a href="javascript:;" onclick="DC3.LightBox.showAlert('ok', 'This uses the <b>showAlert()</b> method with the \s'ok\s' icon specified.')">Here is an alert with icon</a></html>\n\nNow for some examples using the basic showBox() method for the ultimate in flexibility. For this, you need to write your own box div, give it an id then call showBox() from a link (or other Javascript) similar to the examples above. Look at the supplied lightbox divs in MarkupPreBody and the (hidden) divs below to get the idea. It's pretty simple once you get it. If you don't style the lightbox, it will be transparent! The style display:none is vital to keep the lightbox div hidden till you want it to appear.\n\n<html><div class="lightBox" id="myLB" style="display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n<div style="padding: 10px">This uses the standard lightBox CSS class and puts the close box in the standard position. It looks like the showContent() box, but you can see the div and its contents.</div>\n</div>\n<a href="javascript:;" onclick="DC3.LightBox.showBox('myLB')">A box that uses the standard showContent() div in MarkupPreBody</a></html>\n<html><div id="myLB2" style="background:white;color:red;border:5px solid black;display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n<div style="padding: 10px">This uses an inline-styled div and puts the close box in the standard position.</div>\n</div>\n<a href="javascript:;" onclick="DC3.LightBox.showBox('myLB2')">A box that uses an inline-styled div</a></html>
//WORK IN PROGRESS//\n\nOver the last month, I've gone through the process of setting up my workbench for developing plugins. Along the way I've learned some lessons and developed an approach to working towards a completed plugin. I'm not going to describe the structure of a plugin (what the "handler" does, how to add worker functions, etc). Assuming you know //what// to write in a plugin, here's //how// I go about it:\n#Write the plugin in a real editor ([[PrimalScript|http://www.sapien.com/]] highly recommended), save as //plugin//.tid.js.\n#Run [[JavaScript Lint|http://www.javascriptlint.com/]] on it until you have no errors or warnings, and all missing variables you don't expect are resolved. This is //critical// to efficiency!\n#Include tests/examples within the plugin code \n#Paste into a tidder tagged as systemConfig\n#Save, reload the TW\n#Open the new tiddler and discover your mistakes\n#Put a {{{debugger;}}} statement at the beginning of the handler and start walking through it\n#Find and fix the problems using your brain and when necessary, the debugger. Consider using the debugger as a brain failure.\n#Make changes in the original source code (the real editor)\n#Loop back to #1 (yeah, all the way back!)\n!Script Editor\nIt's really useful to do your plugin writing and editing in a real editor, not the textarea in TW. YOu'll eventually go mad trying to use the latter. As I mentioned above I recommend [[PrimalScript|http://www.sapien.com/]]. It has full syntax coloring, and limited "intellisense" for known Javascript objects. \nAlternatively, you can use the script editor that is included with Microsoft Office 2000 and later. See the next section on how to install the Office script debugger and editor.\n!Debugger\nIn short, __use Internet Explorer together with the Microsoft Office Script Editor/Debugger!__ The Office script debugger can be used outside Office as a general purpose script debugger. Instructions for installation are given below: \n#Install the Office script debugger. In the Office setup program, click Add or Remove Features, then select \n*Office Tools\n**HTML Source Editing <-- INSTALL\n***Web Scripting <-- INSTALL\nOnce this installation is complete, you'll have the debugger installed. \n# For Office 2003/XP\nCreate a shortcut to ~MSE7.EXE (the debugger) and ~MSE10.CHM (the help file for the debugger) on your desktop or start menu. On my system, these ended up at: \n{{{\nC:\sProgram Files\sMicrosoft Office\sOffice11\sMSE7.EXE\nC:\sProgram Files\sMicrosoft Office\sOffice11\s 1033\sMSE10.CHM\n}}}\n# For Office 2000\nCreate a shortcut to ~MSE.EXE (the debugger) and ~CSOF98.COL (the help file for the debugger) on your desktop or start menu. On my system, these ended up at:\n{{{\nC:\sProgram Files\sVisual Studio\sCommon\sIDE\sIDE98\sMSE.EXE\nC:\sProgram Files\sVisual Studio\sCommon\sIDE\sIDE98\sMSE\s1033\sCSOF98.COL\n}}}\nI already had Visual Studio installed, they may end up at some other location on your system if you don't have Visual Studio. \n#Now open the Office debugger. Select the Tools menu, Options... and under Debugger, check General/~Just-In-Time-Debugger, Script/Attach to programs running on this machine, as well as Script/~Just-In-Time-Debugging. Exit from MSE. \n#The debugger functions as a script editor also, nicer than Notepad. Try opening a document from the File menu and see what happens. \n#To force a break into the debugger, put a {{{debugger;}}}statement into your script . When you hit the {{{debugger;}}} statement or encounter an error, you'll get a "do you want to debug?" alert. Answer Yes, then choose "Script" (the only choice) in the next popup and you'll be in the MSE debugger with the source showing. \nYou cannot edit the source at that point, but you can inspect variables, step through the code, etc. To get past a line that raises an error, use the "Set Next Statement" button to move the execution point past the statement, and if needed type in a corrected statement in the Immediate window. __Arrange the debugger windows so that you have a large window for Locals and make sure the Call Stack and Immediate window are available__ I put the immediate window right below the source, and the Locals window to the right expanded way open. \n<<<\nThe ~FireFox ''~FireBug'' debugger cannot be used for plugins as it cannot show you eval() code. IE/MSE can. @@Late Flash@@ Udo Borkowski just told me about a trick where you develop the plugin in external source and load it via a {{{<script src="xxx.js">}}} tag in ~MarkupPostBody. This looks really useful, and allows ~FireBug to get to it. But, as you'll see in the next section, MSE reallyopens the world up to you in the Locals window!\n<<<\n!Using the Debugger\n
[[New Stuff - LightBoxes]]\n[[Welcome]]\n[[Releases & Events]]
/***\n|''Name:''|LegacyStrikeThroughPlugin|\n|''Description:''|Support for legacy (pre 2.1) strike through formatting|\n|''Version:''|1.0.1|\n|''Date:''|Jul 21, 2006|\n|''Source:''|http://www.tiddlywiki.com/#LegacyStrikeThroughPlugin|\n|''Author:''|MartinBudden (mjbudden (at) gmail (dot) com)|\n|''License:''|[[BSD open source license]]|\n|''CoreVersion:''|2.1.0|\n|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|\n\n***/\n\n//{{{\n\n// Ensure that the LegacyStrikeThrough Plugin is only installed once.\nif(!version.extensions.LegacyStrikeThroughPlugin)\n {\n version.extensions.LegacyStrikeThroughPlugin = true;\n\nconfig.formatters.push(\n{\n name: "legacyStrikeByChar",\n match: "==",\n termRegExp: /(==)/mg,\n element: "strike",\n handler: config.formatterHelpers.createElementAndWikify\n});\n\n} // end of "install only once"\n//}}}\n
/*{{{*/\n#lightBoxOverlay {\n position:absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: 90; \n background-color: #000;\n -moz-opacity: 0.6;\n opacity: .60;\n filter: alpha(opacity=60);\n}\n#lightBoxOverlay[id]{ \n position: fixed;\n}\n\ndiv.lightBox {\n background: #2d2d2d;\n color: #fff;\n border: 2px solid #eee;\n}\n\nimg.lightBoxClose {\n position: absolute;\n top: -5px;\n right: -5px;\n margin: 0px;\n cursor: pointer;\n}\n\ndiv.lightBoxAlert {\n width: 300px;\n height: 64px;\n background: #2d2d2d;\n color: #fff;\n padding: 10px;\n border: 2px solid #eee;\n}\n\ndiv.lightBoxAlertIcon {\n position: absolute;\n top: 8px;\n left: 8px;\n width: 48px;\n height: 48px;\n}\n\ndiv.lightBoxAlertMessage {\n margin-left: 56px;\n margin-top: 16px;\n}\n/*}}}*/
[img[RedMtn-sm.jpg]]\n[[Welcome]]\n[[Releases & Events]]\n[[My Plugins]]\n[[Modified Plugins|Plugins Modified by Me]]\n[[Publications]]\n
<!--{{{-->\n<!-- LightBox translucent overlay -->\n<div id="lightBoxOverlay" onclick="DC3.LightBox.hideBox()" style="display:none"></div>\n<!-- General use simple LightBox -->\n<div class="lightBox" id="lightBox" style="display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n <div id="lightBoxContent"></div>\n</div>\n<!-- General use Alert LightBox -->\n<div class="lightBoxAlert" id="lightBoxAlert" style="display:none">\n <img class="lightBoxClose" src="im/close.gif" onclick="DC3.LightBox.hideBox()" alt="Close" title="Close this window" />\n <div class="lightBoxAlertIcon"><img id="lightBoxAlertIcon" src="runtime" alt="runtime" title="runtime"></div>\n <div class="lightBoxAlertMessage" id="lightBoxAlertMessage">runtime</div>\n</div>\n<!-- End of LightBox -->\n<!--}}}-->
!!Macros\n__Used with HTML forms in tiddlers:__\n* PostFormPlugin - Replaces normal form submit button with a wikified button that posts from HTML form(s) in tiddler without refreshing the whole TiddlyWiki. Optional "are you sure" popup and optional tiddler refresh after post. Requires [[DC3.Ajax]]\n* PersistentFormPlugin - Remember values of HTML form(s) in tiddler via cookies, with optional lifetime control and optional "forget/reset" button.\n__Refresh controls for tiddlers with dynamic content and <iframe>:__ See [[Magic with <iframe>|http://solo.dc3.com/tw/HandlingIframes.html]]\n* RefreshTiddlerPlugin - Tiddler refresh button and optional periodic refresh checkbox\n* RefreshIFramePlugin - <iframe> refresh button and optional periodic refresh checkbox. Refreshes only a single <iframe> within the tiddler.\n!!Libraries\n* [[DC3.Ajax]] - HTTP support including field encoding and post for forms. Loosely patterned after [[.BidiX.Ajax.|http://tiddlywiki.bidix.info/#.BidiX.Ajax]]\n* [[DC3.LightBox]] - Support for LightBox displays on TiddlyWiki\n
Check out the [[DC3.LightBox Examples]], and if you like it, grab the [[DC3.LightBox]] library. Be sure to get the images (see the documentation section of the plugin!).
/***\n|''Name:''|PersistentFormPlugin|\n|''Description:''|Save HTML form states in cookies|\n|''Date:''|Oct 18, 2006|\n|''Source:''|http://solo.dc3.com/tw/index.html#PersistentFormPlugin|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.1.0|\n|''~CoreVersion:''|2.0.11, 2.1.0|\n|''Browser:''|Firefox 1.5; Internet Explorer 6.0; Safari|\nThis macro adds field persistence to HTML forms embedded in tiddlers, and an optional reset button to clear the form and erase any persisted field values. See the [[PostFormPlugin]]. Persistence is done using the same scheme that ~TiddlyWiki's options use, cookies. \n!Usage\nIf you have only one HTML form in your tiddler and don't need a reset button, simply include the macro call {{{<<PersistentForm>>}}}. \n\nIf you want to include a reset button, which will clear the form and the saved values //for that form only//, specify the third parameter, which is the label for the button and the fourth parameter, which is its tooltip, for example:\n\n{{{<<PersistentForm "formId" "1" "Forget" "Reset and forget saved form values">>}}}\n\nThis will cause a button labeled "Forget" to appear at the macro invocation location, apply to form "formId, and it will keep the saved form values for 1 day.\n!!Parameters\n# (optional) ID of form to be persisted (default is first or only form in tiddler)\n# (optional) lifetime of persistence cookies, days (default = 7)\n# (optional) Label of reset button for the form (default = no reset button)\n# (optional) Tooltip for reset button (if specified in param 3)\n!Notes\nThe value names are //not// uniquified to each form, //on purpose//. Thus, it is possible for a TiddlyWiki with several forms to share the same information. When someone fills out one of those forms, the same info will appear in the other forms as long as the form field names in both are the same. You are responsible for choosing form field names that are unique to each form if you want this isolation.\n\nA single cookie is baked by this plugin. It is named {{{pfc}}} and has a value that is the concatenation of all persisted form field name/value pairs as {{{name:value&name:value...}}}\n!!Example\nChange some of the fields, close and reopen this tiddler. See that the fields are restored. Then click the forget button, see the form clear. Close and reopen this tiddler again and see that the form is still cleared.\n<html><form id="myForm">\nText: <input type="text" name = "txt" width="40" value=""></input>\n<input type="checkbox" name="ckbox">Check this!</input><br>\nPassword: <input type="password" name="pass" value=""></input><br>\nSingle select: <select name="sel">\n<option value="a">Type A</option>\n<option value="b">Type B</option>\n<option value="c">Type C</option>\n</select> Multiple select: \n<select multiple size="3" name="selmult">\n<option value="1">Type 1</option>\n<option value="2">Type 2</option>\n<option value="3">Type 3</option>\n</select><br>\n<input type="radio" name="rbtn" value="rb1">Button 1</input><br>\n<input type="radio" name="rbtn" value="rb2">Button 2</input><br>\n<input type="radio" name="rbtn" value="rb3">Button 3</input><br>\n<textarea name=bigtext rows="5" cols="10"></textarea><br>\n</form></html><<PersistentForm "myForm" "1" "Forget" "Forget saved values">>\n!!Revision History\n<<<\n''2006.09.20 [1.0.1]'' Initial creation\n''2006.09.29 [1.0.2]'' Unsupported elements just skipped, allows submit etc. to be present.\n''2006.09.29 [1.0.3]'' Optional param form id to support multiple forms. Optional param to set expiry, now defaults to 7 days. New reset() method to delete all cookies for the target form.\n''2006.10.03 [1.0.4]'' Fix initialization of select-multiple fields, add optional button for resetting form and forgetting cookies, validate on TW 2.1.0, improve doc.\n''2006.10.18 [1.1.0]'' Embed all form data into a single cookie.\n<<<\n!!Code\n***/\n//{{{\nversion.extensions.PersistentForm = {\n major: 1, minor: 1, revision: 0,\n date: new Date(2006, 10, 3), \n type: 'macro',\n source: "http://solo.dc3.com/tw/index.html#PersistentFormPlugin"\n};\n\nconfig.macros.PersistentForm = \n{\n expiry: 7, // Expiry, days (used in onChange())\n \n persistDict: { }, // Persistence data cache\n \n //\n // Turn PersistentForm cookie data into an associative array\n //\n loadPersistDict: function()\n {\n // Find pfc cookie\n var cookies = document.cookie.split(";");\n for(var i = 0; i < cookies.length; i++)\n {\n var p = cookies[i].indexOf("=");\n if(p != -1)\n {\n var name = cookies[i].substr(0, p).trim();\n if(name.substr(0, 3) == "pfc") // Found the 'pfc' cookie\n {\n var value = cookies[i].substr(p + 1).trim(); // This has the form contents\n var fc = value.split("&");\n for(i = 0; i < fc.length; i++)\n {\n p = fc[i].indexOf(":");\n name = fc[i].substr(0, p).trim();\n if(name !== "") {\n value = fc[i].substr(p + 1).trim();\n this.persistDict[name] = value;\n }\n }\n return;\n }\n }\n }\n },\n \n storePersistDict: function()\n {\n var c = "pfc=";\n var empty = true;\n for(var name in this.persistDict) \n c += name + ":" + this.persistDict[name] + "&";\n if(c.length > 4) {\n c = c.substr(0, c.length - 1); // Remove trailing '&'\n empty = false;\n }\n c += "; expires=" +\n new Date(new Date().getTime() + (86400000 * this.expiry)).toGMTString() +\n "; path=/";\n document.cookie = c;\n if(empty) // Don't save empty cookie\n document.cookie = "pfc=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/";\n },\n \n getTargetForm: function(formId, place)\n {\n var forms = place.getElementsByTagName("form");\n if(forms.length === 0) {\n displayMessage("PersistentForm: No form in tiddler!");\n return null;\n }\n var form = null; // Find the target form\n if(formId === "") {\n form = forms[0];\n } else {\n for(var i = 0; i < forms.length; i++) {\n if(forms[i].id == formId) {\n form = forms[i];\n break;\n }\n }\n }\n if(form === null) {\n displayMessage("PersistentForm: No form '" + formId + "' in tiddler.");\n return null;\n }\n return form;\n },\n \n handler: function(place, macroName, params, wikifier, paramString, tiddler) \n {\n var formId = params[0] ? params[0] : ""; // Empty means 1st/only form\n this.expiry = params[1] ? params[1] : 7; // Default to 7 day\n var label = params[2] ? params[2] : ""; // Default to no forget button\n var prompt = params[3] ? params[3] : ""; // Empty tooltip\n \n var form = this.getTargetForm(formId, place);\n if(form === null) return;\n this.loadPersistDict();\n // Enumerate form elements and set values as possible\n var nElem = form.elements.length;\n for(var i = 0; i < nElem; i++) {\n var elem = form.elements[i];\n var elemPersistVal = this.persistDict ? this.persistDict[elem.name] : ""; // Handle no cookie case\n switch(elem.type) {\n case "text":\n case "password":\n case "textarea":\n if(elemPersistVal) elem.value = unescape(elemPersistVal);\n elem.onkeyup = this.onChange;\n break;\n case "select-one":\n if(elemPersistVal) elem.value = unescape(elemPersistVal);\n elem.onchange = this.onChange;\n break;\n case "radio":\n elem.checked = (elemPersistVal == elem.value);\n elem.onclick = this.onChange;\n break;\n case "checkbox":\n if(elemPersistVal) elem.checked = (elemPersistVal == "true");\n elem.onclick = this.onChange;\n break;\n case "select-multiple":\n if(elemPersistVal) {\n var sels = elemPersistVal.split(","); // Array of selected option #s\n for(var j = 0; j < elem.options.length; j++)\n elem.options[j].selected = sels.contains(j.toString());\n }\n elem.onchange = this.onChange;\n break;\n default: // Others unsupported \n //displayMessage("PersistentForm: " + elem.type + " form elements not supported.");\n break; // Keep going\n }\n }\n \n if(label !== "")\n createTiddlyButton(place, label, prompt, \n function() {\n config.macros.PersistentForm.reset(place, form);\n return false;\n } );\n },\n \n onChange: function(e)\n {\n if (!e) e = window.event; // Ugh!\n var elem = e.target;\n if(!elem) elem = e.srcElement; // Ugh Ugh!\n var val;\n switch(elem.type)\n {\n case "text":\n case "password":\n case "select-one":\n case "radio":\n case "textarea":\n val = escape(elem.value);\n break;\n case "checkbox":\n val = elem.checked ? "true" : "false";\n break;\n case "select-multiple": // "i,l,n" #s of sel'd items\n var sels = "";\n for(var j = 0; j < elem.options.length; j++) {\n if(elem.options[j].selected) // Encode only selected (may be none!)\n sels += j + ",";\n }\n if(sels) sels = sels.substr(0, sels.length -1); // Remove trailing comma\n val = sels;\n break;\n }\n config.macros.PersistentForm.persistDict[elem.name] = val;\n config.macros.PersistentForm.storePersistDict();\n },\n \n //\n // Delete cookies for given form/owner, clear the form\n //\n reset: function(place, form)\n {\n if(form === null) return;\n var nElem = form.elements.length;\n for(var i = 0; i < nElem; i++)\n delete this.persistDict[form.elements[i].name];\n this.storePersistDict();\n form.reset();\n }\n};\n//}}}\n
These are plugins that I have modified or fixed for one reason or another. See the edit tracks in the plugins for details.\n\n[[SortableGridPlugin]]
/***\n|''Name:''|PostFormPlugin|\n|''Description:''|Post from form, wikify response|\n|''Date:''|Oct 24, 2006|\n|''Source:''|http://solo.dc3.com/tw/index.html#PostFormPlugin|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.1.3|\n|''~CoreVersion:''|2.0.11, 2.1.0|\n|''Browser:''|Firefox 1.5; Internet Explorer 6.0; Safari|\n|''Require:''|[[DC3.Ajax|http://solo.dc3.com/tw/index.html#DC3.Ajax]] 1.0.5+|\n!!Description\nThis macro replaces the normal submit button for an HTML form contained in a tiddler. More than one form may appear in the tiddler, and this macro may be invoked on any of them. In addition, multiple invocations of this macro may appear in a tiddler, allowing you to provide posting from more than one form in a tiddler. \n\nThe macro invocation is replaced by wikified/styled button. The button's label and tooltip can be optionally specified as macro parameters. Clicking this button causes the contents of the form specified by the first (required) parameter to be ~POSTed to the URI that is the second (required) macro parameter. A standard message appears stating that the form data has been posted, then a second message appears when the response is received.\n\nThe macro also supports an optional parameter which causes a "confirm" popup to appear with specified text and one which causes the tiddler to be automatically refreshed following completion of the form post cycle.\n!!Form Search Scope\nThe macro searches for its form within the scope of the macro handler's 'place' parameter. Normally, this is the tiddler viewer area. However, it is possible to include forms within a wikitext object such as a table cell. In this case, only forms within that table cell will be found by the macro (which also needs to be in that same table cell). This has some implications:\n# The form invocation must be within the same scope as the form it is controlling.\n# If only one form appears within a scope it need not be named, and the first parameter (the form name, see below) may be "".\n!!Usage\nCreate one or more forms in the tiddler, using {{{<html>}}} tags to enclose the form as usual. The form does not need to have {{{action}}} or {{{method}}} attributes, {{{<form>}}} is enough. If you have more than one form in the scope, you must give each one a name, for example, {{{<form name='form1'>}}}. Add form elements (text fields, checkboxes, etc.). each with a name and a value, per HTML. \n\nAt the location you want the post button to appear, invoke the PostForm macro. Remember the invocation must be in the same scope as the form! The macro has several parameters. If you need to include a parameter as a placeholder, use "":\n\n|!Param|!Description|\n|''1''|The name of the form, or "". If "" is given, the first form in the scope is posted even though there may be multiple forms in the scope.|\n|''2''|The URI of the remote form processor (typically a PHP, ASP, or CGI page). See the URI Formats section below.|\n|''3''|(optional) The label of the submit button, default "Submit"|\n|''4''|(optional) The tooltip of the submit button, default "Submit form to server"|\n|''5''|(optional) The text for a yes/no "confirm" popup that will be displayed after clicking the submit button and before posting the form to the server, default "" (no popup). Use this for an "Are you sure", for example.|\n|''6''|(optional) If given and not "false", the tiddler will be refreshed after the server has responded to the post. This can be very useful when combined with the RefreshTiddlerPlugin!|\n!!URI Formats\nThe second parameter is the URI of the remote form perocessor. If the ~TiddlyWiki itself is being read via HTTP from that same server and folder, then only the name of the form processor need be given. This approach makes it possible to move the ~TiddlyWiki and its form processors from one place to another on the net, without having to edit the URI parameter(s) of form post buttons. If the ~TiddlyWiki is being read from local disk, then you'll need to give the complete URI to the remote form processor. If you are unsure, just give the complete URI of the form processor.\n!!Security Issues\nYou may encounter security issues when using this plugin. Suppose you are reading the ~TiddlyWiki from one server, yet the ~PostForm macro specifies a form processor on a different server (different domain name). By default, most browsers will reject this cross-site operation. You may be able to override it, but in general this sort of thing is best avoided!\n!!Remote Form Processor\nThe remote form processor (e.g. PHP, ASP, CGI) is expected to return nothing or wikitext with content type text/plain. If any text is returned, it is wikified and inserted //temporarily// at the end of the tiddler. If the tiddler is closed and reopened, the response text will be gone. If the form is submitted more than once without closing the tiddler, multiple responses will be appended to the tiddler display. If the auto-refresh parameter is supplied, the tiddler will be refreshed after the post and any response text will be discarded.\n!!Examples\nHere's a simple form which is set to post to a mythical PHP page.\n{{{\n<html><form>\n<input type="text" name = "txt" width="40" value="Yo Mama!"></input>\n<input type="checkbox" name="ckbox" checked>Check this!</input>\n</form></html><<PostForm "" "formtest.php">>\n}}}\n<html><form>\n<input type="text" name = "txt" width="40" value="Yo Mama!"></input>\n<input type="checkbox" name="ckbox" checked>Check this!</input>\n</form></html><<PostForm "" "formtest.php">>\n!!Revision History\n<<<\n''2006.09.20 [1.0.1]'' Initial creation\n''2006.09.26 [1.1.1]'' Support multiple forms in a tiddler, ask, auto-refresh, Lint checked. Add documentation.\n''2006.10.03 [1.1.2]'' Validate with TW 2.1.0. No changes needed.\n''2006.10.24 [1.1.3]'' Comment out debugging displayMessage() calls\n<<<\n!!Code\n***/\n//{{{\nversion.extensions.PostForm = {\n major: 1, minor: 1, revision: 2,\n date: new Date(2006, 10, 3), \n type: 'macro',\n source: "http://solo.dc3.com/tw/index.html#PostFormPlugin"\n};\n\nconfig.macros.PostForm = \n{\n onComplete: function(text, params) \n {\n //displayMessage("Post completed successfully.");\n text = text.replace(/(^\ss*)|(\ss*$)/g, ""); // Trim off l/t junk!\n if(text)\n wikify(text, params.place);\n if(params.refresh)\n story.refreshTiddler(params.tiddler.title, null, true);\n },\n \n handler: function(place, macroName, params, wikifier, paramString, tiddler) \n {\n var label = params[2] ? params[2] : "Submit";\n var prompt = params[3] ? params[3] : "Submit form to server";\n var ask = params[4] ? params[4] : "";\n var refresh = params[5] ? true : false;\n var complParams = { \n place: place,\n tiddler: tiddler,\n refresh: refresh\n };\n var forms = place.getElementsByTagName("form");\n if(forms.length === 0) {\n displayMessage("PostForm: No form in tiddler!");\n return;\n }\n var form = null;\n if(forms.length == 1 || param[0] === "") { // Only 1 or unspecified\n form = forms[0]; // First form in the tiddler\n } else {\n for(var i = 0; i < forms.length; i++) {\n if(form[i].name == params[0]) {\n form = forms[i]; // Named form\n break;\n }\n }\n if(form === null) { // Not found by name\n displayMessage("PostForm: No form \s"" + param[0] + "\s"in tiddler!");\n return;\n }\n }\n createTiddlyButton(place, label, prompt, \n function () {\n if(ask) { if(!confirm(ask)) return false; }\n //displayMessage("Posting data to server...");\n DC3.Ajax.postForm(form, params[1], \n config.macros.PostForm.onComplete, complParams);\n return false;\n } );\n }\n};\n//}}}\n
Here are some papers I wrote. Some are in separate ~TiddlyWikis or files.\n''2006.12.13'' [[Remote Astronomy with TiddlyWiki as the Web Interface]]\n''2006.10.01'' [[UploadPlugin Receiver Adventures]]\n''2006.09.29'' [[Magic with <iframe>|http://solo.dc3.com/tw/HandlingIframes.html]]\n
/***\n|''Name:''|RefreshIFramePlugin|\n|''Description:''|Refresh an entire tiddler with optional periodic re-refresh|\n|''Date:''|Oct 6, 2006|\n|''Source:''|http://solo.dc3.com/tw/index.html#RefreshIFramePlugin|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.0.4|\n|''~CoreVersion:''|2.0.11, 2.1.0|\n|''Browser:''|Firefox 1.5; Internet Explorer 6.0; Safari|\n|''Require:''|CheckboxPlugin (http://www.TiddlyTools.com/#CheckboxPlugin)|\n!Description\nThis macro provides a tiddler with an <iframe> refresh control. This permits refreshing just the iframe, avoiding flashing due to refreshing the entire tiddler. Normally, a refresh button appears at the location of the macro invocation. Clicking this button causes the contents of the tiddler to be refreshed. Optionally, a checkbox may also be displayed with which the user can enable and disable automatic periodic refresh of the tiddler at an interval specified by the third parameter. Finally, for special applications, a fourth parameter may be given, and if "true" both the button and the checkbox will be hidden, and the tiddler will simply refresh periodically at the interval given by the third parameter.\n\n@@There is a //lot// you can do with iframes in a tiddler@@. For more information see my ~TiddkyWiki [[Magic With <iframe>|http://solo.dc3.com/tw/HandlingIframes.html]]. \n!Usage\nThis plugin supports tiddlers with a single <iframe>, thus it assiciates itself with the first/only <iframe> in a tiddler. To add refreshing, simply invoke the macro (with optional parameters as desired, see below) wherever you want the button and optional checkbox to appear.\n\nThere are four optional parameters:\n|!Parameter|!Description|\n|''1''|(optional) Button label, default "Refresh"|\n|''2''|(optional) Button tooltip, default "Refresh the remote frame"|\n|''3''|(optional) Periodic refresh interval, sec., default 0 (no periodic refresh checkbox)|\n|''4''|(optional) If present and "true" and parameter 3 > 0 all controls will be hidden and the tiddler will refresh periodically whenever it is visible.|\n!!Revision History\n<<<\n''2006.09.22 [1.0.1]'' Initial creation, from RefreshTiddler\n''2006.09.29 [1.0.2]'' Refresh checkbox cookie names different from RefreshTiddler.\n''2006.10.03 [1.0.3]'' Add parameter to hide button and checkbox while doing periodic refresh. Expand documentation, lint check, validate with ~TiddlyWiki 2.1.0\n''2006.10.03 [1.0.4]'' Handle race condition on IE - initial refresh lost. Force refresh at end of handler(). Disable container refreshing for checkbox, see CheckboxPlugin.\n<<<\n!!Code\n***/\n//{{{\nversion.extensions.RefreshIFrame = {\n major: 1, minor: 0, revision: 4,\n date: new Date(2006, 10, 6), \n type: 'macro',\n source: "#RefreshIFramePlugin"\n};\n\nconfig.macros.RefreshIFrame = \n{\n states: { }, // Associative array of refresh states indexed by tiddler name\n \n handler: function(place, macroName, params, wikifier, paramString, tiddler)\n {\n var tidTitle = tiddler.title; // Shortcut\n if(!this.states[tidTitle]) this.states[tidTitle] = { // Array of state objects for refreshed tiddlers\n iframe: null,\n butLabel: "",\n butTooltip: "",\n refInterval: 0,\n doRefresh: false,\n initPerRef: false,\n timerId: 0,\n chkBox: null\n };\n var state = this.states[tidTitle]; // Shortcut\n var iframes = place.getElementsByTagName("iframe");\n if(iframes.length === 0) {\n displayMessage("PostForm: No iframe in tiddler!");\n return;\n }\n state.iframe = iframes[0]; // Only 1st iframe\n state.butLabel = params[0] ? params[0] : "Refresh"; // Make these react to edits\n state.butTooltip = params[1] ? params[1] : "Refresh the remote frame";\n state.refInterval = params[2] ? params[2] : 0; // 0 = no periodic refresh checkbox\n var hidden = params[3] && params[3].toLowerCase() == "true" && state.refInterval > 0;\n if(!hidden) { // Unless want hidden\n var btn = createTiddlyButton(place, state.butLabel, state.butTooltip, this.onButClick);\n btn.name = tidTitle; // Set button name to tiddler name (see onButClick())\n if(state.refInterval > 0) // If periodic refresh wanted\n {\n wikify(" [ =chkPerRefIfr" + tiddler.created.convertToYYYYMMDDHHMM() + // Uniquify chkbox ID\n "{config.macros.RefreshIFrame.states[\s"" + tidTitle + "\s"].chkBox = this; " + \n "this.refresh.container=false;}" +\n "{config.macros.RefreshIFrame.onChkClick(\s"" + tidTitle + "\s");}] " + \n state.butLabel + " every " + state.refInterval + " seconds", place);\n state.timerId = 0;\n if(!state.initPerRef) this.onChkClick(tidTitle); // Simulate checkbox click (state already from cookie)\n }\n } else if(state.refInterval > 0 && !state.initPerRef) { // Hidden, if per ref and not started, start it\n if(state.timerId) clearTimeout(state.timerId);\n this.startRefresh(tidTitle); \n state.initPerRef = true;\n }\n state.iframe.src = state.iframe.src; // Refresh!\n },\n \n onButClick: function(e) \n {\n if(!e) e = window.event;\n var tidTitle = resolveTarget(e).name; // Name is the tiddler name!\n //displayMessage("but " + tidTitle);\n var iframe = config.macros.RefreshIFrame.states[tidTitle].iframe;\n iframe.src = iframe.src; // Refresh!\n return false;\n },\n \n onChkClick: function(tidTitle) \n {\n var state = this.states[tidTitle];\n if(state.chkBox.checked) {\n if(state.timerId) clearTimeout(state.timerId);\n this.startRefresh(tidTitle); \n } else { \n state.doRefresh = false;\n }\n state.initPerRef = true;\n },\n \n startRefresh: function(tidTitle) \n {\n var state = this.states[tidTitle];\n state.doRefresh = true;\n //displayMessage("st " + tidTitle + " " + state.refInterval);\n state.timerId = setTimeout("config.macros.RefreshIFrame.reRefresh(\s"" + \n tidTitle + "\s")", state.refInterval * 1000);\n },\n \n reRefresh: function(tidTitle)\n {\n var state = this.states[tidTitle];\n state.timerId = 0;\n if(!state.doRefresh) return;\n // Kill re-refresh cycle if tiddler closed or edited\n var tidElem = document.getElementById(story.idPrefix + tidTitle); // DON'T GET CUTE! THIS IS CORRECT!\n //**BUGBUG** Hardwired to EditTemplate!\n if(!tidElem || tidElem.attributes['template'].value == "EditTemplate") // Prevent hidden or editing\n {\n state.initPerRef = false;\n return;\n }\n //displayMessage("re " + tidTitle + " " + state.refInterval);\n state.iframe.src = state.iframe.src; // Refresh!\n state.timerId = setTimeout("config.macros.RefreshIFrame.reRefresh(\s"" + \n tidTitle + "\s")", state.refInterval * 1000);\n }\n};\n//}}}\n
/***\n|''Name:''|RefreshTiddlerPlugin|\n|''Description:''|Refresh an entire tiddler with optional periodic re-refresh|\n|''Date:''|Oct 3, 2006|\n|''Source:''|http://solo.dc3.com/tw/index.html#RefreshTiddlerPlugin|\n|''Author:''|Bob Denny ~DC-3 Dreams, SP|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''Version:''|1.0.4|\n|''~CoreVersion:''|2.0.11, 2.1.0|\n|''Browser:''|Firefox 1.5; Internet Explorer 6.0; Safari|\n|''Require:''|CheckboxPlugin (http://www.TiddlyTools.com/#CheckboxPlugin)|\nThis macro provides a tiddler with refresh control. Why would you want this? Perhaps for a webcam feed which is updated every 15, 30, 60 secs etc... or RSS feed from an active news site etc.\n\nAt a minimum, a refresh button appears at the location of the macro invocation. Clicking this button causes the contents of the tiddler to be refreshed. Optionally, a checkbox may also be displayed with which the user can enable and disable automatic periodic refresh of the tiddler at a specified interval.\n\nThere are three optional parameters\n|!Parameter|!Description|\n|''1''|Button label, default "Refresh"|\n|''2''|Button tooltip, default "Refresh this tiddler"|\n|''3''|Periodic refresh interval, sec., default 0 (no periodic refresh checkbox)|\n!!Revision History\n<<<\n''2006.09.13 [1.0.1]'' Initial creation, several days\n''2006.09.22 [1.0.2]'' Make periodic refresh checkbox optional\n''2006.09.29 [1.0.3]'' Refresh checkbox cookie names different from RefreshIframe.\n''2006.10.03 [1.0.4]'' Lint check, validate on TW 2.1.0.\n<<<\n!!Code\n***/\n//{{{\nversion.extensions.RefreshTiddler = {\n major: 1, minor: 0, revision: 4,\n date: new Date(2006, 10, 3), \n type: 'macro',\n source: "#RefreshTiddlerPlugin"\n};\n\nconfig.macros.RefreshTiddler = \n{\n states: { }, // Associative array of refresh states indexed by tiddler name\n \n handler: function(place, macroName, params, wikifier, paramString, tiddler)\n {\n var tidTitle = tiddler.title; // Shortcut\n if(!this.states[tidTitle]) this.states[tidTitle] = { // Array of state objects for refreshed tiddlers\n butLabel: "",\n butTooltip: "",\n refInterval: 0,\n doRefresh: false,\n initPerRef: false,\n timerId: 0,\n chkBox: null\n };\n var state = this.states[tidTitle]; // Shortcut\n state.butLabel = params[0] ? params[0] : "Refresh"; // Make these react to edits\n state.butTooltip = params[1] ? params[1] : "Refresh this tiddler";\n state.refInterval = params[2] ? params[2] : 0; // 0 = no periodic refresh checkbox\n var btn = createTiddlyButton(place, state.butLabel, state.butTooltip, this.onButClick);\n btn.name = tidTitle; // Set button name to tiddler name (see onButClick())\n if(state.refInterval > 0) // If periodic refresh wanted\n {\n wikify(" [ =chkPerRefTid" + tiddler.created.convertToYYYYMMDDHHMM() + // Uniquify chkbox ID\n "{config.macros.RefreshTiddler.states[\s"" + tidTitle + "\s"].chkBox = this;}" +\n "{config.macros.RefreshTiddler.onChkClick(\s"" + tidTitle + "\s");}] " + \n state.butLabel + " every " + state.refInterval + " seconds", place);\n state.timerId = 0;\n if(!state.initPerRef) this.onChkClick(tidTitle); // Simulate checkbox click (state already from cookie)\n }\n },\n \n onButClick: function(e) \n {\n if(!e) e = window.event;\n var tidTitle = resolveTarget(e).name; // Name is the tiddler name!\n //displayMessage("but " + tidTitle);\n story.refreshTiddler(tidTitle, null, true);\n return false;\n },\n \n onChkClick: function(tidTitle) \n {\n var state = this.states[tidTitle];\n if(state.chkBox.checked) {\n if(state.timerId) clearTimeout(state.timerId);\n this.startRefresh(tidTitle); \n } else { \n state.doRefresh = false;\n }\n state.initPerRef = true;\n },\n \n startRefresh: function(tidTitle) \n {\n var state = this.states[tidTitle];\n state.doRefresh = true;\n //displayMessage("st " + tidTitle + " " + state.refInterval);\n state.timerId = setTimeout("config.macros.RefreshTiddler.reRefresh(\s"" + \n tidTitle + "\s")", state.refInterval * 1000);\n },\n \n reRefresh: function(tidTitle)\n {\n var state = this.states[tidTitle];\n state.timerId = 0;\n if(!state.doRefresh) return;\n // Kill re-refresh cycle if tiddler closed or edited\n var tidElem = document.getElementById(story.idPrefix + tidTitle); // DON'T GET CUTE! THIS IS CORRECT!\n //**BUGBUG** Hardwired to EditTemplate!\n if(!tidElem || tidElem.attributes['template'].value == "EditTemplate") // Prevent hidden or editing\n {\n state.initPerRef = false;\n return;\n }\n //displayMessage("re " + tidTitle + " " + state.refInterval);\n story.refreshTiddler(tidTitle, null, true);\n state.timerId = setTimeout("config.macros.RefreshTiddler.reRefresh(\s"" + \n tidTitle + "\s")", state.refInterval * 1000);\n }\n};\n//}}}\n
''2006.12.25'' Published [[DC3.LightBox]] and [[DC3.LightBox Examples]]\n''2006.12.13'' Published [[Remote Astronomy with TiddlyWiki as the Web Interface]]\n''2006.11.22'' Updated PostFormPlugin and it's required [[DC3.Ajax]] plugin.\n''2006.11.04'' Updated SortableGridPlugin //again//, this time for 2.1.3. Only this time I changed it so it will try to run regardless of the 2.1.x revision. Updated PersistentFormPlugin to store all form info as crumbs in one (big) cookie.\n''2006.10.19'' Updated SortableGridPlugin for TW 2.1.1 and 2.1.2. Hijack is unchanged from 2.1.\n''2006.10.06'' Updated RefreshIFramePlugin, race condition on IE sometimes would not initially regresh on opening tiddler, disable container refreshing on the checkbox.\n''2006.10.03'' Released tiddler and iframe refresh controls RefreshTiddlerPlugin and RefreshIFramePlugin. Updated [[Magic with <iframe>|http://solo.dc3.com/tw/HandlingIframes.html]] with updated RefreshTiddlerPlugin.\n''2006.10.03'' Released plugins for HTML forms in tiddlers: PostFormPlugin and PersistentFormPlugin. Released [[DC3.Ajax]] library for PostFormPlugin and others on the way.\n''2006.10.01'' Published [[UploadPlugin Receiver Adventures]]\n''2006.10.01'' Upgraded SortableGridPlugin. Compatible with TW 2.0.11 and 2.1. New features and improvements.\n''2006.09.29'' Published [[Magic with <iframe>|http://solo.dc3.com/tw/HandlingIframes.html]]
I see the flurry of activity with respect to server-based TW and thought I would post a note here to describe what I did. My application uses TW as a "framework" within which to provide remote access to an automated/robotic astronomical observatory. Rather special purpose. Nonetheless, my experiences may be useful to those of you involved in the core development, so here goes:\n!Background\nMy goal was to provide an "authoring environment" TW in which everything is visible, as well as a "user page" in which most everything is hidden except for the user content. This means most stuff in the user TW would be hidden from lists (hideList) and search (hideSearch). \n\nI started with ~UploadPlugin and created a receiver in Active Server Pages, written in ~JavaScript (one of the languages supported by ASP). My server is home brew, and has a home brew ASP engine within it. My observatory control system also uses Windows Scripts for data/image acquisition and control tasks, and they too are primarily written in ~JavaScript (well, "~JScript", really). By including ASP substitution blocks anywhere in the TW (normal tiddlers and plugins) I get a ton of flexibility. See the next section on how authoring (raw) and end user (ASP substituted) versions are managed.\n!Save to Web - Added functionality\nUsing the authoring TW (author.html), you see the "save to web" link. When you save to web, the receiver ASP does more than just save whatever uploaded file and a backup copy of the previous. If the uploaded file name ends in .html it is sucked into a giant string variable, and if ~TiddlyWiki is found therein:\n#A copy of the html document is saved and optionally backed up as usual\n#All tiddlers tagged systemConfig are extracted, run through the [[JSMin JavaScript minimizer|http://www.crockford.com/javascript/jsmin.html]], and replaced in compressed form. This removes all comments, extra whitespace, etc.\n#In -all- tiddlers, ASP substitution blocks (<% ... %>) are unescaped. Clearly this will not be loadable directly into the browser, but as we'll see, it is first interpreted by the ASP engine in the server, and the result is valid in the browser as a TW.\n#All tiddlers tagged 'deleteUser' are completely removed. These typically contain help info for authors and plugins used only by authoring tiddlers.\n#The ~UploadLog tiddler is removed\n#The resulting TW (still in the giant string) is written out to a new file ''index.asp''. The .asp extension tells the server that the entire TW needs to be passed through the ASP engine for dynamic substitution. __Users see index.asp__.\nAlmost all of this is done with regular expressions. The author.html file is now well over 600K in size, and the compression reduces this to about 470K in the final index.asp. The real benefit of this is that I can freely comment all ~JavaScript (and I have written a //lot//) without worrying about size inflation. Of course authors suffer the additional download time, but they'll understand, and authoring is an occasional task anyway.\n!~ModeSwitchPlugin\nThe TW contains a special plugin ''~ModeSwitchPlugin'' that does a bunch of things to the user TW (index.asp) when it loads, to change the UI:\n#Changes ~SideBarTabs to contain only all and tags, renamed "all items" and "items by category"\n#All tag tiddlers are hidden (excludeLists, excludeSearch)\n#~SideBarOptions is replaced by a special one for users (details available on request) and hidden\n#~OptionsPanel is also customized and hidden\n#~ViewTemplate is replaced to customize the toolbar\n#~DefaultTiddlers is replaced to show the tiddlers that the users see when they first log in, these are different from those that authors see\n#All tiddlers tagged systemContent, systemConfig, and hideUser are hidden (excludeSearch, excludeLists)\n#A bunch of tooltips and messages are changed to remove "tiddler" from the text, replacing it with 'item' and other changes to make it more user-friendly.\n!HTML Forms\nI make heavy use of HTML forms. I wrote a plugin ~PersistentFormPlugin that saves the contents of all forms in a tiddler to a master cookie (separate from TW config options). This allows forms on different tiddlers to share the same data (input elements with the same name) and of course saves input data persistently.\n\nI also wrote my own AJAX library and ~PostFormPlugin that creates a button on the tiddler which will ~AJAX-post from an HTML form on the tiddler. Results come back from an ASP receiver for the form and are either wilified on to the end of the tiddler or displayed via displayMessage().\n!Remote Content\nSome tiddlers get their data from the server dynamically. I wrote a plugin ~RemoteContentPlugin that will wikify whatever comes from the server (another ASP page!) and show it in the tiddler. An optional refresh button and periodic refresh interval are supported.\n!Main Manu\nThe main menu is a DHTML "Explorer Bar" which I lifted from the web, extensively modified, and turned into a plugin. This requires some rather complex HTML in the ~MainMenu tiddler, so I amnot sure if this would be of general interest to the TW community.\n!~LightBox Support\nI got enamored by the "light box" displays I saw on a few web sites and figured out a simple way to dispay them on a TW. A light box appears by dimming the entire browser window and then showing something in a box that appears on top of the TW display. \n\nI turned this into a TW systemConfig library and CSS such that various flavors of alerts could be displayed, as well as a couple of input forms that are remotely controlled by a script running on the observatory control system. The remote script can also put up a "modal" alert that requires the user to confirm it before the remote script continues. It will time out, however, after a configurable interval and the script will get a thrown error. This was a blast to do,and required a lot of the more obscure scriptable interface features of my control system.\n!Take a look!\nI have created a login for anyone that would like to have a look at this. The version that is online is not quite the latest. To login, go to the [[Red Mountain Simulated Observatory|http://simulator.my-sky.com/]] and login with username ''twdev'' and password ''wiki''. This will display the user content; you'll see what astronomers see when using a remote/robotic observatory. \n\nI have given this account administrator privileges, so you can get to the authoring environment by opening the Toolbox menu on the left and clicking Authoring System. You'll immediately see that the ~SiteTitle and ~SiteSubtitle are ASP substitution blocks. This info is generated when the user loads the TW and comes from config settings in the observatory control system. There are may other ASP substitutions that get done this way. @@PLEASE DON'T CHANGE ANYTHING@@ but feel free to look around. I need to document the ~LightBoxPlugin!\n
<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal 'DD MMM YYYY'>><<saveChanges>><<upload>><<slider chkSliderOptionsPanel OptionsPanel 'options »' 'Change TiddlyWiki advanced options'>>
\nBob Denny's extensions to ~TiddlyWiki
Red Mountain Vista
/***\n|''Name:''|SortableGridPlugin|\n|''Description:''|Provide live sorting of tables by column|\n|''Date:''|Nov 4, 2006|\n|''Source:''|[[SortableGridPlugin|http://solo.dc3.com/tw/plugins.html#SortableGridPlugin]]|\n|''Author:''|Stuart Langridge, Demian Johnson, Bob Denny|\n|''License:''|See Below|\n|''Version:''|1.1.2|\n|''~CoreVersion:''|2.0.11 and 2.1.x|\n|''Browser:''|Firefox 1.5/2.0; Internet Explorer 6.0/7.0; Safari|\n!!Description\n@@Please note that this works only with TiddlyWiki 2.0.11 and 2.1.0/1/2@@\n\nThis plugin provides live sorting of tables by clicking on a column header. To sort in reverse, click the same column header a second time. An arrow in the sort column shows the direction of sorting. \n\nIt works by trying to automatically detect the type of data in a column, then sorting by the rules for that data type. Note that the data in the first row (before sorting for the first time) is used for type detection, so if other data types exist in the column below the first row, the results will be unpredictable. If it //can// recognize the string as prticular type it //will// sort that column by that type. Moral: keep all of your data in a column the same type. The following data types are checked in the order shown (in other words the table shows the precedence of type detection):\n\n|!Type |!Description|\n|Date|Various formats for dates, specifically any string format that can be converted to a date/time by Javascript's Date.Parse() method.|\n|Currency|Any string beginning with $, £, or € followed by a numeric string (except no leading sign). Note that it does not do currency conversion, the raw currency values are sorted numerically. {{{/^[$|£|€]{1}\sd*\s.{0,1}\sd+$/}}}|\n|Numeric|Data must consist purely of digits, optional leading plus or minus sign, a single period. Javascript cannot handle the Continental virgule (comma decimal point). {{{/^[\s+|\s-]{0,1}\sd*\s.{0,1}\sd+$/}}}|\n|File Size|Numeric string (except no leading sign) with b, Kb, Mb, or Gb at the end. Sorts according to the actual value represented by the notation. {{{/^\sd*\s.{0,1}\sd+[K|M|G]{0,1}b$}}}|\n|Text|Anything that does not match the formats listed below. Text is sorted without regard to character case.|\n!!Installation\nFollow the usual procedure for installing a plugin: Edit this tiddler, copy, paste into a new tiddler in your TW, and tag it systemConfig. Close, Save, and Shift-Reload your TW's page. The table below (in Example) should have hot column headers and be sortable.\n!!Usage\nTo make a table sortable, append an {{{h}}} to the end of the first row. If the table is thus marked as sortable, the formatter will add a CSS class {{{sortable}}} to the generated {{{<table>}}} element. Thus you can use CSS to alter the appearance of the sortable table and/or its elements.\n!!Example\n|Name |Salary |Extension |Performance |File Size |Start date |h\n|Bloggs, Fred |$12000.00 |1353 |+1.2 |74.2Kb |Aug 19, 2003 21:34:00 |\n|Bloggs, Fred |$12000.00 |1353 |1.2 |3350b |09/18/2003 |\n|Bloggs, Fred |$12000 |1353 |1.200 |55.2Kb |August 18, 2003 |\n|Bloggs, Fred |$12000.00 |1353 |1.2 |2100b |07/18/2003 |\n|Bloggs, Fred |$12000.00 |1353 |01.20 |6.156Mb |08/17/2003 05:43 |\n|Turvey, Kevin |$191200.00 |2342 |-33 |1b |02/05/1979 |\n|Mbogo, Arnold |$32010.12 |2755 |-21.673 |1.2Gb |09/08/1998 |\n|Shakespeare, Bill |£122000.00|3211 |6 |33.22Gb |12/11/1961 |\n|Shakespeare, Hamlet |£9000 |9005 |-8 |3Gb |01/01/2002 |\n|Fitz, Marvin |€3300.30 |5554 |+5 |4Kb |05/22/1995 |\n!!Revision History\n<<<\n''2003.11.?? [?.?.?]'' Stuart Langridge (http://www.kryogenix.org/code/browser/sorttable/) - Core code for DHTML sortable tables. Copyright and license for his code has been carried forward and applies to subsequent additions.\n''2006.02.14 [1.0.0]'' Demian Johnson - Initial release, adaptation of Langridge code to TiddlyWiki.\n''2006.09.29 [1.1.0]'' Bob Denny - Add standard-format plugin documentation, reformat and tabify code for readability, refactor references to plugin, add new "file size" detection and sorting, add sterling and euro to currency detection, allow any real numbers including optional sign and either period or comma for decimal point for numeric sorting, make RegExp matching strict for currency and numeric, clean up lint warnings, correct spelling of Hamlet's name.\n''2006.10.19 [1.1.1]'' Bob Denny - Allow use with TW 2.1.1 and 2.1.2, hijack is identical with 2.1.0.\n''2006.11.04 [1.1.2]'' Bob Denny - Oh hell, accept 2.1.x, bit again by 2.1.3 which was OK.\n<<<\n!!Code\n***/\n//{{{\n//\n// Begin SORTABLE.JS\n// This Code is:\n// Code downloaded from the Browser Experiments section of kryogenix.org is \n// licenced under the so-called MIT licence. The license is below.\n// ----------------------------------------\n// Copyright (c) 1997-date Stuart Langridge\n// ----------------------------------------\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this \n// software and associated documentation files (the "Software"), to deal in the Software \n// without restriction, including without limitation the rights to use, copy, modify, merge, \n// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons \n// to whom the Software is furnished to do so, subject to the following conditions:\n//\n// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, \n// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR \n// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE \n// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR \n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER \n// DEALINGS IN THE SOFTWARE.\n//\n// Modified under the same aforementioned terms by Demian Johnston, 2006\n// Further modified under the same aforementioned terms by Bob Denny, 2006: \n// 1. Add flexible date/time \n// 2. Use 'this' instead of full dotted names \n// 3. Re-indent and tabify after being munged by TW/IE bu\n// 4. Add "file size" sensing and sorting. Validate with Javascript Lint \n//\nversion.extensions.PersistentForm = {\n major: 1, minor: 1, revision: 2,\n date: new Date(2006, 11, 4), \n type: 'extension',\n source: "http://solo.dc3.com/tw/plugins.html#SortableGridPlugin"\n};\n//}}}\n\n//{{{\nconfig.macros.sortableGridPlugin = { SORT_COLUMN_INDEX: 0 };\n\nconfig.macros.sortableGridPlugin.ts_makeSortable = function(table) \n{\n var firstRow;\n if (table.rows && table.rows.length > 0) {\n firstRow = table.rows[0];\n }\n if (!firstRow) return;\n \n // We have a first row: assume it's the header, and make its contents clickable links\n for (var i=0;i<firstRow.cells.length;i++) {\n var cell = firstRow.cells[i];\n var txt = config.macros.sortableGridPlugin.ts_getInnerText(cell);\n cell.innerHTML = '<a href="#" class="sortheader" onclick="config.macros.sortableGridPlugin.ts_resortTable(this);return false;">' +\n txt + '<span class="sortarrow"> </span></a>';\n }\n};\n//}}}\n\n//{{{\nconfig.macros.sortableGridPlugin.ts_getInnerText = function(el) \n{\n if (typeof el == "string") return el;\n if (typeof el == "undefined") { return el; }\n if (el.innerText) return el.innerText; //Not needed but it is faster\n var str = "";\n var cs = el.childNodes;\n var l = cs.length;\n for (var i = 0; i < l; i++) {\n switch (cs[i].nodeType) \n {\n case 1: //ELEMENT_NODE\n str += config.macros.sortableGridPlugin.ts_getInnerText(cs[i]);\n break;\n case 3: //TEXT_NODE\n str += cs[i].nodeValue;\n break;\n }\n }\n return str;\n};\n\nconfig.macros.sortableGridPlugin.getParent = function(el, pTagName) \n{\n if (el === null) \n return null;\n else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) // Gecko bug, supposed to be uppercase\n return el;\n else\n return config.macros.sortableGridPlugin.getParent(el.parentNode, pTagName);\n};\n//}}}\n\n//{{{\nconfig.macros.sortableGridPlugin.ts_resortTable = function(lnk) \n{\n var M = config.macros.sortableGridPlugin;\n // get the span\n var span;\n for (var ci = 0; ci < lnk.childNodes.length; ci++) {\n if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') \n span = lnk.childNodes[ci];\n }\n var td = lnk.parentNode;\n var column = td.cellIndex;\n var table = M.getParent(td,'TABLE');\n \n // Work out a type for the column\n if (table.rows.length <= 1) return;\n var itm = M.ts_getInnerText(table.rows[1].cells[column]);\n var sortfn = M.ts_sort_caseinsensitive;\n if(!isNaN(Date.parse(itm)))\n sortfn = M.ts_sort_date;\n else if(itm.match(/^[$|£|€]{1}\sd*\s.{0,1}\sd+$/)) \n sortfn = M.ts_sort_currency;\n else if(itm.match(/^[\s+|\s-]{0,1}\sd*\s.{0,1}\sd+$/)) \n sortfn = M.ts_sort_numeric;\n else if(itm.match(/^\sd*\s.{0,1}\sd+[K|M|G]{0,1}b$/))\n sortfn = M.ts_sort_fileSize;\n M.SORT_COLUMN_INDEX = column;\n var firstRow = new Array();\n var newRows = new Array();\n for (var i = 0; i < table.rows[0].length; i++) { firstRow[i] = table.rows[0][i]; }\n for (var j = 1; j < table.rows.length; j++) { newRows[j-1] = table.rows[j]; }\n \n newRows.sort(sortfn);\n var ARROW;\n if (span.getAttribute("sortdir") == 'down') {\n ARROW = ' ↑';\n newRows.reverse();\n span.setAttribute('sortdir','up');\n } else {\n ARROW = ' ↓';\n span.setAttribute('sortdir','down');\n }\n \n // We appendChild rows that already exist to the tbody, so it moves them \n // rather than creating new ones. Don't do sortbottom rows\n for ( i=0;i<newRows.length;i++) { \n if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) \n table.tBodies[0].appendChild(newRows[i]);\n }\n // do sortbottom rows only\n for ( i=0;i<newRows.length;i++) { \n if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) \n table.tBodies[0].appendChild(newRows[i]);\n }\n \n // Delete any other arrows there may be showing\n var allspans = document.getElementsByTagName("span");\n for ( ci=0;ci<allspans.length;ci++) {\n if (allspans[ci].className == 'sortarrow') {\n if (M.getParent(allspans[ci],"table") == M.getParent(lnk,"table")) { // in the same table as us?\n allspans[ci].innerHTML = ' ';\n }\n }\n }\n \n span.innerHTML = ARROW;\n};\n//}}}\n\n//{{{\nconfig.macros.sortableGridPlugin.ts_sort_fileSize = function(a, b) \n{\n var M = config.macros.sortableGridPlugin;\n var convert = function(str)\n {\n var val;\n var i;\n if((i = str.indexOf("Kb")) != -1)\n val = 1024.0 * str.substr(0, i);\n else if((i = str.indexOf("Mb")) != -1)\n val = 1048576.0 * str.substr(0, i);\n else if((i = str.indexOf("Gb")) != -1)\n val = 1073741824.0 * str.substr(0, i);\n else\n val = 1.0 * str.substr(0, str.length - 1);\n return val;\n };\n \n var aa = M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]);\n var bb = M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX]);\n var v1 = convert(aa);\n var v2 = convert(bb);\n if(v1 == v2) return 0;\n if(v1 < v2) return -1;\n return 1;\n};\n\nconfig.macros.sortableGridPlugin.ts_sort_date = function(a, b) \n{\n var M = config.macros.sortableGridPlugin;\n // Handles dates per the rules of Date.parse()\n var aa = M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]);\n var bb = M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX]);\n var dt1 = Date.parse(aa);\n var dt2 = Date.parse(bb);\n if (dt1 == dt2) return 0;\n if (dt1 < dt2) return -1;\n return 1;\n};\n\nconfig.macros.sortableGridPlugin.ts_sort_currency = function(a, b) \n{ \n var M = config.macros.sortableGridPlugin;\n var aa = M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');\n var bb = M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');\n return parseFloat(aa) - parseFloat(bb);\n};\n\nconfig.macros.sortableGridPlugin.ts_sort_numeric = function(a, b) \n{ \n var M = config.macros.sortableGridPlugin;\n var aa = parseFloat(M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]));\n if (isNaN(aa)) aa = 0;\n var bb = parseFloat(M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX])); \n if (isNaN(bb)) bb = 0;\n return aa-bb;\n};\n\nconfig.macros.sortableGridPlugin.ts_sort_caseinsensitive = function(a, b) \n{\n var M = config.macros.sortableGridPlugin;\n var aa = M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]).toLowerCase();\n var bb = M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX]).toLowerCase();\n if (aa == bb) return 0;\n if (aa < bb) return -1;\n return 1;\n};\n\n// config.macros.sortableGridPlugin.ts_sort_default = function(a, b) \n// {\n// var M = config.macros.sortableGridPlugin;\n// var aa = M.ts_getInnerText(a.cells[M.SORT_COLUMN_INDEX]);\n// var bb = M.ts_getInnerText(b.cells[M.SORT_COLUMN_INDEX]);\n// if (aa == bb) return 0;\n// if (aa < bb) return -1;\n// return 1;\n// };\n//\n//}}}\n\n//{{{\n// end Code downloaded from the Browser Experiments section of kryogenix.org\n// end Copyright (c) 1997-date Stuart Langridge//\n// END SORTABLE.JS//\n//}}}\n\n//{{{\n//\n//\n// CORE HIJACK WARNINGS: \n// (1) Depends on the table formatter being first in the config.formatters array\n// (2) Version-specifics - test on your version before adding to the logic here!\n//\nif(version.major == 2 && version.minor === 0 && version.revision == 11)\n{\n config.formatters[0].handler = function(w)\n {\n var table = createTiddlyElement(w.output,"table");\n w.nextMatch = w.matchStart;\n var lookaheadRegExp = new RegExp(this.lookahead,"mg");\n var currRowType = null, nextRowType;\n var rowContainer, rowElement;\n var prevColumns = [];\n var rowCount = 0;\n var want_sortable=0;\n do {\n lookaheadRegExp.lastIndex = w.nextMatch;\n var lookaheadMatch = lookaheadRegExp.exec(w.source);\n var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;\n if(matched)\n {\n nextRowType = lookaheadMatch[2];\n if(nextRowType != currRowType)\n rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);\n currRowType = nextRowType;\n if(currRowType == "c")\n {\n if(rowCount === 0)\n rowContainer.setAttribute("align","top");\n else\n rowContainer.setAttribute("align","bottom");\n w.nextMatch = w.nextMatch + 1;\n w.subWikify(rowContainer,this.rowTerminator);\n table.insertBefore(rowContainer,table.firstChild);\n }\n else\n {\n var rowClass = (rowCount & 1) ? "oddRow" : "evenRow";\n rowElement = createTiddlyElement(rowContainer,"tr",null,rowClass);\n this.rowHandler(w,rowElement,prevColumns);\n }\n if(currRowType == "h") {\n want_sortable=1;\n }\n rowCount++;\n }\n } while(matched);\n if (want_sortable) {\n table.setAttribute("class","sortable");\n config.macros.sortableGridPlugin.ts_makeSortable(table);\n }\n };\n} \nelse if(version.major == 2 && version.minor == 1)\n{\n config.formatters[0].handler = function(w)\n {\n var table = createTiddlyElement(w.output,"table");\n var prevColumns = [];\n var currRowType = null;\n var rowContainer;\n var rowCount = 0;\n var want_sortable = 0;\n \n w.nextMatch = w.matchStart;\n this.lookaheadRegExp.lastIndex = w.nextMatch;\n var lookaheadMatch = this.lookaheadRegExp.exec(w.source);\n while(lookaheadMatch && lookaheadMatch.index == w.nextMatch)\n {\n var nextRowType = lookaheadMatch[2];\n if(nextRowType == "k")\n {\n table.className = lookaheadMatch[1];\n w.nextMatch += lookaheadMatch[0].length+1;\n }\n else\n {\n if(nextRowType != currRowType)\n {\n rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);\n currRowType = nextRowType;\n }\n if(currRowType == "c")\n {\n // Caption\n w.nextMatch++;\n if(rowContainer != table.firstChild)\n table.insertBefore(rowContainer,table.firstChild);\n//[rbd lint warn] rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");\n rowContainer.setAttribute("align",rowCount === 0?"top":"bottom");\n w.subWikifyTerm(rowContainer,this.rowTermRegExp);\n }\n else\n {\n this.rowHandler(w,createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow"),prevColumns);\n if(currRowType == "h") want_sortable = 1;\n rowCount++;\n }\n }\n this.lookaheadRegExp.lastIndex = w.nextMatch;\n lookaheadMatch = this.lookaheadRegExp.exec(w.source);\n }\n if (want_sortable) {\n table.setAttribute("class","sortable");\n config.macros.sortableGridPlugin.ts_makeSortable(table);\n }\n };\n}\nelse\n alert("SortableGridPlugin works only with TiddlyWiki 2.0.11 and 2.1.x");\n \n//}}}\n
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |\n| 25/12/2006 18:8:23 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:18:36 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:20:24 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:21:41 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:22:26 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:24:36 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup | Ok |\n| 25/12/2006 18:27:34 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup |\n| 25/12/2006 18:37:6 | Bob Denny | [[index.html|http://solo.dc3.com/tw/index.html]] | [[twstorews.asp|/tw/twstorews.asp]] | | index.html | backup |
!Options used by UploadPlugin\nUsername: <<option txtUploadUserName>>\nPassword: <<option pasUploadPassword>>\n\nUrl of the UploadService script^^(1)^^: <<option txtUploadStoreUrl 50>>\nRelative Directory where to store the file^^(2)^^: <<option txtUploadDir 50>>\nFilename of the uploaded file^^(3)^^: <<option txtUploadFilename 40>>\nDirectory to backup file on webserver^^(4)^^: <<option txtUploadBackupDir>>\n\n^^(1)^^Mandatory either in UploadOptions or in macro parameter\n^^(2)^^If empty stores in the script directory\n^^(3)^^If empty takes the actual filename\n^^(4)^^If empty existing file with same name on webserver will be overwritten\n\n<<upload>> with these options.\n\n!Upload Macro parameters\n{{{\n<<upload [storeUrl [toFilename [backupDir [uploadDir [username]]]]]>>\n Optional positional parameters can be passed to overwrite \n UploadOptions. \n}}}\n\n
/***\n|''Name:''|UploadPlugin|\n|''Description:''|Save to web a TiddlyWiki|\n|''Version:''|3.4.4|\n|''Date:''|Sep 30, 2006|\n|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|\n|''Documentation:''|http://tiddlywiki.bidix.info/#UploadDoc|\n|''Author:''|BidiX (BidiX (at) bidix (dot) info)|\n|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|\n|''~CoreVersion:''|2.0.0|\n|''Browser:''|Firefox 1.5; InternetExplorer 6.0; Safari|\n|''Include:''|config.lib.file; config.lib.log; config.lib.options; PasswordTweak|\n|''Require:''|[[UploadService|http://tiddlywiki.bidix.info/#UploadService]]|\n***/\n//{{{\nversion.extensions.UploadPlugin = {\n major: 3, minor: 4, revision: 4, \n date: new Date(2006,8,30),\n source: 'http://tiddlywiki.bidix.info/#UploadPlugin',\n documentation: 'http://tiddlywiki.bidix.info/#UploadDoc',\n author: 'BidiX (BidiX (at) bidix (dot) info',\n license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',\n coreVersion: '2.0.0',\n browser: 'Firefox 1.5; InternetExplorer 6.0; Safari'\n};\n//}}}\n\n////+++!![config.lib.file]\n\n//{{{\nif (!config.lib) config.lib = {};\nif (!config.lib.file) config.lib.file= {\n author: 'BidiX',\n version: {major: 0, minor: 1, revision: 0}, \n date: new Date(2006,3,9)\n};\nconfig.lib.file.dirname = function (filePath) {\n var lastpos;\n if ((lastpos = filePath.lastIndexOf("/")) != -1) {\n return filePath.substring(0, lastpos);\n } else {\n return filePath.substring(0, filePath.lastIndexOf("\s\s"));\n }\n};\nconfig.lib.file.basename = function (filePath) {\n var lastpos;\n if ((lastpos = filePath.lastIndexOf("#")) != -1) \n filePath = filePath.substring(0, lastpos);\n if ((lastpos = filePath.lastIndexOf("/")) != -1) {\n return filePath.substring(lastpos + 1);\n } else\n return filePath.substring(filePath.lastIndexOf("\s\s")+1);\n};\nwindow.basename = function() {return "@@deprecated@@";};\n//}}}\n////===\n\n////+++!![config.lib.log]\n\n//{{{\nif (!config.lib) config.lib = {};\nif (!config.lib.log) config.lib.log= {\n author: 'BidiX',\n version: {major: 0, minor: 1, revision: 1}, \n date: new Date(2006,8,19)\n};\nconfig.lib.Log = function(tiddlerTitle, logHeader) {\n if (version.major < 2)\n this.tiddler = store.tiddlers[tiddlerTitle];\n else\n this.tiddler = store.getTiddler(tiddlerTitle);\n if (!this.tiddler) {\n this.tiddler = new Tiddler();\n this.tiddler.title = tiddlerTitle;\n this.tiddler.text = "| !date | !user | !location |" + logHeader;\n this.tiddler.created = new Date();\n this.tiddler.modifier = config.options.txtUserName;\n this.tiddler.modified = new Date();\n if (version.major < 2)\n store.tiddlers[tiddlerTitle] = this.tiddler;\n else\n store.addTiddler(this.tiddler);\n }\n return this;\n};\n\nconfig.lib.Log.prototype.newLine = function (line) {\n var now = new Date();\n var newText = "| ";\n newText += now.getDate()+"/"+(now.getMonth()+1)+"/"+now.getFullYear() + " ";\n newText += now.getHours()+":"+now.getMinutes()+":"+now.getSeconds()+" | ";\n newText += config.options.txtUserName + " | ";\n var location = document.location.toString();\n var filename = config.lib.file.basename(location);\n if (!filename) filename = '/';\n newText += "[["+filename+"|"+location + "]] |";\n this.tiddler.text = this.tiddler.text + "\sn" + newText;\n this.addToLine(line);\n};\n\nconfig.lib.Log.prototype.addToLine = function (text) {\n this.tiddler.text = this.tiddler.text + text;\n this.tiddler.modifier = config.options.txtUserName;\n this.tiddler.modified = new Date();\n if (version.major < 2)\n store.tiddlers[this.tiddler.tittle] = this.tiddler;\n else {\n store.addTiddler(this.tiddler);\n story.refreshTiddler(this.tiddler.title);\n store.notify(this.tiddler.title, true);\n }\n if (version.major < 2)\n store.notifyAll(); \n};\n//}}}\n////===\n\n////+++!![config.lib.options]\n\n//{{{\nif (!config.lib) config.lib = {};\nif (!config.lib.options) config.lib.options = {\n author: 'BidiX',\n version: {major: 0, minor: 1, revision: 0}, \n date: new Date(2006,3,9)\n};\n\nconfig.lib.options.init = function (name, defaultValue) {\n if (!config.options[name]) {\n config.options[name] = defaultValue;\n saveOptionCookie(name);\n }\n};\n//}}}\n////===\n\n////+++!![PasswordTweak]\n\n//{{{\nversion.extensions.PasswordTweak = {\n major: 1, minor: 0, revision: 3, date: new Date(2006,8,30),\n type: 'tweak',\n source: 'http://tiddlywiki.bidix.info/#PasswordTweak'\n};\n//}}}\n/***\n!!config.macros.option\n***/\n//{{{\nconfig.macros.option.passwordCheckboxLabel = "Save this password on this computer";\nconfig.macros.option.passwordType = "password"; // password | text\n\nconfig.macros.option.onChangeOption = function(e)\n{\n var opt = this.getAttribute("option");\n var elementType,valueField;\n if(opt) {\n switch(opt.substr(0,3)) {\n case "txt":\n elementType = "input";\n valueField = "value";\n break;\n case "pas":\n elementType = "input";\n valueField = "value";\n break;\n case "chk":\n elementType = "input";\n valueField = "checked";\n break;\n }\n config.options[opt] = this[valueField];\n saveOptionCookie(opt);\n var nodes = document.getElementsByTagName(elementType);\n for(var t=0; t<nodes.length; t++) \n {\n var optNode = nodes[t].getAttribute("option");\n if (opt == optNode) \n nodes[t][valueField] = this[valueField];\n }\n }\n return(true);\n};\n\nconfig.macros.option.handler = function(place,macroName,params)\n{\n var opt = params[0];\n if(config.options[opt] === undefined) {\n return;}\n var c;\n switch(opt.substr(0,3)) {\n case "txt":\n c = document.createElement("input");\n c.onkeyup = this.onChangeOption;\n c.setAttribute ("option",opt);\n c.className = "txtOptionInput "+opt;\n place.appendChild(c);\n c.value = config.options[opt];\n break;\n case "pas":\n // input password\n c = document.createElement ("input");\n c.setAttribute("type",config.macros.option.passwordType);\n c.onkeyup = this.onChangeOption;\n c.setAttribute("option",opt);\n c.className = "pasOptionInput "+opt;\n place.appendChild(c);\n c.value = config.options[opt];\n // checkbox link with this password "save this password on this computer"\n c = document.createElement("input");\n c.setAttribute("type","checkbox");\n c.onclick = this.onChangeOption;\n c.setAttribute("option","chk"+opt);\n c.className = "chkOptionInput "+opt;\n place.appendChild(c);\n c.checked = config.options["chk"+opt];\n // text savePasswordCheckboxLabel\n place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));\n break;\n case "chk":\n c = document.createElement("input");\n c.setAttribute("type","checkbox");\n c.onclick = this.onChangeOption;\n c.setAttribute("option",opt);\n c.className = "chkOptionInput "+opt;\n place.appendChild(c);\n c.checked = config.options[opt];\n break;\n }\n};\n//}}}\n/***\n!! Option cookie stuff\n***/\n//{{{\nwindow.loadOptionsCookie_orig_PasswordTweak = window.loadOptionsCookie;\nwindow.loadOptionsCookie = function()\n{\n var cookies = document.cookie.split(";");\n for(var c=0; c<cookies.length; c++) {\n var p = cookies[c].indexOf("=");\n if(p != -1) {\n var name = cookies[c].substr(0,p).trim();\n var value = cookies[c].substr(p+1).trim();\n switch(name.substr(0,3)) {\n case "txt":\n config.options[name] = unescape(value);\n break;\n case "pas":\n config.options[name] = unescape(value);\n break;\n case "chk":\n config.options[name] = value == "true";\n break;\n }\n }\n }\n};\n\nwindow.saveOptionCookie_orig_PasswordTweak = window.saveOptionCookie;\nwindow.saveOptionCookie = function(name)\n{\n var c = name + "=";\n switch(name.substr(0,3)) {\n case "txt":\n c += escape(config.options[name].toString());\n break;\n case "chk":\n c += config.options[name] ? "true" : "false";\n // is there an option link with this chk ?\n if (config.options[name.substr(3)]) {\n saveOptionCookie(name.substr(3));\n }\n break;\n case "pas":\n if (config.options["chk"+name]) {\n c += escape(config.options[name].toString());\n } else {\n c += "";\n }\n break;\n }\n c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";\n document.cookie = c;\n};\n//}}}\n/***\n!! Initializations\n***/\n//{{{\n// define config.options.pasPassword\nif (!config.options.pasPassword) {\n config.options.pasPassword = 'defaultPassword';\n window.saveOptionCookie('pasPassword');\n}\n// since loadCookies is first called befor password definition\n// we need to reload cookies\nwindow.loadOptionsCookie();\n//}}}\n////===\n\n////+++!![config.macros.upload]\n\n//{{{\nconfig.macros.upload = {\n accessKey: "U",\n formName: "UploadPlugin",\n contentType: "text/html;charset=UTF-8",\n defaultStoreScript: "store.php"\n};\n\n// only this two configs need to be translated\nconfig.macros.upload.messages = {\n aboutToUpload: "About to upload TiddlyWiki to %0",\n backupFileStored: "Previous file backuped in %0",\n crossDomain: "Certainly a cross-domain isue: access to an other site isn't allowed",\n errorDownloading: "Error downloading",\n errorUploadingContent: "Error uploading content",\n fileLocked: "Files is locked: You are not allowed to Upload",\n fileNotFound: "file to upload not found",\n fileNotUploaded: "File %0 NOT uploaded",\n mainFileUploaded: "Main TiddlyWiki file uploaded to %0",\n passwordEmpty: "Unable to upload, your password is empty",\n urlParamMissing: "url param missing",\n rssFileNotUploaded: "RssFile %0 NOT uploaded",\n rssFileUploaded: "Rss File uploaded to %0"\n};\n\nconfig.macros.upload.label = {\n promptOption: "Save and Upload this TiddlyWiki with UploadOptions",\n promptParamMacro: "Save and Upload this TiddlyWiki in %0",\n saveLabel: "save to web", \n saveToDisk: "save to disk",\n uploadLabel: "upload" \n};\n\nconfig.macros.upload.handler = function(place,macroName,params){\n // parameters initialization\n var storeUrl = params[0];\n var toFilename = params[1];\n var backupDir = params[2];\n var uploadDir = params[3];\n var username = params[4];\n var password; // for security reason no password as macro parameter\n var label;\n if (document.location.toString().substr(0,4) == "http")\n label = this.label.saveLabel;\n else\n label = this.label.uploadLabel;\n var prompt;\n if (storeUrl) {\n prompt = this.label.promptParamMacro.toString().format([this.toDirUrl(storeUrl, uploadDir, username)]);\n }\n else {\n prompt = this.label.promptOption;\n }\n createTiddlyButton(place, label, prompt, \n function () {\n config.macros.upload.upload(storeUrl, toFilename, uploadDir, backupDir, username, password); \n return false;}, \n null, null, this.accessKey);\n};\nconfig.macros.upload.UploadLog = function() {\n return new config.lib.Log('UploadLog', " !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |" );\n};\nconfig.macros.upload.UploadLog.prototype = config.lib.Log.prototype;\nconfig.macros.upload.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir, backupDir) {\n var line = " [[" + config.lib.file.basename(storeUrl) + "|" + storeUrl + "]] | ";\n line += uploadDir + " | " + toFilename + " | " + backupDir + " |";\n this.newLine(line);\n};\nconfig.macros.upload.UploadLog.prototype.endUpload = function() {\n this.addToLine(" Ok |");\n};\nconfig.macros.upload.basename = config.lib.file.basename;\nconfig.macros.upload.dirname = config.lib.file.dirname;\nconfig.macros.upload.toRootUrl = function (storeUrl, username)\n{\n return root = (this.dirname(storeUrl)?this.dirname(storeUrl):this.dirname(document.location.toString()));\n}\nconfig.macros.upload.toDirUrl = function (storeUrl, uploadDir, username)\n{\n var root = this.toRootUrl(storeUrl, username);\n if (uploadDir && uploadDir != '.')\n root = root + '/' + uploadDir;\n return root;\n}\nconfig.macros.upload.toFileUrl = function (storeUrl, toFilename, uploadDir, username)\n{\n return this.toDirUrl(storeUrl, uploadDir, username) + '/' + toFilename;\n}\nconfig.macros.upload.upload = function(storeUrl, toFilename, uploadDir, backupDir, username, password)\n{\n // parameters initialization\n storeUrl = (storeUrl ? storeUrl : config.options.txtUploadStoreUrl);\n toFilename = (toFilename ? toFilename : config.options.txtUploadFilename);\n backupDir = (backupDir ? backupDir : config.options.txtUploadBackupDir);\n uploadDir = (uploadDir ? uploadDir : config.options.txtUploadDir);\n username = (username ? username : config.options.txtUploadUserName);\n password = config.options.pasUploadPassword; // for security reason no password as macro parameter\n if (!password || password === '') {\n alert(config.macros.upload.messages.passwordEmpty);\n return;\n }\n if (storeUrl === '') {\n storeUrl = config.macros.upload.defaultStoreScript;\n }\n if (config.lib.file.dirname(storeUrl) === '') {\n storeUrl = config.lib.file.dirname(document.location.toString())+'/'+storeUrl;\n }\n if (toFilename === '') {\n toFilename = config.lib.file.basename(document.location.toString());\n }\n\n clearMessage();\n // only for forcing the message to display\n if (version.major < 2)\n store.notifyAll();\n if (!storeUrl) {\n alert(config.macros.upload.messages.urlParamMissing);\n return;\n }\n // Check that file is not locked\n if (window.BidiX && BidiX.GroupAuthoring && BidiX.GroupAuthoring.lock) {\n if (BidiX.GroupAuthoring.lock.isLocked() && !BidiX.GroupAuthoring.lock.isMyLock()) {\n alert(config.macros.upload.messages.fileLocked);\n return;\n }\n }\n \n var log = new this.UploadLog();\n log.startUpload(storeUrl, toFilename, uploadDir, backupDir);\n if (document.location.toString().substr(0,5) == "file:") {\n saveChanges();\n }\n var toDir = config.macros.upload.toDirUrl(storeUrl, toFilename, uploadDir, username);\n displayMessage(config.macros.upload.messages.aboutToUpload.format([toDir]), toDir);\n this.uploadChanges(storeUrl, toFilename, uploadDir, backupDir, username, password);\n if(config.options.chkGenerateAnRssFeed) {\n //var rssContent = convertUnicodeToUTF8(generateRss());\n var rssContent = generateRss();\n var rssPath = toFilename.substr(0,toFilename.lastIndexOf(".")) + ".xml";\n this.uploadContent(rssContent, storeUrl, rssPath, uploadDir, '', username, password, \n function (responseText) {\n if (responseText.substring(0,1) != '0') {\n displayMessage(config.macros.upload.messages.rssFileNotUploaded.format([rssPath]));\n }\n else {\n var toFileUrl = config.macros.upload.toFileUrl(storeUrl, rssPath, uploadDir, username);\n displayMessage(config.macros.upload.messages.rssFileUploaded.format(\n [toFileUrl]), toFileUrl);\n }\n // for debugging store.php uncomment last line\n //DEBUG alert(responseText);\n });\n }\n return;\n};\n\nconfig.macros.upload.uploadChanges = function(storeUrl, toFilename, uploadDir, backupDir, \n username, password) {\n var original;\n if (document.location.toString().substr(0,4) == "http") {\n original = this.download(storeUrl, toFilename, uploadDir, backupDir, username, password);\n return;\n }\n else {\n // standard way : Local file\n \n original = loadFile(getLocalPath(document.location.toString()));\n if(window.Components) {\n // it's a mozilla browser\n try {\n netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");\n var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]\n .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);\n converter.charset = "UTF-8";\n original = converter.ConvertToUnicode(original);\n }\n catch(e) {\n }\n }\n }\n //DEBUG alert(original);\n this.uploadChangesFrom(original, storeUrl, toFilename, uploadDir, backupDir, \n username, password);\n};\n\nconfig.macros.upload.uploadChangesFrom = function(original, storeUrl, toFilename, uploadDir, backupDir, \n username, password) {\n var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it\n var endSaveArea = '</d' + 'iv>';\n // Locate the storeArea div's\n var posOpeningDiv = original.indexOf(startSaveArea);\n var posClosingDiv = original.lastIndexOf(endSaveArea);\n if((posOpeningDiv == -1) || (posClosingDiv == -1))\n {\n alert(config.messages.invalidFileError.format([document.location.toString()]));\n return;\n }\n var revised = original.substr(0,posOpeningDiv + startSaveArea.length) + \n allTiddlersAsHtml() + "\sn\st\st" +\n original.substr(posClosingDiv);\n var newSiteTitle;\n if(version.major < 2){\n newSiteTitle = (getElementText("siteTitle") + " - " + getElementText("siteSubtitle")).htmlEncode();\n } else {\n newSiteTitle = (wikifyPlain ("SiteTitle") + " - " + wikifyPlain ("SiteSubtitle")).htmlEncode();\n }\n\n revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");\n revised = revised.replaceChunk("<!--PRE-HEAD-START--"+">","<!--PRE-HEAD-END--"+">","\sn" + store.getTiddlerText("MarkupPreHead","") + "\sn");\n revised = revised.replaceChunk("<!--POST-HEAD-START--"+">","<!--POST-HEAD-END--"+">","\sn" + store.getTiddlerText("MarkupPostHead","") + "\sn");\n revised = revised.replaceChunk("<!--PRE-BODY-START--"+">","<!--PRE-BODY-END--"+">","\sn" + store.getTiddlerText("MarkupPreBody","") + "\sn");\n revised = revised.replaceChunk("<!--POST-BODY-START--"+">","<!--POST-BODY-END--"+">","\sn" + store.getTiddlerText("MarkupPostBody","") + "\sn");\n\n var response = this.uploadContent(revised, storeUrl, toFilename, uploadDir, backupDir, \n username, password, function (responseText) {\n if (responseText.substring(0,1) != '0') {\n alert(responseText);\n displayMessage(config.macros.upload.messages.fileNotUploaded.format([getLocalPath(document.location.toString())]));\n }\n else {\n if (uploadDir !== '') {\n toFilename = uploadDir + "/" + config.macros.upload.basename(toFilename);\n } else {\n toFilename = config.macros.upload.basename(toFilename);\n }\n var toFileUrl = config.macros.upload.toFileUrl(storeUrl, toFilename, uploadDir, username);\n if (responseText.indexOf("destfile:") > 0) {\n var destfile = responseText.substring(responseText.indexOf("destfile:")+9, \n responseText.indexOf("\sn", responseText.indexOf("destfile:")));\n toFileUrl = config.macros.upload.toRootUrl(storeUrl, username) + '/' + destfile;\n }\n else {\n toFileUrl = config.macros.upload.toFileUrl(storeUrl, toFilename, uploadDir, username);\n }\n displayMessage(config.macros.upload.messages.mainFileUploaded.format(\n [toFileUrl]), toFileUrl);\n if (backupDir && responseText.indexOf("backupfile:") > 0) {\n var backupFile = responseText.substring(responseText.indexOf("backupfile:")+11, \n responseText.indexOf("\sn", responseText.indexOf("backupfile:")));\n toBackupUrl = config.macros.upload.toRootUrl(storeUrl, username) + '/' + backupFile;\n displayMessage(config.macros.upload.messages.backupFileStored.format(\n [toBackupUrl]), toBackupUrl);\n }\n var log = new config.macros.upload.UploadLog();\n log.endUpload();\n store.setDirty(false);\n // erase local lock\n if (window.BidiX && BidiX.GroupAuthoring && BidiX.GroupAuthoring.lock) {\n BidiX.GroupAuthoring.lock.eraseLock();\n // change mtime with new mtime after upload\n var mtime = responseText.substr(responseText.indexOf("mtime:")+6);\n BidiX.GroupAuthoring.lock.mtime = mtime;\n }\n \n \n }\n // for debugging store.php uncomment last line\n //DEBUG alert(responseText);\n }\n );\n};\n\nconfig.macros.upload.uploadContent = function(content, storeUrl, toFilename, uploadDir, backupDir, \n username, password, callbackFn) {\n var boundary = "---------------------------"+"aab03x"; \n var request;\n try {\n request = new XMLHttpRequest();\n } \n catch (e) { \n request = new ActiveXObject("Msxml2.XMLHTTP"); \n }\n if (window.netscape){\n try {\n if (document.location.toString().substr(0,4) != "http") {\n netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');}\n }\n catch (e) {}\n } \n //DEBUG alert("user["+config.options.txtUploadUserName+"] password[" + config.options.pasUploadPassword + "]");\n // compose headers data\n var sheader = "";\n sheader += "--" + boundary + "\sr\snContent-disposition: form-data; name=\s"";\n sheader += config.macros.upload.formName +"\s"\sr\sn\sr\sn";\n sheader += "backupDir="+backupDir\n +";user=" + username \n +";password=" + password\n +";uploaddir=" + uploadDir;\n // add lock attributes to sheader\n if (window.BidiX && BidiX.GroupAuthoring && BidiX.GroupAuthoring.lock) {\n var l = BidiX.GroupAuthoring.lock.myLock;\n sheader += ";lockuser=" + l.user\n + ";mtime=" + l.mtime\n + ";locktime=" + l.locktime;\n }\n sheader += ";;\sr\sn"; \n sheader += "--" + boundary + "\sr\sn";\n sheader += "Content-disposition: form-data; name=\s"userfile\s"; filename=\s""+toFilename+"\s"\sr\sn";\n sheader += "Content-Type: " + config.macros.upload.contentType + "\sr\sn";\n sheader += "Content-Length: " + content.length + "\sr\sn\sr\sn";\n // compose trailer data\n var strailer = new String();\n strailer = "\sr\sn--" + boundary + "--\sr\sn";\n //strailer = "--" + boundary + "--\sr\sn";\n var data;\n data = sheader + content + strailer;\n //request.open("POST", storeUrl, true, username, password);\n try {\n request.open("POST", storeUrl, true); \n }\n catch(e) {\n alert(config.macros.upload.messages.crossDomain + "\snError:" +e);\n exit;\n }\n request.onreadystatechange = function () {\n if (request.readyState == 4) {\n if (request.status == 200)\n callbackFn(request.responseText);\n else\n alert(config.macros.upload.messages.errorUploadingContent + "\snStatus: "+request.status.statusText);\n }\n };\n request.setRequestHeader("Content-Length",data.length);\n request.setRequestHeader("Content-Type","multipart/form-data; boundary="+boundary);\n request.send(data); \n};\n\n\nconfig.macros.upload.download = function(uploadUrl, uploadToFilename, uploadDir, uploadBackupDir, \n username, password) {\n var request;\n try {\n request = new XMLHttpRequest();\n } \n catch (e) { \n request = new ActiveXObject("Msxml2.XMLHTTP"); \n }\n try {\n if (uploadUrl.substr(0,4) == "http") {\n netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");\n }\n else {\n netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");\n }\n } catch (e) { }\n //request.open("GET", document.location.toString(), true, username, password);\n try {\n request.open("GET", document.location.toString(), true);\n }\n catch(e) {\n alert(config.macros.upload.messages.crossDomain + "\snError:" +e);\n exit;\n }\n \n request.onreadystatechange = function () {\n if (request.readyState == 4) {\n if(request.status == 200) {\n config.macros.upload.uploadChangesFrom(request.responseText, uploadUrl, \n uploadToFilename, uploadDir, uploadBackupDir, username, password);\n }\n else\n alert(config.macros.upload.messages.errorDownloading.format(\n [document.location.toString()]) + "\snStatus: "+request.status.statusText);\n }\n };\n request.send(null);\n};\n\n//}}}\n////===\n\n////+++!![Initializations]\n\n//{{{\nconfig.lib.options.init('txtUploadStoreUrl','store.php');\nconfig.lib.options.init('txtUploadFilename','');\nconfig.lib.options.init('txtUploadDir','');\nconfig.lib.options.init('txtUploadBackupDir','');\nconfig.lib.options.init('txtUploadUserName',config.options.txtUserName);\nconfig.lib.options.init('pasUploadPassword','');\nsetStylesheet(\n ".pasOptionInput {width: 11em;}\sn"+\n ".txtOptionInput.txtUploadStoreUrl {width: 25em;}\sn"+\n ".txtOptionInput.txtUploadFilename {width: 25em;}\sn"+\n ".txtOptionInput.txtUploadDir {width: 25em;}\sn"+\n ".txtOptionInput.txtUploadBackupDir {width: 25em;}\sn"+\n "",\n "UploadOptionsStyles");\nconfig.shadowTiddlers.UploadDoc = "[[Full Documentation|http://tiddlywiki.bidix.info/l#UploadDoc ]]\sn"; \nconfig.options.chkAutoSave = false; saveOptionCookie('chkAutoSave');\n\n//}}}\n////===\n\n////+++!![Core Hijacking]\n\n//{{{\nconfig.macros.saveChanges.label_orig_UploadPlugin = config.macros.saveChanges.label;\nconfig.macros.saveChanges.label = config.macros.upload.label.saveToDisk;\n\nconfig.macros.saveChanges.handler_orig_UploadPlugin = config.macros.saveChanges.handler;\n\nconfig.macros.saveChanges.handler = function(place)\n{\n if ((!readOnly) && (document.location.toString().substr(0,4) != "http"))\n createTiddlyButton(place,this.label,this.prompt,this.onClick,null,null,this.accessKey);\n};\n\n//}}}\n////===\n
I needed to set up my Windows web server (the venerable O'Reilly WebSite Pro) which supports Microsoft Active Server Pages (ASP) "classic" (not ASP.NET). So I set about writing a "store.asp" for UploadPlugin. I had to use a separate ASP upload component, as ASP's Form object can't handle multipart/form-data (the format of uploaded files). \n\nTo make a long story short, I discovered a couple of things in UploadPlugin that kept it from working:\n* The boundary string in the HTTP Content-Type: header didn't match the actual boundary string.\n* There is an extra CRLF after the first part before the second boundary.\nThe first problem arises from the fact that the Microsoft XMLHTTP that UploadPlugin uses lowercases the data passed to its SetRequestheader() function. The boundary string used by [[BidiX |http://tiddlywiki.bidix.info/]] is right out of [[RFC 1867|http://www.faqs.org/rfcs/rfc1867.html]], ending in {{{AaB03x}}}. But this gets folded to {{{aab03x}}} by SetRequestHeader(). My ASP uploading component failed to recognize the boundary because of that case mismatch. ''Solution:'' Change the line that sets the boundary so that all letters are lower case:\n{{{\nvar boundary = "---------------------------"+"aab03x"; //[rbd] Must be lower case\n}}}\nThe second one may not really be a problem, but I did find it in my packet sniffing and comparing to browser-generated uploads. In any case, I did make the following change to bring the form data into RFC compliance:\n{{{\nsheader += ";;\sr\sn"; \nsheader += "--" + boundary + "\sr\sn"; // [[rbd] No leading crlf!\n}}}\nAfter doing this, the uploading started working! I assume that PHP's form processor is immune to both of these. I'll leave it to ~BidiX to make changes when he's doing a release. If you re interested in using UploadPlugin with ASP classic, contact me and I'll give you the store.asp file and the required ASP upload component. \n
Welcome to Red Mountain Vista, my little corner of the [[TiddlyWiki|http://www.tiddlywiki.com]] world. I learned about ~TiddlyWiki in August 2006, and have been thrilled with it since. My heartfelt thanks go out to Jeremy Ruston, ~TiddlyWiki's creator, and all of the other people who have contributed to the effort!\n\nMy focus has been using ~TiddlyWiki as the core of a new web interface to the [[Share your Sky!|http://shareyoursky.com/]] option of my [[ACP Observatory Control Software|http://acp.dc3.com/]], an astronomical observatory control system with remote/web access. See [[Remote Astronomy with TiddlyWiki as the Web Interface]]. The web features of ACP have been around for years. The 5 year old web pages are very crude, reflecting the browser capabilities available back in 2001. All that has changed, and ~TiddlyWiki is at the heart of the new content.\n\nThe picture of Red Mountain was taken just up the road from my home/office (to get the power poles out of the way). I am located on the southern border ot the Tonto National Forest in northeast Mesa, AZ.