├── _id ├── language ├── couchapp.json ├── _attachments ├── images │ └── icon.png ├── index.html ├── style │ └── main.css └── script │ ├── app.js │ ├── sha1.js │ ├── json2.js │ └── jquery.couch.js ├── vendor └── couchapp │ ├── metadata.json │ └── _attachments │ ├── jquery.couchForm.js │ ├── jquery.couch.app.util.js │ ├── jquery.couchProfile.js │ ├── jquery.couchLogin.js │ ├── jquery.couch.app.js │ ├── md5.js │ └── jquery.mustache.js ├── views └── recent-items │ └── map.js └── README.md /_id: -------------------------------------------------------------------------------- 1 | _design/grocery -------------------------------------------------------------------------------- /language: -------------------------------------------------------------------------------- 1 | javascript -------------------------------------------------------------------------------- /couchapp.json: -------------------------------------------------------------------------------- 1 | {"name": "Grocery", "description": "Sync your grocery list."} -------------------------------------------------------------------------------- /_attachments/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchris/GrocerySync-CouchApp/master/_attachments/images/icon.png -------------------------------------------------------------------------------- /vendor/couchapp/metadata.json: -------------------------------------------------------------------------------- 1 | {"name": "couchapp", "fetch_uri": "git://github.com/couchapp/couchapp.git", "description": "official couchapp vendor"} -------------------------------------------------------------------------------- /views/recent-items/map.js: -------------------------------------------------------------------------------- 1 | function(doc) { 2 | if (doc.created_at) { 3 | var date = new Date(doc.created_at.replace(/-/g, "/")); 4 | 5 | emit(((date.getTime() && date) || new Date(doc.created_at)), { 6 | text:doc.text, 7 | check: doc.check, 8 | id : doc._id 9 | }); 10 | } 11 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Generated CouchApp 2 | 3 | This is meant to be an example CouchApp and to ship with most of the CouchApp goodies. 4 | 5 | Install with 6 | 7 | couchapp push . http://localhost:5984/grocery-sync 8 | 9 | or (if you have security turned on) 10 | 11 | couchapp push . http://myname:mypass@localhost:5984/proto 12 | 13 | You can also create this app by running 14 | 15 | couchapp generate proto && cd proto 16 | couchapp push . http://localhost:5984/proto 17 | 18 | ## Todo 19 | 20 | * factor CouchApp Commonjs to jquery.couch.require.js 21 | * use $.couch.app in app.js 22 | 23 | ## License 24 | 25 | Apache 2.0 -------------------------------------------------------------------------------- /_attachments/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Grocery List 5 | 6 | 7 | 8 |
9 | 10 |

Grocery List

11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 37 | 38 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couchForm.js: -------------------------------------------------------------------------------- 1 | // I think this should go in jquery.couch.js 2 | 3 | (function($) { 4 | $.fn.couchForm = function(opts) { 5 | opts = opts || {}; 6 | if (!opts.db) { 7 | opts.db = $.couch.db(document.location.pathname.split('/')[1]); 8 | } 9 | var form = $(this); 10 | form.submit(function(e) { 11 | e.preventDefault(); 12 | var doc = form.serializeObject(); 13 | if (opts.beforeSave) { 14 | doc = opts.beforeSave(doc); 15 | } 16 | opts.db.saveDoc(doc, { 17 | success : function() { 18 | if (opts.success) { 19 | opts.success(doc); 20 | } 21 | form[0].reset(); 22 | } 23 | }); 24 | return false; 25 | }); 26 | }; 27 | // friendly helper http://tinyurl.com/6aow6yn 28 | $.fn.serializeObject = function() { 29 | var o = {}; 30 | var a = this.serializeArray(); 31 | $.each(a, function() { 32 | if (o[this.name]) { 33 | if (!o[this.name].push) { 34 | o[this.name] = [o[this.name]]; 35 | } 36 | o[this.name].push(this.value || ''); 37 | } else { 38 | o[this.name] = this.value || ''; 39 | } 40 | }); 41 | return o; 42 | }; 43 | })(jQuery); 44 | -------------------------------------------------------------------------------- /_attachments/style/main.css: -------------------------------------------------------------------------------- 1 | /* add styles here */ 2 | 3 | body { 4 | font:1em Helvetica, sans-serif; 5 | background: #EEEDE7; 6 | } 7 | 8 | h1 { 9 | margin-top:0; 10 | } 11 | 12 | #site_header { 13 | color: #940303; 14 | font-size: 350%; 15 | margin-bottom: 0.2em; 16 | } 17 | #site_header img { 18 | border:1px solid white; 19 | margin: 0.2em 0.5em; 20 | vertical-align: middle; 21 | } 22 | 23 | #account { 24 | float:right; 25 | } 26 | 27 | #profile { 28 | border:4px solid #edd; 29 | background:#fee; 30 | padding:8px; 31 | margin-bottom:8px; 32 | } 33 | 34 | #content { 35 | padding:8px; 36 | width:60%; 37 | float:left; 38 | } 39 | 40 | #sidebar { 41 | padding:8px; 42 | float:right; 43 | width:30%; 44 | } 45 | 46 | #items li, #profile { 47 | background: none repeat scroll 0 0 #F7F7F7; 48 | border: 4px solid #DEDED2; 49 | border-radius: 10px 10px 10px 10px; 50 | box-shadow: 1px 1px 5px #333333 inset; 51 | margin: 4px 30px; 52 | padding: 8px; 53 | } 54 | 55 | #items li.checked { 56 | background: none repeat scroll 0 0 #ddd; 57 | } 58 | 59 | form { 60 | padding:4px; 61 | margin:6px; 62 | } 63 | form input { 64 | font-size:120%; 65 | padding:0.2em; 66 | } 67 | 68 | div.avatar { 69 | padding:2px; 70 | padding-bottom:0; 71 | margin-right:4px; 72 | float:left; 73 | font-size:0.78em; 74 | width : 60px; 75 | height : 60px; 76 | text-align: center; 77 | } 78 | 79 | div.avatar .name { 80 | padding-top:2px; 81 | } 82 | 83 | div.avatar img { 84 | margin:0 auto; 85 | padding:0; 86 | width : 40px; 87 | height : 40px; 88 | } 89 | 90 | ul { 91 | list-style: none; 92 | } 93 | -------------------------------------------------------------------------------- /_attachments/script/app.js: -------------------------------------------------------------------------------- 1 | // Apache 2.0 J Chris Anderson 2011 2 | $(function() { 3 | var path = unescape(document.location.pathname).split('/'), 4 | design = path[3], 5 | db = $.couch.db(path[1]); 6 | 7 | function drawItems() { 8 | db.view(design + "/recent-items", { 9 | descending : "true", 10 | limit : 50, 11 | update_seq : true, 12 | success : function(data) { 13 | setupChanges(data.update_seq); 14 | var them = $.mustache($("#recent-messages").html(), { 15 | items : data.rows.map(function(r) {return r.value;}) 16 | }); 17 | $("#content").html(them); 18 | } 19 | }); 20 | }; 21 | 22 | $("li input").live("click", function(e) { 23 | var li = $(this).parents("li") 24 | var docid = li.attr("id"); 25 | li.toggleClass("checked"); 26 | db.openDoc(docid, {success : function(doc) { 27 | doc.check = e.currentTarget.checked 28 | db.saveDoc(doc) 29 | }}); 30 | }); 31 | 32 | drawItems(); 33 | var changesRunning = false; 34 | function setupChanges(since) { 35 | if (!changesRunning) { 36 | var changeHandler = db.changes(since); 37 | changesRunning = true; 38 | changeHandler.onChange(drawItems); 39 | } 40 | } 41 | 42 | $("#create-message").couchForm({ 43 | beforeSave : function(doc) { 44 | var top = $("li:first")[0]; 45 | var newid = top ? top.id : '3'; 46 | doc._id = newid.substr(0,40) + Math.random(); 47 | doc.created_at = new Date(); 48 | doc.check = false; 49 | return doc; 50 | } 51 | }); 52 | $("#create-message").find("input").focus(); 53 | }); -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couch.app.util.js: -------------------------------------------------------------------------------- 1 | $.log = function(m) { 2 | if (window && window.console && window.console.log) { 3 | window.console.log(arguments.length == 1 ? m : arguments); 4 | } 5 | }; 6 | 7 | // http://stackoverflow.com/questions/1184624/serialize-form-to-json-with-jquery/1186309#1186309 8 | $.fn.serializeObject = function() { 9 | var o = {}; 10 | var a = this.serializeArray(); 11 | $.each(a, function() { 12 | if (o[this.name]) { 13 | if (!o[this.name].push) { 14 | o[this.name] = [o[this.name]]; 15 | } 16 | o[this.name].push(this.value || ''); 17 | } else { 18 | o[this.name] = this.value || ''; 19 | } 20 | }); 21 | return o; 22 | }; 23 | 24 | // todo remove this crap 25 | function escapeHTML(st) { 26 | return( 27 | st && st.replace(/&/g,'&'). 28 | replace(/>/g,'>'). 29 | replace(/'+a+''; 42 | }).replace(/\@([\w\-]+)/g,function(user,name) { 43 | return ''+user+''; 44 | }).replace(/\#([\w\-\.]+)/g,function(word,tag) { 45 | return ''+word+''; 46 | }); 47 | }; 48 | 49 | $.fn.prettyDate = function() { 50 | $(this).each(function() { 51 | var string, title = $(this).attr("title"); 52 | if (title) { 53 | string = $.prettyDate(title); 54 | } else { 55 | string = $.prettyDate($(this).text()); 56 | } 57 | $(this).text(string); 58 | }); 59 | }; 60 | 61 | $.prettyDate = function(time){ 62 | 63 | var date = new Date(time.replace(/-/g,"/").replace("T", " ").replace("Z", " +0000").replace(/(\d*\:\d*:\d*)\.\d*/g,"$1")), 64 | diff = (((new Date()).getTime() - date.getTime()) / 1000), 65 | day_diff = Math.floor(diff / 86400); 66 | 67 | if (isNaN(day_diff)) return time; 68 | 69 | return day_diff < 1 && ( 70 | diff < 60 && "just now" || 71 | diff < 120 && "1 minute ago" || 72 | diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || 73 | diff < 7200 && "1 hour ago" || 74 | diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || 75 | day_diff == 1 && "yesterday" || 76 | day_diff < 21 && day_diff + " days ago" || 77 | day_diff < 45 && Math.ceil( day_diff / 7 ) + " weeks ago" || 78 | time; 79 | // day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" || 80 | // Math.ceil( day_diff / 365 ) + " years ago"; 81 | }; 82 | 83 | $.argsToArray = function(args) { 84 | if (!args.callee) return args; 85 | var array = []; 86 | for (var i=0; i < args.length; i++) { 87 | array.push(args[i]); 88 | }; 89 | return array; 90 | } 91 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couchProfile.js: -------------------------------------------------------------------------------- 1 | // Copyright Chris Anderson 2011 2 | // Apache 2.0 License 3 | // jquery.couchProfile.js 4 | // depends on md5, 5 | // jquery.couchLogin.js and requires.js 6 | // 7 | // Example Usage (loggedIn and loggedOut callbacks are optional): 8 | // $("#myprofilediv").couchProfile({ 9 | // profileReady : function(profile) { 10 | // alert("hello, do you look like this? "+profile.gravatar_url); 11 | // } 12 | // }); 13 | 14 | (function($) { 15 | $.couchProfile = {}; 16 | $.couchProfile.templates = { 17 | profileReady : '
{{#gravatar_url}}{{/gravatar_url}}
{{nickname}}

Hello {{nickname}}!

', 18 | newProfile : '

Hello {{name}}, Please setup your user profile.

' 19 | }; 20 | 21 | $.fn.couchProfile = function(session, opts) { 22 | opts = opts || {}; 23 | var templates = $.couchProfile.templates; 24 | var userCtx = session.userCtx; 25 | var widget = $(this); 26 | // load the profile from the user doc 27 | var db = $.couch.db(session.info.authentication_db); 28 | var userDocId = "org.couchdb.user:"+userCtx.name; 29 | db.openDoc(userDocId, { 30 | success : function(userDoc) { 31 | var profile = userDoc["couch.app.profile"]; 32 | if (profile) { 33 | profile.name = userDoc.name; 34 | profileReady(profile); 35 | } else { 36 | newProfile(userCtx) 37 | } 38 | } 39 | }); 40 | 41 | function profileReady(profile) { 42 | widget.html($.mustache(templates.profileReady, profile)); 43 | if (opts.profileReady) {opts.profileReady(profile)}; 44 | }; 45 | 46 | function storeProfileOnUserDoc(newProfile) { 47 | // store the user profile on the user account document 48 | $.couch.userDb(function(db) { 49 | var userDocId = "org.couchdb.user:"+userCtx.name; 50 | db.openDoc(userDocId, { 51 | success : function(userDoc) { 52 | userDoc["couch.app.profile"] = newProfile; 53 | db.saveDoc(userDoc, { 54 | success : function() { 55 | newProfile.name = userDoc.name; 56 | profileReady(newProfile); 57 | } 58 | }); 59 | } 60 | }); 61 | }); 62 | }; 63 | 64 | function newProfile(userCtx) { 65 | widget.html($.mustache(templates.newProfile, userCtx)); 66 | widget.find("form").submit(function(e) { 67 | e.preventDefault(); 68 | var form = this; 69 | var name = $("input[name=userCtxName]",form).val(); 70 | var newProfile = { 71 | rand : Math.random().toString(), 72 | nickname : $("input[name=nickname]",form).val(), 73 | email : $("input[name=email]",form).val(), 74 | url : $("input[name=url]",form).val() 75 | }; 76 | // setup gravatar_url if md5.js is loaded 77 | if (hex_md5) { 78 | newProfile.gravatar_url = 'http://www.gravatar.com/avatar/'+hex_md5(newProfile.email || newProfile.rand)+'.jpg?s=40&d=identicon'; 79 | } 80 | storeProfileOnUserDoc(newProfile); 81 | return false; 82 | }); 83 | }; 84 | } 85 | })(jQuery); 86 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couchLogin.js: -------------------------------------------------------------------------------- 1 | // Copyright Chris Anderson 2011 2 | // Apache 2.0 License 3 | // jquery.couchLogin.js 4 | // 5 | // Example Usage (loggedIn and loggedOut callbacks are optional): 6 | // $("#mylogindiv").couchLogin({ 7 | // loggedIn : function(userCtx) { 8 | // alert("hello "+userCtx.name); 9 | // }, 10 | // loggedOut : function() { 11 | // alert("bye bye"); 12 | // } 13 | // }); 14 | 15 | (function($) { 16 | $.fn.couchLogin = function(opts) { 17 | var elem = $(this); 18 | opts = opts || {}; 19 | function initWidget() { 20 | $.couch.session({ 21 | success : function(session) { 22 | var userCtx = session.userCtx; 23 | if (userCtx.name) { 24 | elem.empty(); 25 | elem.append(loggedIn(session)); 26 | if (opts.loggedIn) {opts.loggedIn(session)} 27 | } else if (userCtx.roles.indexOf("_admin") != -1) { 28 | elem.html(templates.adminParty); 29 | } else { 30 | elem.html(templates.loggedOut); 31 | if (opts.loggedOut) {opts.loggedOut()} 32 | }; 33 | } 34 | }); 35 | }; 36 | initWidget(); 37 | function doLogin(name, pass) { 38 | $.couch.login({name:name, password:pass, success:initWidget}); 39 | }; 40 | elem.delegate("a[href=#signup]", "click", function() { 41 | elem.html(templates.signupForm); 42 | elem.find('input[name="name"]').focus(); 43 | return false; 44 | }); 45 | elem.delegate("a[href=#login]", "click", function() { 46 | elem.html(templates.loginForm); 47 | elem.find('input[name="name"]').focus(); 48 | return false; 49 | }); 50 | elem.delegate("a[href=#logout]", "click", function() { 51 | $.couch.logout({success : initWidget}); 52 | return false; 53 | }); 54 | elem.delegate("form.login", "submit", function() { 55 | doLogin($('input[name=name]', this).val(), 56 | $('input[name=password]', this).val()); 57 | return false; 58 | }); 59 | elem.delegate("form.signup", "submit", function() { 60 | var name = $('input[name=name]', this).val(), 61 | pass = $('input[name=password]', this).val(); 62 | $.couch.signup({name : name}, pass, { 63 | success : function() {doLogin(name, pass)} 64 | }); 65 | return false; 66 | }); 67 | } 68 | var templates = { 69 | adminParty : '

Admin party, everyone is admin! Fix this in Futon before proceeding.

', 70 | loggedOut : 'Signup or Login', 71 | loginForm : '
or Signup
', 72 | signupForm : '
or Login
' 73 | }; 74 | function loggedIn(r) { 75 | var auth_db = encodeURIComponent(r.info.authentication_db) 76 | , uri_name = encodeURIComponent(r.userCtx.name) 77 | , span = $('Welcome ! Logout?'); 80 | $('a.name', span).text(r.userCtx.name); // you can get the user name here 81 | return span; 82 | } 83 | })(jQuery); 84 | -------------------------------------------------------------------------------- /_attachments/script/sha1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined 3 | * in FIPS PUB 180-1 4 | * Version 2.1a Copyright Paul Johnston 2000 - 2002. 5 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 6 | * Distributed under the BSD License 7 | * See http://pajhome.org.uk/crypt/md5 for details. 8 | */ 9 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} 23 | function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} 24 | function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} 25 | function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} 26 | function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} 27 | function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function sha1_vm_test() 33 | { 34 | return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; 35 | } 36 | 37 | /* 38 | * Calculate the SHA-1 of an array of big-endian words, and a bit length 39 | */ 40 | function core_sha1(x, len) 41 | { 42 | /* append padding */ 43 | x[len >> 5] |= 0x80 << (24 - len % 32); 44 | x[((len + 64 >> 9) << 4) + 15] = len; 45 | 46 | var w = Array(80); 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | var e = -1009589776; 52 | 53 | for(var i = 0; i < x.length; i += 16) 54 | { 55 | var olda = a; 56 | var oldb = b; 57 | var oldc = c; 58 | var oldd = d; 59 | var olde = e; 60 | 61 | for(var j = 0; j < 80; j++) 62 | { 63 | if(j < 16) w[j] = x[i + j]; 64 | else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); 65 | var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), 66 | safe_add(safe_add(e, w[j]), sha1_kt(j))); 67 | e = d; 68 | d = c; 69 | c = rol(b, 30); 70 | b = a; 71 | a = t; 72 | } 73 | 74 | a = safe_add(a, olda); 75 | b = safe_add(b, oldb); 76 | c = safe_add(c, oldc); 77 | d = safe_add(d, oldd); 78 | e = safe_add(e, olde); 79 | } 80 | return Array(a, b, c, d, e); 81 | 82 | } 83 | 84 | /* 85 | * Perform the appropriate triplet combination function for the current 86 | * iteration 87 | */ 88 | function sha1_ft(t, b, c, d) 89 | { 90 | if(t < 20) return (b & c) | ((~b) & d); 91 | if(t < 40) return b ^ c ^ d; 92 | if(t < 60) return (b & c) | (b & d) | (c & d); 93 | return b ^ c ^ d; 94 | } 95 | 96 | /* 97 | * Determine the appropriate additive constant for the current iteration 98 | */ 99 | function sha1_kt(t) 100 | { 101 | return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : 102 | (t < 60) ? -1894007588 : -899497514; 103 | } 104 | 105 | /* 106 | * Calculate the HMAC-SHA1 of a key and some data 107 | */ 108 | function core_hmac_sha1(key, data) 109 | { 110 | var bkey = str2binb(key); 111 | if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); 112 | 113 | var ipad = Array(16), opad = Array(16); 114 | for(var i = 0; i < 16; i++) 115 | { 116 | ipad[i] = bkey[i] ^ 0x36363636; 117 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 118 | } 119 | 120 | var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); 121 | return core_sha1(opad.concat(hash), 512 + 160); 122 | } 123 | 124 | /* 125 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 126 | * to work around bugs in some JS interpreters. 127 | */ 128 | function safe_add(x, y) 129 | { 130 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 131 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 132 | return (msw << 16) | (lsw & 0xFFFF); 133 | } 134 | 135 | /* 136 | * Bitwise rotate a 32-bit number to the left. 137 | */ 138 | function rol(num, cnt) 139 | { 140 | return (num << cnt) | (num >>> (32 - cnt)); 141 | } 142 | 143 | /* 144 | * Convert an 8-bit or 16-bit string to an array of big-endian words 145 | * In 8-bit function, characters >255 have their hi-byte silently ignored. 146 | */ 147 | function str2binb(str) 148 | { 149 | var bin = Array(); 150 | var mask = (1 << chrsz) - 1; 151 | for(var i = 0; i < str.length * chrsz; i += chrsz) 152 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); 153 | return bin; 154 | } 155 | 156 | /* 157 | * Convert an array of big-endian words to a string 158 | */ 159 | function binb2str(bin) 160 | { 161 | var str = ""; 162 | var mask = (1 << chrsz) - 1; 163 | for(var i = 0; i < bin.length * 32; i += chrsz) 164 | str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); 165 | return str; 166 | } 167 | 168 | /* 169 | * Convert an array of big-endian words to a hex string. 170 | */ 171 | function binb2hex(binarray) 172 | { 173 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 174 | var str = ""; 175 | for(var i = 0; i < binarray.length * 4; i++) 176 | { 177 | str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + 178 | hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); 179 | } 180 | return str; 181 | } 182 | 183 | /* 184 | * Convert an array of big-endian words to a base-64 string 185 | */ 186 | function binb2b64(binarray) 187 | { 188 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 189 | var str = ""; 190 | for(var i = 0; i < binarray.length * 4; i += 3) 191 | { 192 | var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) 193 | | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) 194 | | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); 195 | for(var j = 0; j < 4; j++) 196 | { 197 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; 198 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); 199 | } 200 | } 201 | return str; 202 | } 203 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.couch.app.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy 3 | // of the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | // Usage: The passed in function is called when the page is ready. 14 | // CouchApp passes in the app object, which takes care of linking to 15 | // the proper database, and provides access to the CouchApp helpers. 16 | // $.couch.app(function(app) { 17 | // app.db.view(...) 18 | // ... 19 | // }); 20 | 21 | (function($) { 22 | 23 | function Design(db, name, code) { 24 | this.doc_id = "_design/"+name; 25 | if (code) { 26 | this.code_path = this.doc_id + "/" + code; 27 | } else { 28 | this.code_path = this.doc_id; 29 | } 30 | this.view = function(view, opts) { 31 | db.view(name+'/'+view, opts); 32 | }; 33 | this.list = function(list, view, opts) { 34 | db.list(name+'/'+list, view, opts); 35 | }; 36 | } 37 | 38 | function docForm() { alert("docForm has been moved to vendor/couchapp/lib/docForm.js, use app.require to load") }; 39 | 40 | function resolveModule(path, names, parents, current) { 41 | parents = parents || []; 42 | if (names.length === 0) { 43 | if (typeof current != "string") { 44 | throw ["error","invalid_require_path", 45 | 'Must require a JavaScript string, not: '+(typeof current)]; 46 | } 47 | return [current, parents]; 48 | } 49 | var n = names.shift(); 50 | if (n == '..') { 51 | parents.pop(); 52 | var pp = parents.pop(); 53 | if (!pp) { 54 | throw ["error", "invalid_require_path", path]; 55 | } 56 | return resolveModule(path, names, parents, pp); 57 | } else if (n == '.') { 58 | var p = parents.pop(); 59 | if (!p) { 60 | throw ["error", "invalid_require_path", path]; 61 | } 62 | return resolveModule(path, names, parents, p); 63 | } else { 64 | parents = []; 65 | } 66 | if (!current[n]) { 67 | throw ["error", "invalid_require_path", path]; 68 | } 69 | parents.push(current); 70 | return resolveModule(path, names, parents, current[n]); 71 | } 72 | 73 | function makeRequire(ddoc) { 74 | var moduleCache = []; 75 | function getCachedModule(name, parents) { 76 | var key, i, len = moduleCache.length; 77 | for (i=0;i>> 0; 202 | if (typeof fun != "function") 203 | throw new TypeError(); 204 | 205 | var thisp = arguments[1]; 206 | for (var i = 0; i < len; i++) 207 | { 208 | if (i in this) 209 | fun.call(thisp, this[i], i, this); 210 | } 211 | }; 212 | } 213 | 214 | if (!Array.prototype.indexOf) 215 | { 216 | Array.prototype.indexOf = function(elt) 217 | { 218 | var len = this.length >>> 0; 219 | 220 | var from = Number(arguments[1]) || 0; 221 | from = (from < 0) 222 | ? Math.ceil(from) 223 | : Math.floor(from); 224 | if (from < 0) 225 | from += len; 226 | 227 | for (; from < len; from++) 228 | { 229 | if (from in this && 230 | this[from] === elt) 231 | return from; 232 | } 233 | return -1; 234 | }; 235 | } 236 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/md5.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.1 Copyright (C) Paul Johnston 1999 - 2002. 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 | 10 | /* 11 | * Configurable variables. You may need to tweak these to be compatible with 12 | * the server-side, but the defaults work in most cases. 13 | */ 14 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ 15 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ 16 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ 17 | 18 | /* 19 | * These are the functions you'll usually want to call 20 | * They take string arguments and return either hex or base-64 encoded strings 21 | */ 22 | function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));} 23 | function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));} 24 | function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));} 25 | function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); } 26 | function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); } 27 | function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); } 28 | 29 | /* 30 | * Perform a simple self-test to see if the VM is working 31 | */ 32 | function md5_vm_test() 33 | { 34 | return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72"; 35 | } 36 | 37 | /* 38 | * Calculate the MD5 of an array of little-endian words, and a bit length 39 | keep 40 | */ 41 | function core_md5(x, len) 42 | { 43 | /* append padding */ 44 | x[len >> 5] |= 0x80 << ((len) % 32); 45 | x[(((len + 64) >>> 9) << 4) + 14] = len; 46 | 47 | var a = 1732584193; 48 | var b = -271733879; 49 | var c = -1732584194; 50 | var d = 271733878; 51 | 52 | for(var i = 0; i < x.length; i += 16) 53 | { 54 | var olda = a; 55 | var oldb = b; 56 | var oldc = c; 57 | var oldd = d; 58 | 59 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); 60 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); 61 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); 62 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); 63 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); 64 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); 65 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); 66 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); 67 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); 68 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); 69 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063); 70 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162); 71 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); 72 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101); 73 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290); 74 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329); 75 | 76 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); 77 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); 78 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713); 79 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); 80 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); 81 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083); 82 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335); 83 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); 84 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); 85 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); 86 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); 87 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); 88 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); 89 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); 90 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); 91 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734); 92 | 93 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); 94 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); 95 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562); 96 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556); 97 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); 98 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); 99 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); 100 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640); 101 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174); 102 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); 103 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); 104 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); 105 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); 106 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835); 107 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520); 108 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); 109 | 110 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); 111 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); 112 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905); 113 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); 114 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); 115 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); 116 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523); 117 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); 118 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); 119 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744); 120 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); 121 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649); 122 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); 123 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379); 124 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); 125 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); 126 | 127 | a = safe_add(a, olda); 128 | b = safe_add(b, oldb); 129 | c = safe_add(c, oldc); 130 | d = safe_add(d, oldd); 131 | } 132 | return Array(a, b, c, d); 133 | 134 | } 135 | 136 | /* 137 | * These functions implement the four basic operations the algorithm uses. 138 | */ 139 | function md5_cmn(q, a, b, x, s, t) 140 | { 141 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b); 142 | } 143 | function md5_ff(a, b, c, d, x, s, t) 144 | { 145 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 146 | } 147 | function md5_gg(a, b, c, d, x, s, t) 148 | { 149 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 150 | } 151 | function md5_hh(a, b, c, d, x, s, t) 152 | { 153 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 154 | } 155 | function md5_ii(a, b, c, d, x, s, t) 156 | { 157 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 158 | } 159 | 160 | /* 161 | * Calculate the HMAC-MD5, of a key and some data 162 | */ 163 | function core_hmac_md5(key, data) 164 | { 165 | var bkey = str2binl(key); 166 | if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz); 167 | 168 | var ipad = Array(16), opad = Array(16); 169 | for(var i = 0; i < 16; i++) 170 | { 171 | ipad[i] = bkey[i] ^ 0x36363636; 172 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 173 | } 174 | 175 | var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz); 176 | return core_md5(opad.concat(hash), 512 + 128); 177 | } 178 | 179 | /* 180 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 181 | * to work around bugs in some JS interpreters. 182 | */ 183 | function safe_add(x, y) 184 | { 185 | var lsw = (x & 0xFFFF) + (y & 0xFFFF); 186 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16); 187 | return (msw << 16) | (lsw & 0xFFFF); 188 | } 189 | 190 | /* 191 | * Bitwise rotate a 32-bit number to the left. 192 | */ 193 | function bit_rol(num, cnt) 194 | { 195 | return (num << cnt) | (num >>> (32 - cnt)); 196 | } 197 | 198 | /* 199 | * Convert a string to an array of little-endian words 200 | * If chrsz is ASCII, characters >255 have their hi-byte silently ignored. 201 | keep 202 | */ 203 | function str2binl(str) 204 | { 205 | var bin = Array(); 206 | var mask = (1 << chrsz) - 1; 207 | for(var i = 0; i < str.length * chrsz; i += chrsz) 208 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32); 209 | return bin; 210 | } 211 | 212 | /* 213 | * Convert an array of little-endian words to a string 214 | */ 215 | function binl2str(bin) 216 | { 217 | var str = ""; 218 | var mask = (1 << chrsz) - 1; 219 | for(var i = 0; i < bin.length * 32; i += chrsz) 220 | str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); 221 | return str; 222 | } 223 | 224 | /* 225 | * Convert an array of little-endian words to a hex string. 226 | keep 227 | */ 228 | function binl2hex(binarray) 229 | { 230 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; 231 | var str = ""; 232 | for(var i = 0; i < binarray.length * 4; i++) 233 | { 234 | str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + 235 | hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); 236 | } 237 | return str; 238 | } 239 | 240 | /* 241 | * Convert an array of little-endian words to a base-64 string 242 | */ 243 | function binl2b64(binarray) 244 | { 245 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 246 | var str = ""; 247 | for(var i = 0; i < binarray.length * 4; i += 3) 248 | { 249 | var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) 250 | | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) 251 | | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); 252 | for(var j = 0; j < 4; j++) 253 | { 254 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; 255 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); 256 | } 257 | } 258 | return str; 259 | } 260 | 261 | if (typeof exports != "undefined") { 262 | exports.hex = hex_md5; 263 | } 264 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.mustache.js: -------------------------------------------------------------------------------- 1 | /* 2 | Shameless port of a shameless port 3 | @defunkt => @janl => @aq 4 | 5 | See http://github.com/defunkt/mustache for more info. 6 | */ 7 | 8 | ;(function($) { 9 | 10 | /* 11 | mustache.js — Logic-less templates in JavaScript 12 | 13 | See http://mustache.github.com/ for more info. 14 | */ 15 | 16 | var Mustache = function() { 17 | var Renderer = function() {}; 18 | 19 | Renderer.prototype = { 20 | otag: "{{", 21 | ctag: "}}", 22 | pragmas: {}, 23 | buffer: [], 24 | pragmas_implemented: { 25 | "IMPLICIT-ITERATOR": true 26 | }, 27 | context: {}, 28 | 29 | render: function(template, context, partials, in_recursion) { 30 | // reset buffer & set context 31 | if(!in_recursion) { 32 | this.context = context; 33 | this.buffer = []; // TODO: make this non-lazy 34 | } 35 | 36 | // fail fast 37 | if(!this.includes("", template)) { 38 | if(in_recursion) { 39 | return template; 40 | } else { 41 | this.send(template); 42 | return; 43 | } 44 | } 45 | 46 | template = this.render_pragmas(template); 47 | var html = this.render_section(template, context, partials); 48 | if(in_recursion) { 49 | return this.render_tags(html, context, partials, in_recursion); 50 | } 51 | 52 | this.render_tags(html, context, partials, in_recursion); 53 | }, 54 | 55 | /* 56 | Sends parsed lines 57 | */ 58 | send: function(line) { 59 | if(line != "") { 60 | this.buffer.push(line); 61 | } 62 | }, 63 | 64 | /* 65 | Looks for %PRAGMAS 66 | */ 67 | render_pragmas: function(template) { 68 | // no pragmas 69 | if(!this.includes("%", template)) { 70 | return template; 71 | } 72 | 73 | var that = this; 74 | var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + 75 | this.ctag); 76 | return template.replace(regex, function(match, pragma, options) { 77 | if(!that.pragmas_implemented[pragma]) { 78 | throw({message: 79 | "This implementation of mustache doesn't understand the '" + 80 | pragma + "' pragma"}); 81 | } 82 | that.pragmas[pragma] = {}; 83 | if(options) { 84 | var opts = options.split("="); 85 | that.pragmas[pragma][opts[0]] = opts[1]; 86 | } 87 | return ""; 88 | // ignore unknown pragmas silently 89 | }); 90 | }, 91 | 92 | /* 93 | Tries to find a partial in the curent scope and render it 94 | */ 95 | render_partial: function(name, context, partials) { 96 | name = this.trim(name); 97 | if(!partials || partials[name] === undefined) { 98 | throw({message: "unknown_partial '" + name + "'"}); 99 | } 100 | if(typeof(context[name]) != "object") { 101 | return this.render(partials[name], context, partials, true); 102 | } 103 | return this.render(partials[name], context[name], partials, true); 104 | }, 105 | 106 | /* 107 | Renders inverted (^) and normal (#) sections 108 | */ 109 | render_section: function(template, context, partials) { 110 | if(!this.includes("#", template) && !this.includes("^", template)) { 111 | return template; 112 | } 113 | 114 | var that = this; 115 | // CSW - Added "+?" so it finds the tighest bound, not the widest 116 | var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + 117 | "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + 118 | "\\s*", "mg"); 119 | 120 | // for each {{#foo}}{{/foo}} section do... 121 | return template.replace(regex, function(match, type, name, content) { 122 | var value = that.find(name, context); 123 | if(type == "^") { // inverted section 124 | if(!value || that.is_array(value) && value.length === 0) { 125 | // false or empty list, render it 126 | return that.render(content, context, partials, true); 127 | } else { 128 | return ""; 129 | } 130 | } else if(type == "#") { // normal section 131 | if(that.is_array(value)) { // Enumerable, Let's loop! 132 | return that.map(value, function(row) { 133 | return that.render(content, that.create_context(row), 134 | partials, true); 135 | }).join(""); 136 | } else if(that.is_object(value)) { // Object, Use it as subcontext! 137 | return that.render(content, that.create_context(value), 138 | partials, true); 139 | } else if(typeof value === "function") { 140 | // higher order section 141 | return value.call(context, content, function(text) { 142 | return that.render(text, context, partials, true); 143 | }); 144 | } else if(value) { // boolean section 145 | return that.render(content, context, partials, true); 146 | } else { 147 | return ""; 148 | } 149 | } 150 | }); 151 | }, 152 | 153 | /* 154 | Replace {{foo}} and friends with values from our view 155 | */ 156 | render_tags: function(template, context, partials, in_recursion) { 157 | // tit for tat 158 | var that = this; 159 | 160 | var new_regex = function() { 161 | return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + 162 | that.ctag + "+", "g"); 163 | }; 164 | 165 | var regex = new_regex(); 166 | var tag_replace_callback = function(match, operator, name) { 167 | switch(operator) { 168 | case "!": // ignore comments 169 | return ""; 170 | case "=": // set new delimiters, rebuild the replace regexp 171 | that.set_delimiters(name); 172 | regex = new_regex(); 173 | return ""; 174 | case ">": // render partial 175 | return that.render_partial(name, context, partials); 176 | case "{": // the triple mustache is unescaped 177 | return that.find(name, context); 178 | default: // escape the value 179 | return that.escape(that.find(name, context)); 180 | } 181 | }; 182 | var lines = template.split("\n"); 183 | for(var i = 0; i < lines.length; i++) { 184 | lines[i] = lines[i].replace(regex, tag_replace_callback, this); 185 | if(!in_recursion) { 186 | this.send(lines[i]); 187 | } 188 | } 189 | 190 | if(in_recursion) { 191 | return lines.join("\n"); 192 | } 193 | }, 194 | 195 | set_delimiters: function(delimiters) { 196 | var dels = delimiters.split(" "); 197 | this.otag = this.escape_regex(dels[0]); 198 | this.ctag = this.escape_regex(dels[1]); 199 | }, 200 | 201 | escape_regex: function(text) { 202 | // thank you Simon Willison 203 | if(!arguments.callee.sRE) { 204 | var specials = [ 205 | '/', '.', '*', '+', '?', '|', 206 | '(', ')', '[', ']', '{', '}', '\\' 207 | ]; 208 | arguments.callee.sRE = new RegExp( 209 | '(\\' + specials.join('|\\') + ')', 'g' 210 | ); 211 | } 212 | return text.replace(arguments.callee.sRE, '\\$1'); 213 | }, 214 | 215 | /* 216 | find `name` in current `context`. That is find me a value 217 | from the view object 218 | */ 219 | find: function(name, context) { 220 | name = this.trim(name); 221 | 222 | // Checks whether a value is thruthy or false or 0 223 | function is_kinda_truthy(bool) { 224 | return bool === false || bool === 0 || bool; 225 | } 226 | 227 | var value; 228 | if(is_kinda_truthy(context[name])) { 229 | value = context[name]; 230 | } else if(is_kinda_truthy(this.context[name])) { 231 | value = this.context[name]; 232 | } 233 | 234 | if(typeof value === "function") { 235 | return value.apply(context); 236 | } 237 | if(value !== undefined) { 238 | return value; 239 | } 240 | // silently ignore unkown variables 241 | return ""; 242 | }, 243 | 244 | // Utility methods 245 | 246 | /* includes tag */ 247 | includes: function(needle, haystack) { 248 | return haystack.indexOf(this.otag + needle) != -1; 249 | }, 250 | 251 | /* 252 | Does away with nasty characters 253 | */ 254 | escape: function(s) { 255 | s = String(s === null ? "" : s); 256 | return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { 257 | switch(s) { 258 | case "&": return "&"; 259 | case "\\": return "\\\\"; 260 | case '"': return '\"'; 261 | case "<": return "<"; 262 | case ">": return ">"; 263 | default: return s; 264 | } 265 | }); 266 | }, 267 | 268 | // by @langalex, support for arrays of strings 269 | create_context: function(_context) { 270 | if(this.is_object(_context)) { 271 | return _context; 272 | } else { 273 | var iterator = "."; 274 | if(this.pragmas["IMPLICIT-ITERATOR"]) { 275 | iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; 276 | } 277 | var ctx = {}; 278 | ctx[iterator] = _context; 279 | return ctx; 280 | } 281 | }, 282 | 283 | is_object: function(a) { 284 | return a && typeof a == "object"; 285 | }, 286 | 287 | is_array: function(a) { 288 | return Object.prototype.toString.call(a) === '[object Array]'; 289 | }, 290 | 291 | /* 292 | Gets rid of leading and trailing whitespace 293 | */ 294 | trim: function(s) { 295 | return s.replace(/^\s*|\s*$/g, ""); 296 | }, 297 | 298 | /* 299 | Why, why, why? Because IE. Cry, cry cry. 300 | */ 301 | map: function(array, fn) { 302 | if (typeof array.map == "function") { 303 | return array.map(fn); 304 | } else { 305 | var r = []; 306 | var l = array.length; 307 | for(var i = 0; i < l; i++) { 308 | r.push(fn(array[i])); 309 | } 310 | return r; 311 | } 312 | } 313 | }; 314 | 315 | return({ 316 | name: "mustache.js", 317 | version: "0.3.1-dev", 318 | 319 | /* 320 | Turns a template and view into HTML 321 | */ 322 | to_html: function(template, view, partials, send_fun) { 323 | var renderer = new Renderer(); 324 | if(send_fun) { 325 | renderer.send = send_fun; 326 | } 327 | renderer.render(template, view, partials); 328 | if(!send_fun) { 329 | return renderer.buffer.join("\n"); 330 | } 331 | }, 332 | escape : function(text) { 333 | return new Renderer().escape(text); 334 | } 335 | }); 336 | }(); 337 | 338 | $.mustache = function(template, view, partials) { 339 | return Mustache.to_html(template, view, partials); 340 | }; 341 | 342 | $.mustache.escape = function(text) { 343 | return Mustache.escape(text); 344 | }; 345 | 346 | })(jQuery); 347 | -------------------------------------------------------------------------------- /_attachments/script/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-03-20 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | text = String(text); 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 | -------------------------------------------------------------------------------- /_attachments/script/jquery.couch.js: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 | // use this file except in compliance with the License. You may obtain a copy of 3 | // the License at 4 | // 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to in writing, software 8 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | // License for the specific language governing permissions and limitations under 11 | // the License. 12 | 13 | /** 14 | * @namespace 15 | * $.couch is used to communicate with a CouchDB server, the server methods can 16 | * be called directly without creating an instance. Typically all methods are 17 | * passed an options object which defines a success callback which 18 | * is called with the data returned from the http request to CouchDB, you can 19 | * find the other settings that can be used in the options object 20 | * from 21 | * jQuery.ajax settings 22 | *
$.couch.activeTasks({
  23 |  *   success: function (data) {
  24 |  *     console.log(data);
  25 |  *   }
  26 |  * });
27 | * Outputs (for example): 28 | *
[
  29 |  *  {
  30 |  *   "pid" : "<0.11599.0>",
  31 |  *   "status" : "Copied 0 of 18369 changes (0%)",
  32 |  *   "task" : "recipes",
  33 |  *   "type" : "Database Compaction"
  34 |  *  }
  35 |  *]
36 | */ 37 | (function($) { 38 | 39 | $.couch = $.couch || {}; 40 | /** @lends $.couch */ 41 | 42 | /** 43 | * @private 44 | */ 45 | function encodeDocId(docID) { 46 | var parts = docID.split("/"); 47 | if (parts[0] == "_design") { 48 | parts.shift(); 49 | return "_design/" + encodeURIComponent(parts.join('/')); 50 | } 51 | return encodeURIComponent(docID); 52 | } 53 | 54 | /** 55 | * @private 56 | */ 57 | 58 | var uuidCache = []; 59 | 60 | $.extend($.couch, { 61 | urlPrefix: '', 62 | 63 | /** 64 | * You can obtain a list of active tasks by using the /_active_tasks URL. 65 | * The result is a JSON array of the currently running tasks, with each task 66 | * being described with a single object. 67 | * @see docs for /_active_tasks 70 | * @param {ajaxSettings} options jQuery ajax settings 72 | */ 73 | activeTasks: function(options) { 74 | ajax( 75 | {url: this.urlPrefix + "/_active_tasks"}, 76 | options, 77 | "Active task status could not be retrieved" 78 | ); 79 | }, 80 | 81 | /** 82 | * Returns a list of all the databases in the CouchDB instance 83 | * @see docs for /_all_dbs 86 | * @param {ajaxSettings} options jQuery ajax settings 88 | */ 89 | allDbs: function(options) { 90 | ajax( 91 | {url: this.urlPrefix + "/_all_dbs"}, 92 | options, 93 | "An error occurred retrieving the list of all databases" 94 | ); 95 | }, 96 | 97 | /** 98 | * View and edit the CouchDB configuration, called with just the options 99 | * parameter the entire config is returned, you can be more specific by 100 | * passing the section and option parameters, if you specify a value that 101 | * value will be stored in the configuration. 102 | * @see docs for /_config 105 | * @param {ajaxSettings} options 106 | * 107 | * jQuery ajax settings 108 | * @param {String} [section] the section of the config 109 | * @param {String} [option] the particular config option 110 | * @param {String} [value] value to be set 111 | */ 112 | config: function(options, section, option, value) { 113 | var req = {url: this.urlPrefix + "/_config/"}; 114 | if (section) { 115 | req.url += encodeURIComponent(section) + "/"; 116 | if (option) { 117 | req.url += encodeURIComponent(option); 118 | } 119 | } 120 | if (value === null) { 121 | req.type = "DELETE"; 122 | } else if (value !== undefined) { 123 | req.type = "PUT"; 124 | req.data = toJSON(value); 125 | req.contentType = "application/json"; 126 | req.processData = false 127 | } 128 | 129 | ajax(req, options, 130 | "An error occurred retrieving/updating the server configuration" 131 | ); 132 | }, 133 | 134 | /** 135 | * Returns the session information for the currently logged in user. 136 | * @param {ajaxSettings} options 137 | * 138 | * jQuery ajax settings 139 | */ 140 | session: function(options) { 141 | options = options || {}; 142 | $.ajax({ 143 | type: "GET", url: this.urlPrefix + "/_session", 144 | beforeSend: function(xhr) { 145 | xhr.setRequestHeader('Accept', 'application/json'); 146 | }, 147 | complete: function(req) { 148 | var resp = $.parseJSON(req.responseText); 149 | if (req.status == 200) { 150 | if (options.success) options.success(resp); 151 | } else if (options.error) { 152 | options.error(req.status, resp.error, resp.reason); 153 | } else { 154 | alert("An error occurred getting session info: " + resp.reason); 155 | } 156 | } 157 | }); 158 | }, 159 | 160 | /** 161 | * @private 162 | */ 163 | userDb : function(callback) { 164 | $.couch.session({ 165 | success : function(resp) { 166 | var userDb = $.couch.db(resp.info.authentication_db); 167 | callback(userDb); 168 | } 169 | }); 170 | }, 171 | 172 | /** 173 | * Create a new user on the CouchDB server, user_doc is an 174 | * object with a name field and other information you want 175 | * to store relating to that user, for example 176 | * {"name": "daleharvey"} 177 | * @param {Object} user_doc Users details 178 | * @param {String} password Users password 179 | * @param {ajaxSettings} options 180 | * 181 | * jQuery ajax settings 182 | */ 183 | signup: function(user_doc, password, options) { 184 | options = options || {}; 185 | // prepare user doc based on name and password 186 | user_doc = this.prepareUserDoc(user_doc, password); 187 | $.couch.userDb(function(db) { 188 | db.saveDoc(user_doc, options); 189 | }); 190 | }, 191 | 192 | /** 193 | * Populates a user doc with a new password. 194 | * @param {Object} user_doc User details 195 | * @param {String} new_password New Password 196 | */ 197 | prepareUserDoc: function(user_doc, new_password) { 198 | if (typeof hex_sha1 == "undefined") { 199 | alert("creating a user doc requires sha1.js to be loaded in the page"); 200 | return; 201 | } 202 | var user_prefix = "org.couchdb.user:"; 203 | user_doc._id = user_doc._id || user_prefix + user_doc.name; 204 | if (new_password) { 205 | // handle the password crypto 206 | user_doc.salt = $.couch.newUUID(); 207 | user_doc.password_sha = hex_sha1(new_password + user_doc.salt); 208 | } 209 | user_doc.type = "user"; 210 | if (!user_doc.roles) { 211 | user_doc.roles = []; 212 | } 213 | return user_doc; 214 | }, 215 | 216 | /** 217 | * Authenticate against CouchDB, the options parameter is 218 | *expected to have name and password fields. 219 | * @param {ajaxSettings} options 220 | * 221 | * jQuery ajax settings 222 | */ 223 | login: function(options) { 224 | options = options || {}; 225 | $.ajax({ 226 | type: "POST", url: this.urlPrefix + "/_session", dataType: "json", 227 | data: {name: options.name, password: options.password}, 228 | beforeSend: function(xhr) { 229 | xhr.setRequestHeader('Accept', 'application/json'); 230 | }, 231 | complete: function(req) { 232 | var resp = $.parseJSON(req.responseText); 233 | if (req.status == 200) { 234 | if (options.success) options.success(resp); 235 | } else if (options.error) { 236 | options.error(req.status, resp.error, resp.reason); 237 | } else { 238 | alert("An error occurred logging in: " + resp.reason); 239 | } 240 | } 241 | }); 242 | }, 243 | 244 | 245 | /** 246 | * Delete your current CouchDB user session 247 | * @param {ajaxSettings} options 248 | * 249 | * jQuery ajax settings 250 | */ 251 | logout: function(options) { 252 | options = options || {}; 253 | $.ajax({ 254 | type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json", 255 | username : "_", password : "_", 256 | beforeSend: function(xhr) { 257 | xhr.setRequestHeader('Accept', 'application/json'); 258 | }, 259 | complete: function(req) { 260 | var resp = $.parseJSON(req.responseText); 261 | if (req.status == 200) { 262 | if (options.success) options.success(resp); 263 | } else if (options.error) { 264 | options.error(req.status, resp.error, resp.reason); 265 | } else { 266 | alert("An error occurred logging out: " + resp.reason); 267 | } 268 | } 269 | }); 270 | }, 271 | 272 | /** 273 | * @namespace 274 | * $.couch.db is used to communicate with a specific CouchDB database 275 | *
var $db = $.couch.db("mydatabase");
 276 |      *$db.allApps({
 277 |      *  success: function (data) {
 278 |      *    ... process data ...
 279 |      *  }
 280 |      *});
 281 |      * 
282 | */ 283 | db: function(name, db_opts) { 284 | db_opts = db_opts || {}; 285 | var rawDocs = {}; 286 | function maybeApplyVersion(doc) { 287 | if (doc._id && doc._rev && rawDocs[doc._id] && 288 | rawDocs[doc._id].rev == doc._rev) { 289 | // todo: can we use commonjs require here? 290 | if (typeof Base64 == "undefined") { 291 | alert("please include /_utils/script/base64.js in the page for " + 292 | "base64 support"); 293 | return false; 294 | } else { 295 | doc._attachments = doc._attachments || {}; 296 | doc._attachments["rev-"+doc._rev.split("-")[0]] = { 297 | content_type :"application/json", 298 | data : Base64.encode(rawDocs[doc._id].raw) 299 | }; 300 | return true; 301 | } 302 | } 303 | }; 304 | return /** @lends $.couch.db */{ 305 | name: name, 306 | uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/", 307 | 308 | /** 309 | * Request compaction of the specified database. 310 | * @see docs for /db/_compact 313 | * @param {ajaxSettings} options 314 | * 315 | * jQuery ajax settings 316 | */ 317 | compact: function(options) { 318 | $.extend(options, {successStatus: 202}); 319 | ajax({ 320 | type: "POST", url: this.uri + "_compact", 321 | data: "", processData: false 322 | }, 323 | options, 324 | "The database could not be compacted" 325 | ); 326 | }, 327 | 328 | /** 329 | * Cleans up the cached view output on disk for a given view. 330 | * @see docs for /db/_compact 333 | * @param {ajaxSettings} options jQuery ajax settings 335 | */ 336 | viewCleanup: function(options) { 337 | $.extend(options, {successStatus: 202}); 338 | ajax({ 339 | type: "POST", url: this.uri + "_view_cleanup", 340 | data: "", processData: false 341 | }, 342 | options, 343 | "The views could not be cleaned up" 344 | ); 345 | }, 346 | 347 | /** 348 | * Compacts the view indexes associated with the specified design 349 | * document. You can use this in place of the full database compaction 350 | * if you know a specific set of view indexes have been affected by a 351 | * recent database change. 352 | * @see docs for /db/_compact/design-doc 355 | * @param {String} groupname Name of design-doc to compact 356 | * @param {ajaxSettings} options jQuery ajax settings 358 | */ 359 | compactView: function(groupname, options) { 360 | $.extend(options, {successStatus: 202}); 361 | ajax({ 362 | type: "POST", url: this.uri + "_compact/" + groupname, 363 | data: "", processData: false 364 | }, 365 | options, 366 | "The view could not be compacted" 367 | ); 368 | }, 369 | 370 | /** 371 | * Create a new database 372 | * @see docs for PUT /db/ 375 | * @param {ajaxSettings} options jQuery ajax settings 377 | */ 378 | create: function(options) { 379 | $.extend(options, {successStatus: 201}); 380 | ajax({ 381 | type: "PUT", url: this.uri, contentType: "application/json", 382 | data: "", processData: false 383 | }, 384 | options, 385 | "The database could not be created" 386 | ); 387 | }, 388 | 389 | /** 390 | * Deletes the specified database, and all the documents and 391 | * attachments contained within it. 392 | * @see docs for DELETE /db/ 395 | * @param {ajaxSettings} options jQuery ajax settings 397 | */ 398 | drop: function(options) { 399 | ajax( 400 | {type: "DELETE", url: this.uri}, 401 | options, 402 | "The database could not be deleted" 403 | ); 404 | }, 405 | 406 | /** 407 | * Gets information about the specified database. 408 | * @see docs for GET /db/ 411 | * @param {ajaxSettings} options jQuery ajax settings 413 | */ 414 | info: function(options) { 415 | ajax( 416 | {url: this.uri}, 417 | options, 418 | "Database information could not be retrieved" 419 | ); 420 | }, 421 | 422 | /** 423 | * @namespace 424 | * $.couch.db.changes provides an API for subscribing to the changes 425 | * feed 426 | *
var $changes = $.couch.db("mydatabase").changes();
 427 |          *$changes.onChange = function (data) {
 428 |          *    ... process data ...
 429 |          * }
 430 |          * $changes.stop();
 431 |          * 
432 | */ 433 | changes: function(since, options) { 434 | 435 | options = options || {}; 436 | // set up the promise object within a closure for this handler 437 | var timeout = 100, db = this, active = true, 438 | listeners = [], 439 | promise = /** @lends $.couch.db.changes */ { 440 | /** 441 | * Add a listener callback 442 | * @see docs for /db/_changes 445 | * @param {Function} fun Callback function to run when 446 | * notified of changes. 447 | */ 448 | onChange : function(fun) { 449 | listeners.push(fun); 450 | }, 451 | /** 452 | * Stop subscribing to the changes feed 453 | */ 454 | stop : function() { 455 | active = false; 456 | } 457 | }; 458 | // call each listener when there is a change 459 | function triggerListeners(resp) { 460 | $.each(listeners, function() { 461 | this(resp); 462 | }); 463 | }; 464 | // when there is a change, call any listeners, then check for 465 | // another change 466 | options.success = function(resp) { 467 | timeout = 100; 468 | if (active) { 469 | since = resp.last_seq; 470 | triggerListeners(resp); 471 | getChangesSince(); 472 | }; 473 | }; 474 | options.error = function() { 475 | if (active) { 476 | setTimeout(getChangesSince, timeout); 477 | timeout = timeout * 2; 478 | } 479 | }; 480 | // actually make the changes request 481 | function getChangesSince() { 482 | var opts = $.extend({heartbeat : 10 * 1000}, options, { 483 | feed : "longpoll", 484 | since : since 485 | }); 486 | ajax( 487 | {url: db.uri + "_changes"+encodeOptions(opts)}, 488 | options, 489 | "Error connecting to "+db.uri+"/_changes." 490 | ); 491 | } 492 | // start the first request 493 | if (since) { 494 | getChangesSince(); 495 | } else { 496 | db.info({ 497 | success : function(info) { 498 | since = info.update_seq; 499 | getChangesSince(); 500 | } 501 | }); 502 | } 503 | return promise; 504 | }, 505 | 506 | /** 507 | * Fetch all the docs in this db, you can specify an array of keys to 508 | * fetch by passing the keys field in the 509 | * options 510 | * parameter. 511 | * @see docs for /db/all_docs/ 514 | * @param {ajaxSettings} options jQuery ajax settings 516 | */ 517 | allDocs: function(options) { 518 | var type = "GET"; 519 | var data = null; 520 | if (options["keys"]) { 521 | type = "POST"; 522 | var keys = options["keys"]; 523 | delete options["keys"]; 524 | data = toJSON({ "keys": keys }); 525 | } 526 | ajax({ 527 | type: type, 528 | data: data, 529 | url: this.uri + "_all_docs" + encodeOptions(options) 530 | }, 531 | options, 532 | "An error occurred retrieving a list of all documents" 533 | ); 534 | }, 535 | 536 | /** 537 | * Fetch all the design docs in this db 538 | * @param {ajaxSettings} options jQuery ajax settings 540 | */ 541 | allDesignDocs: function(options) { 542 | this.allDocs($.extend( 543 | {startkey:"_design", endkey:"_design0"}, options)); 544 | }, 545 | 546 | /** 547 | * Fetch all the design docs with an index.html, options 548 | * parameter expects an eachApp field which is a callback 549 | * called on each app found. 550 | * @param {ajaxSettings} options jQuery ajax settings 552 | */ 553 | allApps: function(options) { 554 | options = options || {}; 555 | var self = this; 556 | if (options.eachApp) { 557 | this.allDesignDocs({ 558 | success: function(resp) { 559 | $.each(resp.rows, function() { 560 | self.openDoc(this.id, { 561 | success: function(ddoc) { 562 | var index, appPath, appName = ddoc._id.split('/'); 563 | appName.shift(); 564 | appName = appName.join('/'); 565 | index = ddoc.couchapp && ddoc.couchapp.index; 566 | if (index) { 567 | appPath = ['', name, ddoc._id, index].join('/'); 568 | } else if (ddoc._attachments && 569 | ddoc._attachments["index.html"]) { 570 | appPath = ['', name, ddoc._id, "index.html"].join('/'); 571 | } 572 | if (appPath) options.eachApp(appName, appPath, ddoc); 573 | } 574 | }); 575 | }); 576 | } 577 | }); 578 | } else { 579 | alert("Please provide an eachApp function for allApps()"); 580 | } 581 | }, 582 | 583 | /** 584 | * Returns the specified doc from the specified db. 585 | * @see docs for GET /db/doc 588 | * @param {String} docId id of document to fetch 589 | * @param {ajaxSettings} options jQuery ajax settings 591 | * @param {ajaxSettings} ajaxOptions jQuery ajax settings 593 | */ 594 | openDoc: function(docId, options, ajaxOptions) { 595 | options = options || {}; 596 | if (db_opts.attachPrevRev || options.attachPrevRev) { 597 | $.extend(options, { 598 | beforeSuccess : function(req, doc) { 599 | rawDocs[doc._id] = { 600 | rev : doc._rev, 601 | raw : req.responseText 602 | }; 603 | } 604 | }); 605 | } else { 606 | $.extend(options, { 607 | beforeSuccess : function(req, doc) { 608 | if (doc["jquery.couch.attachPrevRev"]) { 609 | rawDocs[doc._id] = { 610 | rev : doc._rev, 611 | raw : req.responseText 612 | }; 613 | } 614 | } 615 | }); 616 | } 617 | ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)}, 618 | options, 619 | "The document could not be retrieved", 620 | ajaxOptions 621 | ); 622 | }, 623 | 624 | /** 625 | * Create a new document in the specified database, using the supplied 626 | * JSON document structure. If the JSON structure includes the _id 627 | * field, then the document will be created with the specified document 628 | * ID. If the _id field is not specified, a new unique ID will be 629 | * generated. 630 | * @see docs for GET /db/doc 633 | * @param {String} doc document to save 634 | * @param {ajaxSettings} options jQuery ajax settings 636 | */ 637 | saveDoc: function(doc, options) { 638 | options = options || {}; 639 | var db = this; 640 | var beforeSend = fullCommit(options); 641 | if (doc._id === undefined) { 642 | var method = "POST"; 643 | var uri = this.uri; 644 | } else { 645 | var method = "PUT"; 646 | var uri = this.uri + encodeDocId(doc._id); 647 | } 648 | var versioned = maybeApplyVersion(doc); 649 | $.ajax({ 650 | type: method, url: uri + encodeOptions(options), 651 | contentType: "application/json", 652 | dataType: "json", data: toJSON(doc), 653 | beforeSend : beforeSend, 654 | complete: function(req) { 655 | var resp = $.parseJSON(req.responseText); 656 | if (req.status == 200 || req.status == 201 || req.status == 202) { 657 | doc._id = resp.id; 658 | doc._rev = resp.rev; 659 | if (versioned) { 660 | db.openDoc(doc._id, { 661 | attachPrevRev : true, 662 | success : function(d) { 663 | doc._attachments = d._attachments; 664 | if (options.success) options.success(resp); 665 | } 666 | }); 667 | } else { 668 | if (options.success) options.success(resp); 669 | } 670 | } else if (options.error) { 671 | options.error(req.status, resp.error, resp.reason); 672 | } else { 673 | alert("The document could not be saved: " + resp.reason); 674 | } 675 | } 676 | }); 677 | }, 678 | 679 | /** 680 | * Save a list of documents 681 | * @see docs for /db/_bulk_docs 684 | * @param {Object[]} docs List of documents to save 685 | * @param {ajaxSettings} options jQuery ajax settings 687 | */ 688 | bulkSave: function(docs, options) { 689 | var beforeSend = fullCommit(options); 690 | $.extend(options, {successStatus: 201, beforeSend : beforeSend}); 691 | ajax({ 692 | type: "POST", 693 | url: this.uri + "_bulk_docs" + encodeOptions(options), 694 | contentType: "application/json", data: toJSON(docs) 695 | }, 696 | options, 697 | "The documents could not be saved" 698 | ); 699 | }, 700 | 701 | /** 702 | * Deletes the specified document from the database. You must supply 703 | * the current (latest) revision and id of the document 704 | * to delete eg removeDoc({_id:"mydoc", _rev: "1-2345"}) 705 | * @see docs for DELETE /db/doc 708 | * @param {Object} doc Document to delete 709 | * @param {ajaxSettings} options jQuery ajax settings 711 | */ 712 | removeDoc: function(doc, options) { 713 | ajax({ 714 | type: "DELETE", 715 | url: this.uri + 716 | encodeDocId(doc._id) + 717 | encodeOptions({rev: doc._rev}) 718 | }, 719 | options, 720 | "The document could not be deleted" 721 | ); 722 | }, 723 | 724 | /** 725 | * Remove a set of documents 726 | * @see docs for /db/_bulk_docs 729 | * @param {String[]} docs List of document id's to remove 730 | * @param {ajaxSettings} options jQuery ajax settings 732 | */ 733 | bulkRemove: function(docs, options){ 734 | docs.docs = $.each( 735 | docs.docs, function(i, doc){ 736 | doc._deleted = true; 737 | } 738 | ); 739 | $.extend(options, {successStatus: 201}); 740 | ajax({ 741 | type: "POST", 742 | url: this.uri + "_bulk_docs" + encodeOptions(options), 743 | data: toJSON(docs) 744 | }, 745 | options, 746 | "The documents could not be deleted" 747 | ); 748 | }, 749 | 750 | /** 751 | * The COPY command (which is non-standard HTTP) copies an existing 752 | * document to a new or existing document. 753 | * @see docs for COPY /db/doc 756 | * @param {String[]} docId document id to copy 757 | * @param {ajaxSettings} options jQuery ajax settings 759 | * @param {ajaxSettings} options jQuery ajax settings 761 | */ 762 | copyDoc: function(docId, options, ajaxOptions) { 763 | ajaxOptions = $.extend(ajaxOptions, { 764 | complete: function(req) { 765 | var resp = $.parseJSON(req.responseText); 766 | if (req.status == 201) { 767 | if (options.success) options.success(resp); 768 | } else if (options.error) { 769 | options.error(req.status, resp.error, resp.reason); 770 | } else { 771 | alert("The document could not be copied: " + resp.reason); 772 | } 773 | } 774 | }); 775 | ajax({ 776 | type: "COPY", 777 | url: this.uri + encodeDocId(docId) 778 | }, 779 | options, 780 | "The document could not be copied", 781 | ajaxOptions 782 | ); 783 | }, 784 | 785 | /** 786 | * Creates (and executes) a temporary view based on the view function 787 | * supplied in the JSON request. 788 | * @see docs for /db/_temp_view 791 | * @param {Function} mapFun Map function 792 | * @param {Function} reduceFun Reduce function 793 | * @param {Function} language Language the map / reduce funs are 794 | * implemented in 795 | * @param {ajaxSettings} options jQuery ajax settings 797 | */ 798 | query: function(mapFun, reduceFun, language, options) { 799 | language = language || "javascript"; 800 | if (typeof(mapFun) !== "string") { 801 | mapFun = mapFun.toSource ? mapFun.toSource() 802 | : "(" + mapFun.toString() + ")"; 803 | } 804 | var body = {language: language, map: mapFun}; 805 | if (reduceFun != null) { 806 | if (typeof(reduceFun) !== "string") 807 | reduceFun = reduceFun.toSource ? reduceFun.toSource() 808 | : "(" + reduceFun.toString() + ")"; 809 | body.reduce = reduceFun; 810 | } 811 | ajax({ 812 | type: "POST", 813 | url: this.uri + "_temp_view" + encodeOptions(options), 814 | contentType: "application/json", data: toJSON(body) 815 | }, 816 | options, 817 | "An error occurred querying the database" 818 | ); 819 | }, 820 | 821 | /** 822 | * Fetch a _list view output, you can specify a list of 823 | * keys in the options object to recieve only those keys. 824 | * @see 827 | * docs for /db/_design/design-doc/_list/l1/v1 828 | * @param {String} list Listname in the form of ddoc/listname 829 | * @param {String} view View to run list against 830 | * @param {options} CouchDB View Options 832 | * @param {ajaxSettings} options jQuery ajax settings 834 | */ 835 | list: function(list, view, options, ajaxOptions) { 836 | var list = list.split('/'); 837 | var options = options || {}; 838 | var type = 'GET'; 839 | var data = null; 840 | if (options['keys']) { 841 | type = 'POST'; 842 | var keys = options['keys']; 843 | delete options['keys']; 844 | data = toJSON({'keys': keys }); 845 | } 846 | ajax({ 847 | type: type, 848 | data: data, 849 | url: this.uri + '_design/' + list[0] + 850 | '/_list/' + list[1] + '/' + view + encodeOptions(options) 851 | }, 852 | ajaxOptions, 'An error occured accessing the list' 853 | ); 854 | }, 855 | 856 | /** 857 | * Executes the specified view-name from the specified design-doc 858 | * design document, you can specify a list of keys 859 | * in the options object to recieve only those keys. 860 | * @see docs for /db/ 863 | * _design/design-doc/_list/l1/v1 864 | * @param {String} name View to run list against 865 | * @param {ajaxSettings} options jQuery ajax settings 867 | */ 868 | view: function(name, options) { 869 | var name = name.split('/'); 870 | var options = options || {}; 871 | var type = "GET"; 872 | var data= null; 873 | if (options["keys"]) { 874 | type = "POST"; 875 | var keys = options["keys"]; 876 | delete options["keys"]; 877 | data = toJSON({ "keys": keys }); 878 | } 879 | ajax({ 880 | type: type, 881 | data: data, 882 | url: this.uri + "_design/" + name[0] + 883 | "/_view/" + name[1] + encodeOptions(options) 884 | }, 885 | options, "An error occurred accessing the view" 886 | ); 887 | }, 888 | 889 | /** 890 | * Fetch an arbitrary CouchDB database property 891 | * @see docs for /db/_prop 893 | * @param {String} propName Propery name to fetch 894 | * @param {ajaxSettings} options jQuery ajax settings 896 | * @param {ajaxSettings} ajaxOptions jQuery ajax settings 898 | */ 899 | getDbProperty: function(propName, options, ajaxOptions) { 900 | ajax({url: this.uri + propName + encodeOptions(options)}, 901 | options, 902 | "The property could not be retrieved", 903 | ajaxOptions 904 | ); 905 | }, 906 | 907 | /** 908 | * Set an arbitrary CouchDB database property 909 | * @see docs for /db/_prop 911 | * @param {String} propName Propery name to fetch 912 | * @param {String} propValue Propery value to set 913 | * @param {ajaxSettings} options jQuery ajax settings 915 | * @param {ajaxSettings} ajaxOptions jQuery ajax settings 917 | */ 918 | setDbProperty: function(propName, propValue, options, ajaxOptions) { 919 | ajax({ 920 | type: "PUT", 921 | url: this.uri + propName + encodeOptions(options), 922 | data : JSON.stringify(propValue) 923 | }, 924 | options, 925 | "The property could not be updated", 926 | ajaxOptions 927 | ); 928 | } 929 | }; 930 | }, 931 | 932 | encodeDocId: encodeDocId, 933 | 934 | /** 935 | * Accessing the root of a CouchDB instance returns meta information about 936 | * the instance. The response is a JSON structure containing information 937 | * about the server, including a welcome message and the version of the 938 | * server. 939 | * @see 941 | * docs for GET / 942 | * @param {ajaxSettings} options jQuery ajax settings 944 | */ 945 | info: function(options) { 946 | ajax( 947 | {url: this.urlPrefix + "/"}, 948 | options, 949 | "Server information could not be retrieved" 950 | ); 951 | }, 952 | 953 | /** 954 | * Request, configure, or stop, a replication operation. 955 | * @see docs for POST /_replicate 958 | * @param {String} source Path or url to source database 959 | * @param {String} target Path or url to target database 960 | * @param {ajaxSettings} ajaxOptions jQuery ajax settings 962 | * @param {Object} repOpts Additional replication options 963 | */ 964 | replicate: function(source, target, ajaxOptions, repOpts) { 965 | repOpts = $.extend({source: source, target: target}, repOpts); 966 | if (repOpts.continuous && !repOpts.cancel) { 967 | ajaxOptions.successStatus = 202; 968 | } 969 | ajax({ 970 | type: "POST", url: this.urlPrefix + "/_replicate", 971 | data: JSON.stringify(repOpts), 972 | contentType: "application/json" 973 | }, 974 | ajaxOptions, 975 | "Replication failed" 976 | ); 977 | }, 978 | 979 | /** 980 | * Fetch a new UUID 981 | * @see docs for /_uuids 984 | * @param {Int} cacheNum Number of uuids to keep cached for future use 985 | */ 986 | newUUID: function(cacheNum) { 987 | if (cacheNum === undefined) { 988 | cacheNum = 1; 989 | } 990 | if (!uuidCache.length) { 991 | ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async: 992 | false}, { 993 | success: function(resp) { 994 | uuidCache = resp.uuids; 995 | } 996 | }, 997 | "Failed to retrieve UUID batch." 998 | ); 999 | } 1000 | return uuidCache.shift(); 1001 | } 1002 | }); 1003 | 1004 | /** 1005 | * @private 1006 | */ 1007 | function ajax(obj, options, errorMessage, ajaxOptions) { 1008 | options = $.extend({successStatus: 200}, options); 1009 | ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions); 1010 | errorMessage = errorMessage || "Unknown error"; 1011 | $.ajax($.extend($.extend({ 1012 | type: "GET", dataType: "json", cache : !$.browser.msie, 1013 | beforeSend: function(xhr){ 1014 | if(ajaxOptions && ajaxOptions.headers){ 1015 | for (var header in ajaxOptions.headers){ 1016 | xhr.setRequestHeader(header, ajaxOptions.headers[header]); 1017 | } 1018 | } 1019 | }, 1020 | complete: function(req) { 1021 | try { 1022 | var resp = $.parseJSON(req.responseText); 1023 | } catch(e) { 1024 | if (options.error) { 1025 | options.error(req.status, req, e); 1026 | } else { 1027 | alert(errorMessage + ": " + e); 1028 | } 1029 | return; 1030 | } 1031 | if (options.ajaxStart) { 1032 | options.ajaxStart(resp); 1033 | } 1034 | if (req.status == options.successStatus) { 1035 | if (options.beforeSuccess) options.beforeSuccess(req, resp); 1036 | if (options.success) options.success(resp); 1037 | } else if (options.error) { 1038 | options.error(req.status, resp && resp.error || 1039 | errorMessage, resp && resp.reason || "no response"); 1040 | } else { 1041 | alert(errorMessage + ": " + resp.reason); 1042 | } 1043 | } 1044 | }, obj), ajaxOptions)); 1045 | } 1046 | 1047 | /** 1048 | * @private 1049 | */ 1050 | function fullCommit(options) { 1051 | var options = options || {}; 1052 | if (typeof options.ensure_full_commit !== "undefined") { 1053 | var commit = options.ensure_full_commit; 1054 | delete options.ensure_full_commit; 1055 | return function(xhr) { 1056 | xhr.setRequestHeader('Accept', 'application/json'); 1057 | xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString()); 1058 | }; 1059 | } 1060 | }; 1061 | 1062 | /** 1063 | * @private 1064 | */ 1065 | // Convert a options object to an url query string. 1066 | // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"' 1067 | function encodeOptions(options) { 1068 | var buf = []; 1069 | if (typeof(options) === "object" && options !== null) { 1070 | for (var name in options) { 1071 | if ($.inArray(name, 1072 | ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0) 1073 | continue; 1074 | var value = options[name]; 1075 | if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) { 1076 | value = toJSON(value); 1077 | } 1078 | buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); 1079 | } 1080 | } 1081 | return buf.length ? "?" + buf.join("&") : ""; 1082 | } 1083 | 1084 | /** 1085 | * @private 1086 | */ 1087 | function toJSON(obj) { 1088 | return obj !== null ? JSON.stringify(obj) : null; 1089 | } 1090 | 1091 | })(jQuery); 1092 | --------------------------------------------------------------------------------