From af89410d2c3fc0554bcc5a6c53da8a0080e2eb3f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 10 May 2007 22:29:43 +0000 Subject: [PATCH] add s3ajax libaries --- tantan/wordpress-s3/js/S3Ajax.js | 428 +++++++++++++++++++ tantan/wordpress-s3/js/common.js | 156 +++++++ tantan/wordpress-s3/js/firebug.js | 34 ++ tantan/wordpress-s3/js/formatDate.js | 290 +++++++++++++ tantan/wordpress-s3/js/play.js | 246 +++++++++++ tantan/wordpress-s3/js/sha1.js | 202 +++++++++ tantan/wordpress-s3/js/wiki-config.js | 9 + tantan/wordpress-s3/js/wiki.js | 573 ++++++++++++++++++++++++++ tantan/wordpress-s3/js/wiky.js | 373 +++++++++++++++++ tantan/wordpress-s3/js/wiky.lang.js | 40 ++ tantan/wordpress-s3/js/wiky.math.js | 374 +++++++++++++++++ tantan/wordpress-s3/js/wiky.s3wiki.js | 23 ++ 12 files changed, 2748 insertions(+) create mode 100644 tantan/wordpress-s3/js/S3Ajax.js create mode 100644 tantan/wordpress-s3/js/common.js create mode 100644 tantan/wordpress-s3/js/firebug.js create mode 100644 tantan/wordpress-s3/js/formatDate.js create mode 100644 tantan/wordpress-s3/js/play.js create mode 100644 tantan/wordpress-s3/js/sha1.js create mode 100644 tantan/wordpress-s3/js/wiki-config.js create mode 100644 tantan/wordpress-s3/js/wiki.js create mode 100644 tantan/wordpress-s3/js/wiky.js create mode 100644 tantan/wordpress-s3/js/wiky.lang.js create mode 100644 tantan/wordpress-s3/js/wiky.math.js create mode 100644 tantan/wordpress-s3/js/wiky.s3wiki.js diff --git a/tantan/wordpress-s3/js/S3Ajax.js b/tantan/wordpress-s3/js/S3Ajax.js new file mode 100644 index 0000000..cab16fa --- /dev/null +++ b/tantan/wordpress-s3/js/S3Ajax.js @@ -0,0 +1,428 @@ +/** + S3Ajax v0.1 - An AJAX wrapper package for Amazon S3 + + http://decafbad.com/trac/wiki/S3Ajax + l.m.orchard@pobox.com + Share and Enjoy. + + Requires: + http://pajhome.org.uk/crypt/md5/sha1.js +*/ + +// TODO: Figure out if Safari doesn't support PUT and DELETE + +S3Ajax = { + + // Defeat caching with query params on GET requests? + DEFEAT_CACHE: false, + + // Default ACL to use when uploading keys. + DEFAULT_ACL: 'public-read', + + // Default content-type to use in uploading keys. + DEFAULT_CONTENT_TYPE: 'text/plain', + + /** + DANGER WILL ROBINSON - Do NOT fill in your KEY_ID and SECRET_KEY + here. These should be supplied by client-side code, and not + stored in any server-side files. Failure to protect your S3 + credentials will result in surly people doing nasty things + on your tab. + + For example, scoop values up from un-submitted form fields like so: + + S3Ajax.KEY_ID = $('key_id').value; + S3Ajax.SECRET_KEY = $('secret_key').value; + */ + URL: 'http://s3.amazonaws.com', + KEY_ID: '', + SECRET_KEY: '', + + // Flip this to true to potentially get lots of wonky logging. + DEBUG: false, + + /** + Get contents of a key in a bucket. + */ + get: function(bucket, key, cb, err_cb) { + return this.httpClient({ + method: 'GET', + resource: '/' + bucket + '/' + key, + load: function(req, obj) { + if (cb) return cb(req, req.responseText); + }, + error: function(req, obj) { + if (err_cb) return err_cb(req, obj); + if (cb) return cb(req, req.responseText); + } + }) + }, + + /** + Head the meta of a key in a bucket. + */ + head: function(bucket, key, cb, err_cb) { + return this.httpClient({ + method: 'HEAD', + resource: '/' + bucket + '/' + key, + load: function(req, obj) { + if (cb) return cb(req, req.responseText); + }, + error: function(req, obj) { + if (err_cb) return err_cb(req, obj); + if (cb) return cb(req, req.responseText); + } + }) + }, + + /** + Put data into a key in a bucket. + */ + put: function(bucket, key, content/*, [params], cb, [err_cb]*/) { + + // Process variable arguments for optional params. + var idx = 3; + var params = {}; + if (typeof arguments[idx] == 'object') + params = arguments[idx++]; + var cb = arguments[idx++]; + var err_cb = arguments[idx++]; + + if (!params.content_type) + params.content_type = this.DEFAULT_CONTENT_TYPE; + if (!params.acl) + params.acl = this.DEFAULT_ACL; + + return this.httpClient({ + method: 'PUT', + resource: '/' + bucket + '/' + key, + content: content, + content_type: params.content_type, + meta: params.meta, + acl: params.acl, + load: function(req, obj) { + if (cb) return cb(req); + }, + error: function(req, obj) { + if (err_cb) return err_cb(req, obj); + if (cb) return cb(req, obj); + } + }); + }, + + /** + List buckets belonging to the account. + */ + listBuckets: function(cb, err_cb) { + return this.httpClient({ + method:'GET', resource:'/', + force_lists: [ 'ListAllMyBucketsResult.Buckets.Bucket' ], + load: cb, error:err_cb + }); + }, + + /** + Create a new bucket for this account. + */ + createBucket: function(bucket, cb, err_cb) { + return this.httpClient({ + method:'PUT', resource:'/'+bucket, load:cb, error:err_cb + }); + }, + + /** + Delete an empty bucket. + */ + deleteBucket: function(bucket, cb, err_cb) { + return this.httpClient({ + method:'DELETE', resource:'/'+bucket, load:cb, error:err_cb + }); + }, + + /** + Given a bucket name and parameters, list keys in the bucket. + */ + listKeys: function(bucket, params, cb, err_cb) { + return this.httpClient({ + method:'GET', resource: '/'+bucket, + force_lists: [ 'ListBucketResult.Contents' ], + params:params, load:cb, error:err_cb + }); + }, + + /** + Delete a single key in a bucket. + */ + deleteKey: function(bucket, key, cb, err_cb) { + return this.httpClient({ + method:'DELETE', resource: '/'+bucket+'/'+key, load:cb, error:err_cb + }); + }, + + /** + Delete a list of keys in a bucket, with optional callbacks + for each deleted key and when list deletion is complete. + */ + deleteKeys: function(bucket, list, one_cb, all_cb) { + var _this = this; + + // If the list is empty, then fire off the callback. + if (!list.length && all_cb) return all_cb(); + + // Fire off key deletion with a callback to delete the + // next part of list. + var key = list.shift(); + this.deleteKey(bucket, key, function() { + if (one_cb) one_cb(key); + _this.deleteKeys(bucket, list, one_cb, all_cb); + }); + }, + + /** + Perform an authenticated S3 HTTP query. + */ + httpClient: function(kwArgs) { + var _this = this; + + // If need to defeat cache, toss in a date param on GET. + if (this.DEFEAT_CACHE && ( kwArgs.method == "GET" || kwArgs.method == "HEAD" ) ) { + if (!kwArgs.params) kwArgs.params = {}; + kwArgs.params["___"] = new Date().getTime(); + } + + // Prepare the query string and URL for this request. + var qs = (kwArgs.params) ? '?'+queryString(kwArgs.params) : ''; + var url = this.URL + kwArgs.resource + qs; + var hdrs = {}; + + // Handle Content-Type header + if (!kwArgs.content_type && kwArgs.method == 'PUT') + kwArgs.content_type = 'text/plain'; + if (kwArgs.content_type) + hdrs['Content-Type'] = kwArgs.content_type; + else + kwArgs.content_type = ''; + + // Set the timestamp for this request. + var http_date = this.httpDate(); + hdrs['Date'] = http_date; + + var content_MD5 = ''; + /* + // TODO: Fix this Content-MD5 stuff. + if (kwArgs.content && kwArgs.content.hashMD5) { + content_MD5 = kwArgs.content.hashMD5(); + hdrs['Content-MD5'] = content_MD5; + } + */ + + // Handle the ACL parameter + var acl_header_to_sign = ''; + if (kwArgs.acl) { + hdrs['x-amz-acl'] = kwArgs.acl; + acl_header_to_sign = "x-amz-acl:"+kwArgs.acl+"\n"; + } + + // Handle the metadata headers + var meta_to_sign = ''; + if (kwArgs.meta) { + for (var k in kwArgs.meta) { + hdrs['x-amz-meta-'+k] = kwArgs.meta[k]; + meta_to_sign += "x-amz-meta-"+k+":"+kwArgs.meta[k]+"\n"; + } + } + + // Only perform authentication if non-anonymous and credentials available + if (kwArgs['anonymous'] != true && this.KEY_ID && this.SECRET_KEY) { + + // Build the string to sign for authentication. + var s; + s = kwArgs.method + "\n"; + s += content_MD5 + "\n"; + s += kwArgs.content_type + "\n"; + s += http_date + "\n"; + s += acl_header_to_sign; + s += meta_to_sign; + s += kwArgs.resource; + + // Sign the string with our SECRET_KEY. + var signature = this.hmacSHA1(s, this.SECRET_KEY); + hdrs['Authorization'] = "AWS "+this.KEY_ID+":"+signature; + } + + // Perform the HTTP request. + var req = getXMLHttpRequest();alert('test'); + req.open(kwArgs.method, url, true); + for (var k in hdrs) req.setRequestHeader(k, hdrs[k]); + req.onreadystatechange = function() { + if (req.readyState == 4) { + + // Pre-digest the XML if needed. + var obj = null; + if (req.responseXML && kwArgs.parseXML != false) + obj = _this.xmlToObj(req.responseXML, kwArgs.force_lists); + + // Stash away the last request details, if DEBUG active. + if (_this.DEBUG) { + window._lastreq = req; + window._lastobj = obj; + } + + // Dispatch to appropriate handler callback + if ( (req.status >= 400 || (obj && obj.Error) ) && kwArgs.error) + return kwArgs.error(req, obj); + else + return kwArgs.load(req, obj); + + } + } + req.send(kwArgs.content); + return req; + }, + + /** + Turn a simple structure of nested XML elements into a + JavaScript object. + + TODO: Handle attributes? + */ + xmlToObj: function(parent, force_lists, path) { + var obj = {}; + var cdata = ''; + var is_struct = false; + + for(var i=0,node; node=parent.childNodes[i]; i++) { + if (3 == node.nodeType) { + cdata += node.nodeValue; + } else { + is_struct = true; + var name = node.nodeName; + var cpath = (path) ? path+'.'+name : name; + var val = arguments.callee(node, force_lists, cpath); + + if (!obj[name]) { + var do_force_list = false; + if (force_lists) { + for (var j=0,item; item=force_lists[j]; j++) { + if (item == cpath) { + do_force_list=true; break; + } + } + } + obj[name] = (do_force_list) ? [ val ] : val; + } else if (obj[name].length) { + // This is a list of values to append this one to the end. + obj[name].push(val); + } else { + // Has been a single value up till now, so convert to list. + obj[name] = [ obj[name], val ]; + } + } + } + + // If any subnodes were found, return a struct - else return cdata. + return (is_struct) ? obj : cdata; + }, + + /** + Abstract HMAC SHA1 signature calculation. + */ + hmacSHA1: function(data, secret) { + // TODO: Alternate Dojo implementation? + return b64_hmac_sha1(secret, data)+'='; + }, + + /** + Return a date formatted appropriately for HTTP Date header. + Inspired by: http://www.svendtofte.com/code/date_format/ + + TODO: Should some/all of this go into common.js? + */ + httpDate: function(d) { + // Use now as default date/time. + if (!d) d = new Date(); + + // Date abbreviations. + var daysShort = ["Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"]; + var monthsShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + // See: http://www.quirksmode.org/js/introdate.html#sol + function takeYear(theDate) { + var x = theDate.getYear(); + var y = x % 100; + y += (y < 38) ? 2000 : 1900; + return y; + }; + + // Number padding function + function zeropad(num, sz) { + return ( (sz - (""+num).length) > 0 ) ? + arguments.callee("0"+num, sz) : num; + }; + + function gmtTZ(d) { + // Difference to Greenwich time (GMT) in hours + var os = Math.abs(d.getTimezoneOffset()); + var h = ""+Math.floor(os/60); + var m = ""+(os%60); + h.length == 1? h = "0"+h:1; + m.length == 1? m = "0"+m:1; + return d.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m; + }; + + var s; + s = daysShort[d.getDay()] + ", "; + s += d.getDate() + " "; + s += monthsShort[d.getMonth()] + " "; + s += takeYear(d) + " "; + s += zeropad(d.getHours(), 2) + ":"; + s += zeropad(d.getMinutes(), 2) + ":"; + s += zeropad(d.getSeconds(), 2) + " "; + s += gmtTZ(d); + + return s; + }, + + /* Help protect against errant end-commas */ + EOF: null + +}; + +if (!window['queryString']) { + // Swiped from MochiKit + function queryString(params) { + var l = []; + for (k in params) + l.push(k+'='+encodeURIComponent(params[k])) + return l.join("&"); + } +} + +if (!window['getXMLHttpRequest']) { + // Shamelessly swiped from MochiKit/Async.js + function getXMLHttpRequest() { + var self = arguments.callee; + if (!self.XMLHttpRequest) { + var tryThese = [ + function () { return new XMLHttpRequest(); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP'); }, + function () { return new ActiveXObject('Microsoft.XMLHTTP'); }, + function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); }, + function () { return null; } + ]; + for (var i = 0; i < tryThese.length; i++) { + var func = tryThese[i]; + try { + self.XMLHttpRequest = func; + return func(); + } catch (e) { + // pass + } + } + } + return self.XMLHttpRequest(); + } +} + diff --git a/tantan/wordpress-s3/js/common.js b/tantan/wordpress-s3/js/common.js new file mode 100644 index 0000000..e51c716 --- /dev/null +++ b/tantan/wordpress-s3/js/common.js @@ -0,0 +1,156 @@ +/** + Common can't-live-without stuff from MochiKit, Dojo, etal. + Share and Enjoy +*/ + +if (!window.Node) { + window.Node = { + ELEMENT_NODE : 1, + ATTRIBUTE_NODE : 2, + TEXT_NODE : 3, + CDATA_SECTION_NODE : 4, + ENTITY_REFERENCE_NODE : 5, + ENTITY_NODE : 6, + PROCESSING_INSTRUCTIONS_NODE : 7, + COMMENT_NODE : 8, + DOCUMENT_NODE : 9, + DOCUMENT_TYPE_NODE : 10, + DOCUMENT_FRAGMENT_NODE : 11, + NOTATION_NODE : 12 + } +} + +if (!window['addLoadEvent']) { + // See: http://simon.incutio.com/archive/2004/05/26/addLoadEvent + function addLoadEvent(func) { + var oldonload = window.onload; + if (typeof window.onload != 'function') { + window.onload = func; + } else { + window.onload = function() { + oldonload(); + func(); + } + } + } +} + +if (!window['$']) { + // Swiped from MochiKit / Prototype + function $(id) { return document.getElementById(id); } +} + +/** + Get the parsed XML response from an HTTP request, even if + the server claimed a non-XML mimetype. +*/ +function getResponseXML(rv) { + if (rv.responseXML) return rv.responseXML; + else return loadXML(rv.responseText); +} + +function loadXML(data) { + if (DOMParser) return (new DOMParser()).parseFromString(data, 'text/xml'); + if (ActiveXObject) return (new ActiveXObject("microsoft.XMLDOM")).loadXML(data); + return null; +} + +function clearList(lid) { + $(lid).options.length = 0; +} + +function addToList(lid, label, key) { + var list = $(lid); + list.options[list.options.length] = new Option(label, key); +} + +function setList(lid, items) { + var list = $(lid); + this.clearList(lid); + for(i=0;i 11? "pm" : "am"; + } + function A() { + // Uppercase Ante meridiem and Post meridiem + return self.getHours() > 11? "PM" : "AM"; + } + + function B(){ + // Swatch internet time. code simply grabbed from ppk, + // since I was feeling lazy: + // http://www.xs4all.nl/~ppk/js/beat.html + var off = (self.getTimezoneOffset() + 60)*60; + var theSeconds = (self.getHours() * 3600) + + (self.getMinutes() * 60) + + self.getSeconds() + off; + var beat = Math.floor(theSeconds/86.4); + if (beat > 1000) beat -= 1000; + if (beat < 0) beat += 1000; + if ((""+beat).length == 1) beat = "00"+beat; + if ((""+beat).length == 2) beat = "0"+beat; + return beat; + } + + function d() { + // Day of the month, 2 digits with leading zeros + return new String(self.getDate()).length == 1? + "0"+self.getDate() : self.getDate(); + } + function D() { + // A textual representation of a day, three letters + return daysShort[self.getDay()]; + } + function F() { + // A full textual representation of a month + return monthsLong[self.getMonth()]; + } + function g() { + // 12-hour format of an hour without leading zeros + return self.getHours() > 12? self.getHours()-12 : self.getHours(); + } + function G() { + // 24-hour format of an hour without leading zeros + return self.getHours(); + } + function h() { + // 12-hour format of an hour with leading zeros + if (self.getHours() > 12) { + var s = new String(self.getHours()-12); + return s.length == 1? + "0"+ (self.getHours()-12) : self.getHours()-12; + } else { + var s = new String(self.getHours()); + return s.length == 1? + "0"+self.getHours() : self.getHours(); + } + } + function H() { + // 24-hour format of an hour with leading zeros + return new String(self.getHours()).length == 1? + "0"+self.getHours() : self.getHours(); + } + function i() { + // Minutes with leading zeros + return new String(self.getMinutes()).length == 1? + "0"+self.getMinutes() : self.getMinutes(); + } + function j() { + // Day of the month without leading zeros + return self.getDate(); + } + function l() { + // A full textual representation of the day of the week + return daysLong[self.getDay()]; + } + function L() { + // leap year or not. 1 if leap year, 0 if not. + // the logic should match iso's 8601 standard. + var y_ = Y(); + if ( + (y_ % 4 == 0 && y_ % 100 != 0) || + (y_ % 4 == 0 && y_ % 100 == 0 && y_ % 400 == 0) + ) { + return 1; + } else { + return 0; + } + } + function m() { + // Numeric representation of a month, with leading zeros + return self.getMonth() < 9? + "0"+(self.getMonth()+1) : + self.getMonth()+1; + } + function M() { + // A short textual representation of a month, three letters + return monthsShort[self.getMonth()]; + } + function n() { + // Numeric representation of a month, without leading zeros + return self.getMonth()+1; + } + function O() { + // Difference to Greenwich time (GMT) in hours + var os = Math.abs(self.getTimezoneOffset()); + var h = ""+Math.floor(os/60); + var m = ""+(os%60); + h.length == 1? h = "0"+h:1; + m.length == 1? m = "0"+m:1; + return self.getTimezoneOffset() < 0 ? "+"+h+m : "-"+h+m; + } + function r() { + // RFC 822 formatted date + var r; // result + // Thu , 21 Dec 2000 + r = D() + ", " + j() + " " + M() + " " + Y() + + // 16 : 01 : 07 +0200 + " " + H() + ":" + i() + ":" + s() + " " + O(); + return r; + } + function S() { + // English ordinal suffix for the day of the month, 2 characters + return daysSuffix[self.getDate()-1]; + } + function s() { + // Seconds, with leading zeros + return new String(self.getSeconds()).length == 1? + "0"+self.getSeconds() : self.getSeconds(); + } + function t() { + + // thanks to Matt Bannon for some much needed code-fixes here! + var daysinmonths = [null,31,28,31,30,31,30,31,31,30,31,30,31]; + if (L()==1 && n()==2) return 29; // leap day + return daysinmonths[n()]; + } + function U() { + // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + return Math.round(self.getTime()/1000); + } + function W() { + // Weeknumber, as per ISO specification: + // http://www.cl.cam.ac.uk/~mgk25/iso-time.html + + // if the day is three days before newyears eve, + // there's a chance it's "week 1" of next year. + // here we check for that. + var beforeNY = 364+L() - z(); + var afterNY = z(); + var weekday = w()!=0?w()-1:6; // makes sunday (0), into 6. + if (beforeNY <= 2 && weekday <= 2-beforeNY) { + return 1; + } + // similarly, if the day is within threedays of newyears + // there's a chance it belongs in the old year. + var ny = new Date("January 1 " + Y() + " 00:00:00"); + var nyDay = ny.getDay()!=0?ny.getDay()-1:6; + if ( + (afterNY <= 2) && + (nyDay >=4) && + (afterNY >= (6-nyDay)) + ) { + // Since I'm not sure we can just always return 53, + // i call the function here again, using the last day + // of the previous year, as the date, and then just + // return that week. + var prevNY = new Date("December 31 " + (Y()-1) + " 00:00:00"); + return prevNY.formatDate("W"); + } + + // week 1, is the week that has the first thursday in it. + // note that this value is not zero index. + if (nyDay <= 3) { + // first day of the year fell on a thursday, or earlier. + return 1 + Math.floor( ( z() + nyDay ) / 7 ); + } else { + // first day of the year fell on a friday, or later. + return 1 + Math.floor( ( z() - ( 7 - nyDay ) ) / 7 ); + } + } + function w() { + // Numeric representation of the day of the week + return self.getDay(); + } + + function Y() { + // A full numeric representation of a year, 4 digits + + // we first check, if getFullYear is supported. if it + // is, we just use that. ppks code is nice, but wont + // work with dates outside 1900-2038, or something like that + if (self.getFullYear) { + var newDate = new Date("January 1 2001 00:00:00 +0000"); + var x = newDate .getFullYear(); + if (x == 2001) { + // i trust the method now + return self.getFullYear(); + } + } + // else, do this: + // codes thanks to ppk: + // http://www.xs4all.nl/~ppk/js/introdate.html + var x = self.getYear(); + var y = x % 100; + y += (y < 38) ? 2000 : 1900; + return y; + } + function y() { + // A two-digit representation of a year + var y = Y()+""; + return y.substring(y.length-2,y.length); + } + function z() { + // The day of the year, zero indexed! 0 through 366 + var t = new Date("January 1 " + Y() + " 00:00:00"); + var diff = self.getTime() - t.getTime(); + return Math.floor(diff/1000/60/60/24); + } + + var self = this; + if (time) { + // save time + var prevTime = self.getTime(); + self.setTime(time); + } + + var ia = input.split(""); + var ij = 0; + while (ia[ij]) { + if (ia[ij] == "\\") { + // this is our way of allowing users to escape stuff + ia.splice(ij,1); + } else { + if (switches.exists(ia[ij])) { + ia[ij] = eval(ia[ij] + "()"); + } + } + ij++; + } + // reset time, back to what it was + if (prevTime) { + self.setTime(prevTime); + } + return ia.join(""); +} diff --git a/tantan/wordpress-s3/js/play.js b/tantan/wordpress-s3/js/play.js new file mode 100644 index 0000000..806ce4a --- /dev/null +++ b/tantan/wordpress-s3/js/play.js @@ -0,0 +1,246 @@ +/** + Play around with S3Ajax! + + TODO: Remove Dojo dependency +*/ + +if (window.dojo) { + // dojo.setModulePrefix("dojo", "js/dojo"); + // dojo.require("dojo.io.*"); + // dojo.require("dojo.crypto.SHA1"); + dojo.require("dojo.storage.*"); +} + +Play = { + + /** + Initialize the play app. + */ + init: function() { + var _this = this; + + S3Ajax.DEBUG = true; + + // HACK: Wait for the storage flash to become available. + // TODO: Look for an official Dojo event to make this happen. + if (window.dojo) { + this._storage_wait = setInterval(function() { + if ($('dojo-storeContainer')) { + clearInterval(_this._storage_wait); + _this._storage_wait = null; + _this.recall(); + } + }, 100); + } + }, + + /** + Make an attempt to recall S3 credentials. + */ + recall: function() { + if (window.dojo && dojo.storage.get('key_id', '/S3Ajax')) { + S3Ajax.KEY_ID = dojo.storage.get('key_id', '/S3Ajax'); + S3Ajax.SECRET_KEY = dojo.storage.get('secret_key', '/S3Ajax'); + $('key_id').value = S3Ajax.KEY_ID; + $('update_msg').innerHTML = 'Credentials fetched from local storage: '+(new Date()); + + // this.listbuckets(); + // this.list(); + } + }, + + /** + Accept new credentials from the form in the page. + */ + login: function() { + S3Ajax.KEY_ID = $('key_id').value; + S3Ajax.SECRET_KEY = $('secret_key').value; + + // Try to stash the credentials away for next time. + if (window.dojo) { + dojo.storage.set('key_id', S3Ajax.KEY_ID, '/S3Ajax'); + dojo.storage.set('secret_key', S3Ajax.SECRET_KEY, '/S3Ajax'); + } + + $('update_msg').innerHTML = 'Last updated: '+(new Date()); + }, + + /** + Download the specified resource. + */ + download: function() { + var bucket = $('list_bucket').value; + var key = $('key').value; + + $('content').value = "Loading..."; + + S3Ajax.get(bucket, key, + function(req, content) { + $('content').value = content; + $('xfer_msg').innerHTML = "Download succeeded at "+(new Date()); + }, + function(req, objc) { + $('xfer_msg').innerHTML = "Download failed at "+(new Date()); + } + ); + }, + + /** + Upload the specified resource. + */ + upload: function() { + var bucket = $('list_bucket').value; + var key = $('key').value; + var content = $('content').value; + + S3Ajax.put(bucket, key, content, + function(req) { + $('xfer_msg').innerHTML = "Upload succeeded at "+(new Date()); + }, + function(req, obj) { + $('xfer_msg').innerHTML = "Upload failed at "+(new Date()); + } + ); + + /* + S3Ajax.put(bucket, key, content, + { + content_type: "text/plain", + meta: {'posted-by':'S3Ajax'}, + acl: "public-read", + }, + function(req) { + $('xfer_msg').innerHTML = "Upload succeeded at "+(new Date()); + }, + function(req, obj) { + $('xfer_msg').innerHTML = "Upload failed at "+(new Date()); + } + ); + */ + }, + + /** + List available buckets + */ + listbuckets: function() { + var _this = this; + + setList('buckets_list',['Loading...','']); + + S3Ajax.listBuckets( + function(req, obj) { + clearList('buckets_list'); + if (obj.ListAllMyBucketsResult) { + var buckets = obj.ListAllMyBucketsResult.Buckets.Bucket; + for (var i=0, bucket; bucket=buckets[i]; i++) { + addToList( + 'buckets_list', + bucket.Name + ' ['+bucket.CreationDate+']', + bucket.Name + ); + } + } + keys_list = null; + }, + function(req) { + setList('buckets_list', ["Buckets list failed at "+(new Date()),'']); + } + ); + + }, + + /** + Change the current bucket to one selected in list. + */ + selectbucket: function() { + var sel = getSelected('buckets_list'); + if (sel.length) $('list_bucket').value = sel[0]; + }, + + /** + */ + deletebucket: function() { + var _this = this; + var sel = getSelected('buckets_list'); + if (!sel.length) return; + + setList('buckets_list',['Deleting...','']); + S3Ajax.deleteBucket(sel[0], function() { + _this.listbuckets(); + }); + }, + + /** + */ + createbucket: function() { + var _this = this; + var bucket = $('list_bucket').value; + + setList('buckets_list',['Creating...','']); + S3Ajax.createBucket(bucket, function() { + _this.listbuckets(); + }); + }, + + /** + List a bucket's contents. + */ + list: function() { + var _this = this; + setList('keys_list', ['Loading...','']); + + var bucket = $('list_bucket').value; + + var params = {}; + if ($('list_prefix').value) params['prefix'] = $('list_prefix').value; + if ($('list_maxkeys').value) params['max-keys'] = $('list_maxkeys').value; + if ($('list_marker').value) params['marker'] = $('list_marker').value; + + S3Ajax.listKeys(bucket, params, + function(req, obj) { + clearList('keys_list'); + var contents = obj.ListBucketResult.Contents; + for (var i=0, item; item=contents[i]; i++) { + addToList( + 'keys_list', + /*item.LastModified + ' ' +*/ item.Key + ' (' + item.Size + ')', + item.Key + ); + } + }, + function(req, obj) { + setList('keys_list', ["Keys list failed at "+(new Date()),'']); + } + ); + }, + + /** + Download the selected key. + */ + downloadSelectedKey: function() { + var sel = getSelected('keys_list'); + if (sel.length) { + $('key').value = sel[0]; + this.download(); + } + }, + + /** + Delete seleted keys. + */ + deleteSelectedKeys: function() { + var _this = this; + var sel_keys = getSelected('keys_list'); + + if (!window.confirm("Delete " + sel_keys.length + " selected items?")) return; + + S3Ajax.deleteKeys($('list_bucket').value, sel_keys, + function(key) { /*logFire("Deleted "+key);*/ }, + function(req, obj) { /*logFire("Deleted all");*/ _this.list(); } + ); + }, + + /* Help protect against errant end-commas */ + EOF: null +} +addLoadEvent(function(){ Play.init() }); diff --git a/tantan/wordpress-s3/js/sha1.js b/tantan/wordpress-s3/js/sha1.js new file mode 100644 index 0000000..11014f8 --- /dev/null +++ b/tantan/wordpress-s3/js/sha1.js @@ -0,0 +1,202 @@ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} +function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} +function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for(var j = 0; j < 80; j++) + { + if(j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); + +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if(t < 20) return (b & c) | ((~b) & d); + if(t < 40) return b ^ c ^ d; + if(t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); + return str; +} + +/* + * Convert an array of big-endian words to a hex string. + */ +function binb2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} diff --git a/tantan/wordpress-s3/js/wiki-config.js b/tantan/wordpress-s3/js/wiki-config.js new file mode 100644 index 0000000..0504fe3 --- /dev/null +++ b/tantan/wordpress-s3/js/wiki-config.js @@ -0,0 +1,9 @@ +/** + S3AjaxWiki site-specific configuration +*/ +with (S3AjaxWiki) { + DEBUG = true; + ANONYMOUS = true; + BUCKET = 's3wiki'; + DEFAULT_ACL = 'public-read-write'; +} diff --git a/tantan/wordpress-s3/js/wiki.js b/tantan/wordpress-s3/js/wiki.js new file mode 100644 index 0000000..e6f609b --- /dev/null +++ b/tantan/wordpress-s3/js/wiki.js @@ -0,0 +1,573 @@ +/** + S3AjaxWiki v0.1 - A client-side wiki for S3 + + http://decafbad.com/trac/wiki/S3Ajax + l.m.orchard@pobox.com + Share and Enjoy. +*/ + +/* +// TODO: Find a way to minimize the number of JS includes on wiki pages. + +function $import() { + for (var i=0, path; path=arguments[i]; i++) { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = path; + document.getElementsByTagName('head')[0].appendChild(script); + } +} + +$import("js/firebug.js", "js/dojo.js", "js/sha1.js", "js/S3Ajax.js", + "js/wiky.js", "js/wiky.s3wiki.js"); +*/ + +if (window.dojo) { + dojo.require("dojo.storage.*"); +} + +S3AjaxWiki = { + + DEBUG: true, + + ANONYMOUS: false, + BUCKET: 'decafbad', + TEMPLATE_KEY: 'wiki-template.html', + KEY_PREFIX: 'wiki/', + DEFAULT_ACL: 'public-read', + DEFAULT_TYPE: 'text/html', + DEFAULT_META: {'posted-by':'S3AjaxWiki'}, + + /* + These are the IDs of elements injected as a part of the wiki + editing framework, and should not end up in serialized pages. + */ + IGNORED_ELEMENT_IDS: [ + 'content_edit', 'credentials', 'toc', 'page_options', + 'xfer_msg', 'dojo-storeContainer' + ], + + /* + These are attribute names that cause problems in MSIE. + We'll ignore them during page serialization. + TODO: Make this a per-element hash? Need more sophistication here. + */ + IGNORED_ATTRIBUTE_NAMES: [ + 'hideFocus', 'contentEditable', 'disabled', 'tabIndex', + 'bottomMargin', 'noWrap', 'leftMargin', 'topMargin', + 'rightMargin', 'defer' + ], + + /** + Initialize the wiki app + */ + init: function() { + // Debounce init calls. + if (arguments.callee.done) return; + arguments.callee.done = true; + + S3Ajax.DEBUG = this.DEBUG; + S3Ajax.DEFEAT_CACHE = true; + + forEach([ 'FIELDSET' ], function(n) { + window[n] = createDOMFunc(n); + }); + + this.injectEditingFramework(); + if (!this.ANONYMOUS) this.autoLoadCredentials(); + else this.onLogin(); + }, + + /** + Try to load stored credentials when using dojo.storage. + TODO: Scoop out dojo.storage to work separately from dojo proper. + */ + autoLoadCredentials: function() { + var _this = this; + + $('credentials_msg').innerHTML = 'No credentials.'; + this.hideCredentials(); + + // HACK: Wait for the storage flash to become available. + // TODO: Look for an official Dojo event to make this happen. + this.hideCredentials(); + if (window.dojo) { + this._storage_wait = setInterval(function() { + if ($('dojo-storeContainer')) { + clearInterval(_this._storage_wait); + _this._storage_wait = null; + try { + _this.recall(); + } catch(e) { + } + } + }, 100); + } else { + this.onLogin(); + } + }, + + /** + Some things to perform upon (un)successful login. + */ + onLogin: function() { + this.scanWikiWords(); + this.refreshPageList(); + }, + + /** + Scan wiki word links on the page, with the intent to discover + which actually exist. + */ + scanWikiWords: function() { + // Empty the list of words to scan + this._scan_words = {}; + + // Scan through wiki word links and gather unique list. + var links = document.getElementsByTagName('a'); + for (var i=0, link; link=links[i]; i++) { + if ('wikiword' == link.className) { + this._scan_words[link.innerHTML] = true; + } + } + + // Collect a plain list of unique words. + var words = []; + for (var word in this._scan_words) { words.push(word); } + this._doWordScan(words); + }, + + /** + Perform S3 HEAD requests to determine the existence of a given + list of wiki words. Collect the results, make a note of which + words exist or not. + */ + _doWordScan: function(words) { + var _this = this; + + // If the words list is empty, stop. + if (!words.length) { + return this.updateWikiWords(); + } + + // Search for existence of a word, marking it appropriately. + var word = words.shift(); + S3Ajax.head(this.BUCKET, this.KEY_PREFIX + word, + function() { + _this.markWikiWord(word, true); + _this._doWordScan(words); + }, + function() { + _this.markWikiWord(word, false); + _this._doWordScan(words); + } + ); + }, + + /** + Mark a given wiki word as found, or not. + */ + markWikiWord: function(word, found) { + this._scan_words[word] = found; + // HACK: Doing this for every found word might be too often. + this.updateWikiWords(); + }, + + /** + Scan through wiki word links on the page. For every link not + found in S3, wire it up as a new page creation link. + */ + updateWikiWords: function() { + var links = document.getElementsByTagName('a'); + for (var i=0, link; link=links[i]; i++) { + if ('wikiword' == link.className) { + var word = link.innerHTML; + if (!this._scan_words[word]) + this.wireUpNewWord(link, word) + } + } + }, + + /** + Wire up a wiki word link as a new page creation link, changing + its CSS style and hooking its click event up to createNewPage(). + */ + wireUpNewWord: function(link, word) { + var _this = this; + link.className = 'newword'; + link.onclick = function() { + _this.createNewPage(word); return false; + } + }, + + /** + For a given wiki word, create a new page. Load up the wiki page + template, change the header and body titles, save to S3 as a new + key, redirect browser to the new page. + + TODO: Simply redirect if the page already exists? + */ + createNewPage: function(word) { + // Do not create unnamed pages. + if (!word) return; + + var _this = this; + S3Ajax.get(_this.BUCKET, _this.TEMPLATE_KEY, + function(req, data) { + var xml = getResponseXML(req); + + // Update document head title + var page_title = xml.getElementsByTagName('title')[0]; + page_title.firstChild.nodeValue = word; + + // Update page header title. + /* + // HACK: Should getElementById work here? Seems not to. + var head_title = xml.getElementById('page_title'); + head_title.nodeValue = word; + */ + var h1s = xml.getElementsByTagName('h1'); + for (var i=0,h1; h1=h1s[i]; i++) + if ('page_title' == h1.getAttribute('id')) + h1.firstChild.nodeValue = word; + + // Get serialized HTML content from the page DOM + var data = _this.fromDOMtoHTML(xml); + + // Upload to S3, redirect if successful. + S3Ajax.put(_this.BUCKET, _this.KEY_PREFIX + word, data, + { + content_type: _this.DEFAULT_TYPE, + acl: _this.DEFAULT_ACL, + meta: _this.DEFAULT_META + }, + function(req) { + location.href = word; + }, + function(req, obj) { + $('xfer_msg').innerHTML = "Upload failed at "+(new Date()); + } + ); + }, + function(req, data) { + // TODO: Do something useful on not finding the template. + $('xfer_msg').innerHTML = "Template fetch failed at "+(new Date()); + } + ); + }, + + /** + Refresh the list of available pages. + */ + refreshPageList: function() { + var _this = this; + setList('page_list', ['Loading...','']); + S3Ajax.listKeys( + this.BUCKET, { prefix:this.KEY_PREFIX }, + function(req, obj) { + clearList('page_list'); + + // Sort in reverse-chronological change order. + var contents = obj.ListBucketResult.Contents; + contents.sort(function(a,b) { + if (b.LastModified > a.LastModified) return 1; + if (b.LastModified < a.LastModified) return -1; + return 0; + }); + + for (var i=0, item; item=contents[i]; i++) { + var key = item.Key; + var title = key.substr(_this.KEY_PREFIX.length) + addToList('page_list', title, key); + } + }, + function(req, obj) { + setList('page_list', ['Failed to load list of pages.','']); + } + ); + + + }, + + /** + Navigate to a new wiki page. + */ + selectPage: function(key) { + var _this = this; + + if (!key) key = getSelected('page_list')[0]; + + // Page title is key, sans prefix + var title = (key.indexOf(_this.KEY_PREFIX) == 0) ? + key.substr(_this.KEY_PREFIX.length) : key; + + location.href = title; + }, + + /** + */ + editPage: function() { + var content = $('content').innerHTML; + $('editor').value = Wiky.toWiki(content); + this.showEditor(); + }, + + /** + */ + cancelEdit: function() { + this.hideEditor(); + }, + + /** + */ + previewPage: function() { + var content = $('editor').value; + $('content').innerHTML = Wiky.toHtml(content); + this.scanWikiWords(); + this.hideEditor(); + }, + + /** + */ + updatePage: function(key) { + var _this = this; + if (!key) key = this.KEY_PREFIX + $('page_title').innerHTML; + // if (!key) key = getSelected('page_list')[0]; + + // Convert the wiki text to HTML and replace in page. + $('content').innerHTML = Wiky.toHtml($('editor').value); + this.hideEditor(); + + // Get serialized HTML content from the page DOM + var content = _this.fromDOMtoHTML(); + + // Upload to S3, redirect if successful. + S3Ajax.put(_this.BUCKET, key, content, + { + content_type: _this.DEFAULT_TYPE, + acl: _this.DEFAULT_ACL, + meta: {'posted-by':'S3AjaxWiki'} + }, + function(req) { + _this.scanWikiWords(); + $('xfer_msg').innerHTML = ""; // "Upload succeeded at "+(new Date()); + }, + function(req, obj) { + $('xfer_msg').innerHTML = "Upload failed at "+(new Date()); + } + ); + }, + + /** + Make an attempt to recall S3 credentials. + */ + recall: function() { + if (window.dojo) { + if (dojo.storage && dojo.storage.get && dojo.storage.get('key_id', '/S3Ajax')) { + S3Ajax.KEY_ID = dojo.storage.get('key_id', '/S3Ajax'); + S3Ajax.SECRET_KEY = dojo.storage.get('secret_key', '/S3Ajax'); + $('key_id').value = S3Ajax.KEY_ID; + $('credentials_msg').innerHTML = 'Credentials restored.'; + this.hideCredentials(); + this.onLogin(); + } + } + }, + + /** + Accept new credentials from the form in the page. + */ + login: function() { + S3Ajax.KEY_ID = $('key_id').value; + S3Ajax.SECRET_KEY = $('secret_key').value; + + // Try to stash the credentials away for next time. + if (window.dojo) { + dojo.storage.set('key_id', S3Ajax.KEY_ID, '/S3Ajax'); + dojo.storage.set('secret_key', S3Ajax.SECRET_KEY, '/S3Ajax'); + } + + $('credentials_msg').innerHTML = 'Credentials supplied.'; + this.hideCredentials(); + this.onLogin(); + }, + + showEditor: function() { + $('content_edit').style.display = "block"; + $('content').style.display = "none"; + }, + + hideEditor: function() { + $('content_edit').style.display = "none"; + $('content').style.display = ""; + }, + + showCredentials: function() { + $('credentials_fields').style.display = "block"; + }, + + hideCredentials: function() { + $('credentials_fields').style.display = "none"; + }, + + /** + Serialize a given DOM to HTML source. If no node given, assume + the current page should be serialized. Note that this makes efforts + using IGNORED_ELEMENT_IDS to avoid including the injected editor framework. + + TODO: Steal more ideas from MochiKit.DOM.emitHTML() + */ + fromDOMtoHTML: function(node) { + + if (!node) node = document; + + switch (node.nodeType) { + + case Node.DOCUMENT_NODE: + // HACK: Look for the first in a document node. + return this.fromDOMtoHTML(node.getElementsByTagName('html')[0]); + + case Node.TEXT_NODE: + return node.nodeValue; + + case Node.ELEMENT_NODE: + var node_name = node.nodeName.toLowerCase(); + + // Skip elements with ignored IDs + for (var j=0, skip_id; skip_id=this.IGNORED_ELEMENT_IDS[j]; j++) { + if (skip_id == node.id) { return ''; } + } + + // HACK: Firefox kept dropping in Firebug stylesheets + if ('link'==node_name && /chrome:/.test(node.getAttribute('href'))) + return ''; + + // Start serializing the current node. + var out = '<' + node_name; + + // Serialize the attributes of the node. + for (var i=0, attr; attr=node.attributes[i]; i++) { + var name = attr.name; + + // Skip ignored attributes + var skip = false; + for (var j=0, skip_name; skip_name=this.IGNORED_ATTRIBUTE_NAMES[j]; j++) { + if (skip_name == name) { skip=true; break; } + } + if (skip) continue; + + var value = attr.value; + if (value && value!=null && value!="null") + out += ' '+name+'="'+value+'"'; + } + + // Serialize all child nodes found. + var sub_out = ''; + + if ( ('script'==node_name || 'title'==node_name) && node.innerHTML) { + // HACK: MSIE gives me troubles in scooping out these tags contents. + sub_out = ''+node.innerHTML; + } else { + for (var i=0, child; child=node.childNodes[i]; i++) { + sub_out += this.fromDOMtoHTML(child); + } + } + + // Finalize the current node. + if (sub_out || 'script'==node_name || 'title'==node_name) + return out + '>' + sub_out + ''; + else + return out + '/>'; + + default: + return ''; + /* + // TODO: Err... Is this even what I should be doing here? + try { + return node.parentNode.innerHTML; + } catch (e) { + return ''; + } + */ + } + + }, + + /** + Build and inject the DOM structures necessary for editing + wiki pages. The events wired up are a little bit of a hack. + */ + injectEditingFramework: function() { + + var body = document.getElementsByTagName('body')[0]; + + appendChildNodes(body, [ + + // Build the content editing form. + FORM({"id":"content_edit", "onsubmit":"return false"}, + TEXTAREA({"id":"editor", "name":"editor", "cols":"80", "rows":"20"}), + BR(), + FIELDSET({}, + BUTTON({"id":"preview", "onclick":"S3AjaxWiki.previewPage(); return false"}, "Preview Changes"), + BUTTON({"id":"submit", "onclick":"S3AjaxWiki.updatePage(); return false"}, "Save Changes"), + BUTTON({"id":"cancel", "onclick":"S3AjaxWiki.cancelEdit(); return false"}, "Cancel") + ) + ), + + // Build the login credentials form. + (this.ANONYMOUS) ? '' : + FORM({"id":"credentials", "onsubmit":"S3AjaxWiki.login(); return false;"}, + DIV({"id":"cals_status"}, + SPAN({"id":"credentials_msg"}), + " ", + A({"href":"#", "onclick":"S3AjaxWiki.showCredentials(); return false"}, "Login.") + ), + FIELDSET({"id":"credentials_fields"}, + + LEGEND({}, "Credentials"), + + LABEL({"for":"key_id"}, "AWSAccessKey"), BR(), + INPUT({"type":"text", "size":"25", "id":"key_id", "name":"key_id"}), BR(), + + LABEL({"for":"secret_key"}, "SecretAccessKey"), BR(), + INPUT({"type":"password", "size":"25", "id":"secret_key", "name":"secret_key"}), BR(), + + BUTTON({"id":"store", "onclick":"S3AjaxWiki.login(); return false"}, " Store "), + BUTTON({"id":"recall", "onclick":"S3AjaxWiki.recall(); return false"}, " Recall "), + BUTTON({"id":"cancel", "onclick":"S3AjaxWiki.hideCredentials(); return false"}, " Cancel ") + ) + ), + + // Build the page list box. + FORM({'id':'toc', 'onsubmit':'return false'}, + "Recent Changes: ", /* BR(), */ + SELECT({'id':'page_list', 'name':'page_list', 'onchange':'S3AjaxWiki.selectPage(); return false'}), + BUTTON({'id':'get_pages', 'onclick':'S3AjaxWiki.selectPage(); return false'}, 'Go' ) /*, + BUTTON({'id':'list_pages', 'onclick':'S3AjaxWiki.refreshPageList(); return false'}, 'Refresh' ) + */ + ), + + // Set up the page options div + FORM({'id':'page_options', 'onsubmit':'return false'}, + BUTTON({'onclick':'S3AjaxWiki.editPage(); return false;'}, "Edit Page"), + " | ", + INPUT({'type':'text', 'size':'15', 'id':'new_page_name', 'name':'new_page_name', 'value':'NewPage'}), + BUTTON({'onclick':'S3AjaxWiki.createNewPage($("new_page_name").value); return false;'}, "Create Page") + ), + + // Inject the xfer message div. + SPAN({'id':'xfer_msg'}) + + ]); + + }, + + /* Help protect against errant end-commas */ + EOF: null +}; + +/** + Schedule the whole mess to fire up on load. +*/ +addLoadEvent(function(){ S3AjaxWiki.init() }); + diff --git a/tantan/wordpress-s3/js/wiky.js b/tantan/wordpress-s3/js/wiky.js new file mode 100644 index 0000000..71e46ff --- /dev/null +++ b/tantan/wordpress-s3/js/wiky.js @@ -0,0 +1,373 @@ +/* This work is licensed under Creative Commons GNU LGPL License. + + License: http://creativecommons.org/licenses/LGPL/2.1/ + + Author: Stefan Goessner/2005-06 + Web: http://goessner.net/ +*/ +var Wiky = { + version: 0.95, + blocks: null, + rules: { + all: [ + "Wiky.rules.pre", + "Wiky.rules.nonwikiblocks", + "Wiky.rules.wikiblocks", + "Wiky.rules.post", + ], + pre: [ + { rex:/(\r?\n)/g, tmplt:"\xB6" }, // replace line breaks with '¶' .. + ], + post: [ + { rex:/(^\xB6)|(\xB6$)/g, tmplt:"" }, // .. remove linebreaks at BOS and EOS .. + { rex:/@([0-9]+)@/g, tmplt:function($0,$1){return Wiky.restore($1);} }, // resolve blocks .. + { rex:/\xB6/g, tmplt:"\n" } // replace '¶' with line breaks .. + ], + nonwikiblocks: [ + { rex:/\\([%])/g, tmplt:function($0,$1){return Wiky.store($1);} }, + { rex:/\[(?:\{([^}]*)\})?(?:\(([^)]*)\))?%(.*?)%\]/g, tmplt:function($0,$1,$2,$3){return ":p]"+Wiky.store("" + Wiky.apply($3, $2?Wiky.rules.lang[Wiky.attr($2)]:Wiky.rules.code) + "")+"[p:";} } //programm code block + ], + wikiblocks: [ + "Wiky.rules.nonwikiinlines", + "Wiky.rules.escapes", + { rex:/(?:^|\xB6)(={1,6})(.*?)[=]*(?=\xB6|$)/g, tmplt:function($0,$1,$2){ var h=$1.length; return ":p]\xB6"+$2+"\xB6[p:";} }, //

..

+ { rex:/(?:^|\xB6)[-]{4}(?:\xB6|$)/g, tmplt:"\xB6
\xB6" }, // horizontal ruler .. + { rex:/\\\\([ \xB6])/g, tmplt:"
$1" }, // forced line break .. + { rex:/(^|\xB6)([*01aAiIg]*[\.*])[ ]/g, tmplt:function($0,$1,$2){var state=$2.replace(/([*])/g,"u").replace(/([\.])/,"");return ":"+state+"]"+$1+"["+state+":";}}, + { rex:/(?:^|\xB6);[ ](.*?):[ ]/g, tmplt:"\xB6:l][l:$1:d][d:"}, // ; term : definition + { rex:/\[(?:\{([^}]*)\})?(?:\(([^)]*)\))?\"/g, tmplt:function($0,$1,$2){return ":p][p:"; } }, // block quotation start + { rex:/\"\]/g, tmplt:":p][p:" }, // block quotation end + { rex:/\[(\{[^}]*\})?\|/g, tmplt:":t]$1[r:" }, // .. start table .. + { rex:/\|\]/g, tmplt:":r][t:" }, // .. end table .. + { rex:/\|\xB6[ ]?\|/g, tmplt:":r]\xB6[r:" }, // .. end/start table row .. + { rex:/\|/g, tmplt:":c][c:" }, // .. end/start table cell .. + { rex:/^(.*)$/g, tmplt:"[p:$1:p]" }, // start paragraph '[p:' at BOS .. end paragraph ':p]' at EOS .. + { rex:/(([\xB6])([ \t\f\v\xB6]*?)){2,}/g, tmplt:":p]$1[p:" }, // .. separate paragraphs at blank lines .. + { rex:/\[([01AIacdgilprtu]+)[:](.*?)[:]([01AIacdgilprtu]+)\]/g, tmplt:function($0,$1,$2,$3){return Wiky.sectionRule($1==undefined?"":$1,"",Wiky.apply($2,Wiky.rules.wikiinlines),!$3?"":$3);} }, + { rex:/\[[01AIacdgilprtu]+[:]|[:][01AIacdgilprtu]+\]/g, tmplt:"" }, // .. remove singular section delimiters (they frequently exist with incomplete documents while typing) .. + { rex:/(?:([0-9]*)[>])?([ ]?)(.*?)([ ]?)<\/td>/g, tmplt:function($0,$1,$2,$3,$4){return ""+$2+$3+$4+"";} }, + { rex:/<(p|table)>(?:\xB6)?(?:\{(.*?)\})/g, tmplt:function($0,$1,$2){return "<"+$1+Wiky.style($2)+">";} }, + { rex:/

([ \t\f\v\xB6]*?)<\/p>/g, tmplt:"$1" }, // .. remove empty paragraphs .. + "Wiky.rules.shortcuts" + ], + nonwikiinlines: [ + { rex:/%(?:\{([^}]*)\})?(?:\(([^)]*)\))?(.*?)%/g, tmplt:function($0,$1,$2,$3){return Wiky.store("" + Wiky.apply($3, $2?Wiky.rules.lang[Wiky.attr($2)]:Wiky.rules.code) + "");} }, // inline code + { rex:/%(.*?)%/g, tmplt:function($0,$1){return Wiky.store("" + Wiky.apply($2, Wiky.rules.code) + "");} } + ], + wikiinlines: [ + { rex:/\*([^*]+)\*/g, tmplt:"$1" }, // .. strong .. + { rex:/_([^_]+)_/g, tmplt:"$1" }, + { rex:/\^([^^]+)\^/g, tmplt:"$1" }, + { rex:/~([^~]+)~/g, tmplt:"$1" }, + { rex:/\(-(.+?)-\)/g, tmplt:"$1" }, + { rex:/\?([^ \t\f\v\xB6]+)\((.+)\)\?/g, tmplt:"$1" }, // .. abbreviation .. + { rex:/\[(?:\{([^}]*)\})?[Ii]ma?ge?\:([^ ,\]]*)(?:[, ]([^\]]*))?\]/g, tmplt:function($0,$1,$2,$3){return Wiky.store("");} }, // wikimedia image style .. + { rex:/\[([^ ,]+)[, ]([^\]]*)\]/g, tmplt:function($0,$1,$2){return Wiky.store(""+$2+"");}}, // wiki block style uri's .. + { rex:/(((http(s?))\:\/\/)?[A-Za-z0-9\._\/~\-:]+\.(?:png|jpg|jpeg|gif|bmp))/g, tmplt:function($0,$1,$2){return Wiky.store("\""+$1+"\"/");} }, // simple images .. + { rex:/((mailto\:|javascript\:|(news|file|(ht|f)tp(s?))\:\/\/)[A-Za-z0-9\.:_\/~%\-+&#?!=()@\x80-\xB5\xB7\xFF]+)/g, tmplt:"$1" } // simple uri's .. + ], + escapes: [ + { rex:/\\([|*_~\^])/g, tmplt:function($0,$1){return Wiky.store($1);} }, + { rex:/\\&/g, tmplt:"&" }, + { rex:/\\>/g, tmplt:">" }, + { rex:/\\/g, tmplt:"↔"}, // $harr; + { rex:/<-/g, tmplt:"←"}, // ← + { rex:/->/g, tmplt:"→"}, //→ + ], + code: [ + { rex:/&/g, tmplt:"&"}, + { rex://g, tmplt:">"} + ], + lang: {} + }, + + inverse: { + all: [ + "Wiky.inverse.pre", + "Wiky.inverse.nonwikiblocks", + "Wiky.inverse.wikiblocks", + "Wiky.inverse.post" + ], + pre: [ + { rex:/(\r?\n)/g, tmplt:"\xB6" } // replace line breaks with '¶' .. + ], + post: [ + { rex:/@([0-9]+)@/g, tmplt:function($0,$1){return Wiky.restore($1);} }, // resolve blocks .. + { rex:/\xB6/g, tmplt:"\n" } // replace '¶' with line breaks .. + ], + nonwikiblocks: [ + { rex:/]*)>(.*?)<\/pre>/mgi, tmplt:function($0,$1,$2){return Wiky.store("["+Wiky.invStyle($1)+Wiky.invAttr($1,["lang"]).replace(/x\-/,"")+"%"+Wiky.apply($2, Wiky.hasAttr($1,"lang")?Wiky.inverse.lang[Wiky.attrVal($1,"lang").substr(2)]:Wiky.inverse.code)+"%]");} } //code block + ], + wikiblocks: [ + "Wiky.inverse.nonwikiinlines", + "Wiky.inverse.escapes", + "Wiky.inverse.wikiinlines", + { rex:/

(.*?)<\/h1>/mgi, tmplt:"=$1=" }, + { rex:/

(.*?)<\/h2>/mgi, tmplt:"==$1==" }, + { rex:/

(.*?)<\/h3>/mgi, tmplt:"===$1===" }, + { rex:/

(.*?)<\/h4>/mgi, tmplt:"====$1====" }, + { rex:/

(.*?)<\/h5>/mgi, tmplt:"=====$1=====" }, + { rex:/
(.*?)<\/h6>/mgi, tmplt:"======$1======" }, + { rex:/<(p|table)[^>]+(style=\"[^\"]*\")[^>]*>/mgi, tmplt:function($0,$1,$2){return "<"+$1+">"+Wiky.invStyle($2);} }, + { rex:/\xB6{2}
  • \"]*)\"?[^>]*?>([^<]*)/mgi, tmplt:function($0,$1,$2){return $1.replace(/u/g,"*").replace(/([01aAiIg])$/,"$1.")+" "+$2;}}, // list items .. + { rex:/(^|\xB6)<(u|o)l[^>]*?>\xB6/mgi, tmplt:"$1" }, // only outer level list start at BOL ... + { rex:/(<\/(?:dl|ol|ul|p)>[ \xB6]*<(?:p)>)/gi, tmplt:"\xB6\xB6" }, + { rex:/
    (.*?)<\/dt>[ \f\n\r\t\v]*
    /mgi, tmplt:"; $1: " }, + { rex:/]*)>/mgi, tmplt:function($0,$1){return Wiky.store("["+Wiky.invStyle($1)+Wiky.invAttr($1,["cite","title"])+"\"");} }, + { rex:/<\/blockquote>/mgi, tmplt:"\"]" }, + { rex:/\xB6*[ ]?|<\/tr>/mgi, tmplt:"|" }, // ie6 only .. + { rex:/\xB6]*?)>/mgi, tmplt:"\xB6" }, + { rex:/]*?)>/mgi, tmplt:"|$1>" }, + { rex:/]*?)>/mgi, tmplt:"|" }, + { rex://mgi, tmplt:"[" }, + { rex:/<\/table>/mgi, tmplt:"]" }, + { rex:/]*?)>\xB6*|<\/td>\xB6*|\xB6*|<\/tbody>/mgi, tmplt:"" }, + { rex://mgi, tmplt:"----" }, + { rex://mgi, tmplt:"\\\\" }, + { rex:/(

    |<(d|o|u)l[^>]*>|<\/(dl|ol|ul|p)>|<\/(li|dd)>)/mgi, tmplt:"" }, + "Wiky.inverse.shortcuts" + ], + nonwikiinlines: [ + { rex:/(.*?)<\/code>/g, tmplt:function($0,$1){return Wiky.store("%"+Wiky.apply($1, Wiky.inverse["code"])+"%");} } + ], + wikiinlines: [ + { rex:/]*?>(.*?)<\/strong>/mgi, tmplt:"*$1*" }, + { rex:/]*?>(.*?)<\/b>/mgi, tmplt:"*$1*" }, + { rex:/]*?>(.*?)<\/em>/mgi, tmplt:"_$1_" }, + { rex:/]*?>(.*?)<\/i>/mgi, tmplt:"_$1_" }, + { rex:/]*?>(.*?)<\/sup>/mgi, tmplt:"^$1^" }, + { rex:/]*?>(.*?)<\/sub>/mgi, tmplt:"~$1~" }, + { rex:/]*?>(.*?)<\/del>/mgi, tmplt:"(-$1-)" }, + { rex:/(.*?)<\/abbr>/mgi, tmplt:"?$2($1)?" }, + { rex:/]*?>(.*?)<\/a>/mgi, tmplt:function($0,$1,$2){return $1==$2?$1:"["+$1+","+$2+"]";}}, + { rex:/]*)\/>/mgi, tmplt:function($0,$1){var a=Wiky.attrVal($1,"alt"),h=Wiky.attrVal($1,"src"),t=Wiky.attrVal($1,"title"),s=Wiky.attrVal($1,"style");return s||(t&&h!=t)?("["+Wiky.invStyle($1)+"img:"+h+(t&&(","+t))+"]"):h;}}, + ], + escapes: [ + { rex:/([|*_~%\^])/g, tmplt:"\\$1" }, + { rex:/&/g, tmplt:"\\&" }, + { rex:/>/g, tmplt:"\\>" }, + { rex:/</g, tmplt:"\\<" } + ], + shortcuts: [ + { rex:/–|\u2013/g, tmplt:"--"}, + { rex:/—|\u2014/g, tmplt:"---"}, + { rex:/…|\u2026/g, tmplt:"..."}, + { rex:/↔|\u2194/g, tmplt:"<->"}, + { rex:/←|\u2190/g, tmplt:"<-"}, + { rex:/→|\u2192/g, tmplt:"->"} + ], + code: [ + { rex:/&/g, tmplt:"&"}, + { rex:/</g, tmplt:"<"}, + { rex:/>/g, tmplt:">"} + ], + lang: {} + }, + + toHtml: function(str) { + Wiky.blocks = []; + return Wiky.apply(str, Wiky.rules.all); + }, + + toWiki: function(str) { + Wiky.blocks = []; + return Wiky.apply(str, Wiky.inverse.all); + }, + + apply: function(str, rules) { + if (str && rules) + for (var i in rules) { + if (typeof(rules[i]) == "string") + str = Wiky.apply(str, eval(rules[i])); + else + str = str.replace(rules[i].rex, rules[i].tmplt); + } + return str; + }, + store: function(str, unresolved) { + return unresolved ? "@" + (Wiky.blocks.push(str)-1) + "@" + : "@" + (Wiky.blocks.push(str.replace(/@([0-9]+)@/g, function($0,$1){return Wiky.restore($1);}))-1) + "@"; + }, + restore: function(idx) { + return Wiky.blocks[idx]; + }, + attr: function(str, name, idx) { + var a = str && str.split(",")[idx||0]; + return a ? (name ? (" "+name+"=\""+a+"\"") : a) : ""; + }, + hasAttr: function(str, name) { + return new RegExp(name+"=").test(str); + }, + attrVal: function(str, name) { + return str.replace(new RegExp("^.*?"+name+"=\"(.*?)\".*?$"), "$1"); + }, + invAttr: function(str, names) { + var a=[], x; + for (var i in names) + if (str.indexOf(names[i]+"=")>=0) + a.push(str.replace(new RegExp("^.*?"+names[i]+"=\"(.*?)\".*?$"), "$1")); + return a.length ? ("("+a.join(",")+")") : ""; + }, + style: function(str) { + var s = str && str.split(/,|;/), p, style = ""; + for (var i in s) { + p = s[i].split(":"); + if (p[0] == ">") style += "margin-left:4em;"; + else if (p[0] == "<") style += "margin-right:4em;"; + else if (p[0] == ">>") style += "float:right;"; + else if (p[0] == "<<") style += "float:left;"; + else if (p[0] == "=") style += "display:block;margin:0 auto;"; + else if (p[0] == "_") style += "text-decoration:underline;"; + else if (p[0] == "b") style += "border:solid 1px;"; + else if (p[0] == "c") style += "color:"+p[1]+";"; + else if (p[0] == "C") style += "background:"+p[1]+";"; + else if (p[0] == "w") style += "width:"+p[1]+";"; + else style += p[0]+":"+p[1]+";"; + } + return style ? " style=\""+style+"\"" : ""; + }, + invStyle: function(str) { + var s = /style=/.test(str) ? str.replace(/^.*?style=\"(.*?)\".*?$/, "$1") : "", + p = s && s.split(";"), pi, prop = []; + for (var i in p) { + pi = p[i].split(":"); + if (pi[0] == "margin-left" && pi[1]=="4em") prop.push(">"); + else if (pi[0] == "margin-right" && pi[1]=="4em") prop.push("<"); + else if (pi[0] == "float" && pi[1]=="right") prop.push(">>"); + else if (pi[0] == "float" && pi[1]=="left") prop.push("<<"); + else if (pi[0] == "margin" && pi[1]=="0 auto") prop.push("="); + else if (pi[0] == "display" && pi[1]=="block") ; + else if (pi[0] == "text-decoration" && pi[1]=="underline") prop.push("_"); + else if (pi[0] == "border" && pi[1]=="solid 1px") prop.push("b"); + else if (pi[0] == "color") prop.push("c:"+pi[1]); + else if (pi[0] == "background") prop.push("C:"+pi[1]); + else if (pi[0] == "width") prop.push("w:"+pi[1]); + else if (pi[0]) prop.push(pi[0]+":"+pi[1]); + } + return prop.length ? ("{" + prop.join(",") + "}") : ""; + }, + sectionRule: function(fromLevel, style, content, toLevel) { + var trf = { p_p: "

    $1

    ", + p_u: "

    $1

    ", + p_o: "

    $1

    ", + // p - ul + // ul - p + u_p: "$1", + u_c: "$1", + u_r: "$1", + uu_p: "$1", + uo_p: "$1", + uuu_p: "$1", + uou_p: "$1", + uuo_p: "$1", + uoo_p: "$1", + // ul - ul + u_u: "$1", + uu_u: "$1", + uo_u: "$1", + uuu_u: "$1", + uou_u: "$1", + uuo_u: "$1", + uoo_u: "$1", + u_uu: "$1", + // ul - ol + u_o: "$1", + uu_o: "$1", + uo_o: "$1", + uuu_o: "$1", + uou_o: "$1", + uuo_o: "$1", + uoo_o: "$1", + u_uo: "$1", + // ol - p + o_p: "$1", + oo_p: "$1", + ou_p: "$1", + ooo_p: "$1", + ouo_p: "$1", + oou_p: "$1", + ouu_p: "$1", + // ol - ul + o_u: "$1", + oo_u: "$1", + ou_u: "$1", + ooo_u: "$1", + ouo_u: "$1", + oou_u: "$1", + ouu_u: "$1", + o_ou: "$1", + // -- ol - ol -- + o_o: "$1", + oo_o: "$1", + ou_o: "$1", + ooo_o: "$1", + ouo_o: "$1", + oou_o: "$1", + ouu_o: "$1", + o_oo: "$1", + // -- dl -- + l_d: "
    $1
    ", + d_l: "
    $1
    ", + d_u: "
    $1
      ", + d_o: "
      $1
        ", + p_l: "

        $1

        ", + u_l: "$1
    ", + o_l: "$1
    ", + uu_l: "$1
    ", + uo_l: "$1
    ", + ou_l: "$1
    ", + oo_l: "$1
    ", + d_p: "
    $1
    ", + // -- table -- + p_t: "

    $1

    ", + p_r: "

    $1

    ", + p_c: "

    $1

    ", + t_p: "

    $1

    ", + r_r: "$1", + r_p: "

    $1

    ", + r_c: "$1", + r_u: "$1