├── README.md ├── _attachments ├── costco.css ├── costco.js ├── costco.less ├── index.html ├── jquery.couch.app.js ├── jquery.couch.js ├── jquery.min.js ├── less-1.0.35.min.js └── underscore-min.js ├── _id ├── couchapp.json ├── language └── vendor └── couchapp ├── README.md ├── _attachments ├── jquery.couch.app.js ├── jquery.couch.app.util.js ├── jquery.evently.js ├── jquery.mustache.js ├── jquery.pathbinder.js └── loader.js ├── evently ├── README.md ├── account │ ├── _init.js │ ├── adminParty │ │ └── mustache.html │ ├── doLogin.js │ ├── doLogout.js │ ├── doSignup.js │ ├── loggedIn │ │ ├── after.js │ │ ├── data.js │ │ ├── mustache.html │ │ └── selectors.json │ ├── loggedOut │ │ ├── mustache.html │ │ └── selectors.json │ ├── loginForm │ │ ├── after.js │ │ ├── mustache.html │ │ └── selectors │ │ │ ├── a[href=#signup].json │ │ │ └── form │ │ │ └── submit.js │ └── signupForm │ │ ├── after.js │ │ ├── mustache.html │ │ └── selectors │ │ ├── a[href=#login].json │ │ └── form │ │ └── submit.js └── profile │ ├── loggedIn.js │ ├── loggedOut │ ├── after.js │ └── mustache.html │ ├── noProfile │ ├── data.js │ ├── mustache.html │ └── selectors │ │ └── form │ │ └── submit.js │ └── profileReady │ ├── after.js │ ├── data.js │ └── mustache.html ├── lib ├── atom.js ├── cache.js ├── docform.js ├── linkup.js ├── list.js ├── markdown.js ├── md5.js ├── mustache.js ├── path.js └── redirect.js └── metadata.json /README.md: -------------------------------------------------------------------------------- 1 | #costo 2 | [costco](http://harthur.github.com/costco) is a small UI for bulk editing CouchDB documents. 3 | 4 | #install 5 | costco is a [couchapp](http://couchapp.org), you can push it to any db: 6 | 7 | git clone http://github.com/harthur/costco.git 8 | cd costco 9 | couchapp push . http://hostname:5984/mydatabase 10 | 11 | #usage 12 | costco takes a map function and executes it on all the docs in the database. The map function should return the new doc that you'd like to replace the old one, or `null` if it should be deleted. Returning `undefined` does nothing to that doc. 13 | 14 | An example map function that increments a field in all the docs and deletes some docs based on another field: 15 | 16 | function(doc) { 17 | if(doc.text.length > 200) 18 | return null; 19 | 20 | doc.count++; 21 | return doc; 22 | } 23 | 24 | [More examples here](http://harthur.github.com/costco/#examples). Right now this **straight-up loads all the docs into memory**, some batch loading might come in the future. 25 | -------------------------------------------------------------------------------- /_attachments/costco.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | font-size: 90%; 4 | margin: 0; 5 | } 6 | .boxShadow { 7 | -moz-box-shadow: 0 1px 3px #999999; 8 | -webkit-box-shadow: 0 1px 3px #999999; 9 | } 10 | #header { 11 | padding: 7px 5px 5px 0px; 12 | text-align: vertical; 13 | } 14 | #costco-banner { 15 | padding: 8px; 16 | background-color: #919090; 17 | color: white; 18 | margin-right: 12px; 19 | letter-spacing: 0.6px; 20 | } 21 | #new-db-option { font-style: italic; } 22 | #create-box { padding: 20px; } 23 | #container { 24 | margin: 80px auto; 25 | width: 500px; 26 | } 27 | #map-message { 28 | margin-bottom: 8px; 29 | color: #333333; 30 | } 31 | #add-message { 32 | font-style: italic; 33 | color: #555555; 34 | } 35 | #examples { 36 | text-decoration: none; 37 | color: #888888; 38 | font-size: 0.9em; 39 | float: right; 40 | padding: 4px; 41 | border: 1px dotted #aaaaaa; 42 | -moz-border-radius: 3px; 43 | -webkit-border-radius: 3px; 44 | border-radius: 3px; 45 | } 46 | #map-function { 47 | width: 100%; 48 | height: 200px; 49 | font-family: monospace; 50 | font-size: 1.1em; 51 | margin: 8px 0; 52 | } 53 | #map-container { margin-bottom: 20px; } 54 | #update-button { 55 | -moz-border-radius: 6px; 56 | -webkit-border-radius: 6px; 57 | border-radius: 6px; 58 | background-color: #cefcce; 59 | margin-top: 10px; 60 | border: 1px solid #aaaaaa; 61 | } 62 | .button { 63 | padding: 0.5em 0.8em; 64 | display: inline-block; 65 | cursor: pointer; 66 | -moz-border-radius: 8px; 67 | -webkit-border-radius: 8px; 68 | border-radius: 8px; 69 | -moz-box-shadow: 0 1px 3px #999999; 70 | -webkit-box-shadow: 0 1px 3px #999999; 71 | color: #222222; 72 | } 73 | .error { 74 | padding: 10px; 75 | border: 1px solid red; 76 | -moz-border-radius: 4px; 77 | -webkit-border-radius: 4px; 78 | border-radius: 4px; 79 | } 80 | .success { 81 | padding: 10px; 82 | border: 1px solid limegreen; 83 | -moz-border-radius: 4px; 84 | -webkit-border-radius: 4px; 85 | border-radius: 4px; 86 | } 87 | #update-container .button { 88 | float: left; 89 | margin-right: 10px; 90 | background-color: #f2ebec; 91 | padding: 0.2em 0.7em; 92 | } 93 | #status { 94 | margin: 10px auto; 95 | line-height: 2em; 96 | } 97 | -------------------------------------------------------------------------------- /_attachments/costco.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $("#map-function").val("function(doc) {\n \n return doc;\n}"); 3 | $("#map-function").focus().get(0).setSelectionRange(18, 18); 4 | 5 | $("#update-button").click(costco.computeChanges); 6 | $("#update-container").hide(); 7 | $("#status").click(function() {$("#status").empty()}); 8 | 9 | $("#continue-button").click(function() { 10 | $("#status").text("updating docs..."); 11 | costco.updateDocs(function() { 12 | $("#status").html("Docs successfully updated"); 13 | }); 14 | $("#update-container").hide(); 15 | }); 16 | 17 | $("#cancel-button").click(function() { 18 | $('#update-container').hide(); 19 | $('#status').empty(); 20 | }); 21 | 22 | $("#new-db-option").click(function() { 23 | $("#create-box").show(); 24 | }); 25 | $("#create-box").hide(); 26 | $("#create-db").click(costco.createDb); 27 | 28 | $.couch.allDbs({ 29 | success: function(dbs) { 30 | dbs.forEach(function(db){ 31 | $("").val(db).html(db).appendTo("#existing-dbs"); 32 | }); 33 | $.couch.app(function(app) { 34 | $('#db-select').val(app.db.name); 35 | }); 36 | }, 37 | error: function(req, status, err) { 38 | $("#status").html("error fetching dbs: " 39 | + err + ""); 40 | } 41 | }); 42 | }); 43 | 44 | var costco = { 45 | toUpdate : [], 46 | 47 | getDb : function() { 48 | return $.couch.db($("#db-select").val()); 49 | }, 50 | 51 | createDb : function() { 52 | var dbname = $("#new-db-name").val(); 53 | $.couch.db(dbname).create({ 54 | success: function() { 55 | $("").val(dbname).html(dbname).appendTo("#existing-dbs") 56 | $("#db-select").val(dbname); 57 | $("#create-box").hide(); 58 | }, 59 | error: function(req, status, err) { 60 | $("#status").html("could not create db: " 61 | + err + ""); 62 | } 63 | }); 64 | }, 65 | 66 | computeChanges : function() { 67 | $("#status").html("Computing changes..."); 68 | 69 | var text = $("#map-function").val(); 70 | var docs; 71 | try { 72 | docs = JSON.parse(text); 73 | } 74 | catch(e) { 75 | try { 76 | docs = JSON.parse("[" + text + "]"); 77 | } 78 | catch(e) { 79 | // not JSON, must be an edit function 80 | return costco.mapDocs(text); 81 | } 82 | } 83 | if(!docs.length) 84 | docs = [docs]; 85 | 86 | costco.toUpdate = docs; 87 | 88 | $("#status").html("About to add " + docs.length 89 | + " docs to " + costco.getDb().name + ""); 90 | $("#update-container").show(); 91 | }, 92 | 93 | mapDocs : function(funcString) { 94 | try { 95 | eval("var editFunc = " + funcString); 96 | } catch(e) { 97 | $("#status").html("error evaluating function: " 98 | + e + ""); 99 | return; 100 | } 101 | 102 | costco.toUpdate = []; 103 | var deleted = 0 104 | , edited = 0 105 | , failed = 0; 106 | 107 | costco.getDocs(function(data) { 108 | var rows = data.rows; 109 | rows.forEach(function(row) { 110 | var doc = row.doc; 111 | try { 112 | var updated = editFunc(_.clone(doc)); 113 | } catch(e) { 114 | failed++; // ignore if it throws on this doc 115 | return; 116 | } 117 | if(updated === null) { 118 | doc._deleted = true; 119 | costco.toUpdate.push(doc); 120 | deleted++; 121 | } 122 | else if(updated) { 123 | costco.toUpdate.push(updated); 124 | edited++; 125 | } 126 | }); 127 | // todo: make template for this 128 | $("#status").html("About to edit " + edited 129 | + " docs and delete " + deleted + " docs from " 130 | + costco.getDb().name + ""); 131 | if(failed) 132 | $("#status").append(". Edit function threw on " + failed + " docs"); 133 | $("#update-container").show(); 134 | }); 135 | }, 136 | 137 | updateDocs : function(callback) { 138 | if(!costco.toUpdate.length) 139 | return callback(); 140 | 141 | costco.getDb().bulkSave({docs: costco.toUpdate}, { 142 | success: callback, 143 | error: function(req, status, err) { 144 | $("#status").html("error updating docs: " 145 | + err + ""); 146 | } 147 | }); 148 | }, 149 | 150 | getDocs : function(callback) { 151 | costco.getDb().allDocs({ 152 | include_docs : true, 153 | success : callback, 154 | error: function(req, status, err) { 155 | $("#status").html("error retrieving docs: " 156 | + err + ""); 157 | } 158 | }); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /_attachments/costco.less: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | font-size: 90%; 4 | margin: 0; 5 | } 6 | 7 | .roundedCorners (@radius: 8px) { 8 | -moz-border-radius: @radius; 9 | -webkit-border-radius: @radius; 10 | border-radius: @radius; 11 | } 12 | 13 | .boxShadow { 14 | -moz-box-shadow: 0 1px 3px #999; 15 | -webkit-box-shadow: 0 1px 3px #999; 16 | } 17 | 18 | .msg(@color: red) { 19 | padding: 10px; 20 | border: 1px solid @color; 21 | .roundedCorners(4px); 22 | } 23 | 24 | #header { 25 | padding: 7px 5px 5px 0px; 26 | text-align: vertical; 27 | } 28 | 29 | #costco-banner { 30 | padding: 8px; 31 | background-color: #919090; 32 | color: white; 33 | margin-right: 12px; 34 | letter-spacing: 0.6px; 35 | } 36 | 37 | #new-db-option { 38 | font-style: italic; 39 | } 40 | 41 | #create-box { 42 | padding: 20px; 43 | } 44 | 45 | #container { 46 | margin: 80px auto; 47 | width: 500px; 48 | } 49 | 50 | #map-message { 51 | margin-bottom: 8px; 52 | color: #333; 53 | } 54 | 55 | #add-message { 56 | font-style: italic; 57 | color: #555; 58 | } 59 | 60 | #examples { 61 | text-decoration: none; 62 | color: #888; 63 | font-size: .9em; 64 | float:right; 65 | padding: 4px; 66 | border: 1px dotted #aaa; 67 | .roundedCorners(3px); 68 | } 69 | 70 | #map-function { 71 | width: 100%; 72 | height: 200px; 73 | font-family: monospace; 74 | font-size: 1.1em; 75 | margin: 8px 0; 76 | } 77 | 78 | #map-container { 79 | margin-bottom: 20px; 80 | } 81 | 82 | #update-button { 83 | .roundedCorners(6px); 84 | background-color: #CEFCCE; 85 | margin-top: 10px; 86 | border: 1px solid #AAA; 87 | } 88 | 89 | .button { 90 | padding: .5em .8em; 91 | display: inline-block; 92 | cursor: pointer; 93 | .roundedCorners(8px); 94 | .boxShadow; 95 | color: #222; 96 | } 97 | 98 | .error { 99 | .msg(red); 100 | } 101 | 102 | .success { 103 | .msg(limegreen); 104 | } 105 | 106 | #update-container { 107 | .button { 108 | float: left; 109 | margin-right: 10px; 110 | background-color: #F2EBEC; 111 | padding: .2em .7em; 112 | } 113 | } 114 | 115 | #status { 116 | margin: 10px auto; 117 | line-height: 2em; 118 | } 119 | -------------------------------------------------------------------------------- /_attachments/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | costco - bulk edit couchdb docs 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 28 |
29 |
30 |
Edit function 31 | or JSON docs to add:examples
32 | 33 | Update Docs 34 |
35 | 36 |
37 | 38 |
39 |
40 | Continue 41 | Cancel 42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /_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) { 24 | this.doc_id = "_design/"+name; 25 | this.view = function(view, opts) { 26 | db.view(name+'/'+view, opts); 27 | }; 28 | this.list = function(list, view, opts) { 29 | db.list(name+'/'+list, view, opts); 30 | }; 31 | } 32 | 33 | $.couch.app = $.couch.app || function(appFun, opts) { 34 | opts = opts || {}; 35 | $(function() { 36 | var urlPrefix = opts.urlPrefix || ""; 37 | $.couch.urlPrefix = urlPrefix; 38 | 39 | var index = urlPrefix.split('/').length; 40 | var fragments = unescape(document.location.href).split('/'); 41 | var dbname = opts.db || fragments[index + 2]; 42 | var dname = opts.design || fragments[index + 4]; 43 | 44 | var db = $.couch.db(dbname); 45 | var design = new Design(db, dname); 46 | 47 | // docForm applies CouchDB behavior to HTML forms. 48 | // todo make this a couch.app plugin 49 | function docForm(formSelector, opts) { 50 | var localFormDoc = {}; 51 | opts = opts || {}; 52 | opts.fields = opts.fields || []; 53 | 54 | // turn the form into deep json 55 | // field names like 'author-email' get turned into json like 56 | // {"author":{"email":"quentin@example.com"}} 57 | function formToDeepJSON(form, fields, doc) { 58 | form = $(form); 59 | fields.forEach(function(field) { 60 | var element = form.find("[name="+field+"]"); 61 | if (element.attr('type') === 'checkbox') { 62 | var val = element.attr('checked'); 63 | } else { 64 | var val = element.val(); 65 | if (!val) return; 66 | } 67 | var parts = field.split('-'); 68 | var frontObj = doc, frontName = parts.shift(); 69 | while (parts.length > 0) { 70 | frontObj[frontName] = frontObj[frontName] || {}; 71 | frontObj = frontObj[frontName]; 72 | frontName = parts.shift(); 73 | } 74 | frontObj[frontName] = val; 75 | }); 76 | } 77 | 78 | // Apply the behavior 79 | $(formSelector).submit(function(e) { 80 | e.preventDefault(); 81 | if (opts.validate && opts.validate() == false) { return false;} 82 | // formToDeepJSON acts on localFormDoc by reference 83 | formToDeepJSON(this, opts.fields, localFormDoc); 84 | if (opts.beforeSave) {opts.beforeSave(localFormDoc);} 85 | db.saveDoc(localFormDoc, { 86 | success : function(resp) { 87 | if (opts.success) {opts.success(resp, localFormDoc);} 88 | } 89 | }); 90 | 91 | return false; 92 | }); 93 | 94 | // populate form from an existing doc 95 | function docToForm(doc) { 96 | var form = $(formSelector); 97 | // fills in forms 98 | opts.fields.forEach(function(field) { 99 | var parts = field.split('-'); 100 | var run = true, frontObj = doc, frontName = parts.shift(); 101 | while (frontObj && parts.length > 0) { 102 | frontObj = frontObj[frontName]; 103 | frontName = parts.shift(); 104 | } 105 | if (frontObj && frontObj[frontName]) { 106 | var element = form.find("[name="+field+"]"); 107 | if (element.attr('type') === 'checkbox') { 108 | element.attr('checked', frontObj[frontName]); 109 | } else { 110 | element.val(frontObj[frontName]); 111 | } 112 | } 113 | }); 114 | } 115 | 116 | if (opts.id) { 117 | db.openDoc(opts.id, { 118 | attachPrevRev : opts.attachPrevRev, 119 | error: function() { 120 | if (opts.error) {opts.error.apply(opts, arguments);} 121 | }, 122 | success: function(doc) { 123 | if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(doc);} 124 | localFormDoc = doc; 125 | docToForm(doc); 126 | }}); 127 | } else if (opts.template) { 128 | if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(opts.template);} 129 | localFormDoc = opts.template; 130 | docToForm(localFormDoc); 131 | } 132 | var instance = { 133 | deleteDoc : function(opts) { 134 | opts = opts || {}; 135 | if (confirm("Really delete this document?")) { 136 | db.removeDoc(localFormDoc, opts); 137 | } 138 | }, 139 | localDoc : function() { 140 | formToDeepJSON(formSelector, opts.fields, localFormDoc); 141 | return localFormDoc; 142 | } 143 | }; 144 | return instance; 145 | } 146 | 147 | function resolveModule(names, parent, current) { 148 | if (names.length === 0) { 149 | if (typeof current != "string") { 150 | throw ["error","invalid_require_path", 151 | 'Must require a JavaScript string, not: '+(typeof current)]; 152 | } 153 | return [current, parent]; 154 | } 155 | // we need to traverse the path 156 | var n = names.shift(); 157 | if (n == '..') { 158 | if (!(parent && parent.parent)) { 159 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 160 | } 161 | return resolveModule(names, parent.parent.parent, parent.parent); 162 | } else if (n == '.') { 163 | if (!parent) { 164 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 165 | } 166 | return resolveModule(names, parent.parent, parent); 167 | } 168 | if (!current[n]) { 169 | throw ["error", "invalid_require_path", 'Object has no property "'+n+'". '+JSON.stringify(current)]; 170 | } 171 | var p = current; 172 | current = current[n]; 173 | current.parent = p; 174 | return resolveModule(names, p, current); 175 | } 176 | 177 | var p = document.location.pathname.split('/'); 178 | p.shift(); 179 | var qs = document.location.search.replace(/^\?/,'').split('&'); 180 | var q = {}; 181 | qs.forEach(function(param) { 182 | var ps = param.split('='); 183 | var k = decodeURIComponent(ps[0]); 184 | var v = decodeURIComponent(ps[1]); 185 | if (["startkey", "endkey", "key"].indexOf(k) != -1) { 186 | q[k] = JSON.parse(v); 187 | } else { 188 | q[k] = v; 189 | } 190 | }); 191 | var mockReq = { 192 | path : p, 193 | query : q 194 | }; 195 | 196 | var appExports = $.extend({ 197 | db : db, 198 | design : design, 199 | view : design.view, 200 | list : design.list, 201 | docForm : docForm, 202 | req : mockReq 203 | }, $.couch.app.app); 204 | 205 | function handleDDoc(ddoc) { 206 | var moduleCache = []; 207 | 208 | function getCachedModule(name, parent) { 209 | var key, i, len = moduleCache.length; 210 | for (i=0;i>> 0; 296 | if (typeof fun != "function") 297 | throw new TypeError(); 298 | 299 | var thisp = arguments[1]; 300 | for (var i = 0; i < len; i++) 301 | { 302 | if (i in this) 303 | fun.call(thisp, this[i], i, this); 304 | } 305 | }; 306 | } 307 | 308 | if (!Array.prototype.indexOf) 309 | { 310 | Array.prototype.indexOf = function(elt) 311 | { 312 | var len = this.length >>> 0; 313 | 314 | var from = Number(arguments[1]) || 0; 315 | from = (from < 0) 316 | ? Math.ceil(from) 317 | : Math.floor(from); 318 | if (from < 0) 319 | from += len; 320 | 321 | for (; from < len; from++) 322 | { 323 | if (from in this && 324 | this[from] === elt) 325 | return from; 326 | } 327 | return -1; 328 | }; 329 | } 330 | -------------------------------------------------------------------------------- /_attachments/jquery.couch.js: -------------------------------------------------------------------------------- 1 | // $.couch is used to communicate with a CouchDB server, the server methods can 2 | // be called directly without creating an instance. Typically all methods are 3 | // passed an options object which defines a success callback which 4 | // is called with the data returned from the http request to CouchDB, you can 5 | // find the other settings that can be used in the options object 6 | // from 7 | // jQuery.ajax settings 8 | // 9 | // $.couch.activeTasks({ 10 | // success: function (data) { 11 | // console.log(data); 12 | // } 13 | // }); 14 | // 15 | // Outputs (for example): 16 | // 17 | // [ 18 | // { 19 | // "pid" : "<0.11599.0>", 20 | // "status" : "Copied 0 of 18369 changes (0%)", 21 | // "task" : "recipes", 22 | // "type" : "Database Compaction" 23 | // } 24 | // ] 25 | (function($) { 26 | 27 | $.couch = $.couch || {}; 28 | 29 | function encodeDocId(docID) { 30 | var parts = docID.split("/"); 31 | if (parts[0] == "_design") { 32 | parts.shift(); 33 | return "_design/" + encodeURIComponent(parts.join('/')); 34 | } 35 | return encodeURIComponent(docID); 36 | } 37 | 38 | var uuidCache = []; 39 | 40 | $.extend($.couch, { 41 | urlPrefix: '', 42 | 43 | // You can obtain a list of active tasks by using the `/_active_tasks` URL. 44 | // The result is a JSON array of the currently running tasks, with each task 45 | // being described with a single object. 46 | activeTasks: function(options) { 47 | return ajax( 48 | {url: this.urlPrefix + "/_active_tasks"}, 49 | options, 50 | "Active task status could not be retrieved" 51 | ); 52 | }, 53 | 54 | // Returns a list of all the databases in the CouchDB instance 55 | allDbs: function(options) { 56 | return ajax( 57 | {url: this.urlPrefix + "/_all_dbs"}, 58 | options, 59 | "An error occurred retrieving the list of all databases" 60 | ); 61 | }, 62 | 63 | // View and edit the CouchDB configuration, called with just the options 64 | // parameter the entire config is returned, you can be more specific by 65 | // passing the section and option parameters, if you specify a value that 66 | // value will be stored in the configuration. 67 | config: function(options, section, option, value) { 68 | var req = {url: this.urlPrefix + "/_config/"}; 69 | if (section) { 70 | req.url += encodeURIComponent(section) + "/"; 71 | if (option) { 72 | req.url += encodeURIComponent(option); 73 | } 74 | } 75 | if (value === null) { 76 | req.type = "DELETE"; 77 | } else if (value !== undefined) { 78 | req.type = "PUT"; 79 | req.data = toJSON(value); 80 | req.contentType = "application/json"; 81 | req.processData = false 82 | } 83 | 84 | return ajax(req, options, 85 | "An error occurred retrieving/updating the server configuration" 86 | ); 87 | }, 88 | 89 | // Returns the session information for the currently logged in user. 90 | session: function(options) { 91 | options = options || {}; 92 | return $.ajax({ 93 | type: "GET", url: this.urlPrefix + "/_session", 94 | beforeSend: function(xhr) { 95 | xhr.setRequestHeader('Accept', 'application/json'); 96 | }, 97 | complete: function(req) { 98 | var resp = $.parseJSON(req.responseText); 99 | if (req.status == 200) { 100 | if (options.success) options.success(resp); 101 | } else if (options.error) { 102 | options.error(req.status, resp.error, resp.reason); 103 | } else { 104 | alert("An error occurred getting session info: " + resp.reason); 105 | } 106 | } 107 | }); 108 | }, 109 | 110 | userDb : function(callback) { 111 | return $.couch.session({ 112 | success : function(resp) { 113 | var userDb = $.couch.db(resp.info.authentication_db); 114 | callback(userDb); 115 | } 116 | }); 117 | }, 118 | 119 | // Create a new user on the CouchDB server, user_doc is an 120 | // object with a name field and other information you want 121 | // to store relating to that user, for example 122 | // `{"name": "daleharvey"}` 123 | signup: function(user_doc, password, options) { 124 | options = options || {}; 125 | // prepare user doc based on name and password 126 | user_doc = this.prepareUserDoc(user_doc, password); 127 | return $.couch.userDb(function(db) { 128 | db.saveDoc(user_doc, options); 129 | }); 130 | }, 131 | 132 | // Populates a user doc with a new password. 133 | prepareUserDoc: function(user_doc, new_password) { 134 | if (typeof hex_sha1 == "undefined") { 135 | alert("creating a user doc requires sha1.js to be loaded in the page"); 136 | return; 137 | } 138 | var user_prefix = "org.couchdb.user:"; 139 | user_doc._id = user_doc._id || user_prefix + user_doc.name; 140 | if (new_password) { 141 | // handle the password crypto 142 | user_doc.salt = $.couch.newUUID(); 143 | user_doc.password_sha = hex_sha1(new_password + user_doc.salt); 144 | } 145 | user_doc.type = "user"; 146 | if (!user_doc.roles) { 147 | user_doc.roles = []; 148 | } 149 | return user_doc; 150 | }, 151 | 152 | // Authenticate against CouchDB, the options parameter is 153 | // expected to have name and password fields. 154 | login: function(options) { 155 | options = options || {}; 156 | return $.ajax({ 157 | type: "POST", url: this.urlPrefix + "/_session", dataType: "json", 158 | data: {name: options.name, password: options.password}, 159 | beforeSend: function(xhr) { 160 | xhr.setRequestHeader('Accept', 'application/json'); 161 | }, 162 | complete: function(req) { 163 | var resp = $.parseJSON(req.responseText); 164 | if (req.status == 200) { 165 | if (options.success) options.success(resp); 166 | } else if (options.error) { 167 | options.error(req.status, resp.error, resp.reason); 168 | } else { 169 | alert("An error occurred logging in: " + resp.reason); 170 | } 171 | } 172 | }); 173 | }, 174 | 175 | 176 | // Delete your current CouchDB user session 177 | logout: function(options) { 178 | options = options || {}; 179 | return $.ajax({ 180 | type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json", 181 | username : "_", password : "_", 182 | beforeSend: function(xhr) { 183 | xhr.setRequestHeader('Accept', 'application/json'); 184 | }, 185 | complete: function(req) { 186 | var resp = $.parseJSON(req.responseText); 187 | if (req.status == 200) { 188 | if (options.success) options.success(resp); 189 | } else if (options.error) { 190 | options.error(req.status, resp.error, resp.reason); 191 | } else { 192 | alert("An error occurred logging out: " + resp.reason); 193 | } 194 | } 195 | }); 196 | }, 197 | 198 | // $.couch.db is used to communicate with a specific CouchDB database 199 | //
var $db = $.couch.db("mydatabase");
200 |     // $db.allApps({
201 |     //  success: function (data) {
202 |     //    ... process data ...
203 |     //  }
204 |     //});
205 |     //
206 | db: function(name, db_opts) { 207 | db_opts = db_opts || {}; 208 | var rawDocs = {}; 209 | function maybeApplyVersion(doc) { 210 | if (doc._id && doc._rev && rawDocs[doc._id] && 211 | rawDocs[doc._id].rev == doc._rev) { 212 | // todo: can we use commonjs require here? 213 | if (typeof Base64 == "undefined") { 214 | alert("please include /_utils/script/base64.js in the page for " + 215 | "base64 support"); 216 | return false; 217 | } else { 218 | doc._attachments = doc._attachments || {}; 219 | doc._attachments["rev-"+doc._rev.split("-")[0]] = { 220 | content_type :"application/json", 221 | data : Base64.encode(rawDocs[doc._id].raw) 222 | }; 223 | return true; 224 | } 225 | } 226 | }; 227 | return { 228 | name: name, 229 | uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/", 230 | 231 | // Request compaction of the specified database. 232 | compact: function(options) { 233 | $.extend(options, {successStatus: 202}); 234 | return ajax({ 235 | type: "POST", url: this.uri + "_compact", 236 | data: "", processData: false 237 | }, 238 | options, 239 | "The database could not be compacted" 240 | ); 241 | }, 242 | 243 | // Cleans up the cached view output on disk for a given view. 244 | viewCleanup: function(options) { 245 | $.extend(options, {successStatus: 202}); 246 | return ajax({ 247 | type: "POST", url: this.uri + "_view_cleanup", 248 | data: "", processData: false 249 | }, 250 | options, 251 | "The views could not be cleaned up" 252 | ); 253 | }, 254 | 255 | // Compacts the view indexes associated with the specified design 256 | // document. You can use this in place of the full database compaction 257 | // if you know a specific set of view indexes have been affected by a 258 | // recent database change. 259 | compactView: function(groupname, options) { 260 | $.extend(options, {successStatus: 202}); 261 | return ajax({ 262 | type: "POST", url: this.uri + "_compact/" + groupname, 263 | data: "", processData: false 264 | }, 265 | options, 266 | "The view could not be compacted" 267 | ); 268 | }, 269 | 270 | // Create a new database 271 | create: function(options) { 272 | $.extend(options, {successStatus: 201}); 273 | return ajax({ 274 | type: "PUT", url: this.uri, contentType: "application/json", 275 | data: "", processData: false 276 | }, 277 | options, 278 | "The database could not be created" 279 | ); 280 | }, 281 | 282 | // Deletes the specified database, and all the documents and 283 | // attachments contained within it. 284 | drop: function(options) { 285 | return ajax( 286 | {type: "DELETE", url: this.uri}, 287 | options, 288 | "The database could not be deleted" 289 | ); 290 | }, 291 | 292 | // Gets information about the specified database. 293 | info: function(options) { 294 | return ajax( 295 | {url: this.uri}, 296 | options, 297 | "Database information could not be retrieved" 298 | ); 299 | }, 300 | 301 | // $.couch.db.changes provides an API for subscribing to the changes 302 | // feed 303 | //
var $changes = $.couch.db("mydatabase").changes();
304 |         // $changes.onChange = function (data) {
305 |         //    ... process data ...
306 |         // }
307 |         // $changes.stop();
308 |         // 
309 | changes: function(since, options) { 310 | 311 | options = options || {}; 312 | // set up the promise object within a closure for this handler 313 | var timeout = 100, db = this, active = true, 314 | listeners = [], 315 | promise = { 316 | // Add a listener callback 317 | onChange : function(fun) { 318 | listeners.push(fun); 319 | }, 320 | // Stop subscribing to the changes feed 321 | stop : function() { 322 | active = false; 323 | } 324 | }; 325 | 326 | // call each listener when there is a change 327 | function triggerListeners(resp) { 328 | $.each(listeners, function() { 329 | this(resp); 330 | }); 331 | }; 332 | 333 | // when there is a change, call any listeners, then check for 334 | // another change 335 | options.success = function(resp) { 336 | timeout = 100; 337 | if (active) { 338 | since = resp.last_seq; 339 | triggerListeners(resp); 340 | getChangesSince(); 341 | }; 342 | }; 343 | options.error = function() { 344 | if (active) { 345 | setTimeout(getChangesSince, timeout); 346 | timeout = timeout * 2; 347 | } 348 | }; 349 | 350 | // actually make the changes request 351 | function getChangesSince() { 352 | var opts = $.extend({heartbeat : 10 * 1000}, options, { 353 | feed : "longpoll", 354 | since : since 355 | }); 356 | ajax( 357 | {url: db.uri + "_changes"+encodeOptions(opts)}, 358 | options, 359 | "Error connecting to "+db.uri+"/_changes." 360 | ); 361 | } 362 | 363 | // start the first request 364 | if (since) { 365 | getChangesSince(); 366 | } else { 367 | db.info({ 368 | success : function(info) { 369 | since = info.update_seq; 370 | getChangesSince(); 371 | } 372 | }); 373 | } 374 | return promise; 375 | }, 376 | 377 | // Fetch all the docs in this db, you can specify an array of keys to 378 | // fetch by passing the keys field in the 379 | // options 380 | // parameter. 381 | allDocs: function(options) { 382 | console.log("all docs: " + this.uri) 383 | var type = "GET"; 384 | var data = null; 385 | if (options["keys"]) { 386 | type = "POST"; 387 | var keys = options["keys"]; 388 | delete options["keys"]; 389 | data = toJSON({ "keys": keys }); 390 | } 391 | return ajax({ 392 | type: type, 393 | data: data, 394 | url: this.uri + "_all_docs" + encodeOptions(options) 395 | }, 396 | options, 397 | "An error occurred retrieving a list of all documents" 398 | ); 399 | }, 400 | 401 | // Fetch all the design docs in this db 402 | allDesignDocs: function(options) { 403 | return this.allDocs($.extend( 404 | {startkey:"_design", endkey:"_design0"}, options)); 405 | }, 406 | 407 | // Fetch all the design docs with an index.html, options 408 | // parameter expects an eachApp field which is a callback 409 | // called on each app found. 410 | allApps: function(options) { 411 | options = options || {}; 412 | var self = this; 413 | if (options.eachApp) { 414 | this.allDesignDocs({ 415 | success: function(resp) { 416 | $.each(resp.rows, function() { 417 | self.openDoc(this.id, { 418 | success: function(ddoc) { 419 | var index, appPath, appName = ddoc._id.split('/'); 420 | appName.shift(); 421 | appName = appName.join('/'); 422 | index = ddoc.couchapp && ddoc.couchapp.index; 423 | if (index) { 424 | appPath = ['', name, ddoc._id, index].join('/'); 425 | } else if (ddoc._attachments && 426 | ddoc._attachments["index.html"]) { 427 | appPath = ['', name, ddoc._id, "index.html"].join('/'); 428 | } 429 | if (appPath) options.eachApp(appName, appPath, ddoc); 430 | } 431 | }); 432 | }); 433 | } 434 | }); 435 | } else { 436 | alert("Please provide an eachApp function for allApps()"); 437 | } 438 | }, 439 | 440 | // Returns the specified doc from the specified db. 441 | openDoc: function(docId, options, ajaxOptions) { 442 | options = options || {}; 443 | if (db_opts.attachPrevRev || options.attachPrevRev) { 444 | $.extend(options, { 445 | beforeSuccess : function(req, doc) { 446 | rawDocs[doc._id] = { 447 | rev : doc._rev, 448 | raw : req.responseText 449 | }; 450 | } 451 | }); 452 | } else { 453 | $.extend(options, { 454 | beforeSuccess : function(req, doc) { 455 | if (doc["jquery.couch.attachPrevRev"]) { 456 | rawDocs[doc._id] = { 457 | rev : doc._rev, 458 | raw : req.responseText 459 | }; 460 | } 461 | } 462 | }); 463 | } 464 | return ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)}, 465 | options, 466 | "The document could not be retrieved", 467 | ajaxOptions 468 | ); 469 | }, 470 | 471 | // Create a new document in the specified database, using the supplied 472 | // JSON document structure. If the JSON structure includes the _id 473 | // field, then the document will be created with the specified document 474 | // ID. If the _id field is not specified, a new unique ID will be 475 | // generated. 476 | saveDoc: function(doc, options) { 477 | options = options || {}; 478 | var db = this; 479 | var beforeSend = fullCommit(options); 480 | if (doc._id === undefined) { 481 | var method = "POST"; 482 | var uri = this.uri; 483 | } else { 484 | var method = "PUT"; 485 | var uri = this.uri + encodeDocId(doc._id); 486 | } 487 | var versioned = maybeApplyVersion(doc); 488 | return $.ajax({ 489 | type: method, url: uri + encodeOptions(options), 490 | contentType: "application/json", 491 | dataType: "json", data: toJSON(doc), 492 | beforeSend : beforeSend, 493 | complete: function(req) { 494 | var resp = $.parseJSON(req.responseText); 495 | if (req.status == 200 || req.status == 201 || req.status == 202) { 496 | doc._id = resp.id; 497 | doc._rev = resp.rev; 498 | if (versioned) { 499 | db.openDoc(doc._id, { 500 | attachPrevRev : true, 501 | success : function(d) { 502 | doc._attachments = d._attachments; 503 | if (options.success) options.success(resp); 504 | } 505 | }); 506 | } else { 507 | if (options.success) options.success(resp); 508 | } 509 | } else if (options.error) { 510 | options.error(req.status, resp.error, resp.reason); 511 | } else { 512 | alert("The document could not be saved: " + resp.reason); 513 | } 514 | } 515 | }); 516 | }, 517 | 518 | // Save a list of documents 519 | bulkSave: function(docs, options) { 520 | var beforeSend = fullCommit(options); 521 | $.extend(options, {successStatus: 201, beforeSend : beforeSend}); 522 | return ajax({ 523 | type: "POST", 524 | url: this.uri + "_bulk_docs" + encodeOptions(options), 525 | contentType: "application/json", data: toJSON(docs) 526 | }, 527 | options, 528 | "The documents could not be saved" 529 | ); 530 | }, 531 | 532 | // Deletes the specified document from the database. You must supply 533 | // the current (latest) revision and id of the document 534 | // to delete eg removeDoc({_id:"mydoc", _rev: "1-2345"}) 535 | removeDoc: function(doc, options) { 536 | return ajax({ 537 | type: "DELETE", 538 | url: this.uri + 539 | encodeDocId(doc._id) + 540 | encodeOptions({rev: doc._rev}) 541 | }, 542 | options, 543 | "The document could not be deleted" 544 | ); 545 | }, 546 | 547 | // Remove a set of documents 548 | bulkRemove: function(docs, options){ 549 | docs.docs = $.each( 550 | docs.docs, function(i, doc){ 551 | doc._deleted = true; 552 | } 553 | ); 554 | $.extend(options, {successStatus: 201}); 555 | return ajax({ 556 | type: "POST", 557 | url: this.uri + "_bulk_docs" + encodeOptions(options), 558 | data: toJSON(docs) 559 | }, 560 | options, 561 | "The documents could not be deleted" 562 | ); 563 | }, 564 | 565 | // The COPY command (which is non-standard HTTP) copies an existing 566 | // document to a new or existing document. 567 | copyDoc: function(docId, options, ajaxOptions) { 568 | ajaxOptions = $.extend(ajaxOptions, { 569 | complete: function(req) { 570 | var resp = $.parseJSON(req.responseText); 571 | if (req.status == 201) { 572 | if (options.success) options.success(resp); 573 | } else if (options.error) { 574 | options.error(req.status, resp.error, resp.reason); 575 | } else { 576 | alert("The document could not be copied: " + resp.reason); 577 | } 578 | } 579 | }); 580 | return ajax({ 581 | type: "COPY", 582 | url: this.uri + encodeDocId(docId) 583 | }, 584 | options, 585 | "The document could not be copied", 586 | ajaxOptions 587 | ); 588 | }, 589 | 590 | // Creates (and executes) a temporary view based on the view function 591 | // supplied in the JSON request. 592 | query: function(mapFun, reduceFun, language, options) { 593 | language = language || "javascript"; 594 | if (typeof(mapFun) !== "string") { 595 | mapFun = mapFun.toSource ? mapFun.toSource() 596 | : "(" + mapFun.toString() + ")"; 597 | } 598 | var body = {language: language, map: mapFun}; 599 | if (reduceFun != null) { 600 | if (typeof(reduceFun) !== "string") 601 | reduceFun = reduceFun.toSource ? reduceFun.toSource() 602 | : "(" + reduceFun.toString() + ")"; 603 | body.reduce = reduceFun; 604 | } 605 | return ajax({ 606 | type: "POST", 607 | url: this.uri + "_temp_view" + encodeOptions(options), 608 | contentType: "application/json", data: toJSON(body) 609 | }, 610 | options, 611 | "An error occurred querying the database" 612 | ); 613 | }, 614 | 615 | // Fetch a _list view output, you can specify a list of 616 | // keys in the options object to recieve only those keys. 617 | list: function(list, view, options, ajaxOptions) { 618 | var list = list.split('/'); 619 | var options = options || {}; 620 | var type = 'GET'; 621 | var data = null; 622 | if (options['keys']) { 623 | type = 'POST'; 624 | var keys = options['keys']; 625 | delete options['keys']; 626 | data = toJSON({'keys': keys }); 627 | } 628 | return ajax({ 629 | type: type, 630 | data: data, 631 | url: this.uri + '_design/' + list[0] + 632 | '/_list/' + list[1] + '/' + view + encodeOptions(options) 633 | }, 634 | ajaxOptions, 'An error occured accessing the list' 635 | ); 636 | }, 637 | 638 | // Execute an update function for a given document. 639 | updateDoc: function(updateFun, doc_id, options, ajaxOptions) { 640 | 641 | var ddoc_fun = updateFun.split('/'); 642 | var options = options || {}; 643 | var type = 'PUT'; 644 | var data = null; 645 | 646 | return $.ajax({ 647 | type: type, 648 | data: data, 649 | beforeSend: function(xhr) { 650 | xhr.setRequestHeader('Accept', '*/*'); 651 | }, 652 | complete: function(req) { 653 | var resp = req.responseText; 654 | if (req.status == 201) { 655 | if (options.success) options.success(resp); 656 | } else if (options.error) { 657 | options.error(req.status, resp.error, resp.reason); 658 | } else { 659 | alert("An error occurred getting session info: " + resp.reason); 660 | } 661 | }, 662 | url: this.uri + '_design/' + ddoc_fun[0] + 663 | '/_update/' + ddoc_fun[1] + '/' + doc_id + encodeOptions(options) 664 | }); 665 | }, 666 | 667 | // Executes the specified view-name from the specified design-doc 668 | // design document, you can specify a list of keys 669 | // in the options object to recieve only those keys. 670 | view: function(name, options) { 671 | var name = name.split('/'); 672 | var options = options || {}; 673 | var type = "GET"; 674 | var data= null; 675 | if (options["keys"]) { 676 | type = "POST"; 677 | var keys = options["keys"]; 678 | delete options["keys"]; 679 | data = toJSON({ "keys": keys }); 680 | } 681 | return ajax({ 682 | type: type, 683 | data: data, 684 | url: this.uri + "_design/" + name[0] + 685 | "/_view/" + name[1] + encodeOptions(options) 686 | }, 687 | options, "An error occurred accessing the view" 688 | ); 689 | }, 690 | 691 | // Fetch an arbitrary CouchDB database property 692 | getDbProperty: function(propName, options, ajaxOptions) { 693 | return ajax({url: this.uri + propName + encodeOptions(options)}, 694 | options, 695 | "The property could not be retrieved", 696 | ajaxOptions 697 | ); 698 | }, 699 | 700 | // Set an arbitrary CouchDB database property 701 | setDbProperty: function(propName, propValue, options, ajaxOptions) { 702 | return ajax({ 703 | type: "PUT", 704 | url: this.uri + propName + encodeOptions(options), 705 | data : JSON.stringify(propValue) 706 | }, 707 | options, 708 | "The property could not be updated", 709 | ajaxOptions 710 | ); 711 | } 712 | }; 713 | }, 714 | 715 | encodeDocId: encodeDocId, 716 | 717 | // Accessing the root of a CouchDB instance returns meta information about 718 | // the instance. The response is a JSON structure containing information 719 | // about the server, including a welcome message and the version of the 720 | // server. 721 | info: function(options) { 722 | return ajax( 723 | {url: this.urlPrefix + "/"}, 724 | options, 725 | "Server information could not be retrieved" 726 | ); 727 | }, 728 | 729 | // Request, configure, or stop, a replication operation. 730 | replicate: function(source, target, ajaxOptions, repOpts) { 731 | repOpts = $.extend({source: source, target: target}, repOpts); 732 | if (repOpts.continuous && !repOpts.cancel) { 733 | ajaxOptions.successStatus = 202; 734 | } 735 | return ajax({ 736 | type: "POST", url: this.urlPrefix + "/_replicate", 737 | data: JSON.stringify(repOpts), 738 | contentType: "application/json" 739 | }, 740 | ajaxOptions, 741 | "Replication failed" 742 | ); 743 | }, 744 | 745 | // Fetch a new UUID 746 | newUUID: function(cacheNum) { 747 | if (cacheNum === undefined) { 748 | cacheNum = 1; 749 | } 750 | if (!uuidCache.length) { 751 | ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async: 752 | false}, { 753 | success: function(resp) { 754 | uuidCache = resp.uuids; 755 | } 756 | }, 757 | "Failed to retrieve UUID batch." 758 | ); 759 | } 760 | return uuidCache.shift(); 761 | } 762 | }); 763 | 764 | function ajax(obj, options, errorMessage, ajaxOptions) { 765 | 766 | var defaultAjaxOpts = { 767 | contentType: "application/json", 768 | headers:{"Accept": "application/json"} 769 | }; 770 | 771 | options = $.extend({successStatus: 200}, options); 772 | ajaxOptions = $.extend(defaultAjaxOpts, ajaxOptions); 773 | errorMessage = errorMessage || "Unknown error"; 774 | return $.ajax($.extend($.extend({ 775 | type: "GET", dataType: "json", 776 | beforeSend: function(xhr){ 777 | if(ajaxOptions && ajaxOptions.headers){ 778 | for (var header in ajaxOptions.headers){ 779 | xhr.setRequestHeader(header, ajaxOptions.headers[header]); 780 | } 781 | } 782 | }, 783 | complete: function(req) { 784 | try { 785 | var resp = $.parseJSON(req.responseText); 786 | } catch(e) { 787 | if (options.error) { 788 | options.error(req.status, req, e); 789 | } else { 790 | alert(errorMessage + ": " + e); 791 | } 792 | return; 793 | } 794 | if (options.ajaxStart) { 795 | options.ajaxStart(resp); 796 | } 797 | if (req.status == options.successStatus) { 798 | if (options.beforeSuccess) options.beforeSuccess(req, resp); 799 | if (options.success) options.success(resp); 800 | } else if (options.error) { 801 | options.error(req.status, resp && resp.error || 802 | errorMessage, resp && resp.reason || "no response"); 803 | } else { 804 | alert(errorMessage + ": " + resp.reason); 805 | } 806 | } 807 | }, obj), ajaxOptions)); 808 | } 809 | 810 | function fullCommit(options) { 811 | var options = options || {}; 812 | if (typeof options.ensure_full_commit !== "undefined") { 813 | var commit = options.ensure_full_commit; 814 | delete options.ensure_full_commit; 815 | return function(xhr) { 816 | xhr.setRequestHeader('Accept', 'application/json'); 817 | xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString()); 818 | }; 819 | } 820 | }; 821 | 822 | // Convert a options object to an url query string. 823 | // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"' 824 | function encodeOptions(options) { 825 | var buf = []; 826 | if (typeof(options) === "object" && options !== null) { 827 | for (var name in options) { 828 | if ($.inArray(name, 829 | ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0) 830 | continue; 831 | var value = options[name]; 832 | if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) { 833 | value = toJSON(value); 834 | } 835 | buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); 836 | } 837 | } 838 | return buf.length ? "?" + buf.join("&") : ""; 839 | } 840 | 841 | function toJSON(obj) { 842 | return obj !== null ? JSON.stringify(obj) : null; 843 | } 844 | 845 | })(jQuery); -------------------------------------------------------------------------------- /_attachments/less-1.0.35.min.js: -------------------------------------------------------------------------------- 1 | // 2 | // LESS - Leaner CSS v1.0.35 3 | // http://lesscss.org 4 | // 5 | // Copyright (c) 2010, Alexis Sellier 6 | // Licensed under the Apache 2.0 License. 7 | // 8 | (function(y){function q(e){return y.less[e.split("/")[1]]}function U(){for(var e=document.getElementsByTagName("style"),b=0;b0)g.firstChild.nodeValue!==j.nodeValue&&g.replaceChild(j,g.firstChild);else g.appendChild(j)})(document.createTextNode(e));if(d&&D){I("saving "+a+" to cache.");D.setItem(a,e);D.setItem(a+":timestamp",d)}}function $(e,b,d){function g(h,j,n){if(h.status>=200&&h.status<300)j(h.responseText,h.getResponseHeader("Last-Modified"));else typeof n==="function"&&n(h.status,e)}var a=ba(),i=P?false:o.async;typeof a.overrideMimeType==="function"&&a.overrideMimeType("text/css"); 12 | a.open("GET",e,i);a.send(null);if(P)a.status===0?b(a.responseText):d(a.status);else if(i)a.onreadystatechange=function(){a.readyState==4&&g(a,b,d)};else g(a,b,d)}function ba(){if(y.XMLHttpRequest)return new XMLHttpRequest;else try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(e){I("browser doesn't support AJAX.");return null}}function aa(e){return e&&e.parentNode.removeChild(e)}function I(e){o.env=="development"&&typeof console!=="undefined"&&console.log("less: "+e)}function Q(e,b){var d="less-error-message:"+ 13 | R(b),g=document.createElement("div"),a;g.id=d;g.className="less-error-message";b="

"+(e.message||"There is an error in your .less file")+'

'+b+" ";if(e.extract)b+="on line "+e.line+", column "+(e.column+1)+":

"+''.replace(/\[(-?\d)\]/g,function(i,h){return parseInt(e.line)+parseInt(h)||""}).replace(/\{(\d)\}/g, 14 | function(i,h){return e.extract[parseInt(h)]||""}).replace(/\{current\}/,e.extract[1].slice(0,e.column)+''+e.extract[1].slice(e.column)+"");g.innerHTML=b;N(".less-error-message ul, .less-error-message li {\nlist-style-type: none;\nmargin-right: 15px;\npadding: 4px 0;\nmargin: 0;\n}\n.less-error-message label {\nfont-size: 12px;\nmargin-right: 15px;\npadding: 4px 0;\ncolor: #cc7777;\n}\n.less-error-message pre {\ncolor: #ee4444;\npadding: 4px 0;\nmargin: 0;\ndisplay: inline-block;\n}\n.less-error-message pre.ctx {\ncolor: #dd4444;\n}\n.less-error-message h3 {\nfont-size: 20px;\nfont-weight: bold;\npadding: 15px 0 5px 0;\nmargin: 0;\n}\n.less-error-message a {\ncolor: #10a\n}\n.less-error-message .error {\ncolor: red;\nfont-weight: bold;\npadding-bottom: 2px;\nborder-bottom: 1px dashed red;\n}", 15 | {title:"error-message"});g.style.cssText="font-family: Arial, sans-serif;border: 1px solid #e00;background-color: #eee;border-radius: 5px;-webkit-border-radius: 5px;-moz-border-radius: 5px;color: #e00;padding: 15px;margin-bottom: 15px";if(o.env=="development")a=setInterval(function(){if(document.body){document.getElementById(d)?document.body.replaceChild(g,document.getElementById(d)):document.body.insertBefore(g,document.body.firstChild);clearInterval(a)}},10)}if(!Array.isArray)Array.isArray=function(e){return Object.prototype.toString.call(e)=== 16 | "[object Array]"||e instanceof Array};if(!Array.prototype.forEach)Array.prototype.forEach=function(e,b){for(var d=this.length>>>0,g=0;g>>0,g=new Array(d),a=0;a>>0,d=0;if(b===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var g=arguments[1];else{do{if(d in this){g=this[d++];break}if(++d>=b)throw new TypeError;}while(1)}for(;d=d)return-1;if(b<0)b+=d;for(;bE){t[n]=t[n].slice(j-E);E=j}} 19 | function a(f){var k,l,p;if(f instanceof Function)return f.call(O.parsers);else if(typeof f==="string"){f=h.charAt(j)===f?f:null;k=1;g()}else{g();if(f=f.exec(t[n]))k=f[0].length;else return null}if(f){mem=j+=k;for(p=j+t[n].length-k;j0)throw{type:"Syntax",message:"Missing closing `}`", 22 | filename:e.filename};return L.map(function(F){return F.join("")})}([[]]);l=new m.Ruleset([],a(this.parsers.primary));l.root=true;l.toCSS=function(L){var G,H;return function(A,B){function x(u){return u?(h.slice(0,u).match(/\n/g)||"").length:null}var z=[];A=A||{};if(typeof B==="object"&&!Array.isArray(B)){B=Object.keys(B).map(function(u){var F=B[u];if(!(F instanceof m.Value)){F instanceof m.Expression||(F=new m.Expression([F]));F=new m.Value([F])}return new m.Rule("@"+u,F,false,0)});z=[new m.Ruleset(null, 23 | B)]}try{var C=L.call(this,{frames:z}).toCSS([],{compress:A.compress||false})}catch(s){H=h.split("\n");G=x(s.index);A=s.index;for(z=-1;A>=0&&h.charAt(A)!=="\n";A--)z++;throw{type:s.type,message:s.message,filename:e.filename,index:s.index,line:typeof G==="number"?G+1:null,callLine:s.call&&x(s.call)+1,callExtract:H[x(s.call)],stack:s.stack,column:z,extract:[H[G-1],H[G],H[G+1]]};}return A.compress?C.replace(/(\s)+/g,"$1"):C}}(l.eval);if(j=0&&h.charAt(T)!=="\n";T--)Z++;K={name:"ParseError",message:"Syntax Error on line "+f,filename:e.filename,line:f,column:Z,extract:[p[f-2],p[f-1],p[f]]}}if(this.imports.queue.length>0)Y=function(){k(K,l)};else k(K,l)},parsers:{primary:function(){for(var f,k=[];(f=a(this.mixin.definition)||a(this.rule)||a(this.ruleset)||a(this.mixin.call)||a(this.comment)||a(this.directive))||a(/^[\s\n]+/);)f&&k.push(f);return k},comment:function(){var f;if(h.charAt(j)==="/")if(h.charAt(j+ 25 | 1)==="/")return new m.Comment(a(/^\/\/.*/),true);else if(f=a(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new m.Comment(f)},entities:{quoted:function(){var f;if(!(h.charAt(j)!=='"'&&h.charAt(j)!=="'"))if(f=a(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new m.Quoted(f[0],f[1]||f[2])},keyword:function(){var f;if(f=a(/^[A-Za-z-]+/))return new m.Keyword(f)},call:function(){var f,k;if(f=/^([\w-]+|%)\(/.exec(t[n])){f=f[1].toLowerCase();if(f==="url")return null;else j+=f.length+1;if(f==="alpha")return a(this.alpha); 26 | k=a(this.entities.arguments);if(a(")"))if(f)return new m.Call(f,k)}},arguments:function(){for(var f=[],k;k=a(this.expression);){f.push(k);if(!a(","))break}return f},literal:function(){return a(this.entities.dimension)||a(this.entities.color)||a(this.entities.quoted)},url:function(){var f;if(!(h.charAt(j)!=="u"||!a(/^url\(/))){f=a(this.entities.quoted)||a(this.entities.variable)||a(/^[-\w%@$\/.&=:;#+?]+/)||"";if(!a(")"))throw new Error("missing closing ) for url()");return new m.URL(f.value||f instanceof 27 | m.Variable?f:new m.Anonymous(f),S.paths)}},variable:function(){var f,k=j;if(h.charAt(j)==="@"&&(f=a(/^@[\w-]+/)))return new m.Variable(f,k)},color:function(){var f;if(h.charAt(j)==="#"&&(f=a(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/)))return new m.Color(f[1])},dimension:function(){var f;f=h.charCodeAt(j);if(!(f>57||f<45||f===47))if(f=a(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/))return new m.Dimension(f[1],f[2])},javascript:function(){var f;if(h.charAt(j)==="`")if(f=a(/^`([^`]*)`/))return new m.JavaScript(f[1], 28 | j)}},variable:function(){var f;if(h.charAt(j)==="@"&&(f=a(/^(@[\w-]+)\s*:/)))return f[1]},shorthand:function(){var f,k;if(i(/^[@\w.%-]+\/[@\w.-]+/))if((f=a(this.entity))&&a("/")&&(k=a(this.entity)))return new m.Shorthand(f,k)},mixin:{call:function(){var f=[],k,l,p,K=j;k=h.charAt(j);if(!(k!=="."&&k!=="#")){for(;k=a(/^[#.][\w-]+/);){f.push(new m.Element(l,k));l=a(">")}a("(")&&(p=a(this.entities.arguments))&&a(")");if(f.length>0&&(a(";")||i("}")))return new m.mixin.Call(f,p,K)}},definition:function(){var f, 29 | k=[],l,p;if(!(h.charAt(j)!=="."&&h.charAt(j)!=="#"||i(/^[^{]*(;|})/)))if(f=a(/^([#.][\w-]+)\s*\(/)){for(f=f[1];l=a(this.entities.variable)||a(this.entities.literal)||a(this.entities.keyword);){if(l instanceof m.Variable)if(a(":"))if(p=a(this.expression))k.push({name:l.name,value:p});else throw new Error("Expected value");else k.push({name:l.name});else k.push({value:l});if(!a(","))break}if(!a(")"))throw new Error("Expected )");if(l=a(this.block))return new m.mixin.Definition(f,k,l)}}},entity:function(){return a(this.entities.literal)|| 30 | a(this.entities.variable)||a(this.entities.url)||a(this.entities.call)||a(this.entities.keyword)||a(this.entities.javascript)},end:function(){return a(";")||i("}")},alpha:function(){var f;if(a(/^opacity=/i))if(f=a(/^\d+/)||a(this.entities.variable)){if(!a(")"))throw new Error("missing closing ) for alpha()");return new m.Alpha(f)}},element:function(){var f;c=a(this.combinator);if(f=a(/^[.#:]?[\w-]+/)||a("*")||a(this.attribute)||a(/^\([^)@]+\)/))return new m.Element(c,f)},combinator:function(){var f= 31 | h.charAt(j);if(f===">"||f==="&"||f==="+"||f==="~"){for(j++;h.charAt(j)===" ";)j++;return new m.Combinator(f)}else if(f===":"&&h.charAt(j+1)===":"){for(j+=2;h.charAt(j)===" ";)j++;return new m.Combinator("::")}else return h.charAt(j-1)===" "?new m.Combinator(" "):new m.Combinator(null)},selector:function(){for(var f,k=[],l;f=a(this.element);){l=h.charAt(j);k.push(f);if(l==="{"||l==="}"||l===";"||l===",")break}if(k.length>0)return new m.Selector(k)},tag:function(){return a(/^[a-zA-Z][a-zA-Z-]*[0-9]?/)|| 32 | a("*")},attribute:function(){var f="",k,l,p;if(a("[")){if(k=a(/^[a-zA-Z-]+/)||a(this.entities.quoted))f=(p=a(/^[|~*$^]?=/))&&(l=a(this.entities.quoted)||a(/^[\w-]+/))?[k,p,l.toCSS?l.toCSS():l].join(""):k;if(a("]"))if(f)return"["+f+"]"}},block:function(){var f;if(a("{")&&(f=a(this.primary))&&a("}"))return f},ruleset:function(){var f=[],k,l;b();if(k=/^([.#: \w-]+)[\s\n]*\{/.exec(t[n])){j+=k[0].length-1;f=[new m.Selector([new m.Element(null,k[1])])]}else{for(;k=a(this.selector);){f.push(k);if(!a(","))break}k&& 33 | a(this.comment)}if(f.length>0&&(l=a(this.block)))return new m.Ruleset(f,l);else{v=j;d()}},rule:function(){var f;f=h.charAt(j);var k;b();if(!(f==="."||f==="#"||f==="&"))if(name=a(this.variable)||a(this.property)){if(name.charAt(0)!="@"&&(match=/^([^@+\/'"*`(;{}-]*);/.exec(t[n]))){j+=match[0].length-1;f=new m.Anonymous(match[1])}else f=name==="font"?a(this.font):a(this.value);k=a(this.important);if(f&&a(this.end))return new m.Rule(name,f,k,w);else{v=j;d()}}},"import":function(){var f;if(a(/^@import\s+/)&& 34 | (f=a(this.entities.quoted)||a(this.entities.url))&&a(";"))return new m.Import(f,S)},directive:function(){var f,k,l;if(h.charAt(j)==="@")if(k=a(this["import"]))return k;else if(f=a(/^@media|@page/)){l=a(/^[^{]+/).trim();if(k=a(this.block))return new m.Directive(f+" "+l,k)}else if(f=a(/^@[-a-z]+/))if(f==="@font-face"){if(k=a(this.block))return new m.Directive(f,k)}else if((k=a(this.entity))&&a(";"))return new m.Directive(f,k)},font:function(){for(var f=[],k=[],l;l=a(this.shorthand)||a(this.entity);)k.push(l); 35 | f.push(new m.Expression(k));if(a(","))for(;l=a(this.expression);){f.push(l);if(!a(","))break}return new m.Value(f)},value:function(){for(var f,k=[];f=a(this.expression);){k.push(f);if(!a(","))break}if(k.length>0)return new m.Value(k)},important:function(){if(h.charAt(j)==="!")return a(/^! *important/)},sub:function(){var f;if(a("(")&&(f=a(this.expression))&&a(")"))return f},multiplication:function(){var f,k,l,p;if(f=a(this.operand)){for(;(l=a("/")||a("*"))&&(k=a(this.operand));)p=new m.Operation(l, 36 | [p||f,k]);return p||f}},addition:function(){var f,k,l,p;if(f=a(this.multiplication)){for(;(l=a(/^[-+]\s+/)||h.charAt(j-1)!=" "&&(a("+")||a("-")))&&(k=a(this.multiplication));)p=new m.Operation(l,[p||f,k]);return p||f}},operand:function(){return a(this.sub)||a(this.entities.dimension)||a(this.entities.color)||a(this.entities.variable)||a(this.entities.call)},expression:function(){for(var f,k=[];f=a(this.addition)||a(this.entity);)k.push(f);if(k.length>0)return new m.Expression(k)},property:function(){var f; 37 | if(f=a(/^(\*?-?[-a-z_0-9]+)\s*:/))return f[1]}}}};if(typeof y!=="undefined")o.Parser.importer=function(e,b,d){if(e.charAt(0)!=="/"&&b.length>0)e=b[0]+e;X({href:e,title:e},d,true)};(function(e){function b(a){return e.functions.hsla(a.h,a.s,a.l,a.a)}function d(a){if(a instanceof e.Dimension)return parseFloat(a.unit=="%"?a.value/100:a.value);else if(typeof a==="number")return a;else throw{error:"RuntimeError",message:"color functions take numbers as parameters"};}function g(a){return Math.min(1,Math.max(0, 38 | a))}e.functions={rgb:function(a,i,h){return this.rgba(a,i,h,1)},rgba:function(a,i,h,j){a=[a,i,h].map(function(n){return d(n)});j=d(j);return new e.Color(a,j)},hsl:function(a,i,h){return this.hsla(a,i,h,1)},hsla:function(a,i,h,j){function n(v){v=v<0?v+1:v>1?v-1:v;return v*6<1?w+(r-w)*v*6:v*2<1?r:v*3<2?w+(r-w)*(2/3-v)*6:w}a=d(a)%360/360;i=d(i);h=d(h);j=d(j);var r=h<=0.5?h*(i+1):h+i-h*i,w=h*2-r;return this.rgba(n(a+1/3)*255,n(a)*255,n(a-1/3)*255,j)},hue:function(a){return new e.Dimension(Math.round(a.toHSL().h))}, 39 | saturation:function(a){return new e.Dimension(Math.round(a.toHSL().s*100),"%")},lightness:function(a){return new e.Dimension(Math.round(a.toHSL().l*100),"%")},alpha:function(a){return new e.Dimension(a.toHSL().a)},saturate:function(a,i){a=a.toHSL();a.s+=i.value/100;a.s=g(a.s);return b(a)},desaturate:function(a,i){a=a.toHSL();a.s-=i.value/100;a.s=g(a.s);return b(a)},lighten:function(a,i){a=a.toHSL();a.l+=i.value/100;a.l=g(a.l);return b(a)},darken:function(a,i){a=a.toHSL();a.l-=i.value/100;a.l=g(a.l); 40 | return b(a)},spin:function(a,i){a=a.toHSL();i=(a.h+i.value)%360;a.h=i<0?360+i:i;return b(a)},greyscale:function(a){return this.desaturate(a,new e.Dimension(100))},e:function(a){return new e.Anonymous(a instanceof e.JavaScript?a.evaluated:a)},"%":function(a){for(var i=Array.prototype.slice.call(arguments,1),h=a.value,j=0;j255?255:b<0?0:b).toString(16);return b.length===1?"0"+b:b}).join("")},operate:function(b,d){var g=[];d instanceof e.Color||(d=d.toColor());for(var a=0;a<3;a++)g[a]=e.operate(b,this.rgb[a],d.rgb[a]);return new e.Color(g)},toHSL:function(){var b=this.rgb[0]/255,d=this.rgb[1]/255,g=this.rgb[2]/255,a=this.alpha,i=Math.max(b,d,g),h=Math.min(b,d,g),j,n=(i+h)/2,r=i- 44 | h;if(i===h)j=h=0;else{h=n>0.5?r/(2-i-h):r/(i+h);switch(i){case b:j=(d-g)/r+(d":b.compress?">":" > "}[this.value]}})(q("less/tree"));(function(e){e.Expression=function(b){this.value=b};e.Expression.prototype= 48 | {eval:function(b){return this.value.length>1?new e.Expression(this.value.map(function(d){return d.eval(b)})):this.value[0].eval(b)},toCSS:function(b){return this.value.map(function(d){return d.toCSS(b)}).join(" ")}}})(q("less/tree"));(function(e){e.Import=function(b,d){var g=this;this._path=b;this.path=b instanceof e.Quoted?/\.(le?|c)ss$/.test(b.value)?b.value:b.value+".less":b.value.value||b.value;(this.css=/css$/.test(this.path))||d.push(this.path,function(a){if(!a)throw new Error("Error parsing "+ 49 | g.path);g.root=a})};e.Import.prototype={toCSS:function(){return this.css?"@import "+this._path.toCSS()+";\n":""},eval:function(b){var d;if(this.css)return this;else{d=new e.Ruleset(null,this.root.rules.slice(0));for(var g=0;g0){for(i=0;i1?Array.prototype.push.apply(g,i.find(new e.Selector(b.elements.slice(1)),d)):g.push(i);break}});return this._lookups[a]=g},toCSS:function(b,d){var g=[],a=[],i=[],h=[];if(!this.root)if(b.length===0)h=this.selectors.map(function(r){return[r]});else for(var j=0;j0){h=h.map(function(r){return r.map(function(w){return w.toCSS(d)}).join("").trim()}).join(d.compress?",":h.length>3?",\n":", ");g.push(h,(d.compress?"{":" {\n ")+a.join(d.compress?"":"\n ")+(d.compress? 62 | "}":"\n}\n"))}g.push(i);return g.join("")+(d.compress?"\n":"")}}})(q("less/tree"));(function(e){e.Selector=function(b){this.elements=b;if(this.elements[0].combinator.value==="")this.elements[0].combinator.value=" "};e.Selector.prototype.match=function(b){return this.elements[0].value===b.elements[0].value?true:false};e.Selector.prototype.toCSS=function(b){if(this._css)return this._css;return this._css=this.elements.map(function(d){return typeof d==="string"?" "+d.trim():d.toCSS(b)}).join("")}})(q("less/tree")); 63 | (function(e){e.URL=function(b,d){if(!/^(?:https?:\/|file:\/)?\//.test(b.value)&&d.length>0&&typeof y!=="undefined")b.value=d[0]+(b.value.charAt(0)==="/"?b.value.slice(1):b.value);this.value=b;this.paths=d};e.URL.prototype={toCSS:function(){return"url("+this.value.toCSS()+")"},eval:function(b){return new e.URL(this.value.eval(b),this.paths)}}})(q("less/tree"));(function(e){e.Value=function(b){this.value=b;this.is="value"};e.Value.prototype={eval:function(b){return this.value.length===1?this.value[0].eval(b): 64 | new e.Value(this.value.map(function(d){return d.eval(b)}))},toCSS:function(b){return this.value.map(function(d){return d.toCSS(b)}).join(b.compress?",":", ")}}})(q("less/tree"));(function(e){e.Variable=function(b,d){this.name=b;this.index=d};e.Variable.prototype={eval:function(b){var d,g,a=this.name;if(d=e.find(b.frames,function(i){if(g=i.variable(a))return g.value.eval(b)}))return d;else throw{message:"variable "+this.name+" is undefined",index:this.index};}}})(q("less/tree"));q("less/tree").find= 65 | function(e,b){for(var d=0,g;d0||P?"development":"production";o.async=false;o.poll=o.poll||(P?1E3:1500);o.watch=function(){return this.watchMode=true};o.unwatch=function(){return this.watchMode=false};if(o.env==="development"){o.optimization= 66 | 0;/!watch/.test(location.hash)&&o.watch();o.watchTimer=setInterval(function(){o.watchMode&&W(function(e,b,d){e&&N(e.toCSS(),b,d.lastModified)})},o.poll)}else o.optimization=3;var D;try{D=typeof y.localStorage==="undefined"?null:y.localStorage}catch(ca){D=null}var M=document.getElementsByTagName("link"),V=/^text\/(x-)?less$/;o.sheets=[];for(var J=0;J=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};i(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gh? 6 | 1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||{},d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);i(c,function(d){a[d]=b.bind(a[d],a)});return a};b.memoize=function(a,c){var d={};c=c||b.identity;return function(){var e=c.apply(this,arguments);return e in 10 | d?d[e]:(d[e]=a.apply(this,arguments))}};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=D||function(a){if(b.isArray(a))return b.range(0, 11 | a.length);var c=[];for(var d in a)p.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.filter(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a){i(b.rest(arguments),function(c){for(var d in c)a[d]=c[d]});return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false; 12 | if(a==c)return true;if(!a&&c||a&&!c)return false;if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return false;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!(f in c)||!b.isEqual(a[f], 13 | c[f]))return false;return true};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(p.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=l||function(a){return!!(a&&a.concat&&a.unshift&&!a.callee)};b.isArguments=function(a){return a&&a.callee};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return a=== 14 | +a||C.call(a)==="[object Number]"};b.isBoolean=function(a){return a===true||a===false};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){n._=A;return this};b.identity=function(a){return a};b.times=function(a,c,d){for(var e= 15 | 0;e",interpolate:/<%=(.+?)%>/g};b.template=function(a,c){var d=b.templateSettings,e=new RegExp("'(?=[^"+d.end.substr(0,1)+"]*"+d.end.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")+")","g");d=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj||{}){p.push('"+a.replace(/\r/g, 16 | "\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t").replace(e,"\u2704").split("'").join("\\'").split("\u2704").join("'").replace(d.interpolate,"',$1,'").split(d.start).join("');").split(d.end).join("p.push('")+"');}return p.join('');");return c?d(c):d};b.each=b.forEach;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.select=b.filter;b.all=b.every;b.any=b.some;b.contains=b.include;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var k=function(a){this._wrapped=a},q=function(a,c){return c?b(a).chain(): 17 | a},E=function(a,c){k.prototype[a]=function(){var d=b.toArray(arguments);B.call(d,this._wrapped);return q(c.apply(b,d),this._chain)}};b.mixin(b);i(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=j[a];k.prototype[a]=function(){c.apply(this._wrapped,arguments);return q(this._wrapped,this._chain)}});i(["concat","join","slice"],function(a){var c=j[a];k.prototype[a]=function(){return q(c.apply(this._wrapped,arguments),this._chain)}});k.prototype.chain=function(){this._chain= 18 | true;return this};k.prototype.value=function(){return this._wrapped}})(); 19 | -------------------------------------------------------------------------------- /_id: -------------------------------------------------------------------------------- 1 | _design/costco -------------------------------------------------------------------------------- /couchapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "costco", 3 | "description": "UI for bulk editing couchdb docs" 4 | } -------------------------------------------------------------------------------- /language: -------------------------------------------------------------------------------- 1 | javascript -------------------------------------------------------------------------------- /vendor/couchapp/README.md: -------------------------------------------------------------------------------- 1 | ## CouchApp - more than just a filesystem mapper 2 | 3 | This is where documentation will go for the client and server JavaScript parts of CouchApp. -------------------------------------------------------------------------------- /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) { 24 | this.doc_id = "_design/"+name; 25 | this.view = function(view, opts) { 26 | db.view(name+'/'+view, opts); 27 | }; 28 | this.list = function(list, view, opts) { 29 | db.list(name+'/'+list, view, opts); 30 | }; 31 | } 32 | 33 | $.couch.app = $.couch.app || function(appFun, opts) { 34 | opts = opts || {}; 35 | $(function() { 36 | var urlPrefix = opts.urlPrefix || ""; 37 | $.couch.urlPrefix = urlPrefix; 38 | 39 | var index = urlPrefix.split('/').length; 40 | var fragments = unescape(document.location.href).split('/'); 41 | var dbname = opts.db || fragments[index + 2]; 42 | var dname = opts.design || fragments[index + 4]; 43 | 44 | var db = $.couch.db(dbname); 45 | var design = new Design(db, dname); 46 | 47 | // docForm applies CouchDB behavior to HTML forms. 48 | // todo make this a couch.app plugin 49 | function docForm(formSelector, opts) { 50 | var localFormDoc = {}; 51 | opts = opts || {}; 52 | opts.fields = opts.fields || []; 53 | 54 | // turn the form into deep json 55 | // field names like 'author-email' get turned into json like 56 | // {"author":{"email":"quentin@example.com"}} 57 | function formToDeepJSON(form, fields, doc) { 58 | form = $(form); 59 | fields.forEach(function(field) { 60 | var element = form.find("[name="+field+"]"); 61 | if (element.attr('type') === 'checkbox') { 62 | var val = element.attr('checked'); 63 | } else { 64 | var val = element.val(); 65 | if (!val) return; 66 | } 67 | var parts = field.split('-'); 68 | var frontObj = doc, frontName = parts.shift(); 69 | while (parts.length > 0) { 70 | frontObj[frontName] = frontObj[frontName] || {}; 71 | frontObj = frontObj[frontName]; 72 | frontName = parts.shift(); 73 | } 74 | frontObj[frontName] = val; 75 | }); 76 | } 77 | 78 | // Apply the behavior 79 | $(formSelector).submit(function(e) { 80 | e.preventDefault(); 81 | if (opts.validate && opts.validate() == false) { return false;} 82 | // formToDeepJSON acts on localFormDoc by reference 83 | formToDeepJSON(this, opts.fields, localFormDoc); 84 | if (opts.beforeSave) {opts.beforeSave(localFormDoc);} 85 | db.saveDoc(localFormDoc, { 86 | success : function(resp) { 87 | if (opts.success) {opts.success(resp, localFormDoc);} 88 | } 89 | }); 90 | 91 | return false; 92 | }); 93 | 94 | // populate form from an existing doc 95 | function docToForm(doc) { 96 | var form = $(formSelector); 97 | // fills in forms 98 | opts.fields.forEach(function(field) { 99 | var parts = field.split('-'); 100 | var run = true, frontObj = doc, frontName = parts.shift(); 101 | while (frontObj && parts.length > 0) { 102 | frontObj = frontObj[frontName]; 103 | frontName = parts.shift(); 104 | } 105 | if (frontObj && frontObj[frontName]) { 106 | var element = form.find("[name="+field+"]"); 107 | if (element.attr('type') === 'checkbox') { 108 | element.attr('checked', frontObj[frontName]); 109 | } else { 110 | element.val(frontObj[frontName]); 111 | } 112 | } 113 | }); 114 | } 115 | 116 | if (opts.id) { 117 | db.openDoc(opts.id, { 118 | attachPrevRev : opts.attachPrevRev, 119 | error: function() { 120 | if (opts.error) {opts.error.apply(opts, arguments);} 121 | }, 122 | success: function(doc) { 123 | if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(doc);} 124 | localFormDoc = doc; 125 | docToForm(doc); 126 | }}); 127 | } else if (opts.template) { 128 | if (opts.load || opts.onLoad) {(opts.load || opts.onLoad)(opts.template);} 129 | localFormDoc = opts.template; 130 | docToForm(localFormDoc); 131 | } 132 | var instance = { 133 | deleteDoc : function(opts) { 134 | opts = opts || {}; 135 | if (confirm("Really delete this document?")) { 136 | db.removeDoc(localFormDoc, opts); 137 | } 138 | }, 139 | localDoc : function() { 140 | formToDeepJSON(formSelector, opts.fields, localFormDoc); 141 | return localFormDoc; 142 | } 143 | }; 144 | return instance; 145 | } 146 | 147 | function resolveModule(names, parent, current) { 148 | if (names.length === 0) { 149 | if (typeof current != "string") { 150 | throw ["error","invalid_require_path", 151 | 'Must require a JavaScript string, not: '+(typeof current)]; 152 | } 153 | return [current, parent]; 154 | } 155 | // we need to traverse the path 156 | var n = names.shift(); 157 | if (n == '..') { 158 | if (!(parent && parent.parent)) { 159 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 160 | } 161 | return resolveModule(names, parent.parent.parent, parent.parent); 162 | } else if (n == '.') { 163 | if (!parent) { 164 | throw ["error", "invalid_require_path", 'Object has no parent '+JSON.stringify(current)]; 165 | } 166 | return resolveModule(names, parent.parent, parent); 167 | } 168 | if (!current[n]) { 169 | throw ["error", "invalid_require_path", 'Object has no property "'+n+'". '+JSON.stringify(current)]; 170 | } 171 | var p = current; 172 | current = current[n]; 173 | current.parent = p; 174 | return resolveModule(names, p, current); 175 | } 176 | 177 | var p = document.location.pathname.split('/'); 178 | p.shift(); 179 | var qs = document.location.search.replace(/^\?/,'').split('&'); 180 | var q = {}; 181 | qs.forEach(function(param) { 182 | var ps = param.split('='); 183 | var k = decodeURIComponent(ps[0]); 184 | var v = decodeURIComponent(ps[1]); 185 | if (["startkey", "endkey", "key"].indexOf(k) != -1) { 186 | q[k] = JSON.parse(v); 187 | } else { 188 | q[k] = v; 189 | } 190 | }); 191 | var mockReq = { 192 | path : p, 193 | query : q 194 | }; 195 | 196 | var appExports = $.extend({ 197 | db : db, 198 | design : design, 199 | view : design.view, 200 | list : design.list, 201 | docForm : docForm, 202 | req : mockReq 203 | }, $.couch.app.app); 204 | 205 | function handleDDoc(ddoc) { 206 | var moduleCache = []; 207 | 208 | function getCachedModule(name, parent) { 209 | var key, i, len = moduleCache.length; 210 | for (i=0;i>> 0; 296 | if (typeof fun != "function") 297 | throw new TypeError(); 298 | 299 | var thisp = arguments[1]; 300 | for (var i = 0; i < len; i++) 301 | { 302 | if (i in this) 303 | fun.call(thisp, this[i], i, this); 304 | } 305 | }; 306 | } 307 | 308 | if (!Array.prototype.indexOf) 309 | { 310 | Array.prototype.indexOf = function(elt) 311 | { 312 | var len = this.length >>> 0; 313 | 314 | var from = Number(arguments[1]) || 0; 315 | from = (from < 0) 316 | ? Math.ceil(from) 317 | : Math.floor(from); 318 | if (from < 0) 319 | from += len; 320 | 321 | for (; from < len; from++) 322 | { 323 | if (from in this && 324 | this[from] === elt) 325 | return from; 326 | } 327 | return -1; 328 | }; 329 | } 330 | -------------------------------------------------------------------------------- /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 | $(this).text($.prettyDate($(this).text())); 52 | }); 53 | }; 54 | 55 | $.prettyDate = function(time){ 56 | 57 | var date = new Date(time.replace(/-/g,"/").replace("T", " ").replace("Z", " +0000").replace(/(\d*\:\d*:\d*)\.\d*/g,"$1")), 58 | diff = (((new Date()).getTime() - date.getTime()) / 1000), 59 | day_diff = Math.floor(diff / 86400); 60 | 61 | if (isNaN(day_diff)) return time; 62 | 63 | return day_diff < 1 && ( 64 | diff < 60 && "just now" || 65 | diff < 120 && "1 minute ago" || 66 | diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" || 67 | diff < 7200 && "1 hour ago" || 68 | diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") || 69 | day_diff == 1 && "yesterday" || 70 | day_diff < 21 && day_diff + " days ago" || 71 | day_diff < 45 && Math.ceil( day_diff / 7 ) + " weeks ago" || 72 | day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" || 73 | Math.ceil( day_diff / 365 ) + " years ago"; 74 | }; 75 | 76 | $.argsToArray = function(args) { 77 | if (!args.callee) return args; 78 | var array = []; 79 | for (var i=0; i < args.length; i++) { 80 | array.push(args[i]); 81 | }; 82 | return array; 83 | } 84 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.evently.js: -------------------------------------------------------------------------------- 1 | // $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/ 2 | function $$(node) { 3 | var data = $(node).data("$$"); 4 | if (data) { 5 | return data; 6 | } else { 7 | data = {}; 8 | $(node).data("$$", data); 9 | return data; 10 | } 11 | }; 12 | 13 | (function($) { 14 | // utility functions used in the implementation 15 | 16 | function forIn(obj, fun) { 17 | var name; 18 | for (name in obj) { 19 | if (obj.hasOwnProperty(name)) { 20 | fun(name, obj[name]); 21 | } 22 | } 23 | }; 24 | $.forIn = forIn; 25 | function funViaString(fun) { 26 | if (fun && fun.match && fun.match(/^function/)) { 27 | eval("var f = "+fun); 28 | if (typeof f == "function") { 29 | return function() { 30 | try { 31 | return f.apply(this, arguments); 32 | } catch(e) { 33 | // IF YOU SEE AN ERROR HERE IT HAPPENED WHEN WE TRIED TO RUN YOUR FUNCTION 34 | $.log({"message": "Error in evently function.", "error": e, "src" : fun}); 35 | throw(e); 36 | } 37 | }; 38 | } 39 | } 40 | return fun; 41 | }; 42 | 43 | function runIfFun(me, fun, args) { 44 | // if the field is a function, call it, bound to the widget 45 | var f = funViaString(fun); 46 | if (typeof f == "function") { 47 | return f.apply(me, args); 48 | } else { 49 | return fun; 50 | } 51 | } 52 | 53 | $.evently = { 54 | connect : function(source, target, events) { 55 | events.forEach(function(ev) { 56 | $(source).bind(ev, function() { 57 | var args = $.makeArray(arguments); 58 | // remove the original event to keep from stacking args extra deep 59 | // it would be nice if jquery had a way to pass the original 60 | // event to the trigger method. 61 | args.shift(); 62 | $(target).trigger(ev, args); 63 | return false; 64 | }); 65 | }); 66 | }, 67 | paths : [], 68 | changesDBs : {}, 69 | changesOpts : {} 70 | }; 71 | 72 | function extractFrom(name, evs) { 73 | return evs[name]; 74 | }; 75 | 76 | function extractEvents(name, ddoc) { 77 | // extract events from ddoc.evently and ddoc.vendor.*.evently 78 | var events = [true, {}]; 79 | $.forIn(ddoc.vendor, function(k, v) { 80 | if (v.evently && v.evently[name]) { 81 | events.push(v.evently[name]); 82 | } 83 | }); 84 | if (ddoc.evently[name]) {events.push(ddoc.evently[name]);} 85 | return $.extend.apply(null, events); 86 | } 87 | 88 | $.fn.evently = function(events, app, args) { 89 | var elem = $(this); 90 | // store the app on the element for later use 91 | if (app) { 92 | $$(elem).app = app; 93 | } 94 | 95 | if (typeof events == "string") { 96 | events = extractEvents(events, app.ddoc); 97 | } 98 | 99 | $$(elem).evently = events; 100 | // setup the handlers onto elem 101 | forIn(events, function(name, h) { 102 | eventlyHandler(elem, name, h, args); 103 | }); 104 | 105 | if (events._init) { 106 | // $.log("ev _init", elem); 107 | elem.trigger("_init", args); 108 | } 109 | 110 | if (app && events._changes) { 111 | $("body").bind("evently-changes-"+app.db.name, function() { 112 | // we want to unbind this function when the element is deleted. 113 | // maybe jquery 1.4.2 has this covered? 114 | // $.log('changes', elem); 115 | elem.trigger("_changes"); 116 | }); 117 | followChanges(app); 118 | elem.trigger("_changes"); 119 | } 120 | }; 121 | 122 | // eventlyHandler applies the user's handler (h) to the 123 | // elem, bound to trigger based on name. 124 | function eventlyHandler(elem, name, h, args) { 125 | if (h.path) { 126 | elem.pathbinder(name, h.path); 127 | } 128 | var f = funViaString(h); 129 | if (typeof f == "function") { 130 | elem.bind(name, {args:args}, f); 131 | } else if (typeof f == "string") { 132 | elem.bind(name, {args:args}, function() { 133 | $(this).trigger(f, arguments); 134 | return false; 135 | }); 136 | } else if ($.isArray(h)) { 137 | // handle arrays recursively 138 | for (var i=0; i < h.length; i++) { 139 | eventlyHandler(elem, name, h[i], args); 140 | } 141 | } else { 142 | // an object is using the evently / mustache template system 143 | if (h.fun) { 144 | elem.bind(name, {args:args}, funViaString(h.fun)); 145 | } 146 | // templates, selectors, etc are intepreted 147 | // when our named event is triggered. 148 | elem.bind(name, {args:args}, function() { 149 | renderElement($(this), h, arguments); 150 | return false; 151 | }); 152 | } 153 | }; 154 | 155 | $.fn.replace = function(elem) { 156 | // $.log("Replace", this) 157 | $(this).empty().append(elem); 158 | }; 159 | 160 | // todo: ability to call this 161 | // to render and "prepend/append/etc" a new element to the host element (me) 162 | // as well as call this in a way that replaces the host elements content 163 | // this would be easy if there is a simple way to get at the element we just appended 164 | // (as html) so that we can attache the selectors 165 | function renderElement(me, h, args, qrun, arun) { 166 | // if there's a query object we run the query, 167 | // and then call the data function with the response. 168 | if (h.before && (!qrun || !arun)) { 169 | funViaString(h.before).apply(me, args); 170 | } 171 | if (h.async && !arun) { 172 | runAsync(me, h, args) 173 | } else if (h.query && !qrun) { 174 | // $.log("query before renderElement", arguments) 175 | runQuery(me, h, args) 176 | } else { 177 | // $.log("renderElement") 178 | // $.log(me, h, args, qrun) 179 | // otherwise we just render the template with the current args 180 | var selectors = runIfFun(me, h.selectors, args); 181 | var act = (h.render || "replace").replace(/\s/g,""); 182 | var app = $$(me).app; 183 | if (h.mustache) { 184 | // $.log("rendering", h.mustache) 185 | var newElem = mustachioed(me, h, args); 186 | me[act](newElem); 187 | } 188 | if (selectors) { 189 | if (act == "replace") { 190 | var s = me; 191 | } else { 192 | var s = newElem; 193 | } 194 | forIn(selectors, function(selector, handlers) { 195 | // $.log("selector", selector); 196 | // $.log("selected", $(selector, s)); 197 | $(selector, s).evently(handlers, app, args); 198 | // $.log("applied", selector); 199 | }); 200 | } 201 | if (h.after) { 202 | runIfFun(me, h.after, args); 203 | // funViaString(h.after).apply(me, args); 204 | } 205 | } 206 | }; 207 | 208 | // todo this should return the new element 209 | function mustachioed(me, h, args) { 210 | return $($.mustache( 211 | runIfFun(me, h.mustache, args), 212 | runIfFun(me, h.data, args), 213 | runIfFun(me, h.partials, args))); 214 | }; 215 | 216 | function runAsync(me, h, args) { 217 | // the callback is the first argument 218 | funViaString(h.async).apply(me, [function() { 219 | renderElement(me, h, 220 | $.argsToArray(arguments).concat($.argsToArray(args)), false, true); 221 | }].concat($.argsToArray(args))); 222 | }; 223 | 224 | 225 | function runQuery(me, h, args) { 226 | // $.log("runQuery: args", args) 227 | var app = $$(me).app; 228 | var qu = runIfFun(me, h.query, args); 229 | var qType = qu.type; 230 | var viewName = qu.view; 231 | var userSuccess = qu.success; 232 | // $.log("qType", qType) 233 | 234 | var q = {}; 235 | forIn(qu, function(k, v) { 236 | q[k] = v; 237 | }); 238 | 239 | if (qType == "newRows") { 240 | q.success = function(resp) { 241 | // $.log("runQuery newRows success", resp.rows.length, me, resp) 242 | resp.rows.reverse().forEach(function(row) { 243 | renderElement(me, h, [row].concat($.argsToArray(args)), true) 244 | }); 245 | if (userSuccess) userSuccess(resp); 246 | }; 247 | newRows(me, app, viewName, q); 248 | } else { 249 | q.success = function(resp) { 250 | // $.log("runQuery success", resp) 251 | renderElement(me, h, [resp].concat($.argsToArray(args)), true); 252 | userSuccess && userSuccess(resp); 253 | }; 254 | // $.log(app) 255 | app.view(viewName, q); 256 | } 257 | } 258 | 259 | // this is for the items handler 260 | // var lastViewId, highKey, inFlight; 261 | // this needs to key per elem 262 | function newRows(elem, app, view, opts) { 263 | // $.log("newRows", arguments); 264 | // on success we'll set the top key 265 | var thisViewId, successCallback = opts.success, full = false; 266 | function successFun(resp) { 267 | // $.log("newRows success", resp) 268 | $$(elem).inFlight = false; 269 | var JSONhighKey = JSON.stringify($$(elem).highKey); 270 | resp.rows = resp.rows.filter(function(r) { 271 | return JSON.stringify(r.key) != JSONhighKey; 272 | }); 273 | if (resp.rows.length > 0) { 274 | if (opts.descending) { 275 | $$(elem).highKey = resp.rows[0].key; 276 | } else { 277 | $$(elem).highKey = resp.rows[resp.rows.length -1].key; 278 | } 279 | }; 280 | if (successCallback) {successCallback(resp, full)}; 281 | }; 282 | opts.success = successFun; 283 | 284 | if (opts.descending) { 285 | thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : ""); 286 | } else { 287 | thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : ""); 288 | } 289 | // $.log(["thisViewId",thisViewId]) 290 | // for query we'll set keys 291 | if (thisViewId == $$(elem).lastViewId) { 292 | // we only want the rows newer than changesKey 293 | var hk = $$(elem).highKey; 294 | if (hk !== undefined) { 295 | if (opts.descending) { 296 | opts.endkey = hk; 297 | // opts.inclusive_end = false; 298 | } else { 299 | opts.startkey = hk; 300 | } 301 | } 302 | // $.log("add view rows", opts) 303 | if (!$$(elem).inFlight) { 304 | $$(elem).inFlight = true; 305 | app.view(view, opts); 306 | } 307 | } else { 308 | // full refresh 309 | // $.log("new view stuff") 310 | full = true; 311 | $$(elem).lastViewId = thisViewId; 312 | $$(elem).highKey = undefined; 313 | $$(elem).inFlight = true; 314 | app.view(view, opts); 315 | } 316 | }; 317 | 318 | // only start one changes listener per db 319 | function followChanges(app) { 320 | var dbName = app.db.name, changeEvent = function(resp) { 321 | $("body").trigger("evently-changes-"+dbName, [resp]); 322 | }; 323 | if (!$.evently.changesDBs[dbName]) { 324 | if (app.db.changes) { 325 | // new api in jquery.couch.js 1.0 326 | app.db.changes(null, $.evently.changesOpts).onChange(changeEvent); 327 | } else { 328 | // in case you are still on CouchDB 0.11 ;) deprecated. 329 | connectToChanges(app, changeEvent); 330 | } 331 | $.evently.changesDBs[dbName] = true; 332 | } 333 | } 334 | $.evently.followChanges = followChanges; 335 | // deprecated. use db.changes() from jquery.couch.js 336 | // this does not have an api for closing changes request. 337 | function connectToChanges(app, fun, update_seq) { 338 | function changesReq(seq) { 339 | var url = app.db.uri+"_changes?heartbeat=10000&feed=longpoll&since="+seq; 340 | if ($.evently.changesOpts.include_docs) { 341 | url = url + "&include_docs=true"; 342 | } 343 | $.ajax({ 344 | url: url, 345 | contentType: "application/json", 346 | dataType: "json", 347 | complete: function(req) { 348 | var resp = $.httpData(req, "json"); 349 | fun(resp); 350 | connectToChanges(app, fun, resp.last_seq); 351 | } 352 | }); 353 | }; 354 | if (update_seq) { 355 | changesReq(update_seq); 356 | } else { 357 | app.db.info({success: function(db_info) { 358 | changesReq(db_info.update_seq); 359 | }}); 360 | } 361 | }; 362 | 363 | })(jQuery); 364 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/jquery.pathbinder.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // functions for handling the path 3 | // thanks sammy.js 4 | var PATH_REPLACER = "([^\/]+)", 5 | PATH_NAME_MATCHER = /:([\w\d]+)/g, 6 | QUERY_STRING_MATCHER = /\?([^#]*)$/, 7 | SPLAT_MATCHER = /(\*)/, 8 | SPLAT_REPLACER = "(.+)", 9 | _currentPath, 10 | _lastPath, 11 | _pathInterval; 12 | 13 | function hashChanged() { 14 | _currentPath = getPath(); 15 | // if path is actually changed from what we thought it was, then react 16 | if (_lastPath != _currentPath) { 17 | _lastPath = _currentPath; 18 | return triggerOnPath(_currentPath); 19 | } 20 | } 21 | 22 | $.pathbinder = { 23 | changeFuns : [], 24 | paths : [], 25 | begin : function(defaultPath) { 26 | // this should trigger the defaultPath if there's not a path in the URL 27 | // otherwise it should trigger the URL's path 28 | $(function() { 29 | var loadPath = getPath(); 30 | if (loadPath) { 31 | triggerOnPath(loadPath); 32 | } else { 33 | goPath(defaultPath); 34 | triggerOnPath(defaultPath); 35 | } 36 | }) 37 | }, 38 | go : function(path) { 39 | goPath(path); 40 | triggerOnPath(path); 41 | }, 42 | currentPath : function() { 43 | return getPath(); 44 | }, 45 | onChange : function (fun) { 46 | $.pathbinder.changeFuns.push(fun); 47 | } 48 | }; 49 | 50 | function pollPath(every) { 51 | function hashCheck() { 52 | _currentPath = getPath(); 53 | // path changed if _currentPath != _lastPath 54 | if (_lastPath != _currentPath) { 55 | setTimeout(function() { 56 | $(window).trigger('hashchange'); 57 | }, 1); 58 | } 59 | }; 60 | hashCheck(); 61 | _pathInterval = setInterval(hashCheck, every); 62 | $(window).bind('unload', function() { 63 | clearInterval(_pathInterval); 64 | }); 65 | } 66 | 67 | function triggerOnPath(path) { 68 | path = path.replace(/^#/,''); 69 | $.pathbinder.changeFuns.forEach(function(fun) {fun(path)}); 70 | var pathSpec, path_params, params = {}, param_name, param; 71 | for (var i=0; i < $.pathbinder.paths.length; i++) { 72 | pathSpec = $.pathbinder.paths[i]; 73 | // $.log("pathSpec", pathSpec); 74 | if ((path_params = pathSpec.matcher.exec(path)) !== null) { 75 | // $.log("path_params", path_params); 76 | path_params.shift(); 77 | for (var j=0; j < path_params.length; j++) { 78 | param_name = pathSpec.param_names[j]; 79 | param = decodeURIComponent(path_params[j]); 80 | if (param_name) { 81 | params[param_name] = param; 82 | } else { 83 | if (!params.splat) params.splat = []; 84 | params.splat.push(param); 85 | } 86 | }; 87 | pathSpec.callback(params); 88 | // return true; // removed this to allow for multi match 89 | } 90 | }; 91 | }; 92 | 93 | // bind the event 94 | $(function() { 95 | if ('onhashchange' in window) { 96 | // we have a native event 97 | } else { 98 | pollPath(10); 99 | } 100 | // setTimeout(hashChanged,50); 101 | $(window).bind('hashchange', hashChanged); 102 | }); 103 | 104 | function registerPath(pathSpec) { 105 | $.pathbinder.paths.push(pathSpec); 106 | }; 107 | 108 | function setPath(pathSpec, params) { 109 | var newPath = $.mustache(pathSpec.template, params); 110 | goPath(newPath); 111 | }; 112 | 113 | function goPath(newPath) { 114 | // $.log("goPath", newPath) 115 | window.location = '#'+newPath; 116 | _lastPath = getPath(); 117 | }; 118 | 119 | function getPath() { 120 | var matches = window.location.toString().match(/^[^#]*(#.+)$/); 121 | return matches ? matches[1] : ''; 122 | }; 123 | 124 | function makePathSpec(path, callback) { 125 | var param_names = []; 126 | var template = ""; 127 | 128 | PATH_NAME_MATCHER.lastIndex = 0; 129 | 130 | while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) { 131 | param_names.push(path_match[1]); 132 | } 133 | 134 | return { 135 | param_names : param_names, 136 | matcher : new RegExp("^" + path.replace( 137 | PATH_NAME_MATCHER, PATH_REPLACER).replace( 138 | SPLAT_MATCHER, SPLAT_REPLACER) + "/?$"), 139 | template : path.replace(PATH_NAME_MATCHER, function(a, b) { 140 | return '{{'+b+'}}'; 141 | }).replace(SPLAT_MATCHER, '{{splat}}'), 142 | callback : callback 143 | }; 144 | }; 145 | 146 | $.fn.pathbinder = function(name, paths, options) { 147 | options = options || {}; 148 | var self = $(this), pathList = paths.split(/\n/); 149 | $.each(pathList, function() { 150 | var path = this; 151 | if (path) { 152 | // $.log("bind path", path); 153 | var pathSpec = makePathSpec(path, function(params) { 154 | // $.log("path cb", name, path, self) 155 | // $.log("trigger path: "+path+" params: ", params); 156 | self.trigger(name, [params]); 157 | }); 158 | // set the path when the event triggered through other means 159 | if (options.bindPath) { 160 | self.bind(name, function(ev, params) { 161 | params = params || {}; 162 | // $.log("set path", name, pathSpec) 163 | setPath(pathSpec, params); 164 | }); 165 | } 166 | // trigger when the path matches 167 | registerPath(pathSpec); 168 | } 169 | }); 170 | }; 171 | })(jQuery); 172 | -------------------------------------------------------------------------------- /vendor/couchapp/_attachments/loader.js: -------------------------------------------------------------------------------- 1 | 2 | function couchapp_load(scripts) { 3 | for (var i=0; i < scripts.length; i++) { 4 | document.write('