EncryptTaggedPlugin
/***\n|Name|EncryptionPlugin|\n|Source|http://www.dataspill.org/EncryptionPlugin.html|\n|Version|0.0.3|\n|Author|redpig@dataspill.org|\n|License|GPLv2|\n|~CoreVersion|2.1|\n|Type|plugin|\n|Requires||\n|Overrides||\n|Description|Encrypt/decrypt tiddlers with AES|\n\n!!!!!Usage\n<<<\nThis plugin adds AES encryption support to TiddlyWiki. There are several interfaces to do so:\n\n* Tag-wide encryption/decryption with a macro:\n\n{{{<<<encrypt_tag "tagname" "password-realm" "button text">>>}}}\n\nAs shown above, passwords are cached on a per-zone basis where a zone may be\nsomething like "work" or "home". This does not have to have any relation to\nyour tag, but if omitted, will be the same.\n\n* If you're done encrypting/decrypting, you may want to clear the cached password. This can be done with another button creating macro:\n\n{{{<<<encrypt_clear "password-realm">>>}}}\n\n* If you want to take a peek at some encrypted data without fear of saving it, you can add a preview button to your toolbar. If you are using the normal ViewTemplate, just add ''peekcrypt'' like below:\n\n{{{<div class='toolbar' macro='toolbar ... peekcrypt>...}}}\n\n* If you want to just encrypt/decrypt one tiddler at a time, you can add an\n ''autocrypt'' button to the toolbar:\n\n{{{<div class='toolbar' macro='toolbar ... autocrypt>...}}}\n\n* Note: the entropy collection is not particularly great so far. If this is very sensitive datรฆ, you may want to consider a more secure solution or help beef this one up!\n\n\n!!!!!Options\n<<option chkEncryptTags>> Encrypt tiddler tags\n<<option chkEncryptTitles>> Encrypt tiddler titles and references\n\n!!!!!TODO\n* Add versioning and reader/writer for encrypted tiddlers w.out needing a tag.\n* Figure out a better entropy system - look at Clipperz\n* Encrypt-on-save: auto re-encrypt specifically tagged items\n** Alternately add a decrypt-to-edit/encrypt-on-save for a specific tiddler\n* Add toolbar commands for encrypt/decrypt\n* Embed per-tiddler encryption options and autodetect otherwise\n* Add macro-wrapped encryption (think tasklist/tasksum)\n* Make previews more like light boxes\n* Encrypt all object data for a tiddler and not just text/title/tags (JSON?)\n* Compressed or minjs'd code\n* Make reference title-encryption replacement safer (e.g. proper replace())\n* Clean up code encapsulation some more\n* Automatic password updating (e.g. decrypt/re-encrypt all tiddlers)\n* Configurable key bits (allow user to use stronger keys than 128 bit)\n* Local-file key support (load a key file that is optionally password-protected)\n* Encapsulate the rumsby's entropy collector slightly better\n\n<<<\n!!!!!Installation\n<<<\nimport (or copy/paste) the following tiddlers into your document:\n''EncryptTiddlerPlugin'' (tagged with <<tag systemConfig>>)\n<<<\n!!!!!Revisions\n<<<\n!''2007.02.20 [0.0.3]''\n* Added EncryptionPayload: an extensible payload object for safe forward compat\n* Cleaned up peekcrypt and the askUser object\n* Added support for encrypt/decrypt in the toolbar: autocrypt\n!''2007.02.18 [0.0.2]''\n* Added informational messages\n* Added AESprng from Steve Rumsby's Encryption plugin\n* Namespaced the Rijndael plugin on ''Rijndael''\n* Added .encrypt/.decrypt to Tiddler prototype\n* Kinda encapsulated my code in encryptionPlugin\n* Moved encrypted zone name into the encrypted tiddler to make previewing easy\n* Added previewSecret toolbar command to allow previewing of data\n* Added two checkbox options: encrypt tags and encrypt titles\n* Separate tag-wide encryption from password zones/realms. encrypt_tag now supports that.\n* Updated the usage a little bit\n* Added a loop which ensures that entropy collection continues.\n'!'2007.02.15 [0.0.1]''\n* first cut.\n<<<\n!!!!!Credits\n>Based on copyTiddlerPlugin developed by EricShulman from [[ELS Design Studios|http://www.elsdesign.com]]\n>AES implementation courtesy of Fritz Schneider. Thanks Fritz!\n\n!!!!!HTML tidbits\n<html>\n<style>\n.previewToolbar {\ndisplay: block;\ntext-align: right;\n}\n</style>\n<div id='previewArea' style="position:absolute; top:10%; right:50%; margin: 0.5em; padding: 0.5em; position:fixed !important; z-index:99; border: 1px solid #841; background: #db4; color: #014; display:none;">\n</div>\n</html>\n\n!!!!!Code\n***/\n//{{{\n\n\n/* An encapsulated version of Fritz Schneider's Rijndael implementation:\n * Copyright (c) 2001 Fritz Schneider\n * http://www-cse.ucsd.edu/~fritz/rijndael.html\n * Encapsulation by Will Drewry, 2007\n */\nfunction Rijndael() {}; // namesapce\n/* Configurable variables:\n * Valid values are 128, 192, or 256\n */\nRijndael.keySizeInBits = 128;\nRijndael.blockSizeInBits = 128;\n/* PRNG container */\nRijndael.prng = undefined;\n// Private date\n// Note: in the following code the two dimensional arrays are indexed as\n// you would probably expect, as array[row][column]. The state arrays\n// are 2d arrays of the form state[4][Nb].\n\n\n// The number of rounds for the cipher, indexed by [Nk][Nb]\nRijndael.roundsArray = [ ,,,,[,,,,10,, 12,, 14],,\n [,,,,12,, 12,, 14],,\n [,,,,14,, 14,, 14] ];\n// The number of bytes to shift by in shiftRow, indexed by [Nb][row]\nRijndael.shiftOffsets = [ ,,,,[,1, 2, 3],,[,1, 2, 3],,[,1, 3, 4] ];\n// The round constants used in subkey expansion\nRijndael.Rcon = [ \n 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,\n 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,\n 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,\n 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4,\n 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ];\n// Precomputed lookup table for the SBox\nRijndael.SBox = [\n 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, \n 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, \n 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, \n 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, \n 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, \n 179, 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, \n 190, 57, 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, \n 249, 2, 127, 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, \n 188, 182, 218, 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, \n 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, \n 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73,\n 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, \n 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, \n 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, \n 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225,\n 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223,\n 140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, \n 22 ];\n// Precomputed lookup table for the inverse SBox\nRijndael.SBoxInverse = [\n 82, 9, 106, 213, 48, 54, 165, 56, 191, 64, 163, 158, 129, 243, 215, \n 251, 124, 227, 57, 130, 155, 47, 255, 135, 52, 142, 67, 68, 196, 222, \n 233, 203, 84, 123, 148, 50, 166, 194, 35, 61, 238, 76, 149, 11, 66, \n 250, 195, 78, 8, 46, 161, 102, 40, 217, 36, 178, 118, 91, 162, 73, \n 109, 139, 209, 37, 114, 248, 246, 100, 134, 104, 152, 22, 212, 164, 92, \n 204, 93, 101, 182, 146, 108, 112, 72, 80, 253, 237, 185, 218, 94, 21, \n 70, 87, 167, 141, 157, 132, 144, 216, 171, 0, 140, 188, 211, 10, 247, \n 228, 88, 5, 184, 179, 69, 6, 208, 44, 30, 143, 202, 63, 15, 2, \n 193, 175, 189, 3, 1, 19, 138, 107, 58, 145, 17, 65, 79, 103, 220, \n 234, 151, 242, 207, 206, 240, 180, 230, 115, 150, 172, 116, 34, 231, 173,\n 53, 133, 226, 249, 55, 232, 28, 117, 223, 110, 71, 241, 26, 113, 29, \n 41, 197, 137, 111, 183, 98, 14, 170, 24, 190, 27, 252, 86, 62, 75, \n 198, 210, 121, 32, 154, 219, 192, 254, 120, 205, 90, 244, 31, 221, 168,\n 51, 136, 7, 199, 49, 177, 18, 16, 89, 39, 128, 236, 95, 96, 81,\n 127, 169, 25, 181, 74, 13, 45, 229, 122, 159, 147, 201, 156, 239, 160,\n 224, 59, 77, 174, 42, 245, 176, 200, 235, 187, 60, 131, 83, 153, 97, \n 23, 43, 4, 126, 186, 119, 214, 38, 225, 105, 20, 99, 85, 33, 12,\n 125 ];\n// Cipher parameters ... do not change these\nRijndael.Nk = Rijndael.keySizeInBits / 32;\nRijndael.Nb = Rijndael.blockSizeInBits / 32;\nRijndael.Nr = Rijndael.roundsArray[Rijndael.Nk][Rijndael.Nb];\n\n/* Functions */\n// This method circularly shifts the array left by the number of elements\n// given in its parameter. It returns the resulting array and is used for \n// the ShiftRow step. Note that shift() and push() could be used for a more \n// elegant solution, but they require IE5.5+, so I chose to do it manually. \nRijndael.cyclicShiftLeft = function(theArray, positions) {\n var temp = theArray.slice(0, positions);\n theArray = theArray.slice(positions).concat(temp);\n return theArray;\n};\n// Multiplies the element "poly" of GF(2^8) by x. See the Rijndael spec.\nRijndael.xtime = function(poly) {\n poly <<= 1;\n return ((poly & 0x100) ? (poly ^ 0x11B) : (poly));\n};\n// Multiplies the two elements of GF(2^8) together and returns the result.\n// See the Rijndael spec, but should be straightforward: for each power of\n// the indeterminant that has a 1 coefficient in x, add y times that power\n// to the result. x and y should be bytes representing elements of GF(2^8)\nRijndael.mult_GF256 = function(x, y) {\n var bit, result = 0;\n\n for (bit = 1; bit < 256; bit *= 2, y = Rijndael.xtime(y)) {\n if (x & bit) \n result ^= y;\n }\n return result;\n};\n// Performs the substitution step of the cipher. State is the 2d array of\n// state information (see spec) and direction is string indicating whether\n// we are performing the forward substitution ("encrypt") or inverse \n// substitution (anything else)\nRijndael.byteSub = function(state, direction) {\n var S;\n if (direction == "encrypt") // Point S to the SBox we're using\n S = Rijndael.SBox;\n else\n S = Rijndael.SBoxInverse;\n for (var i = 0; i < 4; i++) // Substitute for every byte in state\n for (var j = 0; j < Rijndael.Nb; j++)\n state[i][j] = S[state[i][j]];\n};\n// Performs the row shifting step of the cipher.\nRijndael.shiftRow = function(state, direction) {\n for (var i=1; i<4; i++) // Row 0 never shifts\n if (direction == "encrypt")\n state[i] = Rijndael.cyclicShiftLeft(state[i], Rijndael.shiftOffsets[Rijndael.Nb][i]);\n else\n state[i] = Rijndael.cyclicShiftLeft(state[i], Rijndael.Nb - Rijndael.shiftOffsets[Rijndael.Nb][i]);\n};\n// Performs the column mixing step of the cipher. Most of these steps can\n// be combined into table lookups on 32bit values (at least for encryption)\n// to greatly increase the speed. \nRijndael.mixColumn = function(state, direction) {\n var b = []; // Result of matrix multiplications\n for (var j = 0; j < Rijndael.Nb; j++) { // Go through each column...\n for (var i = 0; i < 4; i++) { // and for each row in the column...\n if (direction == "encrypt")\n b[i] = Rijndael.mult_GF256(state[i][j], 2) ^ // perform mixing\n Rijndael.mult_GF256(state[(i+1)%4][j], 3) ^ \n state[(i+2)%4][j] ^ \n state[(i+3)%4][j];\n else \n b[i] = Rijndael.mult_GF256(state[i][j], 0xE) ^ \n Rijndael.mult_GF256(state[(i+1)%4][j], 0xB) ^\n Rijndael.mult_GF256(state[(i+2)%4][j], 0xD) ^\n Rijndael.mult_GF256(state[(i+3)%4][j], 9);\n }\n for (var i = 0; i < 4; i++) // Place result back into column\n state[i][j] = b[i];\n }\n};\n// Adds the current round key to the state information. Straightforward.\nRijndael.addRoundKey = function(state, roundKey) {\n for (var j = 0; j < Rijndael.Nb; j++) { // Step through columns...\n state[0][j] ^= (roundKey[j] & 0xFF); // and XOR\n state[1][j] ^= ((roundKey[j]>>8) & 0xFF);\n state[2][j] ^= ((roundKey[j]>>16) & 0xFF);\n state[3][j] ^= ((roundKey[j]>>24) & 0xFF);\n }\n};\n// This function creates the expanded key from the input (128/192/256-bit)\n// key. The parameter key is an array of bytes holding the value of the key.\n// The returned value is an array whose elements are the 32-bit words that \n// make up the expanded key.\nRijndael.keyExpansion = function(key) {\n var expandedKey = new Array();\n var temp;\n\n // in case the key size or parameters were changed...\n Rijndael.Nk = Rijndael.keySizeInBits / 32; \n Rijndael.Nb = Rijndael.blockSizeInBits / 32;\n Rijndael.Nr = Rijndael.roundsArray[Rijndael.Nk][Rijndael.Nb];\n\n for (var j=0; j < Rijndael.Nk; j++) // Fill in input key first\n expandedKey[j] = \n (key[4*j]) | (key[4*j+1]<<8) | (key[4*j+2]<<16) | (key[4*j+3]<<24);\n\n // Now walk down the rest of the array filling in expanded key bytes as\n // per Rijndael's spec\n for (j = Rijndael.Nk; j < Rijndael.Nb * (Rijndael.Nr + 1); j++) { // For each word of expanded key\n temp = expandedKey[j - 1];\n if (j % Rijndael.Nk == 0) \n temp = ( (Rijndael.SBox[(temp>>8) & 0xFF]) |\n (Rijndael.SBox[(temp>>16) & 0xFF]<<8) |\n (Rijndael.SBox[(temp>>24) & 0xFF]<<16) |\n (Rijndael.SBox[temp & 0xFF]<<24) ) ^ Rijndael.Rcon[Math.floor(j / Rijndael.Nk) - 1];\n else if (Rijndael.Nk > 6 && j % Rijndael.Nk == 4)\n temp = (Rijndael.SBox[(temp>>24) & 0xFF]<<24) |\n (Rijndael.SBox[(temp>>16) & 0xFF]<<16) |\n (Rijndael.SBox[(temp>>8) & 0xFF]<<8) |\n (Rijndael.SBox[temp & 0xFF]);\n expandedKey[j] = expandedKey[j-Rijndael.Nk] ^ temp;\n }\n return expandedKey;\n};\n// Rijndael's round functions... \nRijndael.Round = function(state, roundKey) {\n Rijndael.byteSub(state, "encrypt");\n Rijndael.shiftRow(state, "encrypt");\n Rijndael.mixColumn(state, "encrypt");\n Rijndael.addRoundKey(state, roundKey);\n};\nRijndael.InverseRound = function(state, roundKey) {\n Rijndael.addRoundKey(state, roundKey);\n Rijndael.mixColumn(state, "decrypt");\n Rijndael.shiftRow(state, "decrypt");\n Rijndael.byteSub(state, "decrypt");\n};\nRijndael.FinalRound = function(state, roundKey) {\n Rijndael.byteSub(state, "encrypt");\n Rijndael.shiftRow(state, "encrypt");\n Rijndael.addRoundKey(state, roundKey);\n};\nRijndael.InverseFinalRound = function(state, roundKey){\n Rijndael.addRoundKey(state, roundKey);\n Rijndael.shiftRow(state, "decrypt");\n Rijndael.byteSub(state, "decrypt"); \n};\n// encrypt is the basic encryption function. It takes parameters\n// block, an array of bytes representing a plaintext block, and expandedKey,\n// an array of words representing the expanded key previously returned by\n// keyExpansion(). The ciphertext block is returned as an array of bytes.\nRijndael.encrypt = function(block, expandedKey) {\n var i;\n if (!block || block.length*8 != Rijndael.blockSizeInBits)\n return; \n if (!expandedKey)\n return;\n\n block = Rijndael.packBytes(block);\n Rijndael.addRoundKey(block, expandedKey);\n for (i=1; i<Rijndael.Nr; i++) \n Rijndael.Round(block, expandedKey.slice(Rijndael.Nb*i, Rijndael.Nb*(i+1)));\n Rijndael.FinalRound(block, expandedKey.slice(Rijndael.Nb*Rijndael.Nr));\n return Rijndael.unpackBytes(block);\n};\n// decrypt is the basic decryption function. It takes parameters\n// block, an array of bytes representing a ciphertext block, and expandedKey,\n// an array of words representing the expanded key previously returned by\n// keyExpansion(). The decrypted block is returned as an array of bytes.\nRijndael.decrypt = function(block, expandedKey) {\n var i;\n if (!block || block.length*8 != Rijndael.blockSizeInBits)\n return;\n if (!expandedKey)\n return;\n\n block = Rijndael.packBytes(block);\n Rijndael.InverseFinalRound(block, expandedKey.slice(Rijndael.Nb*Rijndael.Nr));\n for (i = Rijndael.Nr - 1; i>0; i--) \n Rijndael.InverseRound(block, expandedKey.slice(Rijndael.Nb*i, Rijndael.Nb*(i+1)));\n Rijndael.addRoundKey(block, expandedKey);\n return Rijndael.unpackBytes(block);\n};\n/** byte array code - may move to a separate object later **/\n// This method takes a byte array (byteArray) and converts it to a string by\n// applying String.fromCharCode() to each value and concatenating the result.\n// The resulting string is returned. Note that this function SKIPS zero bytes\n// under the assumption that they are padding added in formatPlaintext().\n// Obviously, do not invoke this method on raw data that can contain zero\n// bytes. It is really only appropriate for printable ASCII/Latin-1 \n// values. Roll your own function for more robust functionality :)\nRijndael.byteArrayToString = function(byteArray) {\n var result = "";\n for(var i=0; i<byteArray.length; i++)\n if (byteArray[i] != 0) \n result += String.fromCharCode(byteArray[i]);\n return result;\n};\n// Reverses the above method\nRijndael.stringToByteArray = function(text) {\n var i;\n // if primitive string or String instance\n if (typeof text == "string" || text.indexOf) {\n text = text.split("");\n // Unicode issues here (ignoring high byte)\n for (i=0; i<text.length; i++)\n text[i] = text[i].charCodeAt(0) & 0xFF;\n }\n return text;\n};\n// This function takes an array of bytes (byteArray) and converts them\n// to a hexadecimal string. Array element 0 is found at the beginning of \n// the resulting string, high nibble first. Consecutive elements follow\n// similarly, for example [16, 255] --> "10ff". The function returns a \n// string.\nRijndael.byteArrayToHex = function(byteArray) {\n var result = "";\n if (!byteArray)\n return;\n for (var i=0; i<byteArray.length; i++)\n result += ((byteArray[i]<16) ? "0" : "") + byteArray[i].toString(16);\n return result;\n};\n// This function converts a string containing hexadecimal digits to an \n// array of bytes. The resulting byte array is filled in the order the\n// values occur in the string, for example "10FF" --> [16, 255]. This\n// function returns an array. \nRijndael.hexToByteArray = function(hexString) {\n var byteArray = [];\n if (hexString.length % 2) // must have even length\n return;\n if (hexString.indexOf("0x") == 0 || hexString.indexOf("0X") == 0)\n hexString = hexString.substring(2);\n for (var i = 0; i<hexString.length; i += 2) \n byteArray[Math.floor(i/2)] = parseInt(hexString.slice(i, i+2), 16);\n return byteArray;\n};\n// This function packs an array of bytes into the four row form defined by\n// Rijndael. It assumes the length of the array of bytes is divisible by\n// four. Bytes are filled in according to the Rijndael spec (starting with\n// column 0, row 0 to 3). This function returns a 2d array.\nRijndael.packBytes = function(octets) {\n var state = new Array();\n if (!octets || octets.length % 4)\n return;\n\n state[0] = new Array(); state[1] = new Array(); \n state[2] = new Array(); state[3] = new Array();\n for (var j=0; j<octets.length; j+= 4) {\n state[0][j/4] = octets[j];\n state[1][j/4] = octets[j+1];\n state[2][j/4] = octets[j+2];\n state[3][j/4] = octets[j+3];\n }\n return state;\n};\n// This function unpacks an array of bytes from the four row format preferred\n// by Rijndael into a single 1d array of bytes. It assumes the input "packed"\n// is a packed array. Bytes are filled in according to the Rijndael spec. \n// This function returns a 1d array of bytes.\nRijndael.unpackBytes = function(packed) {\n var result = new Array();\n for (var j=0; j<packed[0].length; j++) {\n result[result.length] = packed[0][j];\n result[result.length] = packed[1][j];\n result[result.length] = packed[2][j];\n result[result.length] = packed[3][j];\n }\n return result;\n};\n/* Back to Rijndael helpers */\n// This function takes a prospective plaintext (string or array of bytes)\n// and pads it with zero bytes if its length is not a multiple of the block \n// size. If plaintext is a string, it is converted to an array of bytes\n// in the process. The type checking can be made much nicer using the \n// instanceof operator, but this operator is not available until IE5.0 so I \n// chose to use the heuristic below. \nRijndael.formatPlaintext = function(plaintext) {\n var bpb = Rijndael.blockSizeInBits / 8; // bytes per block\n var i;\n // if primitive string or String instance\n if (typeof plaintext == "string" || plaintext.indexOf) {\n plaintext = plaintext.split("");\n // Unicode issues here (ignoring high byte)\n for (i=0; i<plaintext.length; i++)\n plaintext[i] = plaintext[i].charCodeAt(0) & 0xFF;\n }\n for (i = bpb - (plaintext.length % bpb); i > 0 && i < bpb; i--) \n plaintext[plaintext.length] = 0;\n return plaintext;\n};\n// Returns an array containing "howMany" random bytes, but uses the PRNG instead\n// of Fritz's fixed example.\nRijndael.getRandomBytes = function(howMany) {\n var i;\n var bytes = new Array();\n for (i=0; i<howMany; i++)\n bytes[i] = Rijndael.prng.nextInt(255);\n return bytes;\n};\n/* Public API */\n// rijndaelEncrypt(plaintext, key, mode)\n// Encrypts the plaintext using the given key and in the given mode. \n// The parameter "plaintext" can either be a string or an array of bytes. \n// The parameter "key" must be an array of key bytes. If you have a hex \n// string representing the key, invoke hexToByteArray() on it to convert it \n// to an array of bytes. The third parameter "mode" is a string indicating\n// the encryption mode to use, either "ECB" or "CBC". If the parameter is\n// omitted, ECB is assumed.\n// \n// An array of bytes representing the cihpertext is returned. To convert \n// this array to hex, invoke byteArrayToHex() on it. If you are using this \n// "for real" it is a good idea to change the function getRandomBytes() to \n// something that returns truly random bits.\nRijndael.rijndaelEncrypt = function(plaintext, key, mode) {\n var expandedKey, i, aBlock;\n var bpb = Rijndael.blockSizeInBits / 8; // bytes per block\n var ct; // ciphertext\n\n if (!plaintext || !key)\n return;\n if (key.length*8 != Rijndael.keySizeInBits)\n return; \n if (mode == "CBC")\n ct = Rijndael.getRandomBytes(bpb); // get IV\n else {\n mode = "ECB";\n ct = new Array();\n }\n\n // convert plaintext to byte array and pad with zeros if necessary. \n plaintext = Rijndael.formatPlaintext(plaintext);\n expandedKey = Rijndael.keyExpansion(key);\n for (var block=0; block<plaintext.length / bpb; block++) {\n aBlock = plaintext.slice(block*bpb, (block+1)*bpb);\n if (mode == "CBC")\n for (var i=0; i<bpb; i++) \n aBlock[i] ^= ct[block*bpb + i];\n ct = ct.concat(Rijndael.encrypt(aBlock, expandedKey));\n }\n return ct;\n};\n// rijndaelDecrypt(ciphertext, key, mode)\n// Decrypts the using the given key and mode. The parameter "ciphertext" \n// must be an array of bytes. The parameter "key" must be an array of key \n// bytes. If you have a hex string representing the ciphertext or key, \n// invoke hexToByteArray() on it to convert it to an array of bytes. The\n// parameter "mode" is a string, either "CBC" or "ECB".\n//\n// An array of bytes representing the plaintext is returned. To convert \n// this array to a hex string, invoke byteArrayToHex() on it. To convert it \n// to a string of characters, you can use byteArrayToString().\nRijndael.rijndaelDecrypt = function(ciphertext, key, mode) {\n var expandedKey;\n var bpb = Rijndael.blockSizeInBits / 8; // bytes per block\n var pt = new Array(); // plaintext array\n var aBlock; // a decrypted block\n var block; // current block number\n\n if (!ciphertext || !key || typeof ciphertext == "string")\n return;\n if (key.length*8 != Rijndael.keySizeInBits)\n return; \n if (!mode)\n mode = "ECB"; // assume ECB if mode omitted\n\n expandedKey = Rijndael.keyExpansion(key);\n // work backwards to accomodate CBC mode \n for (block=(ciphertext.length / bpb)-1; block>0; block--) {\n aBlock = \n Rijndael.decrypt(ciphertext.slice(block*bpb,(block+1)*bpb), expandedKey);\n if (mode == "CBC") \n for (var i=0; i<bpb; i++) \n pt[(block-1)*bpb + i] = aBlock[i] ^ ciphertext[(block-1)*bpb + i];\n else \n pt = aBlock.concat(pt);\n }\n // do last block if ECB (skips the IV in CBC)\n if (mode == "ECB")\n pt = Rijndael.decrypt(ciphertext.slice(0, bpb), expandedKey).concat(pt);\n return pt;\n};\n/* end Rijndael */\n\n\n\n/*-----------------------*/\n // AES based pseudorandom number generator from YATWA.\n /* Constructor. Called with an array of 32 byte (0-255) values\n containing the initial seed. */\n\nfunction AESprng(seed) {\n this.key = new Array();\n this.key = seed;\n this.itext = Rijndael.byteArrayToString(Rijndael.hexToByteArray("9F489613248148F9C27945C6AE62EECA3E3367BB14064E4E6DC67A9F28AB3BD1"));\n this.nbytes = 0; // Bytes left in buffer\n\n this.next = AESprng_next;\n this.nextbits = AESprng_nextbits;\n this.nextInt = AESprng_nextInt;\n this.round = AESprng_round;\n this.originalBlockSizeInBits = Rijndael.blockSizeInBits;\n this.originalKeySizeInBits = Rijndael.keySizeInBits;\n Rijndael.keySizeInBits = 256;\n Rijndael.blockSizeInBits = 256;\n\n /* Encrypt the initial text with the seed key\n three times, feeding the output of the encryption\n back into the key for the next round. */\n var i, ct;\n for (i = 0; i < 3; i++) {\n this.key = Rijndael.rijndaelEncrypt(this.itext, this.key, "ECB");\n }\n\n /* Now make between one and four additional\n key-feedback rounds, with the number determined\n by bits from the result of the first three\n rounds. */\n\n var n = 1 + (this.key[3] & 2) + (this.key[9] & 1);\n for (i = 0; i < n; i++)\n this.key = Rijndael.rijndaelEncrypt(this.itext, this.key, "ECB");\n Rijndael.blockSizeInBits = this.originalBlockSizeInBits;\n Rijndael.keySizeInBits = this.originalKeySizeInBits;\n}\n\nfunction AESprng_round() {\n this.originalBlockSizeInBits = Rijndael.blockSizeInBits;\n this.originalKeySizeInBits = Rijndael.keySizeInBits;\n Rijndael.keySizeInBits = 256;\n Rijndael.blockSizeInBits = 256;\n this.key = Rijndael.rijndaelEncrypt(this.itext, this.key, "ECB");\n this.nbytes = 32;\n Rijndael.blockSizeInBits = this.originalBlockSizeInBits;\n Rijndael.keySizeInBits = this.originalKeySizeInBits;\n}\n\n// Return next byte from the generator\nfunction AESprng_next() {\n if (this.nbytes <= 0)\n this.round();\n return(this.key[--this.nbytes]);\n}\n\n// Return n bit integer value (up to maximum integer size)\nfunction AESprng_nextbits(n) {\n var i, w = 0, nbytes = Math.floor((n + 7) / 8);\n for (i = 0; i < nbytes; i++)\n w = (w << 8) | this.next();\n return w & ((1 << n) - 1);\n}\n\n// Return integer between 0 and n inclusive\nfunction AESprng_nextInt(n) {\n var p = 1, nb = 0;\n\n // Determine smallest p, 2^p > n\n // nb = log_2 p\n while (n >= p) {\n p <<= 1;\n nb++;\n }\n p--;\n /* Generate values from 0 through n by first generating\n values v from 0 to (2^p)-1, then discarding any results v > n.\n For the rationale behind this (and why taking\n values mod (n + 1) is biased toward smaller values, see\n Ferguson and Schneier, "Practical Cryptography",\n ISBN 0-471-22357-3, section 10.8). */\n while (true) {\n var v = this.nextbits(nb) & p;\n if (v <= n)\n return v;\n }\n}\n\n\n// Entropy collection utilities\n\n/* Start by declaring static storage and initialise\nthe entropy vector from the time we come through\nhere. */\n\nvar entropyData = new Array(); // Collected entropy data\nvar edlen = 0; // Keyboard array data length\n\naddEntropyTime(); // Start entropy collection with page load time\nce(); // Roll milliseconds into initial entropy\n\n// Add a byte to the entropy vector\n\nfunction addEntropyByte(b) {\n entropyData[edlen++] = b;\n}\n\n/* Capture entropy. When the user presses a key or performs\nvarious other events for which we can request\nnotification, add the time in 255ths of a second to the\nentropyData array. The name of the function is short\nso it doesn't bloat the form object declarations in\nwhich it appears in various "onXXX" events. */\n\nfunction ce() {\n addEntropyByte(Math.floor((((new Date).getMilliseconds()) * 255) / 999));\n}\n\n// Add a 32 bit quantity to the entropy vector\n\nfunction addEntropy32(w) {\n var i;\n\n for (i = 0; i < 4; i++) {\n addEntropyByte(w & 0xFF);\n w >>= 8;\n }\n}\n\n/* Add the current time and date (milliseconds since the epoch,\ntruncated to 32 bits) to the entropy vector. */\nfunction addEntropyTime() {\n addEntropy32((new Date()).getTime());\n}\n\n/* Start collection of entropy from mouse movements. The\nargument specifies the number of entropy items to be\nobtained from mouse motion, after which mouse motion\nwill be ignored. Note that you can re-enable mouse\nmotion collection at any time if not already underway. */\nvar mouseMotionCollect = 0;\nvar oldMoveHandler; // For saving and restoring mouse move handler in IE4\n\n// Collect count events event n usecs\nfunction loopSampleMouse(count, timeout, calls) {\n if (edlen > 100000) {\n // Don't collect more for now\n setTimeout("loopSampleMouse("+ count + "," + timeout + "," + calls + ")", timeout); // once a minute\n }\n if (calls > 0) {\n calls--;\n mouseMotionEntropy(count);\n setTimeout("loopSampleMouse("+ count + "," + timeout + "," + calls + ")", timeout); // once a minute\n }\n}\nloopSampleMouse(100, 60000, 10000); // 100 events per minute\n\nfunction mouseMotionEntropy(maxsamp) {\n if (mouseMotionCollect <= 0) {\n mouseMotionCollect = maxsamp;\n if ((document.implementation.hasFeature("Events", "2.0")) &&\n document.addEventListener) {\n // Browser supports Document Object Model (DOM) 2 events\n document.addEventListener("mousemove", mouseMoveEntropy, false);\n } else {\n if (document.attachEvent) {\n // Internet Explorer 5 and above event model\n document.attachEvent("onmousemove", mouseMoveEntropy);\n } else {\n // Internet Explorer 4 event model\n oldMoveHandler = document.onmousemove;\n document.onmousemove = mouseMoveEntropy;\n }\n }\n//dump("Mouse enable", mouseMotionCollect);\n }\n}\n\n/* Collect entropy from mouse motion events. Note that\nthis is craftily coded to work with either DOM2 or Internet\nExplorer style events. Note that we don't use every successive\nmouse movement event. Instead, we XOR the three bytes collected\nfrom the mouse and use that to determine how many subsequent\nmouse movements we ignore before capturing the next one. */\nvar mouseEntropyTime = 0; // Delay counter for mouse entropy collection\n\nfunction mouseMoveEntropy(e) {\n if (!e) {\n e = window.event; // Internet Explorer event model\n }\n if (mouseMotionCollect > 0) {\n if (mouseEntropyTime-- <= 0) {\n addEntropyByte(e.screenX & 0xFF);\n addEntropyByte(e.screenY & 0xFF);\n ce();\n mouseMotionCollect--;\n mouseEntropyTime = (entropyData[edlen - 3] ^ entropyData[edlen - 2] ^\n entropyData[edlen - 1]) % 19;\n //dump("Mouse Move", byteArrayToHex(entropyData.slice(-3)));\n }\n if (mouseMotionCollect <= 0) {\n if (document.removeEventListener) {\n document.removeEventListener("mousemove", mouseMoveEntropy, false);\n } else if (document.detachEvent) {\n document.detachEvent("onmousemove", mouseMoveEntropy);\n } else {\n document.onmousemove = oldMoveHandler;\n }\n //dump("Spung!", 0);\n }\n }\n}\n\n\n\n/* Compute a 32 byte key value from the entropy vector.\nWe compute the value by taking the SHA1 sum of the even\nand odd bytes respectively of the entropy vector, then\nconcatenating the two SHA1 sums. */\nfunction keyFromEntropy() {\n var i, j, k = new Array(32);\n\n if (edlen == 0) {\n alert("Not enough entropy collected yet!");\n }\n //dump("Entropy bytes", edlen);\n\n var holder = new Array(edlen);\n // Get even\n for (i = 0, j = 0; i < edlen; ++j, i += 2) {\n holder[j] = entropyData[i];\n }\n var digestBits = Crypto.be32sToHex(Crypto.sha1(holder, holder.length));\n for (i = 0; i < 16; i++) { // This truncates SHA1. What effect does that have?\n k[i] = digestBits[i];\n }\n\n // Get odd\n for (i = 1, j = 0; i < edlen; ++j, i += 2) {\n holder[j] = entropyData[i];\n }\n digestBits = Crypto.be32sToHex(Crypto.sha1(holder, holder.length));\n\n // Concat\n for (i = 0; i < 16; i++) {\n k[i + 16] = digestBits[i];\n }\n\n //dump("keyFromEntropy", byteArrayToHex(k));\n return k;\n}\n\n/** EncryptionPayload object **/\n\n// For reading/writing encrypted Tiddlers\nfunction EncryptionPayload() {\n // Arbitrary unicode unlikely to start an entry\n this.MAGIC = EncryptionPayload.MAGIC;\n this.VERSION = EncryptionPayload.VERSION;\n this.FIELD_SEPARATOR = EncryptionPayload.FIELD_SEPARATOR;\n this.SUBFIELD_SEPARATOR = EncryptionPayload.SUBFIELD_SEPARATOR;\n this.LAYOUT = EncryptionPayload.LAYOUT\n // Initialize\n for (var i = 2; i < this.LAYOUT.length; ++i) {\n if (this.LAYOUT[i] == 'TAGS') {\n this[this.LAYOUT[i].toLowerCase()] = []\n } else {\n this[this.LAYOUT[i].toLowerCase()] = '';\n }\n }\n\n /** Functions **/\n this.load = function(text) {\n payload = text.split(this.FIELD_SEPARATOR);\n if (payload.length < 2) {\n //displayMessage("Payload too short");\n return false;\n }\n for (var i = 0; i < this.LAYOUT.length; ++i) {\n var field = this.LAYOUT[i];\n var value = '';\n if (payload[i]) value = payload[i];\n switch (field) {\n case 'MAGIC':\n if (value != this.MAGIC) {\n // TODO: proper exception\n //displayMessage("Invalid payload magic");\n return false;\n }\n break;\n case 'VERSION':\n if (value > this.VERSION) {\n //displayMessage("Invalid payload version");\n return false;\n }\n break;\n case 'TAGS':\n this[field.toLowerCase()] = value.split(this.SUBFIELD_SEPARATOR);\n break;\n default:\n this[field.toLowerCase()] = value;\n }\n }\n return true;\n };\n this.serialize = function() {\n var payload = new Array(this.LAYOUT.length);\n\n for (var i = 0; i < this.LAYOUT.length; ++i) {\n var field = this.LAYOUT[i];\n switch (field) {\n case 'MAGIC':\n payload[i] = this.MAGIC;\n break;\n case 'VERSION':\n payload[i] = this.VERSION;\n break;\n case 'TAGS':\n payload[i] = this['tags'].join(this.SUBFIELD_SEPARATOR);\n break;\n default:\n payload[i] = this[field.toLowerCase()];\n }\n }\n return payload.join(this.FIELD_SEPARATOR);\n };\n}\n// Setup EncryptionPayload Constants\nEncryptionPayload.MAGIC = "\su25CB\su25D4\su25D1\su25D5\su25CF";\nEncryptionPayload.VERSION = 1;\nEncryptionPayload.FIELD_SEPARATOR = "$";\nEncryptionPayload.SUBFIELD_SEPARATOR = ",";\n/* Version 1 format\n * magic$version$zone$title$text$tag1|tag2$\n * Future fields can be added after the tags.\n *\n * These are the indices.\n */\nEncryptionPayload.LAYOUT = [\n 'MAGIC',\n 'VERSION',\n 'OPTIONS',\n 'ZONE',\n 'TITLE',\n 'TEXT',\n 'TAGS',\n];\n\n\n\n\n/*-----------------------*/\n\n\nconfig.options.chkEncryptTitles = true;\nconfig.options.chkEncryptTags = true;\nconfig.options.encryptionDefaultZone = 'global';\n\nconfig.messages.encryptionPasswordPrompt = "Enter password for zone %0:";\nconfig.messages.encryptionZonePrompt = "Enter zone:";\nconfig.messages.encryptionWorkStart = "Performing encryption tasks...";\nconfig.messages.encryptionWorkDoneHeader = "Encryption tasks completed:";\nconfig.messages.encryptionWorkDoneDecrypt = "decrypted: '%0'";\nconfig.messages.encryptionWorkDoneEncrypt = "encrypted: '%0'";\nconfig.messages.encryptionTagSyntaxError = "encrypt_tag syntax error: a tag must be specified";\nconfig.messages.encryptionInputMissingDiv = "Unable to open input prompt. No message DIV!";\nconfig.messages.encryptionEncryptedTitle = "Encrypted:%0";\nconfig.messages.encryptionCleared = "Credentials for '%0' cleared.";\n\n\n/* Wraps a string irrespective of existing newlines */\nfunction wrapText(length, text) { var result = ''; var sep = '\sn'; for(var i = 0; i < text.length;) {result += text.substr(i, length); result += sep; i += length; }; return result;}\n\n\n/*** plugin globals ***/\n// global cached passphrase\n// TODO(redpig): add levels by annotating encrypted pages.\n// add structured data to the tiddler which\n// allows for detecting encryption\n// make decrypt not save.\n// \nencryptionPlugin = function() {} // namespace\nencryptionPlugin.engine = {}\nencryptionPlugin.uniqueId = 0;\nencryptionPlugin.newEngine = function(zone, key, mode) {\n encryptionPlugin.engine[zone].key = key;\n encryptionPlugin.engine[zone].mode = mode;\n encryptionPlugin.engine[zone].encrypt = function(pt) {\n return Rijndael.byteArrayToHex(\n Rijndael.rijndaelEncrypt(\n pt,\n Rijndael.stringToByteArray(encryptionPlugin.engine[zone].key),\n encryptionPlugin.engine[zone].mode)\n );\n };\n encryptionPlugin.engine[zone].decrypt = function(ct) {\n // Only hex strings\n var input = ct.replace(/[^A-Za-z0-9]/g, "");\n return Rijndael.byteArrayToString(\n Rijndael.rijndaelDecrypt(\n Rijndael.hexToByteArray(input),\n Rijndael.stringToByteArray(encryptionPlugin.engine[zone].key),\n encryptionPlugin.engine[zone].mode)\n );\n };\n};\n\nencryptionPlugin.askUser = {\n idPrefix: "askUser:",\n closure: undefined, // stores what to do on prompt exit\n reset: function() {encryptionPlugin.askUser.closure = undefined;},\n onAnswer: function(e) {\n var suffix = config.options.encryptionDefaultZone;\n if(!e) var e = window.event;\n switch(e.keyCode) {\n case 13: // Enter\n case 10: // Ctrl-Enter on IE PC\n // Initialize engine from id\n // First strip of password:\n var id = this.getAttribute("id");\n var prefixlen = encryptionPlugin.askUser.idPrefix.length;\n if (id && id.length > prefixlen) {\n suffix = id.substring(prefixlen, id.length);\n }\n // Call the closure to continue if one was set.\n if (encryptionPlugin.askUser.continuation)\n encryptionPlugin.askUser.continuation([suffix, this.value]);\n break;\n case 27: // Escape\n this.value = "";\n clearMessage();\n break;\n }\n encryptionPlugin.askUser.reset();\n return false;\n },\n display: function(msg,type,suffix) {\n clearMessage();\n var e = getMessageDiv();\n if(!e) {\n alert(config.messages.encryptionInputMissingDiv);\n return;\n }\n var ele = createTiddlyElement(e,"input",null,null,null);\n if (type)\n ele.setAttribute("type",type);\n ele.setAttribute("id", encryptionPlugin.askUser.idPrefix+suffix);\n ele.onkeyup = encryptionPlugin.askUser.onAnswer;\n e.appendChild(document.createTextNode(msg));\n e.appendChild(ele);\n return;\n },\n};\n\n\n\nencryptionPlugin.displayArrayMessage = function(text) {\n var e = getMessageDiv();\n if (!e) {\n alert(text);\n return;\n }\n for (var i = 0; i < text.length; ++i) {\n e.appendChild(document.createTextNode(text[i]));\n e.appendChild(document.createElement("br"));\n }\n};\n\nencryptionPlugin.uniqueTitle = function() {\n var ts = (new Date()).formatString("YYYY0MM0DDhhmmss");\n return (ts + (encryptionPlugin.uniqueId++));\n};\n\nencryptionPlugin.getPwAndContinue = function(name, continuation) {\n var setupEngineAndContinue = function(params) {\n var zone = params[0];\n var key = params[1];\n encryptionPlugin.engine[zone] = {};\n // Pad to keysizeinbits/8 length\n var diff = Rijndael.keySizeInBits - (key.length*8);\n if (diff < 0) { // truncate\n key = key.substring(0,Rijndael.keySizeInBits/8);\n } else if (diff > 0) { // pad with zero\n for (var ch = 0; ch < diff/8; ch++) {\n key += "0";\n }\n } \n // Setup a new engine\n encryptionPlugin.newEngine(zone, key,"CBC");\n\n this.value = "";\n clearMessage();\n continuation(params);\n };\n\n if (!name)\n name = config.options.encryptionDefaultZone;\n var msg = config.messages.encryptionPasswordPrompt.format([name]);\n encryptionPlugin.askUser.continuation = setupEngineAndContinue;\n encryptionPlugin.askUser.display(msg, "password", name);\n};\n\nencryptionPlugin.getZonePwAndContinue = function(continuation) {\n var getZone = function(params) {\n var zone = params[1];\n if (encryptionPlugin.engine[zone] == undefined) { // have password, run cont\n encryptionPlugin.getPwAndContinue(zone, continuation);\n } else {\n continuation(params);\n }\n };\n var msg = config.messages.encryptionZonePrompt;\n encryptionPlugin.askUser.continuation = getZone;\n encryptionPlugin.askUser.display(msg, "text", "ignored");\n};\n\n\n\n// Clears the stored passphrase for a given name/zone\nencryptionPlugin.clearCredentials = function(name) {\n if (encryptionPlugin.engine[name])\n delete encryptionPlugin.engine[name];\n};\n\n// Returns an anonymous function which will clear the credentials\n// for the given zone/tag on call.\nencryptionPlugin.clearCredentialsWrapper = function(zone) {\n return function() {\n encryptionPlugin.clearCredentials(zone);\n displayMessage(config.messages.encryptionCleared.format([zone]));\n return false;\n };\n};\n\n// Wraps autoCryptTag and returns a function that will perform the action.\nencryptionPlugin.autoCryptTagWrapper = function(tag, zone) {\n return function() {encryptionPlugin.autoCryptTag(tag, zone);return false;};\n};\n\n// Takes a tag and an encryption zone and automatically encrypts or decrypts each Tiddler\n// in the tag group using the given zone passphrase.\nencryptionPlugin.autoCryptTag = function(tag, zone) {\n var doWork = function(params) {\n var workDone = new Array();\n var tmp = '';\n\n displayMessage(config.messages.encryptionWorkStart);\n workDone.pushUnique(config.messages.encryptionWorkDoneHeader);\n store.forEachTiddler(function(title,tiddler) {\n if (tiddler.tags.find(tag) != null) {\n // Decrypt if the format is recognized, else encrypt!\n var payload = new EncryptionPayload();\n if (payload.load(tiddler.text)) { // decrypt\n tiddler.decrypt(tag, payload);\n workDone.pushUnique(config.messages.encryptionWorkDoneDecrypt.format([tiddler.title]));\n } else { // encrypt\n tmp = tiddler.title;\n tiddler.encrypt(zone, tag);\n workDone.pushUnique(config.messages.encryptionWorkDoneEncrypt.format([tmp]));\n }\n }\n });\n story.refreshAllTiddlers();\n clearMessage();\n encryptionPlugin.displayArrayMessage(workDone);\n };\n\n\n if (encryptionPlugin.engine[zone] == undefined)\n encryptionPlugin.getPwAndContinue(zone, doWork);\n else\n doWork();\n};\n\n/** macros **/\nversion.extensions.EncryptTiddler = { major: 0, minor: 0, revision: 3, date: new Date(2007, 1, 20)};\n\n\n// Usage: encrypt_clear "zone"\nconfig.macros.encrypt_clear = {\n default_label: "encrypt_clear %0",\n prompt: "Clears the passphrase for a given zone"\n};\n\nconfig.macros.encrypt_clear.handler = function(place,macroName,params) { \n var zone = config.options.encryptionDefaultZone;\n if (params[0])\n zone = params[0];\n var label = this.default_label.format([zone]);\n if (params[1] != undefined)\n label = params[1];\n createTiddlyButton(place,label,this.prompt, encryptionPlugin.clearCredentialsWrapper(zone),null,null,this.accessKey);\n};\n\n\n\n// Usage: encrypt_tag ["tag" ["zone"]] ["label"]\nconfig.macros.encrypt_tag = {\n default_label: "encrypt_tag: %0",\n prompt: "Encrypt/decrypt tiddlers of a given tag and zone"\n};\n\nconfig.macros.encrypt_tag.handler = function(place,macroName,params) { \n if (!params[0]) {\n displayMessage(config.messages.encryptionTagSyntaxError);\n return false;\n }\n var tag = params[0];\n var zone = params[0];\n var label = this.default_label.format([zone]);\n if (params[1])\n label = params[1];\n if (params[2]) {\n zone = params[1];\n label = params[2];\n }\n createTiddlyButton(place,label,this.prompt,\n encryptionPlugin.autoCryptTagWrapper(tag, zone),\n null,null,this.accessKey);\n};\n\n\n/*** HELPERS ***/\nfunction getPreviewDiv()\n{\n var msgArea = document.getElementById("previewArea");\n if(!msgArea)\n return null;\n if(!msgArea.hasChildNodes()) {\n createTiddlyButton(createTiddlyElement(msgArea,"div",null,"previewToolbar"),\n "x",\n config.messages.messageClose.tooltip,\n clearPreview);\n }\n msgArea.style.display = "block";\n return createTiddlyElement(msgArea,"div");\n}\n\nfunction displayPreview(text)\n{\n var e = getPreviewDiv();\n if(!e)\n {\n alert(text);\n return;\n }\n e.innerHTML += text;\n}\n\nfunction clearPreview()\n{\n var msgArea = document.getElementById("previewArea");\n if(msgArea)\n {\n removeChildren(msgArea);\n msgArea.style.display = "none";\n }\n return false;\n}\n\n/*** HELPERS ***/\n\n\nconfig.commands.peekcrypt = {\n text: 'preview',\n hideReadOnly: false,\n tooltip: 'Preview this tiddler decrypted',\n handler: function(event,src,title) {\n var tiddler = store.getTiddler(title);\n var payload = new EncryptionPayload();\n if (!payload.load(tiddler.text)) {\n clearMessage();\n displayMessage("Tiddler does not appears to be encrypted");\n return false;\n }\n // Decrypt just the body for preview\n var doWork = function(params) {\n var body = encryptionPlugin.engine[payload.zone].decrypt(payload.text);\n clearPreview();\n displayMessage(body);\n };\n\n // Ensure we have an engine\n if (encryptionPlugin.engine[payload.zone] == undefined)\n encryptionPlugin.getPwAndContinue(payload.zone, doWork);\n else doWork();\n return false;\n }\n};\n\nconfig.commands.autocrypt = {\n text: 'autocrypt',\n hideReadOnly: false,\n tooltip: 'Encrypts or decrypts this tiddler',\n handler: function(event,src,title) {\n var tiddler = store.getTiddler(title);\n\n // See if this is รฆn encrypted payload\n var payload = new EncryptionPayload();\n if (!payload.load(tiddler.text)) { // encrypt\n var doWork = function(params) {\n tiddler.encrypt(params[1],undefined,params[2], params[3]);\n };\n encryptionPlugin.getZonePwAndContinue(doWork);\n } else { // decrypt\n var doWork = function(params) {tiddler.decrypt(undefined, payload);};\n if (encryptionPlugin.engine[payload.zone] == undefined)\n encryptionPlugin.getPwAndContinue(payload.zone, doWork);\n else\n tiddler.decrypt(undefined, payload);\n }\n return false;\n }\n};\n\n\n/** Core extensions **/\n// Allow overriding of globals with encrypt*\nTiddler.prototype.encrypt = function(zone, tag, overrideTitles, overrideTags) {\n var isOpen = false;\n var title = this.title;\n var encryptTitles = config.options.chkEncryptTitles;\n var encryptTags = config.options.chkEncryptTags;\n if (overrideTitles) encryptTitles = overrideTitles;\n if (overrideTags) encryptTags = overrideTags;\n\n // Initialize the PRNG for Rijndael\n // Hopefully we have good entropy already\n if (!Rijndael.prng)\n Rijndael.prng = new AESprng(keyFromEntropy());\n\n // Start the payload with the zone\n var payload = new EncryptionPayload();\n payload.zone = zone;\n\n // Encrypt title\n var etitle = title;\n if (encryptTitles) {\n etitle = config.messages.encryptionEncryptedTitle.format(\n [encryptionPlugin.uniqueTitle()]);\n payload.title = encryptionPlugin.engine[zone].encrypt(title);\n\n // Look for the old title and replace it with the encrypted one.\n var references = store.getReferringTiddlers(title);\n for(var r = 0; r < references.length; ++r) {\n if (tag == undefined || !references[r].isTagged(tag)) {\n var titleRE = new RegExp(title,"g");\n references[r].text = references[r].text.replace(titleRE, etitle);\n references[r].changed();\n }\n }\n story.refreshAllTiddlers();\n }\n\n payload.text = encryptionPlugin.engine[zone].encrypt(this.text)\n this.title = etitle;\n\n if (encryptTags) {\n var new_tags = new Array();\n if (tag) new_tags.pushUnique(tag); \n for (var t = 0; t < this.tags.length; ++t) {\n payload.tags.push(encryptionPlugin.engine[zone].encrypt(this.tags[t]));\n }\n this.tags = new_tags;\n } \n this.text = wrapText(60, payload.serialize());\n\n // Add the new tiddler\n store.addTiddler(this);\n // If the tiddler is visible, close and reopen\n var tiddlerElem = document.getElementById(story.idPrefix + title);\n if (tiddlerElem) {\n story.displayTiddler(tiddlerElem,this.title);\n story.closeTiddler(title);\n // Avoid wonky scrolling. TODO: make this better!\n window.scrollTo(0,0);\n }\n // Delete it by its old name\n if (etitle != title) store.deleteTiddler(title);\n\n return false;\n}\n\nTiddler.prototype.decrypt = function(tag, optional_payload_object) {\n // TODO(redpig): add title and reference encryption\n var title = this.title;\n var payload = null;\n // Assume an already prepped object if supplied\n if (optional_payload_object != undefined) {\n payload = optional_payload_object;\n } else {\n payload = new EncryptionPayload();\n\n if (payload.load(this.text) == false) {\n displayMessage("Unable to decrypt tiddler: " + this.title);\n return false;\n }\n }\n\n // Decrypt title\n var ptitle = title;\n if (payload.title != '') { // if it was encrypted, decrypt it\n ptitle = encryptionPlugin.engine[payload.zone].decrypt(payload.title);\n this.title = ptitle;\n\n // Look for the old title and replace it with the encrypted one.\n var references = store.getReferringTiddlers(title);\n for(var r = 0; r < references.length; ++r) {\n // TODO: add zones based on zone:tag\n if (tag == undefined || !references[r].isTagged(tag)) {\n var titleRE = new RegExp(title,"g");;\n references[r].text = references[r].text.replace(titleRE, ptitle);\n references[r].changed();\n }\n }\n }\n\n plainText = encryptionPlugin.engine[payload.zone].decrypt(payload.text);\n this.text = plainText;\n\n if (payload.tags.length > 0) { // if there are encrypted tags, decrypt!\n for (var t = 3; t < payload.tags; ++t) {\n if (payload.tags[t] != '')\n this.tags.pushUnique(\n encryptionPlugin.engine[payload.zone].decrypt(payload.tags[t]));\n }\n }\n\n // Add the new tiddler\n store.addTiddler(this);\n // If the tiddler is visible, close and reopen\n var tiddlerElem = document.getElementById(story.idPrefix + title);\n if (tiddlerElem) {\n story.displayTiddler(tiddlerElem,this.title);\n story.closeTiddler(title);\n // Avoid wonky scrolling. TODO: make this better!\n window.scrollTo(0,0);\n }\n // Delete it by its old name\n if (ptitle != title) store.deleteTiddler(title);\n return false;\n}\n\n\n//}}}\n
Hello World!
Hello again!
This is an example of references. It isn't perfect, but hey.\n\n[[Example 1]]\n[[Example 2]]\n[[Example 1]]\n\nThis is not a link to Example 1, but it will still get replaced. How annoying.
/%\n|Name|JavascriptShell|\n|Source|http://www.TiddlyTools.com/#JavascriptShell|\n|Version|0.0.0|\n|Author|Eric Shulman - ELS Design Studios|\n|License|http://www.TiddlyTools.com/#LegalStatements <<br>>and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|~CoreVersion|2.1|\n|Type|script|\n|Requires||\n|Overrides||\n|Description||\n%//%\n\nDerived from http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.4\n\n%/<html><div class="shell"><div id="output"></div><input type=text\n onkeydown="jsshell.inputKeydown(event)"\n id="input" class="input" wrap="off" autocomplete="off"\n title="TAB=auto-complete property names, Ctrl+Up/Down=history"\n style="width:100%;height:1.2em;margin-top:.2em;border:1px solid;color:#000;background:#fff;"><span style="float:right">height: <input type="text" name="height" value="20em" size="2" style="width:3em;padding:0;margin:0;" onchange="document.getElementById('output').style.height=this.value"> <input type="button" onclick="jsshell.go('clear()')"value="clear"></span><!--\n --><div>enter a javascript expression or shell function:\n ans, load(URL), scope(obj), <!--\n --><a accesskey="M" href="javascript:jsshell.go('scope(Math); mathHelp();');">Math</a>, <!--\n --><a accesskey="P" href="javascript:jsshell.go('props(ans)')">props(obj)</a>, <!--\n --><a accesskey="B" href="javascript:jsshell.go('blink(ans)')">blink(node)</a>, <!--\n --><a href="javascript:jsshell.go('wikify(ans)')">wikify(text)</a>, <!--\n --><a href="javascript:jsshell.go('print(ans)')">print(text)</a></div></div></html><script>\n\nvar shellstyles="";\nshellstyles+=".shell #output { height:20em;width:100%;white-space:normal;overflow:auto; }";\nshellstyles+=".shell #output { border:1px solid #999;background:#000 !important; }";\nshellstyles+=".shell #output .input { color:#fff !important; }"; // white\nshellstyles+=".shell #output .error { color:#f00 !important; }"; // red\nshellstyles+=".shell #output .normalOutput { color:#0c0 !important; }"; // green\nshellstyles+=".shell #output .propList { color:#0c0 !important; }"; // green\nshellstyles+=".shell #output .print { color:#ccc !important; }"; // gray\nshellstyles+=".shell #output .tabcomplete { color:#ff0 !important; }"; // yellow\nshellstyles+=".shell #output .message { color:#0ff !important; }"; // cyan\nsetStylesheet(shellstyles,"JavascriptShellStyles");\n\nwindow.jsshell = {}; // Put our functions in the global namespace.\n\nwindow.jsshell.refocus = function()\n{\n jsshell._in.blur(); // Needed for Mozilla to scroll correctly.\n jsshell._in.focus();\n}\n\nwindow.jsshell.initTarget = function()\n{\n window.print = jsshell.shellCommands.print;\n}\n\n// Unless the user is selected something, refocus the textbox.\n// (requested by caillon, brendan, asa)\nwindow.jsshell.keepFocusInTextbox = function(e) \n{\n var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard\n \n while (!g.tagName)\n g = g.parentNode;\n var t = g.tagName.toUpperCase();\n if (t=="A" || t=="INPUT")\n return;\n \n if (window.getSelection) {\n // Mozilla\n if (String(window.getSelection()))\n return;\n }\n else if (document.getSelection) {\n // Opera? Netscape 4?\n if (document.getSelection())\n return;\n }\n else {\n // IE\n if ( document.selection.createRange().text )\n return;\n }\n \n jsshell.refocus();\n}\n\n//function inputKeydown(e) {\nwindow.jsshell.inputKeydown = function(e) {\n // Use onkeydown because IE doesn't support onkeypress for arrow keys\n\n //alert(e.keyCode + " ^ " + e.keycode);\n\n if (e.shiftKey && e.keyCode == 13) { // shift-enter\n // don't do anything; allow the shift-enter to insert a line break as normal\n } else if (e.keyCode == 13) { // enter\n // execute the input on enter\n try { jsshell.go(); } catch(er) { alert(er); };\n setTimeout(function() { jsshell._in.value = ""; }, 0); // can't preventDefault on input, so clear it later\n } else if (e.keyCode == 38) { // up\n // go up in history if at top or ctrl-up\n if (e.ctrlKey || jsshell.caretInFirstLine(jsshell._in))\n jsshell.hist(true);\n } else if (e.keyCode == 40) { // down\n // go down in history if at end or ctrl-down\n if (e.ctrlKey || jsshell.caretInLastLine(jsshell._in))\n jsshell.hist(false);\n } else if (e.keyCode == 9) { // tab\n jsshell.tabcomplete();\n setTimeout(function() { jsshell.refocus(); }, 0); // refocus because tab was hit\n } else { }\n\n setTimeout(jsshell.recalculateInputHeight, 0);\n \n //return true;\n};\n\nwindow.jsshell.caretInFirstLine = function(textbox)\n{\n // IE doesn't support selectionStart/selectionEnd\n if (textbox.selectionStart == undefined)\n return true;\n\n var firstLineBreak = textbox.value.indexOf("\sn");\n\n return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak));\n}\n\nwindow.jsshell.caretInLastLine = function(textbox)\n{\n // IE doesn't support selectionStart/selectionEnd\n if (textbox.selectionEnd == undefined)\n return true;\n\n var lastLineBreak = textbox.value.lastIndexOf("\sn");\n \n return (textbox.selectionEnd > lastLineBreak);\n}\n\nwindow.jsshell.recalculateInputHeight = function()\n{\n var rows = jsshell._in.value.split(/\sn/).length\n + 1 // prevent scrollbar flickering in Mozilla\n + (window.opera ? 1 : 0); // leave room for scrollbar in Opera\n\n if (jsshell._in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0.\n jsshell._in.rows = rows;\n}\n\nwindow.jsshell.println = function(s, type)\n{\n if((s=String(s)))\n {\n var newdiv = document.createElement("div");\n newdiv.appendChild(document.createTextNode(s));\n newdiv.className = type;\n jsshell._out.appendChild(newdiv);\n jsshell._out.scrollTop=jsshell._out.scrollHeight-jsshell._out.clientHeight; // ELS: scroll output into view\n return newdiv;\n }\n}\n\nwindow.jsshell.printWithRunin = function(h, s, type)\n{\n var div = jsshell.println(s, type);\n var head = document.createElement("strong");\n head.appendChild(document.createTextNode(h + ": "));\n div.insertBefore(head, div.firstChild);\n}\n\nwindow.jsshell.shellCommands = \n{\nload : function load(url)\n{\n var s = document.createElement("script");\n s.type = "text/javascript";\n s.src = url;\n document.getElementsByTagName("head")[0].appendChild(s);\n jsshell.println("Loading " + url + "...", "message");\n},\n\nclear : function clear()\n{\n jsshell._out.innerHTML = "";\n},\n\nwikify : function wikify(text)\n{\n window.wikify(text, jsshell._out);\n},\n\nprint : function print(s) { jsshell.println(s, "print"); },\n\n// the normal function, "print", shouldn't return a value\n// (suggested by brendan; later noticed it was a problem when showing others)\npr : function pr(s) \n{ \n jsshell.shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()!\n return s;\n},\n\nprops : function props(e, onePerLine)\n{\n if (e === null) {\n jsshell.println("props called with null argument", "error");\n return;\n }\n\n if (e === undefined) {\n jsshell.println("props called with undefined argument", "error");\n return;\n }\n\n var ns = ["Methods", "Fields", "Unreachables"];\n var as = [[], [], []]; // array of (empty) arrays of arrays!\n var p, j, i; // loop variables, several used multiple times\n\n var protoLevels = 0;\n\n for (p = e; p; p = p.__proto__)\n {\n for (i=0; i<ns.length; ++i)\n as[i][protoLevels] = [];\n ++protoLevels;\n }\n\n for(var a in e)\n {\n // Shortcoming: doesn't check that VALUES are the same in object and prototype.\n\n var protoLevel = -1;\n try\n {\n for (p = e; p && (a in p); p = p.__proto__)\n ++protoLevel;\n }\n catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string\n\n var type = 1;\n try\n {\n if ((typeof e[a]) == "function")\n type = 0;\n }\n catch (er) { type = 2; }\n\n as[type][protoLevel].push(a);\n }\n\n function times(s, n) { return n ? s + times(s, n-1) : ""; }\n\n for (j=0; j<protoLevels; ++j)\n for (i=0;i<ns.length;++i)\n if (as[i][j].length) \n jsshell.printWithRunin(\n ns[i] + times(" of prototype", j), \n (onePerLine ? "\sn\sn" : "") + as[i][j].sort().join(onePerLine ? "\sn" : ", ") + (onePerLine ? "\sn\sn" : ""), \n "propList"\n );\n},\n\nblink : function blink(node)\n{\n if (!node) throw("blink: argument is null or undefined.");\n if (node.nodeType == null) throw("blink: argument must be a node.");\n if (node.nodeType == 3) throw("blink: argument must not be a text node");\n if (node.documentElement) throw("blink: argument must not be the document object");\n\n function setOutline(o) { \n return function() {\n if (node.style.outline != node.style.bogusProperty) {\n // browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8).\n node.style.outline = o;\n }\n else if (node.style.MozOutline != node.style.bogusProperty) {\n // browser supports MozOutline (Firefox 1.0.x and older)\n node.style.MozOutline = o;\n }\n else {\n // browser only supports border (IE). border is a fallback because it moves things around.\n node.style.border = o;\n }\n }\n } \n \n function focusIt(a) {\n return function() {\n a.focus(); \n }\n }\n\n if (node.ownerDocument) {\n var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE\n if (windowToFocusNow)\n setTimeout(focusIt(windowToFocusNow.top), 0);\n }\n\n for(var i=1;i<7;++i)\n setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100);\n\n setTimeout(focusIt(window), 800);\n setTimeout(focusIt(jsshell._in), 810);\n},\n\nscope : function scope(sc)\n{\n if (!sc) sc = {};\n jsshell._scope = sc;\n jsshell.println("Scope is now " + sc + ". If a variable is not found in this scope, window will also be searched. New variables will still go on window.", "message");\n},\n\nmathHelp : function mathHelp()\n{\n jsshell.printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList");\n jsshell.printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList");\n},\n\nans : undefined\n};\n\n\nwindow.jsshell.hist = function(up)\n{\n // histList[0] = first command entered, [1] = second, etc.\n // type something, press up --> thing typed is now in "limbo"\n // (last item in histList) and should be reachable by pressing \n // down again.\n\n var L = jsshell.histList.length;\n\n if (L == 1)\n return;\n\n if (up)\n {\n if (jsshell.histPos == L-1)\n {\n // Save this entry in case the user hits the down key.\n jsshell.histList[jsshell.histPos] = jsshell._in.value;\n }\n\n if (jsshell.histPos > 0)\n {\n jsshell.histPos--;\n // Use a timeout to prevent up from moving cursor within new text\n // Set to nothing first for the same reason\n setTimeout(\n function() {\n jsshell._in.value = ''; \n jsshell._in.value = jsshell.histList[jsshell.histPos];\n var caretPos = jsshell._in.value.length;\n if (jsshell._in.setSelectionRange) \n jsshell._in.setSelectionRange(caretPos, caretPos);\n },\n 0\n );\n }\n } \n else // down\n {\n if (jsshell.histPos < L-1)\n {\n jsshell.histPos++;\n jsshell._in.value = jsshell.histList[jsshell.histPos];\n }\n else if (jsshell.histPos == L-1)\n {\n // Already on the current entry: clear but save\n if (jsshell._in.value)\n {\n jsshell.histList[jsshell.histPos] = jsshell._in.value;\n ++jsshell.histPos;\n jsshell._in.value = "";\n }\n }\n }\n}\n\nwindow.jsshell.tabcomplete = function()\n{\n /*\n * Working backwards from s[from], find the spot\n * where this expression starts. It will scan\n * until it hits a mismatched ( or a space,\n * but it skips over quoted strings.\n * If stopAtDot is true, stop at a '.'\n */\n function findbeginning(s, from, stopAtDot)\n {\n /*\n * Complicated function.\n *\n * Return true if s[i] == q BUT ONLY IF\n * s[i-1] is not a backslash.\n */\n function equalButNotEscaped(s,i,q)\n {\n if(s.charAt(i) != q) // not equal go no further\n return false;\n\n if(i==0) // beginning of string\n return true;\n\n if(s.charAt(i-1) == '\s\s') // escaped?\n return false;\n\n return true;\n }\n\n var nparens = 0;\n var i;\n for(i=from; i>=0; i--)\n {\n if(s.charAt(i) == ' ')\n break;\n\n if(stopAtDot && s.charAt(i) == '.')\n break;\n \n if(s.charAt(i) == ')')\n nparens++;\n else if(s.charAt(i) == '(')\n nparens--;\n\n if(nparens < 0)\n break;\n\n // skip quoted strings\n if(s.charAt(i) == '\s'' || s.charAt(i) == '\s"')\n {\n //dump("skipping quoted chars: ");\n var quot = s.charAt(i);\n i--;\n while(i >= 0 && !equalButNotEscaped(s,i,quot)) {\n //dump(s.charAt(i));\n i--;\n }\n //dump("\sn");\n }\n }\n return i;\n }\n\n // XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code)\n // XXX doesn't work in IE, even though it contains IE-specific code\n function getcaretpos(inp)\n {\n if(inp.selectionEnd != null)\n return inp.selectionEnd;\n \n if(inp.createTextRange)\n {\n var docrange = document.selection.createRange();\n var inprange = inp.createTextRange();\n if (inprange.setEndPoint)\n {\n inprange.setEndPoint('EndToStart', docrange);\n return inprange.text.length;\n }\n }\n\n return inp.value.length; // sucks, punt\n }\n\n function setselectionto(inp,pos)\n {\n if(inp.selectionStart) {\n inp.selectionStart = inp.selectionEnd = pos;\n }\n else if(inp.createTextRange) {\n var docrange = document.selection.createRange();\n var inprange = inp.createTextRange();\n inprange.move('character',pos);\n inprange.select();\n }\n else { // err...\n /*\n inp.select();\n if(document.getSelection())\n document.getSelection() = "";\n */\n }\n }\n // get position of cursor within the input box\n var caret = getcaretpos(jsshell._in);\n\n if(caret) {\n //dump("----\sn");\n var dotpos, spacepos, complete, obj;\n //dump("caret pos: " + caret + "\sn");\n // see if there's a dot before here\n dotpos = findbeginning(jsshell._in.value, caret-1, true);\n //dump("dot pos: " + dotpos + "\sn");\n if(dotpos == -1 || jsshell._in.value.charAt(dotpos) != '.') {\n dotpos = caret;\n//dump("changed dot pos: " + dotpos + "\sn");\n }\n\n // look backwards for a non-variable-name character\n spacepos = findbeginning(jsshell._in.value, dotpos-1, false);\n //dump("space pos: " + spacepos + "\sn");\n // get the object we're trying to complete on\n if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret)\n {\n // try completing function args\n if(jsshell._in.value.charAt(dotpos) == '(' ||\n (jsshell._in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos))\n {\n var fn,fname;\n var from = (jsshell._in.value.charAt(dotpos) == '(') ? dotpos : spacepos;\n spacepos = findbeginning(jsshell._in.value, from-1, false);\n\n fname = jsshell._in.value.substr(spacepos+1,from-(spacepos+1));\n //dump("fname: " + fname + "\sn");\n try {\n with(window)\n with(jsshell._scope)\n with(jsshell.shellCommands)\n fn = eval(fname);\n }\n catch(er) {\n //dump('fn is not a valid object\sn');\n return;\n }\n if(fn == undefined) {\n //dump('fn is undefined');\n return;\n }\n if(fn instanceof Function)\n {\n // Print function definition, including argument names, but not function body\n if(!fn.toString().match(/function .+?\s(\s) +\s{\sn +\s[native code\s]\sn\s}/))\n jsshell.println(fn.toString().match(/function .+?\s(.*?\s)/), "tabcomplete");\n }\n\n return;\n }\n else\n obj = window;\n }\n else\n {\n var objname = jsshell._in.value.substr(spacepos+1,dotpos-(spacepos+1));\n //dump("objname: |" + objname + "|\sn");\n try {\n with(jsshell._scope)\n with(window)\n obj = eval(objname);\n }\n catch(er) {\n jsshell.printError(er); \n return;\n }\n if(obj == undefined) {\n // sometimes this is tabcomplete's fault, so don't print it :(\n // e.g. completing from "print(document.getElements"\n // jsshell.println("Can't complete from null or undefined expression " + objname, "error");\n return;\n }\n }\n //dump("obj: " + obj + "\sn");\n // get the thing we're trying to complete\n if(dotpos == caret)\n {\n if(spacepos+1 == dotpos || spacepos == dotpos)\n {\n // nothing to complete\n //dump("nothing to complete\sn");\n return;\n }\n\n complete = jsshell._in.value.substr(spacepos+1,dotpos-(spacepos+1));\n }\n else {\n complete = jsshell._in.value.substr(dotpos+1,caret-(dotpos+1));\n }\n //dump("complete: " + complete + "\sn");\n // ok, now look at all the props/methods of this obj\n // and find ones starting with 'complete'\n var matches = [];\n var bestmatch = null;\n for(var a in obj)\n {\n //a = a.toString();\n //XXX: making it lowercase could help some cases,\n // but screws up my general logic.\n if(a.substr(0,complete.length) == complete) {\n matches.push(a);\n ////dump("match: " + a + "\sn");\n // if no best match, this is the best match\n if(bestmatch == null)\n {\n bestmatch = a;\n }\n else {\n // the best match is the longest common string\n function min(a,b){ return ((a<b)?a:b); }\n var i;\n for(i=0; i< min(bestmatch.length, a.length); i++)\n {\n if(bestmatch.charAt(i) != a.charAt(i))\n break;\n }\n bestmatch = bestmatch.substr(0,i);\n ////dump("bestmatch len: " + i + "\sn");\n }\n ////dump("bestmatch: " + bestmatch + "\sn");\n }\n }\n bestmatch = (bestmatch || "");\n ////dump("matches: " + matches + "\sn");\n var objAndComplete = (objname || obj) + "." + bestmatch;\n //dump("matches.length: " + matches.length + ", jsshell.tooManyMatches: " + jsshell.tooManyMatches + ", objAndComplete: " + objAndComplete + "\sn");\n if(matches.length > 1 && (jsshell.tooManyMatches == objAndComplete || matches.length <= 10)) {\n\n jsshell.printWithRunin("Matches: ", matches.join(', '), "tabcomplete");\n jsshell.tooManyMatches = null;\n }\n else if(matches.length > 10)\n {\n jsshell.println(matches.length + " matches. Press tab again to see them all", "tabcomplete");\n jsshell.tooManyMatches = objAndComplete;\n }\n else {\n jsshell.tooManyMatches = null;\n }\n if(bestmatch != "")\n {\n var sstart;\n if(dotpos == caret) {\n sstart = spacepos+1;\n }\n else {\n sstart = dotpos+1;\n }\n jsshell._in.value = jsshell._in.value.substr(0, sstart)\n + bestmatch\n + jsshell._in.value.substr(caret);\n setselectionto(jsshell._in,caret + (bestmatch.length - complete.length));\n }\n }\n}\n\nwindow.jsshell.printQuestion = function(q)\n{\n jsshell.println(q, "input");\n}\n\nwindow.jsshell.printAnswer = function(a)\n{\n if (a !== undefined) {\n jsshell.println(a, "normalOutput");\n jsshell.shellCommands.ans = a;\n }\n}\n\nwindow.jsshell.printError = function(er)\n{ \n var lineNumberString;\n\n lastError = er; // for debugging the shell\n if (er.name)\n {\n // lineNumberString should not be "", to avoid a very wacky bug in IE 6.\n lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": ";\n jsshell.println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString.\n }\n else\n jsshell.println(er, "error"); // Because security errors in Moz /only/ have toString.\n}\n\nwindow.jsshell.go = function(s)\n{\n jsshell._in.value = jsshell.question = s ? s : jsshell._in.value;\n\n if (jsshell.question == "")\n return;\n\n jsshell.histList[jsshell.histList.length-1] = jsshell.question;\n jsshell.histList[jsshell.histList.length] = "";\n jsshell.histPos = jsshell.histList.length - 1;\n \n // Unfortunately, this has to happen *before* the JavaScript is run, so that \n // print() output will go in the right place.\n jsshell._in.value='';\n jsshell.recalculateInputHeight();\n jsshell.printQuestion(jsshell.question);\n\n if (window.closed) {\n jsshell.printError("Target window has been closed.");\n return;\n }\n \n try { ("jsshell" in window) }\n catch(er) {\n jsshell.printError("The JavaScript Shell cannot access variables in the target window. The most likely reason is that the target window now has a different page loaded and that page has a different hostname than the original page.");\n return;\n }\n\n if (!("jsshell" in window))\n initTarget(); // silent\n\n // Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC).\n// window.location.href = "javascript:try{ jsshell.printAnswer(eval('with(jsshell._scope) with(jsshell.shellCommands) {' + jsshell.question + String.fromCharCode(10) + '}')); } catch(er) { jsshell.printError(er); }; setTimeout(jsshell.refocus, 0); void 0";\n try { \n jsshell.printAnswer(eval(\n 'with(jsshell._scope) with(jsshell.shellCommands) {' \n + jsshell.question + String.fromCharCode(10) + \n '}')); \n } catch(er) { \n jsshell.printError(er); \n }; \n setTimeout(jsshell.refocus, 0);\n}\n\nwindow.jsshell.histList = [""]; \nwindow.jsshell.histPos = 0; \nwindow.jsshell._scope = {}; \nwindow.jsshell.question;\nwindow.jsshell._in;\nwindow.jsshell._out;\nwindow.jsshell.tooManyMatches = null;\nwindow.jsshell.lastError = null;\n\njsshell._in = document.getElementById("input");\njsshell._out = document.getElementById("output");\n\njsshell.initTarget();\n\njsshell.recalculateInputHeight();\njsshell.refocus();\n\n</script>
<div id='header' class='header' macro='gradient vert #555555 #3b3b3b '>\n <div class='siteTitle' refresh='content' tiddler='SiteTitle'></div>\n <span id='topMenu' refresh='content' tiddler='MainMenu'></span>\n</div>\n\n<div id='sidebar'>\n<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>\n<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>\n</div>\n<div id='displayArea'>\n<div id='messageArea'></div>\n<div id='tiddlerDisplay'></div>\n</div>\n<!--}}}-->
<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal 'DD MMM YYYY'>><<encrypt_tag "sekrt" "home-notes" "encrypt">><<encrypt_clear "home-notes" "clear pw">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel 'options ยป' 'Change TiddlyWiki advanced options'>>
EncryptionPlugin
http://www.dataspill.org/EncryptionPlugin.html
/*{{{*/\n/*Monochrome Theme for TiddlyWiki*/\n/*Design and CSS by Saq Imtiaz*/\n/*Version 1.0*/\n/*}}}*/\n/*{{{*/\n\nbody {background:#3B3B3B; color:#C3C3C3; font:12px Verdana, Helvetica, sans-serif;\n }\n\n#header {padding: 0em 0em 0em 0em; background:transparent; font-family: arial,helvetica; font-size:12px;\n }\n\n.siteTitle {\npadding-top:5px;\nfloat:left;\nfont-family: 'Trebuchet MS' sans-serif;\nfont-weight: bold;\nfont-size: 32px;\ncolor: #ccc; margin-right:2em;margin-left:0.5em;\n}\n\n#topMenu br {display:none;}\n#topMenu a, #topMenu .tiddlyLink, #topMenu .button {margin:0em; color:#666; padding:15px 15px 10px 15px;padding-top:1.6em;border:none; border-right: 1px solid #666;float:left;}\n#topMenu {border-left: 1px solid #666; float:left;margin:0;}\n#topMenu a:hover {color:#ccc; background:#3b3b3b;}\n\n#displayArea {margin-left:1.35em; margin-right:17.65em; margin-top:0.5em; padding-top:1em; padding-bottom:10px;}\n\n.tiddler {background:#454545; margin-bottom:20px; padding:1em 2em 1em 2em;}\n\na, a:hover{\ncolor:#fff;\ntext-decoration: none; background:transparent;\n}\n\n.viewer a, .viewer a:hover{border-bottom:1px dotted #fff; font-weight:normal;}\n\n.viewer .button, .editorFooter .button{\ncolor: #fff;\nborder: 1px solid #fff;\n}\n\n.viewer .button:hover,\n.editorFooter .button:hover, .viewer .button:active, .viewer .highlight,.editorFooter .button:active, .editorFooter .highlight{\ncolor: #fff;\nbackground: #3B3B3B;\nborder-color: #3B3B3B;\n}\n\n.title {color:#ccc; font-family:'Lucida Grande', Verdana, Sans-Serif; font-size:1.5em;\n}\n\n.subtitle, .subtitle a { color: #777; font-size: 0.95em;margin:0.2em;}\n.shadow .title{color:#777;}\n\n.toolbar {font-size:90%;}\n.selected .toolbar a {color:#666;border:0;}\n.selected .toolbar a:hover {color:#999; background:transparent;border:0;}\n\n.toolbar .button:hover, .toolbar .highlight, .toolbar .marked, .toolbar a.button:active{color:#666;border:0; background:transparent;border:0;}\n\n.tagging, .tagged {\nborder: 1px solid #555;\nbackground-color: #444;\n}\n\n.selected .tagging, .selected .tagged {\nbackground-color: #3B3B3B;\nborder: 1px solid #666;\n}\n\n.tagging .listTitle, .tagged .listTitle {\ncolor: #666;\n}\n\n.selected .tagging .listTitle, .selected .tagged .listTitle {\ncolor: #aaa;\n}\n\n.tagging .button, .tagged .button {\ncolor: #838383;\n}\n.selected .tagging .button, .selected .tagged .button {\ncolor:#c3c3c3;\n}\n\n.highlight, .marked {background:transparent; color:#111; border:none; text-decoration:underline;}\n\n.tagging .button:hover, .tagged .button:hover, .tagging .button:active, .tagged .button:active {\nborder: none; background:transparent; text-decoration:underline; color:#333;\n}\n\n#sidebarOptions {margin-top:1em;}\n#sidebar {margin-right:1.35em;}\n\n#sidebarTabs .tabContents { \n font-family: arial,helvetica;}\n\n#sidebarOptions a, #sidebarOptions a:hover{border:none;color:#666;}\n#sidebarOptions a:hover, #sidebarOptions a:active {background:#454545; color:#ccc;}\n#sidebarTabs .tabContents {background:#454545;border:0px solid #666; border-right:1px solid #454545;}\n#sidebarOptions input {background:#ccc; border:1px solid #666;}\n\n#sidebarTabs .tabContents .tiddlyLink, #sidebarTabs .tabContents .button{color:#666;font-weight:normal;}\n#sidebarTabs .tabContents .tiddlyLink:hover, #sidebarTabs .tabContents .button:hover {color:#ccc; background:transparent;}\n.listTitle {color:#777;}\n\n#sidebarTabs .tabSelected,#sidebarTabs .tabSelected:hover{background:#454545;border:none;color:#ccc; border:1px solid #454545;}\n#sidebarTabs .tabUnselected{background:#3B3B3B; border:1px solid #454545; color:#666;}\n\n #sidebarTabs .txtMoreTab .tabSelected,\n #sidebarTabs .txtMoreTab .tab:hover,\n #sidebarTabs .txtMoreTab .tabContents{\ncolor: #ccc;\nbackground: #3B3B3B; border:1px solid #3B3B3B;\n}\n\n #sidebarTabs .txtMoreTab .tabUnselected {\n\ncolor: #777; border:1px solid #3B3B3B;\nbackground: #454545;\n}\n\n\n#sidebarTabs .tabContents .button:hover, #sidebarTabs .tabContents .highlight, #sidebarTabs .tabContents .marked, #sidebarTabs .tabContents a.button:active{color:#ccc; background:transparent;}\n\n #sidebarOptions .sliderPanel {\nbackground: #454545; font-size: .9em;\n}\n\n#sidebarOptions .sliderPanel input {border:1px solid #666; background:#ccc;}\n#sidebarOptions .sliderPanel .txtOptionInput {border:1px solid #666;width:9em;}\n\n#sidebarOptions .sliderPanel a {font-weight:normal; color:#666;background-color: #454545; border-bottom:1px dotted #333;}\n\n#sidebarOptions .sliderPanel a:hover {\ncolor:#ccc;\nbackground-color: #454545;\nborder:none;\nborder-bottom:1px dotted #111;\n}\n\n.popup {\nbackground: #3B3B3B;\nborder: 1px solid #454545;\n}\n\n.popup li.disabled {\ncolor: #000;\n}\n\n.popup li a, .popup li a:visited {\ncolor: #777;\nborder: none;\n}\n\n.popup li a:hover {\nbackground: #3b3b3b;\ncolor: #c3c3c3;\nborder: none;\n}\n.popup hr {\n color: #777;\n background: #777;\n border-bottom: 1px;\n}\n\n.listBreak div{\n border-bottom: 1px solid #777;\n}\n\n#messageArea {\nborder: 4px dotted #ccc;\nbackground: #454545;\ncolor: #777;\nfont-size:90%;\n}\n\n#messageArea .button{\n\ncolor: #3B3B3B;\nbackground:#ccc;\nborder: 1px solid #ccc;\n}\n\n#messageArea .button:hover {\n\ncolor: #ccc;\nbackground: #3B3B3B;\nborder-color: #3B3B3B;\n}\n\n.viewer blockquote {\nborder-left: 5px solid #3B3B3B; background:#3B3B3B\n}\n\n.viewer table, .viewer td {\nborder: 1px solid #2E2E2E;\n}\n\n.viewer th, thead td {\nbackground: #3B3B3B;\nborder: 1px solid #3B3B3B;\ncolor: #ccc;\n}\n.viewer pre {\nborder: 1px solid #3b3b3b;\nbackground: #5F5F5F;\n}\n\n.viewer code {\ncolor: #c3c3c3; background:#5f5f5f;\n}\n\n.viewer hr {\nborder-top: dashed 1px #222; margin:0 1em;\n}\n\n.editor input {\nborder: 1px solid #ccc; margin-top:5px;\n}\n\n.editor textarea {\nborder: 1px solid #ccc;\n}\n\nh1,h2,h3,h4,h5 { color: #9c9c9c; background: transparent; padding-bottom:2px; font-family: Arial, Helvetica, sans-serif; }\nh1 {font-size:18px;}\nh2 {font-size:16px;}\nh3 {font-size: 14px;}
/***\n|Name|TidIDEPlugin|\n|Source|http://www.TiddlyTools.com/#TidIDEPlugin|\n|Version|1.6.1|\n|Author|Eric Shulman - ELS Design Studios|\n|License|http://www.TiddlyTools.com/#LegalStatements <<br>>and [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|~CoreVersion|2.1|\n|Type|plugin|\n|Requires||\n|Overrides||\n|Description||\n\n~TidIDE (//prounounced "Tie Dyed"//) - ''Tid''dlyWiki ''I''ntegrated ''D''evelopment ''E''nvironment - tools for ~TiddlyWiki authors and editors. \n\nProvides a full-featured tiddler editor with key-by-key ''LIVE PREVIEW'' of //formatted// tiddler content!! Also includes diagnostic tools to help you debug your TiddlyWiki problems by letting you view current TiddlyWiki internal option values, messages, shadows, stylesheets, notify and macro functions or display the internal DOM (Document Object Model) tree structure for any specific part of the TiddlyWiki document.\n!!!!!Configuration\n<<<\nAutomatically freeze preview updates when a tiddler takes more than <<option txtTidIDEAutoFreeze>> milliseconds to render.\n<<<\n!!!!!Usage/Example\n<<<\n{{{<<tidIDE id:example "font:Courier New" size:8pt system +edit:GettingStarted>>}}}\n<<tidIDE id:example "font:Courier New" size:8pt system +edit:GettingStarted>>\n!!!!!parameters:\n* ''id'' - assign a unique ID to this instance of TidIDE. (default id=current tiddler title or "" if not in a tiddler)\n* ''font'' - sets the CSS font-family used by textarea controls in editor and system information panels. Note: if the font name includes a space (e.g., Courier New), then you must enclose the entire parameter in double-quotes: {{{"font:Courier New"}}}.\n* ''size'' - sets the CSS font-size used by text input and droplist controls in editor and system information panels.\n* ''system'' includes system information panel.\n* ''edit'' includes tiddler editor/previewer.\n**''edit:here'' automatically sets the editor to show the current tiddler contents (if in a tiddler)\n**''edit:tiddlertitle'' automatically sets the editor to show the specified tiddler contents\n* use ''{{{[[label|tiddlertitle]]}}}'' to include 'custom panels' (and corresponding labelled checkboxes to toggle their display)\n* all parameters are optional. The default panel is "edit:here".\n* panel parameters preceded by a "+" are displayed by default. If only one panel specified in the parameters, it is automatically displayed, even if the "+" is omitted.\n!!!!!using the editor\nThe editor includes a droplist of all tiddlers in the document, sorted alpha-numerically by tiddler title. Shadow tiddlers that have not been customized are added to the end of this list and marked with "(shadow)". Next to the droplist are several buttons:\n* ''view'' opens the tiddler in the normal ~TiddlyWiki display area\n* ''add'' prompts for a new tiddler title and begins a new editing session\n* ''remove'' deletes an existing tiddler (note: shadow tiddlers cannot be removed)\n* ''save'' saves changes to the tiddler currently being edited\n* ''save as'' saves changes using a new tiddler title\nIf a tiddlername was not specified in the macro, select a tiddler from the droplist (or press ''add'') to begin editing. Once a tiddler has been loaded into the editor, you can change it's content, enter or select tags.\n\nNormally, when you save changes to a tiddler, the created/modified dates and tiddler author are automatically updated. However, it is sometimes useful to make small changes to a tiddler without automatically updating the date/author information. Select the ''minor edits'' checkbox to prevent those values from being //automatically// changed. In addition, this enables the date/author edit fields which allows you to //manually// 'back date' a tiddler or change the author to another name. When the tiddler is saved, the date/author values shown in the edit fields will be used.\n!!!!!using the previewer\nThe ''preview'' checkbox adds a display area that shows you what your tiddler changes will look like, //before// committing to those changes.\n\nBy default, this preview display is automatically rendered each time a key is typed into the tiddler content edit field. As soon as changes are entered, they will be instantly visible within the preview display. Unfortunately, the partial tiddler source definitions that occur //during// editing may somtimes cause rendering problems, and some exceptionally complex tiddlers make take an unusually long amount of time to completely render their content. In such cases, key-by-key display updates are undesirable or impractical.\n\nWhen ''preview'' is selected, you can also select ''freeze'' to suspend automatic key-by-key preview display updates. The preview display will not be re-rendered again until you press the ''refresh'' button, or clear the 'freeze' checkbox, or switch to editing a different tiddler. The editor automatically freezes the preview display whenever the //rendering time// exceeds a pre-determined time limit (see configuration section), specified in milliseconds. Note: the ''actual elapsed time'' used to process and render any given tiddler is reported in the browser's status bar area whenever that tiddler is previewed.\n\nThe previewer also can display a ''DOM viewer'' and an ''HTML viewer'' that are also updated with each keystroke. These text-based displays can be helpful while attempting to correct or enhance the formatting of tiddler content, especially when complex combinations of wiki-syntax produce unexpected or undesired results.\n!!!!!system information and TW option settings\nYou can use the ''system information'' panel to view a variety of system internal data and functions, and view/modify ''all'' of ~TiddlyWiki's internal config.option.* settings. NOTE: Non-default config.options are stored in cookies and are retrieved whenever the TW document is loaded into a browser; however, ''core TW functions and custom-defined plugins can explicitly ignore or reset any locally-stored cookie values and use their own, internally-defined values'' instead. As a result, changes to these may be completely ignored, or may only have an effect during the current TW document "session" (i.e., until the TW document is reloaded), even though a persistent cookie value has been saved.\n!!!!! ~DOMViewer macro\nsyntax: {{{<<DOMViewer rows:nn indent:xxxx inline path elementID|tiddlertitle>>}}}\n\nWhenever TiddlyWiki renders a given tiddler, it creates a 'tree' of DOM (Document Object Model) elements that represent the information that is displayed by the browser. You can use the ''DOMViewer'' macro to examine the internal DOM elements that are produced by TiddlyWiki's formatter (the 'wikifier'), or elements directly produced by embedded macros that create custom formatted output. This can be particularly helpful when trying to fine tune the layout and appearance of your tiddler content.\n\nDOMViewer creates a textarea control and reports the DOM tree for the current 'insertion point' where the DOMViewer macro is being placed. ''inline'' flag uses TiddlyWiki rendering instead of textarea control. ''path'' shows the relative location of each child element in the DOM tree, using subscript notation, ''[elementID or tiddlertitle]'' displays DOM elements starting from the node with the specified ID. If that ID is not found in the DOM tree, the macro attempts to open a tiddler with that title and then displays the "tiddler"+title DOM elements that were rendered.\n<<<\n!!!!!Installation\n<<<\nimport (or copy/paste) the following tiddlers into your document:\n''TidIDEPlugin'' (tagged with <<tag systemConfig>>)\n^^documentation and javascript for macro handling^^\n<<<\n!!!!!Revision History\n<<<\n''2006.12.09 [1.6.1]'' in handler(), allow non-existing tiddler title when processing "edit:title" param\nso that new tiddler (or journal) can be created directly from newTiddler, newJournal, or tidIDE macro (without pressing "new" button). Also, set 'edit=text' attribute on text area field so that default content can be initialized from "text:xxx" parameter specified in newTiddler/newJournal macro.\n''2006.11.28 [1.6.0]'' added font and size params to set CSS for form controls in editor and system info panels\n''2006.09.28 [1.5.8]'' use separate form ID and definition for each panel (as well as checkbox 'selector' form), so that forms in custom panels don't conflict with each other.\n''2006.08.27 [1.5.7]'' in handler(), corrected initial display setting for custom 'toolspanel' when '+' prefix has been used for 'defOpen'\n''2006.08.15 [1.5.6]'' in handler(), supress header/selectors if only one panel to display. Also, init system_panel as needed.\n''2006.08.04 [1.5.5]'' in handler(), fix construction of tiddler list to permit use of apostrophes (') in tiddler names.\n''2006.05.22 [1.5.4]'' in setsys(), remove "(cookie)" prefix from selected item text when setting cookie name (was preventing saving of cookie values)\n''2006.05.17 [1.5.3]'' in setsys(), call saveOptionsCookie(). Also, set tiddler editor textarea height (%maxrows%) using config.options.txtMaxEditRows\n''2006.04.30 [1.5.2]'' documentation update\n''2006.04.30 [1.5.1]'' in save(), when performing "save as" behavior, set current tiddler title (f.current) to new title\n''2006.04.24 [1.5.0]'' added macro parameters to dynamically configure and assemble HTML for IDE panels. Supports multiple custom panels loaded from tiddlers and {{{[[label|tiddlername]]}}}\n''2006.04.24 [1.4.6]'' layout adjustments: move system panel above editor panel and move config setting controls to top of system panel\n''2006.04.23 [1.4.5]'' fix HTML so that click on "readonly" checkbox won't change "minor edits" option value.\n''2006.04.23 [1.4.4]'' in render(), strip carriage returns (\sr) that are added by IE's textarea control. Fixes errors in wikify() of 'block-mode' syntax. Also, defer rendering HTML and DOM preview displays until those options are checked and still more code cleanup\n''2006.04.23 [1.4.3]'' init "minor edits" checkbox state from config.options.chkForceMinorEdits value\n''2006.04.23 [1.4.2]'' added "TidIDE v#.#.#: " title in front of subsystem checkboxes.\n''2006.04.23 [1.4.1]'' added 'readonly' checkbox and handling to editor.\n''2006.04.23 [1.4.0]'' implemented 'minor edits' logic, including use of TW AdvancedOptions setting. Replaced separate MDY date input fields with date/time text input fields (using formatted date input).\n''2006.04.22 [1.3.2]'' Layout changes: Added editor/system/tools "subsystem" checkboxes at top of panel. Added automatic read-only notice. Moved tools_panel to bottom. Added 'minor edits' checkbox (handler not yet implemented).\n''2006.04.22 [1.3.1]'' assorted code cleanup and optimizations\n''2006.04.22 [1.3.0]'' added "tools" section via custom-defined TidIDETools tiddler content\n''2006.04.22 [1.2.2]'' corrected 'wrap' and 'white-space' CSS for system viewer textarea control so that IE preserves newlines.\n''2006.04.22 [1.2.1]'' added checkbox indicators in options droplist. Allows easy preview of boolean state value for chk* options.\n''2006.04.22 [1.2.0]'' added options droplist to "system" display and supporting setsys() function to update internal config.options.* values\nlayout adjustments: consolidate some buttons, general tweaks for spacing, sizes, etc.\n''2006.04.21 [1.1.1]'' migrated remaining functionality from ToolkitPlugin (now obsolete).\n''2006.04.21 [1.1.0]'' added "system" display and supporting functions\n''2006.04.21 [1.0.1]'' added formatHTML() for better HTML display in preview\n''2006.04.20 [1.0.0] 4:20:00pm'' official release... renamed from ~TiddlerEditorPlugin to TidIDEPlugin. (pronounced "Tie Dyed"... dude!)\n''2006.04.20 [0.9.9]'' added "run" button to dynamically load systemConfig plugins (with warning/confirmation)\n''2006.04.20 [0.9.8]'' layout adjustments for narrow displays\n''2006.04.20 [0.9.7]'' added HTML viewer to preview display\n''2006.04.20 [0.9.6]'' added DOM viewer to preview display\n''2006.04.19 [0.9.5]'' improved save() handler so saving 'unnamed' edit does fallback to 'save as' prompt for tiddler name\n''2006.04.19 [0.9.4]'' added 'preview status' display field and refresh button. Currently shows preview rendering time and autofreeze notice, if any.\n''2006.04.19 [0.9.3]'' correct IE object error by explicitly using "window." scope when referencing addKeyDownHandlers() function definition\n''2006.04.18 [0.9.2]'' if TextAreaPlugin is installed, call addKeyDownHandlers() for extended ctrl-F/G and TAB keystrokes in textarea\n''2006.04.18 [0.9.1]'' "save as" now presents an "overwriteWarning" message box instead of always rejecting existing tiddler titles\n''2006.04.18 [0.9.0]'' added "save as". Use TW standard text for new tiddler title and default text\n''2006.04.18 [0.8.5]'' added "display:inline" to input elements to prevent unwanted line breaks between controls when macro is used in EditTemplate definitions\n''2006.04.18 [0.8.4]'' added cookie for 'auto-freeze' time limit. Also, added more documentation.\n''2006.04.17 [0.8.3]'' added timing wrapper around preview wikify(). Automatically freeze preview display if tiddler rendering exceeds time limit\n''2006.04.17 [0.8.2]'' more code cleanup for better 'dirty' flag handling\n''2006.04.17 [0.8.1]'' show/hide freeze checkbox when toggling preview display. Also, code cleanup for better 'multiple instance' definition\n''2006.04.17 [0.8.0]'' added "freeze" checkbox to toggle 'live update' of preview display. Also, layout/CSS adjustments for better appearance in IE\n''2006.04.16 [0.7.1]'' correct month number offset (was 0-11 instead of 1-12)\n''2006.04.16 [0.7.0]'' added support for 'dirty' flag, read-only mode and improved alert/confirm/prompt handling\n''2006.04.16 [0.6.0]'' created "add/remove" functions. Added handling to trigger autoSave() if option is set.\n''2006.04.15 [0.5.1]'' move 'save' logic to separate function, and added handling to create a 'real' tiddler when saving a shadow\n''2006.04.15 [0.5.0]'' Initial ALPHA release. Converted from TiddlerTweaker inline script.\n<<<\n!!!!!Credits\n<<<\nThis feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]].\n<<<\n!!!!!Code\n***/\n// // version info\n//{{{\nversion.extensions.tidIDE = {major: 1, minor: 6, revision: 1, date: new Date(2006,12,9)};\n//}}}\n\n// // macro definition\n//{{{\nconfig.macros.tidIDE = {\n versionMsg: "TidIDE v%0.%1.%2: ",\n datetimefmt: "0MM/0DD/YYYY 0hh:0mm",\n titleMsg: "Please enter a new tiddler title",\n isShadowMsg: "'%0' is a shadow tiddler and cannot be removed.",\n renderMsg: "rendering preview...",\n timeoutMsg: " (> %0ms)",\n freezeMsg: " - preview is frozen. Press [refresh] to re-display.",\n evalMsg: "Warning!!\sn\snThis action will process '%0' as a systemConfig (plugin) tiddler, and may produce unexpected results!\sn\snAre you sure you want to proceed?",\n toolsDef: "<html><a href='javascript:config.macros.tidIDE.set(\s"%0\s",\s"%1\s");'>edit %1...</a></html>",\n editorLabel: "TiddlerEditor",\n systemLabel: "SystemInfo"\n};\nconfig.macros.tidIDE.handler= function(place,macroName,params) {\n var here=story.findContainingTiddler(place);\n var selectors="";\n var panels="";\n var showsys=false;\n var title="";\n var id=""; if (here) id=here.id.substr(7);\n var p=params.shift();\n if (!p) p="edit:here"; // default to editor if no params\n var openpanels=[];\n var panelcount=0;\n var fontsize="8pt";\n var fontface="Courier New,fixed";\n while (p) {\n var defOpen=(p.substr(0,1)=="+"); if (defOpen) p=p.substr(1);\n if (p.substr(0,3)=="id:")\n { id=p.substr(3); }\n else if (p.substr(0,5)=="font:")\n { fontface=p.substr(5); }\n else if (p.substr(0,5)=="size:")\n { fontsize=p.substr(5); }\n else if (p.substr(0,4)=="edit") {\n panelcount++;\n defOpen=defOpen || (!params[0] && panelcount==1); // if only one panel to show, default to open\n var toolname=this.editorLabel;\n if (p.indexOf('|')!=-1) toolname=p.substr(0,p.indexOf('|'));\n selectors+=this.html.editorchk.replace(/%toolname%/mg,toolname);\n selectors=selectors.replace(/%showpanel%/mg,defOpen?"CHECKED":"");\n panels+=this.html.editorpanel;\n // editor panel setup...\n panels=panels.replace(/%showpanel%/mg,defOpen?"block":"none");\n panels=panels.replace(/%maxrows%/mg,config.options.txtMaxEditRows);\n panels=panels.replace(/%disabled%/mg,readOnly?"DISABLED":"");\n panels=panels.replace(/%readonlychk%/mg,readOnly?"CHECKED":"");\n panels=panels.replace(/%minoredits%/mg,config.options.chkForceMinorUpdate&&!readOnly?"":"DISABLED");\n panels=panels.replace(/%minorchk%/mg,config.options.chkForceMinorUpdate?"CHECKED":"");\n panels=panels.replace(/%fontsize%/mg,fontsize);\n panels=panels.replace(/%fontface%/mg,fontface);\n var tiddlers=store.getTiddlers("title"); var tiddlerlist=""; \n for (var t=0; t<tiddlers.length; t++)\n tiddlerlist+='<option value="'+tiddlers[t].title+'">'+tiddlers[t].title+'</option>';\n for (var t in config.shadowTiddlers)\n if (!store.tiddlerExists(t)) tiddlerlist+="<option value='"+t+"'>"+t+" (shadow)</option>";\n panels=panels.replace(/%tiddlerlist%/mg,tiddlerlist);\n var tags = store.getTags(); var taglist="";\n for (var t=0; t<tags.length; t++)\n taglist+="<option value='"+tags[t][0]+"'>"+tags[t][0]+"</option>";\n panels=panels.replace(/%taglist%/mg,taglist);\n if (p.substr(0,5)=="edit:") { \n title=p.substr(5); \n if (here && title=="here") title=here.id.substr(7);\n }\n }\n else if (p=="system") {\n panelcount++;\n defOpen=defOpen || (!params[0] && panelcount==1); // if only one panel to show, default to open\n var toolname=this.systemLabel;\n showsys=defOpen;\n if (p.indexOf('|')!=-1) toolname=p.substr(0,p.indexOf('|'));\n selectors+=this.html.systemchk.replace(/%toolname%/mg,toolname);\n selectors=selectors.replace(/%showpanel%/mg,defOpen?"CHECKED":"");\n panels+=this.html.systempanel;\n panels=panels.replace(/%showpanel%/mg,defOpen?"block":"none");\n panels=panels.replace(/%fontsize%/mg,fontsize);\n panels=panels.replace(/%fontface%/mg,fontface);\n }\n else {\n panelcount++;\n defOpen=defOpen || (!params[0] && panelcount==1); // if only one panel to show, default to open\n var toolid=toolname=p;\n if (p.indexOf('|')!=-1)\n { toolname=p.substr(0,p.indexOf('|')); toolid=p.substr(p.indexOf('|')+1); }\n selectors+=this.html.toolschk.replace(/%toolid%/mg,toolid).replace(/%toolname%/mg,toolname);\n selectors=selectors.replace(/%showpanel%/mg,defOpen?"CHECKED":"");\n panels+=this.html.toolspanel.replace(/%toolid%/mg,toolid);\n panels=panels.replace(/%showpanel%/mg,defOpen?"block":"none");\n if (defOpen) openpanels.push(toolid);\n }\n p=params.shift(); // next param\n }\n var html=this.html.framework;\n if (panelcount<2)\n html=html.replace(/%version%/mg,'').replace(/%selector%/mg,''); // omit header/selectors if just one panel to display\n else {\n html=html.replace(/%version%/mg,\n this.versionMsg.format([version.extensions.tidIDE.major,version.extensions.tidIDE.minor,version.extensions.tidIDE.revision]));\n html=html.replace(/%selector%/mg,selectors+"<hr style='margin:0;padding:0'>");\n }\n html=html.replace(/%panels%/mg,panels);\n html=html.replace(/%id%/mg,id);\n var newIDE=createTiddlyElement(place,"span");\n newIDE.innerHTML=html;\n if (title.length) this.set(id,title); // pre-load tiddler editor (if needed)\n if (showsys) config.macros.tidIDE.getsys(id); // pre-load system information (if needed)\n if (openpanels.length) for (i=0;i<openpanels.length;i++) { config.macros.tidIDE.loadPanel(id,openpanels[i]); }\n // see [[TextAreaPlugin]] for extended ctrl-F/G (search/search again)and TAB handler definitions\n var elems=newIDE.getElementsByTagName("textarea");\n for (var i=0;i<elems.length;i++) { \n if (window.addKeyDownHandlers!=undefined) window.addKeyDownHandlers(elems[i]);\n }\n}\n//}}}\n\n// // CUSTOM PANEL FUNCTIONS \n//{{{\nconfig.macros.tidIDE.loadPanel=function(id,toolid) {\n var place=document.getElementById(id+"_"+toolid+"_panel"); if (!place) return;\n var t=store.getTiddler(toolid);\n place.innerHTML=""; \n if (t) wikify(t.text,place); else place.innerHTML=this.toolsDef.format([id,toolid]);\n}\n//}}}\n\n// // EDITOR PANEL FUNCTIONS\n//{{{\nconfig.macros.tidIDE.set=function(id,title) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n if (f.dirty && !confirm(config.commands.cancelTiddler.warning.format([f.current]))) return;\n // reset to form defaults\n f.dirty=false;\n f.current="";\n f.created.value=f.created.defaultValue;\n f.modified.value=f.modified.defaultValue;\n f.author.value=f.author.defaultValue;\n f.content.value=f.content.defaultValue;\n f.tags.value=f.tags.defaultValue;\n f.size.value=f.size.defaultValue;\n f.freeze.checked=false;\n f.domview.value="";\n f.htmlview.value="";\n f.status.value="";\n p.innerHTML="";\n if (!title.length) return;\n f.current=title;\n // values for new/shadow tiddlers\n var cdate=new Date();\n var mdate=new Date();\n var modifier=config.options.txtUserName;\n var text=config.views.editor.defaultText.format([title]);\n var tags="";\n // adjust values for shadow tiddlers\n if (store.isShadowTiddler(title))\n { modifier=config.views.wikified.shadowModifier; text=store.getTiddlerText(title) }\n // get values for specified tiddler (if it exists)\n var t=store.getTiddler(title);\n if (t) { var cdate=t.created; var mdate=t.modified; var modifier=t.modifier; var text=t.text; var tags=t.getTags(); }\n if (!t && !store.isShadowTiddler(title)) f.tiddlers.options[f.tiddlers.options.length]=new Option(title,title,false,true); // add item to list\n f.tiddlers.value=title; // select current title (just in case it wasn't already selected)\n f.created.value=cdate.formatString(this.datetimefmt);\n f.modified.value=mdate.formatString(this.datetimefmt);\n f.author.value=modifier;\n f.content.value=text;\n f.tags.value=tags;\n f.minoredits.checked=config.options.chkForceMinorUpdate&&!readOnly;\n f.size.value=f.content.value.length+" bytes";\n if (f.preview.checked) { p.style.display="block"; this.render(id); }\n}\n\nconfig.macros.tidIDE.add=function(id) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n if (f.dirty && !confirm(config.commands.cancelTiddler.warning.format([f.current]))) return;\n var title=prompt(this.titleMsg,config.macros.newTiddler.title);\n while (title && store.tiddlerExists(title) && !confirm(config.messages.overwriteWarning.format([title])))\n title=prompt(this.titleMsg,config.macros.newTiddler.title);\n if (!title || !title.trim().length) return; // cancelled by user\n f.dirty=false; // suppress unneeded confirmation message\n this.set(id,title);\n}\n\nconfig.macros.tidIDE.remove=function(id) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n if (!f.current.length) return;\n if (!store.tiddlerExists(f.current) && store.isShadowTiddler(f.current)) { alert(this.isShadowMsg.format([f.current])); return; }\n if (config.options.chkConfirmDelete && !confirm(config.commands.deleteTiddler.warning.format([f.current]))) return;\n if (store.tiddlerExists(f.current)) {\n story.closeTiddler(f.current);\n store.removeTiddler(f.current);\n store.setDirty(true);\n if(config.options.chkAutoSave) saveChanges();\n }\n f.tiddlers.options[f.tiddlers.selectedIndex]=null; // remove item from list\n f.dirty=false; // suppress unneeded confirmation message\n this.set(id,""); // clear form controls\n}\n\nconfig.macros.tidIDE.save=function(id,saveAs) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var title=f.current;\n if (!title || !title.trim().length || saveAs) { // get a new title\n title=prompt(this.titleMsg,config.macros.newTiddler.title);\n while (title && store.tiddlerExists(title) && !confirm(config.messages.overwriteWarning.format([title])))\n title=prompt(this.titleMsg,config.macros.newTiddler.title);\n if (!title || !title.trim().length) return; // cancelled by user\n f.tiddlers.options[f.tiddlers.options.length]=new Option(title,title,false,true); // add item to list\n f.current=title;\n }\n var author=config.options.txtUserName;\n var mdate=new Date();\n var content=f.content.value;\n var tags=f.tags.value;\n var tiddler=store.saveTiddler(title,title,content,author,mdate,tags);\n if (f.minoredits.checked) {\n var author=f.author.value;\n var mdate=new Date(f.modified.value);\n var cdate=new Date(f.created.value);\n tiddler.assign(null,null,author,mdate,null,cdate);\n }\n store.setDirty(true);\n if(config.options.chkAutoSave) saveChanges();\n story.refreshTiddler(title,null,true);\n f.dirty=false;\n}\n//}}}\n\n// // EDITOR PANEL: PREVIEW FUNCTIONS\n//{{{\nif (config.options.txtTidIDEAutoFreeze==undefined)\n config.options.txtTidIDEAutoFreeze=250; // limit (in milliseconds) for auto-freezing preview display\n\nconfig.macros.tidIDE.render=function(id) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n var d=document.getElementById(id+"_domview");\n var h=document.getElementById(id+"_htmlview");\n p.innerHTML="";\n f.status.value=this.renderMsg;\n var start=new Date();\n wikify(f.content.value.replace(regexpCarriageReturn,''),p);\n var end=new Date();\n this.renderDOM(id);\n this.renderHTML(id);\n f.status.value=f.current+": "+(end-start+1)+"ms";\n // automatically suspend preview updates for slow rendering tiddlers\n if (end-start+1>config.options.txtTidIDEAutoFreeze) {\n f.freeze.checked=true;\n f.status.value+=this.timeoutMsg.format([config.options.txtTidIDEAutoFreeze]);\n }\n if (f.freeze.checked) f.status.value+=this.freezeMsg;\n}\n\nconfig.macros.tidIDE.renderDOM=function(id) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n var d=document.getElementById(id+"_domview");\n var h=document.getElementById(id+"_htmlview");\n p.style.height=(f.dom.checked||f.html.checked)?"10em":"25em";\n if (f.dom.checked) d.value=this.getNodeTree(p,"| ");\n d.style.display=f.dom.checked?"inline":"none";\n d.style.width=f.html.checked?"49.5%":"100%";\n h.style.width=f.dom.checked?"49.5%":"100%";\n}\n\nconfig.macros.tidIDE.renderHTML=function(id) {\n var place=document.getElementById(id+"_editorpanel"); if (!place) return;\n var f=document.getElementById(id+"_editorform");\n var p=document.getElementById(id+"_preview");\n var d=document.getElementById(id+"_domview");\n var h=document.getElementById(id+"_htmlview");\n p.style.height=(f.dom.checked||f.html.checked)?"10em":"25em";\n if (f.html.checked) h.value=this.formatHTML(p.innerHTML);\n h.style.display=f.html.checked?"inline":"none";\n d.style.width=f.html.checked?"49.5%":"100%";\n h.style.width=f.dom.checked?"49.5%":"100%";\n}\n\nconfig.macros.tidIDE.formatHTML=function(txt) {\n if (config.browser.isIE) return txt; // BYPASS - 4/24/2006 due to IE hang problem. Will fix later...\n var out="";\n var indent="";\n var level=0;\n for (var i=0;i<txt.length;i++) {\n var c=txt.substr(i,1);\n if (c=="<") {\n if (txt.substr(i+1,1)=="/") indent=indent.substr(0,indent.length-2);\n out+="\sn"+indent;\n if (txt.substr(i+1,1)!="/" && txt.substr(i+1,3)!="br>" && txt.substr(i+1,2)!="p>" && txt.substr(i+1,3)!="hr>") indent+=" ";\n }\n out+=c;\n if (c=="\sn")\n out+=indent;\n if (c==">" && txt.substr(i+1,1)!="<")\n out+="\sn"+indent;\n }\n return out;\n}\n\nconfig.macros.tidIDE.getNodeTree=function(theNode,theIndent,showPath,inline,thePrefix,thePath)\n{\n if (!theNode) return "";\n if (!thePrefix) thePrefix="";\n if (!thePath) thePath="";\n var mquote='"'+(inline?"{{{":"");\n var endmquote=(inline?"}}}":"")+'"';\n // generate output for this node\n var out = thePrefix;\n if (showPath && thePath.length)\n out += (inline?"//":"")+thePath.substr(1)+":"+(inline?"//":"")+"\sr\sn"+thePrefix;\n if (theNode.className=="DOMViewer")\n return out+'[DOMViewer]\sr\sn'; // avoid self-referential recursion\n out += (inline?"''":"")+theNode.nodeName.toUpperCase()+(inline?"''":"");\n if (theNode.nodeName=="#text")\n out += ' '+mquote+theNode.nodeValue.replace(/\sn/g,'\s\sn')+endmquote;\n if (theNode.className)\n out += ' class='+mquote+theNode.className+endmquote;\n if (theNode.type)\n out += ' type='+mquote+theNode.type+endmquote;\n if (theNode.id)\n out += ' id='+mquote+theNode.id+endmquote;\n if (theNode.name)\n out += " "+theNode.name+(theNode.value?"="+mquote+theNode.value+endmquote:"");\n if (theNode.href)\n out += ' href='+mquote+theNode.href+endmquote;\n if (theNode.src)\n out += ' src='+mquote+theNode.src+endmquote;\n if (theNode.attributes && theNode.getAttribute("tiddlyLink")!=undefined)\n out += ' tiddler='+mquote+theNode.getAttribute("tiddlyLink")+endmquote;\n out += "\sr\sn";\n // recursively generate output for child nodes\n thePath=thePath+"."+theNode.nodeName.toLowerCase();\n thePrefix=theIndent+thePrefix;\n for (var i=0;i<theNode.childNodes.length;i++)\n {\n var thisChild=theNode.childNodes.item(i);\n var theNum=(inline?"~~":"(")+(i+1)+(inline?"~~":")");\n out += this.getNodeTree(thisChild,theIndent,showPath,inline,thePrefix,thePath+theNum);\n }\n return out;\n}\n//}}}\n\n// // DOMViewer macro\n//{{{\nversion.extensions.DOMViewer = version.extensions.tidIDE;\nconfig.macros.DOMViewer = { };\nconfig.macros.DOMViewer.handler = function(place,macroName,params) {\n // set default params\n var inline=false;\n var theRows=15;\n var theIndent="| ";\n var showPath=false;\n var theTarget=place;\n // unpack options parameters\n if (params[0]=='inline') { inline=true; theIndent=">"; params.shift(); } \n if (params[0]&&(params[0].substr(0,7)=="indent:")) { theIndent=params[0].substr(7); params.shift(); } \n if (params[0]&&(params[0].substr(0,5)=="rows:")) { theRows=params[0].substr(5); params.shift(); } \n if (params[0]=='path') { showPath=true; params.shift(); } \n if (params[0]) {\n theTarget=document.getElementById(params[0]);\n if (!theTarget)\n if (store.getTiddler(params[0])!=undefined) {\n theTarget=document.getElementById("tiddler"+params[0]);\n if (!theTarget && confirm("DOMViewer asks:\sn\snIs it OK to open tiddler '"+params[0]+"' now?")) { \n story.displayTiddler(null,params[0],1,null,null,false);\n theTarget=document.getElementById("tiddler"+params[0]);\n }\n }\n params.shift();\n }\n // generate and display DOM tree\n if (inline) {\n var out=config.macros.tidIDE.getNodeTree(theTarget,theIndent,showPath,inline);\n wikify(out,place);\n }\n else {\n var out=config.macros.tidIDE.getNodeTree(theTarget,theIndent,showPath,inline);\n var css=".DOMViewer{width:100%;font-size:8pt;color:inherit;background:transparent;border:0px;}";\n setStylesheet(css,"DOMViewerPlugin");\n var theTextArea=createTiddlyElement(place,"textarea",null,"DOMViewer",out);\n theTextArea.rows=theRows;\n theTextArea.cols=60;\n theTextArea.wrap="off";\n theTextArea.theTarget=theTarget;\n theTextArea.theIndent=theIndent;\n theTextArea.showPath=showPath;\n }\n}\n//}}}\n\n// // SYSTEM PANEL FUNCTIONS\n//{{{\nconfig.macros.tidIDE.showObject=function(o) { // generate formatted output for displaying object references\n var t="";\n for (var p in o) {\n if (typeof o[p]=="function") {\n t+="- - - - - - - - - - "+p+" - - - - - - - - - -\sn";\n t+=o[p].toString();\n t+="\sn- - - - - - - - - - END: "+p+" - - - - - - - - - -\sn";\n }\n else\n t+='['+typeof o[p]+'] '+p+": "+o[p]+"\sn";\n }\n return t;\n}\n\nconfig.macros.tidIDE.getsys=function(id) {\n var place=document.getElementById(id+"_systempanel"); if (!place) return;\n var f=document.getElementById(id+"_systemform");\n f.sysview.value="";\n // OPTIONS\n while (f.sys_opts.options.length > 1) { f.sys_opts.options[1]=null; } // clear list\n f.config_view.value=""; // clear edit field\n var cookies = { };\n if (document.cookie != "") {\n var p = document.cookie.split("; ");\n for (var i=0; i < p.length; i++) {\n var pos=p[i].indexOf("=");\n if (pos==-1)\n cookies[p[i]]="";\n else\n cookies[p[i].substr(0,pos)]=unescape(p[i].slice(pos+1));\n }\n }\n var c=1;\n var opt=new Array(); for (var i in config.options) opt.push(i); opt.sort();\n for(var i=0; i<opt.length; i++) {\n if ((opt[i].substr(0,3)=="txt")||(opt[i].substr(0,3)=="chk")) {\n var txt = (opt[i].substr(0,3)=="chk"?("["+(config.options[opt[i]]?"x":"_")+"] "):"")+opt[i]+(cookies[opt[i]]?" (cookie)":"");\n var val = config.options[opt[i]];\n f.sys_opts.options[c++]=new Option(txt,val,false,false);\n }\n }\n // STYLESHEETS\n while (f.sys_styles.options.length > 1) { f.sys_styles.options[1]=null; } // clear list\n var c=1;\n var styles=document.getElementsByTagName("style");\n for(var i=0; i < styles.length; i++) {\n var id=styles[i].getAttribute("id"); if (!id) id="(default)";\n var txt=id;\n var val="/* stylesheet:"+txt+" */\sn"+styles[i].innerHTML;\n f.sys_styles.options[c++]=new Option(txt,val,false,false);\n }\n // SHADOWS\n while (f.sys_shadows.options.length > 1) { f.sys_shadows.options[1]=null; } // clear list\n var c=1;\n for(var s in config.shadowTiddlers) f.sys_shadows.options[c++]=new Option(s,config.shadowTiddlers[s],false,false);\n // NOTIFICATIONS\n while (f.sys_notify.options.length > 1) { f.sys_notify.options[1]=null; } // clear list\n var c=1;\n for (var i=0; i<store.namedNotifications.length; i++) {\n var n = store.namedNotifications[i];\n var fn = n.notify.toString();\n fn = fn.substring(fn.indexOf("function ")+9,fn.indexOf("{")-1);\n var txt=(n.name?n.name:"any change")+"="+fn;\n var val="/* notify: "+txt+" */\sn"+n.notify.toString();\n f.sys_notify.options[c++]=new Option(txt,val,false,false);\n }\n // MACROS\n while (f.sys_macros.options.length > 1) { f.sys_macros.options[1]=null; } // clear list\n var c=1;\n var macros=new Array(); for (var m in config.macros) macros.push(m); macros.sort();\n for(var i=0; i < macros.length; i++)\n f.sys_macros.options[c++]=new Option(macros[i],this.showObject(config.macros[macros[i]]),false,false);\n // TOOLBAR COMMANDS\n while (f.sys_commands.options.length > 1) { f.sys_commands.options[1]=null; } // clear list\n var c=1;\n for(var cmd in config.commands)\n f.sys_commands.options[c++]=new Option(cmd,this.showObject(config.commands[cmd]),false,false);\n // FORMATTERS\n while (f.sys_formatters.options.length > 1) { f.sys_formatters.options[1]=null; } // clear list\n var c=1;\n for(var i=0; i < config.formatters.length; i++)\n f.sys_formatters.options[c++]=new Option(config.formatters[i].name,this.showObject(config.formatters[i]),false,false);\n // PARAMIFIERS\n while (f.sys_params.options.length > 1) { f.sys_params.options[1]=null; } // clear list\n var c=1;\n for(var param in config.paramifiers)\n f.sys_params.options[c++]=new Option(param,this.showObject(config.paramifiers[param]),false,false);\n // GLOBALS\n //global variables and functions (excluding most DOM and ~TiddyWiki core definitions)://\n var DOM0_globals = {\n addEventListener: 1, alert: 1, atob: 1, back: 1, blur: 1, btoa: 1, captureEvents: 1, clearInterval: 1,\n clearTimeout: 1, close: 1, closed: 1, Components: 1, confirm: 1, content: 1, controllers: 1, crypto: 1,\n defaultStatus: 1, defaultStatus: 1, directories: 1, disableExternalCapture: 1, dispatchEvent: 1, document: 1,\n dump: 1, enableExternalCapture: 1, escape: 1, find: 1, focus: 1, forward: 1, frameElement: 1, frames: 1,\n fullScreen: 1, getAttention: 1, getComputedStyle: 1, getSelection: 1, history: 1, home: 1, innerHeight: 1,\n innerWidth: 1, length: 1, location: 1, locationbar: 1, menubar: 1, moveBy: 1, moveTo: 1, name: 1,\n navigator: 1, open: 1, openDialog: 1, opener: 1, outerHeight: 1, outerWidth: 1, pageXOffset: 1,\n pageYOffset: 1, parent: 1, personalbar: 1, pkcs11: 1, print: 1, prompt: 1, prompter: 1, releaseEvents: 1,\n removeEventListener: 1, resizeBy: 1, resizeTo: 1, routeEvent: 1, screen: 1, screenX: 1, screenY: 1,\n scroll: 1, scrollbars: 1, scrollBy: 1, scrollByLines: 1, scrollByPages: 1, scrollMaxX: 1, scrollMaxY: 1,\n scrollTo: 1, scrollX: 1, scrollY: 1, self: 1, setInterval: 1, setResizable: 1, setTimeout: 1, sidebar: 1,\n sizeToContent: 1, status: 1, statusbar: 1, stop: 1, toolbar: 1, top: 1, unescape: 1, updateCommands: 1,\n window: 1, getInterface: 1\n };\n var tw_globals = {\n version: 1, config: 1, DEFAULT_VIEW_TEMPLATE: 1, DEFAULT_EDIT_TEMPLATE: 1, store: 1, story: 1,\n Formatter: 1, anim: 1, readOnly: 1, highlightHack: 1, main: 1, restart: 1, saveTest: 1, loadSystemConfig: 1,\n processConfig: 1, invokeMacro: 1, Formatter: 1, wikify: 1, wikifyPlain: 1, highlightify: 1, Wikifier: 1, \n Tiddler: 1, regexpBackSlashEn: 1, regexpBackSlash: 1, regexpBackSlashEss: 1, regexpNewLine: 1, \n regexpCarriageReturn: 1, TiddlyWiki: 1, displayTiddlers: 1, displayTiddler: 1, Story: 1, displayMessage: 1,\n clearMessage: 1, refreshElements: 1, applyHtmlMacros: 1, refreshPageTemplate: 1, applyPageTemplate: 1,\n refreshDisplay: 1, refreshPageTitle: 1, refreshStyles: 1, loadOptionsCookie: 1, saveOptionCookie: 1,\n saveUsingSafari: 1, startSaveArea: 1, endSaveArea: 1, checkUnsavedChanges: 1, saveChanges: 1,\n getBackupPath: 1, generateRss: 1, allTiddlersAsHtml: 1,\n convertUTF8ToUnicode: 1, manualConvertUTF8ToUnicode: 1, mozConvertUTF8ToUnicode: 1,\n convertUnicodeToUTF8: 1, manualConvertUnicodeToUTF8: 1, mozConvertUnicodeToUTF8: 1,\n saveFile: 1, loadFile: 1, ieSaveFile: 1, ieLoadFile: 1, mozillaSaveFile: 1, mozillaLoadFile: 1,\n operaUrlToFilename: 1, operaSaveFile: 1, operaLoadFile: 1, safariFilenameToUrl: 1, safariLoadFile: 1,\n safariSaveFile: 1, detectPlugin: 1, createTiddlyButton: 1, createTiddlyLink: 1, refreshTiddlyLink: 1,\n createExternalLink: 1, onClickTiddlerLink: 1, createTagButton: 1, onClickTag: 1, onClickTagOpenAll: 1,\n createTiddlyError: 1, Animator: 1, Zoomer: 1, Cascade: 1, Scroller: 1, Slider: 1, Popup: 1,\n createTiddlerPopup: 1, scrollToTiddlerPopup: 1, hideTiddlerPopup: 1, RGB: 1, drawGradient: 1,\n createTiddlyText: 1, createTiddlyElement: 1, addEvent: 1, removeEvent: 1, addClass: 1,\n removeClass: 1, hasClass: 1, resolveTarget: 1, getPlainText: 1, ensureVisible: 1, \n findWindowWidth: 1, findWindowHeight: 1, findScrollX: 1, findScrollY: 1, findPosX: 1, findPosY: 1,\n insertSpacer: 1, removeChildren: 1, setStylesheet: 1,\n Packages: 1, sun: 1, java: 1, netscape: 1, XPCNativeWrapper: 1, GeckoActiveXObject: 1\n };\n while (f.sys_globals.options.length > 1) { f.sys_globals.options[1]=null; } // clear list\n if (config.browser.isIE) return; // BYPASS - 8/16/2006 // DON'T LIST GLOBALS IN IE... throws object error - WFFL\n try {\n var c=1;\n for (var v in window) if (!(DOM0_globals[v] || tw_globals[v])) {\n var t=window[v];\n if ((typeof window[v])=='object') {\n var t='';\n for (var p in window[v]) {\n t+=((typeof window[v][p])!='function')?('['+typeof window[v][p]+'] '+p):p;\n t+=((typeof window[v][p])!='function')?('='+window[v][p]):'';\n t+='\sn';\n }\n }\n f.sys_globals.options[c++]=new Option(((typeof window[v])!='function')?('['+typeof window[v]+'] '+v):v,t,false,false);\n } \n }\n catch(e) { ; }\n}\n\nconfig.macros.tidIDE.setsys=function(id) {\n var place=document.getElementById(id+"_systempanel"); if (!place) return;\n var f=document.getElementById(id+"_systemform");\n if (f.sys_opts.selectedIndex==0) return; // heading - do nothing\n var name=f.sys_opts.options[f.sys_opts.selectedIndex].text.replace(/\s[[Xx_]\s] /,'').replace(/ \s(cookie\s)/,'')\n var value=f.config_view.value;\n config.options[name]=value;\n saveOptionCookie(name);\n f.sys_opts.options[f.sys_opts.selectedIndex].value=value;\n return;\n}\n//}}}\n\n// // HTML DEFINITIONS\n//{{{\nconfig.macros.tidIDE.html = { };\nconfig.macros.tidIDE.html.framework = " \s\n <html> %version% <form style='display:inline;margin:0;padding:0;'>%selector%</form> %panels% </html> \s\n";\n//}}}\n//{{{\nconfig.macros.tidIDE.html.editorchk = " \s\n <input type=checkbox name=editor \s\n style='display:inline;width:auto;margin:1px;' \s\n title='add/delete/modify tiddlers' %showpanel% \s\n onclick='document.getElementById(\s"%id%_editorpanel\s").style.display=this.checked?\s"block\s":\s"none\s"; \s\n if (this.checked) config.macros.tidIDE.render(\s"%id%\s");'>%toolname% \s\n";\nconfig.macros.tidIDE.html.systemchk = " \s\n <input type=checkbox name=system \s\n style='display:inline;width:auto;margin:1px;' \s\n title='view TiddlyWiki system internals and configurable options' %showpanel% \s\n onclick='document.getElementById(\s"%id%_systempanel\s").style.display=this.checked?\s"block\s":\s"none\s"; \s\n if (this.checked) config.macros.tidIDE.getsys(\s"%id%\s");'>%toolname% \s\n";\nconfig.macros.tidIDE.html.toolschk = " \s\n <input type=checkbox name=tools \s\n style='display:inline;width:auto;margin:1px;' \s\n title='' %showpanel% \s\n onclick='document.getElementById(\s"%id%_%toolid%_panel\s").style.display=this.checked?\s"block\s":\s"none\s"; \s\n if (this.checked) config.macros.tidIDE.loadPanel(\s"%id%\s",\s"%toolid%\s");'>%toolname% \s\n";\n//}}}\n//{{{\nconfig.macros.tidIDE.html.toolspanel = " \s\n <div id='%id%_%toolid%_panel' style='display:%showpanel%;margin:0;margin-top:0.5em'> \s\n </div> \s\n";\n//}}}\n//{{{\nconfig.macros.tidIDE.html.systempanel = " \s\n <div id='%id%_systempanel' style='display:%showpanel%;margin:0;margin-top:0.5em;white-space:nowrap'> \s\n <form id='%id%_systemform' style='display:inline;margin:0;padding:0;'> \s\n <!-- configurable options --> \s\n <table style='width:100%;border:0;padding:0;margin:0'><tr style='border:0;padding:0;margin:0'> \s\n <td style='width:30%;border:0;padding:0;margin:0'> \s\n <select size=1 name='sys_opts' style='width:100%;font-size:%fontsize%;' \s\n onchange='this.form.config_view.value=this.value'> \s\n <option value=\s"\s">config.options.*</option> \s\n </select> \s\n </td><td style='width:50%;border:0;padding:0;margin:0;'> \s\n <input type=text name='config_view' size=60 style='width:99%;font-size:%fontsize%;' value=''> \s\n </td><td style='width:20%;white-space:nowrap;border:0;padding:0;margin:0;'> \s\n <input type=button style='width:50%;' value='set option' title='save this TiddlyWiki option value' \s\n onclick='config.macros.tidIDE.setsys(\s"%id%\s");config.macros.tidIDE.getsys(\s"%id%\s");'><!-- \s\n --><input type=button style='width:50%;' value='refresh' title='retrieve current options and system values' \s\n onclick='this.form.sysview.style.display=\s"none\s"; config.macros.tidIDE.getsys(\s"%id%\s");'> \s\n </td></tr><tr style='border:0;padding:0;margin:0'><td colspan=3 \s\n style='white-space:nowrap;width:100%;border:0;padding:0;margin:0'> \s\n <!-- system objects --> \s\n <select size=1 name='sys_styles' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">stylesheets...</option> \s\n </select><select size=1 name='sys_shadows' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">shadows...</option> \s\n </select><select size=1 name='sys_notify' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">notifications...</option> \s\n </select><select size=1 name='sys_globals' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">globals...</option> \s\n </select><br><select size=1 name='sys_macros' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">macros...</option> \s\n </select><select size=1 name='sys_commands' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">toolbars...</option> \s\n </select><select size=1 name='sys_formatters' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">wikifiers...</option> \s\n </select><select size=1 name='sys_params' style='width:25%;font-size:%fontsize%;' \s\n onchange='this.form.sysview.style.display=\s"block\s"; this.form.%id%_sysview.value=this.value'> \s\n <option value=\s"\s">paramifiers...</option> \s\n </select> \s\n <!-- system value display area --> \s\n <span style='white-space:normal;'><textarea id='%id%_sysview' name=sysview cols=60 rows=12 \s\n onfocus='this.select()' style='width:99.5%;height:16em;font-size:%fontsize%;font-family:%fontface%;display:none'></textarea></span> \s\n </td></tr></table> \s\n </form> \s\n </div> \s\n";\n//}}}\n//{{{\nconfig.macros.tidIDE.html.editorpanel = " \s\n <div id='%id%_editorpanel' style='display:%showpanel%;margin:0;margin-top:0.5em'> \s\n <form id='%id%_editorform' style='display:inline;margin:0;padding:0;'> \s\n <!-- tiddler editor list and buttons --> \s\n <select size=1 name=tiddlers style='display:inline;width:40%;font-size:%fontsize%;' \s\n onchange='config.macros.tidIDE.set(\s"%id%\s",this.value); this.value=this.form.current;'> \s\n <option value=''>select a tiddler...</option> \s\n %tiddlerlist% \s\n </select><!-- \s\n --><input name=add type=button style='display:inline;width:10%' \s\n value='new' title='create a new tiddler' \s\n onclick='config.macros.tidIDE.add(\s"%id%\s")' %disabled%><!-- \s\n --><input name=remove type=button style='display:inline;width:10%' \s\n value='remove' title='delete this tiddler' \s\n onclick='config.macros.tidIDE.remove(\s"%id%\s")' %disabled%><!-- \s\n --><input name=save type=button style='display:inline;width:10%' \s\n value='save' title='save changes to this tiddler' \s\n onclick='config.macros.tidIDE.save(\s"%id%\s")' %disabled%><!-- \s\n --><input name=saveas type=button style='display:inline;width:10%' \s\n value='save as' title='save changes to a new tiddler' \s\n onclick='config.macros.tidIDE.save(\s"%id%\s",true)' %disabled%><!-- \s\n --><input name=view type=button style='display:inline;width:10%' \s\n value='open' title='open this tiddler for regular viewing' \s\n onclick='if (!this.form.current.length) return; story.displayTiddler(null,this.form.current)'><!-- \s\n --><!-- COMMENTED OUT <input name=run type=button style='display:inline;width:9%' \s\n value='run' title='evaluate this tiddler as a javascript \s"systemConfig\s" plugin' \s\n onclick='if (!confirm(config.macros.tidIDE.evalMsg.format([this.form.current]))) return false; \s\n var err=processConfig(this.form.content.value); \s\n if(err)displayMessage(config.messages.customConfigError.format([err,this.form.current]));'> END COMMENT --><!-- \s\n --><input name=previewbutton type=button style='display:inline;width:10%;' \s\n value='preview' title='show \s"live\s" preview display' \s\n onclick='document.getElementById(\s"%id%_previewpanel\s").style.display=\s"block\s"; \s\n this.form.preview.checked=true; config.macros.tidIDE.render(\s"%id%\s")'><!-- \s\n hidden field for preview show/hide state: \s\n --><input name=preview type=checkbox style='display:none;'>\s\n <!-- tiddler content edit --> \s\n <div><textarea id='%id%_content' name='content' edit='text' cols=60 rows=%maxrows% \s\n style='width:100%;font-size:%fontsize%;font-family:%fontface%;' \s\n onkeyup='var f=this.form; f.dirty=true; f.size.value=this.value.length+\s" bytes\s"; \s\n var p=document.getElementById(\s"%id%_preview\s"); \s\n if (f.preview.checked && !f.freeze.checked) { config.macros.tidIDE.render(\s"%id%\s"); }'></textarea></div> \s\n <!-- tag edit and droplist --> \s\n <table width='100%' style='border:0;padding:0;margin:0'><tr style='border:0;padding:0;margin:0'> \s\n <td style='border:0;padding:0;margin:0'> \s\n <input type=text name=tags size=60 style='width:100%;font-size:%fontsize%;' value='' \s\n onchange='this.form.dirty=true' %disabled%> \s\n </td><td width='1' style='border:0;padding:0;margin:0;'> \s\n <select size=1 name=taglist style='font-size:%fontsize%;' \s\n onchange='this.form.dirty=true; this.form.tags.value+=\s" \s"+this.value' %disabled%> \s\n <option value=''>select tags...</option> \s\n %taglist% \s\n </select> \s\n </td></tr></table> \s\n <!-- created/modified dates, author, current tiddler size --> \s\n <div style='float:right;'> \s\n created <input type=text name=created size=15 \s\n style='display:inline;font-size:%fontsize%;text-align:center;padding:0;' value='' \s\n onchange='this.form.dirty=true' %minoredits%> \s\n modified <input type=text name=modified size=15 \s\n style='display:inline;font-size:%fontsize%;text-align:center;padding:0;' value='' \s\n onchange='this.form.dirty=true;' %minoredits%> \s\n by <input type=text name=author size=15 \s\n style='display:inline;font-size:%fontsize%;padding:0;' value='' \s\n onfocus='this.select()' onchange='this.form.dirty=true' %minoredits%> \s\n <input type=text name=size size=10 \s\n style='display:inline;font-size:%fontsize%;text-align:center;padding:0;' value='' \s\n onfocus='this.blur()' onkeydown='return false' DISABLED> \s\n </div> \s\n <!-- toggles: read-only, minor edit --> \s\n <span style='white-space:nowrap'> \s\n <input type=checkbox name=readonly \s\n style='display:inline;width:auto;margin:1px;' %readonlychk% \s\n title='do not allow tiddler changes to be saved' \s\n onclick='readOnly=config.options.chkHttpReadOnly=this.checked;saveOptionCookie(\s"chkHttpReadOnly\s"); \s\n var f=this.form; f.minoredits.disabled=f.tags.disabled=f.taglist.disabled=this.checked; \s\n f.add.disabled=f.remove.disabled=f.save.disabled=f.saveas.disabled=this.checked; \s\n f.created.disabled=f.modified.disabled=f.author.disabled=this.checked||!f.minoredits.checked;'>readonly \s\n <input type=checkbox name=minoredits \s\n style='display:inline;width:auto;margin:1px;' %disabled% %minorchk% \s\n title='check: save datestamps/author as entered, uncheck: auto-update modified/author' \s\n onclick='this.form.created.disabled=this.form.modified.disabled=this.form.author.disabled=!this.checked; \s\n config.options.chkForceMinorUpdate=this.checked;saveOptionCookie(\s"chkForceMinorUpdate\s");'>minor edits \s\n </span> \s\n <!-- tiddler preview display --> \s\n <div id='%id%_previewpanel' style='display:none;white-space:nowrap'> \s\n <div id='%id%_preview' class='viewer' style='margin:0;margin-top:.5em;height:25em;overflow:auto;white-space:normal'> \s\n &nbsp; \s\n </div> \s\n <!-- DOM and HTML viewers --> \s\n <textarea id='%id%_domview' name=domview cols=60 rows=12 wrap=off \s\n onfocus='this.select()' style='display:none;width:100%;height:16em;font-size:%fontsize%;'></textarea><!-- \s\n --><textarea id='%id%_htmlview' name=htmlview cols=60 rows=12 wrap=off \s\n onfocus='this.select()' style='display:none;width:100%;height:16em;font-size:%fontsize%;'></textarea> \s\n <!-- status line, preview option checkboxes, run/refresh buttons --> \s\n <table width='100%' style='border:0;padding:0;margin:0'><tr style='border:0;padding:0;margin:0'> \s\n <td style='border:0;padding:0;margin:0'> \s\n <input type=text '%id%_status' name=status style='padding:0;width:100%;font-size:%fontsize%;'> \s\n </td><td style='width:1%;border:0;padding:0;margin:0;text-align:right;white-space:nowrap'> \s\n <input type=checkbox name=dom style='display:inline;width:auto;margin:1px;' \s\n title='show Document Object Model (DOM) information' \s\n onclick='config.macros.tidIDE.renderDOM(\s"%id%\s");'>DOM \s\n <input type=checkbox name=html style='display:inline;width:auto;margin:1px;' \s\n title='show rendered HTML' \s\n onclick='config.macros.tidIDE.renderHTML(\s"%id%\s");'>HTML \s\n <input type=checkbox name=freeze style='display:inline;width:auto;margin:1px;' \s\n title='do not update preview display as changes are made' \s\n onclick='var p=document.getElementById(\s"%id%_preview\s"); \s\n if (this.checked) this.form.status.value+=config.macros.tidIDE.freezeMsg; \s\n else config.macros.tidIDE.render(\s"%id%\s");'>freeze \s\n <!-- COMMENTED OUT <input type=button style='display:inline;width:auto;' value='run' \s\n title='evaluate this tiddler as a javascript \s"systemConfig\s" plugin' \s\n onclick='if (!confirm(config.macros.tidIDE.evalMsg.format([this.form.current]))) return false; \s\n var err=processConfig(this.form.content.value); \s\n if(err)displayMessage(config.messages.customConfigError.format([err,this.form.current]));'> END COMMENT --><!-- \s\n --><input type=button style='display:inline;width:auto;' value='refresh' \s\n title='update preview display' \s\n onclick='config.macros.tidIDE.render(\s"%id%\s")'><!-- \s\n --><input type=button style='display:inline;width:auto;' value='hide' \s\n title='hide preview display' \s\n onclick='document.getElementById(\s"%id%_previewpanel\s").style.display=\s"none\s"; \s\n this.form.preview.checked=false; config.macros.tidIDE.render(\s"%id%\s")'> \s\n </td></tr></table> \s\n </div> \s\n </form> \s\n </div> \s\n";\n//}}}
<!--{{{-->\n<div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler permalink references jump peekcrypt autocrypt'></div>\n<div class='title' macro='view title'></div>\n<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date [[DD MMM YYYY]]'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date [[DD MMM YYYY]]'></span>)</div>\n<div class='tagging' macro='tagging'></div>\n<div class='tagged' macro='tags'></div>\n<div class='viewer' macro='view text wikified'></div>\n<div class='tagClear'></div>\n<!--}}}-->
password-safe$$ca31ddb35d26013d6d8bf6e3c65339a142e3f2cedcd26\ne251dd24010c152e82e4ce76185b0f47acad9cc80d443003a8b$$\n
<<tidIDE id:example "font:Verdana" size:8pt system +edit:EncryptTaggedPlugin>>
Stored passwords are on the left encrypted with the password ''foo''.\n\n<<encrypt_tag "passwords" "password-safe" "encrypt/decrypt all passwords>>\n\n<<encrypt_clear "password-safe">>
password-safe$$e85dab01dce42ddc21c9f9783849e8d9a0b5d2f7372a0\n017cfcb02536a5cee9958b026052db75366cda9c0bb93a26c66$$\n