├── README.textile ├── load.sh ├── yak ├── cookie.js ├── favicon.png ├── index.html ├── jquery.min.js ├── json2.js ├── md5-min.js ├── riak.js ├── styles.css └── yakriak.js └── yakmr ├── mapMessageSince ├── reduceLimitLastN └── reduceSortTimestamp /README.textile: -------------------------------------------------------------------------------- 1 | **This repository is not maintained, use at your own risk.** 2 | 3 | h1. yakriak 4 | 5 | A simple web-based chatroom app. Designed as a proof-of-concept for Ben Black's Riak training at VelocityConf 2010. 6 | 7 | h2. Features / Problems 8 | 9 | * Everything is stored and served out of Riak. No special abstraction. Take that, couchapps! 10 | * Browser Compatibility: 11 | ** WebKit-based (Chrome, Safari) 12 | ** Firefox 3.6 and 4.0 13 | ** MSIE8, maybe 7 (tested via 8's IE7 mode) 14 | * Remembers your login credentials via unencrypted cookies (not transmitted). 15 | * Randomizes the polling interval to avoid thundering-herd effects. 16 | * Uses full-bucket map-reduce, so won't perform well at huge numbers of messages. Future work could change to use key-filters but would still incur the price of list-keys. 17 | * Your email address will be used for Gravatar, should you decide not to remain anonymous. 18 | 19 | h2. Setup 20 | 21 | # Startup Riak locally on the standard HTTP port (8098). 22 | # Join your local node to the cluster, if necessary. 23 | # Run the @load.sh@ bash script. 24 | # Visit "http://127.0.0.1:8098/riak/yak/index.html":http://127.0.0.1:8098/riak/yak/index.html 25 | 26 | h2. Learn more 27 | 28 | Read the source! The majority of the work is in @yak/yakriak.js@, and the map and reduce functions in the @yakmr/@ directory. 29 | 30 | h2. Credit where credit's due 31 | 32 | * jQuery 1.4.2 (jQuery team) 33 | * riak-javascript-client (Basho) 34 | * json2.js (Douglas Crockford) 35 | * cookie.js (Maxime Haineault, John W. Long) 36 | * md5.js (Paul Johnston) 37 | -------------------------------------------------------------------------------- /load.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Allow running the script and specifiying an install target 4 | # ./load.sh node-address:host 5 | if [ -n "$1" ] ; then 6 | node=$1 7 | else 8 | node="127.0.0.1:8098" 9 | fi 10 | 11 | function storeInRiak { 12 | echo "Storing $1 as $2"; 13 | curl -X PUT "http://$node/riak/$1" -H "Content-Type: $2" --data-binary @$1 14 | } 15 | 16 | for file in yak/*.html; do 17 | storeInRiak $file 'text/html' 18 | done 19 | 20 | for file in yak/*.js; do 21 | storeInRiak $file 'application/javascript' 22 | done 23 | 24 | for file in yak/*.css; do 25 | storeInRiak $file 'text/css' 26 | done 27 | 28 | for file in yak/*.png; do 29 | storeInRiak $file 'image/png' 30 | done 31 | 32 | for file in yakmr/*; do 33 | storeInRiak $file 'application/javascript' 34 | done 35 | -------------------------------------------------------------------------------- /yak/cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | cookie.js 3 | 4 | Copyright (c) 2007, 2008 Maxime Haineault 5 | (http://www.haineault.com/code/cookie-js/, http://code.google.com/p/cookie-js/) 6 | 7 | Portions Copyright (c) 2008, John W. Long 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | */ 28 | 29 | Cookie = { 30 | get: function(name) { 31 | // Still not sure that "[a-zA-Z0-9.()=|%/]+($|;)" match *all* allowed characters in cookies 32 | tmp = document.cookie.match((new RegExp(name +'=[a-zA-Z0-9.()=|%/]+($|;)','g'))); 33 | if (!tmp || !tmp[0]) { 34 | return null; 35 | } else { 36 | return unescape(tmp[0].substring(name.length + 1, tmp[0].length).replace(';', '')) || null; 37 | } 38 | }, 39 | 40 | set: function(name, value, expireInHours, path, domain, secure) { 41 | var cookie = [ 42 | name + '=' + escape(value), 43 | 'path=' + ((!path || path == '') ? '/' : path) 44 | ]; 45 | if (Cookie._notEmpty(domain)) cookie.push('domain=' + domain); 46 | if (Cookie._notEmpty(expireInHours)) cookie.push(Cookie._hoursToExpireDate(expireInHours)); 47 | if (Cookie._notEmpty(secure)) cookie.push('secure'); 48 | return document.cookie = cookie.join(';'); 49 | }, 50 | 51 | erase: function(name, path, domain) { 52 | path = (!path || typeof path != 'string') ? '' : path; 53 | domain = (!domain || typeof domain != 'string') ? '' : domain; 54 | if (Cookie.get(name)) Cookie.set(name, '', 'Thu, 01-Jan-70 00:00:01 GMT', path, domain); 55 | }, 56 | 57 | // Returns true if cookies are enabled 58 | accept: function() { 59 | Cookie.set('b49f729efde9b2578ea9f00563d06e57', 'true'); 60 | if (Cookie.get('b49f729efde9b2578ea9f00563d06e57') == 'true') { 61 | Cookie.erase('b49f729efde9b2578ea9f00563d06e57'); 62 | return true; 63 | } 64 | return false; 65 | }, 66 | 67 | _notEmpty: function(value) { 68 | return (typeof value != 'undefined' && value != null && value != ''); 69 | }, 70 | 71 | // Private function for calculating the date of expiration based on hours 72 | _hoursToExpireDate: function(hours) { 73 | if (parseInt(hours) == 'NaN' ) return ''; 74 | else { 75 | now = new Date(); 76 | now.setTime(now.getTime() + (parseInt(hours) * 60 * 60 * 1000)); 77 | return now.toGMTString(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /yak/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seancribbs/yakriak/381e3dc01ede11beebb480d288cd81d6f123bc9c/yak/favicon.png -------------------------------------------------------------------------------- /yak/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | YakRiak - The Riak-powered Chatroom 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

YakRiak: The Riak-powered Chatroom

16 | 17 |
18 |
19 | Sign in 20 |
    21 |
  1. 22 | 23 | 24 |
  2. 25 |
  3. 26 | 27 | 28 |
  4. 29 |
  5. 30 | 31 |
  6. 32 |
33 |
34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /yak/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery JavaScript Library v1.4.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright 2010, John Resig 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | * 9 | * Includes Sizzle.js 10 | * http://sizzlejs.com/ 11 | * Copyright 2010, The Dojo Foundation 12 | * Released under the MIT, BSD, and GPL Licenses. 13 | * 14 | * Date: Sat Feb 13 22:33:48 2010 -0500 15 | */ 16 | (function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, 21 | Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& 22 | (d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, 23 | a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== 24 | "find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, 25 | function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; 34 | var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, 35 | parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= 36 | false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= 37 | s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, 38 | applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; 39 | else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, 40 | a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== 41 | w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, 42 | cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= 47 | c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); 48 | a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, 49 | function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); 50 | k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), 51 | C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= 53 | e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& 54 | f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; 55 | if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", 63 | e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, 64 | "_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, 65 | d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 71 | e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); 72 | t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| 73 | g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, 80 | CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, 81 | g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, 82 | text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, 83 | setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= 84 | h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== 86 | "="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, 87 | h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& 90 | q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; 91 | if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); 92 | (function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: 93 | function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= 96 | {},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== 97 | "string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", 98 | d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? 99 | a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== 100 | 1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= 102 | c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, 103 | wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, 104 | prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, 105 | this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); 106 | return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, 107 | ""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); 111 | return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", 112 | ""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= 113 | c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? 114 | c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= 115 | function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= 116 | Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, 117 | "border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= 118 | a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= 119 | a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== 120 | "string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, 121 | serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), 122 | function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, 123 | global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& 124 | e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? 125 | "&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== 126 | false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= 127 | false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", 128 | c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| 129 | d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); 130 | g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== 131 | 1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== 132 | "json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; 133 | if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== 139 | "number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| 140 | c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; 141 | this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= 142 | this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, 143 | e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; 149 | a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); 150 | c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, 151 | d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- 152 | f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": 153 | "pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in 154 | e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); 155 | -------------------------------------------------------------------------------- /yak/json2.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | http://www.JSON.org/json2.js 4 | 2009-09-29 5 | 6 | Public Domain. 7 | 8 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 9 | 10 | See http://www.JSON.org/js.html 11 | 12 | 13 | This code should be minified before deployment. 14 | See http://javascript.crockford.com/jsmin.html 15 | 16 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 17 | NOT CONTROL. 18 | 19 | 20 | This file creates a global JSON object containing two methods: stringify 21 | and parse. 22 | 23 | JSON.stringify(value, replacer, space) 24 | value any JavaScript value, usually an object or array. 25 | 26 | replacer an optional parameter that determines how object 27 | values are stringified for objects. It can be a 28 | function or an array of strings. 29 | 30 | space an optional parameter that specifies the indentation 31 | of nested structures. If it is omitted, the text will 32 | be packed without extra whitespace. If it is a number, 33 | it will specify the number of spaces to indent at each 34 | level. If it is a string (such as '\t' or ' '), 35 | it contains the characters used to indent at each level. 36 | 37 | This method produces a JSON text from a JavaScript value. 38 | 39 | When an object value is found, if the object contains a toJSON 40 | method, its toJSON method will be called and the result will be 41 | stringified. A toJSON method does not serialize: it returns the 42 | value represented by the name/value pair that should be serialized, 43 | or undefined if nothing should be serialized. The toJSON method 44 | will be passed the key associated with the value, and this will be 45 | bound to the value 46 | 47 | For example, this would serialize Dates as ISO strings. 48 | 49 | Date.prototype.toJSON = function (key) { 50 | function f(n) { 51 | // Format integers to have at least two digits. 52 | return n < 10 ? '0' + n : n; 53 | } 54 | 55 | return this.getUTCFullYear() + '-' + 56 | f(this.getUTCMonth() + 1) + '-' + 57 | f(this.getUTCDate()) + 'T' + 58 | f(this.getUTCHours()) + ':' + 59 | f(this.getUTCMinutes()) + ':' + 60 | f(this.getUTCSeconds()) + 'Z'; 61 | }; 62 | 63 | You can provide an optional replacer method. It will be passed the 64 | key and value of each member, with this bound to the containing 65 | object. The value that is returned from your method will be 66 | serialized. If your method returns undefined, then the member will 67 | be excluded from the serialization. 68 | 69 | If the replacer parameter is an array of strings, then it will be 70 | used to select the members to be serialized. It filters the results 71 | such that only members with keys listed in the replacer array are 72 | stringified. 73 | 74 | Values that do not have JSON representations, such as undefined or 75 | functions, will not be serialized. Such values in objects will be 76 | dropped; in arrays they will be replaced with null. You can use 77 | a replacer function to replace those with JSON values. 78 | JSON.stringify(undefined) returns undefined. 79 | 80 | The optional space parameter produces a stringification of the 81 | value that is filled with line breaks and indentation to make it 82 | easier to read. 83 | 84 | If the space parameter is a non-empty string, then that string will 85 | be used for indentation. If the space parameter is a number, then 86 | the indentation will be that many spaces. 87 | 88 | Example: 89 | 90 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 91 | // text is '["e",{"pluribus":"unum"}]' 92 | 93 | 94 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 95 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 96 | 97 | text = JSON.stringify([new Date()], function (key, value) { 98 | return this[key] instanceof Date ? 99 | 'Date(' + this[key] + ')' : value; 100 | }); 101 | // text is '["Date(---current time---)"]' 102 | 103 | 104 | JSON.parse(text, reviver) 105 | This method parses a JSON text to produce an object or array. 106 | It can throw a SyntaxError exception. 107 | 108 | The optional reviver parameter is a function that can filter and 109 | transform the results. It receives each of the keys and values, 110 | and its return value is used instead of the original value. 111 | If it returns what it received, then the structure is not modified. 112 | If it returns undefined then the member is deleted. 113 | 114 | Example: 115 | 116 | // Parse the text. Values that look like ISO date strings will 117 | // be converted to Date objects. 118 | 119 | myData = JSON.parse(text, function (key, value) { 120 | var a; 121 | if (typeof value === 'string') { 122 | a = 123 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 124 | if (a) { 125 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 126 | +a[5], +a[6])); 127 | } 128 | } 129 | return value; 130 | }); 131 | 132 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 133 | var d; 134 | if (typeof value === 'string' && 135 | value.slice(0, 5) === 'Date(' && 136 | value.slice(-1) === ')') { 137 | d = new Date(value.slice(5, -1)); 138 | if (d) { 139 | return d; 140 | } 141 | } 142 | return value; 143 | }); 144 | 145 | 146 | This is a reference implementation. You are free to copy, modify, or 147 | redistribute. 148 | */ 149 | 150 | /*jslint evil: true, strict: false */ 151 | 152 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 153 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 154 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 155 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 156 | test, toJSON, toString, valueOf 157 | */ 158 | 159 | 160 | // Create a JSON object only if one does not already exist. We create the 161 | // methods in a closure to avoid creating global variables. 162 | 163 | if (!this.JSON) { 164 | this.JSON = {}; 165 | } 166 | 167 | (function () { 168 | 169 | function f(n) { 170 | // Format integers to have at least two digits. 171 | return n < 10 ? '0' + n : n; 172 | } 173 | 174 | if (typeof Date.prototype.toJSON !== 'function') { 175 | 176 | Date.prototype.toJSON = function (key) { 177 | 178 | return isFinite(this.valueOf()) ? 179 | this.getUTCFullYear() + '-' + 180 | f(this.getUTCMonth() + 1) + '-' + 181 | f(this.getUTCDate()) + 'T' + 182 | f(this.getUTCHours()) + ':' + 183 | f(this.getUTCMinutes()) + ':' + 184 | f(this.getUTCSeconds()) + 'Z' : null; 185 | }; 186 | 187 | String.prototype.toJSON = 188 | Number.prototype.toJSON = 189 | Boolean.prototype.toJSON = function (key) { 190 | return this.valueOf(); 191 | }; 192 | } 193 | 194 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | gap, 197 | indent, 198 | meta = { // table of character substitutions 199 | '\b': '\\b', 200 | '\t': '\\t', 201 | '\n': '\\n', 202 | '\f': '\\f', 203 | '\r': '\\r', 204 | '"' : '\\"', 205 | '\\': '\\\\' 206 | }, 207 | rep; 208 | 209 | 210 | function quote(string) { 211 | 212 | // If the string contains no control characters, no quote characters, and no 213 | // backslash characters, then we can safely slap some quotes around it. 214 | // Otherwise we must also replace the offending characters with safe escape 215 | // sequences. 216 | 217 | escapable.lastIndex = 0; 218 | return escapable.test(string) ? 219 | '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' ? c : 222 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 223 | }) + '"' : 224 | '"' + string + '"'; 225 | } 226 | 227 | 228 | function str(key, holder) { 229 | 230 | // Produce a string from holder[key]. 231 | 232 | var i, // The loop counter. 233 | k, // The member key. 234 | v, // The member value. 235 | length, 236 | mind = gap, 237 | partial, 238 | value = holder[key]; 239 | 240 | // If the value has a toJSON method, call it to obtain a replacement value. 241 | 242 | if (value && typeof value === 'object' && 243 | typeof value.toJSON === 'function') { 244 | value = value.toJSON(key); 245 | } 246 | 247 | // If we were called with a replacer function, then call the replacer to 248 | // obtain a replacement value. 249 | 250 | if (typeof rep === 'function') { 251 | value = rep.call(holder, key, value); 252 | } 253 | 254 | // What happens next depends on the value's type. 255 | 256 | switch (typeof value) { 257 | case 'string': 258 | return quote(value); 259 | 260 | case 'number': 261 | 262 | // JSON numbers must be finite. Encode non-finite numbers as null. 263 | 264 | return isFinite(value) ? String(value) : 'null'; 265 | 266 | case 'boolean': 267 | case 'null': 268 | 269 | // If the value is a boolean or null, convert it to a string. Note: 270 | // typeof null does not produce 'null'. The case is included here in 271 | // the remote chance that this gets fixed someday. 272 | 273 | return String(value); 274 | 275 | // If the type is 'object', we might be dealing with an object or an array or 276 | // null. 277 | 278 | case 'object': 279 | 280 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 281 | // so watch out for that case. 282 | 283 | if (!value) { 284 | return 'null'; 285 | } 286 | 287 | // Make an array to hold the partial results of stringifying this object value. 288 | 289 | gap += indent; 290 | partial = []; 291 | 292 | // Is the value an array? 293 | 294 | if (Object.prototype.toString.apply(value) === '[object Array]') { 295 | 296 | // The value is an array. Stringify every element. Use null as a placeholder 297 | // for non-JSON values. 298 | 299 | length = value.length; 300 | for (i = 0; i < length; i += 1) { 301 | partial[i] = str(i, value) || 'null'; 302 | } 303 | 304 | // Join all of the elements together, separated with commas, and wrap them in 305 | // brackets. 306 | 307 | v = partial.length === 0 ? '[]' : 308 | gap ? '[\n' + gap + 309 | partial.join(',\n' + gap) + '\n' + 310 | mind + ']' : 311 | '[' + partial.join(',') + ']'; 312 | gap = mind; 313 | return v; 314 | } 315 | 316 | // If the replacer is an array, use it to select the members to be stringified. 317 | 318 | if (rep && typeof rep === 'object') { 319 | length = rep.length; 320 | for (i = 0; i < length; i += 1) { 321 | k = rep[i]; 322 | if (typeof k === 'string') { 323 | v = str(k, value); 324 | if (v) { 325 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 326 | } 327 | } 328 | } 329 | } else { 330 | 331 | // Otherwise, iterate through all of the keys in the object. 332 | 333 | for (k in value) { 334 | if (Object.hasOwnProperty.call(value, k)) { 335 | v = str(k, value); 336 | if (v) { 337 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 338 | } 339 | } 340 | } 341 | } 342 | 343 | // Join all of the member texts together, separated with commas, 344 | // and wrap them in braces. 345 | 346 | v = partial.length === 0 ? '{}' : 347 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 348 | mind + '}' : '{' + partial.join(',') + '}'; 349 | gap = mind; 350 | return v; 351 | } 352 | } 353 | 354 | // If the JSON object does not yet have a stringify method, give it one. 355 | 356 | if (typeof JSON.stringify !== 'function') { 357 | JSON.stringify = function (value, replacer, space) { 358 | 359 | // The stringify method takes a value and an optional replacer, and an optional 360 | // space parameter, and returns a JSON text. The replacer can be a function 361 | // that can replace values, or an array of strings that will select the keys. 362 | // A default replacer method can be provided. Use of the space parameter can 363 | // produce text that is more easily readable. 364 | 365 | var i; 366 | gap = ''; 367 | indent = ''; 368 | 369 | // If the space parameter is a number, make an indent string containing that 370 | // many spaces. 371 | 372 | if (typeof space === 'number') { 373 | for (i = 0; i < space; i += 1) { 374 | indent += ' '; 375 | } 376 | 377 | // If the space parameter is a string, it will be used as the indent string. 378 | 379 | } else if (typeof space === 'string') { 380 | indent = space; 381 | } 382 | 383 | // If there is a replacer, it must be a function or an array. 384 | // Otherwise, throw an error. 385 | 386 | rep = replacer; 387 | if (replacer && typeof replacer !== 'function' && 388 | (typeof replacer !== 'object' || 389 | typeof replacer.length !== 'number')) { 390 | throw new Error('JSON.stringify'); 391 | } 392 | 393 | // Make a fake root object containing our value under the key of ''. 394 | // Return the result of stringifying the value. 395 | 396 | return str('', {'': value}); 397 | }; 398 | } 399 | 400 | 401 | // If the JSON object does not yet have a parse method, give it one. 402 | 403 | if (typeof JSON.parse !== 'function') { 404 | JSON.parse = function (text, reviver) { 405 | 406 | // The parse method takes a text and an optional reviver function, and returns 407 | // a JavaScript value if the text is a valid JSON text. 408 | 409 | var j; 410 | 411 | function walk(holder, key) { 412 | 413 | // The walk method is used to recursively walk the resulting structure so 414 | // that modifications can be made. 415 | 416 | var k, v, value = holder[key]; 417 | if (value && typeof value === 'object') { 418 | for (k in value) { 419 | if (Object.hasOwnProperty.call(value, k)) { 420 | v = walk(value, k); 421 | if (v !== undefined) { 422 | value[k] = v; 423 | } else { 424 | delete value[k]; 425 | } 426 | } 427 | } 428 | } 429 | return reviver.call(holder, key, value); 430 | } 431 | 432 | 433 | // Parsing happens in four stages. In the first stage, we replace certain 434 | // Unicode characters with escape sequences. JavaScript handles many characters 435 | // incorrectly, either silently deleting them, or treating them as line endings. 436 | 437 | cx.lastIndex = 0; 438 | if (cx.test(text)) { 439 | text = text.replace(cx, function (a) { 440 | return '\\u' + 441 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 442 | }); 443 | } 444 | 445 | // In the second stage, we run the text against regular expressions that look 446 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 447 | // because they can cause invocation, and '=' because it can cause mutation. 448 | // But just to be safe, we want to reject all unexpected forms. 449 | 450 | // We split the second stage into 4 regexp operations in order to work around 451 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 452 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 453 | // replace all simple value tokens with ']' characters. Third, we delete all 454 | // open brackets that follow a colon or comma or that begin the text. Finally, 455 | // we look to see that the remaining characters are only whitespace or ']' or 456 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 457 | 458 | if (/^[\],:{}\s]*$/. 459 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 460 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 461 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 462 | 463 | // In the third stage we use the eval function to compile the text into a 464 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 465 | // in JavaScript: it can begin a block or an object literal. We wrap the text 466 | // in parens to eliminate the ambiguity. 467 | 468 | j = eval('(' + text + ')'); 469 | 470 | // In the optional fourth stage, we recursively walk the new structure, passing 471 | // each name/value pair to a reviver function for possible transformation. 472 | 473 | return typeof reviver === 'function' ? 474 | walk({'': j}, '') : j; 475 | } 476 | 477 | // If the text is not JSON parseable, then a SyntaxError is thrown. 478 | 479 | throw new SyntaxError('JSON.parse'); 480 | }; 481 | } 482 | }()); 483 | -------------------------------------------------------------------------------- /yak/md5-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 3 | * Digest Algorithm, as defined in RFC 1321. 4 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for more info. 8 | */ 9 | var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<>>(32-b))}; -------------------------------------------------------------------------------- /yak/riak.js: -------------------------------------------------------------------------------- 1 | /* -*- mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil -*- */ 2 | /** 3 | This file is provided to you under the Apache License, 4 | Version 2.0 (the "License"); you may not use this file 5 | except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, 11 | software distributed under the License is distributed on an 12 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | KIND, either express or implied. See the License for the 14 | specific language governing permissions and limitations 15 | under the License. 16 | **/ 17 | 18 | /** 19 | * This is a Javascript client for the Riak REST API. It 20 | * has two dependencies: 21 | * Douglas Crockford's JSON library: http://www.json.org/js.html 22 | * jQuery: http://jquery.com/ (but only for Ajax requests) 23 | * 24 | */ 25 | 26 | /** 27 | * TODO: Handle sibling values 28 | */ 29 | 30 | /** 31 | * Utility functions which don't belong anywhere else 32 | */ 33 | var RiakUtil = function() { 34 | return { 35 | /** 36 | * Base64 encode a number 37 | * @param num - Number to encode 38 | * @return string containing base64 encoded number 39 | */ 40 | base64Encode: function(num) { 41 | var base64digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 42 | return base64digits[(num >>> 26)] + base64digits[((num >>> 20)&63)] + 43 | base64digits[((num >>> 14)&63)] + base64digits[((num >>> 8)&63)] + 44 | base64digits[((num >>> 2)&63)] + base64digits[((num << 4)&63)] + '=='; 45 | }, 46 | /** 47 | * Trim spaces from beginning/end of text 48 | * @param text - Text to trimg 49 | * @return string with leading & trailing spaces removed 50 | */ 51 | trim: function(text) { 52 | var tmp = text.replace(/^\s+/, ''); 53 | return tmp.replace(/\s+$/, ''); 54 | }, 55 | 56 | /** 57 | * Was request successful? 58 | * @param req- XMLHttpRequest object 59 | * @return true if status is 2xx, false otherwise 60 | */ 61 | wasSuccessful: function(req) { 62 | return req.status > 199 && req.status < 300; 63 | }, 64 | /** 65 | * Create a modified accepts object 66 | */ 67 | multipart_accepts: function() { 68 | var current = jQuery.ajaxSettings.accepts; 69 | var accepts = {}; 70 | for (prop in current) { 71 | accepts[prop] = current[prop]; 72 | } 73 | accepts.multipart = "multipart/mixed;q=1.1"; 74 | return accepts; 75 | }, 76 | get_boundary: function(contentType) { 77 | var idx = contentType.indexOf("boundary="); 78 | if(idx < 0) 79 | throw('Could not locate boundary for multipart/mixed'); 80 | return contentType.substr(idx+9); 81 | }, 82 | /** 83 | * Parse a 300 request into siblings. This handles embedded 84 | * new lines and control characters. Unfortunately Firefox 85 | * seems to trim embedded \000 in the XHR response. Beware 86 | * for binary data (images etc) you may need to set allow_mult 87 | * false for the bucket until an alternative is found. 88 | * 89 | * @param contentType content type header with boundary information 90 | * @param text body of 300 response to be split 91 | * @return true if status is 2xx, false otherwise 92 | */ 93 | parseSiblings: function(contentType, text) { 94 | var prefixAt = function(idx, prefix) { 95 | return (text.substr(idx, prefix.length) === prefix); 96 | }; 97 | var nextChunk = function(lookFor, start_idx) { 98 | var idx = text.indexOf(lookFor, start_idx); 99 | if (idx < 0) 100 | throw("Could not find next chunk"); 101 | return idx; 102 | }; 103 | 104 | var boundary = RiakUtil.get_boundary(contentType); 105 | var nextBoundary = "--"+boundary+"\r\n"; 106 | var idx = nextChunk(nextBoundary, 0) + nextBoundary.length; // skip preamble 107 | var last_idx; 108 | var siblings = []; 109 | var sibling = {}; 110 | nextBoundary = "\r\n--"+boundary; 111 | for(;;) { 112 | // If a header 113 | if (prefixAt(idx, "\r\n") != true) { 114 | last_idx = idx; 115 | idx = nextChunk(": ", last_idx); 116 | var hdr = text.substring(last_idx, idx); 117 | last_idx = idx + 2; 118 | idx = nextChunk("\r\n", last_idx); 119 | var val = text.substring(last_idx, idx); 120 | if (hdr === 'Content-Type') { 121 | sibling.contentType = val; 122 | } else if (hdr === 'Link') { 123 | sibling.linkHeader = val; 124 | } 125 | idx += 2; 126 | } else { // Idx points to \r\n at end of headers, grab the body 127 | last_idx = idx + 2; 128 | idx = nextChunk(nextBoundary, last_idx + 2) 129 | sibling.body = text.substring(last_idx, idx); 130 | siblings.push(sibling); 131 | sibling = {}; 132 | idx += nextBoundary.length; 133 | if (prefixAt(idx, "--\r\n")) // --boundary-- is the end of 134 | break; 135 | else if (prefixAt(idx, "\r\n")) 136 | idx += 2; 137 | else 138 | throw("Expecting boundary or end of multipart/mixed"); 139 | } 140 | } 141 | return siblings; 142 | } 143 | }; 144 | }(); 145 | 146 | /** 147 | * Builds a map/reduce chain and executes it 148 | * @param client RiakClient object 149 | * @param bucketName Riak bucket name 150 | * @param key Riak bucket key (optional) 151 | */ 152 | function RiakMapper(client, bucketName, key) { 153 | if (bucketName === undefined || 154 | client === undefined) { 155 | throw('Cannot construct RiakMapper without bucketName and client'); 156 | } 157 | this.client = client; 158 | this.bucket = bucketName; 159 | this.key = key; 160 | this.phases = []; 161 | }; 162 | 163 | /** 164 | * Add a map phase to a map/reduce job 165 | * @param options - Hash describing the map phase 166 | * @return RiakMapper instance for method chaining fun 167 | */ 168 | RiakMapper.prototype.map = function(options) { 169 | this.phases.push(this._buildPhase({map: null}, options)); 170 | return this; 171 | }; 172 | 173 | /** 174 | * Add a map phase to a map/reduce job 175 | * @param options - Hash describing the reduce phase 176 | * @return RiakMapper instance for method chaining fun 177 | */ 178 | RiakMapper.prototype.reduce = function(options) { 179 | this.phases.push(this._buildPhase({reduce: null}, options)); 180 | return this; 181 | }; 182 | 183 | /** 184 | * Add a link phase to a map/reduce job 185 | * @param options - Hash describing the link phase 186 | */ 187 | RiakMapper.prototype.link = function(options) { 188 | this.phases.push(this._buildPhase({link: null}, options)); 189 | return this; 190 | }; 191 | 192 | /** 193 | * Runs a map/reduce job 194 | * @param timeout - Job timeout (in milliseconds). Defaults to 60000 195 | * @param callback - Function to call when op completes 196 | * 197 | * callback - function(success, request, results) 198 | * @param success - Boolean indicating success or failure 199 | * @param results - JSON decoded results or null 200 | * @param request - XMLHttpRequest object 201 | */ 202 | RiakMapper.prototype.run = function(timeout, callback) { 203 | if (timeout === undefined || timeout === null) { 204 | timeout = 60000; 205 | } 206 | else if (typeof timeout === 'function') { 207 | callback = timeout; 208 | timeout = 60000; 209 | } 210 | var mapper = this; 211 | var job = {'inputs': this._buildInputs(), 212 | 'query': this.phases, 213 | 'timeout': timeout}; 214 | jQuery.ajax({url: this.client.mapredUrl, 215 | type: 'POST', 216 | data: JSON.stringify(job), 217 | beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', mapper.client.clientId); }, 218 | complete: function(req, StatusText) { if (callback !== undefined) { 219 | if (RiakUtil.wasSuccessful(req)) { 220 | callback(true, JSON.parse(req.responseText), req); 221 | } 222 | else { 223 | try { 224 | var error = {error: JSON.parse(req.responseText)}; 225 | callback(false, error, req); 226 | } 227 | catch (e) { 228 | callback(false, null, req); 229 | } 230 | } } } }); 231 | }; 232 | 233 | /** Start RiakMapper internals **/ 234 | RiakMapper.prototype._buildPhase = function(starter, options) { 235 | if (typeof options.source === 'function') { 236 | source = options.source; 237 | try 238 | { 239 | /* Create a string with minimal padding - JSON.parse 240 | * does not like embedded newlines in strings 241 | * and function.toString() on FireFox (on 3.6.3) generates 242 | * a string with embedded newlines. 243 | */ 244 | options.source = source.toString(-1); 245 | } 246 | catch (e) 247 | { 248 | options.source = source.toString(); 249 | } 250 | } 251 | if ((starter.map === null || 252 | starter.reduce === null) && (options.language === null || options.language === undefined)) { 253 | options.language = 'javascript'; 254 | } 255 | if (starter.map === null) { 256 | starter.map = options; 257 | } 258 | else if (starter.reduce === null){ 259 | starter.reduce = options; 260 | } 261 | else { 262 | if (options.bucket === null || options.bucket === undefined) { 263 | options.bucket = this.bucketName; 264 | } 265 | starter.link = options; 266 | } 267 | return starter; 268 | }; 269 | 270 | RiakMapper.prototype._buildInputs = function() { 271 | if (this.key !== null && this.key !== undefined) { 272 | return [[this.bucket, this.key]]; 273 | } 274 | else { 275 | return this.bucket; 276 | } 277 | } 278 | /** End RiakMapper internals **/ 279 | 280 | /** 281 | * Models an entry in a Riak bucket 282 | * @param bucketName - Riak bucket name 283 | * @param key - Object's key 284 | * @param client - Owning RiakClient 285 | * @param body - Object's data 286 | * @param contentType - Mime type associated with data 287 | * @param vclock - Riak-assigned vclock 288 | */ 289 | function RiakObject(bucketName, key, client, body, contentType, vclock) { 290 | if (client === undefined) { 291 | throw("Cannot construct RiakObject without a client reference"); 292 | } 293 | this.bucket = bucketName; 294 | this.key = key; 295 | this.client = client; 296 | if (contentType === undefined) { 297 | this.contentType = 'application/octet-stream'; 298 | } 299 | else { 300 | this.contentType = contentType; 301 | } 302 | if (contentType === 'application/json') { 303 | if (body !== undefined) { 304 | this.body = JSON.parse(body); 305 | } 306 | else { 307 | this.body = ''; 308 | } 309 | } 310 | else { 311 | if (body === undefined) { 312 | this.body = ''; 313 | } 314 | else { 315 | this.body = body; 316 | } 317 | } 318 | this.vclock = vclock; 319 | this.links = []; 320 | }; 321 | 322 | /** 323 | * 'Hydrates' a RiakObject from a HTTP request 324 | * @param bucket - Riak bucket name 325 | * @param key - Riak bucket key 326 | * @param client - Owning RiakClient 327 | * @param req - XMLHttpRequest 328 | */ 329 | RiakObject.fromRequest = function(bucket, key, client, req) { 330 | var contentType = req.getResponseHeader('Content-Type'); 331 | var vclock = req.getResponseHeader('X-Riak-Vclock'); 332 | var linkHeader = req.getResponseHeader('Link'); 333 | var body = req.responseText; 334 | var retval = new RiakObject(bucket, key, client, body, contentType, vclock); 335 | retval.setLinks(linkHeader); 336 | return retval; 337 | }; 338 | 339 | RiakObject.fromMultipart = function(bucket, key, client, vclock, multipartChunk) { 340 | var retval = new RiakObject(bucket, key, client, multipartChunk.body, multipartChunk.contentType, vclock); 341 | retval.setLinks(multipartChunk.linkHeader); 342 | return retval; 343 | } 344 | 345 | /** 346 | * Begins building a map/reduce job which will 347 | * use the current object as input 348 | * @param options - Hash description the map phase 349 | */ 350 | RiakObject.prototype.map = function(options) { 351 | var mapper = new RiakMapper(this.client, this.bucket, this.key); 352 | return mapper.map(options); 353 | }; 354 | 355 | /** 356 | * Begins building a map/reduce job which will 357 | * use the current object as input 358 | * @param options - Hash description the reduce phase 359 | */ 360 | RiakObject.prototype.reduce = function(options) { 361 | var mapper = new RiakMapper(this.client, this.bucket, this.key); 362 | return mapper.reduce(options); 363 | }; 364 | 365 | /** 366 | * Begins building a map/reduce job which will 367 | * use the current object as input 368 | * @param options - Hash description the link phase 369 | */ 370 | RiakObject.prototype.link = function(options) { 371 | var mapper = new RiakMapper(this.client, this.bucket, this.key); 372 | return mapper.link(options); 373 | }; 374 | 375 | /** 376 | * Parses a raw link header and populates the links array 377 | * @param linkHeader - Raw link header string 378 | */ 379 | RiakObject.prototype.setLinks = function(linkHeader) { 380 | var parsedLinks = new Array(); 381 | if (linkHeader != '') { 382 | var links = linkHeader.split(","); 383 | for (var i = 0; i < links.length; i++) { 384 | var linkParts = links[i].split(';'); 385 | var linkTag = RiakUtil.trim(linkParts[1]); 386 | var linkTo = RiakUtil.trim(linkParts[0].replace(/Link: ?/, '')); 387 | linkTo = linkTo.replace(//, ''); 388 | linkTo = linkTo.replace(/\"/g, ''); 389 | linkTag = linkTag.replace('riaktag=', ''); 390 | linkTag = linkTag.replace(/\"/g, ''); 391 | parsedLinks.push({tag: linkTag.toString(), target: linkTo.toString()}); 392 | } 393 | } 394 | this.links = parsedLinks; 395 | }; 396 | 397 | /** 398 | * Retrieves the links collection 399 | * @return Array of link hashes (e.g. [{tag: 'userInfo', target: '/riak/users/bob'}]) 400 | */ 401 | RiakObject.prototype.getLinks = function() { 402 | return this.links; 403 | }; 404 | 405 | /** 406 | * Returns the links formatted for the Link header 407 | * @return - Link header string 408 | */ 409 | RiakObject.prototype.getLinkHeader = function() { 410 | if (this.links.length == 0) { 411 | return ''; 412 | } 413 | var header = ''; 414 | for (var i = 0; i < this.links.length; i++) { 415 | link = this.links[i]; 416 | header = header + '<' + link.target + '>; '; 417 | if (link.tag === 'rel=up') { 418 | header = header + 'rel="up", '; 419 | } 420 | else { 421 | header = header + 'riaktag=\"' + link.tag + '\", '; 422 | } 423 | } 424 | header = header.replace(/\"\"/g, '\"'); 425 | return header.replace(/,\s$/, ''); 426 | }; 427 | 428 | /** 429 | * Adds a link to the object's link collection 430 | * @param link - Pointer to other object (e.g. /riak/foo/bar) 431 | * @param tag - Tag for the link (e.g. 'userInfo') 432 | * @param noDuplicates - Toggle duplicate checking on/off 433 | * @return true if added, false otherwise 434 | */ 435 | RiakObject.prototype.addLink = function(link, tag, noDuplicates) { 436 | if (link.indexOf('/') == -1) { 437 | throw('Invalid link: ' + link); 438 | } 439 | var retval = true; 440 | if (noDuplicates === false || noDuplicates === undefined) { 441 | this.links.push({tag: tag, target:link}); 442 | } 443 | else { 444 | var foundDuplicate = false; 445 | for (var i = 0; i < this.links.length; i++) { 446 | foundDuplicate = this.links[i].tag === tag && 447 | this.links[i].target === link; 448 | if (foundDuplicate) { 449 | retval = false; 450 | break; 451 | } 452 | } 453 | if (!foundDuplicate) { 454 | this.links.push({tag: tag, target: link}); 455 | } 456 | } 457 | return retval; 458 | }; 459 | 460 | /** 461 | * Removes a link from the links collection based on 462 | * link and tag 463 | * @param link - Pointer to other object 464 | * @param tag - Tag for the link 465 | * @return true if link removed, false if not 466 | */ 467 | RiakObject.prototype.removeLink = function(link, tag) { 468 | var retval = false; 469 | var newLinks = this.links.filter(function(l) { return l.link !== link || l.tag !== tag; }); 470 | if (newLinkes.length != this.links.length) { 471 | retval = true; 472 | this.links = newLinks; 473 | } 474 | return retval; 475 | }; 476 | 477 | /** 478 | * Resets the links collection to an empty array 479 | */ 480 | RiakObject.prototype.clearLinks = function() { 481 | this.links = []; 482 | }; 483 | 484 | 485 | /** 486 | * Deletes an object from a Riak bucket 487 | * @param callback - Function to call when op complete 488 | * 489 | * callback - function(success, request) 490 | * @param success - Boolean flag indicating successful removal 491 | * @param request - XMLHTTPRequest object 492 | */ 493 | RiakObject.prototype.remove = function(callback) { 494 | var object = this; 495 | jQuery.ajax({url: this.client._buildPath('DELETE', this.bucket, this.key), 496 | type: 'DELETE', 497 | accepts: RiakUtil.multipart_accepts(), 498 | dataType: 'multipart', 499 | beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', object.client.clientId); 500 | if (object.vclock !== undefined && object.vclock !== null) { 501 | req.setRequestHeader('X-Riak-Vclock', object.vclock); 502 | } 503 | }, 504 | complete: function(req, statusText) { if (callback !== undefined) { 505 | if (RiakUtil.wasSuccessful(req)) { 506 | callback(true, req); 507 | } 508 | else { 509 | callback(false, req); 510 | } 511 | } }}); 512 | }; 513 | 514 | /** 515 | * Store the object in Riak 516 | * @param callback - Function to call when op completes 517 | * 518 | * callback - function(status, object, request) 519 | * @param status - 'status' of the result: 'ok', 'failed', or 'siblings' 520 | * @param object - If status is 'ok', object is an updated RiakObject instance 521 | * If status is 'siblings', object is an array of RiakObject instances 522 | * which the client can pick from to resolve the conflict 523 | * If status is 'failed', object is null 524 | * NOTE: Use the updated version to prevent siblings & vector clock explosion 525 | * @param request - XMLHttpRequest object 526 | */ 527 | RiakObject.prototype.store = function(callback) { 528 | if (this.contentType === null) { 529 | throw('RiakObject missing contentType'); 530 | } 531 | var object = this; 532 | var objectData = null; 533 | if (this.contentType === 'application/json') { 534 | if (this.body !== undefined && this.body !== null) { 535 | objectData = JSON.stringify(this.body); 536 | } 537 | else { 538 | objectData = this.body; 539 | } 540 | } 541 | else { 542 | objectData = this.body; 543 | } 544 | jQuery.ajax({url: this.client._buildPath('PUT', this.bucket, this.key), 545 | type: 'PUT', 546 | data: objectData, 547 | contentType: this.contentType, 548 | accepts: RiakUtil.multipart_accepts(), 549 | dataType: 'multipart', 550 | beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', object.client.clientId); 551 | if (object.vclock !== undefined && object.vclock !== null) { 552 | req.setRequestHeader('X-Riak-Vclock', object.vclock); 553 | } 554 | var linkHeader = object.getLinkHeader(); 555 | if (linkHeader !== '') { 556 | req.setRequestHeader('Link', linkHeader); 557 | } 558 | }, 559 | complete: function(req, statusText) { object._store(req, callback); } }); 560 | }; 561 | 562 | /** Start RiakObject Internals **/ 563 | RiakObject.prototype._store = function(req, callback) { 564 | if (req.readyState != 4) { 565 | return; 566 | } 567 | if (callback !== undefined && callback !== null) { 568 | if (req.status == 200 || req.status == 204) { 569 | callback('ok', RiakObject.fromRequest(this.bucket, this.key, this.client, req), req); 570 | } 571 | /* Uh-oh, we've got siblings! */ 572 | else if (req.status == 300) { 573 | var siblingData = RiakUtil.parseSiblings(req.getResponseHeader('Content-Type'), 574 | req.responseText); 575 | var vclock = req.getResponseHeader('X-Riak-Vclock'); 576 | var thisObject = this; 577 | var siblings = []; 578 | for (var i = 0; i < siblingData.length; i++) { 579 | var sd = siblingData[i]; 580 | var sib = RiakObject.fromMultipart(thisObject.bucket, thisObject.key, 581 | thisObject.client, vclock, sd); 582 | siblings.push(sib); 583 | } 584 | callback('siblings', siblings, req); 585 | } 586 | else { 587 | callback('failed', null, req); 588 | } 589 | } 590 | }; 591 | 592 | /** End RiakObject Internals **/ 593 | 594 | /** 595 | * Models a Riak bucket 596 | * @param bucket - Riak bucket name 597 | * @param client - RiakClient reference 598 | */ 599 | function RiakBucket(bucket, client, props) { 600 | if (client === undefined) { 601 | throw("Cannot construct RiakBucket without client reference"); 602 | } 603 | this.name = bucket; 604 | this.client = client; 605 | if (props == undefined) { 606 | this.props = {}; 607 | } 608 | else { 609 | this.props = props.props; 610 | } 611 | }; 612 | 613 | /** 614 | * "Hydrates" a RiakBucket from a HTTP request 615 | * @param bucketName - Riak bucket name (duh!) 616 | * @param client - RiakClient object 617 | * @param req - Active XMLHttpRequest object 618 | * @return populated RiakBucket instance 619 | */ 620 | RiakBucket.fromRequest = function(bucketName, client, req) { 621 | var props = JSON.parse(req.responseText); 622 | return new RiakBucket(bucketName, client, props); 623 | }; 624 | 625 | /** 626 | * Begins building a map/reduce job which will 627 | * use the entire bucket contents as input 628 | * @param options - Hash description the map phase 629 | * @return RiakMapper object 630 | */ 631 | RiakBucket.prototype.map = function(options) { 632 | var mapper = new RiakMapper(this.client, this.name); 633 | return mapper.map(options); 634 | }; 635 | 636 | /** 637 | * Begins building a map/reduce job which will 638 | * use the entire bucket contents as input 639 | * @param options - Hash description the reduce phase 640 | * @return RiakMapper object 641 | */ 642 | RiakBucket.prototype.reduce = function(options) { 643 | var mapper = new RiakMapper(this.client, this.name); 644 | return mapper.reduce(options); 645 | }; 646 | 647 | /** 648 | * Begins building a map/reduce job which will 649 | * use the entire bucket contents as input 650 | * @param options - Hash description the link phase 651 | * @return RiakMapper object 652 | */ 653 | RiakBucket.prototype.link = function(options) { 654 | var mapper = new RiakMapper(this.client, this.name); 655 | options.bucket = this.name; 656 | return mapper.link(options); 657 | }; 658 | 659 | /** 660 | * Sets/gets the nValue for this bucket 661 | * @param n -- New nValue (optional) 662 | * @return the current nValue 663 | */ 664 | RiakBucket.prototype.nValue = function(n) { 665 | var retval = this.props.n_val; 666 | if (n !== undefined) { 667 | this.props.n_val = n; 668 | retval = n; 669 | } 670 | return retval; 671 | }; 672 | 673 | /** 674 | * Enables/disables multiple bucket entries 675 | * @param flag -- true or false 676 | * @return the current setting 677 | */ 678 | RiakBucket.prototype.allowsMultiples = function(flag) { 679 | var retval = this.props.allow_mult; 680 | if (flag !== undefined) { 681 | this.props.allow_mult = flag; 682 | retval = flag; 683 | } 684 | return retval; 685 | }; 686 | 687 | /** 688 | * Stores bucket 689 | * @param callback - Function to call when op has completed 690 | * 691 | * callback - function(bucket, request) 692 | * @param bucket - Updated bucket or null if store failed 693 | * @param request - XMLHTTPRequest object 694 | */ 695 | RiakBucket.prototype.store = function(callback) { 696 | var bucket = this; 697 | var currentProps = {}; 698 | currentProps.props = this.props; 699 | jQuery.ajax({url: this.client._buildPath('PUT', this.name), 700 | type: 'PUT', 701 | data: JSON.stringify(currentProps), 702 | contentType: 'application/json', 703 | dataType: 'text', 704 | beforeSend: function(req) { 705 | req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId); 706 | }, 707 | complete: function(req, statusText) { bucket._store(req, callback); } }); 708 | }; 709 | 710 | /** 711 | * Fetch an entry from the bucket 712 | * @param key - Riak bucket key 713 | * @param callback - Function to call when op has completed 714 | * 715 | * callback - function(object, request) 716 | * @param object - RiakObject if found, otherwise null 717 | * @param request - XMLHTTPRequest object 718 | */ 719 | RiakBucket.prototype.get = function(key, callback) { 720 | var bucket = this; 721 | jQuery.ajax({url: this.client._buildPath('GET', this.name, key), 722 | type: 'GET', 723 | accepts: RiakUtil.multipart_accepts(), 724 | dataType: 'multipart', 725 | beforeSend: function(req) { 726 | req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId); 727 | }, 728 | complete: function(req, statusText) { bucket._handleGetObject(key, req, callback, false); } }); 729 | }; 730 | 731 | /** 732 | * Fetch an entry from the bucket or create a new one 733 | * if not found 734 | * @param key - Riak bucket key 735 | * @param callback - Function to call when op has completed 736 | * 737 | * callback - function(object, request) 738 | * @param object - RiakObject instance 739 | * @param request - XMLHTTPRequest object 740 | */ 741 | RiakBucket.prototype.get_or_new = function(key, callback) { 742 | var bucket = this; 743 | jQuery.ajax({url: this.client._buildPath('GET', this.name, key), 744 | type: 'GET', 745 | accepts: RiakUtil.multipart_accepts(), 746 | dataType: 'multipart', 747 | beforeSend: function(req) { 748 | req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId); 749 | }, 750 | complete: function(req, statusText) { bucket._handleGetObject(key, req, callback, true); } }); 751 | }; 752 | 753 | /** 754 | * Deletes an object from a Riak bucket 755 | * @param key - Riak bucket key 756 | * @param callback - Function to call when op complete 757 | * 758 | * callback - function(success, request) 759 | * @param success - Boolean flag indicating successful removal 760 | * @param request - XMLHTTPRequest object 761 | */ 762 | RiakBucket.prototype.remove = function(key, callback) { 763 | var bucket = this; 764 | jQuery.ajax({url: this.client._buildPath('DELETE', bucket.name, key), 765 | type: 'DELETE', 766 | accepts: RiakUtil.multipart_accepts(), 767 | dataType: 'multipart', 768 | beforeSend: function(req) { req.setRequestHeader('X-Riak-ClientId', bucket.client.clientId); 769 | if (bucket.vclock !== undefined && bucket.vclock !== null) { 770 | req.setRequestHeader('X-Riak-Vclock', bucket.vclock); 771 | } 772 | }, 773 | complete: function(req, statusText) { if (callback !== undefined) { 774 | if (RiakUtil.wasSuccessful(req)) { 775 | callback(true, req); 776 | } 777 | else { 778 | callback(false, req); 779 | } 780 | } }}); 781 | }; 782 | 783 | /** Start RiakBucket internals **/ 784 | 785 | RiakBucket.prototype._store = function(req, callback) { 786 | if (req.readyState != 4) { 787 | return; 788 | } 789 | if (callback !== undefined) { 790 | if (req.status == 204) { 791 | this.client.bucket(this.name, callback); 792 | } 793 | else { 794 | callback(null, req); 795 | } 796 | } 797 | }; 798 | 799 | RiakBucket.prototype._handleGetObject = function(key, req, callback, createEmpty) { 800 | if (req.readyState != 4) { 801 | return; 802 | } 803 | var status = 'failed'; 804 | var object = null; 805 | if (callback !== null && callback !== undefined) { 806 | if (req.status == 200) { 807 | status = 'ok'; 808 | object = RiakObject.fromRequest(this.name, key, this.client, req); 809 | } 810 | else if ((req.status == 0 || req.status == 404)) 811 | { 812 | if (createEmpty === true) { 813 | status = 'ok'; 814 | object = new RiakObject(this.name, key, this.client); 815 | } 816 | /* must not create empty return failed/null */ 817 | } 818 | /* Uh-oh, we've got siblings! */ 819 | else if (req.status == 300) { 820 | var siblingData = RiakUtil.parseSiblings(req.getResponseHeader('Content-Type'), 821 | req.responseText); 822 | var vclock = req.getResponseHeader('X-Riak-Vclock'); 823 | var thisBucket = this; 824 | var siblings = []; 825 | for (var i = 0; i < siblingData.length; i++) { 826 | var sd = siblingData[i]; 827 | var sib = RiakObject.fromMultipart(thisBucket.name, key, 828 | thisBucket.client, vclock, sd); 829 | siblings.push(sib); 830 | } 831 | status = 'siblings' 832 | object = siblings; 833 | } 834 | callback(status, object, req); 835 | } 836 | }; 837 | 838 | /** End RiakBucket internals **/ 839 | 840 | 841 | /** 842 | * Entry point for interacting with Riak 843 | * @param baseUrl - URL for 'raw' interface (optional, default: '/riak') 844 | * @param mapredUrl - URL for map/reduce jobs (optional, default: '/mapred') 845 | */ 846 | function RiakClient(baseUrl, mapredUrl) { 847 | if (baseUrl === undefined) { 848 | baseUrl = '/riak/'; 849 | } 850 | else { 851 | if (baseUrl[0] !== '/') { 852 | baseUrl = '/' + baseUrl; 853 | } 854 | if ((baseUrl.slice(-1) !== '/')) { 855 | baseUrl += '/'; 856 | } 857 | } 858 | this.baseUrl = baseUrl; 859 | this.clientId = "js_" + RiakUtil.base64Encode(Math.floor(Math.random() * 4294967296)); 860 | if (mapredUrl !== undefined) { 861 | this.mapredUrl = mapredUrl; 862 | } 863 | else { 864 | this.mapredUrl = '/mapred'; 865 | } 866 | }; 867 | 868 | /** 869 | * Fetches a bucket from Riak 870 | * Buckets *always* exist so no need to handle 871 | * @param bucket Riak bucket name 872 | * @param callback Function to call when op completes 873 | * 874 | * callback - function(bucket, request) 875 | * @param bucket - RiakBucket instance 876 | * @param request - XMLHTTPRequest object 877 | */ 878 | RiakClient.prototype.bucket = function(bucket, callback) { 879 | var client = this; 880 | jQuery.ajax({url: this._buildPath('GET', bucket), 881 | type: 'GET', 882 | contentType: 'application/json', 883 | dataType: 'text', 884 | beforeSend: function(req) { 885 | req.setRequestHeader('X-Riak-ClientId', this.clientId); 886 | }, 887 | complete: function(req, statusText) { client._handleGetBucket(bucket, req, callback, false); } }); 888 | }; 889 | 890 | /** Begin RiakClient internal functions **/ 891 | RiakClient.prototype._handleGetBucket = function(bucketName, req, callback, createEmpty) { 892 | var bucket = null; 893 | if (req.readyState != 4) { 894 | return; 895 | } 896 | if (callback !== undefined) { 897 | if (req.status == 200) { 898 | bucket = RiakBucket.fromRequest(bucketName, this, req); 899 | } 900 | callback(bucket, req); 901 | } 902 | }; 903 | 904 | RiakClient.prototype._buildPath = function(method, bucket, key) { 905 | var path = this.baseUrl + bucket; 906 | /* Reluctantly adding a cache breaker to each request. FireFox 907 | ** sometimes caches XHR responses which triggers failures in the 908 | ** unit tests (and presumably real code). See 'bypassing the cache' 909 | ** in https://developer-stage.mozilla.org/En/Using_XMLHttpRequest 910 | */ 911 | var cache_breaker = Math.floor(Math.random() * 4294967296).toString(); 912 | if (key !== undefined) { 913 | path = path + '/' + key + "?" + cache_breaker; 914 | if (method === 'PUT') { 915 | path = path + '&returnbody=true'; 916 | } 917 | } 918 | else { 919 | path = path + "?" + cache_breaker; 920 | if (method === 'GET') { 921 | path = path + '&keys=false'; 922 | } 923 | } 924 | return path; 925 | }; 926 | 927 | /** End RiakClient internal Functions **/ 928 | -------------------------------------------------------------------------------- /yak/styles.css: -------------------------------------------------------------------------------- 1 | /* Generic top-level elements */ 2 | html, body { 3 | height: 100%; 4 | min-height: 100%; 5 | font-family: Helvetica, Arial, sans-serif; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | /* Layout top-level elements */ 11 | h1 { 12 | font-size: 14px; 13 | line-height: 1em; 14 | min-width: 99%; 15 | max-width: 100%; 16 | max-height: 5%; 17 | margin: 0; 18 | padding: 5px; 19 | background-color: #cdd; 20 | } 21 | 22 | ol#chatlog { 23 | margin:0; 24 | height: 75%; 25 | min-height: 75%; 26 | max-height: 75%; 27 | overflow-y: auto; 28 | overflow-x: hidden; 29 | padding: 0 3px; 30 | } 31 | 32 | form#chatbox { 33 | height: 75px; 34 | min-height: 75px; 35 | width: 100%; 36 | min-width: 100%; 37 | position: fixed; 38 | bottom: 0; 39 | } 40 | 41 | form#login { 42 | width: 300px; 43 | margin: 20% auto 0; 44 | } 45 | 46 | /* Timeline styles */ 47 | ol#chatlog li { 48 | margin: 0; 49 | border-bottom: 1px solid #d8d8d8; 50 | list-style-type: none; 51 | padding: 3px; 52 | float: left; 53 | min-width: 99%; 54 | max-width: 100%; 55 | width: 100%; 56 | clear: both; 57 | font-size: 14px; 58 | } 59 | 60 | ol#chatlog li span.timestamp { 61 | float:right; 62 | color: #999; 63 | font-size: 10px; 64 | margin: 0 3px; 65 | } 66 | 67 | ol#chatlog li span.name { 68 | -moz-border-radius: 5px; 69 | -webkit-border-radius: 5px; 70 | border-radius: 5px; 71 | background-color: #ddd; 72 | font-size: 12px; 73 | padding: 1px 6px; 74 | margin-right: 10px; 75 | } 76 | 77 | ol#chatlog li img { 78 | float: left; 79 | padding: 1px; 80 | margin: 0 3px 0 0; 81 | border: 1px solid #eee; 82 | } 83 | 84 | ol#chatlog li.me { 85 | background-color: #cfc; 86 | border-bottom: 1px solid #696; 87 | } 88 | 89 | ol#chatlog li.me span.name { 90 | color: white; 91 | background-color: #060; 92 | } 93 | 94 | ol#chatlog li.me span.timestamp { 95 | color: #696; 96 | } 97 | 98 | /* Generic form elements */ 99 | form ol { 100 | list-style-type: none; 101 | padding: 0; 102 | } 103 | 104 | form ol li { 105 | padding: 0; 106 | margin-bottom: 0.5em; 107 | } 108 | 109 | fieldset { 110 | border: none; 111 | -moz-border-radius: 5px; 112 | -webkit-border-radius: 5px; 113 | border-radius: 5px; 114 | background-color: #dee; 115 | } 116 | 117 | fieldset legend { 118 | background-color: #cdd; 119 | -moz-border-radius: 5px; 120 | -webkit-border-radius: 5px; 121 | border-radius: 5px; 122 | padding: 2px 5px; 123 | } 124 | 125 | label { 126 | display: block; 127 | font-size: 80%; 128 | font-weight: bold; 129 | } 130 | 131 | label + input { 132 | font-size: 18px; 133 | } 134 | 135 | input#message { 136 | width: 99%; 137 | max-width: 100%; 138 | } 139 | -------------------------------------------------------------------------------- /yak/yakriak.js: -------------------------------------------------------------------------------- 1 | Function.prototype.bind = function(target){ 2 | var func = this; 3 | return function(){ return func.apply(target, arguments); }; 4 | } 5 | 6 | var YakRiak = function(){ 7 | this.interval = 1000; // Polling interval: 1s (Use caution!) 8 | this.since = new Date().getTime() - (60 * 60 * 1000); // Scope to the last hour initially 9 | this.client = new RiakClient(); 10 | this.bucket = new RiakBucket('messages', this.client); 11 | window.onunload = this.stop.bind(this); 12 | }; 13 | 14 | YakRiak.prototype.poll = function(){ 15 | this.bucket. 16 | map({"bucket":"yakmr", "key":"mapMessageSince", "arg":this.since}). 17 | reduce({"bucket":"yakmr", "key":"reduceSortTimestamp", "keep":true}). 18 | run(this._poll.bind(this)); 19 | }; 20 | 21 | YakRiak.prototype.initialPoll = function(){ 22 | this.bucket. 23 | map({"bucket":"yakmr", "key":"mapMessageSince", "arg":this.since}). 24 | reduce({"bucket":"yakmr", "key":"reduceSortTimestamp"}). 25 | reduce({"bucket":"yakmr", "key":"reduceLimitLastN", "arg":25, "keep":true}). 26 | run(this._poll.bind(this)); 27 | } 28 | 29 | YakRiak.prototype._poll = function(successful, data, request){ 30 | var yakriak = this; 31 | if(successful){ 32 | var last_item = data[data.length - 1]; 33 | if(last_item && last_item.timestamp) 34 | this.since = last_item.timestamp + 0.01; // Try to avoid duplicates on next poll 35 | $.each(data, this.displayMessage.bind(this)); 36 | } 37 | this.pollingTimeout = setTimeout(function(){ yakriak.poll(); }, this.randomInterval()); 38 | }; 39 | 40 | // Should help prevent dogpile effects 41 | YakRiak.prototype.randomInterval = function(){ 42 | return Math.round((Math.random()-0.5)*this.interval/2 + this.interval); 43 | }; 44 | 45 | YakRiak.prototype.displayMessage = function(index, item){ 46 | if($('#' + item.key).length == 0){ 47 | var elem = $('
  • '); 48 | var avatar = $('').attr('src', 'http://gravatar.com/avatar/' + item.gravatar + '?s=40'); 49 | var name = $('').addClass('name').html(item.name); 50 | var message = $('').addClass('message').html(item.message); 51 | var timestamp = $('').addClass('timestamp').text(new Date(item.timestamp).toLocaleTimeString()); 52 | elem.append(timestamp).append(avatar).append(name).append(message); 53 | if(item.name == this.name && item.gravatar == this.gravatar) 54 | elem.addClass('me'); 55 | $('ol#chatlog').append(elem).attr({ scrollTop: $('ol#chatlog').attr("scrollHeight") }); 56 | } 57 | } 58 | 59 | YakRiak.prototype.postMessage = function(message){ 60 | var yakriak = this; 61 | message = $.trim(message); 62 | if(message.length > 0){ 63 | var key = hex_md5(this.client.clientId + new Date().toString()); 64 | var object = new RiakObject('messages', key, this.client, undefined, 'application/json'); 65 | object.body = { 66 | 'key': key, 67 | 'message': this.escape(message), 68 | 'name': this.escape(this.name), 69 | 'gravatar': this.gravatar, 70 | 'timestamp': new Date().getTime() 71 | }; 72 | object.store(function(){ yakriak.displayMessage(null, object.body) }); 73 | } 74 | $('form#chatbox').get(0).reset(); 75 | }; 76 | 77 | YakRiak.prototype.escape = function(string){ 78 | return string.replace(/&/g, '&').replace(//g, '>'); 79 | } 80 | 81 | YakRiak.prototype.start = function(name, email){ 82 | this.name = name; 83 | this.gravatar = (email && email.indexOf('@') != -1) ? hex_md5(email) : email; 84 | if($.trim(this.name).length != 0){ 85 | if(Cookie.accept()){ 86 | Cookie.set('yakriak.name', this.name, 4); 87 | Cookie.set('yakriak.gravatar', this.gravatar, 4); 88 | } 89 | $('form#login').hide(); 90 | $('ol#chatlog, form#chatbox').show(); 91 | this.initialPoll(); 92 | } else { 93 | alert("Please enter a name for yourself. An email would be nice too (not sent over the wire)."); 94 | } 95 | }; 96 | 97 | YakRiak.prototype.stop = function(){ 98 | clearTimeout(this.pollingTimeout); 99 | }; 100 | 101 | (function($){ 102 | $(function(){ 103 | var yakriak = new YakRiak(); 104 | if(Cookie.accept()){ 105 | var name = Cookie.get('yakriak.name'); 106 | var gravatar = Cookie.get('yakriak.gravatar'); 107 | if(name) 108 | yakriak.start(name, gravatar); 109 | } 110 | $('form#login').submit(function(e){ 111 | e.preventDefault(); 112 | yakriak.start($('#name').val(), $('#email').val()); 113 | return false; 114 | }); 115 | $('form#chatbox').submit(function(e){ 116 | e.preventDefault(); 117 | yakriak.postMessage($('#message').val()); 118 | }); 119 | }); 120 | })(jQuery); 121 | -------------------------------------------------------------------------------- /yakmr/mapMessageSince: -------------------------------------------------------------------------------- 1 | function(object, keyData, arg){ 2 | var value = Riak.mapValuesJson(object)[0]; 3 | if(value.timestamp >= arg) 4 | return [value]; 5 | else 6 | return []; 7 | } 8 | -------------------------------------------------------------------------------- /yakmr/reduceLimitLastN: -------------------------------------------------------------------------------- 1 | function(values, arg){ 2 | return values.slice(-arg); 3 | } 4 | -------------------------------------------------------------------------------- /yakmr/reduceSortTimestamp: -------------------------------------------------------------------------------- 1 | function(values){ 2 | return Riak.filterNotFound(values). 3 | sort(function(a,b){ 4 | return a.timestamp - b.timestamp; 5 | }); 6 | } 7 | --------------------------------------------------------------------------------