├── 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 |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;b'+b+" ";if(e.extract)b+="on line "+e.line+", column "+(e.column+1)+":
"+'{0}
{current}
{2}
0)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