diff options
Diffstat (limited to 'share')
52 files changed, 7989 insertions, 0 deletions
diff --git a/share/Makefile.am b/share/Makefile.am new file mode 100644 index 00000000..afd37f14 --- /dev/null +++ b/share/Makefile.am @@ -0,0 +1,56 @@ +## 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. + +datarootdir = @prefix@/share + +nobase_dist_pkgdata_DATA = \ + server/main.js \ + www/browse/database.html \ + www/browse/document.html \ + www/browse/index.html \ + www/couch_tests.html \ + www/favicon.ico \ + www/image/add.gif \ + www/image/apply.gif \ + www/image/bg.png \ + www/image/cancel.gif \ + www/image/delete-mini.gif \ + www/image/delete.gif \ + www/image/hgrad.gif \ + www/image/load.gif \ + www/image/logo.png \ + www/image/order-asc.gif \ + www/image/order-desc.gif \ + www/image/path.gif \ + www/image/run-mini.gif \ + www/image/run.gif \ + www/image/running.gif \ + www/image/save.gif \ + www/image/test_failure.gif \ + www/image/test_success.gif \ + www/image/thead.gif \ + www/image/thead-key.gif \ + www/image/toggle-collapse.gif \ + www/image/toggle-expand.gif \ + www/image/twisty.gif \ + www/index.html \ + www/replicator.html \ + www/script/browse.js \ + www/script/couch.js \ + www/script/couch_tests.js \ + www/script/jquery.js \ + www/script/jquery.cookies.js \ + www/script/json2.js \ + www/script/pprint.js \ + www/script/shell.js \ + www/shell.html \ + www/style/layout.css diff --git a/share/server/main.js b/share/server/main.js new file mode 100644 index 00000000..91e13742 --- /dev/null +++ b/share/server/main.js @@ -0,0 +1,165 @@ +// 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. + +var cmd; +var map_funs = []; // The map functions to compute against documents +var map_results = []; + +try { + var sandbox = evalcx(''); + sandbox.map = function(key, value) { + map_results.push([key, value]); + } +} catch (e) { + // fallback for older versions of spidermonkey that don't have evalcx + var sandbox = null; + map = function(key, value) { + map_results.push([key, value]); + } +} + +// Commands are in the form of json arrays: +// ["commandname",..optional args...]\n +// +// Responses are json values followed by a new line ("\n") + +while (cmd = eval(readline())) { + switch (cmd[0]) { + case "reset": + // clear the map_functions and run gc + map_funs = []; + gc(); + print("true"); // indicates success + break; + case "add_fun": + // The second arg is a string that will compile to a function. + // and then we add it to map_functions array + try { + var functionObject = sandbox ? evalcx(cmd[1], sandbox) : eval(cmd[1]); + } catch (err) { + print(toJSON({error: {id: "map_compilation_error", + reason: err.toString() + " (" + toJSON(cmd[1]) + ")"}})); + break; + } + if (typeof(functionObject) == "function") { + map_funs.push(functionObject); + print("true"); + } else { + print(toJSON({error: "map_compilation_error", + reason: "expression does not eval to a function. (" + cmd[1] + ")"})); + } + break; + case "map_doc": + // The second arg is a document. We compute all the map functions against + // it. + // + // Each function can output multiple keys value, pairs for each document + // + // Example output of map_doc after three functions set by add_fun cmds: + // [ + // [["Key","Value"]], <- fun 1 returned 1 key value + // [], <- fun 2 returned 0 key values + // [["Key1","Value1"],["Key2","Value2"]],<- fun 3 returned 2 key values + // ] + // + var doc = cmd[1]; + seal(doc); // seal to prevent map functions from changing doc + var buf = []; + for (var i = 0; i < map_funs.length; i++) { + map_results = []; + try { + map_funs[i](doc); + buf.push(map_results.filter(function(pair) { + return pair[0] !== undefined && pair[1] !== undefined; + })); + } catch (err) { + if (err == "fatal_error") { + // Only if it's a "fatal_error" do we exit. What's a fatal error? + // That's for the query to decide. + // + // This will make it possible for queries to completely error out, + // by catching their own local exception and rethrowing a + // fatal_error. But by default if they don't do error handling we + // just eat the exception and carry on. + print(toJSON({error: "map_runtime_error", + reason: "function raised fatal exception"})); + quit(); + } + print(toJSON({log: "function raised exception (" + err + ")"})); + buf.push([]); + } + } + print(toJSON(buf)); + break; + default: + print(toJSON({error: "query_server_error", + reason: "unknown command '" + cmd[0] + "'"})); + quit(); + } +} + +function toJSON(val) { + if (typeof(val) == "undefined") { + throw new TypeError("Cannot encode undefined value as JSON"); + } + var subs = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', + '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; + if (typeof(val) == "xml") { // E4X support + val = val.toXMLString(); + } + return { + "Array": function(v) { + var buf = []; + for (var i = 0; i < v.length; i++) { + buf.push(toJSON(v[i])); + } + return "[" + buf.join(",") + "]"; + }, + "Boolean": function(v) { + return v.toString(); + }, + "Date": function(v) { + var f = function(n) { return n < 10 ? '0' + n : n } + return '"' + v.getUTCFullYear() + '-' + + f(v.getUTCMonth() + 1) + '-' + + f(v.getUTCDate()) + 'T' + + f(v.getUTCHours()) + ':' + + f(v.getUTCMinutes()) + ':' + + f(v.getUTCSeconds()) + 'Z"'; + }, + "Number": function(v) { + return isFinite(v) ? v.toString() : "null"; + }, + "Object": function(v) { + if (v === null) return "null"; + var buf = []; + for (var k in v) { + if (!v.hasOwnProperty(k) || typeof(k) !== "string" || v[k] === undefined) { + continue; + } + buf.push(toJSON(k, val) + ": " + toJSON(v[k])); + } + return "{" + buf.join(",") + "}"; + }, + "String": function(v) { + if (/["\\\x00-\x1f]/.test(v)) { + v = v.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = subs[b]; + if (c) return c; + c = b.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }); + } + return '"' + v + '"'; + } + }[val != null ? val.constructor.name : "Object"](val); +} diff --git a/share/www/browse/_create_database.html b/share/www/browse/_create_database.html new file mode 100644 index 00000000..5c909a7a --- /dev/null +++ b/share/www/browse/_create_database.html @@ -0,0 +1,33 @@ +<!-- + +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. + +--> +<form action="" method="post"> + <h2>Create New Database</h2> + <fieldset> + <p class="help"> + Please enter the name of the database. Note that only lowercase + characters (<tt>a-z</tt>), digits (<tt>0-9</tt>), or any of the + characters <tt>_</tt>, <tt>$</tt>, <tt>(</tt>, <tt>)</tt>, <tt>+</tt>, + <tt>-</tt>, and <tt>/</tt> are allowed. + </p> + <table summary=""><tbody><tr> + <th><label>Database Name:</label></th> + <td><input type="text" name="name" size="24"></td> + </tr></table> + </fieldset> + <div class="buttons"> + <button type="submit">Create</button> + <button type="button" class="cancel">Cancel</button> + </div> +</form> diff --git a/share/www/browse/_create_document.html b/share/www/browse/_create_document.html new file mode 100644 index 00000000..fc9eef02 --- /dev/null +++ b/share/www/browse/_create_document.html @@ -0,0 +1,31 @@ +<!-- + +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. + +--> +<form action="" method="post"> + <h2>Create New Document</h2> + <fieldset> + <p class="help"> + Please enter a unique ID of the document, or leave the field empty to get + an auto-generated ID. + </p> + <table summary=""><tbody><tr> + <th><label>Document ID:</label></th> + <td><input type="text" name="docid" size="32"></td> + </tr></table> + </fieldset> + <div class="buttons"> + <button type="submit">Create</button> + <button type="button" class="cancel">Cancel</button> + </div> +</form> diff --git a/share/www/browse/_delete_database.html b/share/www/browse/_delete_database.html new file mode 100644 index 00000000..16be8f30 --- /dev/null +++ b/share/www/browse/_delete_database.html @@ -0,0 +1,27 @@ +<!-- + +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. + +--> +<form action="" method="post"> + <h2>Delete Database</h2> + <fieldset> + <p class="help"> + Are you sure you want to delete this database? Note that this is an + <strong>irreversible operation</strong>! + </p> + </fieldset> + <div class="buttons"> + <button type="submit">Delete</button> + <button type="button" class="cancel">Cancel</button> + </div> +</form> diff --git a/share/www/browse/_delete_document.html b/share/www/browse/_delete_document.html new file mode 100644 index 00000000..6b5497a4 --- /dev/null +++ b/share/www/browse/_delete_document.html @@ -0,0 +1,26 @@ +<!-- + +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. + +--> +<form action="" method="post"> + <h2>Delete Document</h2> + <fieldset> + <p class="help"> + Are you sure you want to delete this document? + </p> + </fieldset> + <div class="buttons"> + <button type="submit">Delete</button> + <button type="button" class="cancel">Cancel</button> + </div> +</form> diff --git a/share/www/browse/_save_view_as.html b/share/www/browse/_save_view_as.html new file mode 100644 index 00000000..a7f10785 --- /dev/null +++ b/share/www/browse/_save_view_as.html @@ -0,0 +1,35 @@ +<!-- + +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. + +--> +<form action="" method="post" id="view-save-as" onload="initForm(this)"> + <h2>Save View As…</h2> + <fieldset> + <p class="help"> + You can save this function code as a permanent view in the database. Just + enter or select the design document and the name of the view below. Note + that if you choose an existing view, it will be overwritten! + </p> + <table summary=""><tbody><tr> + <th><label for="input_docid">Design Document:</label></th> + <td><tt>_design/</tt><input type="text" id="input_docid" name="docid" size="20"></td> + </tr><tr> + <th><label for="input_name">View Name:<label></th> + <td><input type="text" id="input_name" name="name" size="30"></td> + </tr></table> + </fieldset> + <div class="buttons"> + <button type="submit">Save</button> + <button type="button" class="cancel">Cancel</button> + </div> +</form> diff --git a/share/www/browse/database.html b/share/www/browse/database.html new file mode 100644 index 00000000..33041bd7 --- /dev/null +++ b/share/www/browse/database.html @@ -0,0 +1,151 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>Browse Database</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="../style/layout.css" type="text/css"> + <script src="../script/json2.js"></script> + <script src="../script/jquery.js"></script> + <script src="../script/jquery.cookies.js"></script> + <script src="../script/jquery.dialog.js"></script> + <script src="../script/jquery.resizer.js"></script> + <script src="../script/jquery.suggest.js"></script> + <script src="../script/couch.js"></script> + <script src="../script/browse.js"></script> + <script src="../script/pprint.js"></script> + <script> + var page = new CouchDatabasePage(); + if (window != parent) { + parent.updateNavigation(location.pathname, location.search.split("/", 2)[0]); + } + + $(document).ready(function() { + $("h1 strong").text(page.dbName); + $("#viewcode label").click(function() { + $("#viewcode").toggleClass("expanded"); + }); + $("#viewcode button.run").click(function() { + page.updateDocumentListing(); + }); + $("#viewcode button.revert").click(function() { + page.revertViewChanges(); + }); + $("#viewcode button.save").click(function() { + page.saveViewChanges(); + }); + $("#viewcode button.saveas").click(function() { + page.saveViewAs(); + }); + $("#viewcode textarea").resizable({grippie: $("#viewcode .bottom")}); + + // Restore preferences/state from cookies + var query = $.cookies.get(page.dbName + ".query"); + if (query) $("#viewcode textarea").val(query); + var desc = $.cookies.get(page.dbName + ".desc"); + if (desc) $("#documents thead th.key").addClass("desc"); + var rowsPerPage = $.cookies.get(page.dbName + ".perpage"); + if (rowsPerPage) $("#perpage").val(rowsPerPage); + + page.populateViewsMenu(); + page.populateViewEditor(); + page.updateDocumentListing(); + + $("#switch select").change(function() { + var viewName = $(this).val(); + if (!viewName) $.cookies.remove(page.dbName + ".view"); + location.href = "?" + page.dbName + (viewName ? "/" + viewName : ""); + }); + $("#documents thead th.key").click(function() { + $(this).toggleClass("desc"); + page.updateDocumentListing(); + }); + $("#perpage").change(function() { + page.updateDocumentListing(); + $.cookies.set(page.dbName + ".perpage", this.value); + }); + $("#toolbar button.add").click(page.addDocument); + $("#toolbar button.delete").click(page.deleteDatabase); + }); + </script> + </head> + + <body> + <h1> + <a href="index.html">Overview</a> + <strong>?</strong> + </h1> + <div id="wrap"> + <div id="switch"> + <label>Select view: <select> + <option value="">All documents</option> + <option value="_design_docs">Design documents</option> + <option value="_temp_view">Custom query…</option> + </select></label> + </div> + <ul id="toolbar"> + <li><button class="add">Create Document …</button></li> + <li><button class="delete">Delete Database</button></li> + </ul> + + <div id="viewcode" style="display: none"> + <div class="top"> + <a id="designdoc-link"></a> + <label for="viewcode_textarea">View Function</label> + </div> + <textarea id="viewcode_textarea" rows="5" cols="79" spellcheck="false" wrap="off">function(doc) { + map(null, doc); +}</textarea> + <div class="bottom"> + <button class="save" type="button" disabled>Save</button> + <button class="saveas" type="button">Save As…</button> + <button class="revert" type="button" disabled>Revert</button> + <button class="run" type="button">Run</button> + </div> + </div> + + <table id="documents" class="listing" cellspacing="0"> + <caption>Documents</caption> + <thead> + <tr> + <th class="key"><div>Key</div></th> + <th class="value">Value</th> + </tr> + </thead> + <tbody class="content"> + </tbody> + <tbody class="footer"> + <tr> + <td colspan="4"> + <div id="paging"> + <a class="prev">← Previous Page</a> | + <label>Rows per page: <select id="perpage"> + <option selected>10</option> + <option>25</option> + <option>50</option> + <option>100</option> + </select></label> | + <a class="next">Next Page →</a> + </div> + <span></span> + </td> + </tr> + </tbody> + </table> + + </div> + </body> +</html> diff --git a/share/www/browse/document.html b/share/www/browse/document.html new file mode 100644 index 00000000..f7876539 --- /dev/null +++ b/share/www/browse/document.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>View Document</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="../style/layout.css" type="text/css"> + <script src="../script/json2.js"></script> + <script src="../script/jquery.js"></script> + <script src="../script/jquery.cookies.js"></script> + <script src="../script/jquery.dialog.js"></script> + <script src="../script/jquery.resizer.js"></script> + <script src="../script/couch.js"></script> + <script src="../script/browse.js"></script> + <script src="../script/pprint.js"></script> + <script> + var page = new CouchDocumentPage(); + if (window != parent) { + var dbLink = $("h1 a.dbname").get(0); + parent.updateNavigation( + location.pathname.replace(/document\.html/, "database.html"), + location.search.split("/")[0] + ); + } + + $(function() { + $("h1 a.dbname").text(page.dbName).attr("href", "database.html?" + page.dbName); + $("h1 strong").text(page.doc._id); + page.updateFieldListing(); + $("#toolbar button.save").click(page.saveDocument); + $("#toolbar button.add").click(page.addField); + $("#toolbar button.delete").click(page.deleteDocument); + }); + </script> + </head> + + <body> + <h1> + <a href="index.html">Overview</a> + <a class="dbname" href="#">?</a> + <strong>?</strong> + </h1> + <div id="wrap"> + <ul id="toolbar"> + <li><button class="save">Save Document</button></li> + <li><button class="add">Add Field</button></li> + <li><button class="delete">Delete Document</button></li> + </ul> + + <table id="fields" class="listing" cellspacing="0"> + <col class="field"><col class="value"> + <caption>Fields</caption> + <thead> + <tr> + <th>Field</th> + <th>Value</th> + </tr> + </thead> + <tbody class="content"> + </tbody> + <tbody class="footer"> + <tr> + <td colspan="2"> + <div id="paging"> + <a class="prev">← Previous Version</a> | <a class="next">Next Version →</a> + </div> + <span></span> + </td> + </tr> + </tbody> + </table> + + </div> + </body> +</html> diff --git a/share/www/browse/index.html b/share/www/browse/index.html new file mode 100644 index 00000000..bc901dcf --- /dev/null +++ b/share/www/browse/index.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>Welcome</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="../style/layout.css" type="text/css"> + <script src="../script/json2.js"></script> + <script src="../script/jquery.js"></script> + <script src="../script/jquery.cookies.js"></script> + <script src="../script/jquery.dialog.js"></script> + <script src="../script/couch.js"></script> + <script src="../script/browse.js"></script> + <script> + var page = new CouchIndexPage(); + $(document).ready(function() { + page.updateDatabaseListing(); + $("#toolbar button.add").click(function() { + page.addDatabase(); + }); + if (window != parent) parent.updateNavigation(); + }); + </script> + </head> + <body> + <h1><strong>Overview</strong></h1> + <div id="wrap"> + <ul id="toolbar"> + <li><button class="add">Create Database …</button></li> + </ul> + + <table class="listing" id="databases" cellspacing="0"> + <caption>Databases</caption> + <thead> + <tr> + <th>Name</th> + <th>Number of Documents</th> + <th>Update Seq</th> + </tr> + </thead> + <tbody class="content"> + </tbody> + <tbody class="footer"> + <tr> + <td colspan="3"></td> + </tr> + </tbody> + </table> + + </div> + </body> +</html> diff --git a/share/www/couch_tests.html b/share/www/couch_tests.html new file mode 100644 index 00000000..a1e4ee6e --- /dev/null +++ b/share/www/couch_tests.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>Test Suite</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="style/layout.css" type="text/css"> + <script src="script/json2.js"></script> + <script src="script/jquery.js"></script> + <script src="script/couch.js"></script> + <script src="script/pprint.js"></script> + <script> + $(document).ready(function() { + $("#toolbar button.load").click(function() { + location.reload(true); + }); + }); + </script> + <script src="script/couch_tests.js"></script> + <script> + $(document).ready(function() { + updateTestsListing(); + $("#toolbar button.run").click(runAllTests); + if (window != parent) parent.updateNavigation(); + }); + </script> + </head> + <body> + <h1> + <a href="browse/index.html">Overview</a> + <strong>Test Suite</strong> + </h1> + <div id="wrap"> + <ul id="toolbar"> + <li><button class="run">Run All</button></li> + <li><button class="load">Reload</button></li> + </ul> + <table class="listing" id="tests" cellspacing="0"> + <caption>Tests</caption> + <thead> + <tr> + <th class="name">Name</th> + <th class="status">Status</th> + <th class="duration">Elapsed Time</th> + <th class="details">Details</th> + </tr> + </thead> + <tbody class="content"> + </tbody> + <tbody class="footer"> + <tr> + <td colspan="4"></td> + </tr> + </tbody> + </table> + </div> + </body> +</html> diff --git a/share/www/favicon.ico b/share/www/favicon.ico Binary files differnew file mode 100644 index 00000000..34bfaa86 --- /dev/null +++ b/share/www/favicon.ico diff --git a/share/www/image/add.gif b/share/www/image/add.gif Binary files differnew file mode 100644 index 00000000..9a65fc80 --- /dev/null +++ b/share/www/image/add.gif diff --git a/share/www/image/apply.gif b/share/www/image/apply.gif Binary files differnew file mode 100644 index 00000000..63de0d53 --- /dev/null +++ b/share/www/image/apply.gif diff --git a/share/www/image/bg.png b/share/www/image/bg.png Binary files differnew file mode 100644 index 00000000..844add78 --- /dev/null +++ b/share/www/image/bg.png diff --git a/share/www/image/cancel.gif b/share/www/image/cancel.gif Binary files differnew file mode 100644 index 00000000..4329076e --- /dev/null +++ b/share/www/image/cancel.gif diff --git a/share/www/image/delete-mini.gif b/share/www/image/delete-mini.gif Binary files differnew file mode 100644 index 00000000..b2090067 --- /dev/null +++ b/share/www/image/delete-mini.gif diff --git a/share/www/image/delete.gif b/share/www/image/delete.gif Binary files differnew file mode 100644 index 00000000..0e58475f --- /dev/null +++ b/share/www/image/delete.gif diff --git a/share/www/image/grippie.gif b/share/www/image/grippie.gif Binary files differnew file mode 100644 index 00000000..a8807896 --- /dev/null +++ b/share/www/image/grippie.gif diff --git a/share/www/image/hgrad.gif b/share/www/image/hgrad.gif Binary files differnew file mode 100644 index 00000000..08aa80ca --- /dev/null +++ b/share/www/image/hgrad.gif diff --git a/share/www/image/load.gif b/share/www/image/load.gif Binary files differnew file mode 100644 index 00000000..e06762e9 --- /dev/null +++ b/share/www/image/load.gif diff --git a/share/www/image/logo.png b/share/www/image/logo.png Binary files differnew file mode 100644 index 00000000..cbe991cd --- /dev/null +++ b/share/www/image/logo.png diff --git a/share/www/image/order-asc.gif b/share/www/image/order-asc.gif Binary files differnew file mode 100644 index 00000000..d2a237ae --- /dev/null +++ b/share/www/image/order-asc.gif diff --git a/share/www/image/order-desc.gif b/share/www/image/order-desc.gif Binary files differnew file mode 100644 index 00000000..1043b499 --- /dev/null +++ b/share/www/image/order-desc.gif diff --git a/share/www/image/path.gif b/share/www/image/path.gif Binary files differnew file mode 100644 index 00000000..98545488 --- /dev/null +++ b/share/www/image/path.gif diff --git a/share/www/image/run-mini.gif b/share/www/image/run-mini.gif Binary files differnew file mode 100644 index 00000000..6256ef1d --- /dev/null +++ b/share/www/image/run-mini.gif diff --git a/share/www/image/run.gif b/share/www/image/run.gif Binary files differnew file mode 100644 index 00000000..386ee04a --- /dev/null +++ b/share/www/image/run.gif diff --git a/share/www/image/running.gif b/share/www/image/running.gif Binary files differnew file mode 100644 index 00000000..b7924420 --- /dev/null +++ b/share/www/image/running.gif diff --git a/share/www/image/save.gif b/share/www/image/save.gif Binary files differnew file mode 100644 index 00000000..fb713ca7 --- /dev/null +++ b/share/www/image/save.gif diff --git a/share/www/image/spinner.gif b/share/www/image/spinner.gif Binary files differnew file mode 100644 index 00000000..d84f6537 --- /dev/null +++ b/share/www/image/spinner.gif diff --git a/share/www/image/test_failure.gif b/share/www/image/test_failure.gif Binary files differnew file mode 100644 index 00000000..2a873b24 --- /dev/null +++ b/share/www/image/test_failure.gif diff --git a/share/www/image/test_success.gif b/share/www/image/test_success.gif Binary files differnew file mode 100644 index 00000000..6df8bae2 --- /dev/null +++ b/share/www/image/test_success.gif diff --git a/share/www/image/thead-key.gif b/share/www/image/thead-key.gif Binary files differnew file mode 100644 index 00000000..42a43b58 --- /dev/null +++ b/share/www/image/thead-key.gif diff --git a/share/www/image/thead.gif b/share/www/image/thead.gif Binary files differnew file mode 100644 index 00000000..1587b1f2 --- /dev/null +++ b/share/www/image/thead.gif diff --git a/share/www/image/toggle-collapse.gif b/share/www/image/toggle-collapse.gif Binary files differnew file mode 100644 index 00000000..f0979304 --- /dev/null +++ b/share/www/image/toggle-collapse.gif diff --git a/share/www/image/toggle-expand.gif b/share/www/image/toggle-expand.gif Binary files differnew file mode 100644 index 00000000..03fa8360 --- /dev/null +++ b/share/www/image/toggle-expand.gif diff --git a/share/www/image/twisty.gif b/share/www/image/twisty.gif Binary files differnew file mode 100644 index 00000000..5ba57a1a --- /dev/null +++ b/share/www/image/twisty.gif diff --git a/share/www/index.html b/share/www/index.html new file mode 100644 index 00000000..8616989a --- /dev/null +++ b/share/www/index.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>Apache CouchDB: Futon Utility Client</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="style/layout.css" type="text/css"> + <style type="text/css"> + html { height: 100%; overflow: hidden; } + body { background: #fff url(image/bg.png) 100% 0 repeat-y; + padding: 0 185px 0 0; height: 100%; overflow: hidden; + } + * html body { padding-right: 210px; } + iframe { background: transparent; border: none; width: 100%; height: 100%; } + </style> + <script src="script/json2.js"></script> + <script src="script/jquery.js"></script> + <script src="script/couch.js"></script> + <script> + function updateDatabaseList() { + var dbs = CouchDB.allDbs(); + $("#dbs").empty(); + for (var i = 0; i < dbs.length; i++) { + var dbname = dbs[i]; + $("#dbs").append("<li><a href='browse/database.html?" + dbname + + "' target='content'>" + dbname + "</a></li>"); + } + } + function updateNavigation(path, queryString) { + function fixupPath(path) { // hack for IE/Win + return (path.charAt(0) != "/") ? ("/" + path) : path; + } + if (path == null) { + var l = frames["content"].location; + path = l.pathname; + if (queryString == null) { + queryString = l.search; + } + } + var href = fixupPath(path + queryString); + $("#nav li li a").each(function() { + if (fixupPath(this.pathname) + this.search == href) { + $(this).parent("li").addClass("selected"); + } else { + $(this).parent("li").removeClass("selected"); + } + }); + $("#nav>li").each(function() { + if ($(this).is(":has(li.selected)")) { + $(this).addClass("selected"); + } else { + $(this).removeClass("selected"); + } + }); + } + $(function() { + $("#version").text(CouchDB.getVersion()); + updateDatabaseList(); + }); + </script> + </head> + <body> + <a href="browse/index.html" target="content"> + <img id="logo" src="image/logo.png" width="170" height="151" alt="Apache CouchDB: Relax"> + </a> + <ul id="nav"> + <li><span>Tools</span><ul> + <li><a href="browse/index.html" target="content">Overview</a></li> + <li><a href="shell.html" target="content">Javascript Shell</a></li> + <li><a href="replicator.html" target="content">Replicator</a></li> + <li><a href="couch_tests.html" target="content">Test Suite</a></li> + </ul></li> + <li><span>Databases</span> + <ul id="dbs"></ul> + </li> + </ul> + <div id="footer">Futon on Apache CouchDB <span id="version">?</span></div> + <div id="view"> + <iframe name="content" src="browse/index.html" allowtransparency="true"> + </div> + </body> +</html> diff --git a/share/www/replicator.html b/share/www/replicator.html new file mode 100644 index 00000000..36dc3d17 --- /dev/null +++ b/share/www/replicator.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en"> + <head> + <title>Replicator</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> + <link rel="stylesheet" href="style/layout.css" type="text/css"> + <script src="script/json2.js"></script> + <script src="script/jquery.js"></script> + <script src="script/couch.js"></script> + <script src="script/pprint.js"></script> + <script> + $(document).ready(function() { + if (window !== parent) parent.updateNavigation(); + + $("fieldset input[type=radio]").click(function() { + var radio = this; + var fieldset = $(this).parents("fieldset").get(0); + $("input[type=text]", fieldset).each(function() { + this.disabled = radio.value == "local"; + if (!this.disabled) this.focus(); + }); + $("select", fieldset).each(function() { + this.disabled = radio.value == "remote"; + if (!this.disabled) this.focus(); + }); + }); + + var allDbs = CouchDB.allDbs(); + $("fieldset select").each(function() { + for (var i = 0; i < allDbs.length; i++) { + $("<option></option>").text(allDbs[i]).appendTo(this); + } + this.selectedIndex = 0; + }); + + $("button#swap").click(function() { + var fromName = $("#source select").val(); + $("#source select").val($("#target select").val()); + $("#target select").val(fromName); + + var fromUrl = $("#source input[type=text]").val(); + $("#source input[type=text]").val($("#target input[type=text]").val()); + $("#target input[type=text]").val(fromUrl); + + var fromType = $("#source input[type=radio]").filter(function() { + return this.checked; + }).val(); + var toType = $("#target input[type=radio]").filter(function() { + return this.checked; + }).val(); + $("#source input[value=" + toType + "]").click(); + $("#target input[value=" + fromType + "]").click(); + + $("#replicate").get(0).focus(); + return false; + }); + + $("button#replicate").click(function() { + $("#records tbody.content").empty(); + var source = $("#from_local")[0].checked ? $("#from_name").val() : $("#from_url").val(); + var target = $("#to_local")[0].checked ? $("#to_name").val() : $("#to_url").val(); + try { + var results = CouchDB.replicate(source, target); + } catch (e) { + alert(e.reason); + return; + } + for (var i = 0; i < results.history.length; i++) { + var record = results.history[i]; + $("<tr><th></th><td class='seq'></td>" + + "<td class='read'></td><td class='copied'></td></tr>") + .find("th").text(record.start_time).end() + .find("td.seq").text(record.start_last_seq + "–" + record.end_last_seq).end() + .find("td.read").text(record.docs_read + " (" + record.read_errors + " errors)").end() + .find("td.copied").text(record.docs_copied + " (" + record.copy_errors + " errors)").end() + .appendTo("#records tbody.content"); + } + $("#records tbody tr").removeClass("odd").filter(":odd").addClass("odd"); + $("#records tbody.footer td").text("Replication session " + results.session_id); + }); + }); + </script> + </head> + <body> + <h1> + <a href="browse/index.html">Overview</a> + <strong>Replicator</strong> + </h1> + <div id="wrap"> + + <form id="replicator"> + <fieldset id="source"> + <legend>Replicate changes from:</legend> + <p> + <label><input type="radio" id="from_local" name="from_type" value="local" checked> Local</label> + <label>database: <select id="from_name" name="from_name"></select></label> + </p><p> + <label><input type="radio" id="from_to_remote" name="from_type" value="remote"> Remote</label> + <label>database: <input type="text" id="from_url" name="from_url" size="30" value="http://" disabled></label> + </p> + </fieldset> + <p class="swap"><button id="swap" tabindex="99">⇄</button></p> + <fieldset id="target"> + <legend>to:</legend> + <p> + <label><input type="radio" id="to_local" name="to_type" value="local" checked> Local</label> + <label>database: <select id="to_name" name="to_name"></select></label> + </p><p> + <label><input type="radio" id="to_remote" name="to_type" value="remote"> Remote</label> + <label>database: <input type="text" id="to_url" name="to_url" size="30" value="http://" disabled></label> + </p> + </fieldset> + <p class="actions"> + <button id="replicate" type="button">Replicate</button> + </p> + </form> + + <table id="records" class="listing"> + <caption>Replication History</caption> + <thead><tr> + <th>When</th> + <th>Sequences</th> + <th>Documents read</th> + <th>Documents copied</th> + </tr></thead> + <tbody class="content"></tbody> + <tbody class="footer"><tr> + <td colspan="4">No replication</td> + </tr></tbody> + </table> + + </div> + </body> +</html> diff --git a/share/www/script/browse.js b/share/www/script/browse.js new file mode 100644 index 00000000..c1127608 --- /dev/null +++ b/share/www/script/browse.js @@ -0,0 +1,632 @@ +// 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. + +/* + * Page class for browse/index.html + */ +function CouchIndexPage() { + page = this; + + this.addDatabase = function() { + $.showDialog("_create_database.html", { + submit: function(data) { + if (!data.name || data.name.length == 0) { + return {name: "Please enter a name."}; + } + try { + new CouchDB(data.name).createDb(); + } catch (e) { + return {name: e.reason}; + } + if (window !== parent) parent.setTimeout("updateDatabaseList()", 500); + window.open("database.html?" + data.name, "content"); + } + }); + return false; + } + + this.updateDatabaseListing = function() { + var allDbs = CouchDB.allDbs(); + for (var i = 0; i < allDbs.length; i++) { + var dbName = allDbs[i]; + var info = new CouchDB(dbName).info(); + $("#databases tbody.content").append( + "<tr><th><a href='database.html?" + dbName + "'>" + + dbName + "</a></th><td>" + info.doc_count +"</td><td>" + + info.update_seq + "</td></tr>"); + $("#databases tbody tr:odd").addClass("odd"); + $("#databases tbody.footer tr td").text(allDbs.length + " database(s)"); + } + } + +} + +/* + * Page class for browse/database.html + */ +function CouchDatabasePage() { + var urlParts = location.search.substr(1).split("/"); + var dbName = urlParts.shift(); + var viewName = (urlParts.length > 0) ? urlParts.join("/") : null; + if (!viewName) { + viewName = $.cookies.get(dbName + ".view") || ""; + } else { + $.cookies.set(dbName + ".view", viewName); + } + var db = new CouchDB(dbName); + + this.dbName = dbName; + this.viewName = viewName; + this.db = db; + this.isDirty = false; + page = this; + + this.addDocument = function() { + $.showDialog("_create_document.html", { + submit: function(data) { + try { + var result = db.save(data.docid ? {_id: data.docid} : {}); + } catch (err) { + return {docid: err.reason}; + } + location.href = "document.html?" + dbName + "/" + result.id; + } + }); + } + + this.deleteDatabase = function() { + $.showDialog("_delete_database.html", { + submit: function() { + db.deleteDb(); + location.href = "index.html"; + if (window !== null) { + parent.$("#dbs li").filter(function(index) { + return $("a", this).text() == dbName; + }).remove(); + } + } + }); + } + + this.populateViewEditor = function() { + if (viewName.match(/^_design\//)) { + page.revertViewChanges(); + var dirtyTimeout = null; + function updateDirtyState() { + clearTimeout(dirtyTimeout); + dirtyTimeout = setTimeout(function() { + var buttons = $("#viewcode button.save, #viewcode button.revert"); + page.isDirty = $("#viewcode textarea").val() != page.storedViewCode; + if (page.isDirty) { + buttons.removeAttr("disabled"); + } else { + buttons.attr("disabled", "disabled"); + } + }, 100); + } + $("#viewcode textarea").bind("input", updateDirtyState); + if ($.browser.msie) { // sorry, browser detection + $("#viewcode textarea").get(0).onpropertychange = updateDirtyState + } else if ($.browser.safari) { + $("#viewcode textarea").bind("paste", updateDirtyState) + .bind("change", updateDirtyState) + .bind("keydown", updateDirtyState) + .bind("keypress", updateDirtyState) + .bind("keyup", updateDirtyState) + .bind("textInput", updateDirtyState); + } + } + } + + this.populateViewsMenu = function() { + var designDocs = db.allDocs({startkey: "_design/", endkey: "_design/ZZZ"}); + $("#switch select").each(function() { + this.options.length = 3; + for (var i = 0; i < designDocs.rows.length; i++) { + var doc = db.open(designDocs.rows[i].id); + var optGroup = $("<optgroup></optgroup>").attr("label", doc._id.substr(8)); + for (var name in doc.views) { + if (!doc.views.hasOwnProperty(name)) continue; + $("<option></option>").attr("value", doc._id + "/" + name).text(name) + .appendTo(optGroup); + } + optGroup.appendTo(this); + } + this.autocomplete = false; + }); + } + + this.revertViewChanges = function() { + if (!page.storedViewCode) { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + var designDoc = db.open(["_design", designDocId].join("/")); + if (designDoc) { + page.storedViewCode = designDoc.views[localViewName]; + } + } + $("#viewcode textarea").val(page.storedViewCode); + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + } + + this.saveViewAs = function() { + if (viewName && /^_design/.test(viewName)) { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + } else { + var designDocId = "", localViewName = "" + } + $.showDialog("_save_view_as.html", { + load: function(elem) { + $("#input_docid", elem).val(designDocId).suggest(function(text, callback) { + var matches = []; + var docs = db.allDocs({ + count: 10, startkey: "_design/" + text, + endkey: "_design/" + text + "ZZZZ" + }); + for (var i = 0; i < docs.rows.length; i++) { + matches[i] = docs.rows[i].id.substr(8); + } + callback(matches); + }); + $("#input_name", elem).val(localViewName).suggest(function(text, callback) { + var matches = []; + try { + var doc = db.open("_design/" + $("#input_docid").val()); + } catch (err) { + return; + } + if (!doc || !doc.views) return; + for (var viewName in doc.views) { + if (!doc.views.hasOwnProperty(viewName) || !viewName.match("^" + text)) { + continue; + } + matches.push(viewName); + } + callback(matches); + }); + }, + submit: function(data) { + 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"; + return errors; + } + var viewCode = $("#viewcode textarea").val(); + var docId = ["_design", data.docid].join("/"); + var designDoc = db.open(docId); + if (!designDoc) designDoc = {_id: docId, language: "text/javascript"}; + if (designDoc.views === undefined) designDoc.views = {}; + designDoc.views[data.name] = viewCode; + db.save(designDoc); + page.isDirty = false; + location.href = "database.html?" + dbName + "/" + designDoc._id + "/" + data.name; + } + }); + } + + this.saveViewChanges = function() { + var viewNameParts = viewName.split("/"); + var designDocId = viewNameParts[1]; + var localViewName = viewNameParts[2]; + var designDoc = db.open(["_design", designDocId].join("/")); + var viewCode = $("#viewcode textarea").val(); + designDoc.views[localViewName] = viewCode; + db.save(designDoc); + page.isDirty = false; + $("#viewcode button.revert, #viewcode button.save").attr("disabled", "disabled"); + } + + this.updateDesignDocLink = function() { + if (viewName && /^_design/.test(viewName)) { + var docId = "_design/" + viewName.split("/")[1]; + $("#designdoc-link").attr("href", "document.html?" + dbName + "/" + docId).text(docId); + } else { + $("#designdoc-link").removeAttr("href").text(""); + } + } + + this.updateDocumentListing = function(options) { + if (options === undefined) options = {}; + if (options.count === undefined) { + options.count = parseInt($("#perpage").val(), 10); + } + if ($("#documents thead th.key").is(".desc")) { + options.descending = true; + $.cookies.set(dbName + ".desc", "1"); + } else { + if (options.descending !== undefined) delete options.descending; + $.cookies.remove(dbName + ".desc"); + } + $("#paging a").unbind(); + $("#documents tbody.content").empty(); + this.updateDesignDocLink(); + + var result = null; + if (!viewName) { + $("#switch select").get(0).selectedIndex = 0; + result = db.allDocs(options); + } else { + $("#switch select").each(function() { + for (var i = 0; i < this.options.length; i++) { + if (this.options[i].value == viewName) { + this.selectedIndex = i; + break; + } + } + }); + docs = []; + if (viewName == "_temp_view") { + $("#viewcode").show().addClass("expanded"); + var query = $("#viewcode textarea").val(); + $.cookies.set(db.name + ".query", query); + try { + result = db.query(query, options); + } catch (e) { + alert(e.reason ? e.reason : e.message); + return; + } + } else if (viewName == "_design_docs") { + options.startkey = options.descending ? "_design/ZZZZ" : "_design/"; + options.endkey = options.descending ? "_design/" : "_design/ZZZZ"; + result = db.allDocs(options); + } else { + $("#viewcode").show(); + var currentViewCode = $("#viewcode textarea").val(); + if (currentViewCode != page.storedViewCode) { + result = db.query(currentViewCode, options); + } else { + result = db.view(viewName.substr(8), options); + } + } + } + if (result.offset === undefined) { + result.offset = 0; + } + if (result.offset > 0) { + $("#paging a.prev").attr("href", "#" + (result.offset - options.count)).click(function() { + var firstDoc = result.rows[0]; + page.updateDocumentListing({ + startkey: firstDoc.key !== undefined ? firstDoc.key : null, + startkey_docid: firstDoc.id, + skip: 1, + count: -options.count + }); + return false; + }); + } else { + $("#paging a.prev").removeAttr("href"); + } + if (result.total_rows - result.offset > options.count) { + $("#paging a.next").attr("href", "#" + (result.offset + options.count)).click(function() { + var lastDoc = result.rows[result.rows.length - 1]; + page.updateDocumentListing({ + startkey: lastDoc.key !== undefined ? lastDoc.key : null, + startkey_docid: lastDoc.id, + skip: 1, + count: options.count + }); + return false; + }); + } else { + $("#paging a.next").removeAttr("href"); + } + + for (var i = 0; i < result.rows.length; i++) { + var row = result.rows[i]; + var tr = $("<tr></tr>"); + var key = row.key; + $("<td class='key'><a href='document.html?" + db.name + "/" + row.id + "'>" + + "<em></em><br><span class='docid'>ID: " + row.id + + "</span></a></td>").find("em").text( + key !== null ? prettyPrintJSON(key, 0, "") : "null" + ).end().appendTo(tr); + var value = row.value; + $("<td class='value'></td>").text( + value !== null ? prettyPrintJSON(value, 0, "") : "null" + ).appendTo(tr).dblclick(function() { + location.href = this.previousSibling.firstChild.href; + }); + tr.appendTo("#documents tbody.content"); + } + + $("#documents tbody tr:odd").addClass("odd"); + $("#documents tbody.footer td span").text( + "Showing " + Math.min(result.total_rows, result.offset + 1) + "-" + + (result.offset + result.rows.length) + " of " + result.total_rows + + " document" + (result.total_rows != 1 ? "s" : "")); + } + + 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/database.html + */ +function CouchDocumentPage() { + var urlParts = location.search.substr(1).split("/"); + var dbName = urlParts.shift(); + var idParts = urlParts.join("/").split("@", 2); + var docId = idParts[0]; + var docRev = (idParts.length > 1) ? idParts[1] : null; + var db = new CouchDB(dbName); + var doc = db.open(docId, {revs_info: true}); + var revs = doc._revs_info; + delete doc._revs_info; + if (docRev != null) { + try { + doc = db.open(docId, {rev: docRev}); + } catch (e) { + alert("The requested revision was not found. " + + "You will be redirected back to the latest revision."); + location.href = "?" + dbName + "/" + docId; + return; + } + } + + this.dbName = dbName; + this.db = db; + this.doc = doc; + this.isDirty = false; + page = this; + + this.addField = function() { + var fieldName = "unnamed"; + var fieldIdx = 1; + while (doc.hasOwnProperty(fieldName)) { + fieldName = "unnamed " + fieldIdx++; + } + doc[fieldName] = null; + var row = _addRowForField(fieldName); + page.isDirty = true; + _editKey(row.find("th"), fieldName); + } + + this.updateFieldListing = function() { + $("#fields tbody.content").empty(); + var propNames = []; + for (var prop in doc) { + if (!doc.hasOwnProperty(prop)) continue; + propNames.push(prop); + } + // Order properties alphabetically, but put internal fields first + propNames.sort(function(a, b) { + var a0 = a.charAt(0), b0 = b.charAt(0); + if (a0 == "_" && b0 != "_") { + return -1; + } else if (a0 != "_" && b0 == "_") { + return 1; + } else { + return a < b ? -1 : a != b ? 1 : 0; + } + }); + for (var pi = 0; pi < propNames.length; pi++) { + _addRowForField(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", "?" + dbName + "/" + docId + "@" + prevRev); + } + if (currentIndex > 0) { + var nextRev = revs[currentIndex - 1].rev; + $("#paging a.next").attr("href", "?" + dbName + "/" + docId + "@" + nextRev); + } + $("#fields tbody.footer td span").text("Showing revision " + + (revs.length - currentIndex) + " of " + revs.length); + } + } + + this.deleteDocument = function() { + $.showDialog("_delete_document.html", { + submit: function() { + db.deleteDoc(doc); + location.href = "database.html?" + dbName; + } + }); + } + + this.saveDocument = function() { + try { + db.save(doc); + } catch (e) { + alert(e.reason); + return; + } + page.isDirty = false; + location.href = "?" + dbName + "/" + docId; + } + + window.onbeforeunload = function() { + if (page.isDirty) { + return "You've made changes to this document that have not been " + + "saved yet."; + } + } + + function _addRowForField(fieldName) { + var value = _renderValue(doc[fieldName]); + var row = $("<tr><th></th><td></td></tr>") + .find("th").append($("<b></b>").text(fieldName)).dblclick(function() { + _editKey(this, $(this).text()); + }).end() + .find("td").append(value).dblclick(function() { + _editValue(this, $(this).prev("th").text()); + }).end() + .appendTo("#fields tbody.content"); + if (fieldName != "_id" && fieldName != "_rev") { + row.find("th, td").attr("title", "Double click to edit"); + _initKey(row, fieldName); + _initValue(value); + } + $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd"); + return row; + } + + function _editKey(cell, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") return; + var th = $(cell); + th.empty(); + var input = $("<input type='text' spellcheck='false'>"); + input.dblclick(function() { return false; }).keydown(function(evt) { + switch (evt.keyCode) { + case 13: applyChange(); break; + case 27: cancelChange(); break; + } + }); + var tools = $("<div class='tools'></div>"); + function applyChange() { + input.nextAll().remove(); + var newName = input.val(); + if (!newName.length || newName == fieldName) { + cancelChange(); + return; + } + doc[newName] = doc[fieldName]; + delete doc[fieldName]; + th.children().remove(); + th.append($("<b></b>").text(newName)); + _initKey(th.parent("tr"), fieldName); + page.isDirty = true; + } + function cancelChange() { + th.children().remove(); + th.append($("<b></b>").text(fieldName)); + _initKey(th.parent("tr"), fieldName); + } + + $("<button type='button' class='apply'></button>").click(function() { + applyChange(); + }).appendTo(tools); + $("<button type='button' class='cancel'></button>").click(function() { + cancelChange(); + }).appendTo(tools); + tools.appendTo(th); + input.val(fieldName).appendTo(th); + input.each(function() { this.focus(); this.select(); }); + } + + function _editValue(cell, fieldName) { + if (fieldName == "_id" || fieldName == "_rev") return; + var td = $(cell); + var value = doc[fieldName]; + var needsTextarea = $("dl", td).length > 0 || $("code", td).text().length > 60; + td.empty(); + if (needsTextarea) { + var input = $("<textarea rows='8' cols='40' spellcheck='false'></textarea>"); + } else { + var input = $("<input type='text' spellcheck='false'>"); + } + input.dblclick(function() { return false; }).keydown(function(evt) { + switch (evt.keyCode) { + case 13: if (!needsTextarea) applyChange(); break; + case 27: cancelChange(); break; + } + }); + var tools = $("<div class='tools'></div>"); + function applyChange() { + input.nextAll().remove(); + try { + var newValue = input.val() || "null"; + if (newValue == doc[fieldName]) { + cancelChange(); + return; + } + doc[fieldName] = JSON.parse(newValue); + td.children().remove(); + page.isDirty = true; + var value = _renderValue(doc[fieldName]); + td.append(value); + _initValue(value); + } catch (err) { + input.addClass("invalid"); + var msg = err.message; + if (msg == "parseJSON") { + msg = "Please enter a valid JSON value (for example, \"string\")."; + } + $("<div class='error'></div>").text(msg).insertAfter(input); + } + } + function cancelChange() { + td.children().remove(); + var value = _renderValue(doc[fieldName]); + td.append(value); + _initValue(value); + } + + $("<button type='button' class='apply'></button>").click(function() { + applyChange(); + }).appendTo(tools); + $("<button type='button' class='cancel'></button>").click(function() { + cancelChange(); + }).appendTo(tools); + tools.appendTo(td); + input.val(prettyPrintJSON(value)).appendTo(td); + input.each(function() { this.focus(); this.select(); }); + if (needsTextarea) input.resizable(); + } + + function _initKey(row, fieldName) { + if (fieldName != "_id" && fieldName != "_rev") { + $("<button type='button' class='delete' title='Delete field'></button>").click(function() { + delete doc[fieldName]; + row.remove(); + page.isDirty = true; + $("#fields tbody tr").removeClass("odd").filter(":odd").addClass("odd"); + }).prependTo(row.find("th")); + } + } + + function _initValue(value) { + value.find("dd").filter(":has(dl)").hide().prev("dt").addClass("collapsed"); + value.find("dd").not(":has(dl)").addClass("inline").prev().addClass("inline"); + value.find("dt.collapsed").click(function() { + $(this).toggleClass("collapsed").next().toggle(); + }); + } + + function _renderValue(value) { + var type = typeof(value); + if (type == "object" && value !== null) { + var list = $("<dl></dl>"); + for (var i in value) { + if (!value.hasOwnProperty(i)) continue; + $("<dt></dt>").text(i).appendTo(list); + $("<dd></dd>").append(_renderValue(value[i])).appendTo(list); + } + return list; + } else { + return $("<code></code>").addClass(type).text( + value !== null ? JSON.stringify(value) : "null" + ); + } + } + +} diff --git a/share/www/script/couch.js b/share/www/script/couch.js new file mode 100644 index 00000000..9815882a --- /dev/null +++ b/share/www/script/couch.js @@ -0,0 +1,205 @@ +// 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) { + this.name = name + this.uri = "/" + encodeURIComponent(name) + "/"; + request = CouchDB.request; + + // Creates the database on the server + this.createDb = function() { + var req = request("PUT", this.uri); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + return result; + } + + // Deletes the database on the server + this.deleteDb = function() { + var req = request("DELETE", this.uri); + if (req.status == 404) + return false; + var result = JSON.parse(req.responseText); + if (req.status != 202) + throw result; + return result; + } + + // Save a document to the database + this.save = function(doc, options) { + var req; + if (doc._id == undefined) + req = request("POST", this.uri + encodeOptions(options), { + body: JSON.stringify(doc) + }); + else + req = request("PUT", this.uri + encodeURIComponent(doc._id) + encodeOptions(options), { + body: JSON.stringify(doc) + }); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + // set the _id and _rev members on the input object, for caller convenience. + doc._id = result.id; + doc._rev = result.rev; + return result; + } + + // Open a document from the database + this.open = function(docId, options) { + var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options)); + if (req.status == 404) + return null; + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // Deletes a document from the database + this.deleteDoc = function(doc) { + var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev); + var result = JSON.parse(req.responseText); + if (req.status != 202) + throw result; + doc._rev = result.rev; //record rev in input document + doc._deleted = true; + return result; + } + + this.bulkSave = function(docs, options) { + var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), { + body: JSON.stringify({"docs": docs}) + }); + var result = JSON.parse(req.responseText); + if (req.status != 201) + throw result; + return result; + } + + // Applies the map function to the contents of database and returns the results. + this.query = function(mapFun, options) { + if (typeof(mapFun) != "string") + mapFun = mapFun.toSource ? mapFun.toSource() : "(" + mapFun.toString() + ")"; + var req = request("POST", this.uri + "_temp_view" + encodeOptions(options), { + headers: {"Content-Type": "text/javascript"}, + body: mapFun + }); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + this.view = function(viewname, options) { + var req = request("GET", this.uri + "_view/" + viewname + encodeOptions(options)); + if (req.status == 404) + return null; + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // gets information about the database + this.info = function() { + var req = request("GET", this.uri); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + this.allDocs = function(options) { + var req = request("GET", this.uri + "_all_docs" + encodeOptions(options)); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; + } + + // 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 (!options.hasOwnProperty(name)) continue; + var value = options[name]; + if (name == "key" || name == "startkey" || name == "endkey") { + value = toJSON(value); + } + buf.push(encodeURIComponent(name) + "=" + encodeURIComponent(value)); + } + } + if (!buf.length) { + return ""; + } + return "?" + buf.join("&"); + } + + function toJSON(obj) { + return obj !== null ? JSON.stringify(obj) : null; + } +} + +CouchDB.allDbs = function() { + var req = CouchDB.request("GET", "/_all_dbs"); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; +} + +CouchDB.getVersion = function() { + var req = CouchDB.request("GET", "/"); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result.version; +} + +CouchDB.replicate = function(source, target) { + var req = CouchDB.request("POST", "/_replicate", { + body: JSON.stringify({source: source, target: target}) + }); + var result = JSON.parse(req.responseText); + if (req.status != 200) + throw result; + return result; +} + +CouchDB.request = function(method, uri, options) { + options = options || {}; + var req = null; + if (typeof(XMLHttpRequest) != "undefined") { + req = new XMLHttpRequest(); + } else if (typeof(ActiveXObject) != "undefined") { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } else { + throw new Error("No XMLHTTPRequest support detected"); + } + req.open(method, uri, false); + if (options.headers) { + var headers = options.headers; + for (var headerName in headers) { + if (!headers.hasOwnProperty(headerName)) continue; + req.setRequestHeader(headerName, headers[headerName]); + } + } + req.send(options.body || null); + return req; +} diff --git a/share/www/script/couch_tests.js b/share/www/script/couch_tests.js new file mode 100644 index 00000000..bd159310 --- /dev/null +++ b/share/www/script/couch_tests.js @@ -0,0 +1,1027 @@ +// 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. + +var tests = { + + // Do some basic tests. + basics: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // Get the database info, check the doc_count + T(db.info().doc_count == 0); + + // create a document and save it to the database + var doc = {_id:"0",a:1,b:1}; + var result = db.save(doc); + + T(result.ok==true); // return object has an ok member with a value true + T(result.id); // the _id of the document is set. + T(result.rev); // the revision id of the document is set. + + // Verify the input doc is now set with the doc id and rev + // (for caller convenience). + T(doc._id == result.id && doc._rev == result.rev); + + var id = result.id; // save off the id for later + + // Create some more documents. + // Notice the use of the ok member on the return result. + T(db.save({_id:"1",a:2,b:4}).ok); + T(db.save({_id:"2",a:3,b:9}).ok); + T(db.save({_id:"3",a:4,b:16}).ok); + + // Check the database doc count + T(db.info().doc_count == 4); + + // Check the all docs + var results = db.allDocs(); + var rows = results.rows; + + for(var i=0; i < rows.length; i++) { + T(rows[i].id >= "0" && rows[i].id <= "4"); + } + + // Test a simple map functions + + // create a map function that selects all documents whose "a" member + // has a value of 4, and then returns the document's b value. + var mapFunction = function(doc){ + if(doc.a==4) + map(null, doc.b); + }; + + results = db.query(mapFunction); + + // verify only one document found and the result value (doc.b). + T(results.total_rows == 1 && results.rows[0].value == 16); + + // reopen document we saved earlier + existingDoc = db.open(id); + + T(existingDoc.a==1); + + //modify and save + existingDoc.a=4; + db.save(existingDoc); + + // redo the map query + results = db.query(mapFunction); + + // the modified document should now be in the results. + T(results.total_rows == 2); + + // write 2 more documents + T(db.save({a:3,b:9}).ok); + T(db.save({a:4,b:16}).ok); + + results = db.query(mapFunction); + + // 1 more document should now be in the result. + T(results.total_rows == 3); + T(db.info().doc_count == 6); + + // delete a document + T(db.deleteDoc(existingDoc).ok); + + // make sure we can't open the doc + T(db.open(existingDoc._id) == null); + + results = db.query(mapFunction); + + // 1 less document should now be in the results. + T(results.total_rows == 2); + T(db.info().doc_count == 5); + }, + + // Do some edit conflict detection tests + conflicts: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // create a doc and save + var doc = {_id:"foo",a:1,b:1}; + T(db.save(doc).ok); + + // reopen + var doc2 = db.open(doc._id); + + // ensure the revisions are the same + T(doc._id == doc2._id && doc._rev == doc2._rev); + + // edit the documents. + doc.a = 2; + doc2.a = 3; + + // save one document + T(db.save(doc).ok); + + // save the other document + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 1" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now clear out the _rev member and save. This indicates this document is + // new, not based on an existing revision. + doc2._rev = undefined; + try { + db.save(doc2); // this should generate a conflict exception + T("no save conflict 2" && false); // we shouldn't hit here + } catch (e) { + T(e.error == "conflict"); + } + + // Now delete the document from the database + T(db.deleteDoc(doc).ok); + + T(db.save(doc2).ok); // we can save a new document over a deletion without + // knowing the deletion rev. + }, + + recreate_doc: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // First create a new document with the ID "foo", and delete it again + var doc = {_id: "foo", a: "bar", b: 42}; + T(db.save(doc).ok); + T(db.deleteDoc(doc).ok); + + // Now create a new document with the same ID, save it, and then modify it + // This should work fine, but currently results in a conflict error, at + // least "sometimes" + for (var i = 0; i < 10; i++) { + doc = {_id: "foo"}; + T(db.save(doc).ok); + doc = db.open("foo"); + doc.a = "baz"; + try { + T(db.save(doc).ok); + } finally { + // And now, we can't even delete the document anymore :/ + T(db.deleteDoc(doc).rev != undefined); + } + } + }, + + // test saving a semi-large quanitity of documents and do some view queries. + lots_of_docs: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // keep number lowish for now to keep tests fasts. Crank up manually to + // to really test. + var numDocsToCreate = 500; + + var docs = makeDocs(numDocsToCreate); + T(db.bulkSave(docs).ok); + + // query all documents, and return the doc.integer member as a key. + results = db.query(function(doc){ map(doc.integer, null) }); + + T(results.total_rows == numDocsToCreate); + + // validate the keys are ordered ascending + for(var i=0; i<numDocsToCreate; i++) { + T(results.rows[i].key==i); + } + + // do the query again, but with descending output + results = db.query(function(doc){ map(doc.integer, null) }, {descending:true}); + + T(results.total_rows == numDocsToCreate); + + // validate the keys are ordered descending + for(var i=0; i<numDocsToCreate; i++) { + T(results.rows[numDocsToCreate-1-i].key==i); + } + }, + + multiple_rows: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var nc = {_id:"NC", cities:["Charlotte", "Raleigh"]}; + var ma = {_id:"MA", cities:["Boston", "Lowell", "Worcester", "Cambridge", "Springfield"]}; + var fl = {_id:"FL", cities:["Miami", "Tampa", "Orlando", "Springfield"]}; + + T(db.save(nc).ok); + T(db.save(ma).ok); + T(db.save(fl).ok); + + var generateListOfCitiesAndState = "function(doc) {" + + " for (var i = 0; i < doc.cities.length; i++)" + + " map(doc.cities[i] + \", \" + doc._id, null);" + + "}"; + + var results = db.query(generateListOfCitiesAndState); + var rows = results.rows; + + T(rows[0].key == "Boston, MA"); + T(rows[1].key == "Cambridge, MA"); + T(rows[2].key == "Charlotte, NC"); + T(rows[3].key == "Lowell, MA"); + T(rows[4].key == "Miami, FL"); + T(rows[5].key == "Orlando, FL"); + T(rows[6].key == "Raleigh, NC"); + T(rows[7].key == "Springfield, FL"); + T(rows[8].key == "Springfield, MA"); + T(rows[9].key == "Tampa, FL"); + T(rows[10].key == "Worcester, MA"); + + // add another city to NC + nc.cities.push("Wilmington"); + T(db.save(nc).ok); + + var results = db.query(generateListOfCitiesAndState); + var rows = results.rows; + + T(rows[0].key == "Boston, MA"); + T(rows[1].key == "Cambridge, MA"); + T(rows[2].key == "Charlotte, NC"); + T(rows[3].key == "Lowell, MA"); + T(rows[4].key == "Miami, FL"); + T(rows[5].key == "Orlando, FL"); + T(rows[6].key == "Raleigh, NC"); + T(rows[7].key == "Springfield, FL"); + T(rows[8].key == "Springfield, MA"); + T(rows[9].key == "Tampa, FL"); + T(rows[10].key == "Wilmington, NC"); + T(rows[11].key == "Worcester, MA"); + + // now delete MA + T(db.deleteDoc(ma).ok); + + var results = db.query(generateListOfCitiesAndState); + var rows = results.rows; + + T(rows[0].key == "Charlotte, NC"); + T(rows[1].key == "Miami, FL"); + T(rows[2].key == "Orlando, FL"); + T(rows[3].key == "Raleigh, NC"); + T(rows[4].key == "Springfield, FL"); + T(rows[5].key == "Tampa, FL"); + T(rows[6].key == "Wilmington, NC"); + }, + + large_docs: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var longtext = "0123456789\n"; + + for (var i=0; i<10; i++) { + longtext = longtext + longtext + } + T(db.save({"longtest":longtext}).ok); + T(db.save({"longtest":longtext}).ok); + T(db.save({"longtest":longtext}).ok); + T(db.save({"longtest":longtext}).ok); + + // query all documents, and return the doc.foo member as a key. + results = db.query(function(doc){ + map(null, doc.longtest); + }); + }, + + utf8: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var texts = []; + + texts[0] = "1. Ascii: hello" + texts[1] = "2. Russian: На берегу пустынных волн" + texts[2] = "3. Math: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i)," + texts[3] = "4. Geek: STARGΛ̊TE SG-1" + texts[4] = "5. Braille: ⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" + + // check that we can save a reload with full fidelity + for (var i=0; i<texts.length; i++) { + T(db.save({_id:i.toString(), text:texts[i]}).ok); + } + + for (var i=0; i<texts.length; i++) { + T(db.open(i.toString()).text == texts[i]); + } + + // check that views and key collation don't blow up + var rows = db.query(function(doc) { map(null, doc.text) }).rows; + for (var i=0; i<texts.length; i++) { + T(rows[i].value == texts[i]); + } + }, + + attachments: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var binAttDoc = { + _id:"bin_doc", + _attachments:{ + "foo.txt": { + "content-type":"text/plain", + "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + } + + T(db.save(binAttDoc).ok); + + var xhr = CouchDB.request("GET", "/test_suite_db/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + T(xhr.getResponseHeader("content-type") == "text/plain") + }, + + content_negotiation: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + var xhr; + + xhr = CouchDB.request("GET", "/test_suite_db/"); + T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + + xhr = CouchDB.request("GET", "/test_suite_db/", { + headers: {"Accept": "text/html;text/plain;*/*"} + }); + T(xhr.getResponseHeader("Content-Type") == "text/plain;charset=utf-8"); + + xhr = CouchDB.request("GET", "/test_suite_db/", { + headers: {"Accept": "application/json"} + }); + T(xhr.getResponseHeader("Content-Type") == "application/json"); + }, + + design_docs: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var numDocs = 50; + + var designDoc = { + _id:"_design/test", + language: "text/javascript", + views: { + all_docs: "function(doc) { map(doc.integer, null) }", + no_docs: "function(doc) {}", + single_doc: "function(doc) { if (doc._id == \"1\") { map(1, null) }}" + } + } + T(db.save(designDoc).ok); + + T(db.bulkSave(makeDocs(numDocs)).ok); + + for (var loop = 0; loop < 2; loop++) { + var rows = db.view("test/all_docs").rows + for (var i=0; i < numDocs; i++) { + T(rows[i].key == i); + } + T(db.view("test/no_docs").total_rows == 0) + T(db.view("test/single_doc").total_rows == 1) + restartServer(); + } + + T(db.deleteDoc(designDoc).ok); + T(db.open(designDoc._id) == null); + T(db.view("test/no_docs") == null); + + restartServer(); + T(db.open(designDoc._id) == null); + T(db.view("test/no_docs") == null); + }, + + view_collation: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + // NOTE, the values are already in their correct sort order. Consider this + // a specification of collation of json types. + + var values = [] + + // special values sort before all other types + values.push(null) + values.push(false) + values.push(true) + + // then numbers + values.push(1) + values.push(2) + values.push(3.0) + values.push(4) + + // then text, case sensitive + values.push("a") + values.push("A") + values.push("aa") + values.push("b") + values.push("B") + values.push("ba") + values.push("bb") + + // then arrays. compared element by element until different. + // Longer arrays sort after their prefixes + values.push(["a"]) + values.push(["b"]) + values.push(["b","c"]) + values.push(["b","c", "a"]) + values.push(["b","d"]) + values.push(["b","d", "e"]) + + // then object, compares each key value in the list until different. + // larger objects sort after their subset objects. + values.push({a:1}) + values.push({a:2}) + values.push({b:1}) + values.push({b:2}) + values.push({b:2, a:1}) // Member order does matter for collation. + // CouchDB preserves member order + // but doesn't require that clients will. + // (this test might fail if used with a js engine + // that doesn't preserve order) + values.push({b:2, c:2}) + + for (var i=0; i<values.length; i++) { + db.save({_id:(i).toString(), foo:values[i]}); + } + + var queryFun = function(doc) { map(doc.foo, null); } + var rows = db.query(queryFun).rows; + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[i])) + } + + // everything has collated correctly. Now to check the descending output + rows = db.query(queryFun, {descending:true}).rows + for (i=0; i<values.length; i++) { + T(equals(rows[i].key, values[values.length - 1 -i])) + } + + // now check the key query args + for (i=1; i<values.length; i++) { + var queryOptions = {key:values[i]} + rows = db.query(queryFun, queryOptions).rows; + T(rows.length == 1 && equals(rows[0].key, values[i])) + } + }, + + view_conflicts: function(debug) { + var dbA = new CouchDB("test_suite_db_a"); + dbA.deleteDb(); + dbA.createDb(); + var dbB = new CouchDB("test_suite_db_b"); + dbB.deleteDb(); + dbB.createDb(); + if (debug) debugger; + + var docA = {_id: "foo", bar: 42}; + T(dbA.save(docA).ok); + CouchDB.replicate(dbA.name, dbB.name); + + var docB = dbB.open("foo"); + docB.bar = 43; + dbB.save(docB); + docA.bar = 41; + dbA.save(docA); + CouchDB.replicate(dbA.name, dbB.name); + + var doc = dbB.open("foo", {conflicts: true}); + T(doc._conflicts.length == 1); + var conflictRev = doc._conflicts[0]; + if (doc.bar == 41) { // A won + T(conflictRev == docB._rev); + } else { // B won + T(doc.bar == 43); + T(conflictRev == docA._rev); + } + + var results = dbB.query(function(doc) { + if (doc._conflicts) { + map(doc._id, doc._conflicts); + } + }); + T(results.rows[0].value[0] == conflictRev); + }, + + view_pagination: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(100); + T(db.bulkSave(docs).ok); + + var queryFun = function(doc) { map(doc.integer, null) }; + var i; + + // page through the view ascending and going forward + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, count:10}); + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == i) + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i + j); + } + } + + // page through the view ascending and going backward + for (i = docs.length - 1; i >= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + count:-10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == i - 9) + var j; + for (j = 0; j < 10;j++) { + T(queryResults.rows[j].key == i - 9 + j); + } + } + + // page through the view descending and going forward + for (i = docs.length - 1; i >= 0; i -= 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:10}) + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 1) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i - j); + } + } + + // page through the view descending and going backward + for (i = 0; i < docs.length; i += 10) { + var queryResults = db.query(queryFun, {startkey:i, startkey_docid:i, + descending:true, count:-10}); + T(queryResults.rows.length == 10) + T(queryResults.total_rows == docs.length) + T(queryResults.offset == docs.length - i - 10) + var j; + for (j = 0; j < 10; j++) { + T(queryResults.rows[j].key == i + 9 - j); + } + } + }, + + view_sandboxing: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var docs = makeDocs(2); + T(db.bulkSave(docs).ok); + + // make sure that attempting to change the document throws an error + var results = db.query(function(doc) { + doc._id = "foo"; + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot invoke interpreter internals such as the + // garbage collector + var results = db.query(function(doc) { + gc(); + map(null, doc); + }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_funs array defined used by + // the view server + var results = db.query(function(doc) { map_funs.push(1); map(null, doc) }); + T(results.total_rows == 0); + + // make sure that a view cannot access the map_results array defined used by + // the view server + var results = db.query(function(doc) { map_results.push(1); map(null, doc) }); + T(results.total_rows == 0); + }, + + view_xml: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + db.save({content: "<doc><title id='xml'>Testing XML</title></doc>"}); + db.save({content: "<doc><title id='e4x'>Testing E4X</title></doc>"}); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.text(), null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "Testing E4X"); + T(results.rows[1].key == "Testing XML"); + + var results = db.query( + "function(doc) {\n" + + " var xml = new XML(doc.content);\n" + + " map(xml.title.@id, null);\n" + + "}"); + T(results.total_rows == 2); + T(results.rows[0].key == "e4x"); + T(results.rows[1].key == "xml"); + }, + + replication: function(debug) { + if (debug) debugger; + var dbPairs = [ + {source:"test_suite_db_a", + target:"test_suite_db_b"}, + {source:"test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"test_suite_db_b"}, + {source:"http://localhost:5984/test_suite_db_a", + target:"http://localhost:5984/test_suite_db_b"} + ] + var dbA = new CouchDB("test_suite_db_a"); + var dbB = new CouchDB("test_suite_db_b"); + var numDocs = 10; + var xhr; + for (var testPair = 0; testPair < dbPairs.length; testPair++) { + var A = dbPairs[testPair].source + var B = dbPairs[testPair].target + + dbA.deleteDb(); + dbA.createDb(); + dbB.deleteDb(); + dbB.createDb(); + + var docs = makeDocs(numDocs); + T(dbA.bulkSave(docs).ok); + + T(CouchDB.replicate(A, B).ok); + + for (var j = 0; j < numDocs; j++) { + docA = dbA.open("" + j); + docB = dbB.open("" + j); + T(docA._rev == docB._rev); + } + + // now check binary attachments + var binDoc = { + _id:"bin_doc", + _attachments:{ + "foo.txt": { + "type":"base64", + "data": "VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=" + } + } + } + + dbA.save(binDoc); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + xhr = CouchDB.request("GET", "/test_suite_db_a/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + xhr = CouchDB.request("GET", "/test_suite_db_b/bin_doc/foo.txt"); + T(xhr.responseText == "This is a base64 encoded text") + + dbA.save({_id:"foo1",value:"a"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + docA = dbA.open("foo1"); + docB = dbB.open("foo1"); + T(docA._rev == docB._rev); + + dbA.deleteDoc(docA); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + T(dbA.open("foo1") == null); + T(dbB.open("foo1") == null); + + dbA.save({_id:"foo",value:"a"}); + dbB.save({_id:"foo",value:"b"}); + + T(CouchDB.replicate(A, B).ok); + T(CouchDB.replicate(B, A).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // make sure the same rev is in each db + T(docA._rev === docB._rev); + + // make sure the conflicts are the same in each db + T(docA._conflicts[0] === docB._conflicts[0]); + + // delete a conflict. + dbA.deleteDoc({_id:"foo", _rev:docA._conflicts[0]}); + + // replicate the change + T(CouchDB.replicate(A, B).ok); + + // open documents and include the conflict meta data + docA = dbA.open("foo", {conflicts: true}); + docB = dbB.open("foo", {conflicts: true}); + + // We should have no conflicts this time + T(docA._conflicts === undefined) + T(docB._conflicts === undefined); + } + }, + + etags_head: function(debug) { + var db = new CouchDB("test_suite_db"); + db.deleteDb(); + db.createDb(); + if (debug) debugger; + + var xhr; + + // create a new doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 201); + + // extract the ETag header values + var etag = xhr.getResponseHeader("etag") + + // get the doc and verify the headers match + xhr = CouchDB.request("GET", "/test_suite_db/1"); + T(etag == xhr.getResponseHeader("etag")); + + // 'head' the doc and verify the headers match + xhr = CouchDB.request("HEAD", "/test_suite_db/1", { + headers: {"if-none-match": "s"} + }); + T(etag == xhr.getResponseHeader("etag")); + + // replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}", + headers: {"if-match": etag} + }); + T(xhr.status == 201); + + // extract the new ETag value + var etagOld= etag; + etag = xhr.getResponseHeader("etag") + + // fail to replace a doc + xhr = CouchDB.request("PUT", "/test_suite_db/1", { + body: "{}" + }); + T(xhr.status == 412) + + // verify get w/Etag + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etagOld} + }); + T(xhr.status == 200); + xhr = CouchDB.request("GET", "/test_suite_db/1", { + headers: {"if-none-match": etag} + }); + T(xhr.status == 304); + + // fail to delete a doc + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etagOld} + }); + T(xhr.status == 412); + + //now do it for real + xhr = CouchDB.request("DELETE", "/test_suite_db/1", { + headers: {"if-match": etag} + }); + T(xhr.status == 202) + } + +}; + +function makeDocs(n, templateDoc) { + var templateDocSrc = templateDoc ? templateDoc.toSource() : "{}" + var docs = [] + for(var i=0; i<n; i++) { + var newDoc = eval("(" + templateDocSrc + ")"); + newDoc._id = (i).toString(); + newDoc.integer = i + newDoc.string = (i).toString(); + docs.push(newDoc) + } + return docs; +} + +// *********************** Test Framework of Sorts ************************* // + +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).html(" "); + $("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, 1000); + }); + } + } + runNext(); +} + +var numFailures = 0; +var currentRow = null; + +function runTest(button, callback, debug) { + 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).html(" "); + var testFun = tests[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) { + $("<ol></ol>").appendTo($("td.details", row)); + } + $("<li><b>Exception raised:</b> <code class='error'></code></li>") + .find("code").text(JSON.stringify(e)).end() + .appendTo($("td.details ol", row)); + if (debug) { + currentRow = null; + throw e; + } + } + if ($("td.details ol", row).length) { + $("<a href='#'>Run with debugger</a>").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"); + updateTestsFooter(); + currentRow = null; + if (callback) callback(); + } + $("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.title = name; + $("<pre></pre>").text(tests[name].toString()).appendTo(win.document.body).fadeIn(); +} + +function updateTestsListing() { + for (var name in tests) { + if (!tests.hasOwnProperty(name)) continue; + var testFunction = tests[name]; + var row = $("<tr><th></th><td></td><td></td><td></td></tr>") + .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").html(" ").end() + .find("td:nth(2)").addClass("details").html(" ").end(); + $("<button type='button' class='run' title='Run test'></button>").click(function() { + this.blur(); + runTest(this); + 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.not(":contains('not run'))"); + var testsFailed = testsRun.not(".success"); + $("#tests tbody.footer td").text(testsRun.length + " of " + tests.length + + " test(s) run, " + testsFailed.length + " failures"); +} + +// 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) { + if (!arg1) { + if (currentRow) { + if ($("td.details ol", currentRow).length == 0) { + $("<ol></ol>").appendTo($("td.details", currentRow)); + } + $("<li><b>Assertion failed:</b> <code class='failure'></code></li>") + .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() + .appendTo($("td.details ol", currentRow)); + } + numFailures += 1 + } +} + +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 restartServer() { + var xhr = CouchDB.request("POST", "/_restart"); + do { + xhr = CouchDB.request("GET", "/"); + } while(xhr.status != 200); +} diff --git a/share/www/script/jquery.cookies.js b/share/www/script/jquery.cookies.js new file mode 100644 index 00000000..a2817461 --- /dev/null +++ b/share/www/script/jquery.cookies.js @@ -0,0 +1,47 @@ +// 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($) { + $.cookies = $.cookies || {} + $.fn.extend($.cookies, { + + /* Return the value of a cookie. */ + get: function(name) { + var nameEq = 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)); + } + } + return null; + }, + + /* Create or update a cookie. */ + set: function(name, value, days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24*60*60*1000)); + expires = "; expires=" + date.toGMTString(); + } + document.cookie = name + "=" + escape(value) + expires; + }, + + /* Remove a cookie. */ + remove: function(name) { + $.cookies.set(name, "", -1); + } + + }); +})(jQuery); diff --git a/share/www/script/jquery.dialog.js b/share/www/script/jquery.dialog.js new file mode 100644 index 00000000..dc8f9c5c --- /dev/null +++ b/share/www/script/jquery.dialog.js @@ -0,0 +1,92 @@ +// 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 = $('<div id="overlay" style="z-index:1001"></div>') + .css("opacity", "0"); + var dialog = $('<div id="dialog" style="z-index:1002;position:fixed;display:none;"></div>'); + if ($.browser.msie) { + var frame = $('<iframe id="overlay-frame" style="z-index:1000;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="javascript:false"></iframe>') + .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(); + $('<div class="error"></div>').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; + }); + var errors = options.submit(data); + if (errors == {}) { + dismiss(); + } else { + for (var name in errors) { + showError(name, errors[name]); + } + } + return false; + }); + }); + }); + } + +})(jQuery); diff --git a/share/www/script/jquery.js b/share/www/script/jquery.js new file mode 100644 index 00000000..2e43a823 --- /dev/null +++ b/share/www/script/jquery.js @@ -0,0 +1,3408 @@ +(function(){ +/* + * jQuery 1.2.3 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $ + * $Rev: 4663 $ + */ + +// Map over jQuery in case of overwrite +if ( window.jQuery ) + var _jQuery = window.jQuery; + +var jQuery = window.jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.prototype.init( selector, context ); +}; + +// Map over the $ in case of overwrite +if ( window.$ ) + var _$ = window.$; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +// A simple way to check for HTML strings or ID strings +// (both of which we optimize for) +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +// Is it a simple selector +var isSimple = /^.[^:#\[\.]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + return this; + + // Handle HTML strings + } else if ( typeof selector == "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Make sure an element was located + if ( elem ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + else { + this[0] = elem; + this.length = 1; + return this; + } + + else + selector = []; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return new jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray( + // HANDLE: $(array) + selector.constructor == Array && selector || + + // HANDLE: $(arraylike) + // Watch for when an array-like object, contains DOM nodes, is passed in as the selector + (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || + + // HANDLE: $(*) + [ selector ] ); + }, + + // The current version of jQuery being used + jquery: "1.2.3", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // The number of elements contained in the matched element set + length: 0, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + var ret = -1; + + // Locate the position of the desired element + this.each(function(i){ + if ( this == elem ) + ret = i; + }); + + return ret; + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( name.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined; + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text != "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) + // The elements to wrap the target around + jQuery( html, this[0].ownerDocument ) + .clone() + .insertBefore( this[0] ) + .map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, false, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, true, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + find: function( selector ) { + var elems = jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + }); + + return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? + jQuery.unique( elems ) : + elems ); + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var clone = this.cloneNode(true), + container = document.createElement("div"); + container.appendChild(clone); + return jQuery.clean([container.innerHTML])[0]; + } else + return this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if ( events === true ) + this.find("*").andSelf().each(function(i){ + if (this.nodeType == 3) + return; + var events = jQuery.data( this, "events" ); + + for ( var type in events ) + for ( var handler in events[ type ] ) + jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); + }); + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, this ) ); + }, + + not: function( selector ) { + if ( selector.constructor == String ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ) ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return !selector ? this : this.pushStack( jQuery.merge( + this.get(), + selector.constructor == String ? + jQuery( selector ).get() : + selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ? + selector : [selector] ) ); + }, + + is: function( selector ) { + return selector ? + jQuery.multiFilter( selector, this ).length > 0 : + false; + }, + + hasClass: function( selector ) { + return this.is( "." + selector ); + }, + + val: function( value ) { + if ( value == undefined ) { + + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + + // Everything else, we just grab the value + } else + return (this[0].value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = value.constructor == Array ? + value : + [ value ]; + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value == undefined ? + (this.length ? + this[0].innerHTML : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + data: function( key, value ){ + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value == null ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data == undefined && this.length ) + data = jQuery.data( this[0], key ); + + return data == null && parts[1] ? + this.data( parts[0] ) : + data; + } else + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ + jQuery.data( this, key, value ); + }); + }, + + removeData: function( key ){ + return this.each(function(){ + jQuery.removeData( this, key ); + }); + }, + + domManip: function( args, table, reverse, callback ) { + var clone = this.length > 1, elems; + + return this.each(function(){ + if ( !elems ) { + elems = jQuery.clean( args, this.ownerDocument ); + + if ( reverse ) + elems.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); + + var scripts = jQuery( [] ); + + jQuery.each(elems, function(){ + var elem = clone ? + jQuery( this ).clone( true )[0] : + this; + + // execute all scripts after the elements have been injected + if ( jQuery.nodeName( elem, "script" ) ) { + scripts = scripts.add( elem ); + } else { + // Remove any inner scripts for later evaluation + if ( elem.nodeType == 1 ) + scripts = scripts.add( jQuery( "script", elem ).remove() ); + + // Inject the elements into the document + callback.call( obj, elem ); + } + }); + + scripts.each( evalScript ); + }); + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.prototype.init.prototype = jQuery.prototype; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target != "object" && typeof target != "function" ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == 1 ) { + target = this; + i = 0; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + // Prevent never-ending loop + if ( target === options[ name ] ) + continue; + + // Recurse if we're merging object values + if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType ) + target[ name ] = jQuery.extend( target[ name ], options[ name ] ); + + // Don't bring in undefined values + else if ( options[ name ] != undefined ) + target[ name ] = options[ name ]; + + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning this function. + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + data = jQuery.trim( data ); + + if ( data ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.browser.msie ) + script.text = data; + else + script.appendChild( document.createTextNode( data ) ); + + head.appendChild( script ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data != undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? + jQuery.cache[ id ][ name ] : + id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + + for ( name in jQuery.cache[ id ] ) + break; + + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( object, callback, args ) { + if ( args ) { + if ( object.length == undefined ) { + for ( var name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( var i = 0, length = object.length; i < length; i++ ) + if ( callback.apply( object[ i ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( object.length == undefined ) { + for ( var name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var i = 0, length = object.length, value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames != undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use is(".class") + has: function( elem, className ) { + return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + var padding = 0, border = 0; + jQuery.each( which, function() { + padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + val -= Math.round(padding + border); + } + + if ( jQuery(elem).is(":visible") ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, val); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret; + + // A helper method for determining if an element's values are broken + function color( elem ) { + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle( elem, null ); + return !ret || ret.getPropertyValue("color") == ""; + } + + // We need to handle opacity special in IE + if ( name == "opacity" && jQuery.browser.msie ) { + ret = jQuery.attr( elem.style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + // Opera sometimes will give the wrong display answer, this fixes it, see #2037 + if ( jQuery.browser.opera && name == "display" ) { + var save = elem.style.outline; + elem.style.outline = "0 solid black"; + elem.style.outline = save; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && elem.style && elem.style[ name ] ) + ret = elem.style[ name ]; + + else if ( document.defaultView && document.defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var getComputedStyle = document.defaultView.getComputedStyle( elem, null ); + + if ( getComputedStyle && !color( elem ) ) + ret = getComputedStyle.getPropertyValue( name ); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + var swap = [], stack = []; + + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( var i = 0; i < stack.length; i++ ) + if ( color( stack[ i ] ) ) { + swap[ i ] = stack[ i ].style.display; + stack[ i ].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = name == "display" && swap[ stack.length - 1 ] != null ? + "none" : + ( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || ""; + + // Finally, revert the display styles back + for ( var i = 0; i < swap.length; i++ ) + if ( swap[ i ] != null ) + stack[ i ].style.display = swap[ i ]; + } + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = ret || 0; + ret = elem.style.pixelLeft + "px"; + + // Revert the changed values + elem.style.left = style; + elem.runtimeStyle.left = runtimeStyle; + } + } + + return ret; + }, + + clean: function( elems, context ) { + var ret = []; + context = context || document; + // !context.createElement fails in IE with an error but returns typeof 'object' + if (typeof context.createElement == 'undefined') + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + jQuery.each(elems, function(i, elem){ + if ( !elem ) + return; + + if ( elem.constructor == Number ) + elem = elem.toString(); + + // Convert html string into DOM nodes + if ( typeof elem == "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + "></" + tag + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); + + var wrap = + // option or optgroup + !tags.indexOf("<opt") && + [ 1, "<select multiple='multiple'>", "</select>" ] || + + !tags.indexOf("<leg") && + [ 1, "<fieldset>", "</fieldset>" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "<table>", "</table>" ] || + + !tags.indexOf("<tr") && + [ 2, "<table><tbody>", "</tbody></table>" ] || + + // <thead> matched above + (!tags.indexOf("<td") || !tags.indexOf("<th")) && + [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] || + + !tags.indexOf("<col") && + [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] || + + // IE can't serialize <link> and <script> tags normally + jQuery.browser.msie && + [ 1, "div<div>", "</div>" ] || + + [ 0, "", "" ]; + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( wrap[0]-- ) + div = div.lastChild; + + // Remove IE's autoinserted <tbody> from table fragments + if ( jQuery.browser.msie ) { + + // String was a <table>, *may* have spurious <tbody> + var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + + // IE completely kills leading whitespace when innerHTML is used + if ( /^\s/.test( elem ) ) + div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild ); + + } + + elem = jQuery.makeArray( div.childNodes ); + } + + if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) ) + return; + + if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options ) + ret.push( elem ); + + else + ret = jQuery.merge( ret, elem ); + + }); + + return ret; + }, + + attr: function( elem, name, value ) { + // don't set attributes on text and comment nodes + if (!elem || elem.nodeType == 3 || elem.nodeType == 8) + return undefined; + + var fix = jQuery.isXMLDoc( elem ) ? + {} : + jQuery.props; + + // Safari mis-reports the default selected property of a hidden option + // Accessing the parent's selectedIndex property fixes it + if ( name == "selected" && jQuery.browser.safari ) + elem.parentNode.selectedIndex; + + // Certain attributes only work when accessed via the old DOM 0 way + if ( fix[ name ] ) { + if ( value != undefined ) + elem[ fix[ name ] ] = value; + + return elem[ fix[ name ] ]; + + } else if ( jQuery.browser.msie && name == "style" ) + return jQuery.attr( elem.style, "cssText", value ); + + else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") ) + return elem.getAttributeNode( name ).nodeValue; + + // IE elem.getAttribute passes even for style + else if ( elem.tagName ) { + + if ( value != undefined ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode ) + throw "type property can't be changed"; + + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) ) + return elem.getAttribute( name, 2 ); + + return elem.getAttribute( name ); + + // elem is actually elem.style ... set the style + } else { + // IE actually uses filters for opacity + if ( name == "opacity" && jQuery.browser.msie ) { + if ( value != undefined ) { + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + elem.zoom = 1; + + // Set the alpha filter to set the opacity + elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) + + (parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"); + } + + return elem.filter && elem.filter.indexOf("opacity=") >= 0 ? + (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() : + ""; + } + + name = name.replace(/-([a-z])/ig, function(all, letter){ + return letter.toUpperCase(); + }); + + if ( value != undefined ) + elem[ name ] = value; + + return elem[ name ]; + } + }, + + trim: function( text ) { + return (text || "").replace( /^\s+|\s+$/g, "" ); + }, + + makeArray: function( array ) { + var ret = []; + + // Need to use typeof to fight Safari childNodes crashes + if ( typeof array != "array" ) + for ( var i = 0, length = array.length; i < length; i++ ) + ret.push( array[ i ] ); + else + ret = array.slice( 0 ); + + return ret; + }, + + inArray: function( elem, array ) { + for ( var i = 0, length = array.length; i < length; i++ ) + if ( array[ i ] == elem ) + return i; + + return -1; + }, + + merge: function( first, second ) { + // We have to loop this way because IE & Opera overwrite the length + // expando of getElementsByTagName + + // Also, we need to make sure that the correct elements are being returned + // (IE returns comment nodes in a '*' query) + if ( jQuery.browser.msie ) { + for ( var i = 0; second[ i ]; i++ ) + if ( second[ i ].nodeType != 8 ) + first.push( second[ i ] ); + + } else + for ( var i = 0; second[ i ]; i++ ) + first.push( second[ i ] ); + + return first; + }, + + unique: function( array ) { + var ret = [], done = {}; + + try { + + for ( var i = 0, length = array.length; i < length; i++ ) { + var id = jQuery.data( array[ i ] ); + + if ( !done[ id ] ) { + done[ id ] = true; + ret.push( array[ i ] ); + } + } + + } catch( e ) { + ret = array; + } + + return ret; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) + if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) ) + ret.push( elems[ i ] ); + + return ret; + }, + + map: function( elems, callback ) { + var ret = []; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + var value = callback( elems[ i ], i ); + + if ( value !== null && value != undefined ) { + if ( value.constructor != Array ) + value = [ value ]; + + ret = ret.concat( value ); + } + } + + return ret; + } +}); + +var userAgent = navigator.userAgent.toLowerCase(); + +// Figure out what browser is being used +jQuery.browser = { + version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1], + safari: /webkit/.test( userAgent ), + opera: /opera/.test( userAgent ), + msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), + mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) +}; + +var styleFloat = jQuery.browser.msie ? + "styleFloat" : + "cssFloat"; + +jQuery.extend({ + // Check to see if the W3C box model is being used + boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat", + + props: { + "for": "htmlFor", + "class": "className", + "float": styleFloat, + cssFloat: styleFloat, + styleFloat: styleFloat, + innerHTML: "innerHTML", + className: "className", + value: "value", + disabled: "disabled", + checked: "checked", + readonly: "readOnly", + selected: "selected", + maxlength: "maxLength", + selectedIndex: "selectedIndex", + defaultValue: "defaultValue", + tagName: "tagName", + nodeName: "nodeName" + } +}); + +jQuery.each({ + parent: function(elem){return elem.parentNode;}, + parents: function(elem){return jQuery.dir(elem,"parentNode");}, + next: function(elem){return jQuery.nth(elem,2,"nextSibling");}, + prev: function(elem){return jQuery.nth(elem,2,"previousSibling");}, + nextAll: function(elem){return jQuery.dir(elem,"nextSibling");}, + prevAll: function(elem){return jQuery.dir(elem,"previousSibling");}, + siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);}, + children: function(elem){return jQuery.sibling(elem.firstChild);}, + contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);} +}, function(name, fn){ + jQuery.fn[ name ] = function( selector ) { + var ret = jQuery.map( this, fn ); + + if ( selector && typeof selector == "string" ) + ret = jQuery.multiFilter( selector, ret ); + + return this.pushStack( jQuery.unique( ret ) ); + }; +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function(name, original){ + jQuery.fn[ name ] = function() { + var args = arguments; + + return this.each(function(){ + for ( var i = 0, length = args.length; i < length; i++ ) + jQuery( args[ i ] )[ original ]( this ); + }); + }; +}); + +jQuery.each({ + removeAttr: function( name ) { + jQuery.attr( this, name, "" ); + if (this.nodeType == 1) + this.removeAttribute( name ); + }, + + addClass: function( classNames ) { + jQuery.className.add( this, classNames ); + }, + + removeClass: function( classNames ) { + jQuery.className.remove( this, classNames ); + }, + + toggleClass: function( classNames ) { + jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames ); + }, + + remove: function( selector ) { + if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) { + // Prevent memory leaks + jQuery( "*", this ).add(this).each(function(){ + jQuery.event.remove(this); + jQuery.removeData(this); + }); + if (this.parentNode) + this.parentNode.removeChild( this ); + } + }, + + empty: function() { + // Remove element nodes and prevent memory leaks + jQuery( ">*", this ).remove(); + + // Remove any remaining nodes + while ( this.firstChild ) + this.removeChild( this.firstChild ); + } +}, function(name, fn){ + jQuery.fn[ name ] = function(){ + return this.each( fn, arguments ); + }; +}); + +jQuery.each([ "Height", "Width" ], function(i, name){ + var type = name.toLowerCase(); + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + return this[0] == window ? + // Opera reports document.body.client[Width/Height] properly in both quirks and standards + jQuery.browser.opera && document.body[ "client" + name ] || + + // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths) + jQuery.browser.safari && window[ "inner" + name ] || + + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] : + + // Get document width or height + this[0] == document ? + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + Math.max( + Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), + Math.max(document.body["offset" + name], document.documentElement["offset" + name]) + ) : + + // Get or set width or height on the element + size == undefined ? + // Get width or height on the element + (this.length ? jQuery.css( this[0], type ) : null) : + + // Set the width or height on the element (default to pixels if value is unitless) + this.css( type, size.constructor == String ? size : size + "px" ); + }; +}); + +var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ? + "(?:[\\w*_-]|\\\\.)" : + "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)", + quickChild = new RegExp("^>\\s*(" + chars + "+)"), + quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"), + quickClass = new RegExp("^([#.]?)(" + chars + "*)"); + +jQuery.extend({ + expr: { + "": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);}, + "#": function(a,i,m){return a.getAttribute("id")==m[2];}, + ":": { + // Position Checks + lt: function(a,i,m){return i<m[3]-0;}, + gt: function(a,i,m){return i>m[3]-0;}, + nth: function(a,i,m){return m[3]-0==i;}, + eq: function(a,i,m){return m[3]-0==i;}, + first: function(a,i){return i==0;}, + last: function(a,i,m,r){return i==r.length-1;}, + even: function(a,i){return i%2==0;}, + odd: function(a,i){return i%2;}, + + // Child Checks + "first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;}, + "last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;}, + "only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");}, + + // Parent Checks + parent: function(a){return a.firstChild;}, + empty: function(a){return !a.firstChild;}, + + // Text Check + contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;}, + + // Visibility + visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";}, + hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";}, + + // Form attributes + enabled: function(a){return !a.disabled;}, + disabled: function(a){return a.disabled;}, + checked: function(a){return a.checked;}, + selected: function(a){return a.selected||jQuery.attr(a,"selected");}, + + // Form elements + text: function(a){return "text"==a.type;}, + radio: function(a){return "radio"==a.type;}, + checkbox: function(a){return "checkbox"==a.type;}, + file: function(a){return "file"==a.type;}, + password: function(a){return "password"==a.type;}, + submit: function(a){return "submit"==a.type;}, + image: function(a){return "image"==a.type;}, + reset: function(a){return "reset"==a.type;}, + button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");}, + input: function(a){return /input|select|textarea|button/i.test(a.nodeName);}, + + // :has() + has: function(a,i,m){return jQuery.find(m[3],a).length;}, + + // :header + header: function(a){return /h\d/i.test(a.nodeName);}, + + // :animated + animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;} + } + }, + + // The regular expressions that power the parsing engine + parse: [ + // Match: [@value='test'], [@foo] + /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/, + + // Match: :contains('foo') + /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/, + + // Match: :even, :last-chlid, #id, .class + new RegExp("^([:.#]*)(" + chars + "+)") + ], + + multiFilter: function( expr, elems, not ) { + var old, cur = []; + + while ( expr && expr != old ) { + old = expr; + var f = jQuery.filter( expr, elems, not ); + expr = f.t.replace(/^\s*,\s*/, "" ); + cur = not ? elems = f.r : jQuery.merge( cur, f.r ); + } + + return cur; + }, + + find: function( t, context ) { + // Quickly handle non-string expressions + if ( typeof t != "string" ) + return [ t ]; + + // check to make sure context is a DOM element or a document + if ( context && context.nodeType != 1 && context.nodeType != 9) + return [ ]; + + // Set the correct context (if none is provided) + context = context || document; + + // Initialize the search + var ret = [context], done = [], last, nodeName; + + // Continue while a selector expression exists, and while + // we're no longer looping upon ourselves + while ( t && last != t ) { + var r = []; + last = t; + + t = jQuery.trim(t); + + var foundToken = false; + + // An attempt at speeding up child selectors that + // point to a specific element tag + var re = quickChild; + var m = re.exec(t); + + if ( m ) { + nodeName = m[1].toUpperCase(); + + // Perform our own iteration and filter + for ( var i = 0; ret[i]; i++ ) + for ( var c = ret[i].firstChild; c; c = c.nextSibling ) + if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) ) + r.push( c ); + + ret = r; + t = t.replace( re, "" ); + if ( t.indexOf(" ") == 0 ) continue; + foundToken = true; + } else { + re = /^([>+~])\s*(\w*)/i; + + if ( (m = re.exec(t)) != null ) { + r = []; + + var merge = {}; + nodeName = m[2].toUpperCase(); + m = m[1]; + + for ( var j = 0, rl = ret.length; j < rl; j++ ) { + var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild; + for ( ; n; n = n.nextSibling ) + if ( n.nodeType == 1 ) { + var id = jQuery.data(n); + + if ( m == "~" && merge[id] ) break; + + if (!nodeName || n.nodeName.toUpperCase() == nodeName ) { + if ( m == "~" ) merge[id] = true; + r.push( n ); + } + + if ( m == "+" ) break; + } + } + + ret = r; + + // And remove the token + t = jQuery.trim( t.replace( re, "" ) ); + foundToken = true; + } + } + + // See if there's still an expression, and that we haven't already + // matched a token + if ( t && !foundToken ) { + // Handle multiple expressions + if ( !t.indexOf(",") ) { + // Clean the result set + if ( context == ret[0] ) ret.shift(); + + // Merge the result sets + done = jQuery.merge( done, ret ); + + // Reset the context + r = ret = [context]; + + // Touch up the selector string + t = " " + t.substr(1,t.length); + + } else { + // Optimize for the case nodeName#idName + var re2 = quickID; + var m = re2.exec(t); + + // Re-organize the results, so that they're consistent + if ( m ) { + m = [ 0, m[2], m[3], m[1] ]; + + } else { + // Otherwise, do a traditional filter check for + // ID, class, and element selectors + re2 = quickClass; + m = re2.exec(t); + } + + m[2] = m[2].replace(/\\/g, ""); + + var elem = ret[ret.length-1]; + + // Try to do a global search by ID, where we can + if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) { + // Optimization for HTML document case + var oid = elem.getElementById(m[2]); + + // Do a quick check for the existence of the actual ID attribute + // to avoid selecting by the name attribute in IE + // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form + if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] ) + oid = jQuery('[@id="'+m[2]+'"]', elem)[0]; + + // Do a quick check for node name (where applicable) so + // that div#foo searches will be really fast + ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : []; + } else { + // We need to find all descendant elements + for ( var i = 0; ret[i]; i++ ) { + // Grab the tag name being searched for + var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2]; + + // Handle IE7 being really dumb about <object>s + if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" ) + tag = "param"; + + r = jQuery.merge( r, ret[i].getElementsByTagName( tag )); + } + + // It's faster to filter by class and be done with it + if ( m[1] == "." ) + r = jQuery.classFilter( r, m[2] ); + + // Same with ID filtering + if ( m[1] == "#" ) { + var tmp = []; + + // Try to find the element with the ID + for ( var i = 0; r[i]; i++ ) + if ( r[i].getAttribute("id") == m[2] ) { + tmp = [ r[i] ]; + break; + } + + r = tmp; + } + + ret = r; + } + + t = t.replace( re2, "" ); + } + + } + + // If a selector string still exists + if ( t ) { + // Attempt to filter it + var val = jQuery.filter(t,r); + ret = r = val.r; + t = jQuery.trim(val.t); + } + } + + // An error occurred with the selector; + // just return an empty set instead + if ( t ) + ret = []; + + // Remove the root context + if ( ret && context == ret[0] ) + ret.shift(); + + // And combine the results + done = jQuery.merge( done, ret ); + + return done; + }, + + classFilter: function(r,m,not){ + m = " " + m + " "; + var tmp = []; + for ( var i = 0; r[i]; i++ ) { + var pass = (" " + r[i].className + " ").indexOf( m ) >= 0; + if ( !not && pass || not && !pass ) + tmp.push( r[i] ); + } + return tmp; + }, + + filter: function(t,r,not) { + var last; + + // Look for common filter expressions + while ( t && t != last ) { + last = t; + + var p = jQuery.parse, m; + + for ( var i = 0; p[i]; i++ ) { + m = p[i].exec( t ); + + if ( m ) { + // Remove what we just matched + t = t.substring( m[0].length ); + + m[2] = m[2].replace(/\\/g, ""); + break; + } + } + + if ( !m ) + break; + + // :not() is a special case that can be optimized by + // keeping it out of the expression list + if ( m[1] == ":" && m[2] == "not" ) + // optimize if only one selector found (most common case) + r = isSimple.test( m[3] ) ? + jQuery.filter(m[3], r, true).r : + jQuery( r ).not( m[3] ); + + // We can get a big speed boost by filtering by class here + else if ( m[1] == "." ) + r = jQuery.classFilter(r, m[2], not); + + else if ( m[1] == "[" ) { + var tmp = [], type = m[3]; + + for ( var i = 0, rl = r.length; i < rl; i++ ) { + var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ]; + + if ( z == null || /href|src|selected/.test(m[2]) ) + z = jQuery.attr(a,m[2]) || ''; + + if ( (type == "" && !!z || + type == "=" && z == m[5] || + type == "!=" && z != m[5] || + type == "^=" && z && !z.indexOf(m[5]) || + type == "$=" && z.substr(z.length - m[5].length) == m[5] || + (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not ) + tmp.push( a ); + } + + r = tmp; + + // We can get a speed boost by handling nth-child here + } else if ( m[1] == ":" && m[2] == "nth-child" ) { + var merge = {}, tmp = [], + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" || + !/\D/.test(m[3]) && "0n+" + m[3] || m[3]), + // calculate the numbers (first)n+(last) including if they are negative + first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0; + + // loop through all the elements left in the jQuery object + for ( var i = 0, rl = r.length; i < rl; i++ ) { + var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode); + + if ( !merge[id] ) { + var c = 1; + + for ( var n = parentNode.firstChild; n; n = n.nextSibling ) + if ( n.nodeType == 1 ) + n.nodeIndex = c++; + + merge[id] = true; + } + + var add = false; + + if ( first == 0 ) { + if ( node.nodeIndex == last ) + add = true; + } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 ) + add = true; + + if ( add ^ not ) + tmp.push( node ); + } + + r = tmp; + + // Otherwise, find the expression to execute + } else { + var fn = jQuery.expr[ m[1] ]; + if ( typeof fn == "object" ) + fn = fn[ m[2] ]; + + if ( typeof fn == "string" ) + fn = eval("false||function(a,i){return " + fn + ";}"); + + // Execute it against the current filter + r = jQuery.grep( r, function(elem, i){ + return fn(elem, i, m, r); + }, not ); + } + } + + // Return an array of filtered elements (r) + // and the modified expression string (t) + return { r: r, t: t }; + }, + + dir: function( elem, dir ){ + var matched = []; + var cur = elem[dir]; + while ( cur && cur != document ) { + if ( cur.nodeType == 1 ) + matched.push( cur ); + cur = cur[dir]; + } + return matched; + }, + + nth: function(cur,result,dir,elem){ + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) + if ( cur.nodeType == 1 && ++num == result ) + break; + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType == 1 && (!elem || n != elem) ) + r.push( n ); + } + + return r; + } +}); + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code orignated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function(elem, types, handler, data) { + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( jQuery.browser.msie && elem.setInterval != undefined ) + elem = window; + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) + handler.guid = this.guid++; + + // if data is passed, bind to handler + if( data != undefined ) { + // Create temporary function pointer to original handler + var fn = handler; + + // Create unique handler function, wrapped around original handler + handler = function() { + // Pass arguments and context to original handler + return fn.apply(this, arguments); + }; + + // Store data in unique handler + handler.data = data; + + // Set the guid of unique handler to the same of original handler, so it can be removed + handler.guid = fn.guid; + } + + // Init the element's event structure + var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), + handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){ + // returned undefined or false + var val; + + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + if ( typeof jQuery == "undefined" || jQuery.event.triggered ) + return val; + + val = jQuery.event.handle.apply(arguments.callee.elem, arguments); + + return val; + }); + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native + // event in IE. + handle.elem = elem; + + // Handle multiple events seperated by a space + // jQuery(...).bind("mouseover mouseout", fn); + jQuery.each(types.split(/\s+/), function(index, type) { + // Namespaced event handlers + var parts = type.split("."); + type = parts[0]; + handler.type = parts[1]; + + // Get the current list of functions bound to this event + var handlers = events[type]; + + // Init the event handler queue + if (!handlers) { + handlers = events[type] = {}; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) { + // Bind the global event handler to the element + if (elem.addEventListener) + elem.addEventListener(type, handle, false); + else if (elem.attachEvent) + elem.attachEvent("on" + type, handle); + } + } + + // Add the function to the element's handler list + handlers[handler.guid] = handler; + + // Keep track of which events have been used, for global triggering + jQuery.event.global[type] = true; + }); + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + guid: 1, + global: {}, + + // Detach an event or set of events from an element + remove: function(elem, types, handler) { + // don't do events on text and comment nodes + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; + + var events = jQuery.data(elem, "events"), ret, index; + + if ( events ) { + // Unbind all events for the element + if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") ) + for ( var type in events ) + this.remove( elem, type + (types || "") ); + else { + // types is actually an event object here + if ( types.type ) { + handler = types.handler; + types = types.type; + } + + // Handle multiple events seperated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + jQuery.each(types.split(/\s+/), function(index, type){ + // Namespaced event handlers + var parts = type.split("."); + type = parts[0]; + + if ( events[type] ) { + // remove the given handler for the given type + if ( handler ) + delete events[type][handler.guid]; + + // remove all handlers for the given type + else + for ( handler in events[type] ) + // Handle the removal of namespaced events + if ( !parts[1] || events[type][handler].type == parts[1] ) + delete events[type][handler]; + + // remove generic event handler if no more handlers exist + for ( ret in events[type] ) break; + if ( !ret ) { + if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) { + if (elem.removeEventListener) + elem.removeEventListener(type, jQuery.data(elem, "handle"), false); + else if (elem.detachEvent) + elem.detachEvent("on" + type, jQuery.data(elem, "handle")); + } + ret = null; + delete events[type]; + } + } + }); + } + + // Remove the expando if it's no longer used + for ( ret in events ) break; + if ( !ret ) { + var handle = jQuery.data( elem, "handle" ); + if ( handle ) handle.elem = null; + jQuery.removeData( elem, "events" ); + jQuery.removeData( elem, "handle" ); + } + } + }, + + trigger: function(type, data, elem, donative, extra) { + // Clone the incoming data, if any + data = jQuery.makeArray(data || []); + + if ( type.indexOf("!") >= 0 ) { + type = type.slice(0, -1); + var exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Only trigger if we've ever bound an event for it + if ( this.global[type] ) + jQuery("*").add([window, document]).trigger(type, data); + + // Handle triggering a single element + } else { + // don't do events on text and comment nodes + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return undefined; + + var val, ret, fn = jQuery.isFunction( elem[ type ] || null ), + // Check to see if we need to provide a fake event, or not + event = !data[0] || !data[0].preventDefault; + + // Pass along a fake event + if ( event ) + data.unshift( this.fix({ type: type, target: elem }) ); + + // Enforce the right trigger type + data[0].type = type; + if ( exclusive ) + data[0].exclusive = true; + + // Trigger the event + if ( jQuery.isFunction( jQuery.data(elem, "handle") ) ) + val = jQuery.data(elem, "handle").apply( elem, data ); + + // Handle triggering native .onfoo handlers + if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) + val = false; + + // Extra functions don't get the custom event object + if ( event ) + data.shift(); + + // Handle triggering of extra function + if ( extra && jQuery.isFunction( extra ) ) { + // call the extra function and tack the current return value on the end for possible inspection + ret = extra.apply( elem, val == null ? data : data.concat( val ) ); + // if anything is returned, give it precedence and have it overwrite the previous value + if (ret !== undefined) + val = ret; + } + + // Trigger the native events (except for clicks on links) + if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) { + this.triggered = true; + try { + elem[ type ](); + // prevent IE from throwing an error for some hidden elements + } catch (e) {} + } + + this.triggered = false; + } + + return val; + }, + + handle: function(event) { + // returned undefined or false + var val; + + // Empty object is for triggered events with no data + event = jQuery.event.fix( event || window.event || {} ); + + // Namespaced event handlers + var parts = event.type.split("."); + event.type = parts[0]; + + var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 ); + args.unshift( event ); + + for ( var j in handlers ) { + var handler = handlers[j]; + // Pass in a reference to the handler function itself + // So that we can later remove it + args[0].handler = handler; + args[0].data = handler.data; + + // Filter the functions by class + if ( !parts[1] && !event.exclusive || handler.type == parts[1] ) { + var ret = handler.apply( this, args ); + + if ( val !== false ) + val = ret; + + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + + // Clean up added properties in IE to prevent memory leak + if (jQuery.browser.msie) + event.target = event.preventDefault = event.stopPropagation = + event.handler = event.data = null; + + return val; + }, + + fix: function(event) { + // store a copy of the original event object + // and clone to set read-only properties + var originalEvent = event; + event = jQuery.extend({}, originalEvent); + + // add preventDefault and stopPropagation since + // they will not work on the clone + event.preventDefault = function() { + // if preventDefault exists run it on the original event + if (originalEvent.preventDefault) + originalEvent.preventDefault(); + // otherwise set the returnValue property of the original event to false (IE) + originalEvent.returnValue = false; + }; + event.stopPropagation = function() { + // if stopPropagation exists run it on the original event + if (originalEvent.stopPropagation) + originalEvent.stopPropagation(); + // otherwise set the cancelBubble property of the original event to true (IE) + originalEvent.cancelBubble = true; + }; + + // Fix target property, if necessary + if ( !event.target ) + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + + // check if target is a textnode (safari) + if ( event.target.nodeType == 3 ) + event.target = originalEvent.target.parentNode; + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) + event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) + event.which = event.charCode || event.keyCode; + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) + event.metaKey = event.ctrlKey; + + // Add which for click: 1 == left; 2 == middle; 3 == right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button ) + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + + return event; + }, + + special: { + ready: { + setup: function() { + // Make sure the ready event is setup + bindReady(); + return; + }, + + teardown: function() { return; } + }, + + mouseenter: { + setup: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler); + return true; + }, + + teardown: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler); + return true; + }, + + handler: function(event) { + // If we actually just moused on to a sub-element, ignore it + if ( withinElement(event, this) ) return true; + // Execute the right handlers by setting the event type to mouseenter + arguments[0].type = "mouseenter"; + return jQuery.event.handle.apply(this, arguments); + } + }, + + mouseleave: { + setup: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler); + return true; + }, + + teardown: function() { + if ( jQuery.browser.msie ) return false; + jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler); + return true; + }, + + handler: function(event) { + // If we actually just moused on to a sub-element, ignore it + if ( withinElement(event, this) ) return true; + // Execute the right handlers by setting the event type to mouseleave + arguments[0].type = "mouseleave"; + return jQuery.event.handle.apply(this, arguments); + } + } + } +}; + +jQuery.fn.extend({ + bind: function( type, data, fn ) { + return type == "unload" ? this.one(type, data, fn) : this.each(function(){ + jQuery.event.add( this, type, fn || data, fn && data ); + }); + }, + + one: function( type, data, fn ) { + return this.each(function(){ + jQuery.event.add( this, type, function(event) { + jQuery(this).unbind(event); + return (fn || data).apply( this, arguments); + }, fn && data); + }); + }, + + unbind: function( type, fn ) { + return this.each(function(){ + jQuery.event.remove( this, type, fn ); + }); + }, + + trigger: function( type, data, fn ) { + return this.each(function(){ + jQuery.event.trigger( type, data, this, true, fn ); + }); + }, + + triggerHandler: function( type, data, fn ) { + if ( this[0] ) + return jQuery.event.trigger( type, data, this[0], false, fn ); + return undefined; + }, + + toggle: function() { + // Save reference to arguments for access in closure + var args = arguments; + + return this.click(function(event) { + // Figure out which function to execute + this.lastToggle = 0 == this.lastToggle ? 1 : 0; + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[this.lastToggle].apply( this, arguments ) || false; + }); + }, + + hover: function(fnOver, fnOut) { + return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut); + }, + + ready: function(fn) { + // Attach the listeners + bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + else + // Add the function to the wait list + jQuery.readyList.push( function() { return fn.call(this, jQuery); } ); + + return this; + } +}); + +jQuery.extend({ + isReady: false, + readyList: [], + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( jQuery.readyList ) { + // Execute all of them + jQuery.each( jQuery.readyList, function(){ + this.apply( document ); + }); + + // Reset the list of functions + jQuery.readyList = null; + } + + // Trigger any bound ready events + jQuery(document).triggerHandler("ready"); + } + } +}); + +var readyBound = false; + +function bindReady(){ + if ( readyBound ) return; + readyBound = true; + + // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event + if ( document.addEventListener && !jQuery.browser.opera) + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); + + // If IE is used and is not in a frame + // Continually check to see if the document is ready + if ( jQuery.browser.msie && window == top ) (function(){ + if (jQuery.isReady) return; + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( arguments.callee, 0 ); + return; + } + // and execute any waiting functions + jQuery.ready(); + })(); + + if ( jQuery.browser.opera ) + document.addEventListener( "DOMContentLoaded", function () { + if (jQuery.isReady) return; + for (var i = 0; i < document.styleSheets.length; i++) + if (document.styleSheets[i].disabled) { + setTimeout( arguments.callee, 0 ); + return; + } + // and execute any waiting functions + jQuery.ready(); + }, false); + + if ( jQuery.browser.safari ) { + var numStyles; + (function(){ + if (jQuery.isReady) return; + if ( document.readyState != "loaded" && document.readyState != "complete" ) { + setTimeout( arguments.callee, 0 ); + return; + } + if ( numStyles === undefined ) + numStyles = jQuery("style, link[rel=stylesheet]").length; + if ( document.styleSheets.length != numStyles ) { + setTimeout( arguments.callee, 0 ); + return; + } + // and execute any waiting functions + jQuery.ready(); + })(); + } + + // A fallback to window.onload, that will always work + jQuery.event.add( window, "load", jQuery.ready ); +} + +jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + + "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + + "submit,keydown,keypress,keyup,error").split(","), function(i, name){ + + // Handle event binding + jQuery.fn[name] = function(fn){ + return fn ? this.bind(name, fn) : this.trigger(name); + }; +}); + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function(event, elem) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + // Traverse up the tree + while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; } + // Return true if we actually just moused on to a sub-element + return parent == elem; +}; + +// Prevent memory leaks in IE +// And prevent errors on refresh with events like mouseover in other browsers +// Window isn't included so as not to unbind existing unload events +jQuery(window).bind("unload", function() { + jQuery("*").add(document).unbind(); +}); +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( jQuery.isFunction( url ) ) + return this.bind("load", url); + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + callback = callback || function(){}; + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else { + params = jQuery.param( params ); + type = "POST"; + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function(res, status){ + // If successful, inject the HTML into all the matched elements + if ( status == "success" || status == "notmodified" ) + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div/>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + + self.each( callback, [res.responseText, status, res] ); + } + }); + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + serializeArray: function() { + return this.map(function(){ + return jQuery.nodeName(this, "form") ? + jQuery.makeArray(this.elements) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + (this.checked || /select|textarea/i.test(this.nodeName) || + /text|hidden|password/i.test(this.type)); + }) + .map(function(i, elem){ + var val = jQuery(this).val(); + return val == null ? null : + val.constructor == Array ? + jQuery.map( val, function(val, i){ + return {name: elem.name, value: val}; + }) : + {name: elem.name, value: val}; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){ + jQuery.fn[o] = function(f){ + return this.bind(o, f); + }; +}); + +var jsc = (new Date).getTime(); + +jQuery.extend({ + get: function( url, data, callback, type ) { + // shift arguments if data argument was ommited + if ( jQuery.isFunction( data ) ) { + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + if ( jQuery.isFunction( data ) ) { + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + global: true, + type: "GET", + timeout: 0, + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + data: null, + username: null, + password: null, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + // Last-Modified header cache for next request + lastModified: {}, + + ajax: function( s ) { + var jsonp, jsre = /=\?(&|$)/g, status, data; + + // Extend the settings, but re-extend 's' so that it can be + // checked again later (in the test suite, specifically) + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data != "string" ) + s.data = jQuery.param(s.data); + + // Handle JSONP Parameter Callbacks + if ( s.dataType == "jsonp" ) { + if ( s.type.toLowerCase() == "get" ) { + if ( !s.url.match(jsre) ) + s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } else if ( !s.data || !s.data.match(jsre) ) + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) { + jsonp = "jsonp" + jsc++; + + // Replace the =? sequence both in the query string and the data + if ( s.data ) + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + window[ jsonp ] = function(tmp){ + data = tmp; + success(); + complete(); + // Garbage collect + window[ jsonp ] = undefined; + try{ delete window[ jsonp ]; } catch(e){} + if ( head ) + head.removeChild( script ); + }; + } + + if ( s.dataType == "script" && s.cache == null ) + s.cache = false; + + if ( s.cache === false && s.type.toLowerCase() == "get" ) { + var ts = (new Date()).getTime(); + // try replacing _= if it is there + var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2"); + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for get requests + if ( s.data && s.type.toLowerCase() == "get" ) { + s.url += (s.url.match(/\?/) ? "&" : "?") + s.data; + + // IE likes to send both get and post data, prevent this + s.data = null; + } + + // Watch for a new set of requests + if ( s.global && ! jQuery.active++ ) + jQuery.event.trigger( "ajaxStart" ); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && s.dataType == "script" && s.type.toLowerCase() == "get" ) { + var head = document.getElementsByTagName("head")[0]; + var script = document.createElement("script"); + script.src = s.url; + if (s.scriptCharset) + script.charset = s.scriptCharset; + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function(){ + if ( !done && (!this.readyState || + this.readyState == "loaded" || this.readyState == "complete") ) { + done = true; + success(); + complete(); + head.removeChild( script ); + } + }; + } + + head.appendChild(script); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object; Microsoft failed to properly + // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available + var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); + + // Open the socket + xml.open(s.type, s.url, s.async, s.username, s.password); + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set the correct header, if data is being sent + if ( s.data ) + xml.setRequestHeader("Content-Type", s.contentType); + + // Set the If-Modified-Since header, if ifModified mode. + if ( s.ifModified ) + xml.setRequestHeader("If-Modified-Since", + jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" ); + + // Set header so the called script knows that it's an XMLHttpRequest + xml.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // Set the Accepts header for the server, depending on the dataType + xml.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*" : + s.accepts._default ); + } catch(e){} + + // Allow custom headers/mimetypes + if ( s.beforeSend ) + s.beforeSend(xml); + + if ( s.global ) + jQuery.event.trigger("ajaxSend", [xml, s]); + + // Wait for a response to come back + var onreadystatechange = function(isTimeout){ + // The transfer is complete and the data is available, or the request timed out + if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) { + requestDone = true; + + // clear poll interval + if (ival) { + clearInterval(ival); + ival = null; + } + + status = isTimeout == "timeout" && "timeout" || + !jQuery.httpSuccess( xml ) && "error" || + s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" || + "success"; + + if ( status == "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.httpData( xml, s.dataType ); + } catch(e) { + status = "parsererror"; + } + } + + // Make sure that the request was successful or notmodified + if ( status == "success" ) { + // Cache Last-Modified header, if ifModified mode. + var modRes; + try { + modRes = xml.getResponseHeader("Last-Modified"); + } catch(e) {} // swallow exception thrown by FF if header is not available + + if ( s.ifModified && modRes ) + jQuery.lastModified[s.url] = modRes; + + // JSONP handles its own success callback + if ( !jsonp ) + success(); + } else + jQuery.handleError(s, xml, status); + + // Fire the complete handlers + complete(); + + // Stop memory leaks + if ( s.async ) + xml = null; + } + }; + + if ( s.async ) { + // don't attach the handler to the request, just poll it instead + var ival = setInterval(onreadystatechange, 13); + + // Timeout checker + if ( s.timeout > 0 ) + setTimeout(function(){ + // Check to see if the request is still happening + if ( xml ) { + // Cancel the request + xml.abort(); + + if( !requestDone ) + onreadystatechange( "timeout" ); + } + }, s.timeout); + } + + // Send the data + try { + xml.send(s.data); + } catch(e) { + jQuery.handleError(s, xml, null, e); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) + onreadystatechange(); + + function success(){ + // If a local callback was specified, fire it and pass it the data + if ( s.success ) + s.success( data, status ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxSuccess", [xml, s] ); + } + + function complete(){ + // Process result + if ( s.complete ) + s.complete(xml, status); + + // The request was completed + if ( s.global ) + jQuery.event.trigger( "ajaxComplete", [xml, s] ); + + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xml; + }, + + handleError: function( s, xml, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) s.error( xml, status, e ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxError", [xml, s, e] ); + }, + + // Counter for holding the number of active queries + active: 0, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( r ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !r.status && location.protocol == "file:" || + ( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 || + jQuery.browser.safari && r.status == undefined; + } catch(e){} + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xml, url ) { + try { + var xmlRes = xml.getResponseHeader("Last-Modified"); + + // Firefox always returns 200. check Last-Modified date + return xml.status == 304 || xmlRes == jQuery.lastModified[url] || + jQuery.browser.safari && xml.status == undefined; + } catch(e){} + return false; + }, + + httpData: function( r, type ) { + var ct = r.getResponseHeader("content-type"); + var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0; + var data = xml ? r.responseXML : r.responseText; + + if ( xml && data.documentElement.tagName == "parsererror" ) + throw "parsererror"; + + // If the type is "script", eval it in global context + if ( type == "script" ) + jQuery.globalEval( data ); + + // Get the JavaScript object, if JSON is used. + if ( type == "json" ) + data = eval("(" + data + ")"); + + return data; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a ) { + var s = []; + + // If an array was passed in, assume that it is an array + // of form elements + if ( a.constructor == Array || a.jquery ) + // Serialize the form elements + jQuery.each( a, function(){ + s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) ); + }); + + // Otherwise, assume that it's an object of key/value pairs + else + // Serialize the key/values + for ( var j in a ) + // If the value is an array then the key names need to be repeated + if ( a[j] && a[j].constructor == Array ) + jQuery.each( a[j], function(){ + s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) ); + }); + else + s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) ); + + // Return the resulting serialization + return s.join("&").replace(/%20/g, "+"); + } + +}); +jQuery.fn.extend({ + show: function(speed,callback){ + return speed ? + this.animate({ + height: "show", width: "show", opacity: "show" + }, speed, callback) : + + this.filter(":hidden").each(function(){ + this.style.display = this.oldblock || ""; + if ( jQuery.css(this,"display") == "none" ) { + var elem = jQuery("<" + this.tagName + " />").appendTo("body"); + this.style.display = elem.css("display"); + // handle an edge condition where css is - div { display:none; } or similar + if (this.style.display == "none") + this.style.display = "block"; + elem.remove(); + } + }).end(); + }, + + hide: function(speed,callback){ + return speed ? + this.animate({ + height: "hide", width: "hide", opacity: "hide" + }, speed, callback) : + + this.filter(":visible").each(function(){ + this.oldblock = this.oldblock || jQuery.css(this,"display"); + this.style.display = "none"; + }).end(); + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2 ){ + return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ? + this._toggle( fn, fn2 ) : + fn ? + this.animate({ + height: "toggle", width: "toggle", opacity: "toggle" + }, fn, fn2) : + this.each(function(){ + jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ](); + }); + }, + + slideDown: function(speed,callback){ + return this.animate({height: "show"}, speed, callback); + }, + + slideUp: function(speed,callback){ + return this.animate({height: "hide"}, speed, callback); + }, + + slideToggle: function(speed, callback){ + return this.animate({height: "toggle"}, speed, callback); + }, + + fadeIn: function(speed, callback){ + return this.animate({opacity: "show"}, speed, callback); + }, + + fadeOut: function(speed, callback){ + return this.animate({opacity: "hide"}, speed, callback); + }, + + fadeTo: function(speed,to,callback){ + return this.animate({opacity: to}, speed, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + return this[ optall.queue === false ? "each" : "queue" ](function(){ + if ( this.nodeType != 1) + return false; + + var opt = jQuery.extend({}, optall); + var hidden = jQuery(this).is(":hidden"), self = this; + + for ( var p in prop ) { + if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden ) + return jQuery.isFunction(opt.complete) && opt.complete.apply(this); + + if ( p == "height" || p == "width" ) { + // Store display property + opt.display = jQuery.css(this, "display"); + + // Make sure that nothing sneaks out + opt.overflow = this.style.overflow; + } + } + + if ( opt.overflow != null ) + this.style.overflow = "hidden"; + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function(name, val){ + var e = new jQuery.fx( self, opt, name ); + + if ( /toggle|show|hide/.test(val) ) + e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + else { + var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/), + start = e.cur(true) || 0; + + if ( parts ) { + var end = parseFloat(parts[2]), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit != "px" ) { + self.style[ name ] = (end || 1) + unit; + start = ((end || 1) / e.cur(true)) * start; + self.style[ name ] = start + unit; + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) + end = ((parts[1] == "-=" ? -1 : 1) * end) + start; + + e.custom( start, end, unit ); + } else + e.custom( start, val, "" ); + } + }); + + // For JS strict compliance + return true; + }); + }, + + queue: function(type, fn){ + if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) { + fn = type; + type = "fx"; + } + + if ( !type || (typeof type == "string" && !fn) ) + return queue( this[0], type ); + + return this.each(function(){ + if ( fn.constructor == Array ) + queue(this, type, fn); + else { + queue(this, type).push( fn ); + + if ( queue(this, type).length == 1 ) + fn.apply(this); + } + }); + }, + + stop: function(clearQueue, gotoEnd){ + var timers = jQuery.timers; + + if (clearQueue) + this.queue([]); + + this.each(function(){ + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) + if ( timers[i].elem == this ) { + if (gotoEnd) + // force the next step to be the last + timers[i](true); + timers.splice(i, 1); + } + }); + + // start the next in the queue if the last step wasn't forced + if (!gotoEnd) + this.dequeue(); + + return this; + } + +}); + +var queue = function( elem, type, array ) { + if ( !elem ) + return undefined; + + type = type || "fx"; + + var q = jQuery.data( elem, type + "queue" ); + + if ( !q || array ) + q = jQuery.data( elem, type + "queue", + array ? jQuery.makeArray(array) : [] ); + + return q; +}; + +jQuery.fn.dequeue = function(type){ + type = type || "fx"; + + return this.each(function(){ + var q = queue(this, type); + + q.shift(); + + if ( q.length ) + q[0].apply( this ); + }); +}; + +jQuery.extend({ + + speed: function(speed, easing, fn) { + var opt = speed && speed.constructor == Object ? speed : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && easing.constructor != Function && easing + }; + + opt.duration = (opt.duration && opt.duration.constructor == Number ? + opt.duration : + { slow: 600, fast: 200 }[opt.duration]) || 400; + + // Queueing + opt.old = opt.complete; + opt.complete = function(){ + if ( opt.queue !== false ) + jQuery(this).dequeue(); + if ( jQuery.isFunction( opt.old ) ) + opt.old.apply( this ); + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + timerId: null, + + fx: function( elem, options, prop ){ + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) + options.orig = {}; + } + +}); + +jQuery.fx.prototype = { + + // Simple function for setting a style value + update: function(){ + if ( this.options.step ) + this.options.step.apply( this.elem, [ this.now, this ] ); + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + + // Set display property to block for height/width animations + if ( this.prop == "height" || this.prop == "width" ) + this.elem.style.display = "block"; + }, + + // Get the current size + cur: function(force){ + if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null ) + return this.elem[ this.prop ]; + + var r = parseFloat(jQuery.css(this.elem, this.prop, force)); + return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0; + }, + + // Start an animation from one number to another + custom: function(from, to, unit){ + this.startTime = (new Date()).getTime(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + this.update(); + + var self = this; + function t(gotoEnd){ + return self.step(gotoEnd); + } + + t.elem = this.elem; + + jQuery.timers.push(t); + + if ( jQuery.timerId == null ) { + jQuery.timerId = setInterval(function(){ + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) + if ( !timers[i]() ) + timers.splice(i--, 1); + + if ( !timers.length ) { + clearInterval( jQuery.timerId ); + jQuery.timerId = null; + } + }, 13); + } + }, + + // Simple 'show' function + show: function(){ + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); + this.options.show = true; + + // Begin the animation + this.custom(0, this.cur()); + + // Make sure that we start at a small width/height to avoid any + // flash of content + if ( this.prop == "width" || this.prop == "height" ) + this.elem.style[this.prop] = "1px"; + + // Start by showing the element + jQuery(this.elem).show(); + }, + + // Simple 'hide' function + hide: function(){ + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function(gotoEnd){ + var t = (new Date()).getTime(); + + if ( gotoEnd || t > this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + var done = true; + for ( var i in this.options.curAnim ) + if ( this.options.curAnim[i] !== true ) + done = false; + + if ( done ) { + if ( this.options.display != null ) { + // Reset the overflow + this.elem.style.overflow = this.options.overflow; + + // Reset the display + this.elem.style.display = this.options.display; + if ( jQuery.css(this.elem, "display") == "none" ) + this.elem.style.display = "block"; + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) + this.elem.style.display = "none"; + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) + for ( var p in this.options.curAnim ) + jQuery.attr(this.elem.style, p, this.options.orig[p]); + } + + // If a callback was provided, execute it + if ( done && jQuery.isFunction( this.options.complete ) ) + // Execute the complete function + this.options.complete.apply( this.elem ); + + return false; + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } + +}; + +jQuery.fx.step = { + scrollLeft: function(fx){ + fx.elem.scrollLeft = fx.now; + }, + + scrollTop: function(fx){ + fx.elem.scrollTop = fx.now; + }, + + opacity: function(fx){ + jQuery.attr(fx.elem.style, "opacity", fx.now); + }, + + _default: function(fx){ + fx.elem.style[ fx.prop ] = fx.now + fx.unit; + } +}; +// The Offset Method +// Originally By Brandon Aaron, part of the Dimension Plugin +// http://jquery.com/plugins/project/dimensions +jQuery.fn.offset = function() { + var left = 0, top = 0, elem = this[0], results; + + if ( elem ) with ( jQuery.browser ) { + var parent = elem.parentNode, + offsetChild = elem, + offsetParent = elem.offsetParent, + doc = elem.ownerDocument, + safari2 = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent), + fixed = jQuery.css(elem, "position") == "fixed"; + + // Use getBoundingClientRect if available + if ( elem.getBoundingClientRect ) { + var box = elem.getBoundingClientRect(); + + // Add the document scroll offsets + add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft), + box.top + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop)); + + // IE adds the HTML element's border, by default it is medium which is 2px + // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; } + // IE 7 standards mode, the border is always 2px + // This border/offset is typically represented by the clientLeft and clientTop properties + // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS + // Therefore this method will be off by 2px in IE while in quirksmode + add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop ); + + // Otherwise loop through the offsetParents and parentNodes + } else { + + // Initial element offsets + add( elem.offsetLeft, elem.offsetTop ); + + // Get parent offsets + while ( offsetParent ) { + // Add offsetParent offsets + add( offsetParent.offsetLeft, offsetParent.offsetTop ); + + // Mozilla and Safari > 2 does not include the border on offset parents + // However Mozilla adds the border for table or table cells + if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 ) + border( offsetParent ); + + // Add the document scroll offsets if position is fixed on any offsetParent + if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" ) + fixed = true; + + // Set offsetChild to previous offsetParent unless it is the body element + offsetChild = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent; + // Get next offsetParent + offsetParent = offsetParent.offsetParent; + } + + // Get parent scroll offsets + while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) { + // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug + if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) ) + // Subtract parent scroll offsets + add( -parent.scrollLeft, -parent.scrollTop ); + + // Mozilla does not add the border for a parent that has overflow != visible + if ( mozilla && jQuery.css(parent, "overflow") != "visible" ) + border( parent ); + + // Get next parent + parent = parent.parentNode; + } + + // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild + // Mozilla doubles body offsets with a non-absolutely positioned offsetChild + if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) || + (mozilla && jQuery.css(offsetChild, "position") != "absolute") ) + add( -doc.body.offsetLeft, -doc.body.offsetTop ); + + // Add the document scroll offsets if position is fixed + if ( fixed ) + add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft), + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop)); + } + + // Return an object with top and left properties + results = { top: top, left: left }; + } + + function border(elem) { + add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) ); + } + + function add(l, t) { + left += parseInt(l) || 0; + top += parseInt(t) || 0; + } + + return results; +}; +})(); diff --git a/share/www/script/jquery.resizer.js b/share/www/script/jquery.resizer.js new file mode 100644 index 00000000..8ebd0c66 --- /dev/null +++ b/share/www/script/jquery.resizer.js @@ -0,0 +1,55 @@ +// 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.resizable = function(options) { + if ($.browser.safari && parseInt($.browser.version) >= 522) + return this; // safari3 and later provides textarea resizing natively + options = options || {}; + options.grippie = options.grippie || null; + options.minHeight = options.minHeight || 32; + options.maxHeight = options.maxHeight || null; + + return this.each(function() { + var grippie = options.grippie; + if (!grippie) grippie = $("<div></div>").appendTo(this.parentNode); + grippie.addClass("grippie"); + var elem = $(this); + grippie.mousedown(function(e) { + var pos = e.screenY; + var height = elem.height(); + $(document) + .mousemove(function(e) { + var offset = e.screenY - pos; + if (offset) { + var newHeight = height + offset; + if (newHeight >= options.minHeight && + (!options.maxHeight || newHeight <= options.maxHeight)) { + elem.height(newHeight); + height = newHeight; + } + pos = e.screenY; + } + document.onselectstart = function() { return false }; // for IE + return false; + }) + .one("mouseup", function() { + $(document).unbind("mousemove"); + document.onselectstart = null; // for IE + }); + return false; + }); + }); + } + +})(jQuery); diff --git a/share/www/script/jquery.suggest.js b/share/www/script/jquery.suggest.js new file mode 100644 index 00000000..4bb8b2d0 --- /dev/null +++ b/share/www/script/jquery.suggest.js @@ -0,0 +1,129 @@ +// 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($) { + + suggest = function(elem, options) { + var timer = null; + var prevVal = null; + + var input = $(elem).attr("autocomplete", "off"); + var offset = input.offset(); + var dropdown = $('<ul style="z-index: 10000"></ul>') + .addClass(options.dropdownClass).appendTo("body").css({ + top: (offset.top + elem.offsetHeight) + "px", + left: offset.left + "px", + minWidth: input.css("width") + }); + + input + .blur(function() { + setTimeout(function() { dropdown.hide() }, 200); + }) + .keydown(function(e) { + if (timer) clearTimeout(timer); + if ($.inArray(e.keyCode, [38, 40]) != -1 || + (dropdown.is(":visible") && (e.keyCode == 27 || + ($.inArray(e.keyCode, [9, 13]) != -1 && getSelection())))) { + e.preventDefault(); e.stopPropagation(); + switch(e.keyCode) { + case 38: // up + moveUp(); + break; + case 40: // down + moveDown(); + break; + case 9: // tab + case 13: // return + commit(); + break; + case 27: // escape + dropdown.hide(); + break; + } + return false; + } else { + timer = setTimeout(function() { suggest() }, options.delay); + } + }); + + function suggest(force) { + var newVal = $.trim(input.val()); + if (force || newVal != prevVal) { + if (force || newVal.length >= options.minChars) { + options.callback($.trim(input.val()), function(items) { + show(items); + }); + } else { + dropdown.hide(); + } + prevVal = newVal; + } + } + + function show(items) { + if (!items) return; + if (!items.length) { dropdown.hide(); return; } + var html = []; + for (var i = 0; i < items.length; i++) { + html.push('<li>' + items[i] + '</li>'); + } + dropdown.html(html.join("")).slideDown("fast"); + dropdown.children('li').click(function(e) { + $(this).addClass("selected"); + commit(); + }); + } + + function commit() { + var sel = getSelection(); + if (sel) { + prevVal = sel.text(); + input.val(prevVal); + dropdown.hide(); + } + if (timer) clearTimeout(timer) + } + + function getSelection() { + if (!dropdown.is(":visible")) return null; + var sel = dropdown.children("li.selected"); + return sel.length ? sel : null; + } + + function moveDown() { + if (!dropdown.is(":visible")) suggest(true); + var sel = getSelection(); + if (sel) sel.removeClass("selected").next().addClass("selected"); + else dropdown.children("li:first-child").addClass("selected"); + } + + function moveUp() { + if (!dropdown.is(":visible")) suggest(true); + var sel = getSelection(); + if (sel) sel.removeClass("selected").prev().addClass("selected"); + else dropdown.children("li:last-child").addClass("selected"); + } + } + + $.fn.suggest = function(callback, options) { + options = options || {}; + options.callback = callback; + options.delay = options.delay || 100; + options.dropdownClass = options.dropdownClass || "suggest-dropdown"; + options.minChars = options.minChars || 1; + return this.each(function() { + suggest(this, options); + }); + }; + +})(jQuery); diff --git a/share/www/script/json2.js b/share/www/script/json2.js new file mode 100644 index 00000000..e412ed20 --- /dev/null +++ b/share/www/script/json2.js @@ -0,0 +1,159 @@ +/* + json2.js + 2008-03-14 + + Public Domain + + No warranty expressed or implied. Use at your own risk. + + See http://www.JSON.org/js.html + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + Use your own copy. It is extremely unwise to load third party + code into your pages. +*/ + +if (!this.JSON) { + + JSON = function () { + + function f(n) { // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + Date.prototype.toJSON = function () { + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + + var m = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + function stringify(value, whitelist) { + var a, // The array holding the partial texts. + i, // The loop counter. + k, // The member key. + l, // Length. + r = /["\\\x00-\x1f\x7f-\x9f]/g, + v; // The member value. + + switch (typeof value) { + case 'string': + + return r.test(value) ? + '"' + value.replace(r, function (a) { + var c = m[a]; + if (c) { + return c; + } + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }) + '"' : + '"' + value + '"'; + + case 'number': + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + return String(value); + + case 'object': + + if (!value) { + return 'null'; + } + + if (typeof value.toJSON === 'function') { + return stringify(value.toJSON()); + } + a = []; + if (typeof value.length === 'number' && + !(value.propertyIsEnumerable('length'))) { + + l = value.length; + for (i = 0; i < l; i += 1) { + a.push(stringify(value[i], whitelist) || 'null'); + } + + return '[' + a.join(',') + ']'; + } + if (whitelist) { + l = whitelist.length; + for (i = 0; i < l; i += 1) { + k = whitelist[i]; + if (typeof k === 'string') { + v = stringify(value[k], whitelist); + if (v) { + a.push(stringify(k) + ':' + v); + } + } + } + } else { + + for (k in value) { + if (typeof k === 'string') { + v = stringify(value[k], whitelist); + if (v) { + a.push(stringify(k) + ':' + v); + } + } + } + } + + return '{' + a.join(',') + '}'; + } + } + + return { + stringify: stringify, + parse: function (text, filter) { + var j; + + function walk(k, v) { + var i, n; + if (v && typeof v === 'object') { + for (i in v) { + if (Object.prototype.hasOwnProperty.apply(v, [i])) { + n = walk(i, v[i]); + if (n !== undefined) { + v[i] = n; + } else { + delete v[i]; + } + } + } + } + return filter(k, v); + } + + if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + j = eval('(' + text + ')'); + + return typeof filter === 'function' ? walk('', j) : j; + } + + throw new SyntaxError('parseJSON'); + } + }; + }(); +}
\ No newline at end of file diff --git a/share/www/script/pprint.js b/share/www/script/pprint.js new file mode 100644 index 00000000..981db0f2 --- /dev/null +++ b/share/www/script/pprint.js @@ -0,0 +1,60 @@ +// 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. + +/* JSON pretty printing */ + +function prettyPrintJSON(val, indent, linesep, depth) { + indent = indent != null ? indent : 4; + linesep = linesep != null ? linesep : "\n"; + depth = depth != null ? depth : 1; + var propsep = linesep.length ? "," + linesep : ", "; + var tab = []; + for (var i = 0; i < indent * depth; i++) tab.push(""); + tab = tab.join(" "); + switch (typeof val) { + case "boolean": + case "number": + case "string": + return JSON.stringify(val); + case "object": { + if (val === 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 ? propsep : linesep); + buf.push( + tab, prettyPrintJSON(val[index], indent, linesep, depth + 1) + ); + } + if (index >= 0) buf.push(linesep, tab.substr(indent)); + buf.push("]"); + } else { + buf.push("{"); + var index = 0; + for (var key in val) { + if (!val.hasOwnProperty(key)) continue; + buf.push(index > 0 ? propsep : linesep); + buf.push( + tab, JSON.stringify(key), ": ", + prettyPrintJSON(val[key], indent, linesep, depth + 1) + ); + index++; + } + if (index >= 0) buf.push(linesep, tab.substr(indent)); + buf.push("}"); + } + return buf.join(""); + } + } +} diff --git a/share/www/script/shell.js b/share/www/script/shell.js new file mode 100644 index 00000000..f4c6aff5 --- /dev/null +++ b/share/www/script/shell.js @@ -0,0 +1,700 @@ +// 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. + +var +histList = [""], +histPos = 0, +_scope = {}, +_win, // a top-level context +question, +_in, +_out, +tooManyMatches = null, +lastError = null; + +function refocus() +{ + _in.blur(); // Needed for Mozilla to scroll correctly. + _in.focus(); +} + +function init() +{ + _in = document.getElementById("input"); + _out = document.getElementById("output"); + + _win = window; + + if (opener && !opener.closed) + { + println("Using bookmarklet version of shell: commands will run in opener's context.", "message"); + _win = opener; + } + + initTarget(); + + recalculateInputHeight(); + refocus(); +} + +function initTarget() +{ + _win.Shell = window; + _win.print = shellCommands.print; +} + + +// Unless the user is selected something, refocus the textbox. +// (requested by caillon, brendan, asa) +function keepFocusInTextbox(e) +{ + var g = e.srcElement ? e.srcElement : e.target; // IE vs. standard + + while (!g.tagName) + g = g.parentNode; + var t = g.tagName.toUpperCase(); + if (t=="A" || t=="INPUT") + return; + + if (window.getSelection) { + // Mozilla + if (String(window.getSelection())) + return; + } + else if (document.getSelection) { + // Opera? Netscape 4? + if (document.getSelection()) + return; + } + else { + // IE + if ( document.selection.createRange().text ) + return; + } + + refocus(); +} + +function inputKeydown(e) { + // Use onkeydown because IE doesn't support onkeypress for arrow keys + + //alert(e.keyCode + " ^ " + e.keycode); + + if (e.shiftKey && e.keyCode == 13) { // shift-enter + // don't do anything; allow the shift-enter to insert a line break as normal + } else if (e.keyCode == 13) { // enter + // execute the input on enter + try { go(); } catch(er) { alert(er); }; + setTimeout(function() { _in.value = ""; }, 0); // can't preventDefault on input, so clear it later + } else if (e.keyCode == 38) { // up + // go up in history if at top or ctrl-up + if (e.ctrlKey || caretInFirstLine(_in)) + hist(true); + } else if (e.keyCode == 40) { // down + // go down in history if at end or ctrl-down + if (e.ctrlKey || caretInLastLine(_in)) + hist(false); + } else if (e.keyCode == 9) { // tab + tabcomplete(); + setTimeout(function() { refocus(); }, 0); // refocus because tab was hit + } else { } + + setTimeout(recalculateInputHeight, 0); + + //return true; +}; + +function caretInFirstLine(textbox) +{ + // IE doesn't support selectionStart/selectionEnd + if (textbox.selectionStart == undefined) + return true; + + var firstLineBreak = textbox.value.indexOf("\n"); + + return ((firstLineBreak == -1) || (textbox.selectionStart <= firstLineBreak)); +} + +function caretInLastLine(textbox) +{ + // IE doesn't support selectionStart/selectionEnd + if (textbox.selectionEnd == undefined) + return true; + + var lastLineBreak = textbox.value.lastIndexOf("\n"); + + return (textbox.selectionEnd > lastLineBreak); +} + +function recalculateInputHeight() +{ + var rows = _in.value.split(/\n/).length + + 1 // prevent scrollbar flickering in Mozilla + + (window.opera ? 1 : 0); // leave room for scrollbar in Opera + + if (_in.rows != rows) // without this check, it is impossible to select text in Opera 7.60 or Opera 8.0. + _in.rows = rows; +} + +function println(s, type) +{ + if((s=String(s))) + { + var newdiv = document.createElement("div"); + newdiv.appendChild(document.createTextNode(s)); + newdiv.className = type; + _out.appendChild(newdiv); + return newdiv; + } +} + +function printWithRunin(h, s, type) +{ + var div = println(s, type); + var head = document.createElement("strong"); + head.appendChild(document.createTextNode(h + ": ")); + div.insertBefore(head, div.firstChild); +} + + +var shellCommands = +{ +load : function load(url) +{ + var s = _win.document.createElement("script"); + s.type = "text/javascript"; + s.src = url; + _win.document.getElementsByTagName("head")[0].appendChild(s); + println("Loading " + url + "...", "message"); +}, + +clear : function clear() +{ + var CHILDREN_TO_PRESERVE = 3; + while (_out.childNodes[CHILDREN_TO_PRESERVE]) + _out.removeChild(_out.childNodes[CHILDREN_TO_PRESERVE]); +}, + +print : function print(s) { println(s, "print"); }, + +// the normal function, "print", shouldn't return a value +// (suggested by brendan; later noticed it was a problem when showing others) +pr : function pr(s) +{ + shellCommands.print(s); // need to specify shellCommands so it doesn't try window.print()! + return s; +}, + +props : function props(e, onePerLine) +{ + if (e === null) { + println("props called with null argument", "error"); + return; + } + + if (e === undefined) { + println("props called with undefined argument", "error"); + return; + } + + var ns = ["Methods", "Fields", "Unreachables"]; + var as = [[], [], []]; // array of (empty) arrays of arrays! + var p, j, i; // loop variables, several used multiple times + + var protoLevels = 0; + + for (p = e; p; p = p.__proto__) + { + for (i=0; i<ns.length; ++i) + as[i][protoLevels] = []; + ++protoLevels; + } + + for(var a in e) + { + // Shortcoming: doesn't check that VALUES are the same in object and prototype. + + var protoLevel = -1; + try + { + for (p = e; p && (a in p); p = p.__proto__) + ++protoLevel; + } + catch(er) { protoLevel = 0; } // "in" operator throws when param to props() is a string + + var type = 1; + try + { + if ((typeof e[a]) == "function") + type = 0; + } + catch (er) { type = 2; } + + as[type][protoLevel].push(a); + } + + function times(s, n) { return n ? s + times(s, n-1) : ""; } + + for (j=0; j<protoLevels; ++j) + for (i=0;i<ns.length;++i) + if (as[i][j].length) + printWithRunin( + ns[i] + times(" of prototype", j), + (onePerLine ? "\n\n" : "") + as[i][j].sort().join(onePerLine ? "\n" : ", ") + (onePerLine ? "\n\n" : ""), + "propList" + ); +}, + +blink : function blink(node) +{ + if (!node) throw("blink: argument is null or undefined."); + if (node.nodeType == null) throw("blink: argument must be a node."); + if (node.nodeType == 3) throw("blink: argument must not be a text node"); + if (node.documentElement) throw("blink: argument must not be the document object"); + + function setOutline(o) { + return function() { + if (node.style.outline != node.style.bogusProperty) { + // browser supports outline (Firefox 1.1 and newer, CSS3, Opera 8). + node.style.outline = o; + } + else if (node.style.MozOutline != node.style.bogusProperty) { + // browser supports MozOutline (Firefox 1.0.x and older) + node.style.MozOutline = o; + } + else { + // browser only supports border (IE). border is a fallback because it moves things around. + node.style.border = o; + } + } + } + + function focusIt(a) { + return function() { + a.focus(); + } + } + + if (node.ownerDocument) { + var windowToFocusNow = (node.ownerDocument.defaultView || node.ownerDocument.parentWindow); // Moz vs. IE + if (windowToFocusNow) + setTimeout(focusIt(windowToFocusNow.top), 0); + } + + for(var i=1;i<7;++i) + setTimeout(setOutline((i%2)?'3px solid red':'none'), i*100); + + setTimeout(focusIt(window), 800); + setTimeout(focusIt(_in), 810); +}, + +scope : function scope(sc) +{ + if (!sc) sc = {}; + _scope = sc; + println("Scope is now " + sc + ". If a variable is not found in this scope, window will also be searched. New variables will still go on window.", "message"); +}, + +mathHelp : function mathHelp() +{ + printWithRunin("Math constants", "E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2", "propList"); + printWithRunin("Math methods", "abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan", "propList"); +}, + +ans : undefined +}; + + +function hist(up) +{ + // histList[0] = first command entered, [1] = second, etc. + // type something, press up --> thing typed is now in "limbo" + // (last item in histList) and should be reachable by pressing + // down again. + + var L = histList.length; + + if (L == 1) + return; + + if (up) + { + if (histPos == L-1) + { + // Save this entry in case the user hits the down key. + histList[histPos] = _in.value; + } + + if (histPos > 0) + { + histPos--; + // Use a timeout to prevent up from moving cursor within new text + // Set to nothing first for the same reason + setTimeout( + function() { + _in.value = ''; + _in.value = histList[histPos]; + var caretPos = _in.value.length; + if (_in.setSelectionRange) + _in.setSelectionRange(caretPos, caretPos); + }, + 0 + ); + } + } + else // down + { + if (histPos < L-1) + { + histPos++; + _in.value = histList[histPos]; + } + else if (histPos == L-1) + { + // Already on the current entry: clear but save + if (_in.value) + { + histList[histPos] = _in.value; + ++histPos; + _in.value = ""; + } + } + } +} + +function tabcomplete() +{ + /* + * Working backwards from s[from], find the spot + * where this expression starts. It will scan + * until it hits a mismatched ( or a space, + * but it skips over quoted strings. + * If stopAtDot is true, stop at a '.' + */ + function findbeginning(s, from, stopAtDot) + { + /* + * Complicated function. + * + * Return true if s[i] == q BUT ONLY IF + * s[i-1] is not a backslash. + */ + function equalButNotEscaped(s,i,q) + { + if(s.charAt(i) != q) // not equal go no further + return false; + + if(i==0) // beginning of string + return true; + + if(s.charAt(i-1) == '\\') // escaped? + return false; + + return true; + } + + var nparens = 0; + var i; + for(i=from; i>=0; i--) + { + if(s.charAt(i) == ' ') + break; + + if(stopAtDot && s.charAt(i) == '.') + break; + + if(s.charAt(i) == ')') + nparens++; + else if(s.charAt(i) == '(') + nparens--; + + if(nparens < 0) + break; + + // skip quoted strings + if(s.charAt(i) == '\'' || s.charAt(i) == '\"') + { + //dump("skipping quoted chars: "); + var quot = s.charAt(i); + i--; + while(i >= 0 && !equalButNotEscaped(s,i,quot)) { + //dump(s.charAt(i)); + i--; + } + //dump("\n"); + } + } + return i; + } + + // XXX should be used more consistently (instead of using selectionStart/selectionEnd throughout code) + // XXX doesn't work in IE, even though it contains IE-specific code + function getcaretpos(inp) + { + if(inp.selectionEnd != null) + return inp.selectionEnd; + + if(inp.createTextRange) + { + var docrange = _win.Shell.document.selection.createRange(); + var inprange = inp.createTextRange(); + if (inprange.setEndPoint) + { + inprange.setEndPoint('EndToStart', docrange); + return inprange.text.length; + } + } + + return inp.value.length; // sucks, punt + } + + function setselectionto(inp,pos) + { + if(inp.selectionStart) { + inp.selectionStart = inp.selectionEnd = pos; + } + else if(inp.createTextRange) { + var docrange = _win.Shell.document.selection.createRange(); + var inprange = inp.createTextRange(); + inprange.move('character',pos); + inprange.select(); + } + else { // err... + /* + inp.select(); + if(_win.Shell.document.getSelection()) + _win.Shell.document.getSelection() = ""; + */ + } + } + // get position of cursor within the input box + var caret = getcaretpos(_in); + + if(caret) { + //dump("----\n"); + var dotpos, spacepos, complete, obj; + //dump("caret pos: " + caret + "\n"); + // see if there's a dot before here + dotpos = findbeginning(_in.value, caret-1, true); + //dump("dot pos: " + dotpos + "\n"); + if(dotpos == -1 || _in.value.charAt(dotpos) != '.') { + dotpos = caret; +//dump("changed dot pos: " + dotpos + "\n"); + } + + // look backwards for a non-variable-name character + spacepos = findbeginning(_in.value, dotpos-1, false); + //dump("space pos: " + spacepos + "\n"); + // get the object we're trying to complete on + if(spacepos == dotpos || spacepos+1 == dotpos || dotpos == caret) + { + // try completing function args + if(_in.value.charAt(dotpos) == '(' || + (_in.value.charAt(spacepos) == '(' && (spacepos+1) == dotpos)) + { + var fn,fname; + var from = (_in.value.charAt(dotpos) == '(') ? dotpos : spacepos; + spacepos = findbeginning(_in.value, from-1, false); + + fname = _in.value.substr(spacepos+1,from-(spacepos+1)); + //dump("fname: " + fname + "\n"); + try { + with(_win.Shell._scope) + with(_win) + with(Shell.shellCommands) + fn = eval(fname); + } + catch(er) { + //dump('fn is not a valid object\n'); + return; + } + if(fn == undefined) { + //dump('fn is undefined'); + return; + } + if(fn instanceof Function) + { + // Print function definition, including argument names, but not function body + if(!fn.toString().match(/function .+?\(\) +\{\n +\[native code\]\n\}/)) + println(fn.toString().match(/function .+?\(.*?\)/), "tabcomplete"); + } + + return; + } + else + obj = _win; + } + else + { + var objname = _in.value.substr(spacepos+1,dotpos-(spacepos+1)); + //dump("objname: |" + objname + "|\n"); + try { + with(_win.Shell._scope) + with(_win) + obj = eval(objname); + } + catch(er) { + printError(er); + return; + } + if(obj == undefined) { + // sometimes this is tabcomplete's fault, so don't print it :( + // e.g. completing from "print(document.getElements" + // println("Can't complete from null or undefined expression " + objname, "error"); + return; + } + } + //dump("obj: " + obj + "\n"); + // get the thing we're trying to complete + if(dotpos == caret) + { + if(spacepos+1 == dotpos || spacepos == dotpos) + { + // nothing to complete + //dump("nothing to complete\n"); + return; + } + + complete = _in.value.substr(spacepos+1,dotpos-(spacepos+1)); + } + else { + complete = _in.value.substr(dotpos+1,caret-(dotpos+1)); + } + //dump("complete: " + complete + "\n"); + // ok, now look at all the props/methods of this obj + // and find ones starting with 'complete' + var matches = []; + var bestmatch = null; + for(var a in obj) + { + //a = a.toString(); + //XXX: making it lowercase could help some cases, + // but screws up my general logic. + if(a.substr(0,complete.length) == complete) { + matches.push(a); + ////dump("match: " + a + "\n"); + // if no best match, this is the best match + if(bestmatch == null) + { + bestmatch = a; + } + else { + // the best match is the longest common string + function min(a,b){ return ((a<b)?a:b); } + var i; + for(i=0; i< min(bestmatch.length, a.length); i++) + { + if(bestmatch.charAt(i) != a.charAt(i)) + break; + } + bestmatch = bestmatch.substr(0,i); + ////dump("bestmatch len: " + i + "\n"); + } + ////dump("bestmatch: " + bestmatch + "\n"); + } + } + bestmatch = (bestmatch || ""); + ////dump("matches: " + matches + "\n"); + var objAndComplete = (objname || obj) + "." + bestmatch; + //dump("matches.length: " + matches.length + ", tooManyMatches: " + tooManyMatches + ", objAndComplete: " + objAndComplete + "\n"); + if(matches.length > 1 && (tooManyMatches == objAndComplete || matches.length <= 10)) { + + printWithRunin("Matches: ", matches.join(', '), "tabcomplete"); + tooManyMatches = null; + } + else if(matches.length > 10) + { + println(matches.length + " matches. Press tab again to see them all", "tabcomplete"); + tooManyMatches = objAndComplete; + } + else { + tooManyMatches = null; + } + if(bestmatch != "") + { + var sstart; + if(dotpos == caret) { + sstart = spacepos+1; + } + else { + sstart = dotpos+1; + } + _in.value = _in.value.substr(0, sstart) + + bestmatch + + _in.value.substr(caret); + setselectionto(_in,caret + (bestmatch.length - complete.length)); + } + } +} + +function printQuestion(q) +{ + println(q, "input"); +} + +function printAnswer(a) +{ + if (a !== undefined) { + println(a, "normalOutput"); + shellCommands.ans = a; + } +} + +function printError(er) +{ + var lineNumberString; + + lastError = er; // for debugging the shell + if (er.name) + { + // lineNumberString should not be "", to avoid a very wacky bug in IE 6. + lineNumberString = (er.lineNumber != undefined) ? (" on line " + er.lineNumber + ": ") : ": "; + println(er.name + lineNumberString + er.message, "error"); // Because IE doesn't have error.toString. + } + else + println(er, "error"); // Because security errors in Moz /only/ have toString. +} + +function go(s) +{ + _in.value = question = s ? s : _in.value; + + if (question == "") + return; + + histList[histList.length-1] = question; + histList[histList.length] = ""; + histPos = histList.length - 1; + + // Unfortunately, this has to happen *before* the JavaScript is run, so that + // print() output will go in the right place. + _in.value=''; + recalculateInputHeight(); + printQuestion(question); + + if (_win.closed) { + printError("Target window has been closed."); + return; + } + + try { ("Shell" in _win) } + catch(er) { + printError("The JavaScript Shell cannot access variables in the target window. The most likely reason is that the target window now has a different page loaded and that page has a different hostname than the original page."); + return; + } + + if (!("Shell" in _win)) + initTarget(); // silent + + // Evaluate Shell.question using _win's eval (this is why eval isn't in the |with|, IIRC). + _win.location.href = "javascript:try{ Shell.printAnswer(eval('with(Shell._scope) with(Shell.shellCommands) {' + Shell.question + String.fromCharCode(10) + '}')); } catch(er) { Shell.printError(er); }; setTimeout(Shell.refocus, 0); void 0"; +} diff --git a/share/www/shell.html b/share/www/shell.html new file mode 100644 index 00000000..ebc4a130 --- /dev/null +++ b/share/www/shell.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<!-- + +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. + +--> +<html lang="en" onclick="keepFocusInTextbox(event)"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>JavaScript Shell 1.4</title> + <link rel="stylesheet" href="style/layout.css" type="text/css"> + <script src="script/shell.js"></script> + <!-- for http://ted.mielczarek.org/code/mozilla/extensiondev/ --> + <script type="text/javascript" src="chrome://extensiondev/content/rdfhistory.js"></script> + <script type="text/javascript" src="chrome://extensiondev/content/chromeShellExtras.js"></script> + <script type='text/javascript' src='script/json2.js'></script> + <script type='text/javascript' src='script/couch.js'></script> + <style type="text/css"> + #help { border-bottom: 1px solid #eee; color: #666; margin: 0 0 1em; } + /* Preserve line breaks, but wrap too if browser supports it */ + #output { white-space: pre; white-space: -moz-pre-wrap; } + form { margin: 0; padding: 0; } + #input { background: transparent; border: none; + border-left: 2px solid #999; font: bold 110% monospace; + margin: .5em 0 0 -12px; overflow: auto; padding: 0 0 0 10px; + width: 100%; + } + .input { color: #000; font: bold 110% monospace; margin-top: .5em; } + .normalOutput, .print, .error, .propList, .message, .tabcomplete { + background: #e4e4e4; margin: 0 1em; padding: 2px; + } + .normalOutput { color: #666; font-family: monospace; } + .print { color: #666; } + .error { color: #f00; background: #f9e4e4; } + .propList { color: #0a0; } + .message { color: #0d0; } + .tabcomplete { color: #d0d; } + </style> + </head> + <body onload="init()"> + <h1> + <a href="browse/index.html">Overview</a> + <strong>Javascript Shell</strong> + </h1> + <div id="wrap"> + <div id="help"> + <p> + Features: auto-completion of property names using Tab, multiline input + using Shift+Enter, input history with (Ctrl+)Up/Down, + <a accesskey="M" href="javascript:go('scope(Math); mathHelp();');" title="Accesskey: M">Math</a>, + <a accesskey="H" href="http://www.squarefree.com/shell/?ignoreReferrerFrom=shell1.4" title="Accesskey: H" target="_blank">help</a> + <br> + Values and functions: ans, print(string), + <code><a accesskey="P" href="javascript:go('props(ans)')" title="Accesskey: P">props(object)</a></code>, + <code><a accesskey="B" href="javascript:go('blink(ans)')" title="Accesskey: B">blink(node)</a></code>, + <code><a accesskey="C" href="javascript:go('clear()')" title="Accesskey: C">clear()</a></code>, + <code>load(scriptURL)</code>, <code>scope(object)</code> + </p> + </div> + <div id="output"></div> + <div> + <textarea id="input" class="input" wrap="off" onkeydown="inputKeydown(event)" rows="1"></textarea> + </div> + + </div> + </body> + <script> + if (parent !== window) parent.updateNavigation(); + </script> +</html> diff --git a/share/www/style/layout.css b/share/www/style/layout.css new file mode 100644 index 00000000..37bf0c79 --- /dev/null +++ b/share/www/style/layout.css @@ -0,0 +1,405 @@ +/* + +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. + +*/ + +/* General styles */ + +html, body { background: transparent; color: #000; + font: normal 90% Arial,Helvetica,sans-serif; margin: 0; +} +:link, :visited { color: #ba1e16; text-decoration: none; } +:link img, :visited img { border: none; } + +h1 { background: #666 url(../image/hgrad.gif) 100% 0 repeat-y; + border-bottom: 1px solid #333; color: #999; + font: 125% normal Arial,Helvetica,sans-serif; + line-height: 1.8em; margin: 0 0 1em; padding: 0 0 0 1em; +} +h1 :link, h1 :visited, h1 strong { padding: .4em .5em; } +h1 :link, h1 :visited { + background: url(../image/path.gif) 100% 50% no-repeat; padding-right: 2em; +} +h1 :link, h1 :visited { color: #bbb; cursor: pointer; + text-shadow: #333 2px 2px 1px; +} +h1 strong { color: #fff; text-shadow: #000 2px 2px 4px; } + +hr { border: 1px solid #999; border-width: 1px 0 0; } +dl dt { font-weight: bold; } +code, tt { font-family: "DejaVu Sans Mono",Monaco,monospace; } + +button { font-size: 100%; -webkit-appearance: square-button; } +input, select, textarea { background: #fff; border: 1px solid; + border-color: #999 #ddd #ddd #999; margin: 0; padding: 1px; +} +textarea { font-family: "DejaVu Sans Mono",Monaco,monospace; font-size: 100%; } +fieldset { border: none; font-size: 95%; margin: 0; padding: .2em 0 0; } +fieldset legend { color: #666; font-weight: bold; padding: 0; } +fieldset input, fieldset select { font-size: 95%; } +fieldset p { margin: .4em; } + +/* Tabular listings */ + +table.listing { border-collapse: separate; border-spacing: 0; + border: 1px solid #a7a7a7; clear: both; width: 100%; +} +table.listing caption { display: none; } +table.listing th, table.listing td { padding: .2em .5em; } +table.listing thead th { background: #dadada url(../image/thead.gif) repeat-x; + border: 1px solid #a7a7a7; border-width: 0 0 1px 1px; color: #333; + font-size: 95%; font-weight: normal; text-align: left; + text-shadow: #999 2px 1px 2px; text-transform: capitalize; + white-space: nowrap; +} +table.listing thead th:first-child { border-left: none; } +table.listing thead th.key { + background: #a7afb6 url(../image/thead-key.gif) 0 0 repeat-x; + padding-top: 2px; +} +table.listing thead th.key div { + background: url(../image/order-asc.gif) 100% 3px no-repeat; cursor: pointer; +} +table.listing thead th.desc div { + background-image: url(../image/order-desc.gif); +} +table.listing tbody tr th, table.listing tbody tr td { background: #feffea; } +table.listing tbody tr.odd th, table.listing tbody tr.odd td, +table.listing tbody.odd tr th, table.listing tbody.odd tr td { + background: #fff; +} +table.listing tbody th, table.listing tbody td { + border-left: 1px solid #d9d9d9; padding: .4em .5em; vertical-align: top; +} +table.listing tbody th:first-child, table.listing tbody td:first-child { + border-left: none; +} +table.listing tbody th { text-align: left; } +table.listing tbody th :link, table.listing tbody th :visited { + display: block; +} +table.listing tbody.content th button { + background: transparent no-repeat; border: none; cursor: pointer; + float: left; margin: 0 5px 0 -20px; padding: 0; width: 15px; height: 15px; +} +table.listing tbody.content th button:hover { background-position: -15px 0; } +table.listing tbody.footer tr td { background: #e9e9e9; + border-top: 1px solid #a7a7a7; color: #999; font-size: 90%; + line-height: 1.8em; +} +table.listing tbody.footer #paging { float: right; } +table.listing tbody.footer #paging a, +table.listing tbody.footer #paging label { + padding: 0 .5em; +} +table.listing tbody.footer #paging label { color: #666; } +table.listing tbody.footer #paging select { font-size: 90%; padding: 0; } + +/* Resizer grippies */ + +div.grippie { background: #e9e9e9 url(../image/grippie.gif) 50% 50% no-repeat; + border: 1px solid #aaa; border-top: none; cursor: row-resize; + min-height: 10px; +} + +/* Suggest results */ + +ul.suggest-dropdown { border: 1px solid #999; background-color: #eee; + padding: 0; margin: 0; list-style: none; opacity: .85; position: absolute; + z-index: 10000; display: none; -webkit-box-shadow: 2px 2px 10px #333; +} +ul.suggest-dropdown li { padding: 2px 5px; white-space: nowrap; color: #101010; + text-align: left; +} +ul.suggest-dropdown li.selected { cursor: pointer; background: Highlight; + color: HighlightText; +} + +/* Logo & Navigation */ + +#wrap { padding: 0 20px 3em; } + +#logo { position: absolute; top: 20px; right: 20px; } + +#nav { color: #333; font-size: 110%; font-weight: bold; list-style: none; + margin: 0; overflow: auto; padding: 0; position: absolute; top: 185px; + bottom: 20px; right: 0; width: 210px; +} +#nav ul { list-style: none; margin: 0; padding: 0; } +#nav li { color: #999; margin: 5px 0 0; padding: 3px 0; } +#nav li span { padding: 0 20px; } +#nav li.selected { background: #e9e9e9; } +#nav li li { font-size: 90%; font-weight: normal; margin: 0; + padding: 2px 20px 2px 40px; +} +#nav li li:hover { background: #e4e4e4; } +#nav li.selected li:hover { background: #d7d7d7; } +#nav li li :link, #nav li li :visited { color: #333; display: block; + text-decoration: none; +} +#nav li li :link:hover, #nav li li :visited:hover { color: #000; } +#nav li li :link:focus, #nav li li :visited:focus { outline: none; } +#nav li li.selected { background: #aaa !important; border-top: 1px solid #999; + color: #fff; padding-top: 1px; +} +#nav li li.selected :link, #nav li li.selected :visited { color: #fff; } +#nav li li.selected :link:hover, #nav li li.selected :visited:hover { + color: #fff; +} + +#footer { background: #ddd; border-top: 1px solid #bbb; color: #000; + font-size: 80%; opacity: .7; padding: 5px 10px; position: absolute; right: 0; + bottom: 0; height: 10px; width: 190px; text-align: right; +} +#view { position: absolute; left: 0; right: 210px; top: 0; bottom: 0; + height: 100%; +} + +/* Toolbar */ + +#toolbar { font-size: 90%; line-height: 16px; list-style: none; + margin: 0 0 .5em; padding: 5px 5px 5px 3px; +} +#toolbar li { display: inline; } +#toolbar button { background: transparent 2px 2px no-repeat; border: none; + color: #666; margin: 0; padding: 2px 1em 2px 22px; cursor: pointer; + font-size: 95%; font-weight: bold; line-height: 16px; +} +#toolbar button:hover { background-position: 2px -30px; color: #000; } +#toolbar button:active { background-position: 2px -62px; color: #000; } +#toolbar button.add { background-image: url(../image/add.gif); } +#toolbar button.delete { background-image: url(../image/delete.gif); } +#toolbar button.edit { background-image: url(../image/edit.gif); } +#toolbar button.load { background-image: url(../image/load.gif); } +#toolbar button.run { background-image: url(../image/run.gif); } +#toolbar button.save { background-image: url(../image/save.gif); } + +/* Dialogs */ + +#overlay { background: #bbb; cursor: wait; position: fixed; width: 100%; + height: 100%; top: 0; left: 0; +} +*html #overlay { position: absolute; + width: expression(document.body.clientWidth + 'px'); + height: expression(document.body.clientHeight + 'px'); +} +#dialog { background: #333 url(../image/spinner.gif) 50% 50% no-repeat; + color: #f4f4f4; overflow: hidden; opacity: .95; max-width: 33em; + padding: 1em 1em 0; -moz-border-radius: 7px; -webkit-border-radius: 7px; + -webkit-box-shadow: 4px 4px 6px #333; +} +*html #dialog { width: 33em; } +#dialog.loading { width: 220px; height: 80px; } +#dialog.loaded { background-image: none; } +#dialog h2 { background: #666; border-top: 1px solid #555; + border-bottom: 1px solid #777; color: #ccc; font-size: 110%; + font-weight: bold; margin: 0 -2em; padding: .35em 2em; +} +#dialog fieldset { background: #222; border-top: 1px solid #111; + margin: 0 0 1em; padding: .5em 1em 1em; + -moz-border-radius-bottomleft: 7px; -moz-border-radius-bottomright: 7px; + -webkit-border-bottom-left-radius: 7px; + -webkit-border-bottom-right-radius: 7px; +} +#dialog p.help { color: #bbb; margin: 0 0 1em; } +#dialog fieldset table { margin-top: 1em; } +#dialog fieldset th, #dialog fieldset td { padding: .5em; + vertical-align: top; +} +#dialog fieldset th { color: #999; font-weight: bold; + text-align: right; +} +#dialog fieldset input { background: #e9e9e9; vertical-align: middle; } +#dialog fieldset input.error { background: #f9e4e4; } +#dialog fieldset div.error { padding-top: .3em; color: #b33; } +#dialog .buttons { padding: 0 .5em .5em; text-align: right; } +#dialog .buttons button { background: #444; border: 1px solid #aaa; + color: #ddd; cursor: pointer; font-size: 90%; font-weight: normal; + margin: 0 0 0 5px; padding: .2em 2em; -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} +#dialog .buttons button[type=submit] { font-weight: bold; } +#dialog .buttons button:hover { background: #555; } +#dialog .buttons button:active { background: #333; color: #fff; } + +/* View selector */ + +#switch { color: #666; float: right; font-size: 90%; font-weight: bold; + line-height: 16px; padding: 5px; +} +#switch select { font-size: 90%; } + +/* View function editing */ + +#viewcode { background: #fff; border: 1px solid; + border-color: #999 #ddd #ddd #999; margin: 0 0 1em; padding: 0 .5em; +} +#viewcode div { background-color: #e9e9e9; border: 1px solid; + border-color: #ddd #ddd #e9e9e9 #ddd; color: #333; margin: 0 -.5em; + padding: 0 .5em 2px; +} +#viewcode .top { color: #aaa; font-size: 95%; } +#viewcode .top a { float: right; font-size: 90%; line-height: 1.4em; + padding: 2px 2px 0 0; +} +#viewcode .top a:link, #viewcode .top a:visited { color: #999; } +#viewcode label { background: url(../image/twisty.gif) 0 3px no-repeat; + color: #666; cursor: pointer; display: block; padding: 2px 0 0 15px; +} +#viewcode div.bottom, #viewcode textarea { display: none; } +#viewcode textarea { border: none; color: #333; max-width: 100%; + min-height: 50px; padding: .5em 0; width: 100%; +} +#viewcode div.bottom { border-bottom: none; padding: 1px 3px; } +#viewcode div.bottom button { font-size: 90%; margin: 0 1em 0 0; + padding-left: 2em; padding-right: 2em; +} +*html #viewcode div.bottom button { padding: 0 .5em; } +*+html #viewcode div.bottom button { padding: 0 .5em; } +#viewcode div.bottom button.revert, #viewcode div.bottom button.save, +#viewcode div.bottom button.saveas { + float: right; margin: 0 0 0 1em; +} +#viewcode div.bottom button.save { font-weight: bold; } +#viewcode.expanded label { background-position: 0 -96px; } +#viewcode.expanded textarea, #viewcode.expanded div.bottom { display: block; } + +/* Documents table */ + +#documents thead th { width: 50%; } +#documents tbody.content td { color: #666; + font: normal 11px "DejaVu Sans Mono",Monaco,monospace; +} +#documents tbody.content td.key { color: #333; } +#documents tbody.content td.key a { display: block; } +#documents tbody.content td.key em { font-style: normal; } +#documents tbody.content td.key span.docid { color: #999; + font: normal 10px Arial,Helvetica,sans-serif; +} +#documents tbody.content td.value { font-size: 10px; } + +/* Document fields table */ + +#fields { table-layout: fixed; } +#fields col.field { width: 33%; } +#fields tbody.content th { padding-left: 25px; padding-right: 48px; } +#fields tbody.content th button { + background-image: url(../image/delete-mini.gif); +} +#fields tbody.content th b { display: block; padding: 2px; } +#fields tbody.content td { padding-left: 14px; padding-right: 48px; } +#fields tbody.content td code { color: #999; display: block; font-size: 11px; + padding: 2px; +} +#fields tbody.content td code.string { color: #393; } +#fields tbody.content td code.number, #fields tbody.content td code.boolean { + color: #339; +} +#fields tbody.content td dl { margin: 0; padding: 0; } +#fields tbody.content td dt { + background: transparent url(../image/toggle-collapse.gif) 0 3px no-repeat; + clear: left; cursor: pointer; line-height: 1em; margin-left: -12px; + padding-left: 14px; +} +#fields tbody.content td dd { line-height: 1em; margin: 0; + padding: 0 0 0 1em; +} +#fields tbody.content td dt.collapsed { + background-image: url(../image/toggle-expand.gif); +} +#fields tbody.content td dt.inline { background-image: none; cursor: default; + float: left; margin-left: 0; padding-left: 2px; padding-right: .5em; + padding-top: 2px; +} +#fields tbody.content input, #fields tbody.content textarea { + background: #fff; border: 1px solid; border-color: #999 #ddd #ddd #999; + margin: 0; padding: 1px; width: 100%; +} +#fields tbody.content th input { font-family: inherit; font-size: inherit; + font-weight: bold; +} +#fields tbody.content td input, #fields tbody.content td textarea { + font: 10px normal "DejaVu Sans Mono",Monaco,monospace; +} +#fields tbody.content input.invalid, +#fields tbody.content textarea.invalid { + background: #f9f4f4; border-color: #b66 #ebb #ebb #b66; +} +#fields tbody.content div.tools { margin: 2px 2px 0; float: right; + margin-right: -45px; +} +#fields tbody.content div.tools button { background: transparent 0 0 no-repeat; + border: none; cursor: pointer; display: block; float: left; margin: 0 .2em; + width: 11px; height: 11px; +} +#fields tbody.content div.tools button:hover { background-position: 0 -22px; } +#fields tbody.content div.tools button:active { background-position: 0 -44px; } +#fields tbody.content div.tools button.apply { + background-image: url(../image/apply.gif); +} +#fields tbody.content div.tools button.cancel { + background-image: url(../image/cancel.gif); +} +#fields tbody.content div.error { color: #d33; } + +/* Test suite */ + +#tests { table-layout: fixed; } +#tests thead th.name { width: 20%; } +#tests thead th.status { padding-left: 20px; width: 10em; } +#tests thead th.duration { text-align: right; width: 7em; } +#tests tbody.content th { cursor: help; padding-left: 25px; + white-space: nowrap; +} +#tests tbody.content th button { + background-image: url(../image/run-mini.gif); +} +#tests tbody.content td.duration { text-align: right; width: 6em; } +#tests tbody.content td.status { background-position: 5px 8px; + background-repeat: no-repeat; color: #999; padding-left: 20px; +} +#tests tbody.content td.details { width: 50%; overflow: auto; } +#tests tbody.content td.details a { border-bottom: 1px dashed #ccc; + color: #999; float: right; font-size: 85%; +} +#tests tbody.content td.details ol { color: #999; margin: 0; + padding: 0 0 0 1.5em; +} +#tests tbody.content td.details ol b { color: #333; font-weight: normal; } +#tests tbody.content td.details ol code { color: #c00; font-size: 100%; } +#tests tbody.content td.details ol code.error { white-space: pre; } +#tests tbody.content td.running { + background-image: url(../image/running.gif); color: #333; +} +#tests tbody.content td.success { + background-image: url(../image/test_success.gif); color: #060; +} +#tests tbody.content td.error, #tests tbody.content td.failure { + background-image: url(../image/test_failure.gif); color: #c00; +} + +/* Replication */ + +form#replicator { background: #f4f4f4; border: 1px solid; + border-color: #999 #ccc #ccc #999; margin: .5em 1em 1.5em; padding: .5em; + -moz-border-radius: 7px; -webkit-border-radius: 7px; +} +form#replicator fieldset { float: left; padding: 1px; } +form#replicator p.swap { float: left; margin: 2em 0 0; padding: 1px 1em; } +form#replicator p.swap button { background: transparent; border: none; + color: #666; cursor: pointer; font-size: 150%; +} +form#replicator p.swap button:hover { color: #000; } +form#replicator p.actions { padding: 1px; clear: left; margin: 0; + text-align: right; +} |