From 949f9da6a6548704786d809be76a3a2d47d6fabe Mon Sep 17 00:00:00 2001 From: Adam Kocoloski Date: Thu, 26 Aug 2010 12:21:37 -0400 Subject: move futon to share/www --- rel/overlay/share/www/script/base64.js | 124 + rel/overlay/share/www/script/couch.js | 473 ++ rel/overlay/share/www/script/couch_test_runner.js | 425 ++ rel/overlay/share/www/script/couch_tests.js | 100 + rel/overlay/share/www/script/futon.browse.js | 1279 ++++ rel/overlay/share/www/script/futon.format.js | 143 + rel/overlay/share/www/script/futon.js | 471 ++ rel/overlay/share/www/script/jquery.couch.js | 668 +++ rel/overlay/share/www/script/jquery.dialog.js | 96 + rel/overlay/share/www/script/jquery.editinline.js | 114 + rel/overlay/share/www/script/jquery.form.js | 660 +++ rel/overlay/share/www/script/jquery.js | 6240 ++++++++++++++++++++ rel/overlay/share/www/script/jquery.resizer.js | 84 + rel/overlay/share/www/script/jquery.suggest.js | 163 + rel/overlay/share/www/script/json2.js | 481 ++ rel/overlay/share/www/script/jspec/jspec.css | 149 + rel/overlay/share/www/script/jspec/jspec.jquery.js | 72 + rel/overlay/share/www/script/jspec/jspec.js | 1756 ++++++ rel/overlay/share/www/script/jspec/jspec.xhr.js | 195 + rel/overlay/share/www/script/oauth.js | 511 ++ rel/overlay/share/www/script/sha1.js | 202 + rel/overlay/share/www/script/test/all_docs.js | 95 + .../share/www/script/test/attachment_names.js | 87 + .../share/www/script/test/attachment_paths.js | 147 + .../share/www/script/test/attachment_views.js | 98 + rel/overlay/share/www/script/test/attachments.js | 273 + .../share/www/script/test/attachments_multipart.js | 248 + rel/overlay/share/www/script/test/auth_cache.js | 249 + rel/overlay/share/www/script/test/basics.js | 251 + rel/overlay/share/www/script/test/batch_save.js | 48 + rel/overlay/share/www/script/test/bulk_docs.js | 100 + rel/overlay/share/www/script/test/changes.js | 402 ++ rel/overlay/share/www/script/test/compact.js | 59 + rel/overlay/share/www/script/test/config.js | 67 + rel/overlay/share/www/script/test/conflicts.js | 64 + .../share/www/script/test/content_negotiation.js | 39 + rel/overlay/share/www/script/test/cookie_auth.js | 244 + rel/overlay/share/www/script/test/copy_doc.js | 48 + .../share/www/script/test/delayed_commits.js | 154 + rel/overlay/share/www/script/test/design_docs.js | 165 + .../share/www/script/test/design_options.js | 74 + rel/overlay/share/www/script/test/design_paths.js | 72 + rel/overlay/share/www/script/test/erlang_views.js | 133 + rel/overlay/share/www/script/test/etags_head.js | 78 + rel/overlay/share/www/script/test/etags_views.js | 126 + rel/overlay/share/www/script/test/form_submit.js | 26 + rel/overlay/share/www/script/test/http.js | 54 + .../share/www/script/test/invalid_docids.js | 77 + rel/overlay/share/www/script/test/jsonp.js | 82 + rel/overlay/share/www/script/test/large_docs.js | 33 + rel/overlay/share/www/script/test/list_views.js | 428 ++ rel/overlay/share/www/script/test/lorem.txt | 103 + rel/overlay/share/www/script/test/lorem_b64.txt | 1 + rel/overlay/share/www/script/test/lots_of_docs.js | 55 + .../share/www/script/test/method_override.js | 40 + rel/overlay/share/www/script/test/multiple_rows.js | 80 + rel/overlay/share/www/script/test/oauth.js | 267 + rel/overlay/share/www/script/test/proxyauth.js | 131 + rel/overlay/share/www/script/test/purge.js | 105 + rel/overlay/share/www/script/test/reader_acl.js | 198 + rel/overlay/share/www/script/test/recreate_doc.js | 80 + rel/overlay/share/www/script/test/reduce.js | 169 + .../share/www/script/test/reduce_builtin.js | 153 + rel/overlay/share/www/script/test/reduce_false.js | 44 + .../share/www/script/test/reduce_false_temp.js | 37 + rel/overlay/share/www/script/test/replication.js | 530 ++ rel/overlay/share/www/script/test/rev_stemming.js | 99 + rel/overlay/share/www/script/test/rewrite.js | 371 ++ .../share/www/script/test/security_validation.js | 329 ++ .../share/www/script/test/show_documents.js | 414 ++ rel/overlay/share/www/script/test/stats.js | 330 ++ .../share/www/script/test/update_documents.js | 164 + rel/overlay/share/www/script/test/users_db.js | 103 + rel/overlay/share/www/script/test/utf8.js | 41 + rel/overlay/share/www/script/test/uuids.js | 120 + .../share/www/script/test/view_collation.js | 116 + .../share/www/script/test/view_collation_raw.js | 123 + .../share/www/script/test/view_compaction.js | 104 + .../share/www/script/test/view_conflicts.js | 49 + rel/overlay/share/www/script/test/view_errors.js | 162 + .../share/www/script/test/view_include_docs.js | 138 + .../www/script/test/view_multi_key_all_docs.js | 54 + .../share/www/script/test/view_multi_key_design.js | 138 + .../share/www/script/test/view_multi_key_temp.js | 37 + rel/overlay/share/www/script/test/view_offsets.js | 108 + .../share/www/script/test/view_pagination.js | 122 + .../share/www/script/test/view_sandboxing.js | 52 + .../share/www/script/test/view_update_seq.js | 89 + rel/overlay/share/www/script/test/view_xml.js | 39 + 89 files changed, 23722 insertions(+) create mode 100644 rel/overlay/share/www/script/base64.js create mode 100644 rel/overlay/share/www/script/couch.js create mode 100644 rel/overlay/share/www/script/couch_test_runner.js create mode 100644 rel/overlay/share/www/script/couch_tests.js create mode 100644 rel/overlay/share/www/script/futon.browse.js create mode 100644 rel/overlay/share/www/script/futon.format.js create mode 100644 rel/overlay/share/www/script/futon.js create mode 100644 rel/overlay/share/www/script/jquery.couch.js create mode 100644 rel/overlay/share/www/script/jquery.dialog.js create mode 100644 rel/overlay/share/www/script/jquery.editinline.js create mode 100644 rel/overlay/share/www/script/jquery.form.js create mode 100644 rel/overlay/share/www/script/jquery.js create mode 100644 rel/overlay/share/www/script/jquery.resizer.js create mode 100644 rel/overlay/share/www/script/jquery.suggest.js create mode 100644 rel/overlay/share/www/script/json2.js create mode 100644 rel/overlay/share/www/script/jspec/jspec.css create mode 100644 rel/overlay/share/www/script/jspec/jspec.jquery.js create mode 100644 rel/overlay/share/www/script/jspec/jspec.js create mode 100644 rel/overlay/share/www/script/jspec/jspec.xhr.js create mode 100644 rel/overlay/share/www/script/oauth.js create mode 100644 rel/overlay/share/www/script/sha1.js create mode 100644 rel/overlay/share/www/script/test/all_docs.js create mode 100644 rel/overlay/share/www/script/test/attachment_names.js create mode 100644 rel/overlay/share/www/script/test/attachment_paths.js create mode 100644 rel/overlay/share/www/script/test/attachment_views.js create mode 100644 rel/overlay/share/www/script/test/attachments.js create mode 100644 rel/overlay/share/www/script/test/attachments_multipart.js create mode 100644 rel/overlay/share/www/script/test/auth_cache.js create mode 100644 rel/overlay/share/www/script/test/basics.js create mode 100644 rel/overlay/share/www/script/test/batch_save.js create mode 100644 rel/overlay/share/www/script/test/bulk_docs.js create mode 100644 rel/overlay/share/www/script/test/changes.js create mode 100644 rel/overlay/share/www/script/test/compact.js create mode 100644 rel/overlay/share/www/script/test/config.js create mode 100644 rel/overlay/share/www/script/test/conflicts.js create mode 100644 rel/overlay/share/www/script/test/content_negotiation.js create mode 100644 rel/overlay/share/www/script/test/cookie_auth.js create mode 100644 rel/overlay/share/www/script/test/copy_doc.js create mode 100644 rel/overlay/share/www/script/test/delayed_commits.js create mode 100644 rel/overlay/share/www/script/test/design_docs.js create mode 100644 rel/overlay/share/www/script/test/design_options.js create mode 100644 rel/overlay/share/www/script/test/design_paths.js create mode 100644 rel/overlay/share/www/script/test/erlang_views.js create mode 100644 rel/overlay/share/www/script/test/etags_head.js create mode 100644 rel/overlay/share/www/script/test/etags_views.js create mode 100644 rel/overlay/share/www/script/test/form_submit.js create mode 100644 rel/overlay/share/www/script/test/http.js create mode 100644 rel/overlay/share/www/script/test/invalid_docids.js create mode 100644 rel/overlay/share/www/script/test/jsonp.js create mode 100644 rel/overlay/share/www/script/test/large_docs.js create mode 100644 rel/overlay/share/www/script/test/list_views.js create mode 100644 rel/overlay/share/www/script/test/lorem.txt create mode 100644 rel/overlay/share/www/script/test/lorem_b64.txt create mode 100644 rel/overlay/share/www/script/test/lots_of_docs.js create mode 100644 rel/overlay/share/www/script/test/method_override.js create mode 100644 rel/overlay/share/www/script/test/multiple_rows.js create mode 100644 rel/overlay/share/www/script/test/oauth.js create mode 100644 rel/overlay/share/www/script/test/proxyauth.js create mode 100644 rel/overlay/share/www/script/test/purge.js create mode 100644 rel/overlay/share/www/script/test/reader_acl.js create mode 100644 rel/overlay/share/www/script/test/recreate_doc.js create mode 100644 rel/overlay/share/www/script/test/reduce.js create mode 100644 rel/overlay/share/www/script/test/reduce_builtin.js create mode 100644 rel/overlay/share/www/script/test/reduce_false.js create mode 100644 rel/overlay/share/www/script/test/reduce_false_temp.js create mode 100644 rel/overlay/share/www/script/test/replication.js create mode 100644 rel/overlay/share/www/script/test/rev_stemming.js create mode 100644 rel/overlay/share/www/script/test/rewrite.js create mode 100644 rel/overlay/share/www/script/test/security_validation.js create mode 100644 rel/overlay/share/www/script/test/show_documents.js create mode 100644 rel/overlay/share/www/script/test/stats.js create mode 100644 rel/overlay/share/www/script/test/update_documents.js create mode 100644 rel/overlay/share/www/script/test/users_db.js create mode 100644 rel/overlay/share/www/script/test/utf8.js create mode 100644 rel/overlay/share/www/script/test/uuids.js create mode 100644 rel/overlay/share/www/script/test/view_collation.js create mode 100644 rel/overlay/share/www/script/test/view_collation_raw.js create mode 100644 rel/overlay/share/www/script/test/view_compaction.js create mode 100644 rel/overlay/share/www/script/test/view_conflicts.js create mode 100644 rel/overlay/share/www/script/test/view_errors.js create mode 100644 rel/overlay/share/www/script/test/view_include_docs.js create mode 100644 rel/overlay/share/www/script/test/view_multi_key_all_docs.js create mode 100644 rel/overlay/share/www/script/test/view_multi_key_design.js create mode 100644 rel/overlay/share/www/script/test/view_multi_key_temp.js create mode 100644 rel/overlay/share/www/script/test/view_offsets.js create mode 100644 rel/overlay/share/www/script/test/view_pagination.js create mode 100644 rel/overlay/share/www/script/test/view_sandboxing.js create mode 100644 rel/overlay/share/www/script/test/view_update_seq.js create mode 100644 rel/overlay/share/www/script/test/view_xml.js (limited to 'rel/overlay/share/www/script') diff --git a/rel/overlay/share/www/script/base64.js b/rel/overlay/share/www/script/base64.js new file mode 100644 index 00000000..e0aab303 --- /dev/null +++ b/rel/overlay/share/www/script/base64.js @@ -0,0 +1,124 @@ +/* Copyright (C) 1999 Masanao Izumo + * Version: 1.0 + * LastModified: Dec 25 1999 + * This library is free. You can redistribute it and/or modify it. + */ + /* Modified by Chris Anderson to not use CommonJS */ + /* Modified by Dan Webb not to require Narwhal's binary library */ + +var Base64 = {}; +(function(exports) { + + var encodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var decodeChars = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 + ]; + + exports.encode = function (str) { + var out, i, length; + var c1, c2, c3; + + length = len(str); + i = 0; + out = []; + while(i < length) { + c1 = str.charCodeAt(i++) & 0xff; + if(i == length) + { + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt((c1 & 0x3) << 4)); + out.push("=".charCodeAt(0)); + out.push("=".charCodeAt(0)); + break; + } + c2 = str.charCodeAt(i++); + if(i == length) + { + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4))); + out.push(encodeChars.charCodeAt((c2 & 0xF) << 2)); + out.push("=".charCodeAt(0)); + break; + } + c3 = str.charCodeAt(i++); + out.push(encodeChars.charCodeAt(c1 >> 2)); + out.push(encodeChars.charCodeAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4))); + out.push(encodeChars.charCodeAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6))); + out.push(encodeChars.charCodeAt(c3 & 0x3F)); + } + + var str = ""; + out.forEach(function(chr) { str += String.fromCharCode(chr) }); + return str; + }; + + exports.decode = function (str) { + var c1, c2, c3, c4; + var i, length, out; + + length = len(str); + i = 0; + out = []; + while(i < length) { + /* c1 */ + do { + c1 = decodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < length && c1 == -1); + if(c1 == -1) + break; + + /* c2 */ + do { + c2 = decodeChars[str.charCodeAt(i++) & 0xff]; + } while(i < length && c2 == -1); + if(c2 == -1) + break; + + out.push(String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4))); + + /* c3 */ + do { + c3 = str.charCodeAt(i++) & 0xff; + if(c3 == 61) + return out.join(''); + c3 = decodeChars[c3]; + } while(i < length && c3 == -1); + if(c3 == -1) + break; + + out.push(String.fromCharCode(((c2 & 0xF) << 4) | ((c3 & 0x3C) >> 2))); + + /* c4 */ + do { + c4 = str.charCodeAt(i++) & 0xff; + if(c4 == 61) + return out.join(''); + c4 = decodeChars[c4]; + } while(i < length && c4 == -1); + + if(c4 == -1) + break; + + out.push(String.fromCharCode(((c3 & 0x03) << 6) | c4)); + } + + return out.join(''); + }; + + var len = function (object) { + if (object.length !== undefined) { + return object.length; + } else if (object.getLength !== undefined) { + return object.getLength(); + } else { + return undefined; + } + }; +})(Base64); diff --git a/rel/overlay/share/www/script/couch.js b/rel/overlay/share/www/script/couch.js new file mode 100644 index 00000000..33fd82ba --- /dev/null +++ b/rel/overlay/share/www/script/couch.js @@ -0,0 +1,473 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// A simple class to represent a database. Uses XMLHttpRequest to interface with +// the CouchDB server. + +function CouchDB(name, httpHeaders) { + this.name = name; + this.uri = "/" + encodeURIComponent(name) + "/"; + + // The XMLHttpRequest object from the most recent request. Callers can + // use this to check result http status and headers. + this.last_req = null; + + this.request = function(method, uri, requestOptions) { + requestOptions = requestOptions || {} + requestOptions.headers = combine(requestOptions.headers, httpHeaders) + return CouchDB.request(method, uri, requestOptions); + } + + // Creates the database on the server + this.createDb = function() { + this.last_req = this.request("PUT", this.uri); + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + } + + // Deletes the database on the server + this.deleteDb = function() { + this.last_req = this.request("DELETE", this.uri); + if (this.last_req.status == 404) { + return false; + } + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + } + + // Save a document to the database + this.save = function(doc, options) { + if (doc._id == undefined) { + doc._id = CouchDB.newUuids(1)[0]; + } + + this.last_req = this.request("PUT", this.uri + + encodeURIComponent(doc._id) + encodeOptions(options), + {body: JSON.stringify(doc)}); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; + return result; + } + + // Open a document from the database + this.open = function(docId, options) { + this.last_req = this.request("GET", this.uri + encodeURIComponent(docId) + + encodeOptions(options)); + if (this.last_req.status == 404) { + return null; + } + CouchDB.maybeThrowError(this.last_req); + return JSON.parse(this.last_req.responseText); + } + + // Deletes a document from the database + this.deleteDoc = function(doc) { + this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + + "?rev=" + doc._rev); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; //record rev in input document + doc._deleted = true; + return result; + } + + // Deletes an attachment from a document + this.deleteDocAttachment = function(doc, attachment_name) { + this.last_req = this.request("DELETE", this.uri + encodeURIComponent(doc._id) + + "/" + attachment_name + "?rev=" + doc._rev); + CouchDB.maybeThrowError(this.last_req); + var result = JSON.parse(this.last_req.responseText); + doc._rev = result.rev; //record rev in input document + return result; + } + + this.bulkSave = function(docs, options) { + // first prepoulate the UUIDs for new documents + var newCount = 0 + for (var i=0; i= n) { + var uuids = CouchDB.uuids_cache.slice(CouchDB.uuids_cache.length - n); + if(CouchDB.uuids_cache.length - n == 0) { + CouchDB.uuids_cache = []; + } else { + CouchDB.uuids_cache = + CouchDB.uuids_cache.slice(0, CouchDB.uuids_cache.length - n); + } + return uuids; + } else { + CouchDB.last_req = CouchDB.request("GET", "/_uuids?count=" + (buf + n)); + CouchDB.maybeThrowError(CouchDB.last_req); + var result = JSON.parse(CouchDB.last_req.responseText); + CouchDB.uuids_cache = + CouchDB.uuids_cache.concat(result.uuids.slice(0, buf)); + return result.uuids.slice(buf); + } +} + +CouchDB.maybeThrowError = function(req) { + if (req.status >= 400) { + try { + var result = JSON.parse(req.responseText); + } catch (ParseError) { + var result = {error:"unknown", reason:req.responseText}; + } + throw result; + } +} + +CouchDB.params = function(options) { + options = options || {}; + var returnArray = []; + for(var key in options) { + var value = options[key]; + returnArray.push(key + "=" + value); + } + return returnArray.join("&"); +}; diff --git a/rel/overlay/share/www/script/couch_test_runner.js b/rel/overlay/share/www/script/couch_test_runner.js new file mode 100644 index 00000000..451a454a --- /dev/null +++ b/rel/overlay/share/www/script/couch_test_runner.js @@ -0,0 +1,425 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// *********************** Test Framework of Sorts ************************* // + + +function loadScript(url) { + if (typeof document != "undefined") document.write(''); +}; + +function patchTest(fun) { + var source = fun.toString(); + var output = ""; + var i = 0; + var testMarker = "T(" + while (i < source.length) { + var testStart = source.indexOf(testMarker, i); + if (testStart == -1) { + output = output + source.substring(i, source.length); + break; + } + var testEnd = source.indexOf(");", testStart); + var testCode = source.substring(testStart + testMarker.length, testEnd); + output += source.substring(i, testStart) + "T(" + testCode + "," + JSON.stringify(testCode); + i = testEnd; + } + try { + return eval("(" + output + ")"); + } catch (e) { + return null; + } +} + +function runAllTests() { + var rows = $("#tests tbody.content tr"); + $("td", rows).text(""); + $("td.status", rows).removeClass("error").removeClass("failure").removeClass("success").text("not run"); + var offset = 0; + function runNext() { + if (offset < rows.length) { + var row = rows.get(offset); + runTest($("th button", row).get(0), function() { + offset += 1; + setTimeout(runNext, 100); + }, false, true); + } else { + saveTestReport(); + } + } + runNext(); +} + +var numFailures = 0; +var currentRow = null; + +function runTest(button, callback, debug, noSave) { + + // offer to save admins + if (currentRow != null) { + alert("Can not run multiple tests simultaneously."); + return; + } + var row = currentRow = $(button).parents("tr").get(0); + $("td.status", row).removeClass("error").removeClass("failure").removeClass("success"); + $("td", row).text(""); + $("#toolbar li.current").text("Running: "+row.id); + var testFun = couchTests[row.id]; + function run() { + numFailures = 0; + var start = new Date().getTime(); + try { + if (debug == undefined || !debug) { + testFun = patchTest(testFun) || testFun; + } + testFun(debug); + var status = numFailures > 0 ? "failure" : "success"; + } catch (e) { + var status = "error"; + if ($("td.details ol", row).length == 0) { + $("
    ").appendTo($("td.details", row)); + } + $("
  1. Exception raised:
  2. ") + .find("code").text(JSON.stringify(e)).end() + .appendTo($("td.details ol", row)); + if (debug) { + currentRow = null; + throw e; + } + } + if ($("td.details ol", row).length) { + $("Run with debugger").click(function() { + runTest(this, undefined, true); + }).prependTo($("td.details ol", row)); + } + var duration = new Date().getTime() - start; + $("td.status", row).removeClass("running").addClass(status).text(status); + $("td.duration", row).text(duration + "ms"); + $("#toolbar li.current").text("Finished: "+row.id); + updateTestsFooter(); + currentRow = null; + if (callback) callback(); + if (!noSave) saveTestReport(); + } + $("td.status", row).addClass("running").text("running…"); + setTimeout(run, 100); +} + +function showSource(cell) { + var name = $(cell).text(); + var win = window.open("", name, "width=700,height=500,resizable=yes,scrollbars=yes"); + win.document.location = "script/test/" + name + ".js"; +} + +var readyToRun; +function setupAdminParty(fun) { + if (readyToRun) { + fun(); + } else { + function removeAdmins(confs, doneFun) { + // iterate through the config and remove current user last + // current user is at front of list + var remove = confs.pop(); + if (remove) { + $.couch.config({ + success : function() { + removeAdmins(confs, doneFun); + } + }, "admins", remove[0], null); + } else { + doneFun(); + } + }; + $.couch.session({ + success : function(resp) { + var userCtx = resp.userCtx; + if (userCtx.name && userCtx.roles.indexOf("_admin") != -1) { + // admin but not admin party. dialog offering to make admin party + $.showDialog("dialog/_admin_party.html", { + submit: function(data, callback) { + $.couch.config({ + success : function(conf) { + var meAdmin, adminConfs = []; + for (var name in conf) { + if (name == userCtx.name) { + meAdmin = [name, conf[name]]; + } else { + adminConfs.push([name, conf[name]]); + } + } + adminConfs.unshift(meAdmin); + removeAdmins(adminConfs, function() { + callback(); + $.futon.session.sidebar(); + readyToRun = true; + setTimeout(fun, 500); + }); + } + }, "admins"); + } + }); + } else if (userCtx.roles.indexOf("_admin") != -1) { + // admin party! + readyToRun = true; + fun(); + } else { + // not an admin + alert("Error: You need to be an admin to run the tests."); + }; + } + }); + } +}; + +function updateTestsListing() { + for (var name in couchTests) { + var testFunction = couchTests[name]; + var row = $("") + .find("th").text(name).attr("title", "Show source").click(function() { + showSource(this); + }).end() + .find("td:nth(0)").addClass("status").text("not run").end() + .find("td:nth(1)").addClass("duration").end() + .find("td:nth(2)").addClass("details").end(); + $("").click(function() { + this.blur(); + var self = this; + // check for admin party + setupAdminParty(function() { + runTest(self); + }); + return false; + }).prependTo(row.find("th")); + row.attr("id", name).appendTo("#tests tbody.content"); + } + $("#tests tr").removeClass("odd").filter(":odd").addClass("odd"); + updateTestsFooter(); +} + +function updateTestsFooter() { + var tests = $("#tests tbody.content tr td.status"); + var testsRun = tests.filter(".success, .error, .failure"); + var testsFailed = testsRun.not(".success"); + var totalDuration = 0; + $("#tests tbody.content tr td.duration:contains('ms')").each(function() { + var text = $(this).text(); + totalDuration += parseInt(text.substr(0, text.length - 2), 10); + }); + $("#tests tbody.footer td").html(""+testsRun.length + " of " + tests.length + + " test(s) run, " + testsFailed.length + " failures (" + + totalDuration + " ms) "); +} + +// make report and save to local db +// display how many reports need replicating to the mothership +// have button to replicate them + +function saveTestReport(report) { + var report = makeTestReport(); + if (report) { + var db = $.couch.db("test_suite_reports"); + var saveReport = function(db_info) { + report.db = db_info; + $.couch.info({success : function(node_info) { + report.node = node_info; + db.saveDoc(report); + }}) + }; + var createDb = function() { + db.create({success: function() { + db.info({success:saveReport}); + }}); + } + db.info({error: createDb, success:saveReport}); + } +}; + +function makeTestReport() { + var report = {}; + report.summary = $("#tests tbody.footer td").text(); + report.platform = testPlatform(); + var date = new Date(); + report.timestamp = date.getTime(); + report.timezone = date.getTimezoneOffset(); + report.tests = []; + $("#tests tbody.content tr").each(function() { + var status = $("td.status", this).text(); + if (status != "not run") { + var test = {}; + test.name = this.id; + test.status = status; + test.duration = parseInt($("td.duration", this).text()); + test.details = []; + $("td.details li", this).each(function() { + test.details.push($(this).text()); + }); + if (test.details.length == 0) { + delete test.details; + } + report.tests.push(test); + } + }); + if (report.tests.length > 0) return report; +}; + +function testPlatform() { + var b = $.browser; + var bs = ["mozilla", "msie", "opera", "safari"]; + for (var i=0; i < bs.length; i++) { + if (b[bs[i]]) { + return {"browser" : bs[i], "version" : b.version}; + } + }; + return {"browser" : "undetected"}; +} + + +function reportTests() { + // replicate the database to couchdb.couchdb.org +} + +// Use T to perform a test that returns false on failure and if the test fails, +// display the line that failed. +// Example: +// T(MyValue==1); +function T(arg1, arg2, testName) { + if (!arg1) { + if (currentRow) { + if ($("td.details ol", currentRow).length == 0) { + $("
      ").appendTo($("td.details", currentRow)); + } + var message = (arg2 != null ? arg2 : arg1).toString(); + $("
    1. Assertion " + (testName ? "'" + testName + "'" : "") + " failed:
    2. ") + .find("code").text(message).end() + .appendTo($("td.details ol", currentRow)); + } + numFailures += 1 + } +} + +function TEquals(expected, actual, testName) { + T(equals(expected, actual), "expected '" + repr(expected) + + "', got '" + repr(actual) + "'", testName); +} + +function equals(a,b) { + if (a === b) return true; + try { + return repr(a) === repr(b); + } catch (e) { + return false; + } +} + +function repr(val) { + if (val === undefined) { + return null; + } else if (val === null) { + return "null"; + } else { + return JSON.stringify(val); + } +} + +function makeDocs(start, end, templateDoc) { + var templateDocSrc = templateDoc ? JSON.stringify(templateDoc) : "{}" + if (end === undefined) { + end = start; + start = 0; + } + var docs = [] + for (var i = start; i < end; i++) { + var newDoc = eval("(" + templateDocSrc + ")"); + newDoc._id = (i).toString(); + newDoc.integer = i; + newDoc.string = (i).toString(); + docs.push(newDoc) + } + return docs; +} + +function run_on_modified_server(settings, fun) { + try { + // set the settings + for(var i=0; i < settings.length; i++) { + var s = settings[i]; + var xhr = CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { + body: JSON.stringify(s.value), + headers: {"X-Couch-Persist": "false"} + }); + CouchDB.maybeThrowError(xhr); + s.oldValue = xhr.responseText; + } + // run the thing + fun(); + } finally { + // unset the settings + for(var j=0; j < i; j++) { + var s = settings[j]; + if(s.oldValue == "\"\"\n") { // unset value + CouchDB.request("DELETE", "/_config/" + s.section + "/" + s.key, { + headers: {"X-Couch-Persist": "false"} + }); + } else { + CouchDB.request("PUT", "/_config/" + s.section + "/" + s.key, { + body: s.oldValue, + headers: {"X-Couch-Persist": "false"} + }); + } + } + } +} + +function stringFun(fun) { + var string = fun.toSource ? fun.toSource() : "(" + fun.toString() + ")"; + return string; +} + +function waitForSuccess(fun, tag) { + var start = new Date(); + while(true) { + if (new Date() - start > 5000) { + throw("timeout: "+tag); + } else { + try { + fun(); + break; + } catch (e) {} + // sync http req allow async req to happen + CouchDB.request("GET", "/test_suite_db/?tag="+encodeURIComponent(tag)); + } + } +} + +function waitForRestart() { + var waiting = true; + while (waiting) { + try { + CouchDB.request("GET", "/"); + CouchDB.request("GET", "/"); + waiting = false; + } catch(e) { + // the request will fail until restart completes + } + } +}; + +function restartServer() { + var xhr; + try { + CouchDB.request("POST", "/_restart"); + } catch(e) { + // this request may sometimes fail + } + waitForRestart(); +} + diff --git a/rel/overlay/share/www/script/couch_tests.js b/rel/overlay/share/www/script/couch_tests.js new file mode 100644 index 00000000..c5257ea6 --- /dev/null +++ b/rel/overlay/share/www/script/couch_tests.js @@ -0,0 +1,100 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Used by replication test +if (typeof window == 'undefined' || !window) { + CouchDB.host = "127.0.0.1:5984"; + CouchDB.inBrowser = false; +} else { + CouchDB.host = window.location.host; + CouchDB.inBrowser = true; +} + +CouchDB.urlPrefix = ".."; +var couchTests = {}; + +function loadTest(file) { + loadScript("script/test/"+file); +}; +// keep first +loadTest("basics.js"); + +// keep sorted +loadTest("all_docs.js"); +loadTest("attachments.js"); +loadTest("attachments_multipart.js"); +loadTest("attachment_names.js"); +loadTest("attachment_paths.js"); +loadTest("attachment_views.js"); +loadTest("auth_cache.js"); +loadTest("batch_save.js"); +loadTest("bulk_docs.js"); +loadTest("changes.js"); +loadTest("compact.js"); +loadTest("config.js"); +loadTest("conflicts.js"); +loadTest("content_negotiation.js"); +loadTest("cookie_auth.js"); +loadTest("copy_doc.js"); +loadTest("delayed_commits.js"); +loadTest("design_docs.js"); +loadTest("design_options.js"); +loadTest("design_paths.js"); +loadTest("erlang_views.js"); +loadTest("etags_head.js"); +loadTest("etags_views.js"); +loadTest("form_submit.js"); +loadTest("http.js"); +loadTest("invalid_docids.js"); +loadTest("jsonp.js"); +loadTest("large_docs.js"); +loadTest("list_views.js"); +loadTest("lots_of_docs.js"); +loadTest("method_override.js"); +loadTest("multiple_rows.js"); +loadScript("script/oauth.js"); +loadScript("script/sha1.js"); +loadTest("oauth.js"); +loadTest("proxyauth.js"); +loadTest("purge.js"); +loadTest("reader_acl.js"); +loadTest("recreate_doc.js"); +loadTest("reduce.js"); +loadTest("reduce_builtin.js"); +loadTest("reduce_false.js"); +loadTest("reduce_false_temp.js"); +loadTest("replication.js"); +loadTest("rev_stemming.js"); +loadTest("rewrite.js"); +loadTest("security_validation.js"); +loadTest("show_documents.js"); +loadTest("stats.js"); +loadTest("update_documents.js"); +loadTest("users_db.js"); +loadTest("utf8.js"); +loadTest("uuids.js"); +loadTest("view_collation.js"); +loadTest("view_collation_raw.js"); +loadTest("view_conflicts.js"); +loadTest("view_compaction.js"); +loadTest("view_errors.js"); +loadTest("view_include_docs.js"); +loadTest("view_multi_key_all_docs.js"); +loadTest("view_multi_key_design.js"); +loadTest("view_multi_key_temp.js"); +loadTest("view_offsets.js"); +loadTest("view_pagination.js"); +loadTest("view_sandboxing.js"); +loadTest("view_update_seq.js"); +loadTest("view_xml.js"); +// keep sorted + diff --git a/rel/overlay/share/www/script/futon.browse.js b/rel/overlay/share/www/script/futon.browse.js new file mode 100644 index 00000000..5f687941 --- /dev/null +++ b/rel/overlay/share/www/script/futon.browse.js @@ -0,0 +1,1279 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + $.futon = $.futon || {}; + $.extend($.futon, { + + // Page class for browse/index.html + CouchIndexPage: function() { + page = this; + + $.futon.storage.declare("per_page", {defaultValue: 10}); + + this.addDatabase = function() { + $.showDialog("dialog/_create_database.html", { + submit: function(data, callback) { + if (!data.name || data.name.length == 0) { + callback({name: "Please enter a name."}); + return; + } + $.couch.db(data.name).create({ + error: function(status, id, reason) { callback({name: reason}) }, + success: function(resp) { + location.href = "database.html?" + encodeURIComponent(data.name); + callback(); + } + }); + } + }); + return false; + } + + this.updateDatabaseListing = function(offset) { + offset |= 0; + var maxPerPage = parseInt($("#perpage").val(), 10); + + $.couch.allDbs({ + success: function(dbs) { + $("#paging a").unbind(); + $("#databases tbody.content").empty(); + + var dbsOnPage = dbs.slice(offset, offset + maxPerPage); + + $.each(dbsOnPage, function(idx, dbName) { + $("#databases tbody.content").append("" + + "" + + dbName + "" + + "" + + ""); + $.couch.db(dbName).info({ + success: function(info) { + $("#databases tbody.content tr:eq(" + idx + ")") + .find("td.size").text($.futon.formatSize(info.disk_size)).end() + .find("td.count").text(info.doc_count).end() + .find("td.seq").text(info.update_seq); + }, + error : function() {} + }); + }); + $("#databases tbody tr:odd").addClass("odd"); + + if (offset > 0) { + $("#paging a.prev").attr("href", "#" + (offset - maxPerPage)).click(function() { + page.updateDatabaseListing(offset - maxPerPage); + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + if (offset + maxPerPage < dbs.length) { + $("#paging a.next").attr("href", "#" + (offset + maxPerPage)).click(function() { + page.updateDatabaseListing(offset + maxPerPage); + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + var firstNum = offset + 1; + var lastNum = firstNum + dbsOnPage.length - 1; + $("#databases tbody.footer tr td span").text( + "Showing " + firstNum + "-" + lastNum + " of " + dbs.length + + " databases"); + } + }); + } + + }, + + // Page class for browse/database.html + CouchDatabasePage: function() { + var urlParts = location.search.substr(1).split("/"); + var dbName = decodeURIComponent(urlParts.shift()); + + $.futon.storage.declareWithPrefix(dbName + ".", { + desc: {}, + language: {defaultValue: "javascript"}, + map_fun: {defaultValue: ""}, + reduce_fun: {defaultValue: ""}, + reduce: {}, + group_level: {defaultValue: 100}, + per_page: {defaultValue: 10}, + view: {defaultValue: ""}, + stale: {defaultValue: false} + }); + + var viewName = (urlParts.length > 0) ? urlParts.join("/") : null; + if (viewName) { + $.futon.storage.set("view", viewName); + } else { + viewName = $.futon.storage.get("view"); + if (viewName) { + this.redirecting = true; + location.href = "database.html?" + encodeURIComponent(dbName) + + "/" + viewName; + } + } + var db = $.couch.db(dbName); + + this.dbName = dbName; + this.viewName = viewName; + this.viewLanguage = "javascript"; + this.db = db; + this.isDirty = false; + this.isTempView = viewName == "_temp_view"; + page = this; + + var templates = { + javascript: "function(doc) {\n emit(null, doc);\n}", + python: "def fun(doc):\n yield None, doc", + ruby: "lambda {|doc|\n emit(nil, doc);\n}" + } + + this.newDocument = function() { + location.href = "document.html?" + encodeURIComponent(db.name); + } + + this.compactAndCleanup = function() { + $.showDialog("dialog/_compact_cleanup.html", { + submit: function(data, callback) { + switch (data.action) { + case "compact_database": + db.compact({success: function(resp) { callback() }}); + break; + case "compact_views": + var groupname = page.viewName.substring(8, + page.viewName.indexOf("/_view")); + db.compactView(groupname, {success: function(resp) { callback() }}); + break; + case "view_cleanup": + db.viewCleanup({success: function(resp) { callback() }}); + break; + } + } + }); + } + + this.deleteDatabase = function() { + $.showDialog("dialog/_delete_database.html", { + submit: function(data, callback) { + db.drop({ + success: function(resp) { + callback(); + location.href = "index.html"; + if (window !== null) { + $("#dbs li").filter(function(index) { + return $("a", this).text() == dbName; + }).remove(); + $.futon.navigation.removeDatabase(dbName); + } + } + }); + } + }); + } + + this.databaseSecurity = function() { + $.showDialog("dialog/_database_security.html", { + load : function(d) { + db.getDbProperty("_security", { + success: function(r) { + ["admin", "reader"].forEach(function(key) { + var names = []; + var roles = []; + + if (r && typeof r[key + "s"] === "object") { + if ($.isArray(r[key + "s"]["names"])) { + names = r[key + "s"]["names"]; + } + if ($.isArray(r[key + "s"]["roles"])) { + roles = r[key + "s"]["roles"]; + } + } + + $("input[name=" + key + "_names]", d).val(JSON.stringify(names)); + $("input[name=" + key + "_roles]", d).val(JSON.stringify(roles)); + }); + } + }); + }, + // maybe this should be 2 forms + submit: function(data, callback) { + var errors = {}; + var secObj = { + admins: { + names: [], + roles: [] + }, + readers: { + names: [], + roles: [] + } + }; + + ["admin", "reader"].forEach(function(key) { + var names, roles; + + try { + names = JSON.parse(data[key + "_names"]); + } catch(e) { } + try { + roles = JSON.parse(data[key + "_roles"]); + } catch(e) { } + + if ($.isArray(names)) { + secObj[key + "s"]["names"] = names; + } else { + errors[key + "_names"] = "The " + key + + " names must be an array of strings"; + } + if ($.isArray(roles)) { + secObj[key + "s"]["roles"] = roles; + } else { + errors[key + "_roles"] = "The " + key + + " roles must be an array of strings"; + } + }); + + if ($.isEmptyObject(errors)) { + db.setDbProperty("_security", secObj); + } + callback(errors); + } + }); + } + + this.populateViewEditor = function() { + if (viewName.match(/^_design\//)) { + page.revertViewChanges(function() { + var dirtyTimeout = null; + function updateDirtyState() { + clearTimeout(dirtyTimeout); + dirtyTimeout = setTimeout(function() { + var buttons = $("#viewcode button.save, #viewcode button.revert"); + var viewCode = { + map: $("#viewcode_map").val(), + reduce: $("#viewcode_reduce").val() + }; + $("#reduce, #grouplevel").toggle(!!viewCode.reduce); + page.isDirty = (viewCode.map != page.storedViewCode.map) + || (viewCode.reduce != (page.storedViewCode.reduce || "")) + || page.viewLanguage != page.storedViewLanguage; + if (page.isDirty) { + buttons.removeAttr("disabled"); + } else { + buttons.attr("disabled", "disabled"); + } + }, 100); + } + $("#viewcode textarea").enableTabInsertion() + .bind("input", updateDirtyState); + if ($.browser.msie || $.browser.safari) { + $("#viewcode textarea").bind("paste", updateDirtyState) + .bind("change", updateDirtyState) + .bind("keydown", updateDirtyState) + .bind("keypress", updateDirtyState) + .bind("keyup", updateDirtyState) + .bind("textInput", updateDirtyState); + } + $("#language").change(updateDirtyState); + page.updateDocumentListing(); + }); + } else if (viewName == "_temp_view") { + $("#viewcode textarea").enableTabInsertion(); + page.viewLanguage = $.futon.storage.get("language"); + page.updateViewEditor( + $.futon.storage.get("map_fun", templates[page.viewLanguage]), + $.futon.storage.get("reduce_fun") + ); + } else { + $("#grouplevel, #reduce").hide(); + page.updateDocumentListing(); + } + page.populateLanguagesMenu(); + if (this.isTempView) { + $("#tempwarn").show(); + } + } + + // Populate the languages dropdown, and listen to selection changes + this.populateLanguagesMenu = function() { + var all_langs = {}; + fill_language = function() { + var select = $("#language"); + for (var language in all_langs) { + var option = $(document.createElement("option")) + .attr("value", language).text(language) + .appendTo(select); + } + if (select[0].options.length == 1) { + select[0].disabled = true; + } else { + select[0].disabled = false; + select.val(page.viewLanguage); + select.change(function() { + var language = $("#language").val(); + if (language != page.viewLanguage) { + var mapFun = $("#viewcode_map").val(); + if (mapFun == "" || mapFun == templates[page.viewLanguage]) { + // no edits made, so change to the new default + $("#viewcode_map").val(templates[language]); + } + page.viewLanguage = language; + $("#viewcode_map")[0].focus(); + } + return false; + }); + } + } + $.couch.config({ + success: function(resp) { + for (var language in resp) { + all_langs[language] = resp[language]; + } + + $.couch.config({ + success: function(resp) { + for (var language in resp) { + all_langs[language] = resp[language]; + } + fill_language(); + } + }, "native_query_servers"); + }, + error : function() {} + }, "query_servers"); + } + + this.populateViewsMenu = function() { + var select = $("#switch select"); + db.allDocs({startkey: "_design/", endkey: "_design0", + include_docs: true, + success: function(resp) { + select[0].options.length = 3; + for (var i = 0; i < resp.rows.length; i++) { + var doc = resp.rows[i].doc; + var optGroup = $(document.createElement("optgroup")) + .attr("label", doc._id.substr(8)).appendTo(select); + var viewNames = []; + for (var name in doc.views) { + viewNames.push(name); + } + viewNames.sort(); + for (var j = 0; j < viewNames.length; j++) { + var path = $.couch.encodeDocId(doc._id) + "/_view/" + + encodeURIComponent(viewNames[j]); + var option = $(document.createElement("option")) + .attr("value", path).text(viewNames[j]).appendTo(optGroup); + if (path == viewName) { + option[0].selected = true; + } + } + } + } + }); + if (!viewName.match(/^_design\//)) { + $.each(["_all_docs", "_design_docs", "_temp_view"], function(idx, name) { + if (viewName == name) { + select[0].options[idx].selected = true; + } + }); + } + } + + this.revertViewChanges = function(callback) { + if (!page.storedViewCode) { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + db.openDoc("_design/" + designDocId, { + error: function(status, error, reason) { + if (status == 404) { + $.futon.storage.del("view"); + location.href = "database.html?" + encodeURIComponent(db.name); + } + }, + success: function(resp) { + if(!resp.views || !resp.views[localViewName]) { + $.futon.storage.del("view"); + location.href = "database.html?" + encodeURIComponent(db.name); + } + var viewCode = resp.views[localViewName]; + page.viewLanguage = resp.language || "javascript"; + $("#language").val(page.viewLanguage); + page.updateViewEditor(viewCode.map, viewCode.reduce || ""); + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + page.storedViewCode = viewCode; + page.storedViewLanguage = page.viewLanguage; + if (callback) callback(); + } + }, {async: false}); + } else { + page.updateViewEditor(page.storedViewCode.map, + page.storedViewCode.reduce || ""); + page.viewLanguage = page.storedViewLanguage; + $("#language").val(page.viewLanguage); + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + page.isDirty = false; + if (callback) callback(); + } + } + + this.updateViewEditor = function(mapFun, reduceFun) { + if (!mapFun) return; + $("#viewcode_map").val(mapFun); + $("#viewcode_reduce").val(reduceFun); + var lines = Math.max( + mapFun.split("\n").length, + reduceFun.split("\n").length + ); + $("#reduce, #grouplevel").toggle(!!reduceFun); + $("#viewcode textarea").attr("rows", Math.min(15, Math.max(3, lines))); + } + + this.saveViewAs = function() { + if (viewName && /^_design/.test(viewName)) { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + } else { + var designDocId = "", localViewName = ""; + } + $.showDialog("dialog/_save_view_as.html", { + load: function(elem) { + $("#input_docid", elem).val(designDocId).suggest(function(text, callback) { + db.allDocs({ + limit: 10, startkey: "_design/" + text, endkey: "_design0", + success: function(docs) { + var matches = []; + for (var i = 0; i < docs.rows.length; i++) { + var docName = docs.rows[i].id.substr(8); + if (docName.indexOf(text) == 0) { + matches[i] = docName; + } + } + callback(matches); + } + }); + }); + $("#input_name", elem).val(localViewName).suggest(function(text, callback) { + db.openDoc("_design/" + $("#input_docid").val(), { + error: function() {}, // ignore + success: function(doc) { + var matches = []; + if (!doc.views) return; + for (var viewName in doc.views) { + if (viewName.indexOf(text) == 0) { + matches.push(viewName); + } + } + callback(matches); + } + }); + }); + }, + submit: function(data, callback) { + if (!data.docid || !data.name) { + var errors = {}; + if (!data.docid) errors.docid = "Please enter a document ID"; + if (!data.name) errors.name = "Please enter a view name"; + callback(errors); + } else { + var viewCode = { + map: $("#viewcode_map").val(), + reduce: $("#viewcode_reduce").val() || undefined + }; + var docId = ["_design", data.docid].join("/"); + function save(doc) { + if (!doc) { + doc = {_id: docId, language: page.viewLanguage}; + } else { + var numViews = 0; + for (var viewName in (doc.views || {})) { + if (viewName != data.name) numViews++; + } + if (numViews > 0 && page.viewLanguage != doc.language) { + callback({ + docid: "Cannot save to " + data.docid + + " because its language is \"" + doc.language + + "\", not \"" + page.viewLanguage + "\"." + }); + return; + } + doc.language = page.viewLanguage; + } + if (doc.views === undefined) doc.views = {}; + doc.views[data.name] = viewCode; + db.saveDoc(doc, { + success: function(resp) { + callback(); + page.isDirty = false; + location.href = "database.html?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(doc._id) + + "/_view/" + encodeURIComponent(data.name); + } + }); + } + db.openDoc(docId, { + error: function(status, error, reason) { + if (status == 404) save(null); + else alert(reason); + }, + success: function(doc) { + save(doc); + } + }); + } + } + }); + } + + this.saveViewChanges = function() { + var viewNameParts = viewName.split("/"); + var designDocId = decodeURIComponent(viewNameParts[1]); + var localViewName = decodeURIComponent(viewNameParts[3]); + db.openDoc("_design/" + designDocId, { + success: function(doc) { + var numViews = 0; + for (var viewName in (doc.views || {})) { + if (viewName != localViewName) numViews++; + } + if (numViews > 0 && page.viewLanguage != doc.language) { + alert("Cannot save view because the design document language " + + "is \"" + doc.language + "\", not \"" + + page.viewLanguage + "\"."); + return; + } + doc.language = page.viewLanguage; + var viewDef = doc.views[localViewName]; + viewDef.map = $("#viewcode_map").val(); + viewDef.reduce = $("#viewcode_reduce").val() || undefined; + db.saveDoc(doc, { + success: function(resp) { + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save") + .attr("disabled", "disabled"); + } + }); + } + }); + } + + this.updateDesignDocLink = function() { + if (viewName && /^_design/.test(viewName)) { + var docId = "_design/" + decodeURIComponent(viewName.split("/")[1]); + $("#designdoc-link").attr("href", "document.html?" + + encodeURIComponent(dbName) + "/" + $.couch.encodeDocId(docId)).text(docId); + } else { + $("#designdoc-link").removeAttr("href").text(""); + } + } + + this.jumpToDocument = function(docId) { + if (docId != "") { + location.href = 'document.html?' + encodeURIComponent(db.name) + + "/" + $.couch.encodeDocId(docId); + } + } + + this.updateDocumentListing = function(options) { + if (options === undefined) options = {}; + if (options.limit === undefined) { + var perPage = parseInt($("#perpage").val(), 10) + // Fetch an extra row so we know when we're on the last page for + // reduce views + options.limit = perPage + 1; + } else { + perPage = options.limit - 1; + } + if ($("#documents thead th.key").is(".desc")) { + if (typeof options.descending == 'undefined') options.descending = true; + var descend = true; + $.futon.storage.set("desc", "1"); + } else { + var descend = false; + $.futon.storage.del("desc"); + } + $("#paging a").unbind(); + $("#documents").find("tbody.content").empty().end().show(); + page.updateDesignDocLink(); + + options.success = function(resp) { + if (resp.offset === undefined) { + resp.offset = 0; + } + var descending_reverse = ((options.descending && !descend) || (descend && (options.descending === false))); + var has_reduce_prev = resp.total_rows === undefined && (descending_reverse ? resp.rows.length > perPage : options.startkey !== undefined); + if (descending_reverse && resp.rows) { + resp.rows = resp.rows.reverse(); + if (resp.rows.length > perPage) { + resp.rows.push(resp.rows.shift()); + } + } + if (resp.rows !== null && (has_reduce_prev || (descending_reverse ? + (resp.total_rows - resp.offset > perPage) : + (resp.offset > 0)))) { + $("#paging a.prev").attr("href", "#" + (resp.offset - perPage)).click(function() { + var opt = { + descending: !descend, + limit: options.limit + }; + if (resp.rows.length > 0) { + var firstDoc = resp.rows[0]; + opt.startkey = firstDoc.key !== undefined ? firstDoc.key : null; + if (firstDoc.id !== undefined) { + opt.startkey_docid = firstDoc.id; + } + opt.skip = 1; + } + page.updateDocumentListing(opt); + return false; + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + var has_reduce_next = resp.total_rows === undefined && (descending_reverse ? options.startkey !== undefined : resp.rows.length > perPage); + if (resp.rows !== null && (has_reduce_next || (descending_reverse ? + (resp.offset - resp.total_rows < perPage) : + (resp.total_rows - resp.offset > perPage)))) { + $("#paging a.next").attr("href", "#" + (resp.offset + perPage)).click(function() { + var opt = { + descending: descend, + limit: options.limit + }; + if (resp.rows.length > 0) { + var lastDoc = resp.rows[Math.min(perPage, resp.rows.length) - 1]; + opt.startkey = lastDoc.key !== undefined ? lastDoc.key : null; + if (lastDoc.id !== undefined) { + opt.startkey_docid = lastDoc.id; + } + opt.skip = 1; + } + page.updateDocumentListing(opt); + return false; + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + for (var i = 0; i < Math.min(perPage, resp.rows.length); i++) { + var row = resp.rows[i]; + var tr = $(""); + var key = "null"; + if (row.key !== null) { + key = $.futon.formatJSON(row.key, {indent: 0, linesep: ""}); + } + if (row.id) { + $("
      " + + "ID: " + $.futon.escape(row.id) + "
      ") + .find("strong").text(key).end() + .appendTo(tr); + } else { + $("") + .find("strong").text(key).end() + .appendTo(tr); + } + var value = "null"; + if (row.value !== null) { + value = $.futon.formatJSON(row.value, { + html: true, indent: 0, linesep: "", quoteKeys: false + }); + } + $("
      ").find("div").html(value).end() + .appendTo(tr).dblclick(function() { + location.href = this.previousSibling.firstChild.href; + }); + tr.appendTo("#documents tbody.content"); + } + var firstNum = 1; + var lastNum = totalNum = Math.min(perPage, resp.rows.length); + if (resp.total_rows != null) { + if (descending_reverse) { + lastNum = Math.min(resp.total_rows, resp.total_rows - resp.offset); + firstNum = lastNum - totalNum + 1; + } else { + firstNum = Math.min(resp.total_rows, resp.offset + 1); + lastNum = firstNum + totalNum - 1; + } + totalNum = resp.total_rows; + } else { + totalNum = "unknown"; + } + $("#paging").show(); + + $("#documents tbody.footer td span").text( + "Showing " + firstNum + "-" + lastNum + " of " + totalNum + + " row" + (firstNum != lastNum || totalNum == "unknown" ? "s" : "")); + $("#documents tbody tr:odd").addClass("odd"); + } + options.error = function(status, error, reason) { + alert("Error: " + error + "\n\n" + reason); + } + + if (!viewName || viewName == "_all_docs") { + $("#switch select")[0].selectedIndex = 0; + db.allDocs(options); + } else { + if (viewName == "_temp_view") { + $("#viewcode").show().removeClass("collapsed"); + var mapFun = $("#viewcode_map").val(); + $.futon.storage.set("map_fun", mapFun); + var reduceFun = $.trim($("#viewcode_reduce").val()) || null; + if (reduceFun) { + $.futon.storage.set("reduce_fun", reduceFun); + if ($("#reduce :checked").length) { + var level = parseInt($("#grouplevel select").val(), 10); + options.group = level > 0; + if (options.group && level < 100) { + options.group_level = level; + } + } else { + options.reduce = false; + } + } + $.futon.storage.set("language", page.viewLanguage); + db.query(mapFun, reduceFun, page.viewLanguage, options); + } else if (viewName == "_design_docs") { + options.startkey = options.descending ? "_design0" : "_design"; + options.endkey = options.descending ? "_design" : "_design0"; + db.allDocs(options); + } else { + $("button.compactview").show(); + $("#viewcode").show(); + var currentMapCode = $("#viewcode_map").val(); + var currentReduceCode = $.trim($("#viewcode_reduce").val()) || null; + if (currentReduceCode) { + if ($("#reduce :checked").length) { + var level = parseInt($("#grouplevel select").val(), 10); + options.group = level > 0; + if (options.group && level < 100) { + options.group_level = level; + } + } else { + options.reduce = false; + } + } + if (page.isDirty) { + db.query(currentMapCode, currentReduceCode, page.viewLanguage, options); + } else { + var viewParts = viewName.split('/'); + + if ($.futon.storage.get("stale")) { + options.stale = "ok"; + } + + db.view(viewParts[1] + "/" + viewParts[3], options); + } + } + } + } + + window.onbeforeunload = function() { + $("#switch select").val(viewName); + if (page.isDirty) { + return "You've made changes to the view code that have not been " + + "saved yet."; + } + } + + }, + + // Page class for browse/document.html + CouchDocumentPage: function() { + var urlParts = location.search.substr(1).split("/"); + var dbName = decodeURIComponent(urlParts.shift()); + if (urlParts.length) { + var idParts = urlParts.join("/").split("@", 2); + var docId = decodeURIComponent(idParts[0]); + var docRev = (idParts.length > 1) ? idParts[1] : null; + this.isNew = false; + } else { + var docId = $.couch.newUUID(); + var docRev = null; + this.isNew = true; + } + var db = $.couch.db(dbName); + + $.futon.storage.declare("tab", {defaultValue: "tabular", scope: "cookie"}); + + this.dbName = dbName; + this.db = db; + this.docId = docId; + this.doc = null; + this.isDirty = this.isNew; + page = this; + + this.activateTabularView = function() { + if ($("#fields tbody.source textarea").length > 0) + return; + + $.futon.storage.set("tab", "tabular"); + $("#tabs li").removeClass("active").filter(".tabular").addClass("active"); + $("#fields thead th:first").text("Field").attr("colspan", 1).next().show(); + $("#fields tbody.content").show(); + $("#fields tbody.source").hide(); + return false; + } + + this.activateSourceView = function() { + $.futon.storage.set("tab", "source"); + $("#tabs li").removeClass("active").filter(".source").addClass("active"); + $("#fields thead th:first").text("Source").attr("colspan", 2).next().hide(); + $("#fields tbody.content").hide(); + $("#fields tbody.source").find("td").each(function() { + $(this).html($("
      ").html($.futon.formatJSON(page.doc, {html: true})))
      +            .makeEditable({allowEmpty: false,
      +              createInput: function(value) {
      +                var rows = value.split("\n").length;
      +                return $("").enableTabInsertion();
      +              },
      +              prepareInput: function(input) {
      +                $(input).makeResizable({vertical: true});
      +              },
      +              end: function() {
      +                $(this).html($("
      ").html($.futon.formatJSON(page.doc, {html: true})));
      +              },
      +              accept: function(newValue) {
      +                page.doc = JSON.parse(newValue);
      +                page.isDirty = true;
      +                page.updateFieldListing(true);
      +              },
      +              populate: function(value) {
      +                return $.futon.formatJSON(page.doc);
      +              },
      +              validate: function(value) {
      +                try {
      +                  var doc = JSON.parse(value);
      +                  if (typeof doc != "object")
      +                    throw new SyntaxError("Please enter a valid JSON document (for example, {}).");
      +                  return true;
      +                } catch (err) {
      +                  var msg = err.message;
      +                  if (msg == "parseJSON" || msg == "JSON.parse") {
      +                    msg = "There is a syntax error in the document.";
      +                  }
      +                  $("
      ").text(msg).appendTo(this); + return false; + } + } + }); + }).end().show(); + return false; + } + + this.addField = function() { + if (!$("#fields tbody.content:visible").length) { + location.hash = "#tabular"; + page.activateTabularView(); + } + var fieldName = "unnamed"; + var fieldIdx = 1; + while (page.doc.hasOwnProperty(fieldName)) { + fieldName = "unnamed " + fieldIdx++; + } + page.doc[fieldName] = null; + var row = _addRowForField(page.doc, fieldName); + page.isDirty = true; + row.find("th b").dblclick(); + } + + var _sortFields = function(a, b) { + var a0 = a.charAt(0), b0 = b.charAt(0); + if (a0 == "_" && b0 != "_") { + return -1; + } else if (a0 != "_" && b0 == "_") { + return 1; + } else if (a == "_attachments" || b == "_attachments") { + return a0 == "_attachments" ? 1 : -1; + } else { + return a < b ? -1 : a != b ? 1 : 0; + } + } + + this.updateFieldListing = function(noReload) { + $("#fields tbody.content").empty(); + + function handleResult(doc, revs) { + page.doc = doc; + var propNames = []; + for (var prop in doc) { + propNames.push(prop); + } + // Order properties alphabetically, but put internal fields first + propNames.sort(_sortFields); + for (var pi = 0; pi < propNames.length; pi++) { + _addRowForField(doc, propNames[pi]); + } + if (revs.length > 1) { + var currentIndex = 0; + for (var i = 0; i < revs.length; i++) { + if (revs[i].rev == doc._rev) { + currentIndex = i; + break; + } + } + if (currentIndex < revs.length - 1) { + var prevRev = revs[currentIndex + 1].rev; + $("#paging a.prev").attr("href", "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId) + "@" + prevRev); + } + if (currentIndex > 0) { + var nextRev = revs[currentIndex - 1].rev; + $("#paging a.next").attr("href", "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId) + "@" + nextRev); + } + $("#fields tbody.footer td span").text("Showing revision " + + (revs.length - currentIndex) + " of " + revs.length); + } + if ($.futon.storage.get("tab") == "source") { + page.activateSourceView(); + } + } + + if (noReload) { + handleResult(page.doc, []); + return; + } + + if (!page.isNew) { + db.openDoc(docId, {revs_info: true, + success: function(doc) { + var revs = doc._revs_info || []; + delete doc._revs_info; + if (docRev != null) { + db.openDoc(docId, {rev: docRev, + error: function(status, error, reason) { + alert("The requested revision was not found. You will " + + "be redirected back to the latest revision."); + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(docId); + }, + success: function(doc) { + handleResult(doc, revs); + } + }); + } else { + handleResult(doc, revs); + } + } + }); + } else { + handleResult({_id: docId}, []); + $("#fields tbody td").dblclick(); + } + } + + this.deleteDocument = function() { + $.showDialog("dialog/_delete_document.html", { + submit: function(data, callback) { + db.removeDoc(page.doc, { + success: function(resp) { + callback(); + location.href = "database.html?" + encodeURIComponent(dbName); + } + }); + } + }); + } + + this.saveDocument = function() { + db.saveDoc(page.doc, { + error: function(status, error, reason) { + alert("Error: " + error + "\n\n" + reason); + }, + success: function(resp) { + page.isDirty = false; + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(page.docId); + } + }); + } + + this.uploadAttachment = function() { + if (page.isDirty) { + alert("You need to save or revert any changes you have made to the " + + "document before you can attach a new file."); + return false; + } + $.showDialog("dialog/_upload_attachment.html", { + load: function(elem) { + $("input[name='_rev']", elem).val(page.doc._rev); + }, + submit: function(data, callback) { + if (!data._attachments || data._attachments.length == 0) { + callback({_attachments: "Please select a file to upload."}); + return; + } + var form = $("#upload-form"); + form.find("#progress").css("visibility", "visible"); + form.ajaxSubmit({ + url: db.uri + $.couch.encodeDocId(page.docId), + success: function(resp) { + form.find("#progress").css("visibility", "hidden"); + page.isDirty = false; + location.href = "?" + encodeURIComponent(dbName) + + "/" + $.couch.encodeDocId(page.docId); + } + }); + } + }); + } + + window.onbeforeunload = function() { + if (page.isDirty) { + return "You've made changes to this document that have not been " + + "saved yet."; + } + } + + function _addRowForField(doc, fieldName) { + var row = $("") + .find("th").append($("").text(fieldName)).end() + .appendTo("#fields tbody.content"); + if (fieldName == "_attachments") { + row.find("td").append(_renderAttachmentList(doc[fieldName])); + } else { + row.find("td").append(_renderValue(doc[fieldName])); + _initKey(doc, row, fieldName); + _initValue(doc, row, fieldName); + } + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + row.data("name", fieldName); + return row; + } + + function _initKey(doc, row, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") { + return; + } + + var cell = row.find("th"); + + $("").click(function() { + delete doc[fieldName]; + row.remove(); + page.isDirty = true; + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + }).prependTo(cell); + + cell.find("b").makeEditable({allowEmpty: false, + accept: function(newName, oldName) { + doc[newName] = doc[oldName]; + delete doc[oldName]; + row.data("name", newName); + $(this).text(newName); + page.isDirty = true; + }, + begin: function() { + row.find("th button.delete").hide(); + return true; + }, + end: function(keyCode) { + row.find("th button.delete").show(); + if (keyCode == 9) { // tab, move to editing the value + row.find("td").dblclick(); + } + }, + validate: function(newName, oldName) { + $("div.error", this).remove(); + if (newName != oldName && doc[newName] !== undefined) { + $("
      Already have field with that name.
      ") + .appendTo(this); + return false; + } + return true; + } + }); + } + + function _initValue(doc, row, fieldName) { + if ((fieldName == "_id" && !page.isNew) || fieldName == "_rev") { + return; + } + + row.find("td").makeEditable({acceptOnBlur: false, allowEmpty: true, + createInput: function(value) { + value = doc[row.data("name")]; + var elem = $(this); + if (elem.find("dl").length > 0 || + elem.find("code").is(".array, .object") || + typeof(value) == "string" && (value.length > 60 || value.match(/\n/))) { + return $(""); + } + return $(""); + }, + end: function() { + $(this).children().remove(); + $(this).append(_renderValue(doc[row.data("name")])); + }, + prepareInput: function(input) { + if ($(input).is("textarea")) { + var height = Math.min(input.scrollHeight, document.body.clientHeight - 100); + $(input).height(height).makeResizable({vertical: true}).enableTabInsertion(); + } + }, + accept: function(newValue) { + var fieldName = row.data("name"); + try { + doc[fieldName] = JSON.parse(newValue); + } catch (err) { + doc[fieldName] = newValue; + } + page.isDirty = true; + if (fieldName == "_id") { + page.docId = page.doc._id = doc[fieldName]; + $("h1 strong").text(page.docId); + } + }, + populate: function(value) { + value = doc[row.data("name")]; + if (typeof(value) == "string") { + return value; + } + return $.futon.formatJSON(value); + }, + validate: function(value) { + $("div.error", this).remove(); + try { + var parsed = JSON.parse(value); + if (row.data("name") == "_id" && typeof(parsed) != "string") { + $("
      The document ID must be a string.
      ") + .appendTo(this); + return false; + } + return true; + } catch (err) { + return true; + } + } + }); + } + + function _renderValue(value) { + function isNullOrEmpty(val) { + if (val == null) return true; + for (var i in val) return false; + return true; + } + function render(val) { + var type = typeof(val); + if (type == "object" && !isNullOrEmpty(val)) { + var list = $("
      "); + for (var i in val) { + $("
      ").text(i).appendTo(list); + $("
      ").append(render(val[i])).appendTo(list); + } + return list; + } else { + var html = $.futon.formatJSON(val, { + html: true, + escapeStrings: false + }); + var n = $(html); + if (n.text().length > 140) { + // This code reduces a long string in to a summarized string with a link to expand it. + // Someone, somewhere, is doing something nasty with the event after it leaves these handlers. + // At this time I can't track down the offender, it might actually be a jQuery propogation issue. + var fulltext = n.text(); + var mintext = n.text().slice(0, 140); + var e = $('...'); + var m = $('X'); + var expand = function (evt) { + n.empty(); + n.text(fulltext); + n.append(m); + evt.stopPropagation(); + evt.stopImmediatePropagation(); + evt.preventDefault(); + } + var minimize = function (evt) { + n.empty(); + n.text(mintext); + // For some reason the old element's handler won't fire after removed and added again. + e = $('...'); + e.click(expand); + n.append(e); + evt.stopPropagation(); + evt.stopImmediatePropagation(); + evt.preventDefault(); + } + e.click(expand); + n.click(minimize); + n.text(mintext); + n.append(e) + } + return n; + } + } + var elem = render(value); + + elem.find("dd:has(dl)").hide().prev("dt").addClass("collapsed"); + elem.find("dd:not(:has(dl))").addClass("inline").prev().addClass("inline"); + elem.find("dt.collapsed").click(function() { + $(this).toggleClass("collapsed").next().toggle(); + }); + + return elem; + } + + function _renderAttachmentList(attachments) { + var ul = $("
        ").addClass("attachments"); + $.each(attachments, function(idx, attachment) { + _renderAttachmentItem(idx, attachment).appendTo(ul); + }); + return ul; + } + + function _renderAttachmentItem(name, attachment) { + var attachmentHref = db.uri + $.couch.encodeDocId(page.docId) + + "/" + encodeAttachment(name); + var li = $("
      • "); + $("").text(name) + .attr("href", attachmentHref) + .wrapInner("").appendTo(li); + $("()").text("" + $.futon.formatSize(attachment.length) + + ", " + attachment.content_type).addClass("info").appendTo(li); + if (name == "tests.js") { + li.find('span.info').append(', open in test runner'); + } + _initAttachmentItem(name, attachment, li); + return li; + } + + function _initAttachmentItem(name, attachment, li) { + $("").click(function() { + if (!li.siblings("li").length) { + delete page.doc._attachments; + li.parents("tr").remove(); + $("#fields tbody.content tr").removeClass("odd").filter(":odd").addClass("odd"); + } else { + delete page.doc._attachments[name]; + li.remove(); + } + page.isDirty = true; + return false; + }).prependTo($("a", li)); + } + }, + + }); + + function encodeAttachment(name) { + var encoded = [], parts = name.split('/'); + for (var i=0; i < parts.length; i++) { + encoded.push(encodeURIComponent(parts[i])); + }; + return encoded.join('%2f'); + } + +})(jQuery); diff --git a/rel/overlay/share/www/script/futon.format.js b/rel/overlay/share/www/script/futon.format.js new file mode 100644 index 00000000..0d536e36 --- /dev/null +++ b/rel/overlay/share/www/script/futon.format.js @@ -0,0 +1,143 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + $.futon = $.futon || {}; + $.extend($.futon, { + escape: function(string) { + return string.replace(/&/g, "&") + .replace(//g, ">"); + }, + + // JSON pretty printing + formatJSON: function(val, options) { + options = $.extend({ + escapeStrings: true, + indent: 4, + linesep: "\n", + quoteKeys: true + }, options || {}); + var itemsep = options.linesep.length ? "," + options.linesep : ", "; + + function format(val, depth) { + var tab = []; + for (var i = 0; i < options.indent * depth; i++) tab.push(""); + tab = tab.join(" "); + + var type = typeof val; + switch (type) { + case "boolean": + case "number": + case "string": + var retval = val; + if (type == "string" && !options.escapeStrings) { + retval = indentLines(retval.replace(/\r\n/g, "\n"), tab.substr(options.indent)); + } else { + if (options.html) { + retval = $.futon.escape(JSON.stringify(val)); + } else { + retval = JSON.stringify(val); + } + } + if (options.html) { + retval = "" + retval + ""; + } + return retval; + + case "object": { + if (val === null) { + if (options.html) { + return "null"; + } + return "null"; + } + if (val.constructor == Date) { + return JSON.stringify(val); + } + + var buf = []; + + if (val.constructor == Array) { + buf.push("["); + for (var index = 0; index < val.length; index++) { + buf.push(index > 0 ? itemsep : options.linesep); + buf.push(tab, format(val[index], depth + 1)); + } + if (index >= 0) { + buf.push(options.linesep, tab.substr(options.indent)); + } + buf.push("]"); + if (options.html) { + return "" + buf.join("") + ""; + } + + } else { + buf.push("{"); + var index = 0; + for (var key in val) { + buf.push(index > 0 ? itemsep : options.linesep); + var keyDisplay = options.quoteKeys ? JSON.stringify(key) : key; + if (options.html) { + if (options.quoteKeys) { + keyDisplay = keyDisplay.substr(1, keyDisplay.length - 2); + } + keyDisplay = "" + $.futon.escape(keyDisplay) + ""; + if (options.quoteKeys) { + keyDisplay = '"' + keyDisplay + '"'; + } + } + buf.push(tab, keyDisplay, + ": ", format(val[key], depth + 1)); + index++; + } + if (index >= 0) { + buf.push(options.linesep, tab.substr(options.indent)); + } + buf.push("}"); + if (options.html) { + return "" + buf.join("") + ""; + } + } + + return buf.join(""); + } + } + } + + function indentLines(text, tab) { + var lines = text.split("\n"); + for (var i in lines) { + lines[i] = (i > 0 ? tab : "") + $.futon.escape(lines[i]); + } + return lines.join("
        "); + } + + return format(val, 1); + }, + + // File size pretty printing + formatSize: function(size) { + var jump = 512; + if (size < jump) return size + " bytes"; + var units = ["KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + var i = 0; + while (size >= jump && i < units.length) { + i += 1; + size /= 1024 + } + return size.toFixed(1) + ' ' + units[i - 1]; + } + + }); + +})(jQuery); diff --git a/rel/overlay/share/www/script/futon.js b/rel/overlay/share/www/script/futon.js new file mode 100644 index 00000000..b15a5eec --- /dev/null +++ b/rel/overlay/share/www/script/futon.js @@ -0,0 +1,471 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/ +function $$(node) { + var data = $(node).data("$$"); + if (data) { + return data; + } else { + data = {}; + $(node).data("$$", data); + return data; + } +}; + +(function($) { + + function Session() { + + function doLogin(name, password, callback) { + $.couch.login({ + name : name, + password : password, + success : function() { + $.futon.session.sidebar(); + callback(); + }, + error : function(code, error, reason) { + $.futon.session.sidebar(); + callback({name : "Error logging in: "+reason}); + } + }); + }; + + function doSignup(name, password, callback, runLogin) { + $.couch.signup({ + name : name + }, password, { + success : function() { + if (runLogin) { + doLogin(name, password, callback); + } else { + callback(); + } + }, + error : function(status, error, reason) { + $.futon.session.sidebar(); + if (error == "conflict") { + callback({name : "Name '"+name+"' is taken"}); + } else { + callback({name : "Signup error: "+reason}); + } + } + }); + }; + + function validateUsernameAndPassword(data, callback) { + if (!data.name || data.name.length == 0) { + callback({name: "Please enter a name."}); + return false; + }; + if (!data.password || data.password.length == 0) { + callback({password: "Please enter a password."}); + return false; + }; + return true; + }; + + function createAdmin() { + $.showDialog("dialog/_create_admin.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + $.couch.config({ + success : function() { + doLogin(data.name, data.password, function(errors) { + if(!$.isEmptyObject(errors)) { + callback(errors); + return; + } + doSignup(data.name, null, function(errors) { + if (errors && errors.name && errors.name.indexOf && errors.name.indexOf("taken") == -1) { + callback(errors); + } else { + callback(); + } + }, false); + }); + } + }, "admins", data.name, data.password); + } + }); + return false; + }; + + function login() { + $.showDialog("dialog/_login.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + doLogin(data.name, data.password, callback); + } + }); + return false; + }; + + function logout() { + $.couch.logout({ + success : function(resp) { + $.futon.session.sidebar(); + } + }) + }; + + function signup() { + $.showDialog("dialog/_signup.html", { + submit: function(data, callback) { + if (!validateUsernameAndPassword(data, callback)) return; + doSignup(data.name, data.password, callback, true); + } + }); + return false; + }; + + this.setupSidebar = function() { + $("#userCtx .login").click(login); + $("#userCtx .logout").click(logout); + $("#userCtx .signup").click(signup); + $("#userCtx .createadmin").click(createAdmin); + }; + + this.sidebar = function() { + // get users db info? + $("#userCtx span").hide(); + $.couch.session({ + success : function(r) { + var userCtx = r.userCtx; + $$("#userCtx").userCtx = userCtx; + if (userCtx.name) { + $("#userCtx .name").text(userCtx.name).attr({href : $.couch.urlPrefix + "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+encodeURIComponent(userCtx.name)}); + if (userCtx.roles.indexOf("_admin") != -1) { + $("#userCtx .loggedinadmin").show(); + } else { + $("#userCtx .loggedin").show(); + } + } else if (userCtx.roles.indexOf("_admin") != -1) { + $("#userCtx .adminparty").show(); + } else { + $("#userCtx .loggedout").show(); + }; + } + }) + }; + }; + + function Navigation() { + var nav = this; + this.loaded = false; + this.eventHandlers = { + load: [] + }; + + this.ready = function(callback) { + if (callback) { + if (this.loaded) { + callback.apply(this); + } + this.eventHandlers["load"].push(callback); + } else { + this.loaded = true; + callbacks = this.eventHandlers["load"]; + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].apply(this); + } + } + } + + this.addDatabase = function(name) { + var current = $.futon.storage.get("recent", ""); + var recentDbs = current ? current.split(",") : []; + if ($.inArray(name, recentDbs) == -1) { + recentDbs.unshift(name); + if (recentDbs.length > 10) recentDbs.length = 10; + $.futon.storage.set("recent", recentDbs.join(",")); + this.updateDatabases(); + } + } + + this.removeDatabase = function(name) { + // remove database from recent databases list + var current = $.futon.storage.get("recent", ""); + var recentDbs = current ? current.split(",") : []; + var recentIdx = $.inArray(name, recentDbs); + if (recentIdx >= 0) { + recentDbs.splice(recentIdx, 1); + $.futon.storage.set("recent", recentDbs.join(",")); + this.updateDatabases(); + } + } + + this.updateDatabases = function() { + var selection = null; + $("#dbs .selected a").each(function() { + selection = [this.pathname, this.search]; + }); + $("#dbs").empty(); + var recentDbs = $.futon.storage.get("recent").split(","); + recentDbs.sort(); + $.each(recentDbs, function(idx, name) { + if (name) { + $("#dbs").append("
      • " + + "" + + "" + name + + "
      • "); + } + }); + if (selection) { + this.updateSelection(selection[0], selection[1]); + } + $("#dbs button.remove").click(function() { + nav.removeDatabase(this.value); + return false; + }); + } + + this.updateSelection = function(path, queryString) { + function fixupPath(path) { // hack for IE/Win + return (path.charAt(0) != "/") ? ("/" + path) : path; + } + if (!path) { + path = location.pathname; + if (!queryString) { + queryString = location.search; + } + } else if (!queryString) { + queryString = ""; + } + var href = fixupPath(path + queryString); + $("#nav li").removeClass("selected"); + $("#nav li a").each(function() { + if (fixupPath(this.pathname) + this.search != href) return; + $(this).parent("li").addClass("selected").parents("li").addClass("selected"); + }); + } + + this.toggle = function(speed) { + if (speed === undefined) { + speed = 500; + } + var sidebar = $("#sidebar").stop(true, true); + var hidden = !$(sidebar).is(".hidden"); + + $("#wrap").animate({ + marginRight: hidden ? 0 : 210 + }, speed, function() { + $(document.body).toggleClass("fullwidth", hidden); + }); + sidebar.toggleClass("hidden").animate({ + width: hidden ? 26 : 210, + height: hidden ? $("h1").outerHeight() - 1 : "100%", + right: hidden ? 0 : -210 + }, speed).children(":not(#sidebar-toggle)").animate({ + opacity: "toggle" + }, speed); + $("h1").animate({marginRight: hidden ? 26 : 0}, speed); + + $("#sidebar-toggle") + .attr("title", hidden ? "Show Sidebar" : "Hide Sidebar"); + $.futon.storage.set("sidebar", hidden ? "hidden" : "show"); + }; + } + + function Storage() { + var storage = this; + this.decls = {}; + + this.declare = function(name, options) { + this.decls[name] = $.extend({}, { + scope: "window", + defaultValue: null, + prefix: "" + }, options || {}); + } + + this.declareWithPrefix = function(prefix, decls) { + for (var name in decls) { + var options = decls[name]; + options.prefix = prefix; + storage.declare(name, options); + } + } + + this.del = function(name) { + lookup(name, function(decl) { + handlers[decl.scope].del(decl.prefix + name); + }); + } + + this.get = function(name, defaultValue) { + return lookup(name, function(decl) { + var value = handlers[decl.scope].get(decl.prefix + name); + if (value !== undefined) { + return value; + } + if (defaultValue !== undefined) { + return defaultValue; + } + return decl.defaultValue; + }); + } + + this.set = function(name, value) { + lookup(name, function(decl) { + if (value == decl.defaultValue) { + handlers[decl.scope].del(decl.prefix + name); + } else { + handlers[decl.scope].set(decl.prefix + name, value); + } + }); + } + + function lookup(name, callback) { + var decl = storage.decls[name]; + if (decl === undefined) { + return decl; + } + return callback(decl); + } + + // add suffix to cookie names to be able to separate between ports + var cookiePrefix = location.port + "_"; + + var handlers = { + + "cookie": { + get: function(name) { + var nameEq = cookiePrefix + name + "="; + var parts = document.cookie.split(';'); + for (var i = 0; i < parts.length; i++) { + var part = parts[i].replace(/^\s+/, ""); + if (part.indexOf(nameEq) == 0) { + return unescape(part.substring(nameEq.length, part.length)); + } + } + }, + set: function(name, value) { + var date = new Date(); + date.setTime(date.getTime() + 14*24*60*60*1000); // two weeks + document.cookie = cookiePrefix + name + "=" + escape(value) + + "; expires=" + date.toGMTString(); + }, + del: function(name) { + var date = new Date(); + date.setTime(date.getTime() - 24*60*60*1000); // yesterday + document.cookie = cookiePrefix + name + "=" + + "; expires=" + date.toGMTString(); + } + }, + + "window": { + get: function(name) { + return JSON.parse(window.name || "{}")[name]; + }, + set: function(name, value) { + var obj = JSON.parse(window.name || "{}"); + obj[name] = value || null; + window.name = JSON.stringify(obj); + }, + del: function(name) { + var obj = JSON.parse(window.name || "{}"); + delete obj[name]; + window.name = JSON.stringify(obj); + } + } + + }; + + } + + $.couch.urlPrefix = ".."; + $.futon = $.futon || {}; + $.extend($.futon, { + navigation: new Navigation(), + session : new Session(), + storage: new Storage() + }); + + $.fn.addPlaceholder = function() { + if (this[0] && "placeholder" in document.createElement("input")) { + return; // found native placeholder support + } + return this.live('focusin', function() { + var input = $(this); + if (input.val() === input.attr("placeholder")) { + input.removeClass("placeholder").val(""); + } + }).live("focusout", function() { + var input = $(this); + if (input.val() === "") { + input.val(input.attr("placeholder")).addClass("placeholder"); + } + }).trigger("focusout"); + } + + $.fn.enableTabInsertion = function(chars) { + chars = chars || "\t"; + var width = chars.length; + return this.keydown(function(evt) { + if (evt.keyCode == 9) { + var v = this.value; + var start = this.selectionStart; + var scrollTop = this.scrollTop; + if (start !== undefined) { + this.value = v.slice(0, start) + chars + v.slice(start); + this.selectionStart = this.selectionEnd = start + width; + } else { + document.selection.createRange().text = chars; + this.caretPos += width; + } + return false; + } + }); + } + + $(document) + .ajaxStart(function() { $(this.body).addClass("loading"); }) + .ajaxStop(function() { $(this.body).removeClass("loading"); }); + + $.futon.storage.declare("sidebar", {scope: "cookie", defaultValue: "show"}); + $.futon.storage.declare("recent", {scope: "cookie", defaultValue: ""}); + + $(function() { + document.title = "Apache CouchDB - Futon: " + document.title; + if ($.futon.storage.get("sidebar") == "hidden") { + // doing this as early as possible prevents flickering + $(document.body).addClass("fullwidth"); + } + $("input[placeholder]").addPlaceholder(); + + $.get("_sidebar.html", function(resp) { + $("#wrap").append(resp) + .find("#sidebar-toggle").click(function(e) { + $.futon.navigation.toggle(e.shiftKey ? 2500 : 500); + return false; + }); + if ($.futon.storage.get("sidebar") == "hidden") { + $.futon.navigation.toggle(0); + } + + $.futon.navigation.updateDatabases(); + $.futon.navigation.updateSelection(); + $.futon.navigation.ready(); + $.futon.session.setupSidebar(); + $.futon.session.sidebar(); + + $.couch.info({ + success: function(info, status) { + $("#version").text(info.version); + } + }); + }); + }); + +})(jQuery); diff --git a/rel/overlay/share/www/script/jquery.couch.js b/rel/overlay/share/www/script/jquery.couch.js new file mode 100644 index 00000000..ebf7d52a --- /dev/null +++ b/rel/overlay/share/www/script/jquery.couch.js @@ -0,0 +1,668 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + $.couch = $.couch || {}; + + function encodeDocId(docID) { + var parts = docID.split("/"); + if (parts[0] == "_design") { + parts.shift(); + return "_design/" + encodeURIComponent(parts.join('/')); + } + return encodeURIComponent(docID); + }; + + function prepareUserDoc(user_doc, new_password) { + if (typeof hex_sha1 == "undefined") { + alert("creating a user doc requires sha1.js to be loaded in the page"); + return; + } + var user_prefix = "org.couchdb.user:"; + user_doc._id = user_doc._id || user_prefix + user_doc.name; + if (new_password) { + // handle the password crypto + user_doc.salt = $.couch.newUUID(); + user_doc.password_sha = hex_sha1(new_password + user_doc.salt); + } + user_doc.type = "user"; + if (!user_doc.roles) { + user_doc.roles = [] + } + return user_doc; + }; + + var uuidCache = []; + + $.extend($.couch, { + urlPrefix: '', + activeTasks: function(options) { + ajax( + {url: this.urlPrefix + "/_active_tasks"}, + options, + "Active task status could not be retrieved" + ); + }, + + allDbs: function(options) { + ajax( + {url: this.urlPrefix + "/_all_dbs"}, + options, + "An error occurred retrieving the list of all databases" + ); + }, + + config: function(options, section, option, value) { + var req = {url: this.urlPrefix + "/_config/"}; + if (section) { + req.url += encodeURIComponent(section) + "/"; + if (option) { + req.url += encodeURIComponent(option); + } + } + if (value === null) { + req.type = "DELETE"; + } else if (value !== undefined) { + req.type = "PUT"; + req.data = toJSON(value); + req.contentType = "application/json"; + req.processData = false + } + + ajax(req, options, + "An error occurred retrieving/updating the server configuration" + ); + }, + + session: function(options) { + options = options || {}; + $.ajax({ + type: "GET", url: this.urlPrefix + "/_session", + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("An error occurred getting session info: " + resp.reason); + } + } + }); + }, + + userDb : function(callback) { + $.couch.session({ + success : function(resp) { + var userDb = $.couch.db(resp.info.authentication_db); + callback(userDb); + } + }); + }, + + signup: function(user_doc, password, options) { + options = options || {}; + // prepare user doc based on name and password + user_doc = prepareUserDoc(user_doc, password); + $.couch.userDb(function(db) { + db.saveDoc(user_doc, options); + }) + }, + + login: function(options) { + options = options || {}; + $.ajax({ + type: "POST", url: this.urlPrefix + "/_session", dataType: "json", + data: {name: options.name, password: options.password}, + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("An error occurred logging in: " + resp.reason); + } + } + }); + }, + logout: function(options) { + options = options || {}; + $.ajax({ + type: "DELETE", url: this.urlPrefix + "/_session", dataType: "json", + username : "_", password : "_", + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("An error occurred logging out: " + resp.reason); + } + } + }); + }, + + db: function(name, db_opts) { + db_opts = db_opts || {}; + var rawDocs = {}; + function maybeApplyVersion(doc) { + if (doc._id && doc._rev && rawDocs[doc._id] && rawDocs[doc._id].rev == doc._rev) { + // todo: can we use commonjs require here? + if (typeof Base64 == "undefined") { + alert("please include /_utils/script/base64.js in the page for base64 support"); + return false; + } else { + doc._attachments = doc._attachments || {}; + doc._attachments["rev-"+doc._rev.split("-")[0]] = { + content_type :"application/json", + data : Base64.encode(rawDocs[doc._id].raw) + } + return true; + } + } + }; + return { + name: name, + uri: this.urlPrefix + "/" + encodeURIComponent(name) + "/", + + compact: function(options) { + $.extend(options, {successStatus: 202}); + ajax({ + type: "POST", url: this.uri + "_compact", + data: "", processData: false + }, + options, + "The database could not be compacted" + ); + }, + viewCleanup: function(options) { + $.extend(options, {successStatus: 202}); + ajax({ + type: "POST", url: this.uri + "_view_cleanup", + data: "", processData: false + }, + options, + "The views could not be cleaned up" + ); + }, + compactView: function(groupname, options) { + $.extend(options, {successStatus: 202}); + ajax({ + type: "POST", url: this.uri + "_compact/" + groupname, + data: "", processData: false + }, + options, + "The view could not be compacted" + ); + }, + create: function(options) { + $.extend(options, {successStatus: 201}); + ajax({ + type: "PUT", url: this.uri, contentType: "application/json", + data: "", processData: false + }, + options, + "The database could not be created" + ); + }, + drop: function(options) { + ajax( + {type: "DELETE", url: this.uri}, + options, + "The database could not be deleted" + ); + }, + info: function(options) { + ajax( + {url: this.uri}, + options, + "Database information could not be retrieved" + ); + }, + changes: function(since, options) { + options = options || {}; + // set up the promise object within a closure for this handler + var timeout = 100, db = this, active = true, + listeners = [], + promise = { + onChange : function(fun) { + listeners.push(fun); + }, + stop : function() { + active = false; + } + }; + // call each listener when there is a change + function triggerListeners(resp) { + $.each(listeners, function() { + this(resp); + }); + }; + // when there is a change, call any listeners, then check for another change + options.success = function(resp) { + timeout = 100; + if (active) { + since = resp.last_seq; + triggerListeners(resp); + getChangesSince(); + }; + }; + options.error = function() { + if (active) { + setTimeout(getChangesSince, timeout); + timeout = timeout * 2; + } + }; + // actually make the changes request + function getChangesSince() { + var opts = $.extend({heartbeat : 10 * 1000}, options, { + feed : "longpoll", + since : since + }); + ajax( + {url: db.uri + "_changes"+encodeOptions(opts)}, + options, + "Error connecting to "+db.uri+"/_changes." + ); + } + // start the first request + if (since) { + getChangesSince(); + } else { + db.info({ + success : function(info) { + since = info.update_seq; + getChangesSince(); + } + }); + } + return promise; + }, + allDocs: function(options) { + var type = "GET"; + var data = null; + if (options["keys"]) { + type = "POST"; + var keys = options["keys"]; + delete options["keys"]; + data = toJSON({ "keys": keys }); + } + ajax({ + type: type, + data: data, + url: this.uri + "_all_docs" + encodeOptions(options) + }, + options, + "An error occurred retrieving a list of all documents" + ); + }, + allDesignDocs: function(options) { + this.allDocs($.extend({startkey:"_design", endkey:"_design0"}, options)); + }, + allApps: function(options) { + options = options || {}; + var self = this; + if (options.eachApp) { + this.allDesignDocs({ + success: function(resp) { + $.each(resp.rows, function() { + self.openDoc(this.id, { + success: function(ddoc) { + var index, appPath, appName = ddoc._id.split('/'); + appName.shift(); + appName = appName.join('/'); + index = ddoc.couchapp && ddoc.couchapp.index; + if (index) { + appPath = ['', name, ddoc._id, index].join('/'); + } else if (ddoc._attachments && ddoc._attachments["index.html"]) { + appPath = ['', name, ddoc._id, "index.html"].join('/'); + } + if (appPath) options.eachApp(appName, appPath, ddoc); + } + }); + }); + } + }); + } else { + alert("Please provide an eachApp function for allApps()"); + } + }, + openDoc: function(docId, options, ajaxOptions) { + options = options || {}; + if (db_opts.attachPrevRev || options.attachPrevRev) { + $.extend(options, { + beforeSuccess : function(req, doc) { + rawDocs[doc._id] = { + rev : doc._rev, + raw : req.responseText + }; + } + }); + } else { + $.extend(options, { + beforeSuccess : function(req, doc) { + if (doc["jquery.couch.attachPrevRev"]) { + rawDocs[doc._id] = { + rev : doc._rev, + raw : req.responseText + }; + } + } + }); + } + ajax({url: this.uri + encodeDocId(docId) + encodeOptions(options)}, + options, + "The document could not be retrieved", + ajaxOptions + ); + }, + saveDoc: function(doc, options) { + options = options || {}; + var db = this; + var beforeSend = fullCommit(options); + if (doc._id === undefined) { + var method = "POST"; + var uri = this.uri; + } else { + var method = "PUT"; + var uri = this.uri + encodeDocId(doc._id); + } + var versioned = maybeApplyVersion(doc); + $.ajax({ + type: method, url: uri + encodeOptions(options), + contentType: "application/json", + dataType: "json", data: toJSON(doc), + beforeSend : beforeSend, + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 200 || req.status == 201 || req.status == 202) { + doc._id = resp.id; + doc._rev = resp.rev; + if (versioned) { + db.openDoc(doc._id, { + attachPrevRev : true, + success : function(d) { + doc._attachments = d._attachments; + if (options.success) options.success(resp); + } + }); + } else { + if (options.success) options.success(resp); + } + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("The document could not be saved: " + resp.reason); + } + } + }); + }, + bulkSave: function(docs, options) { + var beforeSend = fullCommit(options); + $.extend(options, {successStatus: 201, beforeSend : beforeSend}); + ajax({ + type: "POST", + url: this.uri + "_bulk_docs" + encodeOptions(options), + contentType: "application/json", data: toJSON(docs) + }, + options, + "The documents could not be saved" + ); + }, + removeDoc: function(doc, options) { + ajax({ + type: "DELETE", + url: this.uri + + encodeDocId(doc._id) + + encodeOptions({rev: doc._rev}) + }, + options, + "The document could not be deleted" + ); + }, + bulkRemove: function(docs, options){ + docs.docs = $.each( + docs.docs, function(i, doc){ + doc._deleted = true; + } + ); + $.extend(options, {successStatus: 201}); + ajax({ + type: "POST", + url: this.uri + "_bulk_docs" + encodeOptions(options), + data: toJSON(docs) + }, + options, + "The documents could not be deleted" + ); + }, + copyDoc: function(docId, options, ajaxOptions) { + ajaxOptions = $.extend(ajaxOptions, { + complete: function(req) { + var resp = $.httpData(req, "json"); + if (req.status == 201) { + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp.error, resp.reason); + } else { + alert("The document could not be copied: " + resp.reason); + } + } + }); + ajax({ + type: "COPY", + url: this.uri + encodeDocId(docId) + }, + options, + "The document could not be copied", + ajaxOptions + ); + }, + query: function(mapFun, reduceFun, language, options) { + language = language || "javascript"; + if (typeof(mapFun) !== "string") { + mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")"; + } + var body = {language: language, map: mapFun}; + if (reduceFun != null) { + if (typeof(reduceFun) !== "string") + reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString() + ")"; + body.reduce = reduceFun; + } + ajax({ + type: "POST", + url: this.uri + "_temp_view" + encodeOptions(options), + contentType: "application/json", data: toJSON(body) + }, + options, + "An error occurred querying the database" + ); + }, + list: function(list, view, options) { + var list = list.split('/'); + var options = options || {}; + var type = 'GET'; + var data = null; + if (options['keys']) { + type = 'POST'; + var keys = options['keys']; + delete options['keys']; + data = toJSON({'keys': keys }); + } + ajax({ + type: type, + data: data, + url: this.uri + '_design/' + list[0] + + '/_list/' + list[1] + '/' + view + encodeOptions(options) + }, + options, 'An error occured accessing the list' + ); + }, + view: function(name, options) { + var name = name.split('/'); + var options = options || {}; + var type = "GET"; + var data= null; + if (options["keys"]) { + type = "POST"; + var keys = options["keys"]; + delete options["keys"]; + data = toJSON({ "keys": keys }); + } + ajax({ + type: type, + data: data, + url: this.uri + "_design/" + name[0] + + "/_view/" + name[1] + encodeOptions(options) + }, + options, "An error occurred accessing the view" + ); + }, + getDbProperty: function(propName, options, ajaxOptions) { + ajax({url: this.uri + propName + encodeOptions(options)}, + options, + "The property could not be retrieved", + ajaxOptions + ); + }, + + setDbProperty: function(propName, propValue, options, ajaxOptions) { + ajax({ + type: "PUT", + url: this.uri + propName + encodeOptions(options), + data : JSON.stringify(propValue) + }, + options, + "The property could not be updated", + ajaxOptions + ); + } + }; + }, + + encodeDocId: encodeDocId, + + info: function(options) { + ajax( + {url: this.urlPrefix + "/"}, + options, + "Server information could not be retrieved" + ); + }, + + replicate: function(source, target, ajaxOptions, repOpts) { + repOpts = $.extend({source: source, target: target}, repOpts); + if (repOpts.continuous) { + ajaxOptions.successStatus = 202; + } + ajax({ + type: "POST", url: this.urlPrefix + "/_replicate", + data: JSON.stringify(repOpts), + contentType: "application/json" + }, + ajaxOptions, + "Replication failed" + ); + }, + + newUUID: function(cacheNum) { + if (cacheNum === undefined) { + cacheNum = 1; + } + if (!uuidCache.length) { + ajax({url: this.urlPrefix + "/_uuids", data: {count: cacheNum}, async: false}, { + success: function(resp) { + uuidCache = resp.uuids + } + }, + "Failed to retrieve UUID batch." + ); + } + return uuidCache.shift(); + } + }); + + function ajax(obj, options, errorMessage, ajaxOptions) { + options = $.extend({successStatus: 200}, options); + ajaxOptions = $.extend({contentType: "application/json"}, ajaxOptions); + errorMessage = errorMessage || "Unknown error"; + $.ajax($.extend($.extend({ + type: "GET", dataType: "json", cache : !$.browser.msie, + beforeSend: function(xhr){ + if(ajaxOptions && ajaxOptions.headers){ + for (var header in ajaxOptions.headers){ + xhr.setRequestHeader(header, ajaxOptions.headers[header]); + } + } + }, + complete: function(req) { + try { + var resp = $.httpData(req, "json"); + } catch(e) { + if (options.error) { + options.error(req.status, req, e); + } else { + alert(errorMessage + ": " + e); + } + return; + } + if (options.ajaxStart) { + options.ajaxStart(resp); + } + if (req.status == options.successStatus) { + if (options.beforeSuccess) options.beforeSuccess(req, resp); + if (options.success) options.success(resp); + } else if (options.error) { + options.error(req.status, resp && resp.error || errorMessage, resp && resp.reason || "no response"); + } else { + alert(errorMessage + ": " + resp.reason); + } + } + }, obj), ajaxOptions)); + } + + function fullCommit(options) { + var options = options || {}; + if (typeof options.ensure_full_commit !== "undefined") { + var commit = options.ensure_full_commit; + delete options.ensure_full_commit; + return function(xhr) { + xhr.setRequestHeader("X-Couch-Full-Commit", commit.toString()); + }; + } + }; + + // Convert a options object to an url query string. + // ex: {key:'value',key2:'value2'} becomes '?key="value"&key2="value2"' + function encodeOptions(options) { + var buf = []; + if (typeof(options) === "object" && options !== null) { + for (var name in options) { + if ($.inArray(name, ["error", "success", "beforeSuccess", "ajaxStart"]) >= 0) + continue; + var value = options[name]; + if ($.inArray(name, ["key", "startkey", "endkey"]) >= 0) { + value = toJSON(value); + } + buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); + } + } + return buf.length ? "?" + buf.join("&") : ""; + } + + function toJSON(obj) { + return obj !== null ? JSON.stringify(obj) : null; + } + +})(jQuery); diff --git a/rel/overlay/share/www/script/jquery.dialog.js b/rel/overlay/share/www/script/jquery.dialog.js new file mode 100644 index 00000000..02c0c497 --- /dev/null +++ b/rel/overlay/share/www/script/jquery.dialog.js @@ -0,0 +1,96 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + + $.fn.centerBox = function() { + return this.each(function() { + var s = this.style; + s.left = (($(window).width() - $(this).width()) / 2) + "px"; + s.top = (($(window).height() - $(this).height()) / 2) + "px"; + }); + } + + $.showDialog = function(url, options) { + options = options || {}; + options.load = options.load || function() {}; + options.cancel = options.cancel || function() {}; + options.validate = options.validate || function() { return true }; + options.submit = options.submit || function() {}; + + var overlay = $('
        ') + .css("opacity", "0"); + var dialog = $(''); + if ($.browser.msie) { + var frame = $('') + .css("opacity", "0").appendTo(document.body); + if (parseInt($.browser.version)<7) { + dialog.css("position", "absolute"); + overlay.css("position", "absolute"); + $("html,body").css({width: "100%", height: "100%"}); + } + } + overlay.appendTo(document.body).fadeTo(100, 0.6); + dialog.appendTo(document.body).addClass("loading").centerBox().fadeIn(400); + + $(document).keydown(function(e) { + if (e.keyCode == 27) dismiss(); // dismiss on escape key + }); + function dismiss() { + dialog.fadeOut("fast", function() { + $("#dialog, #overlay, #overlay-frame").remove(); + }); + $(document).unbind("keydown"); + } + overlay.click(function() { dismiss(); }); + + function showError(name, message) { + var input = dialog.find(":input[name=" + name + "]"); + input.addClass("error").next("div.error").remove(); + $('
        ').text(message).insertAfter(input); + } + + $.get(url, function(html) { + $(html).appendTo(dialog); + dialog.removeClass("loading").addClass("loaded").centerBox().each(function() { + options.load(dialog.children()[0]); + $(":input:first", dialog).each(function() { this.focus() }); + $("button.cancel", dialog).click(function() { // dismiss on cancel + dismiss(); + options.cancel(); + }); + $("form", dialog).submit(function(e) { // invoke callback on submit + e.preventDefault(); + dialog.find("div.error").remove().end().find(".error").removeClass("error"); + var data = {}; + $.each($("form :input", dialog).serializeArray(), function(i, field) { + data[field.name] = field.value; + }); + $("form :file", dialog).each(function() { + data[this.name] = this.value; // file inputs need special handling + }); + options.submit(data, function callback(errors) { + if ($.isEmptyObject(errors)) { + dismiss(); + } else { + for (var name in errors) { + showError(name, errors[name]); + } + } + }); + return false; + }); + }); + }); + } + +})(jQuery); diff --git a/rel/overlay/share/www/script/jquery.editinline.js b/rel/overlay/share/www/script/jquery.editinline.js new file mode 100644 index 00000000..b48607d4 --- /dev/null +++ b/rel/overlay/share/www/script/jquery.editinline.js @@ -0,0 +1,114 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +(function($) { + + function startEditing(elem, options) { + var editable = $(elem); + var origHtml = editable.html(); + var origText = options.populate($.trim(editable.text())); + + if (!options.begin.apply(elem, [origText])) { + return; + } + + var input = options.createInput.apply(elem, [origText]) + .addClass("editinline").val(origText) + .dblclick(function() { return false; }) + .keydown(function(evt) { + switch (evt.keyCode) { + case 13: { // return + if (!input.is("textarea")) applyChange(evt.keyCode); + break; + } + case 27: { // escape + cancelChange(evt.keyCode); + break; + } + case 9: { // tab + if (!input.is("textarea")) { + applyChange(evt.keyCode); + return false; + } + } + } + }); + if (options.acceptOnBlur) { + input.blur(function() { + return applyChange(); + }); + } + + function applyChange(keyCode) { + var newText = input.val(); + if (newText == origText) { + cancelChange(keyCode); + return true; + } + if ((!options.allowEmpty && !newText.length) || + !options.validate.apply(elem, [newText, origText])) { + input.addClass("invalid"); + return false; + } + input.remove(); + tools.remove(); + options.accept.apply(elem, [newText, origText]); + editable.removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + return true; + } + + function cancelChange(keyCode) { + options.cancel.apply(elem, [origText]); + editable.html(origHtml).removeClass("editinline-container"); + options.end.apply(elem, [keyCode]); + } + + var tools = $(""); + $("") + .text(options.acceptLabel).click(applyChange).appendTo(tools); + $("") + .text(options.cancelLabel).click(cancelChange).appendTo(tools) + + editable.html("").append(tools).append(input) + .addClass("editinline-container"); + options.prepareInput.apply(elem, [input[0]]); + input.each(function() { this.focus(); this.select(); }); + } + + $.fn.makeEditable = function(options) { + options = $.extend({ + allowEmpty: true, + acceptLabel: "", + cancelLabel: "", + toolTip: "Double click to edit", + acceptOnBlur: true, + + // callbacks + begin: function() { return true }, + accept: function(newValue, oldValue) {}, + cancel: function(oldValue) {}, + createInput: function(value) { return $("") }, + prepareInput: function(input) {}, + end: function(keyCode) {}, + populate: function(value) { return value }, + validate: function() { return true } + }, options || {}); + + return this.each(function() { + $(this).attr("title", options.toolTip).dblclick(function() { + startEditing(this, options); + }); + }); + } + +})(jQuery); diff --git a/rel/overlay/share/www/script/jquery.form.js b/rel/overlay/share/www/script/jquery.form.js new file mode 100644 index 00000000..dde39427 --- /dev/null +++ b/rel/overlay/share/www/script/jquery.form.js @@ -0,0 +1,660 @@ +/* + * jQuery Form Plugin + * version: 2.36 (07-NOV-2009) + * @requires jQuery v1.2.6 or later + * + * Examples and documentation at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ +;(function($) { + +/* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are intended to be exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').bind('submit', function() { + $(this).ajaxSubmit({ + target: '#output' + }); + return false; // <-- important! + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + +/** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ +$.fn.ajaxSubmit = function(options) { + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log('ajaxSubmit: skipping submit process - no element selected'); + return this; + } + + if (typeof options == 'function') + options = { success: options }; + + var url = $.trim(this.attr('action')); + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/)||[])[1]; + } + url = url || window.location.href || ''; + + options = $.extend({ + url: url, + type: this.attr('method') || 'GET', + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); + return this; + } + + // provide opportunity to alter form data before it is serialized + if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSerialize callback'); + return this; + } + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) { + if(options.data[n] instanceof Array) { + for (var k in options.data[n]) + a.push( { name: n, value: options.data[n][k] } ); + } + else + a.push( { name: n, value: options.data[n] } ); + } + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { + log('ajaxSubmit: submit aborted via beforeSubmit callback'); + return this; + } + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) { + log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); + return this; + } + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i].apply(options, [data, status, $form]); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + var multipart = false; +// var mp = 'multipart/form-data'; +// multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if ((files.length && options.iframe !== false) || options.iframe || found || multipart) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + + if ($(':input[name=submit]', form).length) { + alert('Error: Form elements must not be named "submit".'); + return; + } + + var opts = $.extend({}, $.ajaxSettings, options); + var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('