summaryrefslogtreecommitdiffstats
path: root/etherpad/src/static
diff options
context:
space:
mode:
authorAlexander Sulfrian <alexander@sulfrian.net>2010-06-08 09:01:43 +0200
committerAlexander Sulfrian <alexander@sulfrian.net>2010-06-08 09:01:43 +0200
commitd1fa08fdc9cb11dccee76d668ff85df30458c295 (patch)
tree1d19df6405103577d872902486792e8c23bce711 /etherpad/src/static
parentd7c5ad7d6263fd1baf9bfdbaa4c50b70ef2fbdb2 (diff)
parent70d1f9d6fcaefe611e778b8dbf3bafea8934aa08 (diff)
downloadetherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.gz
etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.tar.bz2
etherpad-d1fa08fdc9cb11dccee76d668ff85df30458c295.zip
Merge remote branch 'upstream/master'
Conflicts: etherpad/src/etherpad/control/pro/admin/pro_admin_control.js etherpad/src/etherpad/control/pro/pro_main_control.js etherpad/src/etherpad/control/pro_help_control.js etherpad/src/etherpad/globals.js etherpad/src/etherpad/legacy_urls.js etherpad/src/etherpad/pne/pne_utils.js etherpad/src/etherpad/pro/pro_utils.js etherpad/src/main.js etherpad/src/plugins/fileUpload/templates/fileUpload.ejs etherpad/src/plugins/testplugin/templates/page.ejs etherpad/src/static/css/pad2_ejs.css etherpad/src/static/css/pro-help.css etherpad/src/static/img/jun09/pad/protop.gif etherpad/src/static/js/store.js etherpad/src/themes/default/templates/framed/framedheader-pro.ejs etherpad/src/themes/default/templates/main/home.ejs etherpad/src/themes/default/templates/pro-help/main.ejs etherpad/src/themes/default/templates/pro-help/pro-help-template.ejs infrastructure/com.etherpad/licensing.scala trunk/etherpad/src/etherpad/collab/ace/contentcollector.js trunk/etherpad/src/etherpad/collab/ace/linestylefilter.js trunk/etherpad/src/static/css/home-opensource.css trunk/etherpad/src/static/js/ace.js trunk/etherpad/src/static/js/linestylefilter_client.js trunk/etherpad/src/templates/email/eepnet_license_info.ejs trunk/etherpad/src/templates/pad/pad_body2.ejs trunk/etherpad/src/templates/pad/pad_content.ejs trunk/etherpad/src/templates/pad/padfull_body.ejs trunk/etherpad/src/templates/pro/admin/pne-license-manager.ejs
Diffstat (limited to 'etherpad/src/static')
-rw-r--r--etherpad/src/static/crossdomain.xml12
-rw-r--r--etherpad/src/static/css/admin/admin-stats.css183
-rw-r--r--etherpad/src/static/css/admin/pluginmanager.css62
-rw-r--r--etherpad/src/static/css/broadcast.css386
-rw-r--r--etherpad/src/static/css/etherpad.css770
-rw-r--r--etherpad/src/static/css/framedpage.css175
-rw-r--r--etherpad/src/static/css/global-pro-account.css52
-rw-r--r--etherpad/src/static/css/home-opensource.css40
-rw-r--r--etherpad/src/static/css/lib/jquery.contextmenu.css244
-rw-r--r--etherpad/src/static/css/pad2_ejs.css910
-rw-r--r--etherpad/src/static/css/pro-signup.css69
-rw-r--r--etherpad/src/static/css/pro/account.css254
-rw-r--r--etherpad/src/static/css/pro/framedpage-pro.css125
-rw-r--r--etherpad/src/static/css/pro/padlist.css115
-rw-r--r--etherpad/src/static/css/pro/pro-admin.css343
-rw-r--r--etherpad/src/static/css/pro/pro-home.css65
-rw-r--r--etherpad/src/static/favicon.icobin0 -> 1354 bytes
-rw-r--r--etherpad/src/static/img/davy/bg/home-createpad.pngbin0 -> 4327 bytes
-rw-r--r--etherpad/src/static/img/davy/bg/product.pngbin0 -> 161 bytes
-rw-r--r--etherpad/src/static/img/davy/btn/createpad-small.gifbin0 -> 3148 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/backgrad.gifbin0 -> 697 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/colorpicker.gifbin0 -> 2020 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/connectingbar.gifbin0 -> 10819 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/docpaneledge2.pngbin0 -> 635 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/docpanelmiddle2.pngbin0 -> 295 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_background.gifbin0 -> 181 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_background_left.gifbin0 -> 204 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_background_right.gifbin0 -> 867 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_bold.gifbin0 -> 224 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_clearauthorship.gifbin0 -> 397 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_groupleft.gifbin0 -> 186 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_groupright.gifbin0 -> 185 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_indent.gifbin0 -> 99 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_insertunorderedlist.gifbin0 -> 147 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_italic.gifbin0 -> 201 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_outdent.gifbin0 -> 99 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_redo.gifbin0 -> 232 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_save.gifbin0 -> 139 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_strikethrough.gifbin0 -> 336 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_underline.gifbin0 -> 223 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbar_undo.gifbin0 -> 230 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/editbarback.gifbin0 -> 368 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/feedbackbox2.gifbin0 -> 6262 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/fileicons.gifbin0 -> 1397 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/hdraggie.gifbin0 -> 453 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/icon_import_export.gifbin0 -> 96 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/icon_pad_options.gifbin0 -> 67 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/icon_saved_revisions.gifbin0 -> 81 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/icon_security.gifbin0 -> 87 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/icon_time_slider.gifbin0 -> 74 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/inviteshare.gifbin0 -> 511 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/inviteshare2.gifbin0 -> 1836 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/layoutbuttons.gifbin0 -> 3750 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/ok_or_cancel.gifbin0 -> 1630 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/overlay2.pngbin0 -> 149 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/padtop5.gifbin0 -> 3872 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/padtop5.pngbin0 -> 6604 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/padtop5.xcfbin0 -> 44819 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/padtopback2.gifbin0 -> 372 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/public.gifbin0 -> 1034 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/roundcorner_left.gifbin0 -> 123 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/roundcorner_right.gifbin0 -> 131 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/roundcorner_right_orange.gifbin0 -> 171 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/savedrevarrows.gifbin0 -> 866 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/savedrevsgfx2.gifbin0 -> 1904 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/sharebox4.gifbin0 -> 5788 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/sharedistri.gifbin0 -> 85 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/syncdone.gifbin0 -> 211 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/syncing.gifbin0 -> 673 bytes
-rw-r--r--etherpad/src/static/img/jun09/pad/viewbargfx.gifbin0 -> 155 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-cyan-menu-item-hover.gifbin0 -> 52 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-menu-item-hover.gifbin0 -> 52 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-semitransparent-menu-item-hover.pngbin0 -> 2837 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-human-menu-item-hover.gifbin0 -> 195 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-osx-menu-item-hover.gifbin0 -> 87 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-bg.gifbin0 -> 64 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-menu-item-hover.gifbin0 -> 347 bytes
-rw-r--r--etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-xp-bg.gifbin0 -> 223 bytes
-rw-r--r--etherpad/src/static/img/may09/doc.gifbin0 -> 632 bytes
-rw-r--r--etherpad/src/static/img/may09/html.gifbin0 -> 1040 bytes
-rw-r--r--etherpad/src/static/img/may09/pdf.gifbin0 -> 398 bytes
-rw-r--r--etherpad/src/static/img/may09/txt.gifbin0 -> 381 bytes
-rw-r--r--etherpad/src/static/img/misc/status-ball.gifbin0 -> 1553 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/crushed_button_depressed.pngbin0 -> 4134 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/crushed_button_undepressed.pngbin0 -> 4166 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/crushed_current_location.pngbin0 -> 1009 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/crushed_timeslider_mockup.pngbin0 -> 8164 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/current_location.pngbin0 -> 1100 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/pause.pngbin0 -> 2883 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/play.pngbin0 -> 3017 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/play_button.pngbin0 -> 4867 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/star.pngbin0 -> 3241 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/star_selected.pngbin0 -> 3242 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/stepper_buttons.pngbin0 -> 4858 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/timeslider_background.pngbin0 -> 915 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/timeslider_left.pngbin0 -> 1653 bytes
-rw-r--r--etherpad/src/static/img/pad/timeslider/timeslider_right.pngbin0 -> 1581 bytes
-rw-r--r--etherpad/src/static/img/pro/box/blue-boxtop.gifbin0 -> 523 bytes
-rw-r--r--etherpad/src/static/img/pro/buttons/bluebutton120.gifbin0 -> 951 bytes
-rw-r--r--etherpad/src/static/img/pro/header/pro-header-logo.pngbin0 -> 5527 bytes
-rw-r--r--etherpad/src/static/img/pro/header/pro-header-plustopnav-back.gifbin0 -> 474 bytes
-rw-r--r--etherpad/src/static/img/pro/padlist/gear-drop.gifbin0 -> 300 bytes
-rw-r--r--etherpad/src/static/img/pro/padlist/paper-icon.gifbin0 -> 619 bytes
-rw-r--r--etherpad/src/static/img/pro/padlist/trash-icon.gifbin0 -> 1080 bytes
-rw-r--r--etherpad/src/static/img/pro/topnav/pro-topnav-back.gifbin0 -> 137 bytes
-rw-r--r--etherpad/src/static/img/pro/topnav/pro-topnav-notch.gifbin0 -> 92 bytes
-rw-r--r--etherpad/src/static/js/billing.js111
-rw-r--r--etherpad/src/static/js/billing_shared.js94
-rw-r--r--etherpad/src/static/js/broadcast.js610
-rw-r--r--etherpad/src/static/js/broadcast_revisions.js119
-rw-r--r--etherpad/src/static/js/broadcast_slider.js401
-rw-r--r--etherpad/src/static/js/collab_client.js628
-rw-r--r--etherpad/src/static/js/confirmation.js21
-rw-r--r--etherpad/src/static/js/connection_diagnostics.js126
-rw-r--r--etherpad/src/static/js/draggable.js60
-rw-r--r--etherpad/src/static/js/etherpad.js217
-rw-r--r--etherpad/src/static/js/jquery-1.2.6.js3549
-rw-r--r--etherpad/src/static/js/jquery-1.3.2.js4376
-rw-r--r--etherpad/src/static/js/json2.js498
-rw-r--r--etherpad/src/static/js/lib/jquery.contextmenu.js284
-rw-r--r--etherpad/src/static/js/pad2.js591
-rw-r--r--etherpad/src/static/js/pad_chat.js295
-rw-r--r--etherpad/src/static/js/pad_connectionstatus.js63
-rw-r--r--etherpad/src/static/js/pad_cookie.js101
-rw-r--r--etherpad/src/static/js/pad_docbar.js347
-rw-r--r--etherpad/src/static/js/pad_editbar.js107
-rw-r--r--etherpad/src/static/js/pad_editor.js136
-rw-r--r--etherpad/src/static/js/pad_impexp.js187
-rw-r--r--etherpad/src/static/js/pad_modals.js364
-rw-r--r--etherpad/src/static/js/pad_savedrevs.js408
-rw-r--r--etherpad/src/static/js/pad_userlist.js604
-rw-r--r--etherpad/src/static/js/pad_utils.js359
-rw-r--r--etherpad/src/static/js/plugins.js22
-rw-r--r--etherpad/src/static/js/pricing.js19
-rw-r--r--etherpad/src/static/js/pro/guest-knock-client.js53
-rw-r--r--etherpad/src/static/js/pro/pro-padlist-client.js104
-rw-r--r--etherpad/src/static/js/pro/signin-client.js27
-rw-r--r--etherpad/src/static/js/pulse.jquery.js105
-rw-r--r--etherpad/src/static/js/statpage.js143
-rw-r--r--etherpad/src/static/js/store.js116
-rw-r--r--etherpad/src/static/js/swfobject.js24
-rw-r--r--etherpad/src/static/js/timeslider.js663
-rw-r--r--etherpad/src/static/js/undo-xpopup.js25
-rw-r--r--etherpad/src/static/robots.txt1
144 files changed, 19763 insertions, 0 deletions
diff --git a/etherpad/src/static/crossdomain.xml b/etherpad/src/static/crossdomain.xml
new file mode 100644
index 0000000..9e76390
--- /dev/null
+++ b/etherpad/src/static/crossdomain.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<cross-domain-policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd">
+<site-control permitted-cross-domain-policies="all"/>
+<allow-http-request-headers-from domain="*" headers="*"/>
+<allow-access-from domain="*.pad.spline.de" to-ports="*"/>
+<allow-access-from domain="pad.spline.de" to-ports="*"/>
+<allow-access-from domain="*.pad.spline.inf.fu-berlin.de" to-ports="*"/>
+<allow-access-from domain="pad.spline.inf.fu-berlin.de" to-ports="*"/>
+<allow-access-from domain="*.pad.spline.nomad" to-ports="*"/>
+<allow-access-from domain="pad.spline.nomad" to-ports="*"/>
+</cross-domain-policy>
diff --git a/etherpad/src/static/css/admin/admin-stats.css b/etherpad/src/static/css/admin/admin-stats.css
new file mode 100644
index 0000000..94e0d19
--- /dev/null
+++ b/etherpad/src/static/css/admin/admin-stats.css
@@ -0,0 +1,183 @@
+#backtoadmin {
+ color: #88f;
+ padding: 4px;
+ text-decoration: none;
+}
+
+#topnav {
+ margin-top: .5em;
+ margin-bottom: 1em;
+ font-family: Verdana, sans-serif;
+ font-size: 1.2em;
+}
+
+#topnav ul {
+ padding: 0;
+ margin: 0 0 0 12px;
+}
+
+#topnav ul li {
+ float: left;
+ display: inline;
+}
+#topnav ul li a {
+ display: block;
+ padding: .4em 1em;
+ text-decoration: none;
+ color: blue;
+}
+#topnav ul li.selected a {
+ background: #fff;
+ color: black;
+ border-bottom: 1px solid black;
+}
+
+/* ----- */
+
+/*.statbox {
+ display: box;
+ overflow: hidden;
+ padding-left: 8px;
+}
+*/
+
+.latesttable {
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+}
+
+.latesttable td {
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 2px 6px;
+}
+
+/*
+.statbox table td span { }
+
+.statbox .stat-title {
+ display: block;
+ font-family: Verdana, sans-serif;
+ font-size: 1.4em;
+ text-decoration: none;
+ border-bottom: 1px solid #bbb;
+ margin-top: 1em;
+}
+
+.statbox .stat-table {
+ float: left;
+ padding: 4px;
+}
+.statbox .stat-graph {
+ float: left;
+}
+*/
+form#statprefs {
+ background: #eee;
+ padding: 1em;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ margin-top: 1em;
+}
+
+a.viewall {
+ margin-left: 8px;
+}
+
+div.statentry {
+/* width: 800px;*/
+ border: 1px solid #060;
+ background: #afa;
+ margin: 1em;
+}
+
+body {
+ margin: 0;
+ min-width: 800px;
+}
+
+div.warning {
+ background: #ffa;
+ border: 1px solid #630;
+}
+
+div.error {
+ background: #faa;
+ border: 1px solid #600;
+}
+
+.statentry h2 {
+ font-size: 13pt;
+ font-family: sans-serif;
+ background: #0a0;
+ color: white;
+ padding: 5px;
+ margin: 0;
+ cursor: pointer;
+}
+
+.statentry h3 {
+ font-size: 13pt;
+ font-family: sans-serif;
+ font-weight: normal;
+ margin: 3px;
+ padding: 0;
+}
+
+.statentry h4 {
+ font-size: 12pt;
+ font-weight: bold;
+ margin: 3px;
+}
+
+.statentry h2:hover {
+ text-decoration: underline;
+}
+
+.warning h2 {
+ background: #ea0;
+}
+
+.error h2 {
+ background: #a00;
+}
+
+.statentry table {
+ width: 100%;
+}
+
+.statentry .graph {
+ background: white;
+ padding: 2px;
+ width: 600px;
+}
+
+.graph .datalinks {
+ margin-top: 10px;
+ font-size: .8em;
+ text-align: right;
+ color: gray;
+}
+
+.graph .datalinks a {
+ color: gray;
+}
+
+.statentry .latest {
+ background: white;
+ vertical-align: top;
+ font-size:;
+}
+
+.statbody {
+ display: none;
+}
+
+/*div.categorywrapper {
+ -moz-column-width: 500px;
+ -moz-column-gap: 20px;
+ -webkit-column-width: 500px;
+ -webkit-column-gap: 20px;
+ column-width: 500px;
+ column-gap: 20px;
+}*/ \ No newline at end of file
diff --git a/etherpad/src/static/css/admin/pluginmanager.css b/etherpad/src/static/css/admin/pluginmanager.css
new file mode 100644
index 0000000..136a713
--- /dev/null
+++ b/etherpad/src/static/css/admin/pluginmanager.css
@@ -0,0 +1,62 @@
+#editorcontainer {
+ padding: 5pt;
+}
+
+#editorcontainerbox {
+ overflow: auto;
+ height: auto;
+}
+
+#editbarinner {
+ line-height: 36px;
+ font-size: 16px;
+ padding-left: 6pt;
+}
+
+#editbarinner a {
+ font-size: 12px;
+}
+
+#editorcontainerbox table {
+ margin: 10pt;
+ border-collapse: collapse;
+}
+
+#editorcontainerbox table tr th {
+ font-weight: bold;
+ background: #c3c3c3;
+}
+
+#editorcontainerbox table tr td,
+#editorcontainerbox table tr th {
+ border: 1px solid #c3c3c3;
+ padding: 2pt;
+}
+
+#editorcontainerbox table tr:first-child th,
+#editorcontainerbox table tr:first-child td {
+ border-top-color: #e6e6e6;
+}
+
+#editorcontainerbox table tr th:first-child,
+#editorcontainerbox table tr td:first-child {
+ border-left-color: #e6e6e6;
+}
+
+.mousover_parent .mouseover_child {
+ display: none;
+ position: absolute;
+ padding: 4pt;
+
+ border-top-color: #e6e6e6;
+ border-bottom-color: #c3c3c3;
+ border-left-color: #e6e6e6;
+ border-right-color: #c3c3c3;
+ border: 1px solid;
+
+ background: #ffffff;
+}
+
+.mousover_parent:hover .mouseover_child {
+ display: block;
+}
diff --git a/etherpad/src/static/css/broadcast.css b/etherpad/src/static/css/broadcast.css
new file mode 100644
index 0000000..afb65b8
--- /dev/null
+++ b/etherpad/src/static/css/broadcast.css
@@ -0,0 +1,386 @@
+*,html.body { margin: 0; padding: 0; }
+h1, h2, h3, h4, h5, h6 { display: inline; line-height: 2em; }
+
+.clear { clear: both; }
+
+html { font-size: 62.5%; }
+
+body { background: #ebebeb url(/static/img/jun09/pad/backgrad.gif) repeat-x left top; }
+body, textarea { font-family: Arial, sans-serif; }
+
+#topbar { height: 25px; background: #326cbd url(/static/img/jun09/pad/padtopback2.gif) repeat-x left top;
+ position: relative; }
+
+#padpage { margin-left: auto; margin-right: auto; width: 914px; vertical-align: top;}
+
+#topbarleft { float: left; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/padtop4.png) no-repeat left top; width: 5px; }
+#topbarright { float: right; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/padtop4.png) no-repeat right top; width: 5px; }
+
+
+.propad #topbar { background: #2c2c2c url(/static/img/jun09/pad/protop.png) repeat-x 0 -25px; }
+.propad #topbarleft { background: url(/static/img/jun09/pad/protop.png) no-repeat left top; }
+.propad #topbarright { background: url(/static/img/jun09/pad/protop.png) no-repeat right top; }
+
+a#backtoprosite, #accountnav {
+ display: block; position: absolute; height: 15px; line-height: 15px;
+ width: auto; top: 5px; font-size: 1.2em;
+}
+a#backtoprosite, #accountnav a { color: #cde7ff; text-decoration: underline; }
+#accountnav { right: 10px; color: #fff; }
+
+
+#topbarcenter { margin-left: 150px; margin-right: 150px; }
+a#topbaretherpad { margin-left: auto; margin-right: auto; display: block; width: 127px;
+ position: relative; top: 0px; height: 0; padding-top: 25px;
+ background: url(/static/img/jun09/pad/padtop4.png) no-repeat -397px 0px; overflow: hidden; }
+
+.propad a#topbaretherpad { background: url(/static/img/jun09/pad/protop.png) no-repeat -397px 0px; }
+
+#padmain {
+ margin: 7px;
+ margin-top: 5px;
+ margin-right: 0px;
+ padding: 19px;
+ padding-top:16px;
+ border: 1px solid rgb(194, 194, 194);
+ background-color: white;
+ min-height: 500px;
+ font-family: Arial, sans-serif;
+ font-size: 1.2em;
+ line-height: 17px;
+ width: 670px;
+ position: absolute;
+ top:27px;
+}
+
+/*
+ * Fancy title bar
+ */
+#padmain h1 {
+ font-family: Verdana, sans-serif;
+ font-size: 1.5em;
+ font-weight: 400;
+ display: inline-block;
+ display: -moz-inline-box;
+ padding-top: 4px;
+ padding-bottom: 10px;
+}
+
+#padcontent {
+ font-size: 0.93em;
+ line-height: 1.5em;
+ font-weight: 25;
+}
+
+#titlebar {
+ margin-bottom: 25px;
+ height: 20px;
+ width: auto;
+}
+
+#titlebar #revision {
+ float: right;
+ width: auto;
+ text-align: right;
+ vertical-align: top;
+}
+
+#revision #revision_label {
+ font-weight: bold;
+ font-size: 1.0em;
+ line-height: 1.4em;
+}
+
+#revision #revision_date {
+ font-weight: light;
+ font-size: 0.8em;
+ color: rgb(184, 184, 184);
+}
+
+#rightbars {
+ margin-left: 730px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ margin-right: 7px;
+ position: absolute;
+ top:27px;
+}
+
+#rightbar {
+ width: 143px;
+ background-color: white;
+ border: 1px solid rgb(194, 194, 194);
+ padding: 16px;
+ padding-top: 13px;
+ font-size: 1.20em;
+ line-height: 1.8em;
+ vertical-align: top;
+}
+
+#rightbars h2 {
+ font-weight: 700;
+ font-size: 1.2em;
+ padding-top: 20px;
+ padding-bottom: 4px;
+}
+
+#rightbar img {
+ padding-left: 4px;
+ padding-right: 8px;
+ vertical-align: text-bottom;
+}
+#rightbar a {
+ color: rgb(50, 132, 213);
+ text-decoration: none;
+}
+
+#legend {
+ width: 143px;
+ background-color: white;
+ border: 1px solid rgb(194, 194, 194);
+ padding: 16px;
+ padding-top: 0px;
+ font-size: 1.20em;
+ line-height: 1.8em;
+ vertical-align: top;
+ margin-top: 10px;
+}
+#legend h2 {
+ padding-top: 10px;
+}
+
+#authorstable {
+ vertical-align: middle;
+}
+
+#authorstable div.swatch {
+ width:15px;
+ height:15px;
+ margin: 5px;
+ margin-top:3px;
+ margin-right: 14px;
+ border: rgb(149, 149, 149) 1px solid;
+}
+
+#rightbar h2 {
+ font-weight: 700;
+ font-size: 1.2em;
+ padding-top: 20px;
+ padding-bottom: 4px;
+}
+
+#rightbar a {
+ color: rgb(50, 132, 213);
+ text-decoration: none;
+}
+
+#timeslider-wrapper {
+ position: relative;
+ left: 0px;
+ right: 0px;
+ top: 0px;
+}
+
+#timeslider-left {
+ position: absolute;
+ left:-2px;
+ background-image: url(/static/img/pad/timeslider/timeslider_left.png);
+ width: 134px;
+ height: 63px;
+}
+
+#timeslider-right {
+ position: absolute;
+ top:0px;
+ right:-2px;
+ background-image: url(/static/img/pad/timeslider/timeslider_right.png);
+ width: 155px;
+ height: 63px;
+}
+
+
+#timeslider {
+ margin:7px;
+ margin-bottom: 0px;
+ width: 894px;
+ height: 63px;
+ margin-left: 9px;
+ margin-right: 9px;
+ background-image: url(/static/img/pad/timeslider/timeslider_background.png);
+ position: relative;
+}
+
+div#timeslider #timeslider-slider {
+ position: absolute;
+ left: 0px;
+ top: 1px;
+ height: 61px;
+ width: 100%;
+}
+
+div#ui-slider-handle {
+ width: 13px;
+ height: 61px;
+ background-image: url(/static/img/pad/timeslider/crushed_current_location.png);
+ cursor: pointer;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ user-select: none;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+* html div#ui-slider-handle { /* IE 6/7 */
+ background-image: url(/static/img/pad/timeslider/current_location.gif);
+}
+
+div#ui-slider-bar {
+ position: relative;
+ margin-right: 148px;
+ height: 35px;
+ margin-left: 5px;
+ top: 20px;
+ cursor: pointer;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ user-select: none;
+
+}
+
+div#timeslider div#playpause_button {
+ background-image: url(/static/img/pad/timeslider/crushed_button_undepressed.png);
+ width: 47px;
+ height: 47px;
+ position: absolute;
+ right: 77px;
+ top: 9px;
+}
+
+div#timeslider div#playpause_button div#playpause_button_icon {
+ background-image: url(/static/img/pad/timeslider/play.png);
+ width: 47px;
+ height: 47px;
+ position: absolute;
+ top :0px;
+ left:0px;
+}
+* html div#timeslider div#playpause_button div#playpause_button_icon {
+ background-image: url(/static/img/pad/timeslider/play.gif); /* IE 6/7 */
+}
+
+div#timeslider div#playpause_button div.pause#playpause_button_icon {
+ background-image: url(/static/img/pad/timeslider/pause.png);
+}
+* html div#timeslider div#playpause_button div.pause#playpause_button_icon {
+ background-image: url(/static/img/pad/timeslider/pause.gif); /* IE 6/7 */
+}
+
+div #timeslider div#steppers div#leftstar {
+ position: absolute;
+ right: 34px;
+ top: 8px;
+ width:30px;
+ height:21px;
+ background: url(/static/img/pad/timeslider/stepper_buttons.png) 0px 44px;
+ overflow:hidden;
+}
+
+div #timeslider div#steppers div#rightstar {
+ position: absolute;
+ right: 5px;
+ top: 8px;
+ width:29px;
+ height:21px;
+ background: url(/static/img/pad/timeslider/stepper_buttons.png) 29px 44px;
+ overflow:hidden;
+}
+
+div #timeslider div#steppers div#leftstep {
+ position: absolute;
+ right: 34px;
+ top: 33px;
+ width:30px;
+ height:21px;
+ background: url(/static/img/pad/timeslider/stepper_buttons.png) 0px 22px;
+ overflow:hidden;
+}
+
+div #timeslider div#steppers div#rightstep {
+ position: absolute;
+ right: 5px;
+ top: 33px;
+ width:29px;
+ height:21px;
+ background: url(/static/img/pad/timeslider/stepper_buttons.png) 29px 22px;
+ overflow:hidden;
+}
+
+#timeslider div.star {
+ position: absolute;
+ top: 40px;
+ background-image: url(/static/img/pad/timeslider/star.png);
+ width: 15px;
+ height: 16px;
+ cursor: pointer;
+}
+* html #timeslider div.star {
+ background-image: url(/static/img/pad/timeslider/star.gif); /* IE 6/7 */
+}
+
+#timeslider div#timer {
+ position: absolute;
+ font-family: Arial, sans-serif;
+ left: 7px;
+ top: 9px;
+ width: 122px;
+ text-align: center;
+ color: white;
+ font-size: 11px;
+}
+
+#padcontent ul, ol, li {
+ padding: 0;
+ margin: 0;
+}
+#padcontent ul { margin-left: 1.5em; }
+#padcontent ul ul { margin-left: 0 !important; }
+#padcontent ul.list-bullet1 { margin-left: 1.5em; }
+#padcontent ul.list-bullet2 { margin-left: 3em; }
+#padcontent ul.list-bullet3 { margin-left: 4.5em; }
+#padcontent ul.list-bullet4 { margin-left: 6em; }
+#padcontent ul.list-bullet5 { margin-left: 7.5em; }
+#padcontent ul.list-bullet6 { margin-left: 9em; }
+#padcontent ul.list-bullet7 { margin-left: 10.5em; }
+#padcontent ul.list-bullet8 { margin-left: 12em; }
+
+#padcontent ul { list-style-type: disc; }
+#padcontent ul.list-bullet1 { list-style-type: disc; }
+#padcontent ul.list-bullet2 { list-style-type: circle; }
+#padcontent ul.list-bullet3 { list-style-type: square; }
+#padcontent ul.list-bullet4 { list-style-type: disc; }
+#padcontent ul.list-bullet5 { list-style-type: circle; }
+#padcontent ul.list-bullet6 { list-style-type: square; }
+#padcontent ul.list-bullet7 { list-style-type: disc; }
+#padcontent ul.list-bullet8 { list-style-type: circle; }
+
+#error {
+ position: absolute;
+ margin-left: 9px;
+ top:4px;
+ left: 0px;
+ right:9px;
+/* width:894px;*/
+ height:34px;
+ background-color: rgb(247, 247, 247);
+ z-index:10;
+ text-align: center;
+ font-family: Verdana;
+ padding-top: 20px;
+ font-size: 16px;
+}
+#error a {
+ color: rgb(50, 132, 213);
+ text-decoration: none;
+}
diff --git a/etherpad/src/static/css/etherpad.css b/etherpad/src/static/css/etherpad.css
new file mode 100644
index 0000000..70bf464
--- /dev/null
+++ b/etherpad/src/static/css/etherpad.css
@@ -0,0 +1,770 @@
+/*-----
+ Reset
+-----*/
+
+ html, body, div, span, applet, object, iframe,
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+ a, abbr, acronym, address, big, cite, code,
+ del, dfn, em, font, img, ins, kbd, q, s, samp,
+ small, strike, strong, sub, sup, tt, var,
+ dl, dt, dd, ol, ul, li,
+ fieldset, form, label, legend,
+ table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 1em;
+ font-family: inherit;
+ vertical-align: baseline;
+ }
+ :focus {
+ outline: 0;
+ }
+ body {
+ line-height: 1;
+ color: #333;
+ background: #f7f7f7;
+ font-size: 75%;
+ }
+ html>body {
+ font-size: 12px;
+ }
+ ol, ul {
+ list-style: none;
+ }
+ table {
+ border-collapse: separate;
+ border-spacing: 0;
+ }
+ caption, th, td {
+ text-align: left;
+ font-weight: normal;
+ }
+ blockquote:before, blockquote:after,
+ q:before, q:after {
+ content: "";
+ }
+ blockquote, q {
+ quotes: "" "";
+ }
+
+/*----------------------------------------------------------------*/
+/* global */
+/*----------------------------------------------------------------*/
+a.obfuscemail {
+ text-decoration: none;
+}
+
+a:link,a:visited {
+ text-decoration: none;
+ color: #004ca8;
+}
+a:hover {
+ text-decoration: underline;
+ color: #005CCB;
+}
+.clear {
+ clear: both;
+}
+em {
+ font-style: italic;
+}
+strong {
+ font-weight: bold;
+}
+
+/*----------------------------------------------------------------*/
+/* newpad button */
+/*----------------------------------------------------------------*/
+
+.fpcontent .newpadbuttonwrap {
+ width: 152px;
+ height: 35px;
+ background: url(/static/img/davy/btn/createpad-small.gif) no-repeat bottom left;
+}
+.fpcontent .newpadbuttonwrap a {
+ width: 152px;
+ position: relative;
+ padding: 35px 0 0 0;
+ overflow: hidden;
+ background: transparent url(/static/img/davy/btn/createpad-small.gif) no-repeat top left;
+ height: 0px;
+ display: block;
+}
+
+/*----------------------------------------------------------------*/
+/* 500 error page */
+/*----------------------------------------------------------------*/
+#errorpage .error500 {
+ font-size: 1em;
+ background: #fcc;
+ border: 1px solid #f00;
+ padding: 1em;
+ margin: 1em 2em;
+ font-weight: bold;
+}
+
+/*----------------------------------------------------------------*/
+/* padviewpage */
+/*----------------------------------------------------------------*/
+body#padviewbody {
+ background-color: #ebebeb;
+}
+#padviewpage a {
+ text-decoration: underline;
+}
+#padviewpage #padviewheader {
+ margin: 8px;
+ padding: 8px;
+ border: 1px solid #ccc;
+ background-color: #e0e0ff;
+ line-height: 160%;
+}
+#padviewpage #padviewheader h1 {
+ font-weight: bold;
+ font-family: "Lucida Grande","Lucida Sans Unicode",sans-serif;
+ font-size: 2em;
+}
+#padviewpage .metadata {
+ color: #333;
+}
+#padviewpage .rlabel {
+ font-weight: bold;
+}
+#padviewpage p {
+ margin-top: 2px;
+}
+#padviewpage #padcontent {
+ border: 1px solid #ccc;
+ margin: 8px;
+ padding: 8px;
+ font-family: sans-serif;
+ font-size: 12px;
+ background-color: #fff;
+ line-height: 130%;
+}
+#padviewpage #padviewfooter {
+ margin: 8px;
+ padding: 8px;
+ font-size: 12px;
+}
+
+
+#padviewpage #export td.exportpic a img {
+ border: 0;
+}
+
+#padviewpage #export a.disabledexport {
+ color: gray;
+ text-decoration: none;
+}
+
+#padviewpage #export {
+ font-size: 1em;
+}
+
+#padviewpage #export .exportlink {
+ margin: 2px 0;
+}
+
+#padviewpage #export td.exportpic {
+ padding-left: 10px;
+}
+
+#padviewpage #export td.labelcell {
+ padding-left: 4px;
+}
+
+#export img {
+ vertical-align: middle;
+ padding: 4px;
+ padding-bottom: 8px;
+ padding-left: 3px;
+}
+
+#export span.titlelabel {
+ vertical-align: top;
+ padding-right: 12px;
+ font-size: 1.3em;
+ color: rgb(0, 0, 0);
+ font-weight: bold;
+ margin-top: 10px;
+}
+
+#export td.labelcell a {
+
+ vertical-align: middle;
+ font-size: 1em;
+ padding-right: 12px;
+ color: rgb(0, 52, 143);
+ font-weight: bold;
+}
+
+
+/*----------------------------------------------------------------*/
+/* feature tour page */
+/*----------------------------------------------------------------*/
+#featuretourpage {}
+#featuretourpage p { margin-top: 1em; }
+#featuretourpage #screencastmsg { margin: 2em 0; }
+#featuretourpage .featurebox {
+ clear: both;
+ padding: 1em 1em;
+ margin-top: 2em;
+ margin-left: auto;
+ margin-right: auto;
+ background: #eee;
+ border: 1px solid #ccc;
+}
+#featuretourpage .featurebox .featureprose {
+}
+#featuretourpage .featurebox .featureprose h2 {
+}
+#featuretourpage .featurebox .featureprose p {
+ padding: 0;
+ margin: 1em 0;
+}
+#featuretourpage .featurebox img {
+ border: 1px solid #aaa;
+ margin: 0 1em 1em 1em;
+}
+#featuretourpage #usersbox div.featureprose { }
+#featuretourpage #usersimg { float: right; }
+#featuretourpage #editsimg { padding: 0; margin: 0; }
+#featuretourpage #neverlosework img { float: left; }
+#featuretourpage #neverlosework div.featureprose { }
+#featuretourpage #lockimg { float: right; border: 0;}
+#featuretourpage #revisionsimg { float: left; margin-left: 0; }
+#featuretourpage #codeimg { float: left; margin-left: 0; }
+#featuretourpage h2 {
+ margin-top: 0;
+ font-size: 1.5em;
+ font-weight: bold;
+}
+#featuretourpage p {
+ font-size: 1.1em;
+}
+
+/*----------------------------------------------------------------*/
+/* product page */
+/*----------------------------------------------------------------*/
+
+#productpage p {
+ font-size: 1.2em;
+ color: #333;
+}
+
+#productpage h1 {
+}
+
+#productpage h2 {
+ font-size: 1.6em;
+ font-weight: bold;
+ font-family: inherit;
+ color: #399;
+ font-style: italic;
+ border-bottom: 1px solid #399;
+ margin-top: 1.5em;
+ margin-bottom: 0.3em;
+}
+
+#productpage #howuse {
+ margin-left: 80px;
+ margin-right: 80px;
+}
+
+#productpage #howuse p {
+ font-size: 1.2em;
+ line-height: 150%;
+}
+
+#productpage .tourbar { width: 100%; }
+#productpage .tourbar td { padding: 3px; }
+#productpage .tourbar .left { text-align: left; font-weight: bold; font-size: 1.6em; }
+#productpage .tourbar .right { text-align: right; }
+
+#productpage .tourbar a {
+ color: #33f;
+ font-size: 1.4em;
+ font-weight: bold;
+}
+
+#productpage #tourtop { border-bottom: 1px solid #999; }
+/* #productpage #tourbot { border-top: 1px solid #999; } */
+
+#productpage #pageshot img {
+ display: block;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 0;
+}
+
+#productpage .javascripton #tourbody {
+ /*height: 650px;*/
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+#productpage .javascripton .tourprose {
+ display: none;
+}
+#productpage #usecases table {
+ height: 300px;
+ padding: 20px auto;
+ border: 1px solid #aaa;
+ width: 100%;
+}
+#productpage #usecases td {
+ vertical-align: top;
+}
+
+#productpage #usecases p {
+ font-family: Georgia, serif;
+ font-size: 1.3em;
+ line-height: 1.3;
+}
+
+#productpage #usecases h3 {
+ padding: 0; margin: 0;
+ font-weight: bold;
+ color: black;
+ font-family: inherit;
+ font-size: 1.6em;
+ margin-top: 0.5em;
+}
+
+#productpage #usecases strong {
+ font-style: normal;
+ font-weight: normal;
+ background-color: #ffc;
+}
+
+#productpage #usecases p.intro { margin: 0.5em 0;}
+
+#productpage #usecases #prosecell {
+ padding-left: 20px;
+ padding-right: 20px;
+ padding-top: 15px;
+ border-left: 1px solid #ccc;
+ background: #fff url(/static/img/oct/insetrect.gif) no-repeat right top;
+}
+
+#productpage #usecases #prosecell p {
+ padding: 0;
+ margin: 0;
+ margin-bottom: 0.8em;
+}
+
+#productpage .showpageshot #usecases { display: none; }
+#productpage .showusecases #pageshot { display: none; }
+
+#productpage #tourleftnavcell { width: 200px; }
+
+#productpage ul#tourleftnav { margin: 0; padding: 0; list-style: none; }
+#productpage ul#tourleftnav li { margin: 0; padding: 0; background: #fff; }
+#productpage ul#tourleftnav li a { color: #33f; text-decoration: none; }
+#productpage ul#tourleftnav li a:hover { text-decoration: underline; }
+#productpage ul#tourleftnav li a { outline: none; }
+#productpage ul#tourleftnav li { background: url(/static/img/oct/usecasesnavup.gif) repeat-x left center; border-bottom: 1px solid #ccc; }
+/*#productpage ul#tourleftnav li:hover { background: url(/static/img/oct/usecasesnavuph.gif) repeat-x left center; }*/
+#productpage ul#tourleftnav li.selected { background: url(/static/img/oct/usecasesnavdown.gif) repeat-x left center }
+/*#productpage ul#tourleftnav li.selected:hover { background: url(/static/img/oct/usecasesnavdownh.gif) repeat-x left center; }*/
+#productpage ul#tourleftnav li.selected a { color: #000; background: url(/static/img/oct/tinytriangle.gif) no-repeat 95% center; }
+
+#productpage #tourleftnav a {
+ font-size: 1.2em;
+ padding: 0.4em 0.4em;
+ display: block;
+ font-weight: bold;
+ font-family: inherit;
+ font-style: italic;
+ cursor: pointer;
+}
+
+.fpcontent .newpadbuttonwrap {
+ margin: 0 auto;
+}
+
+
+/*----------------------------------------------------------------*/
+/* faq page */
+/*----------------------------------------------------------------*/
+#faqpage hr {
+ width: 100%;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ margin-left: auto;
+ margin-right: auto;
+ color: #ccc;
+}
+div#faqpage h2 {
+ border-bottom: 1px solid #aaa;
+ margin: 0;
+}
+#faqpage div.answer {
+ color: #222;
+ padding: 1em;
+}
+#faqpage ul.qlist { margin: 2em 0 4em 1.4em; }
+#faqpage ul.qlist li { margin: .5em 0; }
+
+/*----------------------------------------------------------------*/
+/* contact page */
+/*----------------------------------------------------------------*/
+#contactpage div.cbox {
+ padding: 1em;
+ margin: 2em 0;
+ width: 330px;
+ height: 200px;
+}
+#contactpage h2 {
+ margin: 0;
+}
+#contactpage #boxleft {
+ float: left;
+ margin-left: 1em;
+}
+#contactpage #boxright {
+ float: right;
+ margin-right: 1em;
+}
+#contactpage #boxright p {
+ font-size: 1.4em;
+ font-family: serif;
+ color: #222;
+ padding-left: 2em;
+}
+/*----------------------------------------------------------------*/
+/* company page */
+/*----------------------------------------------------------------*/
+
+#companypage div#appjetinc { width: 300px; float: left; padding: 2em; }
+#companypage div#appjetinc p { padding-left: 1em; }
+#companypage img#ajlogo { margin-top: 1em; border: 0; }
+#companypage img#pier38 { border: 1px solid #888; float: right; margin-top: 1em; }
+#companypage table img { border: 1px solid #444; margin-top: 4px; }
+#companypage table td { padding: 15px; vertical-align: top; }
+#companypage table td p.intro { margin-top: 0; }
+
+/*----------------------------------------------------------------*/
+/* blog */
+/*----------------------------------------------------------------*/
+
+.blogbody { background: #f8f8f8; }
+
+.blogpage a#subscribelink {
+ font-size: .92em;
+ display: block;
+ background: #fff;
+ border: 1px solid #666;
+ font-weight: bold;
+ margin-bottom: 1em;
+ padding: 4px 8px;
+ color: #049;
+ text-decoration: none;
+}
+
+.blogpage div#subscribewrap a:hover {
+ background: #def;
+}
+.blogpage a#subscribelink img {
+ border: 0;
+ float: left;
+}
+.blogpage div#subscribewrap span.subtext {
+ display: block;
+ float: left;
+ padding-left: 8px;
+ padding-top: 2px;
+}
+
+div#blogcol1 {
+ width: 520px;
+ padding-left: 50px;
+ float: left;
+ font-size: .9em;
+}
+
+div#blogcol2 {
+ float: left;
+ width: 240px;
+ margin-top: 24px;
+ margin-left: 1em;
+}
+
+div#recentpostsbox {
+ border: 1px solid #ccc;
+ background: #fefefe;
+ padding: 12px 6px;
+ font-size: .8em;
+}
+div#recentpostsbox p {
+ margin: 0;
+ font-weight: bold;
+ padding-left: .5em;
+}
+div#recentpostsbox a {
+ color: #049;
+ text-decoration: none;
+}
+div#recentpostsbox a:hover {
+ text-decoration: underline;
+}
+div#recentpostsbox ul li {
+ margin: 0;
+ margin-top: .4em;
+}
+
+.blogpage div.bpheader {
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ background: #eee;
+ margin-top: 24px;
+ padding: 1em;
+}
+
+.blogpage div.blogpost_mainpage_content {
+ background: #fff;
+ padding: 0 1em;
+ padding-top: 0.1em;
+ padding-bottom: 0.5em;
+ border-right: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ font-size: 100%;
+ line-height: 1.4;
+}
+.blogpage div.blogpost_mainpage_content a {
+ text-decoration: underline;
+}
+.blogpage div.blogpost_mainpage_content a:hover {
+ color: red;
+}
+
+.blogpage div.bpheader div.postdate a {
+ text-decoration: none;
+ font-size: 1em;
+ font-weight: bold;
+ font-style: italic;
+ color: #555;
+}
+.blogpage div.bpheader h2 { margin: 0; border: 0; }
+.blogpage div.bpheader h2 a {
+ font-size: 1em;
+ text-decoration: none;
+ color: #049;
+ margin: 0;
+ font-style: normal;
+}
+.blogpage div#disqus_thread {
+ margin-top: 40px;
+}
+.blogpage .commentslink {
+ text-align: right;
+}
+.singleblogpost #blogposttop {
+ margin-left: 50px;
+}
+
+.blogpage h3 {
+ font-weight: bold;
+ font-size: 1em;
+ color: #050;
+}
+
+.blogpost_mainpage_content ol { }
+.blogpost_mainpage_content ol li {
+ margin-top: 1em;
+ margin-left: 2em;
+ list-style: decimal;
+}
+
+.blogpage div.code {
+ font-family: monospace;
+ border: 1px solid yellow;
+ padding: 0.5em;
+ background: #ffd;
+}
+
+.blogpage tt {
+ font-family: monospace;
+}
+
+
+/*----------------------------------------------------------------*/
+/* create pad */
+/*----------------------------------------------------------------*/
+#createpadpage form {
+ width: 80%;
+ margin-left: auto;
+ margin-right: auto;
+ border: 1px solid #ddd;
+ background: #eef;
+ font-size: 1.8em;
+ text-align: center;
+ padding: 2em;
+}
+#createpadpage #padurl {
+ background: #fff;
+ border: 1px solid #ccc;
+ padding: 1em;
+}
+#createpadpage input {
+ font-size: 1.8em;
+}
+/*----------------------------------------------------------------*/
+/* pad full */
+/*----------------------------------------------------------------*/
+#padfullpage #msg {
+ margin: 2em 0;
+ padding: 2em;
+ background: #eee;
+ border: 1px solid #aaa;
+ font-size: 1.3em;
+}
+#padfullpage #padurlwrap {
+ text-align: center;
+ margin-bottom: 3em;
+}
+#padfullpage #padurl {
+ background: #fff;
+ border: 1px solid #ccc;
+ padding: 1em;
+}
+/*----------------------------------------------------------------*/
+/* beta signup */
+/*----------------------------------------------------------------*/
+#betasignuppage img#betasign { float: left; margin-top: 20px; }
+#betasignuppage div#betaformwrap {
+ margin: 15px;
+ padding: 20px;
+ margin-left: 200px;
+ border: 1px solid #ccc;
+ background: #eee;
+}
+#betasignuppage div#betaformwrap p {
+ margin: 0;
+ color: #333;
+ margin-bottom: 1em;
+}
+#betasignuppage div#betaform { padding: 2em 0; }
+#betasignuppage div#betaform input#email { font-size: 1.6em; color: #555; }
+#betasignuppage div#betaform button { font-size: 1.6em; }
+#betasignuppage div#confirm { display: none; }
+#betasignuppage div#error {
+ margin: 1em 3em 2em 2em;
+ color: red;
+ display: none;
+}
+#betasignuppage div#confirm {
+ margin: 2em 2em 2em 0em;
+ color: green;
+ font-weight: bold;
+ display: none;
+}
+#betasignuppage div#subtext { font-size: .9em; color: #666; }
+
+/*----------------------------------------------------------------*/
+/* time slider */
+/*----------------------------------------------------------------*/
+
+body#padsliderbody {
+ font-size: 1.2em;
+ padding: 20px;
+ background-color: #fff;
+}
+
+#padsliderbody #stuff {
+ width: 600px;
+ margin-top: 10px;
+}
+
+#padsliderbody #sliderui {
+ margin: 10px;
+}
+
+#padsliderbody #controls {
+ background: #eee;
+ padding: 5px;
+ border: 1px solid #999;
+}
+
+#padsliderbody #currevdisplay {
+ margin-top: 3px;
+}
+
+#padsliderbody #controls a {
+ color: #00f;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+#padsliderbody #stuff {
+ padding: 5px;
+}
+
+/*----------------------------------------------------------------*/
+/* testimonials */
+/*----------------------------------------------------------------*/
+
+#testimonials {
+ padding: 0 3em;
+ font-family: times serif;
+}
+
+#testimonials .head {
+ font-weight: bold;
+ padding-top: 4px;
+ padding-right: 80px;
+}
+
+#testimonials .quote-open {
+ background: url(/static/img/about/quote-open.png) no-repeat left top;
+ padding-left: 80px;
+ margin-top: 2em;
+}
+
+#testimonials .quote-close {
+ background: url(/static/img/about/quote-close.png) no-repeat right bottom;
+ padding-right: 80px;
+ text-align: justify;
+ color: #222;
+}
+
+#testimonials .attrib {
+ font-style: italic;
+ text-align: right;
+ padding-left: 2em;
+ padding-right: 80px;
+ color: #444;
+}
+
+#testimonials .attrib p {
+ margin: 0;
+}
+
+
+/* pne faq */
+
+div.pne-faq dt {
+ font-size: 1.1em;
+ border-bottom: 1px solid #444;
+ color: #666;
+ margin: 1.6em 0 0.75em 0;
+}
+
+/* support page */
+
+div#support-content {
+ margin: 0 4em;
+}
+
+div#forums-content {
+ margin: 0 4em;
+}
diff --git a/etherpad/src/static/css/framedpage.css b/etherpad/src/static/css/framedpage.css
new file mode 100644
index 0000000..a99554b
--- /dev/null
+++ b/etherpad/src/static/css/framedpage.css
@@ -0,0 +1,175 @@
+/*------
+ Global Container
+------*/
+
+#container {
+ font-family: Arial, Helvetica, Calibri, sans-serif;
+}
+body.home {
+ background: #f7f7f7 url(/static/img/davy/bg/home2.png) repeat-x top;
+}
+.home #container {
+ width: 920px; margin: 0 auto;
+}
+body.nothome {
+ background: #f7f7f7 url(/static/img/davy/bg/product.png) repeat-x top;
+}
+.nothome #container {
+ width: 910px; margin: 0 auto;
+}
+
+/*------
+ Layout
+------*/
+
+#navigation,
+.home #top,
+.home #bottom,
+#footer {
+ width: 888px;
+ margin: 0 auto;
+}
+
+/* framed page general */
+div.fpcontent {
+ width: 848px;
+ margin: 0 auto;
+
+ font-size: 1.3em;
+ padding: 20px;
+
+ background-color: #fff;
+ border-left: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ border-top: 0;
+}
+div.fpcontent h1 {
+ color: #666;
+ border-bottom: 1px solid #666;
+ margin: .8m 0 1em 0;
+ font-size: 1.8em;
+}
+div.fpcontent h2 {
+ color: #666;
+ border-bottom: 1px solid #666;
+ font-size: 1.4em;
+ margin: 1em 0;
+}
+div.fpcontent p {
+ margin: 1em 0;
+ line-height: 150%;
+}
+div.fpcontent ul {
+ list-style: disc;
+ padding-left: 1.5em;
+}
+div.fpcontent ul li {
+ margin: 1em 0;
+ padding-left: .5em;
+}
+
+/*----------
+ Navigation
+----------*/
+
+#topnav_wrap {
+ background: url(/static/img/davy/bg/product.png) repeat-x top;
+}
+#navigation {
+ height: 38px;
+ overflow: hidden;
+ background: url(/static/img/davy/bg/home2.png) repeat-x top;
+}
+#navigation h1 a {
+ display: block;
+ width: 120px;
+ position: relative;
+ padding: 38px 0 0 0;
+ overflow: hidden;
+ background: transparent url(/static/img/davy/gfx/product-logo.gif) no-repeat 0 6px;
+ height: 0px;
+ float: left;
+}
+.home #navigation h1 a {
+ display: none;
+}
+#navigation ul {
+ margin-right: -10px;
+}
+#navigation li {
+ display: inline;
+}
+#navigation li a {
+ font-family: Calibri, "Trebuchet MS", Trebuchet, Arial, sans-serif;
+ font-size: 1.208em;
+ text-transform: uppercase;
+ color: #fff;
+ text-shadow: 0 1px 0 #223f6b;
+ letter-spacing: 1px;
+ display: block;
+ padding: 11px 10px 13px 10px;
+ float: right;
+}
+.home #navigation .topnav_pricing a {
+ color: #ffc261;
+}
+.home #navigation .topnav_pricing a:hover {
+ color: #FEAC59;
+}
+#navigation li.selected a {
+ color: #bddbff;
+}
+.home #navigation li.selected a {
+ background: url(/static/img/davy/bg/home-nav-selected.png) no-repeat center 32px;
+}
+.nothome #navigation li.selected a {
+ background: url(/static/img/davy/bg/product-nav-selected-white.png) no-repeat center 32px;
+}
+#navigation li a:hover {
+ color: #DEEDFF;
+ text-decoration: none;
+}
+
+
+/*------
+ Footer
+------*/
+
+.home #footer {
+ border-top: 1px solid #d9d9d9;
+ margin-top: 24px;
+}
+
+.nothome #footer {
+ margin-top: 0px;
+}
+
+ #footer-inner {
+ border-top: 1px solid #f9f9f9;
+ padding: 12px 0;
+ color: #666;
+ font-size: .917em;
+ }
+
+#footer-left {
+ float: left;
+ width: 700px;
+}
+ #footer ul,
+ #footer li {
+ display: inline;
+ }
+ #footer li {
+ margin-left: 1em;
+ }
+
+ #footer #appjet {
+ float: right;
+ margin-right: -12px;
+ }
+ #footer #appjet a {
+ background: url(/static/img/davy/gfx/plane.gif) no-repeat right center;
+ padding-right: 12px;
+ }
+
diff --git a/etherpad/src/static/css/global-pro-account.css b/etherpad/src/static/css/global-pro-account.css
new file mode 100644
index 0000000..6c34446
--- /dev/null
+++ b/etherpad/src/static/css/global-pro-account.css
@@ -0,0 +1,52 @@
+div.error {
+ border: 1px solid red;
+ background: #fee;
+ padding: 1em;
+ margin: 1em 0;
+ width: 600px;
+ font-weight: bold;
+}
+
+form#global-sign-in {
+ background: #eeeef6;
+ padding: 1em;
+ border: 1px solid #ddd;
+ margin: 1em 0;
+ width: 600px;
+}
+
+form label {
+ color: #444;
+ margin-bottom: .2em;
+}
+
+form input {
+ border: 1px solid #377ec6;
+}
+
+form#global-sign-in label {
+ display: block;
+ margin-top: 1em;
+}
+
+form#global-sign-in button {
+ border: 0;
+ cursor: pointer;
+ color: #fff;
+ font-weight: bold;
+ overflow: visible;
+ padding: 0;
+ background: #70a4ec;
+ border: 1px solid #3773c6;
+ padding: 4px 16px;
+ margin-top: 14px;
+}
+
+.global-pro-account p {
+ font-size: 86%;
+}
+
+div.tip {
+ margin: .5em 0;
+ font-size: 90%;
+}
diff --git a/etherpad/src/static/css/home-opensource.css b/etherpad/src/static/css/home-opensource.css
new file mode 100644
index 0000000..bb0201e
--- /dev/null
+++ b/etherpad/src/static/css/home-opensource.css
@@ -0,0 +1,40 @@
+
+#home {
+ width: 600px;
+ margin: 0 auto;
+ padding: 2em;
+ text-align: center;
+}
+
+#home #title {
+ font-size: 3.6em;
+}
+
+#home a#home-newpad{
+ display: block;
+ padding: 1em;
+ margin: 12px 60px;
+ font-size: 1.6em;
+ border: 1px solid black;
+ background: #049;
+ color: #fff;
+}
+
+#home a#home-newpad:hover{
+ background: #26b;
+}
+
+#home a#home-newteam {
+ display: block;
+ padding: 1em;
+ margin: 12px 60px;
+ font-size: 1.6em;
+ border: 1px solid black;
+ background: #940;
+ color: #fff;
+}
+
+#home a#home-newteam:hover {
+ background: #b26;
+}
+
diff --git a/etherpad/src/static/css/lib/jquery.contextmenu.css b/etherpad/src/static/css/lib/jquery.contextmenu.css
new file mode 100644
index 0000000..15a69aa
--- /dev/null
+++ b/etherpad/src/static/css/lib/jquery.contextmenu.css
@@ -0,0 +1,244 @@
+/* Classic Windows Theme (default) */
+/* =============================== */
+.context-menu-theme-default {
+ border:2px outset white;
+ background-color:#D4D0C8;
+}
+.context-menu-theme-default .context-menu-item {
+ text-align:left;
+ cursor:pointer;
+ padding:4px 28px 4px 16px;
+ color:black;
+ font-family:Tahoma,Arial;
+ font-size:11px;
+}
+.context-menu-theme-default .context-menu-separator {
+ margin:4px 2px;
+ font-size:0px;
+ border-top:1px solid #808080;
+ border-bottom:1px solid white;
+}
+.context-menu-theme-default .context-menu-item-disabled {
+ color:#808080;
+}
+.context-menu-theme-default .context-menu-item .context-menu-item-inner {
+ background:none no-repeat fixed 999px 999px; /* Make sure icons don't appear */
+}
+.context-menu-theme-default .context-menu-item-hover {
+ background-color:#0A246A;
+ color:white;
+}
+.context-menu-theme-default .context-menu-item-disabled-hover {
+ background-color:#0A246A;
+}
+
+/* Windows XP Theme */
+/* ================ */
+.context-menu-theme-xp {
+ border:1px solid #666;
+ padding:1px;
+ background:#F9F8F7 url(/static/img/lib/jquery.contextmenu.images/cmenu-xp-bg.gif) repeat-y top left;
+}
+.context-menu-theme-xp .context-menu-separator {
+ margin:4px 2px;
+ font-size:0px;
+ border-top:1px solid #808080;
+ border-bottom:1px solid white;
+}
+.context-menu-theme-xp .context-menu-item {
+ text-align:left;
+ color:black;
+ font-family:arial;
+ font-size:11px;
+ cursor:pointer;
+}
+.context-menu-theme-xp .context-menu-item .context-menu-item-inner {
+ background:none no-repeat 2px center;
+ padding:4px 10px 4px 30px;
+}
+.context-menu-theme-xp .context-menu-item-hover .context-menu-item-inner {
+ background:#B6BDD2 none no-repeat 2px center;
+ padding:3px 9px 3px 29px;
+ border:1px solid #0A246A;
+}
+
+/* Windows Vista Theme */
+/* =================== */
+.context-menu-theme-vista {
+ background:#FAFAFA url(/static/img/lib/jquery.contextmenu.images/cmenu-vista-bg.gif) repeat-y left top;
+ border:1px solid #868686;
+}
+.context-menu-theme-vista .context-menu-item {
+ text-align:left;
+ cursor:pointer;
+ color:black;
+ font-family:Tahoma,Arial;
+ font-size:11px;
+}
+.context-menu-theme-vista .context-menu-separator {
+ margin:0px 0px 0px 32px;
+ font-size:0px;
+ border-top:1px solid #C5C5C5;
+ border-bottom:1px solid #F5F5F5;
+}
+.context-menu-theme-vista .context-menu-item-hover {
+ background:transparent url(/static/img/lib/jquery.contextmenu.images/cmenu-vista-menu-item-hover.gif) repeat-x left center;
+ border:1px solid #D7D0B3;
+}
+.context-menu-theme-vista .context-menu-item .context-menu-item-inner {
+ padding:4px 16px 4px 35px;
+ margin-left:1px;
+ background-color:none;
+ background-repeat:no-repeat;
+ background-position:3px center;
+ background-image:none;
+}
+.context-menu-theme-vista .context-menu-item-hover .context-menu-item-inner {
+ padding:3px 15px 3px 35px;
+ margin-left:0px;
+}
+.context-menu-theme-vista .context-menu-item-disabled {
+ color:#A7A7A7;
+}
+
+/* OSX Theme */
+/* ========= */
+.context-menu-theme-osx {
+ background-color:white;
+ opacity: .93;
+ filter: alpha(opacity=93);
+ zoom:1.0;
+ border:1px solid #b2b2b2;
+}
+.context-menu-theme-osx .context-menu-item {
+ text-align:left;
+ cursor:pointer;
+ color:black;
+ font-family:Lucida Grande,Arial;
+ font-weight:700;
+ font-size:12px;
+ opacity: 1.0;
+ filter: alpha(opacity=100);
+ z-index:1;
+}
+.context-menu-theme-osx .context-menu-separator {
+ margin:5px 1px 4px 1px;
+ font-size:0px;
+ border-top:1px solid #e4e4e4;
+}
+.context-menu-theme-osx .context-menu-item-hover {
+ background-color:#1C44F2;
+ color:white;
+}
+.context-menu-theme-osx .context-menu-item .context-menu-item-inner {
+ padding:2px 10px 2px 22px;
+ background-color:none;
+ background-repeat:no-repeat;
+ background-position:4px center;
+ background-image:none;
+}
+.context-menu-theme-osx .context-menu-item-disabled {
+ color:#939393;
+}
+
+/* Linux Human Theme */
+/* ================= */
+.context-menu-theme-human {
+ background:#F9F5F2;
+ border:1px solid #963;
+}
+.context-menu-theme-human .context-menu-item {
+ text-align:left;
+ cursor:pointer;
+ color:black;
+ font-family:Helvetica,DejaVu Sans,Arial;
+ font-size:12px;
+ line-height:20px;
+ height:28px;
+ border:1px solid #F9F5F2;
+ border-left:0;
+ border-right:0;
+}
+.context-menu-theme-human .context-menu-separator {
+ margin:0px 0px 0px 32px;
+ font-size:0px;
+ border-top:1px solid #C5C5C5;
+ border-bottom:1px solid #F5F5F5;
+}
+.context-menu-theme-human .context-menu-item-hover {
+ background:transparent url(/static/img/lib/jquery.contextmenu.images/cmenu-human-menu-item-hover.gif) repeat-x left center;
+ border-color:#963;
+}
+.context-menu-theme-human .context-menu-item .context-menu-item-inner {
+ padding:4px 16px 4px 35px;
+ margin-left:0px;
+ background-color:none;
+ background-repeat:no-repeat;
+ background-position:3px center;
+ background-image:none;
+}
+.context-menu-theme-human .context-menu-item-hover .context-menu-item-inner {
+}
+.context-menu-theme-human .context-menu-item-disabled {
+ color:#A7A7A7;
+}
+
+/* Gloss Theme */
+/* =========== */
+.context-menu-theme-gloss {
+ background:#f4f4f4 url(/static/img/lib/jquery.contextmenu.images/cmenu-gloss-bg.gif) repeat-y left center;
+ border:1px solid #f4f4f4;
+ padding:1px;
+ padding-right:0;
+}
+.context-menu-theme-gloss .context-menu-item {
+ text-align:left;
+ cursor:pointer;
+ color:black;
+ font-family:Helvetica,DejaVu Sans,Arial;
+ font-size:12px;
+ line-height:20px;
+ height:27px;
+ /*border:1px solid transparent;*/
+ border:1px solid #f4f4f4; /* IE6 doesn't have "transparent" -- DG */
+}
+.context-menu-theme-gloss .context-menu-separator {
+ margin:0px 0px 0px 32px;
+ font-size:0px;
+ border-top:1px solid #C5C5C5;
+ border-bottom:1px solid #F5F5F5;
+}
+.context-menu-theme-gloss .context-menu-item-hover {
+ background:transparent url(/static/img/lib/jquery.contextmenu.images/cmenu-gloss-menu-item-hover.gif) repeat-x left center;
+ color:#fff;
+ border-color:#000;
+ border-radius: 3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+}
+.context-menu-theme-gloss .context-menu-item .context-menu-item-inner {
+ padding:4px 16px 4px 35px;
+ margin-left:0px;
+ background-color:none;
+ background-repeat:no-repeat;
+ background-position:3px center;
+ background-image:none;
+}
+.context-menu-theme-gloss .context-menu-item-hover .context-menu-item-inner {
+}
+.context-menu-theme-gloss .context-menu-item-disabled {
+ color:#A7A7A7;
+}
+
+.context-menu-theme-gloss-cyan .context-menu-item-hover {
+ background-image:url(/static/img/lib/jquery.contextmenu.images/cmenu-gloss-cyan-menu-item-hover.gif);
+ border-color:#00c;
+}
+
+.context-menu-theme-gloss-semitransparent .context-menu-item-hover {
+ background-image:url(/static/img/lib/jquery.contextmenu.images/cmenu-item-gloss-semitransparent-menu-item-hover.png);
+ border-color:#00c;
+ background-color:#30f;
+}
+
+
diff --git a/etherpad/src/static/css/pad2_ejs.css b/etherpad/src/static/css/pad2_ejs.css
new file mode 100644
index 0000000..71176ee
--- /dev/null
+++ b/etherpad/src/static/css/pad2_ejs.css
@@ -0,0 +1,910 @@
+
+*,html.body { margin: 0; padding: 0; }
+
+h1, h2, h3, h4, h5, h6 { display: inline; line-height: 2em; }
+
+.clear { clear: both; }
+
+html { font-size: 62.5%; }
+
+body { background: #ebebeb url(/static/img/jun09/pad/backgrad.gif) repeat-x left top; }
+body, textarea { font-family: Arial, sans-serif; }
+
+#padpage { margin-left: auto; margin-right: auto; width: 900px; }
+
+body.fullwidth #padpage { width: auto; margin-left: 6px; margin-right: 6px; min-width: 800px; }
+body.squish1width #padpage { width: 900px; }
+body.squish2width #padpage { width: 800px; }
+
+#topbar { height: 25px; background: #326cbd url(/static/img/jun09/pad/padtopback2.gif) repeat-x left top;
+ position: relative; }
+
+#topbarleft { float: left; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/padtop5.png) no-repeat left top; width: 5px; }
+#topbarright { float: right; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/padtop5.png) no-repeat right top; width: 5px; }
+
+.propad #topbar { background: #2c2c2c url(/static/img/jun09/pad/protop.png) repeat-x 0 -25px; }
+.propad #topbarleft { background: url(/static/img/jun09/pad/protop.png) no-repeat left top; }
+.propad #topbarright { background: url(/static/img/jun09/pad/protop.png) no-repeat right top; }
+
+a#backtoprosite, #accountnav {
+ display: block; position: absolute; height: 15px; line-height: 15px;
+ width: auto; top: 5px; font-size: 1.2em;
+}
+a#backtoprosite, #accountnav a { color: #cde7ff; text-decoration: underline; }
+
+a#backtoprosite { padding-left: 20px; left: 6px;
+ background: url(/static/img/jun09/pad/protop.png) no-repeat -5px -6px; }
+#accountnav { right: 10px; color: #fff; }
+
+#topbarcenter { margin-left: 150px; margin-right: 150px; }
+a#topbaretherpad { margin-left: auto; margin-right: auto; display: block; width: 127px;
+ position: relative; top: 0px; height: 0; padding-top: 25px;
+ background: url(/static/img/jun09/pad/padtop5.png) no-repeat -397px 0px; overflow: hidden; }
+
+.propad a#topbaretherpad { background: url(/static/img/jun09/pad/protop.png) no-repeat -397px 0px; }
+
+#specialkeyarea { top: 5px; left: 250px; color: yellow; font-weight: bold;
+ font-size: 1.5em; position: absolute; }
+
+#alertbar { margin-top: 6px;
+opacity: 0; filter: alpha(opacity = 0); /* IE */
+display: none;
+}
+
+#servermsg { position: relative; zoom: 1; border: 1px solid #992;
+ background: #ffc; padding: 0.8em; font-size: 1.2em; }
+#servermsg h3 { font-weight: bold; margin-right: 10px;
+ margin-bottom: 1em; float: left; width: auto; }
+#servermsg #servermsgdate { font-style: italic; font-weight: normal; color: #666; }
+a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
+
+#shuttingdown { position: relative; zoom: 1; border: 1px solid #992;
+ background: #ffc; padding: 0.6em; font-size: 1.2em; margin-top: 6px; }
+
+#docbar { margin-top: 6px; height: 25px; position: relative; zoom: 1;
+ background: #fbfbfb url(/static/img/jun09/pad/padtopback2.gif) repeat-x 0 -31px; }
+
+.docbarbutton
+{
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-left: 4px;
+ padding-right: 4px;
+ border-left: 1px solid #CCC;
+ white-space: nowrap;
+}
+
+.docbarbutton img
+{
+ border: 0px;
+ width: 13px;
+ margin-right: 2px;
+ vertical-align: middle;
+ margin-top: 3px;
+ margin-bottom: 2px;
+}
+
+.docbarbutton a
+{
+ font-size: 10px;
+ line-height: 18px;
+ text-decoration: none;
+ color: #444;
+ font-weight: bold;
+}
+
+.docbarbutton.highlight
+{
+ background-color: #fef2bd;
+ border: 1px solid #CCC;
+ border-right: 0px;
+}
+
+#docbarleft { position: absolute; left: 0; top: 0; height: 100%;
+ overflow: hidden;
+ background: url(/static/img/jun09/pad/padtop5.gif) no-repeat left -31px; width: 7px; }
+
+<% /* changing the size of the title / rename area means adjusting
+ the #docbarpadtitle.width, #padtitlebuttons.left,
+ and #padtitleedit.width */ %>
+
+#docbarpadtitle { position: absolute; height: auto; left: 9px;
+ width: 280px; font-size: 1.6em; color: #444; font-weight: normal;
+ line-height: 22px; margin-left: 2px; height: 22px; top: 2px;
+ overflow: hidden; text-overflow: ellipsis /*not supported in FF*/;
+ white-space:nowrap; }
+.docbar-public #docbarpadtitle { padding-left: 22px;
+ background: url(/static/img/jun09/pad/public.gif) no-repeat left center; }
+
+#docbarrenamelink { position: absolute; top: 6px;
+ font-size: 1.1em; display: none; }
+#docbarrenamelink a { color: #999; }
+#docbarrenamelink a:hover { color: #48d; }
+#padtitlebuttons { position: absolute; width: 74px; zoom: 1;
+ height: 17px; top: 4px; left: 170px; display: none;
+ background: url(/static/img/jun09/pad/ok_or_cancel.gif) 0px 0px; }
+#padtitlesave { position: absolute; display: block;
+ height: 0; padding-top: 17px; overflow: hidden;
+ width: 23px; left: 0; top: 0; }
+#padtitlecancel { position: absolute; display: block;
+ height: 0; padding-top: 17px; overflow: hidden;
+ width: 35px; right: 0; top: 0; }
+#padtitleedit { position: absolute; top: 2px; left: 5px;
+ height: 15px; padding: 2px; font-size: 1.4em;
+ background: white; border-left: 1px solid #c3c3c3;
+ border-top: 1px solid #c3c3c3;
+ border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
+ width: 150px; display: none;
+}
+
+#padmain { margin-top: 6px; position: relative; zoom: 1; }
+
+#padeditor { margin-right: 300px; zoom: 1; }
+.hidesidebar #padeditor { margin-right: 0; }
+
+#editbar { height: 36px;
+ background: #a5bfe2 url(/static/img/jun09/pad/editbar_background.gif) repeat-x; position: relative; }
+
+#editbarleft { float: left; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/editbar_background_left.gif) no-repeat left top; width: 2px; }
+#editbarright { float: right; height: 100%; overflow: hidden;
+ background: url(/static/img/jun09/pad/editbar_background_right.gif) no-repeat right top; width: 2px; }
+
+#editbartable
+{
+ position:absolute;
+ top: 6px;
+ left: 7px;
+ width: 250px;
+ height: 24px;
+ width:520px;
+}
+
+#editbarsavetable
+{
+ position:absolute;
+ top: 6px;
+ right: 7px;
+ height: 24px;
+ width:23px;
+}
+
+#editbarsavetable td, #editbartable td
+{
+ white-space: nowrap;
+}
+
+.editbarbutton
+{
+ border-top: 1px solid #8b9eba;
+ border-bottom: 1px solid #8b9eba;
+ border-left: 1px solid #758aa9;
+ background-color: white;
+}
+
+.editbarbutton img
+{
+ margin: 0px 2px;
+ border: 0px;
+ width: 16px;
+ height: 16px;
+}
+
+.editbarbutton a:active
+{
+ position: relative;
+ top: 1px;
+ left: 1px;
+}
+
+.editbargroupsfirst
+{
+ border-left-width: 0px !important;
+}
+
+#editbar #syncstatussyncing { position: absolute; height: 26px; width: 26px;
+ background: url(/static/img/jun09/pad/syncing2.gif) no-repeat center center;
+ right: 38px; top: 5px; display: none; }
+#editbar #syncstatusdone { position: absolute; height: 26px; width: 26px;
+ background: url(/static/img/jun09/pad/syncdone.gif) no-repeat center center;
+ right: 38px; top: 5px; display: none; }
+
+#editorcontainerbox {
+ border-left: 1px solid #c4c4c4; border-right: 1px solid #c4c4c4;
+ border-bottom: 1px solid #c4c4c4;
+ background: #fff; overflow: hidden; position: relative;
+ zoom: 1; height: 397px; /*...initially*/ }
+
+#editorcontainer { height: 100%; }
+
+#editorcontainer iframe { width: 100%; padding: 0; margin: 0; }
+
+#editorloadingbox { padding-top: 100px; padding-bottom: 100px; font-size: 2.5em; color: #aaa;
+ text-align: center; position: absolute; width: 100%; height: 30px; z-index: 100; }
+
+#padsidebar { float: right; width: 290px; }
+.hidesidebar #padsidebar { width: 0; overflow: hidden; }
+
+#padusers { border: 1px solid #c4c4c4; background: #fafafa; position: relative; zoom: 1; }
+
+#myuser { background: #d9e7f9; padding: 5px; height: 53px; position: relative; }
+#myswatchbox { position: absolute; left: 5px; top: 5px; width: 22px; height: 22px;
+ /*border-top: 1px solid #c3cfe0; border-left: 1px solid #c3cfe0;
+ border-right: 1px solid #ecf3fc; border-bottom: 1px solid #ecf3fc;*/
+ border: 1px solid #bbb;
+ padding: 1px; background: transparent; cursor: pointer; }
+#myuser .myswatchboxhoverable, #myuser .myswatchboxunhoverable {
+ background: white;
+}
+#myuser .myswatchboxhoverable:hover {
+ background: #bbb;
+}
+#myswatch { width: 100%; height: 100%; background: transparent;/*...initially*/ }
+#mycolorpicker {
+ background: url(/static/img/jun09/pad/colorpicker.gif) no-repeat left top;
+ width: 232px; height: 140px;
+ position: absolute;
+ left: 13px; top: 13px; z-index: 101;
+ display: none;/*...initially*/
+}
+#mycolorpicker .n1 { left: 13px; }
+#mycolorpicker .n2 { left: 40px; }
+#mycolorpicker .n3 { left: 67px; }
+#mycolorpicker .n4 { left: 94px; }
+#mycolorpicker .n5 { left: 121px; }
+#mycolorpicker .n6 { left: 148px; }
+#mycolorpicker .n7 { left: 175px; }
+#mycolorpicker .n8 { left: 202px; }
+
+#mycolorpicker .n9 { left: 13px; top: 34px ! important;}
+#mycolorpicker .n10 { left: 40px; top: 34px ! important;}
+#mycolorpicker .n11 { left: 67px; top: 34px ! important;}
+#mycolorpicker .n12 { left: 94px; top: 34px ! important;}
+#mycolorpicker .n13 { left: 121px; top: 34px ! important;}
+#mycolorpicker .n14 { left: 148px; top: 34px ! important;}
+#mycolorpicker .n15 { left: 175px; top: 34px ! important;}
+#mycolorpicker .n16 { left: 202px; top: 34px ! important;}
+
+#mycolorpicker .n17 { left: 13px; top: 56px ! important;}
+#mycolorpicker .n18 { left: 40px; top: 56px ! important;}
+#mycolorpicker .n19 { left: 67px; top: 56px ! important;}
+#mycolorpicker .n20 { left: 94px; top: 56px ! important;}
+#mycolorpicker .n21 { left: 121px; top: 56px ! important;}
+#mycolorpicker .n22 { left: 148px; top: 56px ! important;}
+#mycolorpicker .n23 { left: 175px; top: 56px ! important;}
+#mycolorpicker .n24 { left: 202px; top: 56px ! important;}
+
+#mycolorpicker .n25 { left: 13px; top: 78px ! important;}
+#mycolorpicker .n26 { left: 40px; top: 78px ! important;}
+#mycolorpicker .n27 { left: 67px; top: 78px ! important;}
+#mycolorpicker .n28 { left: 94px; top: 78px ! important;}
+#mycolorpicker .n29 { left: 121px; top: 78px ! important;}
+#mycolorpicker .n30 { left: 148px; top: 78px ! important;}
+#mycolorpicker .n31 { left: 175px; top: 78px ! important;}
+#mycolorpicker .n32 { left: 202px; top: 78px ! important;}
+
+#mycolorpicker .pickerswatchouter {
+ border: 1px solid white;
+ width: 15px; height: 15px; position: absolute;
+ top: 12px;
+}
+#mycolorpicker .pickerswatch {
+ border: 1px solid #999;
+ width: 13px;
+ height: 13px;
+ position: absolute;
+ left: 0; top: 0;
+}
+#mycolorpicker .picked { border: 1px solid #666 !important; }
+#mycolorpicker .picked .pickerswatch { border: 1px solid #666; }
+#mycolorpickersave { position: absolute; left: 14px; top: 102px;
+ width: 47px; height: 0; padding-top: 20px; overflow: hidden;
+ cursor: pointer; }
+#mycolorpickercancel { position: absolute; left: 87px; top: 102px;
+ width: 44px; height: 0; padding-top: 20px; overflow: hidden;
+ cursor: pointer; }
+#myusernameform { margin-left: 35px; }
+#myusernameedit { font-size: 1.6em; color: #444;
+ padding: 3px; height: 18px; margin: 0; border: 0;
+ width: 197px; background: transparent; }
+#myusernameform input.editable { border: 1px solid #bbb; }
+#myuser .myusernameedithoverable:hover { background: white; }
+#mystatusform { margin-left: 35px; margin-top: 5px; }
+#mystatusedit { font-size: 1.2em; color: #777;
+ font-style: italic; display: none;
+ padding: 2px; height: 14px; margin: 0; border: 1px solid #bbb;
+ width: 199px; background: transparent; }
+#myusernameform .editactive, #myusernameform .editempty {
+ background: white; border-left: 1px solid #c3c3c3;
+ border-top: 1px solid #c3c3c3;
+ border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
+}
+#myusernameform .editempty { color: #ef641e; }
+
+#otherusers {
+ height: 100px;/*...initially*/
+ overflow: auto;
+}
+
+table#otheruserstable { display: none; }
+#nootherusers { padding: 10px; font-size: 1.2em; color: #999; font-weight: bold;}
+#nootherusers a { color: #48d; }
+
+#otheruserstable td {
+ border-bottom: 1px solid #e1e1e1;
+ height: 26px;
+ vertical-align: middle;
+ padding: 0 2px;
+}
+
+#otheruserstable .swatch {
+ border: 1px solid #999; width: 13px; height: 13px; overflow: hidden;
+ margin: 0 4px;
+}
+
+.usertdswatch { width: 1%; }
+.usertdname { font-size: 1.3em; color: #444; }
+.usertdstatus { font-size: 1.1em; font-style: italic; color: #999; }
+.usertdactivity { font-size: 1.1em; color: #777; }
+
+.usertdname input { border: 1px solid #bbb; width: 80px; padding: 2px; }
+.usertdname input.editactive, .usertdname input.editempty {
+ background: white; border-left: 1px solid #c3c3c3;
+ border-top: 1px solid #c3c3c3;
+ border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
+}
+.usertdname input.editempty { color: #888; font-style: italic;}
+
+#userlistbuttonarea { height: 28px; position: relative;
+ background: url(/static/img/jun09/pad/inviteshare2.gif) repeat-x 0 0; }
+#sharebutton {
+ background: url(/static/img/jun09/pad/inviteshare2.gif) no-repeat 0 -31px;
+ position: absolute; display: block; top: 3px; padding-top: 23px;
+ height: 0; overflow: hidden; width: 96px; left: 96px; }
+
+ /*#guestslabel { font-size: 1.2em; position: absolute; width: auto;
+ height: 22px; line-height: 22px; top: 4px; left: 8px; }
+#guestsmenu { font-size: 1.2em; position: absolute; left: 100px;
+ top: 5px; width: 95px; }
+.guestpolicystuff { display: none; }*/
+
+.guestprompt { border: 1px solid #ccc; font-size: 1.2em;
+ padding: 5px; color: #222; background: #ffc; }
+.guestprompt .choices { float: right; }
+.guestprompt a { margin: 0 0.5em; }
+
+#hdraggie {
+ background: url(/static/img/jun09/pad/hdraggie.gif) repeat-x center top;
+ height: 10px; cursor: S-resize; }
+
+#padchat { border: 1px solid #c4c4c4; }
+
+#chattop { background: #ecf2fa; padding: 5px; font-size: 1.2em; border-bottom: 1px solid #ddd; }
+#chattop a { color: #36b; }
+#chatlines { height: 198px;/*...initially*/ overflow: auto; background: #fafafa; position: relative; }
+#chatlines .chatline { color: #444; padding-left: 5px; padding-top: 2px; padding-bottom: 2px;
+ background: #ddd; overflow: hidden; }
+#chatlines .chatlinetime { display: block; font-size: 1em; color: #666; float: right; width: auto;
+ padding-right: 5px; }
+#chatlines .chatlinename, #chatlines .chatlinetext { font-size: 1.2em; }
+#chatlines h2 { margin: 0; padding-left: 5px; padding-top: 2px; padding-bottom: 2px; color: #999; font-style: italic; font-weight: bold; font-size: 1.2em; }
+#chatbottom { background: #ecf2fa; padding: 4px; }
+#chatprompt { font-size: 1.2em; color: #444; float: left; line-height: 22px; width: 35px; text-align: right; }
+#chatentryform { margin-left: 40px; }
+#chatentrybox { font-size: 1.2em; color: #444;
+ padding: 2px; height: 16px; margin: 0; border-left: 1px solid #c3c3c3;
+ border-top: 1px solid #c3c3c3;
+ border-right: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
+ width: 230px; }
+#padchat a#chatloadmore { display: none; font-size: 1.2em; padding: 2px 5px; font-style: italic; }
+#padchat #chatloadingmore { display: none; font-size: 1.2em; padding: 2px 5px; font-style: italic;
+ color: #999; }
+#padchat a#chatloadmore:focus { outline: 0; }
+
+#djs { font-family: monospace; font-size: 10pt;
+ height: 200px; overflow: auto; border: 1px solid #ccc;
+ background: #fee; margin: 0; padding: 6px;
+}
+#djs p { margin: 0; padding: 0; display: block; }
+
+#connectionbox {
+ position: absolute; left: 0; top: 0; width: 100%;
+ height: 191px;/*...initially; #padusers height */
+ z-index: 10; zoom: 1; overflow: hidden;
+}
+#connectionboxinner {
+ position: relative; width: 100%; height: 100%; overflow: hidden;
+}
+.cboxconnecting #connectionboxinner {
+ background: #ffd url(/static/img/jun09/pad/connectingbar.gif) no-repeat center 60px;
+}
+.cboxreconnecting #connectionboxinner {
+ background: #fed url(/static/img/jun09/pad/connectingbar.gif) no-repeat center 60px;
+}
+.cboxdisconnected #connectionboxinner {
+ background: #fdd;
+}
+.cboxdisconnected #connectionboxinner div { display: none; }
+.cboxdisconnected_userdup #connectionboxinner #disconnected_userdup { display: block; }
+.cboxdisconnected_initsocketfail #connectionboxinner #disconnected_initsocketfail { display: block; }
+.cboxdisconnected_looping #connectionboxinner #disconnected_looping { display: block; }
+.cboxdisconnected_slowcommit #connectionboxinner #disconnected_slowcommit { display: block; }
+.cboxdisconnected_unauth #connectionboxinner #disconnected_unauth { display: block; }
+.cboxdisconnected_unknown #connectionboxinner #disconnected_unknown { display: block; }
+.cboxdisconnected_initsocketfail #connectionboxinner #reconnect_advise,
+.cboxdisconnected_looping #connectionboxinner #reconnect_advise,
+.cboxdisconnected_slowcommit #connectionboxinner #reconnect_advise,
+.cboxdisconnected_unknown #connectionboxinner #reconnect_advise { display: block; }
+.cboxdisconnected div#reconnect_form { display: block; }
+.cboxdisconnected .disconnected h2 { display: none; }
+.cboxdisconnected .disconnected .h2_disconnect { display: block; }
+.cboxdisconnected_userdup .disconnected h2.h2_disconnect { display: none; }
+.cboxdisconnected_userdup .disconnected h2.h2_userdup { display: block; }
+.cboxdisconnected_unauth .disconnected h2.h2_disconnect { display: none; }
+.cboxdisconnected_unauth .disconnected h2.h2_unauth { display: block; }
+
+#connectionstatus {
+ position: absolute; width: 37px; height: 32px; overflow: hidden;
+ right: 0;
+ z-index: 11;
+}
+#connectionboxinner .connecting {
+ margin-top: 20px;
+ font-size: 2.0em; color: #555;
+ text-align: center; display: none;
+}
+.cboxconnecting #connectionboxinner .connecting { display: block; }
+
+#connectionboxinner .disconnected h2 {
+ font-size: 1.8em; color: #333;
+ text-align: left;
+ margin-top: 10px; margin-left: 10px; margin-right: 10px;
+ margin-bottom: 10px;
+}
+#connectionboxinner .disconnected p {
+ margin: 10px 10px;
+ font-size: 1.2em;
+ line-height: 1.1;
+ color: #333;
+}
+#connectionboxinner .disconnected { display: none; }
+.cboxdisconnected #connectionboxinner .disconnected { display: block; }
+
+#connectionboxinner .reconnecting {
+ margin-top: 20px;
+ font-size: 1.6em; color: #555;
+ text-align: center; display: none;
+}
+.cboxreconnecting #connectionboxinner .reconnecting { display: block; }
+
+#reconnect_form button {
+ position: relative; width: 268px; height: 28px; left: 10px;
+ font-size: 12pt;
+}
+
+/* We give docbar a higher z-index than its descendant impexp-wrapper in
+ order to allow the Import/Export panel to be on top of stuff lower
+ down on the page in IE. Strange but it works! */
+#docbar { z-index: 52; }
+
+#impexp-wrapper { width: 500px; right: 10px; }
+#impexp-panel { height: 160px; }
+.docbarimpexp-closing #impexp-wrapper { z-index: 50; }
+
+#savedrevs-wrapper { width: 100%; left: 0; }
+#savedrevs-panel { height: 79px; }
+.docbarsavedrevs-closing #savedrevs-wrapper { z-index: 50; }
+#savedrevs-wrapper .dbpanel-rightedge { background-position: 0 -10px; }
+
+#options-wrapper { width: 340px; right: 200px; }
+#options-panel { height: 114px; }
+.docbaroptions-closing #options-wrapper { z-index: 50; }
+
+#security-wrapper { width: 320px; right: 300px; }
+#security-panel { height: 130px; }
+.docbarsecurity-closing #security-wrapper { z-index: 50; }
+
+#revision-notifier { position: absolute; right: 8px; top: 25px;
+ width: auto; height: auto; font-size: 1.2em; background: #ffc;
+ border: 1px solid #aaa; color: #444; padding: 3px 5px;
+ display: none; z-index: 55; }
+#revision-notifier .label { color: #777; font-weight: bold; }
+
+/* We don't ever actually hide the wrapper, even when the panel is
+ cloased, so that its contents can always be manipulated accurately. */
+.dbpanel-wrapper { position: absolute;
+ overflow: hidden; /* animated: */ height: 0; top: 25px; /* /animated */
+ z-index: 51; zoom: 1; }
+.dbpanel-panel { position: absolute; bottom: 0; width: 100%; }
+
+.dbpanel-middle { margin-left: 7px; margin-right: 7px;
+ position: relative; height: 100%; overflow: hidden; zoom: 1; }
+.dbpanel-inner { background: #f7f7f7 /* covered up by images */;
+ width: 100%; height: 100%; position: absolute; overflow: hidden; top: -10px; }
+
+.dbpanel-top { position: absolute; top: 0; width: 100%;
+ height: 400px; background-image: url(/static/img/jun09/pad/docpanelmiddle2.png);
+ background-position: left top; }
+
+.dbpanel-bottom { position: absolute; height: 400px;
+ bottom: -390px; width: 100%;
+ background-image: url(/static/img/jun09/pad/docpanelmiddle2.png);
+ background-position: left top;
+}
+
+* html .dbpanel-top, * html .dbpanel-bottom { /* for IE 6+ */
+ background-color: transparent;
+ background-image: url(/static/img/apr09/blank.gif);
+ /* scale the image instead of repeating, but it amounts to the same */
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/docpanelmiddle2.png", sizingMethod="scale");
+}
+
+.dbpanel-leftedge, .dbpanel-rightedge, .dbpanel-botleftcorner, .dbpanel-botrightcorner {
+ position: absolute;
+ background-repeat: no-repeat;
+ background-color: transparent;
+ background-image: url(/static/img/jun09/pad/docpaneledge2.png);
+}
+
+.dbpanel-leftedge, .dbpanel-rightedge { height: 100%; width: 7px; bottom: 11px; }
+.dbpanel-botleftcorner, .dbpanel-botrightcorner { height: 11px; width: 7px; bottom: 0; }
+
+.dbpanel-leftedge, .dbpanel-botleftcorner { left: 0; background-position: -7px 0; }
+.dbpanel-rightedge, .dbpanel-botrightcorner { right: 0; background-position: 0 0; }
+
+#importexport { position: absolute; top: 5px; left: 0; font-size: 1.2em; color: #444;
+ height: 100%; width: 100%; }
+
+* html .dbpanel-leftedge, * html .dbpanel-rightedge, * html .dbpanel-botleftcorner, * html .dbpanel-botrightcorner {
+ background-color: transparent;
+ background-image: url(/static/img/apr09/blank.gif);
+ /* crop the image */
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/docpaneledge2.png", sizingMethod="crop");
+}
+* html .dbpanel-leftedge, * html .dbpanel-botleftcorner { left: -7px; width: 14px; }
+
+#impexp-importlabel { position: absolute; top: 5px; left: 10px; width: 300px; }
+
+#importform { position: absolute; top: 24px; left: 5px; width: 300px; height: 60px; }
+#importformsubmitdiv, #importformfilediv { padding: 5px 5px; }
+#importexport .importformenabled {
+ background: #cfc;
+ border: 1px solid #292;
+}
+#importexport span.nowrap { white-space: nowrap; }
+#importexport #importstatusball { margin-left: 3px; padding-top: 1px; display: none; }
+#importexport #importarrow { margin-left: 5px; padding-top: 1px; display: none; }
+#importexport .importmessage { border: 1px solid #992;
+ background: #ffc; padding: 5px; font-size: 85%; display: none; }
+#importexport #importmessagefail { margin-top: 5px; }
+#importexport #importmessagesuccess { margin: 0 20px; }
+#importexport a.disabledexport {
+ color: #333; text-decoration: none;
+ opacity: 0.5; filter: alpha(opacity = 50) /*IE*/;
+}
+#importexport #importfileinput { padding: 2px 0; }
+#importexport #importsubmitinput { padding: 2px; }
+
+#impexp-divider { position: absolute; left: 320px; top: 5px; height: 135px; width: 2px;
+ background: #ddd; }
+#impexp-close { display: block; position: absolute; right: 2px; bottom: 15px;
+ width: auto; height: auto; font-size: 85%; color: #444;
+ z-index: 61 /* > clickcatcher */}
+#impexp-disabled-clickcatcher {
+ display: none;
+ position: absolute; width: 100%; height: 100%;
+ z-index: 60;
+}
+
+#impexp-exportlabel { position: absolute; top: 5px; left: 350px;
+ width: 300px; }
+#exportlinks .exportlink {
+ display: block; position: absolute; height: 22px; width: auto;
+ background-repeat: no-repeat;
+ background-image: url(/static/img/jun09/pad/fileicons.gif);
+ line-height: 22px; padding-left: 22px; padding-right: 2px;
+}
+#exportlinks .n1 { left: 350px; top: 30px; }
+#exportlinks .n2 { left: 350px; top: 57px; }
+#exportlinks .n3 { left: 350px; top: 84px; }
+#exportlinks .n4 { left: 485px; top: 30px; }
+#exportlinks .n5 { left: 485px; top: 57px; }
+#exportlinks .n6 { left: 485px; top: 84px; }
+#exportlinks .exporthrefdoc { background-position: 2px -1px; }
+#exportlinks .exporthrefhtml { background-position: 2px -25px; }
+#exportlinks .exporthreflink { background-position: 2px -49px; }
+#exportlinks .exporthrefodt { background-position: 2px -73px; }
+#exportlinks .exporthrefpdf { background-position: 2px -97px; }
+#exportlinks .exporthreftxt { background-position: 2px -121px; }
+
+#savedrevisions { position: absolute; top: 0; left: 0; font-size: 1.2em;
+ color: #444; height: 100%; width: 100%; }
+#savedrevs-scrolly { height: 75px; width: auto; margin-right: 136px;
+ overflow: hidden; position: relative; top: 1px;
+}
+#savedrevs-scrollleft { height: 100%; width: 14px; position: absolute;
+ left: 0; top: 0; cursor: pointer;
+ background: url(/static/img/jun09/pad/savedrevarrows.gif) no-repeat right top;
+}
+#savedrevs-scrollright { height: 100%; width: 14px; position: absolute;
+ right: 0; top: 0; cursor: pointer;
+ background: url(/static/img/jun09/pad/savedrevarrows.gif) no-repeat left top;
+}
+#savedrevs-scrolly .disabledscrollleft { background-position: right bottom; }
+#savedrevs-scrolly .disabledscrollright { background-position: left bottom; }
+#savedrevs-scrollouter { margin-left: 14px; margin-right: 14px;
+ width: auto; height: 100%; overflow: hidden; position: relative;
+}
+#savedrevs-scrollinner { position: absolute; width: 1px; height: 100%;
+ overflow: visible; right: 0/*...initially*/; top: 0; }
+#savedrevisions .srouterbox { width: 120px; height: 100%;
+ position: absolute; top: 0;
+}
+#savedrevisions .srinnerbox { position: relative; top: 8px;
+ height: 59px; width: auto; border-left: 1px solid #ddd;
+ padding: 0 8px 0 8px; }
+#savedrevisions a.srname { display: block; white-space: nowrap;
+ text-overflow: ellipsis /*no FF support*/; overflow: hidden;
+ text-decoration: none; color: #444; cursor: text;
+ padding: 1px; height: 14px; position: relative; left: -1px;
+ width: 100px /*specify for proper overflow in IE*/;
+}
+#savedrevisions a.srname:hover { text-decoration: none; color: #444;
+ border: 1px solid #ccc; padding: 0; }
+#savedrevisions .sractions { font-size: 85%; color: #ccc;
+ margin-top: 1px; height: 12px; }
+#savedrevisions .sractions a { text-decoration: none;
+ color: #06c; }
+#savedrevisions .sractions a:hover { text-decoration: underline; }
+#savedrevisions .srtime { color: #666; font-size: 90%;
+ white-space: nowrap; margin-top: 3px; }
+#savedrevisions .srauthor { color: #666; font-size: 90%;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis /*no FF*/;
+}
+#savedrevisions .srtwirly { position: absolute; display: block;
+ bottom: 0; right: 10px; display: none; }
+#savedrevisions .srnameedit {
+ position: absolute;
+}
+#savedrevs-savenow { display: block; position: absolute;
+ overflow: hidden; height: 0; padding-top: 24px; width: 81px;
+ top: 22px; right: 27px;
+ background: url(/static/img/jun09/pad/savedrevsgfx2.gif) no-repeat 0 0;
+}
+#savedrevs-savenow:active { background-position: 0 -24px; }
+#savedrevs-close { display: block; position: absolute; right: 7px; bottom: 8px;
+ width: auto; height: auto; font-size: 85%; color: #444; }
+form#reconnectform { display: none; }
+
+#padoptions { position: absolute; top: 0; left: 0; font-size: 1.2em;
+ color: #444; height: 100%; width: 100%; line-height: 15px; }
+#options-viewhead { font-weight: bold; position: absolute; top: 10px; left: 15px;
+ width: auto; height: auto; }
+#padoptions label { display: block; }
+#padoptions input { padding: 0; margin: 0; }
+#options-colorscheck { position: absolute; left: 15px; top: 34px; width: 15px; height: 15px; }
+#options-colorslabel { position: absolute; left: 35px; top: 34px; }
+#options-linenoscheck { position: absolute; left: 15px; top: 57px; width: 15px; height: 15px; }
+#options-linenoslabel { position: absolute; left: 35px; top: 57px; }
+#options-fontlabel { position: absolute; left: 15px; top: 82px; }
+#viewfontmenu { position: absolute; top: 80px; left: 90px; width: 110px; }
+#options-viewexplain { position: absolute; left: 215px; top: 15px; width: 100px; height: 70px;
+ padding-left: 10px; padding-top: 10px; border-left: 1px solid #ccc;
+ line-height: 20px; font-weight: bold; color: #999; }
+#options-close { display: block; position: absolute; right: 7px; bottom: 8px;
+ width: auto; height: auto; font-size: 85%; color: #444; }
+
+#padsecurity { position: absolute; top: 0; left: 0; font-size: 1.2em;
+ color: #444; height: 100%; width: 100%; line-height: 15px; }
+#security-close { display: block; position: absolute; right: 7px; bottom: 8px;
+ width: auto; height: auto; font-size: 85%; color: #444; }
+#security-passhead { font-weight: bold; position: absolute; top: 90px; left: 15px;
+ width: auto; height: auto; }
+#security-passbody { position: absolute; left: 75px; top: 90px; }
+#security-passwordedit { height: 15px; border: 1px solid #bbb;
+ position: absolute; top: 0; left: 15px; width: 120px; }
+#security-password a { text-decoration: none; display: block;
+ width: auto; height: auto; }
+#password-savelink, #password-cancellink {position: absolute; top: 0; }
+#security-password a:hover { text-decoration: underline; }
+#password-savelink { left: 144px; color: #06c; }
+#password-cancellink { left: 180px; color: #666; }
+#password-nonedit { left: 15px; position: absolute;
+ width: 220px; top: 0; }
+#password-setlink { color: #06c; }
+#password-clearlink { color: #06c; }
+#password-display { height: 15px; width: auto; }
+#password-inedit { display: none; }
+#password-display, #password-setlink, #password-clearlink {
+ float: left; margin-right: 10px;
+}
+#password-display { font-size: 18px; }
+#security-password .nopassword #password-display { font-size: 100%; }
+#security-password .nopassword #password-clearlink { display: none; }
+#security-password .nopassword #password-setlink { left: 60px; }
+
+#security-access { position: absolute; left: 15px; width: 200px; }
+#security-accesshead { font-weight: bold; position: absolute; top: 10px;
+ left: 0; width: auto; height: auto; }
+#security-access input, #security-access label { position: absolute; }
+#security-access input { left: 10px; }
+#security-access label { left: 30px; width: 250px; }
+#access-private, #access-private-label { top: 35px; }
+#access-public, #access-public-label { top: 60px; }
+#security-access label { color: #999; }
+#security-access label strong { font-weight: normal; padding-right: 10px;
+ color: #444; }
+
+#mainmodals { z-index: 600; /* higher than the modals themselves
+ so that modals are on top in IE */ }
+
+.modalfield { font-size: 1.2em; padding: 1px; border: 1px solid #bbb;
+ position: absolute;}
+#mainmodals .editempty { color: #aaa; }
+
+<% feedbackbox = {width:400, height:270}; %>
+#feedbackbox {
+ position: absolute; display: none;
+ width: <%=feedbackbox.width%>px; height: <%=feedbackbox.height%>px;
+ left: 100px/*set in code*/; bottom: 50px;
+ z-index: 501; zoom: 1;
+}
+#feedbackbox-tl, #feedbackbox-tr, #feedbackbox-bl, #feedbackbox-br,
+#feedbackbox-hide, #feedbackbox-send, #feedbackbox-back {
+ position: absolute; display: block;
+ background-repeat: no-repeat;
+ background-image: url(/static/img/jun09/pad/feedbackbox2.gif);
+}
+#feedbackbox-tl { width: <%=feedbackbox.width-8%>px;
+ height: <%=feedbackbox.height-8%>px; left: 0; top: 0;
+ background-position: left top; }
+#feedbackbox-tr { width: 8px; height: <%=feedbackbox.height-8%>px;
+ right: 0; top: 0; background-position: right top; }
+#feedbackbox-bl { width: <%=feedbackbox.width-8%>px;
+ height: 8px; left: 0; bottom: 0;
+ background-position: left bottom; }
+#feedbackbox-br { width: 8px; height: 8px; bottom: 0; right: 0;
+ background-position: right bottom; }
+#feedbackbox-hide { width: 22px; height: 22px; right: 9px; top: 7px;
+ background-position: -569px -6px;
+}
+#feedbackbox-back { width: <%=feedbackbox.width-16%>px;
+ height: <%=feedbackbox.height-16%>px; left: 8px; top: 8px;
+ background-position: -8px -8px;
+ background-color: white; }
+#feedbackbox-contents { width: <%=feedbackbox.width-16%>px;
+ height: <%=feedbackbox.height-16%>px; left: 8px; top: 8px;
+ position: absolute; font-size: 1.4em; color: #444; }
+#feedbackbox-contentsinner { padding: 10px; }
+#feedbackbox-send { width: 50px; height: 22px; right: 15px; bottom: 15px;
+ background-position: -535px -363px;
+}
+#feedbackbox-email { left: 90px; top: 48px; width: 356px; height: auto; }
+#feedbackbox-message { left: 90px; top: 84px; width: 358px; height: 100px; }
+#feedbackbox-response { position: absolute; bottom: 15px; left: 15px;
+ width: 390px; height: auto; font-size: 1.2em; display: none; }
+#feedbackbox .goodresponse { font-weight: bold; color: green; }
+#feedbackbox .badresponse { font-weight: bold; color: red; }
+#feedbackbox p { margin-bottom: 1em; }
+#feedbackbox ul { margin: 1em 0 1em 2em }
+#feedbackbox li { padding: 0.3em 0; }
+#feedbackbox li a { display: block; font-weight: bold; }
+#feedbackbox li a:hover { background: #ffe; }
+#feedbackbox a, #feedbackbox li a:visited { color: #47b; }
+#feedbackbox tt { font-size: 110%; }
+
+<% var shareboxfull = {width:485, height:326}; %>
+#sharebox {
+ position: absolute;
+ width: 485px;
+ left: 300px/*set in code*/; top: 100px; display: none;
+ z-index: 501; zoom: 1;
+ overflow: hidden;
+ background: white; border: 1px solid #999;
+}
+#sharebox { height: 160px/*set in code*/; }
+.nonprouser #sharebox { height: 110px/*set in code*/; }
+#sharebox-inner { width: 100%; }
+#sharebox-forms { position: absolute; top: 50px; width: 100%; }
+#sharebox-hide, #sharebox-send {
+ position: absolute; background-repeat: no-repeat;
+ background-image: url(/static/img/jun09/pad/sharebox4.gif);
+}
+#sharebox-hide, #sharebox-send { display: block; }
+#sharebox-hide { width: 22px; height: 22px; right: 9px; top: 7px;
+ background-position: <%= -(shareboxfull.width-31) %>px -6px;
+}
+#sharebox-send { width: 87px; height: 22px; right: 15px; top: <%= shareboxfull.height-22-15 %>px;
+ background-position: <%= -(shareboxfull.width-87-15) %>px <%= -(shareboxfull.height-22-15) %>px;
+}
+#sharebox-url { position: absolute; left: 20px; top: 42px; width: 440px; height: 18px;
+ text-align: left; font-size: 1.3em; line-height: 18px; padding: 2px; }
+
+#sharebox-to { left: 90px; top: 117px; height: auto; width: 378px; background: #ffe; }
+#sharebox-subject { left: 90px; top: 150px; height: auto; width: 378px; font-weight: bold; }
+#sharebox-message { left: 90px; top: 182px; width: 380px; height: 90px; }
+#sharebox-response { position: absolute; bottom: 15px; left: 15px;
+ width: 350px; height: auto; font-size: 1.2em; display: none; }
+#sharebox .goodresponse { font-weight: bold; color: green; }
+#sharebox .badresponse { font-weight: bold; color: red; }
+#sharebox-dislink { position: absolute; left: 12px; top: 78px;
+ height: 22px; width: 220px; cursor: pointer;
+ background-image: url(/static/img/jun09/pad/sharedistri.gif);
+ background-repeat: no-repeat;
+ background-position: 0 5px;
+}
+.sharebox-open #sharebox-dislink { background-position: 0 -28px; }
+#sharebox-shownwhenexpanded { display: none; }
+.sharebox-open #sharebox-shownwhenexpanded { display: block; }
+
+#sharebox-pastelink { font-size: 155%; font-weight: bold;
+ top: 13px; left: 17px; position: absolute; color: #444; }
+#sharebox-orsend { font-size: 145%; font-weight: bold;
+ top: 80px; left: 31px; position: absolute; color: #444; }
+#sharebox-fieldname-to, #sharebox-fieldname-subject, #sharebox-fieldname-message {
+ position: absolute; font-weight: bold; font-size: 125%;
+ left: 15px; color: #222;
+}
+#sharebox-fieldname-to { top: 119px; }
+#sharebox-fieldname-subject { top: 152px; }
+#sharebox-fieldname-message { top: 183px; }
+
+#sharebox-stripe { position: absolute; left: 10px;
+ width: 436px; top: 8px; height: 45px; line-height: 1.2; }
+#sharebox-stripe div { padding: 5px; font-size: 130%; }
+#sharebox-stripe strong { font-weight: bold; }
+.sharebox-stripe-public { background: #cfc; }
+.sharebox-stripe-private { background: #fec; }
+.sharebox-stripe-public .private { display: none; }
+.sharebox-stripe-private .public { display: none; }
+#sharebox-stripe a { color: #06c; }
+
+.nonprouser #sharebox-stripe { display: none; }
+.nonprouser #sharebox-forms { top: 0; }
+
+#viewbarcontents { display: none; }
+#viewzoomtitle {
+ position: absolute; left: 10px; top: 4px; height: 20px; line-height: 20px;
+ width: auto;
+}
+#viewzoommenu {
+ position: absolute; top: 3px; left: 50px;
+ width: 65px;
+}
+#bottomarea { height: 28px; overflow: hidden; position: relative;
+ font-size: 1.2em; color: #444; }
+#widthprefcheck { position: absolute;
+ background-image: url(/static/img/jun09/pad/layoutbuttons.gif);
+ background-repeat: no-repeat; cursor: pointer;
+ width: 86px; height: 20px; top: 4px; right: 2px; }
+.widthprefunchecked { background-position: -1px -1px; }
+.widthprefchecked { background-position: -1px -23px; }
+#sidebarcheck { position: absolute;
+ background-image: url(/static/img/jun09/pad/layoutbuttons.gif);
+ background-repeat: no-repeat; cursor: pointer;
+ width: 86px; height: 20px; top: 4px; right: 90px; }
+.sidebarunchecked { background-position: -1px -45px; }
+.sidebarchecked { background-position: -1px -67px; }
+#feedbackbutton { display: block; position: absolute; width: 68px;
+ height: 0; padding-top: 17px; overflow: hidden;
+ background: url(/static/img/jun09/pad/bottomareagfx.gif);
+ top: 5px; right: 220px;
+}
+
+#modaloverlay {
+ z-index: 500; display: none;
+ background-image: url(/static/img/jun09/pad/overlay2.png);
+ background-repeat: repeat-both;
+ width: 100%; position: absolute;
+ height: 400px; left: 0; top: 0;
+}
+
+* html #modaloverlay { /* for IE 6+ */
+ opacity: 1; /* in case this is looked at */
+ background-image: none;
+ background-repeat: no-repeat;
+ /* scale the image */
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/static/img/jun09/pad/overlay2.png", sizingMethod="scale");
+}
diff --git a/etherpad/src/static/css/pro-signup.css b/etherpad/src/static/css/pro-signup.css
new file mode 100644
index 0000000..b58d86d
--- /dev/null
+++ b/etherpad/src/static/css/pro-signup.css
@@ -0,0 +1,69 @@
+.pro-signup {
+}
+
+.pro-signup #about {
+ width: 400px;
+ font-size: 86%;
+ color: #333;
+}
+
+.pro-signup h1 {
+ border: 0;
+}
+
+.pro-signup h3 {
+ font-size: 1.2em;
+ font-weight: bold;
+ margin: 0 0 .75em 0;
+ color: #888;
+}
+
+form#pro-act-form {
+}
+
+div.inputdiv {
+ width: 400px;
+ float: left;
+ background: #efe;
+ padding: .75em;
+ border-right: 1px solid #999;
+}
+
+div.inputdiv p {
+ margin: .2em 0 .6em 0;
+}
+
+div.inputhelp {
+ width: 300px;
+ font-size: 86%;
+ color: #555;
+ float: left;
+ padding-left: 1em;
+ padding-top: .5em;
+}
+
+form input {
+ border: 1px solid #377ec6;
+}
+
+form button {
+ border: 0;
+ cursor: pointer;
+ color: #fff;
+ font-weight: bold;
+ overflow: visible;
+ padding: 0;
+ background: #70a4ec;
+ border: 1px solid #3773c6;
+ padding: 4px 6px;
+ margin-top: 4px;
+}
+
+div.err {
+ margin: 1em 0;
+ padding: 1em;
+ font-weight: bold;
+ border: 1px solid #500;
+ background: #fdd;
+}
+
diff --git a/etherpad/src/static/css/pro/account.css b/etherpad/src/static/css/pro/account.css
new file mode 100644
index 0000000..212a847
--- /dev/null
+++ b/etherpad/src/static/css/pro/account.css
@@ -0,0 +1,254 @@
+.account-container {
+ width: 434px;
+ margin: 0 auto;
+}
+
+#account-error {
+ margin: 1em 0;
+ padding: 1em;
+ background: #fee;
+ border: 1px solid #f66;
+ font-weight: bold;
+}
+
+#account-message {
+ margin: 1em 0;
+ padding: 1em;
+ background: #efe;
+ border: 1px solid #ccc;
+ font-weight: bold;
+}
+
+#signin-notice {
+ margin: 1em 0;
+ padding: 1em;
+ background: #fff6cc;
+ border: 1px solid #ccc;
+}
+
+/*---- blue box (general) ----*/
+/* TODO: move to different file, bluebox.css? */
+
+div.bb {
+ background: #f7f7f7;
+}
+
+div.bb div.bb-top {
+ position: relative;
+ width: 100%;
+ height: 30px;
+ background: url(/static/img/pro/box/blue-boxtop.gif) repeat-x 0 -30px;
+}
+
+div.bb div.bb-topleft {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 30px;
+ width: 9px;
+ background: url(/static/img/pro/box/blue-boxtop.gif) no-repeat 0 0;
+}
+
+div.bb div.bb-topright {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 30px;
+ width: 9px;
+ background: url(/static/img/pro/box/blue-boxtop.gif) no-repeat -9px 0;
+}
+
+div.bb div.bb-title {
+ color: #fff;
+ font-weight: bold;
+ line-height: 30px;
+ padding-left: 10px;
+}
+
+div.bb div.bb-in {
+ border-left: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+
+button.bluebutton {
+ border: 0;
+ cursor: pointer;
+ color: #fff;
+ font-weight: bold;
+ overflow: visible;
+ padding: 0;
+ background: #70a4ec;
+ border: 1px solid #3773c6;
+ padding: 4px 6px;
+}
+
+button.bluebutton120 {
+ background: url(/static/img/pro/buttons/bluebutton120.gif) no-repeat;
+ width: 120px;
+ height: 26px;
+ padding: 0;
+ border: 0;
+}
+
+
+/*---- sign-in box ----*/
+
+div.bb-signin div.bb-in {
+ padding: 10px 12px 20px 12px;
+}
+
+div.bb-signin label#email-label,
+div.bb-signin label#password-label {
+ display: block;
+ float: left;
+ width: 92px;
+ margin-top: 10px;
+ font-size: 1.1em;
+ color: #333;
+ padding-top: 5px;
+}
+
+div.bb-signin input.textin,
+div.bb-signin input.passin {
+ border: 1px solid #c2c2c2;
+ background: #ffffff;
+ margin-top: 10px;
+ width: 300px;
+ font-size: 1.1em;
+ float: right;
+}
+
+div.bb-signin input#rememberMe,
+div.bb-signin label#rememberMe-label {
+ float: left;
+}
+div.bb-signin input#rememberMe {
+ margin-top: 32px;
+}
+div.bb-signin label#rememberMe-label {
+ margin-left: 10px;
+ margin-top: 32px;
+ color: #555;
+ font-size: .9em;
+ display: block;
+}
+
+div.bb-signin button.bluebutton {
+ float: right;
+ margin-top: 24px;
+}
+
+
+div.account-container div#bottom-text {
+ padding-top: 20px;
+ padding-left: 4px;
+ font-size: .9em;
+}
+div.account-container div#bottom-text a {
+ text-decoration: none;
+}
+
+#guest-signin-choice {
+ display: block;
+ border: 1px solid green;
+ background: #efe;
+ padding: 1em;
+ margin: 1em 0;
+}
+
+#account-signin-choice {
+ display: block;
+ border: 1px solid blue;
+ background: #eef;
+ padding: 1em;
+ margin: 1em 0;
+}
+
+div#guest-knock-box {
+ width: 500px;
+ margin: 0 auto;
+ border: 1px solid green;
+ background: #efe;
+ font-weight: bold;
+ padding: 1em;
+ font-size: 1.5em;
+}
+
+div#guest-knock-denied {
+ border: 1px solid red;
+ background: #fee;
+ font-weight: bold;
+ font-size: 1.5em;
+ padding: 1em;
+ margin: 0 auto;
+ width: 500px;
+ display: none;
+}
+
+/*---- recover lost password ----*/
+
+div.bb-forgotpass div.bb-in {
+ padding: 10px 12px 12px 12px;
+}
+
+div.bb-forgotpass div#instructions {
+ font-size: .8em;
+ color: #222;
+}
+
+div.bb-forgotpass label {
+ float: left;
+ width: 92px;
+ margin-top: 10px;
+ font-size: 1.1em;
+ color: #333;
+ padding-top: 5px;
+}
+
+div.bb-forgotpass input.textin {
+ border: 1px solid #c2c2c2;
+ background: #fff;
+ margin-top: 14px;
+ width: 300px;
+ float: right;
+}
+
+div.bb-forgotpass button {
+ float: right;
+ margin-top: 16px;
+}
+
+
+/*---- my account ----*/
+/* TODO: re-style this and move to different file */
+
+div.my-account {
+ width: 600px;
+}
+
+div.my-account h2 {
+ font-size: 1.2em;
+ border-bottom: 1px solid #444;
+ color: #444;
+ margin: 1em 0;
+}
+
+div.my-account table {
+ width: 500px;
+}
+
+div.my-account table .ti input {
+ width: 100%;
+}
+
+div.my-account table th {
+ width: 160px;
+}
+
+div.my-account table th,
+div.my-account table td {
+ padding: 4px 8px;
+}
+
+
diff --git a/etherpad/src/static/css/pro/framedpage-pro.css b/etherpad/src/static/css/pro/framedpage-pro.css
new file mode 100644
index 0000000..cffa58b
--- /dev/null
+++ b/etherpad/src/static/css/pro/framedpage-pro.css
@@ -0,0 +1,125 @@
+/*--- farmed page styles ---*/
+
+/*------
+ Global Container
+------*/
+
+body#framedpagebody {
+ background: #fff;
+}
+
+#container {
+ font-family: Arial, Helvetica, Calibri, sans-serif;
+ width: 920px; margin: 0 auto;
+}
+
+/*------
+ Layout
+------*/
+
+/* framed page general */
+div.fpcontent {
+ width: 888px;
+ margin: 0 auto;
+ font-size: 1.3em;
+ background-color: #fff;
+ padding-top: 1em;
+}
+
+div.fpcontent p {
+ margin: 1em 0;
+ line-height: 150%;
+}
+div.fpcontent ul {
+ list-style: disc;
+ padding-left: 2em;
+}
+div.fpcontent ul li {
+ margin: 1em 0;
+}
+
+/* top header */
+
+body.pro-withtopbar {
+ background: url(/static/img/pro/header/pro-header-plustopnav-back.gif) repeat-x top !important;
+}
+
+#pro-topbar {
+ height: 48px;
+}
+
+#pro-topbar-inner {
+ width: 888px;
+ margin: 0 auto;
+ height: 48px;
+ line-height: 48px;
+ background: url(/static/img/pro/header/pro-header-logo.png) no-repeat top center;
+}
+
+#pro-topbar div#org-name a {
+ font-size: 1.4em;
+ color: #fff;
+ vertical-align: center;
+}
+
+#pro-topbar #accountnav {
+ float: right;
+ vertical-align: center;
+ color: #fff;
+}
+
+#pro-topbar #accountnav a {
+ color: #cde7ff;
+ text-decoration: underline;
+}
+
+
+/* navigation */
+
+#pro-topnav {
+ background: url(/static/img/pro/topnav/pro-topnav-back.gif) repeat-x top;
+ height: 36px;
+}
+
+#pro-topnav-inner {
+ margin: 0 auto;
+ height: 36px;
+ width: 888px;
+}
+
+#pro-topnav ul {
+ float: left;
+}
+#pro-topnav ul li {
+ display: block;
+ height: 36px;
+ float: left;
+}
+#pro-topnav ul li a {
+ display: block;
+ line-height: 36px;
+ margin: 0 20px;
+}
+#pro-topnav ul li.topnav_home a {
+ margin-left: 0;
+}
+#pro-topnav ul li a:hover { }
+#pro-topnav ul li.selected a {
+ color: #000;
+ background: url(/static/img/pro/topnav/pro-topnav-notch.gif) no-repeat center 28px;
+}
+
+#shuttingdown { position: relative; zoom: 1; border: 1px solid #992;
+ background: #ffc; padding: 0.6em; font-size: 1.2em; margin-top: 6px; }
+
+
+/*--- framed page styles ---*/
+
+div.global-pro-notice {
+ margin: .5em 1em;
+ border: 1px solid #f84;
+ background: #ffc;
+ font-weight: bold;
+ padding: 1em;
+}
+
diff --git a/etherpad/src/static/css/pro/padlist.css b/etherpad/src/static/css/pro/padlist.css
new file mode 100644
index 0000000..13d3171
--- /dev/null
+++ b/etherpad/src/static/css/pro/padlist.css
@@ -0,0 +1,115 @@
+
+/*---- nav ----*/
+
+#padlist-nav {
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+}
+
+#padlist-nav ul {
+ margin: 0;
+ padding: 0;
+ float: left;
+}
+
+#padlist-nav form {
+ float: right;
+ padding-top: 2px;
+}
+
+#padlist-nav ul li {
+ list-style: none;
+ float: left;
+ padding: 0;
+ margin: 0;
+}
+
+#padlist-nav ul li a {
+ display: block;
+ padding: 8px 12px;
+ font-size: .8em;
+}
+
+#padlist-nav ul li a.selected {
+ color: black;
+}
+
+#padlist-nav ul li a#nav-all-pads {
+ padding-left: 0;
+}
+
+/*---- showing sentence ----*/
+
+#showing-desc {
+ margin-top: 12px;
+ color: #464;
+ font-size: .8em;
+ font-style: italic;
+}
+
+/*---- table ----*/
+
+#padtable {
+ margin-top: 1em;
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+}
+
+#padtable th {
+ font-weight: bold;
+}
+
+#padtable th,
+#padtable td {
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 4px 8px;
+}
+
+#padtable td.actions {
+ padding: 0;
+}
+
+#padtable tr:hover {
+ background: #ffffaa;
+}
+#padtable tr.toprow:hover {
+ background: inherit;
+}
+
+#padtable div.gear-drop {
+ width: 36px;
+ height: 20px;
+ background: url(/static/img/pro/padlist/gear-drop.gif) no-repeat center 4px;
+ cursor: pointer;
+ padding: 4px 8px;
+}
+
+#padtable tr.selected {
+/* background: #6670ff; */
+ background: #ffff88;
+}
+#padtable tr.selected td {
+ border-top: 1px solid black;
+ border-bottom: 1px solid black;
+ border-right: 0;
+}
+#padtable tr.selected td.first {
+ border-left: 1px solid black;
+}
+#padtable tr.selected td.last {
+ border-right: 1px solid black;
+}
+#padtable tr.selected td a {
+ color: #000;
+}
+
+div.padlist-notice {
+ border: 1px solid #ccc;
+ font-weight: bold;
+ background: #fff6cc;
+ padding: 1em;
+ margin-bottom: 1em;
+ font-size: 82.5%;
+}
+
diff --git a/etherpad/src/static/css/pro/pro-admin.css b/etherpad/src/static/css/pro/pro-admin.css
new file mode 100644
index 0000000..e7462c9
--- /dev/null
+++ b/etherpad/src/static/css/pro/pro-admin.css
@@ -0,0 +1,343 @@
+/*----------------------------------------------------------------*/
+/* admin leftnav */
+/*----------------------------------------------------------------*/
+
+#admin-layout-table {
+ width: 100%;
+}
+
+#admin-layout-table td {
+}
+
+#admin-leftnav {
+ font-size: .81em;
+ border: 1px solid #ccc;
+ white-space: nowrap;
+ background: #eee;
+ padding: 0;
+}
+
+#admin-leftnav .leftnav-title {
+ padding: .75em .25em .25em .25em;
+ border-bottom: 1px solid #ccc;
+}
+#admin-leftnav ul {
+ padding: 0;
+ list-style: none;
+}
+
+#admin-leftnav ul ul {
+ list-style: disc;
+}
+
+#admin-leftnav li {
+ display: block;
+ width: 100%;
+ margin: 0;
+}
+
+#admin-leftnav li a {
+ display: block;
+ margin: 0;
+ padding: .5em;
+}
+
+#admin-leftnav li a:hover {
+ text-decoration: none;
+ background: #ffc;
+}
+
+#admin-leftnav li.selected a {
+ color: #000;
+ font-weight: bold;
+ background: #fff;
+}
+
+/*----------------------------------------------------------------*/
+/* admin content area */
+/*----------------------------------------------------------------*/
+
+#admin-right {
+ padding-left: 1em;
+}
+
+#admin-right h3 {
+ font-weight: bold;
+ font-size: 1.1em;
+ color: #666;
+ border-bottom: 1px solid #666;
+ margin: 1.25em 0;
+}
+
+#admin-right h3.top {
+ margin-top: 0;
+}
+
+/*----------------------------------------------------------------*/
+/* server dashboard */
+/*----------------------------------------------------------------*/
+
+#responsecodes-table {
+ border 1px solid #ccc;
+}
+#responsecodes-table td,
+#responsecodes-table th {
+ padding: .4em;
+}
+#responsecodes-table th {
+ font-weight: bold;
+ border-bottom: 1px solid #ccc;
+ padding-right: 2em;
+}
+
+/*----------------------------------------------------------------*/
+/* license manager */
+/*----------------------------------------------------------------*/
+
+div.lm-error-msg {
+ border: 1px solid #f99;
+ font-weight: bold;
+ background: #fdd;
+ padding: 0 1em;
+ margin-bottom: 1em;
+}
+
+div.lm-notice-msg {
+ border: 1px solid #ccc;
+ font-weight: bold;
+ background: #fff6cc;
+ padding: 0 1em;
+ margin-bottom: 1em;
+}
+
+#lm-status {
+ border: 1px solid #ccc;
+ padding: 1em;
+ background: #dfd;
+}
+
+#lm-status table td {
+ padding: .5em 1.5em .5em 0;
+ border-bottom: 1px solid #ccc;
+ white-space: nowrap;
+}
+
+#lm-edit-button-wrap { margin: 1em 0; }
+
+#lm-edit {
+ background: #eef;
+ border: 1px solid #ccc;
+ padding: 0 1em 1em 1em;
+}
+#lm-edit p {
+ margin: 1em 0 0 0;
+}
+#lm-edit-submit-wrap { margin: 1em 0; }
+
+#lm h3 {
+/* margin-left: 1em; */
+}
+
+/*----------------------------------------------------------------*/
+/* accountmanager */
+/*----------------------------------------------------------------*/
+
+.manage-accounts {
+ font-size: .76em;
+}
+
+.manage-accounts #message {
+ border: 1px solid #ccc;
+ background: #efe;
+ color: #666;
+ font-weight: bold;
+ padding: 1em;
+}
+
+.manage-accounts #warning {
+ border: 1px solid #ccc;
+ background: #ffd;
+ color: #333;
+ font-weight: bold;
+ padding: 1em;
+ margin-top: 1em;
+}
+
+.manage-accounts form#new-account-button {
+ margin: 1em 0;
+}
+
+table#accountlist {
+ border: 1px solid #ccc;
+ border-bottom: 0;
+}
+
+table#accountlist tr:hover {
+ background: #ffc;
+}
+
+table#accountlist th,
+table#accountlist td {
+ white-space: nowrap;
+ padding: .5em 1em .5em .5em;
+ border-bottom: 1px solid #ccc;
+}
+
+table#accountlist th {
+ font-weight: bold;
+ background-color: #eef;
+}
+
+.manage-accounts p.free-notice {
+ font-style: italic;
+ color: #162;
+}
+
+.manage-accounts p.account-tally {
+ font-style: italic;
+}
+
+/* new account form */
+
+.new-account-form {
+ border: 1px solid #ccc;
+ background: #eef;
+ padding: 0;
+ margin: 0;
+}
+
+.new-account-form .forminner {
+ padding: 1em;
+}
+
+.new-account-form div.formfield {
+ margin-top: .5em;
+ padding: 0 1em;
+}
+
+.new-account-form div.formfield label { display: block; margin-top: 1em; }
+.new-account-form div.formfield input.checkboxinput { float: left; width: 20px; }
+.new-account-form div.formfield input.textinput { display: block; width: 240px; }
+.new-account-form div.formfield input.temppassinput { display: block; width: 240px; }
+.new-account-form div.formfield label.checkboxlabel { float: left; margin-top: .333em; padding-left: .25em; }
+.newaccount .buttons-wrap { margin-left: 2em; }
+
+.newaccount #bottom-note {
+ color: #555;
+ margin-left: 2em;
+ width: 50%;
+}
+
+#error-message {
+ border: 1px solid red;
+ background: #fee;
+ padding: 1em;
+ font-weight: bold;
+ margin-bottom: 1em;
+}
+
+/* manage account page */
+
+table#manage-account {
+ border-left: 1px solid #ccc;
+ border-top: 1px solid #ccc;
+ background: #eef;
+}
+table#manage-account td,
+table#manage-account th {
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 4px 8px;
+}
+table#manage-account th {
+ text-align: right;
+}
+
+#delete-account-page div.confirm {
+ font-weight: bold;
+}
+
+#delete-account-page div.account-info {
+ border: 1px solid #555;
+ background: #fcc;
+ padding: 1em;
+ margin: 1em 0;
+ font-family: monospace;
+}
+
+#delete-account-page div.note {
+ margin-top: 1em;
+ margin-right: 222px;
+ font-size: .9em;
+ color: #555;
+}
+
+
+/*----------------------------------------------------------------*/
+/* PNE server config */
+/*----------------------------------------------------------------*/
+
+table#pne-config {
+ font-family: monospace;
+ font-size: 12px;
+ border-top: 1px solid #ccc;
+ border-left: 1px solid #ccc;
+ white-space: nowrap;
+ background: #fefefe;
+}
+
+table#pne-config th {
+ border-bottom: 2px solid #666;
+ font-weight: bold;
+}
+table#pne-config td {
+ padding: 2px;
+ border-bottom: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+}
+
+table#pne-config td.key {
+ color: #009;
+ padding-right: 4px;
+}
+table#pne-config td.val { color: #420; }
+
+/*----------------------------------------------------------------*/
+/* Pro config */
+/*----------------------------------------------------------------*/
+
+div#pro-config-message {
+ border: 1px solid #ccc;
+ padding: 1em;
+ font-weight: bold;
+ margin: 1em 0;
+ background: #cfc;
+}
+
+table#t-pro-config {
+ display: block;
+ border-left: 1px solid #aaa;
+ border-right: 1px solid #aaa;
+ border-bottom: 1px solid #aaa;
+}
+
+table#t-pro-config th,
+table#t-pro-config td {
+ border-top: 1px solid #aaa;
+ padding: 1em;
+ text-align: top;
+ vertical-align: top;
+}
+
+table#t-pro-config td textarea {
+ width: 100%;
+ height: 260px;
+}
+
+table#t-pro-config th {
+ text-align: right;
+ color: #963;
+ font-weight: bold;
+}
+
+
diff --git a/etherpad/src/static/css/pro/pro-home.css b/etherpad/src/static/css/pro/pro-home.css
new file mode 100644
index 0000000..03f163a
--- /dev/null
+++ b/etherpad/src/static/css/pro/pro-home.css
@@ -0,0 +1,65 @@
+
+#welcome-msg {
+ font-size: 1.2em;
+ color: #333;
+}
+
+#homeright {
+ width: 320px;
+ float: right;
+}
+
+#homeleft {
+ width: 548px;
+ float: left;
+}
+
+#homeleft-title {
+ font-weight: bold;
+ font-size: 1.0em;
+ margin-top: 1em;
+}
+
+.news-time-sep {
+ margin-top: 2em;
+}
+
+.news-time-sep .date {
+ float: left;
+ background: #fff;
+ padding-right: 1em;
+ color: #666;
+ font-size: .9em;
+}
+
+.news-time-sep .line {
+ height: .5em;
+ border-bottom: 1px solid #ccc;
+}
+
+.news-item {
+ padding: 0 2em 0 1em;
+ font-size: .86em;
+}
+
+/*--------------------------------------------------------------------------------
+ * recent pads
+ *--------------------------------------------------------------------------------*/
+
+#recent-pads #viewall {
+ display: block;
+ float: left;
+ margin: 0.8em 0;
+ font-size: 0.8em;
+}
+
+#homeright #padtable {
+ width: 100%;
+}
+
+#homeright h3 {
+ font-size: 1.0em;
+ font-weight: bold;
+ margin-top: 1em;
+}
+
diff --git a/etherpad/src/static/favicon.ico b/etherpad/src/static/favicon.ico
new file mode 100644
index 0000000..a833c3a
--- /dev/null
+++ b/etherpad/src/static/favicon.ico
Binary files differ
diff --git a/etherpad/src/static/img/davy/bg/home-createpad.png b/etherpad/src/static/img/davy/bg/home-createpad.png
new file mode 100644
index 0000000..e34e643
--- /dev/null
+++ b/etherpad/src/static/img/davy/bg/home-createpad.png
Binary files differ
diff --git a/etherpad/src/static/img/davy/bg/product.png b/etherpad/src/static/img/davy/bg/product.png
new file mode 100644
index 0000000..5a6f6f2
--- /dev/null
+++ b/etherpad/src/static/img/davy/bg/product.png
Binary files differ
diff --git a/etherpad/src/static/img/davy/btn/createpad-small.gif b/etherpad/src/static/img/davy/btn/createpad-small.gif
new file mode 100644
index 0000000..5df6502
--- /dev/null
+++ b/etherpad/src/static/img/davy/btn/createpad-small.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/backgrad.gif b/etherpad/src/static/img/jun09/pad/backgrad.gif
new file mode 100644
index 0000000..8fee1a5
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/backgrad.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/colorpicker.gif b/etherpad/src/static/img/jun09/pad/colorpicker.gif
new file mode 100644
index 0000000..effa3cc
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/colorpicker.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/connectingbar.gif b/etherpad/src/static/img/jun09/pad/connectingbar.gif
new file mode 100644
index 0000000..34f54e9
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/connectingbar.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/docpaneledge2.png b/etherpad/src/static/img/jun09/pad/docpaneledge2.png
new file mode 100644
index 0000000..c119c74
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/docpaneledge2.png
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/docpanelmiddle2.png b/etherpad/src/static/img/jun09/pad/docpanelmiddle2.png
new file mode 100644
index 0000000..d251c23
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/docpanelmiddle2.png
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_background.gif b/etherpad/src/static/img/jun09/pad/editbar_background.gif
new file mode 100644
index 0000000..54ef6e4
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_background.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_background_left.gif b/etherpad/src/static/img/jun09/pad/editbar_background_left.gif
new file mode 100644
index 0000000..fe8d06e
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_background_left.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_background_right.gif b/etherpad/src/static/img/jun09/pad/editbar_background_right.gif
new file mode 100644
index 0000000..55ab00a
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_background_right.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_bold.gif b/etherpad/src/static/img/jun09/pad/editbar_bold.gif
new file mode 100644
index 0000000..d22bcaf
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_bold.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_clearauthorship.gif b/etherpad/src/static/img/jun09/pad/editbar_clearauthorship.gif
new file mode 100644
index 0000000..2c6d109
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_clearauthorship.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_groupleft.gif b/etherpad/src/static/img/jun09/pad/editbar_groupleft.gif
new file mode 100644
index 0000000..3e18749
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_groupleft.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_groupright.gif b/etherpad/src/static/img/jun09/pad/editbar_groupright.gif
new file mode 100644
index 0000000..bf8b757
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_groupright.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_indent.gif b/etherpad/src/static/img/jun09/pad/editbar_indent.gif
new file mode 100644
index 0000000..989523a
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_indent.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_insertunorderedlist.gif b/etherpad/src/static/img/jun09/pad/editbar_insertunorderedlist.gif
new file mode 100644
index 0000000..b032d59
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_insertunorderedlist.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_italic.gif b/etherpad/src/static/img/jun09/pad/editbar_italic.gif
new file mode 100644
index 0000000..a017402
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_italic.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_outdent.gif b/etherpad/src/static/img/jun09/pad/editbar_outdent.gif
new file mode 100644
index 0000000..4b9bf38
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_outdent.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_redo.gif b/etherpad/src/static/img/jun09/pad/editbar_redo.gif
new file mode 100644
index 0000000..826a254
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_redo.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_save.gif b/etherpad/src/static/img/jun09/pad/editbar_save.gif
new file mode 100644
index 0000000..2ccced6
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_save.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_strikethrough.gif b/etherpad/src/static/img/jun09/pad/editbar_strikethrough.gif
new file mode 100644
index 0000000..92ffa23
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_strikethrough.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_underline.gif b/etherpad/src/static/img/jun09/pad/editbar_underline.gif
new file mode 100644
index 0000000..ec3cc4e
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_underline.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbar_undo.gif b/etherpad/src/static/img/jun09/pad/editbar_undo.gif
new file mode 100644
index 0000000..78ae0be
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbar_undo.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/editbarback.gif b/etherpad/src/static/img/jun09/pad/editbarback.gif
new file mode 100644
index 0000000..ab51802
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/editbarback.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/feedbackbox2.gif b/etherpad/src/static/img/jun09/pad/feedbackbox2.gif
new file mode 100644
index 0000000..f1b8f5b
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/feedbackbox2.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/fileicons.gif b/etherpad/src/static/img/jun09/pad/fileicons.gif
new file mode 100644
index 0000000..26f6388
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/fileicons.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/hdraggie.gif b/etherpad/src/static/img/jun09/pad/hdraggie.gif
new file mode 100644
index 0000000..0a6fe3e
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/hdraggie.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/icon_import_export.gif b/etherpad/src/static/img/jun09/pad/icon_import_export.gif
new file mode 100644
index 0000000..1b77245
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/icon_import_export.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/icon_pad_options.gif b/etherpad/src/static/img/jun09/pad/icon_pad_options.gif
new file mode 100644
index 0000000..68c79a7
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/icon_pad_options.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/icon_saved_revisions.gif b/etherpad/src/static/img/jun09/pad/icon_saved_revisions.gif
new file mode 100644
index 0000000..8040145
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/icon_saved_revisions.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/icon_security.gif b/etherpad/src/static/img/jun09/pad/icon_security.gif
new file mode 100644
index 0000000..9131fc3
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/icon_security.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/icon_time_slider.gif b/etherpad/src/static/img/jun09/pad/icon_time_slider.gif
new file mode 100644
index 0000000..5e4b4ab
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/icon_time_slider.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/inviteshare.gif b/etherpad/src/static/img/jun09/pad/inviteshare.gif
new file mode 100644
index 0000000..55345e5
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/inviteshare.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/inviteshare2.gif b/etherpad/src/static/img/jun09/pad/inviteshare2.gif
new file mode 100644
index 0000000..98d4c85
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/inviteshare2.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/layoutbuttons.gif b/etherpad/src/static/img/jun09/pad/layoutbuttons.gif
new file mode 100644
index 0000000..ea43432
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/layoutbuttons.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/ok_or_cancel.gif b/etherpad/src/static/img/jun09/pad/ok_or_cancel.gif
new file mode 100644
index 0000000..76ba692
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/ok_or_cancel.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/overlay2.png b/etherpad/src/static/img/jun09/pad/overlay2.png
new file mode 100644
index 0000000..c3d3f1c
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/overlay2.png
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/padtop5.gif b/etherpad/src/static/img/jun09/pad/padtop5.gif
new file mode 100644
index 0000000..e6e071d
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/padtop5.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/padtop5.png b/etherpad/src/static/img/jun09/pad/padtop5.png
new file mode 100644
index 0000000..22a0db7
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/padtop5.png
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/padtop5.xcf b/etherpad/src/static/img/jun09/pad/padtop5.xcf
new file mode 100644
index 0000000..c0bf7e4
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/padtop5.xcf
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/padtopback2.gif b/etherpad/src/static/img/jun09/pad/padtopback2.gif
new file mode 100644
index 0000000..db46567
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/padtopback2.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/public.gif b/etherpad/src/static/img/jun09/pad/public.gif
new file mode 100644
index 0000000..ac3093b
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/public.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/roundcorner_left.gif b/etherpad/src/static/img/jun09/pad/roundcorner_left.gif
new file mode 100644
index 0000000..000de75
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/roundcorner_left.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/roundcorner_right.gif b/etherpad/src/static/img/jun09/pad/roundcorner_right.gif
new file mode 100644
index 0000000..97acfbf
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/roundcorner_right.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/roundcorner_right_orange.gif b/etherpad/src/static/img/jun09/pad/roundcorner_right_orange.gif
new file mode 100644
index 0000000..717e3fc
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/roundcorner_right_orange.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/savedrevarrows.gif b/etherpad/src/static/img/jun09/pad/savedrevarrows.gif
new file mode 100644
index 0000000..2aa2e41
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/savedrevarrows.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/savedrevsgfx2.gif b/etherpad/src/static/img/jun09/pad/savedrevsgfx2.gif
new file mode 100644
index 0000000..45c3459
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/savedrevsgfx2.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/sharebox4.gif b/etherpad/src/static/img/jun09/pad/sharebox4.gif
new file mode 100644
index 0000000..eccaa7e
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/sharebox4.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/sharedistri.gif b/etherpad/src/static/img/jun09/pad/sharedistri.gif
new file mode 100644
index 0000000..8eb5891
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/sharedistri.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/syncdone.gif b/etherpad/src/static/img/jun09/pad/syncdone.gif
new file mode 100644
index 0000000..e4d971b
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/syncdone.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/syncing.gif b/etherpad/src/static/img/jun09/pad/syncing.gif
new file mode 100644
index 0000000..bbc731f
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/syncing.gif
Binary files differ
diff --git a/etherpad/src/static/img/jun09/pad/viewbargfx.gif b/etherpad/src/static/img/jun09/pad/viewbargfx.gif
new file mode 100644
index 0000000..396483a
--- /dev/null
+++ b/etherpad/src/static/img/jun09/pad/viewbargfx.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-cyan-menu-item-hover.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-cyan-menu-item-hover.gif
new file mode 100644
index 0000000..d0e428e
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-cyan-menu-item-hover.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-menu-item-hover.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-menu-item-hover.gif
new file mode 100644
index 0000000..8240ba3
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-menu-item-hover.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-semitransparent-menu-item-hover.png b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-semitransparent-menu-item-hover.png
new file mode 100644
index 0000000..6314d53
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-gloss-semitransparent-menu-item-hover.png
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-human-menu-item-hover.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-human-menu-item-hover.gif
new file mode 100644
index 0000000..7e70aae
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-human-menu-item-hover.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-osx-menu-item-hover.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-osx-menu-item-hover.gif
new file mode 100644
index 0000000..aa802e0
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-osx-menu-item-hover.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-bg.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-bg.gif
new file mode 100644
index 0000000..565e771
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-bg.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-menu-item-hover.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-menu-item-hover.gif
new file mode 100644
index 0000000..2825eb1
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-vista-menu-item-hover.gif
Binary files differ
diff --git a/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-xp-bg.gif b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-xp-bg.gif
new file mode 100644
index 0000000..11b238c
--- /dev/null
+++ b/etherpad/src/static/img/lib/jquery.contextmenu.images/cmenu-xp-bg.gif
Binary files differ
diff --git a/etherpad/src/static/img/may09/doc.gif b/etherpad/src/static/img/may09/doc.gif
new file mode 100644
index 0000000..2b62080
--- /dev/null
+++ b/etherpad/src/static/img/may09/doc.gif
Binary files differ
diff --git a/etherpad/src/static/img/may09/html.gif b/etherpad/src/static/img/may09/html.gif
new file mode 100644
index 0000000..f7837e5
--- /dev/null
+++ b/etherpad/src/static/img/may09/html.gif
Binary files differ
diff --git a/etherpad/src/static/img/may09/pdf.gif b/etherpad/src/static/img/may09/pdf.gif
new file mode 100644
index 0000000..1614d2c
--- /dev/null
+++ b/etherpad/src/static/img/may09/pdf.gif
Binary files differ
diff --git a/etherpad/src/static/img/may09/txt.gif b/etherpad/src/static/img/may09/txt.gif
new file mode 100644
index 0000000..c3f026e
--- /dev/null
+++ b/etherpad/src/static/img/may09/txt.gif
Binary files differ
diff --git a/etherpad/src/static/img/misc/status-ball.gif b/etherpad/src/static/img/misc/status-ball.gif
new file mode 100644
index 0000000..085ccae
--- /dev/null
+++ b/etherpad/src/static/img/misc/status-ball.gif
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/crushed_button_depressed.png b/etherpad/src/static/img/pad/timeslider/crushed_button_depressed.png
new file mode 100644
index 0000000..d75dcce
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/crushed_button_depressed.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/crushed_button_undepressed.png b/etherpad/src/static/img/pad/timeslider/crushed_button_undepressed.png
new file mode 100644
index 0000000..d86e3f3
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/crushed_button_undepressed.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/crushed_current_location.png b/etherpad/src/static/img/pad/timeslider/crushed_current_location.png
new file mode 100644
index 0000000..76e0835
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/crushed_current_location.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/crushed_timeslider_mockup.png b/etherpad/src/static/img/pad/timeslider/crushed_timeslider_mockup.png
new file mode 100644
index 0000000..f4ccbf1
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/crushed_timeslider_mockup.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/current_location.png b/etherpad/src/static/img/pad/timeslider/current_location.png
new file mode 100644
index 0000000..ab02792
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/current_location.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/pause.png b/etherpad/src/static/img/pad/timeslider/pause.png
new file mode 100644
index 0000000..657782c
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/pause.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/play.png b/etherpad/src/static/img/pad/timeslider/play.png
new file mode 100644
index 0000000..19afe03
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/play.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/play_button.png b/etherpad/src/static/img/pad/timeslider/play_button.png
new file mode 100644
index 0000000..bc1736d
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/play_button.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/star.png b/etherpad/src/static/img/pad/timeslider/star.png
new file mode 100644
index 0000000..e0c7099
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/star.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/star_selected.png b/etherpad/src/static/img/pad/timeslider/star_selected.png
new file mode 100644
index 0000000..c336589
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/star_selected.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/stepper_buttons.png b/etherpad/src/static/img/pad/timeslider/stepper_buttons.png
new file mode 100644
index 0000000..e011a45
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/stepper_buttons.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/timeslider_background.png b/etherpad/src/static/img/pad/timeslider/timeslider_background.png
new file mode 100644
index 0000000..faa45c6
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/timeslider_background.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/timeslider_left.png b/etherpad/src/static/img/pad/timeslider/timeslider_left.png
new file mode 100644
index 0000000..594d86b
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/timeslider_left.png
Binary files differ
diff --git a/etherpad/src/static/img/pad/timeslider/timeslider_right.png b/etherpad/src/static/img/pad/timeslider/timeslider_right.png
new file mode 100644
index 0000000..3bf10a2
--- /dev/null
+++ b/etherpad/src/static/img/pad/timeslider/timeslider_right.png
Binary files differ
diff --git a/etherpad/src/static/img/pro/box/blue-boxtop.gif b/etherpad/src/static/img/pro/box/blue-boxtop.gif
new file mode 100644
index 0000000..38e3538
--- /dev/null
+++ b/etherpad/src/static/img/pro/box/blue-boxtop.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/buttons/bluebutton120.gif b/etherpad/src/static/img/pro/buttons/bluebutton120.gif
new file mode 100644
index 0000000..2f22003
--- /dev/null
+++ b/etherpad/src/static/img/pro/buttons/bluebutton120.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/header/pro-header-logo.png b/etherpad/src/static/img/pro/header/pro-header-logo.png
new file mode 100644
index 0000000..b36daa8
--- /dev/null
+++ b/etherpad/src/static/img/pro/header/pro-header-logo.png
Binary files differ
diff --git a/etherpad/src/static/img/pro/header/pro-header-plustopnav-back.gif b/etherpad/src/static/img/pro/header/pro-header-plustopnav-back.gif
new file mode 100644
index 0000000..f7398fe
--- /dev/null
+++ b/etherpad/src/static/img/pro/header/pro-header-plustopnav-back.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/padlist/gear-drop.gif b/etherpad/src/static/img/pro/padlist/gear-drop.gif
new file mode 100644
index 0000000..ded0f24
--- /dev/null
+++ b/etherpad/src/static/img/pro/padlist/gear-drop.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/padlist/paper-icon.gif b/etherpad/src/static/img/pro/padlist/paper-icon.gif
new file mode 100644
index 0000000..161b66e
--- /dev/null
+++ b/etherpad/src/static/img/pro/padlist/paper-icon.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/padlist/trash-icon.gif b/etherpad/src/static/img/pro/padlist/trash-icon.gif
new file mode 100644
index 0000000..74b5ede
--- /dev/null
+++ b/etherpad/src/static/img/pro/padlist/trash-icon.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/topnav/pro-topnav-back.gif b/etherpad/src/static/img/pro/topnav/pro-topnav-back.gif
new file mode 100644
index 0000000..336fd05
--- /dev/null
+++ b/etherpad/src/static/img/pro/topnav/pro-topnav-back.gif
Binary files differ
diff --git a/etherpad/src/static/img/pro/topnav/pro-topnav-notch.gif b/etherpad/src/static/img/pro/topnav/pro-topnav-notch.gif
new file mode 100644
index 0000000..5dbe57b
--- /dev/null
+++ b/etherpad/src/static/img/pro/topnav/pro-topnav-notch.gif
Binary files differ
diff --git a/etherpad/src/static/js/billing.js b/etherpad/src/static/js/billing.js
new file mode 100644
index 0000000..c9fa30e
--- /dev/null
+++ b/etherpad/src/static/js/billing.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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() {
+ billing.initFieldDisplay();
+ billing.initCcValidation();
+});
+
+billing.initFieldDisplay = function() {
+ var id = $('#billingselect input:checked').attr("value");
+ $('.billingfield').not('.billingfield.'+id+'req').hide();
+ $('.paymentbutton').click(billing.selectPaymentType);
+
+ $('#billingCountry').click(billing.selectCountry);
+ billing.selectCountry();
+}
+
+billing.selectCountry = function() {
+ var countryCode = $('#billingCountry').attr("value");
+ var id = $('#billingselect input:checked').attr("value");
+ if (countryCode != 'US') {
+ $('.billingfield.intonly.'+id+'req').show();
+ $('.billingfield.usonly').hide();
+ } else {
+ $('.billingfield.intonly').hide();
+ $('.billingfield.usonly.'+id+'req').show();
+ }
+}
+
+billing.countryAntiSelector = function() {
+ var countryCode = $('#billingCountry').attr("value");
+ if (countryCode != 'US') {
+ return '.usonly';
+ } else {
+ return '.intonly';
+ }
+}
+
+billing.selectPaymentType = function() {
+ var radio = $(this).children('input');
+ var id = radio.attr("value");
+ radio.attr("checked", "checked");
+
+ var selector = billing.countryAntiSelector();
+ var toShow = $('.billingfield.'+id+'req:hidden').not('.billingfield'+selector);
+ var toHide = $('.billingfield:visible').not('.billingfield.'+id+'req');
+
+ if (toShow.size() > 0 && toHide.size() > 0) {
+ toHide.fadeOut(200);
+ setTimeout(function() {
+ toShow.fadeIn(200);
+ }, 200);
+ } else if (toShow.size() > 0 || toHide.size() > 0){
+ toShow.fadeIn(200);
+ toHide.fadeOut(200);
+ }
+}
+
+billing.extractCcType = function(numsrc) {
+ var number = $(numsrc).val();
+ var newType = billing.getCcType(number);
+ $('.ccimage').removeClass('ccimageselected');
+ if (newType) {
+ $('#img'+newType).addClass('ccimageselected');
+ }
+ if (billing.validateCcNumber(number)) {
+ $('input[name=billingCCNumber]').css('border', '1px solid #0f0');
+ } else if (billing.validateCcLength(number) ||
+ ! (/^\d*$/.test(number))) {
+ $('input[name=billingCCNumber]').css('border', '1px solid #f00');
+ } else {
+ $('input[name=billingCCNumber]').css('border', '1px solid black');
+ }
+}
+
+billing.handleCcFieldChange = function(target, event) {
+ if (event &&
+ ! (event.keyCode == 8 ||
+ (event.keyCode >= 32 && event.keyCode <= 126))) {
+ return;
+ }
+ var ccValue = $(target).val();
+ if (ccValue == billing.lastCcValue) {
+ return;
+ }
+ billing.lastCcValue = ccValue;
+ setTimeout(function() {
+ billing.extractCcType(target);
+ }, 0);
+}
+
+billing.initCcValidation = function() {
+ $('input[name=billingCCNumber]').keydown(
+ function(event) { billing.handleCcFieldChange(this, event); });
+ $('input[name=billingCCNumber]').blur(
+ function() { billing.handleCcFieldChange(this) });
+ billing.lastCcValue = $('input[name=billingCCNumber]').val();
+} \ No newline at end of file
diff --git a/etherpad/src/static/js/billing_shared.js b/etherpad/src/static/js/billing_shared.js
new file mode 100644
index 0000000..dc3a00c
--- /dev/null
+++ b/etherpad/src/static/js/billing_shared.js
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 billing = {};
+
+billing.CC = function(shortName, prefixes, length) {
+ this.type = shortName;
+ this.prefixes = prefixes;
+ this.length = length;
+ function validateLuhn(number) {
+ var digits = [];
+ var sum = 0;
+ for (var i = 0; i < number.length; ++i) {
+ var c = Number(number.charAt(number.length-1-i));
+ sum += c;
+ if (i % 2 == 1) { // every second digit
+ sum += c;
+ if (2*c >= 10) {
+ sum -= 9;
+ }
+ }
+ }
+ return (sum % 10 == 0);
+ }
+ this.validatePrefix = function(number) {
+ for (var i = 0; i < this.prefixes.length; ++i) {
+ if (number.indexOf(String(this.prefixes[i])) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+ this.validateLength = function(number) {
+ return number.length == this.length;
+ }
+
+ this.validateNumber = function(number) {
+ return this.validateLength(number) &&
+ this.validatePrefix(number) &&
+ validateLuhn(number);
+ }
+}
+
+billing.ccTypes = [
+ new billing.CC('amex', [34, 37], 15),
+ new billing.CC('disc', [6011, 644, 645, 646, 647, 648, 649, 65], 16),
+ new billing.CC('mc', [51, 52, 53, 54, 55], 16),
+ new billing.CC('visa', [4], 16)];
+
+billing.validateCcNumber = function(number) {
+ if (! (/^\d+$/.test(number))) {
+ return false;
+ }
+ for (var i = 0; i < billing.ccTypes.length; ++i) {
+ var ccType = billing.ccTypes[i];
+ if (ccType.validatePrefix(number)) {
+ return ccType.validateNumber(number);
+ }
+ }
+ return false;
+}
+
+billing.validateCcLength = function(number) {
+ for (var i = 0; i < billing.ccTypes.length; ++i) {
+ var ccType = billing.ccTypes[i];
+ if (ccType.validatePrefix(number)) {
+ return ccType.validateLength(number);
+ }
+ }
+ return false;
+}
+
+billing.getCcType = function(number) {
+ for (var i = 0; i < billing.ccTypes.length; ++i) {
+ var ccType = billing.ccTypes[i];
+ if (ccType.validatePrefix(number)) {
+ return ccType.type;
+ }
+ }
+ return false;
+}
diff --git a/etherpad/src/static/js/broadcast.js b/etherpad/src/static/js/broadcast.js
new file mode 100644
index 0000000..8ea0a15
--- /dev/null
+++ b/etherpad/src/static/js/broadcast.js
@@ -0,0 +1,610 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+// just in case... (todo: this must be somewhere else in the client code.)
+// Below Array#map code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_map.htm
+if (!Array.prototype.map)
+{
+ Array.prototype.map = function(fun /*, thisp*/)
+ {
+ var len = this.length >>> 0;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ res[i] = fun.call(thisp, this[i], i, this);
+ }
+
+ return res;
+ };
+}
+
+// Below Array#forEach code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_foreach.htm
+if (!Array.prototype.forEach)
+{
+ Array.prototype.forEach = function(fun /*, thisp*/)
+ {
+ var len = this.length >>> 0;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ fun.call(thisp, this[i], i, this);
+ }
+ };
+}
+
+// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt /*, from*/)
+ {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
+
+function debugLog() {
+ try {
+ // console.log.apply(console, arguments);
+ } catch (e) {console.log("error printing: ",e);}
+}
+
+function randomString() {
+ return "_"+Math.floor(Math.random() * 1000000);
+}
+
+// for IE
+if ($.browser.msie) {
+ try {
+ document.execCommand("BackgroundImageCache", false, true);
+ } catch (e) {}
+}
+
+var userId = "hiddenUser" + randomString();
+var socketId;
+var socket;
+
+var channelState = "DISCONNECTED";
+
+var appLevelDisconnectReason = null;
+
+var padContents = {
+ currentRevision: clientVars.revNum,
+ currentTime : clientVars.currentTime,
+ currentLines: Changeset.splitTextLines(clientVars.initialStyledContents.atext.text),
+ currentDivs : null, // to be filled in once the dom loads
+ apool: (new AttribPool()).fromJsonable(clientVars.initialStyledContents.apool),
+ alines: Changeset.splitAttributionLines(
+ clientVars.initialStyledContents.atext.attribs,
+ clientVars.initialStyledContents.atext.text),
+
+ // generates a jquery element containing HTML for a line
+ lineToElement: function(line, aline) {
+ var element = document.createElement("div");
+ var emptyLine = (line == '\n');
+ var domInfo = domline.createDomLine(! emptyLine, true);
+ linestylefilter.populateDomLine(line, aline, this.apool,
+ domInfo);
+ domInfo.prepareForAdd();
+ element.className = domInfo.node.className;
+ element.innerHTML = domInfo.node.innerHTML;
+ element.id = Math.random();
+ return $(element);
+ },
+
+ applySpliceToDivs: function(start, numRemoved, newLines) {
+ // remove spliced-out lines from DOM
+ for(var i=start; i<start+numRemoved && i<this.currentDivs.length; i++) {
+ debugLog("removing", this.currentDivs[i].attr('id'));
+ this.currentDivs[i].remove();
+ }
+
+ // remove spliced-out line divs from currentDivs array
+ this.currentDivs.splice(start, numRemoved);
+
+ var newDivs = [];
+ for(var i=0;i<newLines.length;i++) {
+ newDivs.push(this.lineToElement(newLines[i],
+ this.alines[start+i]));
+ }
+
+ // grab the div just before the first one
+ var startDiv = this.currentDivs[start-1] || null;
+
+ // insert the div elements into the correct place, in the correct order
+ for(var i=0; i<newDivs.length; i++) {
+ if (startDiv) {
+ startDiv.after(newDivs[i]);
+ }
+ else {
+ $("#padcontent").prepend(newDivs[i]);
+ }
+ startDiv = newDivs[i];
+ }
+
+ // insert new divs into currentDivs array
+ newDivs.unshift(0); // remove 0 elements
+ newDivs.unshift(start);
+ this.currentDivs.splice.apply(this.currentDivs, newDivs);
+ return this;
+ },
+
+ // splice the lines
+ splice: function(start, numRemoved, newLinesVA) {
+ var newLines = Array.prototype.slice.call(arguments, 2).map(
+ function(s) { return s; });
+
+ // apply this splice to the divs
+ this.applySpliceToDivs(start, numRemoved, newLines);
+
+ // call currentLines.splice, to keep the currentLines array up to date
+ newLines.unshift(numRemoved);
+ newLines.unshift(start);
+ this.currentLines.splice.apply(this.currentLines, arguments);
+ },
+ // returns the contents of the specified line I
+ get: function(i) {
+ return this.currentLines[i];
+ },
+ // returns the number of lines in the document
+ length: function() {
+ return this.currentLines.length;
+ },
+
+ getActiveAuthors: function() {
+ var self = this;
+ var authors = [];
+ var seenNums = {};
+ var alines = self.alines;
+ for(var i=0;i<alines.length;i++) {
+ Changeset.eachAttribNumber(alines[i], function(n) {
+ if (! seenNums[n]) {
+ seenNums[n] = true;
+ if (self.apool.getAttribKey(n) == 'author') {
+ var a = self.apool.getAttribValue(n);
+ if (a) {
+ authors.push(a);
+ }
+ }
+ }
+ });
+ }
+ authors.sort();
+ return authors;
+ }
+};
+
+function callCatchingErrors(catcher, func) {
+ try {
+ wrapRecordingErrors(catcher, func)();
+ }
+ catch (e) { /*absorb*/ }
+}
+
+function wrapRecordingErrors(catcher, func) {
+ return function() {
+ try {
+ return func.apply(this, Array.prototype.slice.call(arguments));
+ }
+ catch (e) {
+ // caughtErrors.push(e);
+ // caughtErrorCatchers.push(catcher);
+ // caughtErrorTimes.push(+new Date());
+ // console.dir({catcher: catcher, e: e});
+ debugLog(e); // TODO(kroo): added temporary, to catch errors
+ throw e;
+ }
+ };
+}
+
+function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) {
+ var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
+ debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision);
+ revisionInfo.addChangeset(revision, revision+1, changesetForward, changesetBackward, timeDelta);
+ BroadcastSlider.setSliderLength(revisionInfo.latest);
+ if(broadcasting)
+ applyChangeset(changesetForward, revision+1, false, timeDelta);
+}
+
+/*
+ At this point, we must be certain that the changeset really does map from
+ the current revision to the specified revision. Any mistakes here will
+ cause the whole slider to get out of sync.
+ */
+function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) {
+ // disable the next 'gotorevision' call handled by a timeslider update
+ if(!preventSliderMovement) {
+ goToRevisionIfEnabledCount ++;
+ BroadcastSlider.setSliderPosition(revision);
+ }
+
+ try {
+ // must mutate attribution lines before text lines
+ Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
+ }catch(e) { debugLog(e); }
+
+ Changeset.mutateTextLines(changeset, padContents);
+ padContents.currentRevision = revision;
+ padContents.currentTime += timeDelta * 1000;
+ debugLog('Time Delta: ',timeDelta)
+ updateTimer();
+ BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];}));
+}
+
+function updateTimer() {
+ var zpad = function(str, length) {
+ str = str+"";
+ while(str.length < length)
+ str = '0'+str;
+ return str;
+ }
+ var date = new Date(padContents.currentTime);
+ var dateFormat = function() {
+ var month = zpad(date.getMonth()+1,2);
+ var day = zpad(date.getDate(),2);
+ var year = (date.getFullYear());
+ var hours = zpad(date.getHours(),2);
+ var minutes = zpad(date.getMinutes(),2);
+ var seconds = zpad(date.getSeconds(),2);
+ return ([month,'/',day,'/',year,' ',hours,':',minutes,':',seconds].join(""));
+ }
+
+ $('#timer').html(dateFormat());
+
+ var revisionDate = [
+ "Saved",
+ [
+ "Jan", "Feb", "March", "April", "May", "June",
+ "July", "Aug", "Sept", "Oct", "Nov", "Dec"
+ ][date.getMonth()],
+ date.getDate()+",",
+ date.getFullYear()
+ ].join(" ")
+ $('#revision_date').html(revisionDate)
+
+}
+
+function goToRevision(newRevision) {
+ padContents.targetRevision = newRevision;
+ var self = this;
+ var path = revisionInfo.getPath(padContents.currentRevision, newRevision);
+ debugLog('newRev: ', padContents.currentRevision, path);
+ if( path.status == 'complete') {
+ var cs = path.changesets;
+ debugLog("status: complete, changesets: ",cs, "path:", path);
+ var changeset = cs[0];
+ var timeDelta = path.times[0];
+ for(var i=1; i<cs.length; i++) {
+ changeset = Changeset.compose(changeset, cs[i], padContents.apool);
+ timeDelta += path.times[i];
+ }
+ if(changeset)
+ applyChangeset(changeset, path.rev, true, timeDelta);
+ } else if(path.status == "partial") {
+ debugLog('partial');
+ var sliderLocation = padContents.currentRevision;
+ // callback is called after changeset information is pulled from server
+ // this may never get called, if the changeset has already been loaded
+ var update = function(start, end) {
+ // if we've called goToRevision in the time since, don't goToRevision
+ goToRevision(padContents.targetRevision);
+ };
+
+ // do our best with what we have...
+ var cs = path.changesets;
+
+ var changeset = cs[0];
+ var timeDelta = path.times[0];
+ for(var i=1; i<cs.length; i++) {
+ changeset = Changeset.compose(changeset, cs[i], padContents.apool);
+ timeDelta += path.times[i];
+ }
+ if(changeset)
+ applyChangeset(changeset, path.rev, true, timeDelta);
+
+
+ if(BroadcastSlider.getSliderLength() > 10000) {
+ var start = (Math.floor((newRevision) / 10000) * 10000); // revision 0 to 10
+ changesetLoader.queueUp(start, 100);
+ }
+
+ if(BroadcastSlider.getSliderLength() > 1000) {
+ var start = (Math.floor((newRevision) / 1000) * 1000); // (start from -1, go to 19) + 1
+ changesetLoader.queueUp(start, 10);
+ }
+
+ start = (Math.floor((newRevision) / 100) * 100);
+
+ changesetLoader.queueUp(start, 1, update);
+ }
+ BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];}));
+}
+
+var changesetLoader = {
+ running: false,
+ resolved: [],
+ requestQueue1: [],
+ requestQueue2: [],
+ requestQueue3: [],
+ queueUp: function(revision, width, callback) {
+ if(revision < 0) revision = 0;
+ // if(changesetLoader.requestQueue.indexOf(revision) != -1)
+ // return; // already in the queue.
+ if(changesetLoader.resolved.indexOf(revision+"_"+width) != -1)
+ return; // already loaded from the server
+ changesetLoader.resolved.push(revision+"_"+width);
+
+ var requestQueue = width == 1 ? changesetLoader.requestQueue3 :
+ width == 10 ? changesetLoader.requestQueue2 :
+ changesetLoader.requestQueue1;
+ requestQueue.push({'rev': revision, 'res': width, 'callback': callback});
+ if(!changesetLoader.running) {
+ changesetLoader.running = true;
+ setTimeout(changesetLoader.loadFromQueue, 10);
+ }
+ },
+ loadFromQueue: function() {
+ var self = changesetLoader;
+ var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 :
+ self.requestQueue2.length > 0 ? self.requestQueue2 :
+ self.requestQueue3.length > 0 ? self.requestQueue3 : null;
+
+ if(!requestQueue) {
+ self.running = false;
+ return;
+ }
+
+ var request = requestQueue.pop();
+ var granularity = request.res;
+ var callback = request.callback;
+ var start = request.rev;
+ debugLog("loadinging revision", start, "through ajax");
+ $.getJSON(
+ "/ep/pad/changes/"+clientVars.padIdForUrl+"?s="+start + "&g="+granularity,
+ function(data, textStatus) {
+ if(textStatus !== "success") {
+ console.log(textStatus);
+ BroadcastSlider.showReconnectUI();
+ }
+ self.handleResponse(data, start, granularity, callback);
+
+ setTimeout(self.loadFromQueue, 10); // load the next ajax function
+ }
+ );
+ },
+ handleResponse: function(data, start, granularity, callback) {
+ debugLog("response: ", data);
+ var pool = (new AttribPool()).fromJsonable(data.apool);
+ for(var i=0; i<data.forwardsChangesets.length; i++) {
+ var astart = start + i * granularity - 1; // rev -1 is a blank single line
+ var aend = start + (i+1) * granularity - 1;// totalRevs is the most recent revision
+ if(aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
+ debugLog("adding changeset:", astart, aend);
+ var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
+ var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
+ revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
+ }
+ if(callback)callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
+ }
+};
+
+function handleMessageFromServer() {
+ debugLog("handleMessage:", arguments);
+ var obj = arguments[0]['data'];
+ var expectedType = "COLLABROOM";
+
+ obj = JSON.parse(obj);
+ if (obj['type'] == expectedType) {
+ obj = obj['data'];
+
+ if (obj['type'] == "NEW_CHANGES") {
+ debugLog(obj);
+ var changeset = Changeset.moveOpsToNewPool(
+ obj.changeset, (new AttribPool()).fromJsonable(obj.apool),
+ padContents.apool);
+
+ var changesetBack = Changeset.moveOpsToNewPool(
+ obj.changesetBack, (new AttribPool()).fromJsonable(obj.apool),
+ padContents.apool);
+
+ loadedNewChangeset(changeset, changesetBack, obj.newRev-1, obj.timeDelta);
+ }
+ else if (obj['type'] == "NEW_AUTHORDATA") {
+ var authorMap = {};
+ authorMap[obj.author] = obj.data;
+ receiveAuthorData(authorMap);
+ BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name) {return authorData[name];}));
+ } else if (obj['type'] == "NEW_SAVEDREV") {
+ var savedRev = obj.savedRev;
+ BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
+ }
+ } else {
+ debugLog("incorrect message type: " + obj['type'] + ", expected " + expectedType);
+ }
+}
+
+function handleSocketClosed(params) {
+ debugLog("socket closed!", params);
+ socket = null;
+
+ BroadcastSlider.showReconnectUI();
+ // var reason = appLevelDisconnectReason || params.reason;
+ // var shouldReconnect = params.reconnect;
+ // if (shouldReconnect) {
+ // // determine if this is a tight reconnect loop due to weird connectivity problems
+ // // reconnectTimes.push(+new Date());
+ // var TOO_MANY_RECONNECTS = 8;
+ // var TOO_SHORT_A_TIME_MS = 10000;
+ // if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
+ // ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
+ // TOO_SHORT_A_TIME_MS) {
+ // setChannelState("DISCONNECTED", "looping");
+ // }
+ // else {
+ // setChannelState("RECONNECTING", reason);
+ // setUpSocket();
+ // }
+ // }
+ // else {
+ // BroadcastSlider.showReconnectUI();
+ // setChannelState("DISCONNECTED", reason);
+ // }
+}
+
+function sendMessage(msg) {
+ socket.postMessage(JSON.stringify({type: "COLLABROOM", data: msg}));
+}
+
+function setUpSocket() {
+ // required for Comet
+ if ((! $.browser.msie) &&
+ (! ($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) {
+ document.domain = document.domain; // for comet
+ }
+
+ var success = false;
+ callCatchingErrors("setUpSocket", function() {
+ appLevelDisconnectReason = null;
+
+ socketId = String(Math.floor(Math.random()*1e12));
+ socket = new WebSocket(socketId);
+ socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
+ socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
+ socket.onopen = wrapRecordingErrors("socket.onopen", function() {
+ setChannelState("CONNECTED");
+ var msg = { type:"CLIENT_READY", roomType:'padview',
+ roomName:'padview/'+clientVars.viewId,
+ data: { lastRev:clientVars.revNum,
+ userInfo:{userId: userId} } };
+ sendMessage(msg);
+ });
+ // socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
+ // socket.onlogmessage = function(x) {debugLog(x); };
+ socket.connect();
+ success = true;
+ });
+ if (success) {
+ //initialStartConnectTime = +new Date();
+ }
+ else {
+ abandonConnection("initsocketfail");
+ }
+}
+
+function setChannelState(newChannelState, moreInfo) {
+ if (newChannelState != channelState) {
+ channelState = newChannelState;
+ // callbacks.onChannelStateChange(channelState, moreInfo);
+ }
+}
+
+function abandonConnection(reason) {
+ if (socket) {
+ socket.onclosed = function() {};
+ socket.onhiccup = function() {};
+ socket.disconnect();
+ }
+ socket = null;
+ setChannelState("DISCONNECTED", reason);
+}
+
+window['onloadFuncts'] = [];
+window.onload = function() {
+ window['isloaded'] = true;
+ window['onloadFuncts'].forEach(function(funct) {
+ funct();
+ });
+};
+
+// to start upon window load, just push a function onto this array
+window['onloadFuncts'].push(setUpSocket);
+window['onloadFuncts'].push(function() {
+ // set up the currentDivs and DOM
+ padContents.currentDivs = [];
+ $("#padcontent").html("");
+ for(var i=0; i<padContents.currentLines.length; i++) {
+ var div = padContents.lineToElement(padContents.currentLines[i],
+ padContents.alines[i]);
+ padContents.currentDivs.push(div);
+ $("#padcontent").append(div);
+ }
+ debugLog(padContents.currentDivs);
+});
+
+// this is necessary to keep infinite loops of events firing,
+// since goToRevision changes the slider position
+var goToRevisionIfEnabledCount = 0;
+var goToRevisionIfEnabled = function() {
+ if(goToRevisionIfEnabledCount > 0) {
+ goToRevisionIfEnabledCount --;
+ } else {
+ goToRevision.apply(goToRevision, arguments);
+ }
+}
+
+BroadcastSlider.onSlider(goToRevisionIfEnabled);
+
+(function() {
+ for(var i=0; i<clientVars.initialChangesets.length; i++) {
+ var csgroup = clientVars.initialChangesets[i];
+ var start = clientVars.initialChangesets[i].start;
+ var granularity = clientVars.initialChangesets[i].granularity;
+ debugLog("loading changest on startup: ", start, granularity, csgroup);
+ changesetLoader.handleResponse(csgroup, start, granularity, null);
+ }
+})();
+
+var dynamicCSS = makeCSSManager('dynamicsyntax');
+var authorData = {};
+
+function receiveAuthorData(newAuthorData) {
+ for(var author in newAuthorData) {
+ var data = newAuthorData[author];
+ if ((typeof data.colorId) == 'number') {
+ var bgcolor = clientVars.colorPalette[data.colorId];
+ if (bgcolor && dynamicCSS) {
+ dynamicCSS.selectorStyle(
+ '.'+linestylefilter.getAuthorClassName(author)).backgroundColor =
+ bgcolor;
+ }
+ }
+ authorData[author] = data;
+ }
+}
+
+receiveAuthorData(clientVars.historicalAuthorData);
diff --git a/etherpad/src/static/js/broadcast_revisions.js b/etherpad/src/static/js/broadcast_revisions.js
new file mode 100644
index 0000000..7e99003
--- /dev/null
+++ b/etherpad/src/static/js/broadcast_revisions.js
@@ -0,0 +1,119 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+// revision info is a skip list whos entries represent a particular revision
+// of the document. These revisions are connected together by various
+// changesets, or deltas, between any two revisions.
+function Revision(revNum) {
+ this.rev = revNum;
+ this.changesets = [];
+}
+
+Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta) {
+ var changesetWrapper = {
+ deltaRev: destIndex-this.rev,
+ deltaTime: timeDelta,
+ getValue: function() {
+ return changeset;
+ }
+ };
+ this.changesets.push(changesetWrapper);
+ this.changesets.sort(function(a, b) {
+ return (b.deltaRev - a.deltaRev)
+ });
+}
+
+revisionInfo = {};
+revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta) {
+ var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
+ var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
+ startRevision.addChangeset(toIndex, changeset, timeDelta);
+ endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
+}
+
+revisionInfo.latest = clientVars.totalRevs || -1;
+
+revisionInfo.createNew = function(index) {
+ revisionInfo[index] = new Revision(index);
+ if(index > revisionInfo.latest) {
+ revisionInfo.latest = index;
+ }
+
+ return revisionInfo[index];
+}
+
+// assuming that there is a path from fromIndex to toIndex, and that the links
+// are laid out in a skip-list format
+revisionInfo.getPath = function(fromIndex, toIndex) {
+ var changesets = [];
+ var spans = [];
+ var times = [];
+ var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
+ if(elem.changesets.length != 0 && fromIndex != toIndex) {
+ var reverse = !(fromIndex < toIndex)
+ while(((elem.rev < toIndex) && !reverse) ||
+ ((elem.rev > toIndex) && reverse)) {
+ var couldNotContinue = false;
+ var oldRev = elem.rev;
+
+ for(var i = reverse ? elem.changesets.length - 1 : 0;
+ reverse?i>=0:i<elem.changesets.length;
+ i += reverse ? -1 : 1) {
+ if(((elem.changesets[i].deltaRev < 0) && !reverse) ||
+ ((elem.changesets[i].deltaRev > 0) && reverse)) {
+ couldNotContinue = true;
+ break;
+ }
+
+ if(((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) ||
+ ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse)) {
+ var topush = elem.changesets[i];
+ changesets.push(topush.getValue());
+ spans.push(elem.changesets[i].deltaRev);
+ times.push(topush.deltaTime);
+ elem = revisionInfo[elem.rev + elem.changesets[i].deltaRev];
+ break;
+ }
+ }
+
+ if(couldNotContinue || oldRev == elem.rev) break;
+ }
+ }
+
+ var status = 'partial';
+ if(elem.rev == toIndex)
+ status = 'complete';
+
+ return {
+ 'fromRev':fromIndex,
+ 'rev': elem.rev,
+ 'status': status,
+ 'changesets': changesets,
+ 'spans' : spans,
+ 'times' : times
+ };
+}
+
+// revisionInfo.addChangeset(0, 5, "abcde")
+// revisionInfo.addChangeset(5, 10, "fghij")
+// revisionInfo.addChangeset(10, 11, "k")
+// revisionInfo.addChangeset(11, 12, "l")
+// revisionInfo.addChangeset(12, 13, "m")
+// revisionInfo.addChangeset(13, 14, "n")
+// revisionInfo.addChangeset(14, 15, "o")
+// revisionInfo.addChangeset(15, 20, "pqrst")
+//
+// print (revisionInfo.getPath(15, 0))
diff --git a/etherpad/src/static/js/broadcast_slider.js b/etherpad/src/static/js/broadcast_slider.js
new file mode 100644
index 0000000..371663e
--- /dev/null
+++ b/etherpad/src/static/js/broadcast_slider.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 global = this;
+
+(function() { // wrap this code in its own namespace
+ var sliderLength = 1000;
+ var sliderPos = 0;
+ var sliderActive = false;
+ var slidercallbacks = [];
+ var savedRevisions = [];
+ var sliderPlaying = false;
+
+ function disableSelection(element) {
+ element.onselectstart = function() {
+ return false;
+ };
+ element.unselectable = "on";
+ element.style.MozUserSelect = "none";
+ element.style.cursor = "default";
+ }
+ var _callSliderCallbacks = function(newval) {
+ sliderPos = newval;
+ for(var i=0; i<slidercallbacks.length; i++) {
+ slidercallbacks[i](newval);
+ }
+ }
+
+ var updateSliderElements = function() {
+ for(var i=0; i<savedRevisions.length; i++) {
+ var position = parseInt(savedRevisions[i].attr('pos'));
+ savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width()-2) / (sliderLength * 1.0)) - 1);
+ }
+ $("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width()-2) / (sliderLength * 1.0));
+ }
+
+ var addSavedRevision = function(position, info) {
+ var newSavedRevision = $('<div></div>');
+ newSavedRevision.addClass("star");
+
+ newSavedRevision.attr('pos', position);
+ newSavedRevision.css('position', 'absolute');
+ newSavedRevision.css('left', (position * ($("#ui-slider-bar").width()-2) / (sliderLength * 1.0)) - 1);
+ $("#timeslider-slider").append(newSavedRevision);
+ newSavedRevision.mouseup(function(evt) {
+ BroadcastSlider.setSliderPosition(position);
+ });
+ savedRevisions.push(newSavedRevision);
+ };
+
+ var removeSavedRevision = function (position) {
+ var element = $("div.star [pos="+position+"]");
+ savedRevisions.remove(element);
+ element.remove();
+ return element;
+ };
+
+ /* Begin small 'API' */
+ function onSlider(callback) {
+ slidercallbacks.push(callback);
+ }
+
+ function getSliderPosition() {
+ return sliderPos;
+ }
+
+ function setSliderPosition(newpos) {
+ newpos = Number(newpos);
+ if(newpos < 0 || newpos > sliderLength) return;
+ $("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width()-2) / (sliderLength * 1.0));
+ $("a.tlink").map(function() {
+ $(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
+ });
+ $("#revision_label").html("Version " + newpos);
+
+ if(newpos == 0) {
+ $("#leftstar").css('opacity', .5);
+ $("#leftstep").css('opacity', .5);
+ } else {
+ $("#leftstar").css('opacity', 1);
+ $("#leftstep").css('opacity', 1);
+ }
+
+ if(newpos == sliderLength) {
+ $("#rightstar").css('opacity', .5);
+ $("#rightstep").css('opacity', .5);
+ } else {
+ $("#rightstar").css('opacity', 1);
+ $("#rightstep").css('opacity', 1);
+ }
+
+ sliderPos = newpos;
+ _callSliderCallbacks(newpos);
+ }
+
+ function getSliderLength() {
+ return sliderLength;
+ }
+
+ function setSliderLength(newlength) {
+ sliderLength = newlength;
+ updateSliderElements();
+ }
+
+ // just take over the whole slider screen with a reconnect message
+ function showReconnectUI() {
+ if(!clientVars.sliderEnabled || !clientVars.supportsSlider) {
+ $("#padmain, #rightbars").css('top', "95px");
+ $("#timeslider").show();
+ }
+ $('#error').show();
+ }
+
+ function setAuthors(authors) {
+ $("#authorstable").empty();
+ var numAnonymous = 0;
+ var numNamed = 0;
+ authors.forEach(function(author) {
+ if(author.name) {
+ numNamed ++;
+ var tr = $('<tr></tr>');
+ var swatchtd = $('<td></td>');
+ var swatch = $('<div class="swatch"></div>');
+ swatch.css('background-color', clientVars.colorPalette[author.colorId]);
+ swatchtd.append(swatch);
+ tr.append(swatchtd);
+ var nametd = $('<td></td>');
+ nametd.text(author.name || "unnamed");
+ tr.append(nametd);
+ $("#authorstable").append(tr);
+ } else {
+ numAnonymous ++;
+ }
+ });
+ if(numAnonymous > 0) {
+ var html = "<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">"+(numNamed>0?"...and ":"")+numAnonymous+" unnamed author"+(numAnonymous>1?"s":"")+"</td></tr>";
+ $("#authorstable").append($(html));
+ } if(authors.length == 0) {
+ $("#authorstable").append($("<tr><td colspan=\"2\" style=\"color:#999; padding-left: 10px\">No Authors</td></tr>"))
+ }
+ }
+
+ global.BroadcastSlider = {
+ onSlider: onSlider,
+ getSliderPosition: getSliderPosition,
+ setSliderPosition: setSliderPosition,
+ getSliderLength: getSliderLength,
+ setSliderLength: setSliderLength,
+ isSliderActive: function() {return sliderActive;},
+ playpause: playpause,
+ addSavedRevision: addSavedRevision,
+ showReconnectUI : showReconnectUI,
+ setAuthors: setAuthors
+ }
+
+ function playButtonUpdater() {
+ if(sliderPlaying) {
+ if(getSliderPosition()+1 > sliderLength) {
+ $("#playpause_button_icon").toggleClass('pause');
+ sliderPlaying = false;
+ return;
+ }
+ setSliderPosition(getSliderPosition()+1);
+
+ setTimeout(playButtonUpdater, 100);
+ }
+ }
+
+ function playpause() {
+ $("#playpause_button_icon").toggleClass('pause');
+
+ if(!sliderPlaying) {
+ if(getSliderPosition() == sliderLength)
+ setSliderPosition(0);
+ sliderPlaying = true;
+ playButtonUpdater();
+ } else {
+ sliderPlaying = false;
+ }
+ }
+
+ // assign event handlers to html UI elements after page load
+ $(window).load(function() {
+ disableSelection($("#playpause_button")[0]);
+ disableSelection($("#timeslider")[0]);
+
+ if(clientVars.sliderEnabled && clientVars.supportsSlider) {
+ $(document).keyup(function(e) {
+ var code = -1;
+ if (!e) var e = window.event;
+ if (e.keyCode) code = e.keyCode;
+ else if (e.which) code = e.which;
+
+ if(code == 37) { // left
+ if(!e.shiftKey) {
+ setSliderPosition(getSliderPosition() - 1);
+ } else {
+ var nextStar = 0; // default to first revision in document
+ for(var i=0; i<savedRevisions.length; i++) {
+ var pos = parseInt(savedRevisions[i].attr('pos'));
+ if(pos < getSliderPosition() && nextStar < pos)
+ nextStar = pos;
+ }
+ setSliderPosition(nextStar);
+ }
+ } else if(code == 39) {
+ if(!e.shiftKey) {
+ setSliderPosition(getSliderPosition() + 1);
+ } else {
+ var nextStar = sliderLength; // default to last revision in document
+ for(var i=0; i<savedRevisions.length; i++) {
+ var pos = parseInt(savedRevisions[i].attr('pos'));
+ if(pos > getSliderPosition() && nextStar > pos)
+ nextStar = pos;
+ }
+ setSliderPosition(nextStar);
+ }
+ } else if(code == 32)
+ playpause();
+
+ });
+ }
+
+ $(window).resize(function() {
+ updateSliderElements();
+ });
+
+ $("#ui-slider-bar").mousedown(function(evt) {
+ setSliderPosition(Math.floor((evt.clientX-$("#ui-slider-bar").offset().left) * sliderLength / 742));
+ $("#ui-slider-handle").css('left', (evt.clientX-$("#ui-slider-bar").offset().left));
+ $("#ui-slider-handle").trigger(evt);
+ });
+
+ // Slider dragging
+ $("#ui-slider-handle").mousedown(function(evt) {
+ this.startLoc = evt.clientX;
+ this.currentLoc = parseInt($(this).css('left'));
+ var self = this;
+ sliderActive = true;
+ $(document).mousemove(function(evt2) {
+ $(self).css('pointer', 'move')
+ var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
+ if(newloc < 0) newloc = 0;
+ if(newloc > ($("#ui-slider-bar").width()-2)) newloc = ($("#ui-slider-bar").width()-2);
+ $("#revision_label").html("Version " + Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)));
+ $(self).css('left', newloc);
+ if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
+ _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
+ });
+ $(document).mouseup(function(evt2) {
+ $(document).unbind('mousemove');
+ $(document).unbind('mouseup');
+ sliderActive = false;
+ var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
+ if(newloc < 0) newloc = 0;
+ if(newloc > ($("#ui-slider-bar").width()-2)) newloc = ($("#ui-slider-bar").width()-2);
+ $(self).css('left', newloc);
+ // if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
+ setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
+ self.currentLoc = parseInt($(self).css('left'));
+ });
+ })
+
+ // play/pause toggling
+ $("#playpause_button").mousedown(function(evt) {
+ var self = this;
+
+ $(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_depressed.png)');
+ $(self).mouseup(function(evt2) {
+ $(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_undepressed.png)');
+ $(self).unbind('mouseup');
+ BroadcastSlider.playpause();
+ });
+ $(document).mouseup(function(evt2) {
+ $(self).css('background-image', 'url(/static/img/pad/timeslider/crushed_button_undepressed.png)');
+ $(document).unbind('mouseup');
+ });
+ });
+
+ // next/prev saved revision and changeset
+ $('.stepper').mousedown(function(evt) {
+ var self = this;
+ var origcss = $(self).css('background-position');
+ if (! origcss) {
+ origcss = $(self).css('background-position-x')+" "+$(self).css('background-position-y');
+ }
+ var origpos = parseInt(origcss.split(" ")[1]);
+ var newpos = (origpos - 43);
+ if(newpos < 0) newpos += 87;
+
+ var newcss = (origcss.split(" ")[0] + " " + newpos + "px");
+ if($(self).css('opacity') != 1.0)
+ newcss = origcss;
+
+ $(self).css('background-position', newcss)
+
+ $(self).mouseup(function(evt2) {
+ $(self).css('background-position',origcss);
+ $(self).unbind('mouseup');
+ $(document).unbind('mouseup');
+ if($(self).attr("id") == ("leftstep")) {
+ setSliderPosition(getSliderPosition() - 1);
+ }
+ else if($(self).attr("id") == ("rightstep")) {
+ setSliderPosition(getSliderPosition() + 1);
+ }
+ else if($(self).attr("id") == ("leftstar")) {
+ var nextStar = 0; // default to first revision in document
+ for(var i=0; i<savedRevisions.length; i++) {
+ var pos = parseInt(savedRevisions[i].attr('pos'));
+ if(pos < getSliderPosition() && nextStar < pos)
+ nextStar = pos;
+ }
+ setSliderPosition(nextStar);
+ }
+ else if($(self).attr("id") == ("rightstar")) {
+ var nextStar = sliderLength; // default to last revision in document
+ for(var i=0; i<savedRevisions.length; i++) {
+ var pos = parseInt(savedRevisions[i].attr('pos'));
+ if(pos > getSliderPosition() && nextStar > pos)
+ nextStar = pos;
+ }
+ setSliderPosition(nextStar);
+ }
+ });
+ $(document).mouseup(function(evt2) {
+ $(self).css('background-position',origcss);
+ $(self).unbind('mouseup');
+ $(document).unbind('mouseup');
+ });
+ })
+
+ if(clientVars) {
+ if(clientVars.fullWidth) {
+ $("#padpage").css('width', '100%');
+ $("#revision").css('position', "absolute")
+ $("#revision").css('right', "20px")
+ $("#revision").css('top', "20px")
+ $("#padmain").css('left', '0px');
+ $("#padmain").css('right', '197px');
+ $("#padmain").css('width', 'auto');
+ $("#rightbars").css('right', '7px');
+ $("#rightbars").css('margin-right', '0px');
+ $("#timeslider").css('width', 'auto');
+ }
+
+ if(clientVars.disableRightBar) {
+ $("#rightbars").css('display', 'none');
+ $('#padmain').css('width', 'auto');
+ if(clientVars.fullWidth)
+ $("#padmain").css('right', '7px');
+ else
+ $("#padmain").css('width', '860px');
+ $("#revision").css('position', "absolute");
+ $("#revision").css('right', "20px");
+ $("#revision").css('top', "20px");
+ }
+
+
+ if(clientVars.sliderEnabled) {
+ if(clientVars.supportsSlider) {
+ $("#padmain, #rightbars").css('top', "95px");
+ $("#timeslider").show();
+ setSliderLength(clientVars.totalRevs);
+ setSliderPosition(clientVars.revNum);
+ clientVars.savedRevisions.forEach(function(revision) {
+ addSavedRevision(revision.revNum, revision);
+ })
+ } else {
+ // slider is not supported
+ $("#padmain, #rightbars").css('top', "95px");
+ $("#timeslider").show();
+ $("#error").html("The timeslider feature is not supported on this pad. <a href=\"/ep/about/faq#disabledslider\">Why not?</a>");
+ $("#error").show();
+ }
+ } else {
+ if(clientVars.supportsSlider) {
+ setSliderLength(clientVars.totalRevs);
+ setSliderPosition(clientVars.revNum);
+ }
+ }
+ }
+ });
+})();
+
+BroadcastSlider.onSlider(function(loc) {
+ $("#viewlatest").html(loc==BroadcastSlider.getSliderLength()?"Viewing latest content":"View latest content");
+})
diff --git a/etherpad/src/static/js/collab_client.js b/etherpad/src/static/js/collab_client.js
new file mode 100644
index 0000000..d8834d7
--- /dev/null
+++ b/etherpad/src/static/js/collab_client.js
@@ -0,0 +1,628 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+$(window).bind("load", function() {
+ getCollabClient.windowLoaded = true;
+});
+
+/** Call this when the document is ready, and a new Ace2Editor() has been created and inited.
+ ACE's ready callback does not need to have fired yet.
+ "serverVars" are from calling doc.getCollabClientVars() on the server. */
+function getCollabClient(ace2editor, serverVars, initialUserInfo, options) {
+ var editor = ace2editor;
+
+ var rev = serverVars.rev;
+ var padId = serverVars.padId;
+ var globalPadId = serverVars.globalPadId;
+
+ var state = "IDLE";
+ var stateMessage;
+ var stateMessageSocketId;
+ var channelState = "CONNECTING";
+ var appLevelDisconnectReason = null;
+
+ var lastCommitTime = 0;
+ var initialStartConnectTime = 0;
+
+ var userId = initialUserInfo.userId;
+ var socketId;
+ var socket;
+ var userSet = {}; // userId -> userInfo
+ userSet[userId] = initialUserInfo;
+
+ var reconnectTimes = [];
+ var caughtErrors = [];
+ var caughtErrorCatchers = [];
+ var caughtErrorTimes = [];
+ var debugMessages = [];
+
+ tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
+ tellAceActiveAuthorInfo(initialUserInfo);
+
+ var callbacks = {
+ onUserJoin: function() {},
+ onUserLeave: function() {},
+ onUpdateUserInfo: function() {},
+ onChannelStateChange: function() {},
+ onClientMessage: function() {},
+ onInternalAction: function() {},
+ onConnectionTrouble: function() {},
+ onServerMessage: function() {}
+ };
+
+ $(window).bind("unload", function() {
+ if (socket) {
+ socket.onclosed = function() {};
+ socket.onhiccup = function() {};
+ socket.disconnect(true);
+ }
+ });
+ if ($.browser.mozilla) {
+ // Prevent "escape" from taking effect and canceling a comet connection;
+ // doesn't work if focus is on an iframe.
+ $(window).bind("keydown", function(evt) { if (evt.which == 27) { evt.preventDefault() } });
+ }
+
+ editor.setProperty("userAuthor", userId);
+ editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
+ editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
+
+ function abandonConnection(reason) {
+ if (socket) {
+ socket.onclosed = function() {};
+ socket.onhiccup = function() {};
+ socket.disconnect();
+ }
+ socket = null;
+ setChannelState("DISCONNECTED", reason);
+ }
+
+ function dmesg(str) {
+ if (typeof window.ajlog == "string") window.ajlog += str+'\n';
+ debugMessages.push(str);
+ }
+
+ function handleUserChanges() {
+ if ((! socket) || channelState == "CONNECTING") {
+ if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) {
+ abandonConnection("initsocketfail"); // give up
+ }
+ else {
+ // check again in a bit
+ setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
+ 1000);
+ }
+ return;
+ }
+
+ var t = (+new Date());
+
+ if (state != "IDLE") {
+ if (state == "COMMITTING" && (t - lastCommitTime) > 20000) {
+ // a commit is taking too long
+ appLevelDisconnectReason = "slowcommit";
+ socket.disconnect();
+ }
+ else if (state == "COMMITTING" && (t - lastCommitTime) > 5000) {
+ callbacks.onConnectionTrouble("SLOW");
+ }
+ else {
+ // run again in a few seconds, to detect a disconnect
+ setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
+ 3000);
+ }
+ return;
+ }
+
+ var earliestCommit = lastCommitTime + 500;
+ if (t < earliestCommit) {
+ setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
+ earliestCommit - t);
+ return;
+ }
+
+ var sentMessage = false;
+ var userChangesData = editor.prepareUserChangeset();
+ if (userChangesData.changeset) {
+ lastCommitTime = t;
+ state = "COMMITTING";
+ stateMessage = {type:"USER_CHANGES", baseRev:rev,
+ changeset:userChangesData.changeset,
+ apool: userChangesData.apool };
+ stateMessageSocketId = socketId;
+ sendMessage(stateMessage);
+ sentMessage = true;
+ callbacks.onInternalAction("commitPerformed");
+ }
+
+ if (sentMessage) {
+ // run again in a few seconds, to detect a disconnect
+ setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges),
+ 3000);
+ }
+ }
+
+ function getStats() {
+ var stats = {};
+
+ stats.screen = [$(window).width(), $(window).height(),
+ window.screen.availWidth, window.screen.availHeight,
+ window.screen.width, window.screen.height].join(',');
+ stats.ip = serverVars.clientIp;
+ stats.useragent = serverVars.clientAgent;
+
+ return stats;
+ }
+
+ function setUpSocket() {
+ var success = false;
+ callCatchingErrors("setUpSocket", function() {
+ appLevelDisconnectReason = null;
+
+ var oldSocketId = socketId;
+ socketId = String(Math.floor(Math.random()*1e12));
+ socket = new WebSocket(socketId);
+ socket.onmessage = wrapRecordingErrors("socket.onmessage", handleMessageFromServer);
+ socket.onclosed = wrapRecordingErrors("socket.onclosed", handleSocketClosed);
+ socket.onopen = wrapRecordingErrors("socket.onopen", function() {
+ hiccupCount = 0;
+ setChannelState("CONNECTED");
+ var msg = { type:"CLIENT_READY", roomType:'padpage',
+ roomName:'padpage/'+globalPadId,
+ data: {
+ lastRev:rev,
+ userInfo:userSet[userId],
+ stats: getStats() } };
+ if (oldSocketId) {
+ msg.data.isReconnectOf = oldSocketId;
+ msg.data.isCommitPending = (state == "COMMITTING");
+ }
+ sendMessage(msg);
+ doDeferredActions();
+ });
+ socket.onhiccup = wrapRecordingErrors("socket.onhiccup", handleCometHiccup);
+ socket.onlogmessage = dmesg;
+ socket.connect();
+ success = true;
+ });
+ if (success) {
+ initialStartConnectTime = +new Date();
+ }
+ else {
+ abandonConnection("initsocketfail");
+ }
+ }
+ function setUpSocketWhenWindowLoaded() {
+ if (getCollabClient.windowLoaded) {
+ setUpSocket();
+ }
+ else {
+ setTimeout(setUpSocketWhenWindowLoaded, 200);
+ }
+ }
+ setTimeout(setUpSocketWhenWindowLoaded, 0);
+
+ var hiccupCount = 0;
+ function handleCometHiccup(params) {
+ dmesg("HICCUP (connected:"+(!!params.connected)+")");
+ var connectedNow = params.connected;
+ if (! connectedNow) {
+ hiccupCount++;
+ // skip first "cut off from server" notification
+ if (hiccupCount > 1) {
+ setChannelState("RECONNECTING");
+ }
+ }
+ else {
+ hiccupCount = 0;
+ setChannelState("CONNECTED");
+ }
+ }
+
+ function sendMessage(msg) {
+ socket.postMessage(JSON.stringify({type: "COLLABROOM", data: msg}));
+ }
+
+ function wrapRecordingErrors(catcher, func) {
+ return function() {
+ try {
+ return func.apply(this, Array.prototype.slice.call(arguments));
+ }
+ catch (e) {
+ caughtErrors.push(e);
+ caughtErrorCatchers.push(catcher);
+ caughtErrorTimes.push(+new Date());
+ //console.dir({catcher: catcher, e: e});
+ throw e;
+ }
+ };
+ }
+
+ function callCatchingErrors(catcher, func) {
+ try {
+ wrapRecordingErrors(catcher, func)();
+ }
+ catch (e) { /*absorb*/ }
+ }
+
+ function handleMessageFromServer(evt) {
+ if (! socket) return;
+ if (! evt.data) return;
+ var wrapper = JSON.parse(evt.data);
+ if(wrapper.type != "COLLABROOM") return;
+ var msg = wrapper.data;
+ if (msg.type == "NEW_CHANGES") {
+ var newRev = msg.newRev;
+ var changeset = msg.changeset;
+ var author = (msg.author || '');
+ var apool = msg.apool;
+ if (newRev != (rev+1)) {
+ dmesg("bad message revision on NEW_CHANGES: "+newRev+" not "+(rev+1));
+ socket.disconnect();
+ return;
+ }
+ rev = newRev;
+ editor.applyChangesToBase(changeset, author, apool);
+ }
+ else if (msg.type == "ACCEPT_COMMIT") {
+ var newRev = msg.newRev;
+ if (newRev != (rev+1)) {
+ dmesg("bad message revision on ACCEPT_COMMIT: "+newRev+" not "+(rev+1));
+ socket.disconnect();
+ return;
+ }
+ rev = newRev;
+ editor.applyPreparedChangesetToBase();
+ setStateIdle();
+ callCatchingErrors("onInternalAction", function() {
+ callbacks.onInternalAction("commitAcceptedByServer");
+ });
+ callCatchingErrors("onConnectionTrouble", function() {
+ callbacks.onConnectionTrouble("OK");
+ });
+ handleUserChanges();
+ }
+ else if (msg.type == "NO_COMMIT_PENDING") {
+ if (state == "COMMITTING") {
+ // server missed our commit message; abort that commit
+ setStateIdle();
+ handleUserChanges();
+ }
+ }
+ else if (msg.type == "USER_NEWINFO") {
+ var userInfo = msg.userInfo;
+ var id = userInfo.userId;
+ if (userSet[id]) {
+ userSet[id] = userInfo;
+ callbacks.onUpdateUserInfo(userInfo);
+ dmesgUsers();
+ }
+ else {
+ userSet[id] = userInfo;
+ callbacks.onUserJoin(userInfo);
+ dmesgUsers();
+ }
+ tellAceActiveAuthorInfo(userInfo);
+ }
+ else if (msg.type == "USER_LEAVE") {
+ var userInfo = msg.userInfo;
+ var id = userInfo.userId;
+ if (userSet[id]) {
+ delete userSet[userInfo.userId];
+ fadeAceAuthorInfo(userInfo);
+ callbacks.onUserLeave(userInfo);
+ dmesgUsers();
+ }
+ }
+ else if (msg.type == "DISCONNECT_REASON") {
+ appLevelDisconnectReason = msg.reason;
+ }
+ else if (msg.type == "CLIENT_MESSAGE") {
+ callbacks.onClientMessage(msg.payload);
+ }
+ else if (msg.type == "SERVER_MESSAGE") {
+ callbacks.onServerMessage(msg.payload);
+ }
+ }
+ function updateUserInfo(userInfo) {
+ userInfo.userId = userId;
+ userSet[userId] = userInfo;
+ tellAceActiveAuthorInfo(userInfo);
+ if (! socket) return;
+ sendMessage({type: "USERINFO_UPDATE", userInfo:userInfo});
+ }
+
+ function tellAceActiveAuthorInfo(userInfo) {
+ tellAceAuthorInfo(userInfo.userId, userInfo.colorId);
+ }
+ function tellAceAuthorInfo(userId, colorId, inactive) {
+ if (colorId || (typeof colorId) == "number") {
+ colorId = Number(colorId);
+ if (options && options.colorPalette && options.colorPalette[colorId]) {
+ var cssColor = options.colorPalette[colorId];
+ if (inactive) {
+ editor.setAuthorInfo(userId, {bgcolor: cssColor, fade: 0.5});
+ }
+ else {
+ editor.setAuthorInfo(userId, {bgcolor: cssColor});
+ }
+ }
+ }
+ }
+ function fadeAceAuthorInfo(userInfo) {
+ tellAceAuthorInfo(userInfo.userId, userInfo.colorId, true);
+ }
+
+ function getConnectedUsers() {
+ return valuesArray(userSet);
+ }
+
+ function tellAceAboutHistoricalAuthors(hadata) {
+ for(var author in hadata) {
+ var data = hadata[author];
+ if (! userSet[author]) {
+ tellAceAuthorInfo(author, data.colorId, true);
+ }
+ }
+ }
+
+ function dmesgUsers() {
+ //pad.dmesg($.map(getConnectedUsers(), function(u) { return u.userId.slice(-2); }).join(','));
+ }
+
+ function handleSocketClosed(params) {
+ socket = null;
+
+ $.each(keys(userSet), function() {
+ var uid = String(this);
+ if (uid != userId) {
+ var userInfo = userSet[uid];
+ delete userSet[uid];
+ callbacks.onUserLeave(userInfo);
+ dmesgUsers();
+ }
+ });
+
+ var reason = appLevelDisconnectReason || params.reason;
+ var shouldReconnect = params.reconnect;
+ if (shouldReconnect) {
+
+ // determine if this is a tight reconnect loop due to weird connectivity problems
+ reconnectTimes.push(+new Date());
+ var TOO_MANY_RECONNECTS = 8;
+ var TOO_SHORT_A_TIME_MS = 10000;
+ if (reconnectTimes.length >= TOO_MANY_RECONNECTS &&
+ ((+new Date()) - reconnectTimes[reconnectTimes.length-TOO_MANY_RECONNECTS]) <
+ TOO_SHORT_A_TIME_MS) {
+ setChannelState("DISCONNECTED", "looping");
+ }
+ else {
+ setChannelState("RECONNECTING", reason);
+ setUpSocket();
+ }
+
+ }
+ else {
+ setChannelState("DISCONNECTED", reason);
+ }
+ }
+
+ function setChannelState(newChannelState, moreInfo) {
+ if (newChannelState != channelState) {
+ channelState = newChannelState;
+ callbacks.onChannelStateChange(channelState, moreInfo);
+ }
+ }
+
+ function keys(obj) {
+ var array = [];
+ $.each(obj, function (k, v) { array.push(k); });
+ return array;
+ }
+ function valuesArray(obj) {
+ var array = [];
+ $.each(obj, function (k, v) { array.push(v); });
+ return array;
+ }
+
+ // We need to present a working interface even before the socket
+ // is connected for the first time.
+ var deferredActions = [];
+ function defer(func, tag) {
+ return function() {
+ var that = this;
+ var args = arguments;
+ function action() {
+ func.apply(that, args);
+ }
+ action.tag = tag;
+ if (channelState == "CONNECTING") {
+ deferredActions.push(action);
+ }
+ else {
+ action();
+ }
+ }
+ }
+ function doDeferredActions(tag) {
+ var newArray = [];
+ for(var i=0;i<deferredActions.length;i++) {
+ var a = deferredActions[i];
+ if ((!tag) || (tag == a.tag)) {
+ a();
+ }
+ else {
+ newArray.push(a);
+ }
+ }
+ deferredActions = newArray;
+ }
+
+ function sendClientMessage(msg) {
+ sendMessage({ type: "CLIENT_MESSAGE", payload: msg });
+ }
+
+ function getCurrentRevisionNumber() {
+ return rev;
+ }
+
+ function getDiagnosticInfo() {
+ var maxCaughtErrors = 3;
+ var maxAceErrors = 3;
+ var maxDebugMessages = 50;
+ var longStringCutoff = 500;
+
+ function trunc(str) {
+ return String(str).substring(0, longStringCutoff);
+ }
+
+ var info = { errors: {length: 0} };
+ function addError(e, catcher, time) {
+ var error = {catcher:catcher};
+ if (time) error.time = time;
+
+ // a little over-cautious?
+ try { if (e.description) error.description = e.description; } catch (x) {}
+ try { if (e.fileName) error.fileName = e.fileName; } catch (x) {}
+ try { if (e.lineNumber) error.lineNumber = e.lineNumber; } catch (x) {}
+ try { if (e.message) error.message = e.message; } catch (x) {}
+ try { if (e.name) error.name = e.name; } catch (x) {}
+ try { if (e.number) error.number = e.number; } catch (x) {}
+ try { if (e.stack) error.stack = trunc(e.stack); } catch (x) {}
+
+ info.errors[info.errors.length] = error;
+ info.errors.length++;
+ }
+ for(var i=0; ((i<caughtErrors.length) && (i<maxCaughtErrors)); i++) {
+ addError(caughtErrors[i], caughtErrorCatchers[i], caughtErrorTimes[i]);
+ }
+ if (editor) {
+ var aceErrors = editor.getUnhandledErrors();
+ for(var i=0; ((i<aceErrors.length) && (i<maxAceErrors)) ;i++) {
+ var errorRecord = aceErrors[i];
+ addError(errorRecord.error, "ACE", errorRecord.time);
+ }
+ }
+
+ info.time = +new Date();
+ info.collabState = state;
+ info.channelState = channelState;
+ info.lastCommitTime = lastCommitTime;
+ info.numSocketReconnects = reconnectTimes.length;
+ info.userId = userId;
+ info.currentRev = rev;
+ info.participants = (function() {
+ var pp = [];
+ for(var u in userSet) {
+ pp.push(u);
+ }
+ return pp.join(',');
+ })();
+
+ if (debugMessages.length > maxDebugMessages) {
+ debugMessages = debugMessages.slice(debugMessages.length-maxDebugMessages,
+ debugMessages.length);
+ }
+
+ info.debugMessages = {length: 0};
+ for(var i=0;i<debugMessages.length;i++) {
+ info.debugMessages[i] = trunc(debugMessages[i]);
+ info.debugMessages.length++;
+ }
+
+ return info;
+ }
+
+ function getMissedChanges() {
+ var obj = {};
+ obj.userInfo = userSet[userId];
+ obj.baseRev = rev;
+ if (state == "COMMITTING" && stateMessage) {
+ obj.committedChangeset = stateMessage.changeset;
+ obj.committedChangesetAPool = stateMessage.apool;
+ obj.committedChangesetSocketId = stateMessageSocketId;
+ editor.applyPreparedChangesetToBase();
+ }
+ var userChangesData = editor.prepareUserChangeset();
+ if (userChangesData.changeset) {
+ obj.furtherChangeset = userChangesData.changeset;
+ obj.furtherChangesetAPool = userChangesData.apool;
+ }
+ return obj;
+ }
+
+ function setStateIdle() {
+ state = "IDLE";
+ callbacks.onInternalAction("newlyIdle");
+ schedulePerhapsCallIdleFuncs();
+ }
+
+ function callWhenNotCommitting(func) {
+ idleFuncs.push(func);
+ schedulePerhapsCallIdleFuncs();
+ }
+
+ var idleFuncs = [];
+ function schedulePerhapsCallIdleFuncs() {
+ setTimeout(function() {
+ if (state == "IDLE") {
+ while (idleFuncs.length > 0) {
+ var f = idleFuncs.shift();
+ f();
+ }
+ }
+ }, 0);
+ }
+
+ var self;
+ return (self = {
+ setOnUserJoin: function(cb) { callbacks.onUserJoin = cb; },
+ setOnUserLeave: function(cb) { callbacks.onUserLeave = cb; },
+ setOnUpdateUserInfo: function(cb) { callbacks.onUpdateUserInfo = cb; },
+ setOnChannelStateChange: function(cb) { callbacks.onChannelStateChange = cb; },
+ setOnClientMessage: function(cb) { callbacks.onClientMessage = cb; },
+ setOnInternalAction: function(cb) { callbacks.onInternalAction = cb; },
+ setOnConnectionTrouble: function(cb) { callbacks.onConnectionTrouble = cb; },
+ setOnServerMessage: function(cb) { callbacks.onServerMessage = cb; },
+ updateUserInfo: defer(updateUserInfo),
+ getConnectedUsers: getConnectedUsers,
+ sendClientMessage: sendClientMessage,
+ getCurrentRevisionNumber: getCurrentRevisionNumber,
+ getDiagnosticInfo: getDiagnosticInfo,
+ getMissedChanges: getMissedChanges,
+ callWhenNotCommitting: callWhenNotCommitting,
+ addHistoricalAuthors: tellAceAboutHistoricalAuthors
+ });
+}
+
+function selectElementContents(elem) {
+ if ($.browser.msie) {
+ var range = document.body.createTextRange();
+ range.moveToElementText(elem);
+ range.select();
+ }
+ else {
+ if (window.getSelection) {
+ var browserSelection = window.getSelection();
+ if (browserSelection) {
+ var range = document.createRange();
+ range.selectNodeContents(elem);
+ browserSelection.removeAllRanges();
+ browserSelection.addRange(range);
+ }
+ }
+ }
+}
diff --git a/etherpad/src/static/js/confirmation.js b/etherpad/src/static/js/confirmation.js
new file mode 100644
index 0000000..a0f725c
--- /dev/null
+++ b/etherpad/src/static/js/confirmation.js
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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() {
+ $('#shoppingform').submit(function() {
+ $('#contbutton').attr("disabled", true).attr("value", "Purchasing...");
+ });
+}) \ No newline at end of file
diff --git a/etherpad/src/static/js/connection_diagnostics.js b/etherpad/src/static/js/connection_diagnostics.js
new file mode 100644
index 0000000..cc43d46
--- /dev/null
+++ b/etherpad/src/static/js/connection_diagnostics.js
@@ -0,0 +1,126 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+diagnostics = {};
+
+diagnostics.data = {};
+
+diagnostics.steps = [
+ ['init', "Initializing"],
+ ['examineBrowser', "Examining web browser"],
+ ['testStreaming', "Testing primary transport (streaming)"],
+ ['testPolling', "Testing secondary transport (polling)"],
+ ['testHiccups', "Testing connection hiccups"],
+ ['sendInfo', "Sending information"],
+ ['showResult', ""]
+];
+
+diagnostics.processNext = function(i) {
+ if (i < diagnostics.steps.length) {
+ var msg = "Step "+(i+1)+": "+diagnostics.steps[i][1]+"...";
+ $('#statusmsg').html(msg);
+ diagnostics[diagnostics.steps[i][0]](function() {
+ diagnostics.processNext(i+1);
+ });
+ }
+};
+
+$(document).ready(function() {
+ diagnostics.processNext(0);
+
+ var emailClicked = false;
+ $('#email').click(function() {
+ if (!emailClicked) {
+ $('#email').select();
+ emailClicked = true;
+ }
+ });
+
+ $('#emailsubmit').click(function() {
+ function err(m) {
+ $('#emailerrormsg').hide().html(m).fadeIn('fast');
+ }
+ var email = $('#email').val();
+ if (!etherpad.validEmail(email)) {
+ err("That doesn't look like a valid email address.");
+ return;
+ }
+ $.ajax({
+ type: 'post',
+ url: '/ep/connection-diagnostics/submitemail',
+ data: {email: email, diagnosticStorableId: clientVars.diagnosticStorableId},
+ success: success,
+ error: error
+ });
+ function success(responseText) {
+ if (responseText == "OK") {
+ $('#emailform').html("<p>Thanks! We will look at your case shortly.</p>");
+ } else {
+ err(responseText);
+ }
+ }
+ function error() {
+ err("There was an error processing your request.");
+ }
+ });
+});
+
+diagnostics.init = function(done) {
+ setTimeout(done, 1000);
+};
+
+diagnostics.examineBrowser = function(done) {
+ setTimeout(done, 1000);
+};
+
+diagnostics.testStreaming = function(done) {
+ setTimeout(done, 1000);
+};
+
+diagnostics.testPolling = function(done) {
+ setTimeout(done, 1000);
+};
+
+diagnostics.testHiccups = function(done) {
+ setTimeout(done, 1000);
+};
+
+diagnostics.sendInfo = function(done) {
+
+ // TODO(jd): remove these test data when you submit actual data.
+ diagnostics.data.test1 = "foo";
+ diagnostics.data.test2 = "bar";
+ diagnostics.data.testNested = {a: 1, b: 2, c: 3};
+
+ // send data object back to server.
+ $.ajax({
+ type: 'post',
+ url: '/ep/connection-diagnostics/submitdata',
+ data: {dataJson: JSON.stringify(diagnostics.data),
+ diagnosticStorableId: clientVars.diagnosticStorableId},
+ success: done,
+ error: function() { alert("There was an error submitting the diagnostic information to the server."); done(); }
+ });
+};
+
+diagnostics.showResult = function(done) {
+ $('#linkanimation').hide();
+ $('#statusmsg').html("<br/>Result: your browser and internet"
+ + " connection appear to be incompatibile with EtherPad.");
+ $('#statusmsg').css('color', '#520');
+ $('#emailform').show();
+};
+
diff --git a/etherpad/src/static/js/draggable.js b/etherpad/src/static/js/draggable.js
new file mode 100644
index 0000000..97a1a3d
--- /dev/null
+++ b/etherpad/src/static/js/draggable.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 makeDraggable(jqueryNodes, eventHandler) {
+ jqueryNodes.each(function() {
+ var node = $(this);
+ var state = {};
+ var inDrag = false;
+ function dragStart(evt) {
+ if (inDrag) {
+ return;
+ }
+ inDrag = true;
+ if (eventHandler('dragstart', evt, state) !== false) {
+ $(document).bind('mousemove', dragUpdate);
+ $(document).bind('mouseup', dragEnd);
+ }
+ evt.preventDefault();
+ return false;
+ }
+ function dragUpdate(evt) {
+ if (! inDrag) {
+ return;
+ }
+ eventHandler('dragupdate', evt, state);
+ evt.preventDefault();
+ return false;
+ }
+ function dragEnd(evt) {
+ if (! inDrag) {
+ return;
+ }
+ inDrag = false;
+ try {
+ eventHandler('dragend', evt, state);
+ }
+ finally {
+ $(document).unbind('mousemove', dragUpdate);
+ $(document).unbind('mouseup', dragEnd);
+ evt.preventDefault();
+ }
+ return false;
+ }
+ node.bind('mousedown', dragStart);
+ });
+} \ No newline at end of file
diff --git a/etherpad/src/static/js/etherpad.js b/etherpad/src/static/js/etherpad.js
new file mode 100644
index 0000000..4e51dbf
--- /dev/null
+++ b/etherpad/src/static/js/etherpad.js
@@ -0,0 +1,217 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+$(document).ready(function() {
+ etherpad.deobfuscateEmails();
+
+ if ($('#betasignuppage').size() > 0) {
+ etherpad.betaSignupPageInit();
+ }
+
+ if ($('#productpage').size() > 0) {
+ etherpad.productPageInit();
+ }
+
+ if ($('.pricingpage').size() > 0) {
+ etherpad.pricingPageInit();
+ }
+});
+
+etherpad = {};
+
+//----------------------------------------------------------------
+// general utils
+//----------------------------------------------------------------
+
+etherpad.validEmail = function(x) {
+ return (x.length > 0 &&
+ x.match(/^[\w\.\_\+\-]+\@[\w\_\-]+\.[\w\_\-\.]+$/));
+};
+
+//----------------------------------------------------------------
+// obfuscating emails
+//----------------------------------------------------------------
+
+etherpad.deobfuscateEmails = function() {
+ $("a.obfuscemail").each(function() {
+ $(this).html($(this).html().replace('p*d.sp***e','pad.spline'));
+ this.href = this.href.replace('p*d.sp***e','pad.spline');
+ });
+};
+
+//----------------------------------------------------------------
+// Signing up for pricing info
+//----------------------------------------------------------------
+
+etherpad.pricingPageInit = function() {
+ $('#submitbutton').click(etherpad.pricingSubmit);
+};
+
+etherpad.pricingSubmit = function(edition) {
+ var allData = {};
+ $('#pricingcontact input.ti').each(function() {
+ allData[$(this).attr('id')] = $(this).val();
+ });
+ allData.industry = $('#industry').val();
+
+ $('form button').hide();
+ $('#spinner').show();
+ $('form input').attr('disabled', true);
+
+ $.ajax({
+ type: 'post',
+ url: $('#pricingcontact').attr('action'),
+ data: allData,
+ success: success,
+ error: error
+ });
+
+ function success(responseText) {
+ $('#spinner').hide();
+ if (responseText == "OK") {
+ $('#errorbox').hide();
+ $('#confirmbox').fadeIn('fast');
+ } else {
+ $('#confirmbox').hide();
+ $('#errorbox').hide().html(responseText).fadeIn('fast');
+ $('form button').show();
+ $('form input').removeAttr('disabled');
+ }
+ }
+ function error() {
+ $('#spinner').hide();
+ $('#errorbox').hide().html("Server error.").fadeIn('fast');
+ $('form button').show();
+ $('form input').removeAttr('disabled');
+ }
+
+ return false;
+}
+
+
+//----------------------------------------------------------------
+// Product page (client-side nagivation with JS)
+//----------------------------------------------------------------
+
+etherpad.productPageInit = function() {
+ $("#productpage #tour").addClass("javascripton");
+ etherpad.productPageNavigateTo(window.location.hash.substring(1));
+
+ $("#productpage a.tournav").click(etherpad.tourNavClick);
+}
+
+etherpad.tourNavClick = function() { // to be called as a click event handler
+ var href = $(this).attr('href');
+ var thorpLoc = href.indexOf('#');
+ if (thorpLoc >= 0) {
+ etherpad.productPageNavigateTo(href.substring(thorpLoc+1), true);
+ }
+}
+
+etherpad.productPageNavigateTo = function(hash, shouldAnimate) {
+ function setNavLink(rightOrLeft, text, linkhash) {
+ var navcells = $('#productpage .tourbar .'+rightOrLeft);
+ if (! text) {
+ navcells.html('&nbsp;');
+ }
+ else {
+ navcells.
+ html('<a class="tournav" href="'+clientVars.pageURL+'#'+(linkhash||'')+'">'+text+'</a>').
+ find('a.tournav').click(etherpad.tourNavClick);
+ }
+ }
+ function switchCardsIfNecessary(fromCard, toCard, andThen/*(didAnimate)*/) {
+ if (! $('#productpage #tour').hasClass("show"+toCard)) {
+ var afterAnimate = function() {
+ $("#productpage #"+fromCard).get(0).style.display = "";
+ $('#productpage #tour').removeClass("show"+fromCard).addClass("show"+toCard);
+ if (andThen) andThen(shouldAnimate);
+ }
+ if (shouldAnimate) {
+ $("#productpage #"+fromCard).fadeOut("fast", afterAnimate);
+ }
+ else {
+ afterAnimate();
+ }
+ }
+ else {
+ andThen(false);
+ }
+ }
+ function switchProseIfNecessary(toNum, useAnimation, andThen) {
+ var visibleProse = $("#productpage .tourprose:visible");
+ var alreadyVisible = ($("#productpage #tour"+toNum+"prose:visible").size() > 0);
+ function assignVisibilities() {
+ $("#productpage .tourprose").each(function() {
+ if (this.id == "tour"+toNum+"prose") {
+ this.style.display = 'block';
+ }
+ else {
+ this.style.display = 'none';
+ }
+ });
+ }
+
+ if ((! useAnimation) || visibleProse.size() == 0 || alreadyVisible) {
+ assignVisibilities();
+ andThen();
+ }
+ else {
+ function afterAnimate() {
+ assignVisibilities();
+ andThen();
+ }
+ if (visibleProse.size() > 0 && visibleProse.get(0).id != "tour"+toNum+"prose") {
+ visibleProse.fadeOut("fast", afterAnimate);
+ }
+ else {
+ afterAnimate();
+ }
+ }
+ }
+ function getProseTitle(n) {
+ if (n == 0) return clientVars.screenshotTitle;
+ var atag = $("#productpage #tourleftnav .tour"+n+" a");
+ if (atag.size() > 0) return atag.text();
+ return '';
+ }
+
+ var regexResult;
+ if ((regexResult = /^uses([1-9][0-9]*)$/.exec(hash))) {
+ var tourNum = +regexResult[1];
+ switchCardsIfNecessary("pageshot", "usecases", function(didAnimate) {
+ switchProseIfNecessary(tourNum, shouldAnimate && !didAnimate, function() {
+ /*var n = tourNum;
+ setNavLink("left", "&laquo; "+getProseTitle(n-1), (n == 1 ? "" : "uses"+(n-1)));
+ var nextTitle = getProseTitle(n+1);
+ if (! nextTitle) setNavLink("right", "");
+ else setNavLink("right", nextTitle+" &raquo;", "uses"+(n+1));*/
+ /*setNavLink("left", "&laquo; "+getProseTitle(0), "");
+ setNavLink("right", "");*/
+ setNavLink("right", "&laquo; "+getProseTitle(0), "");
+ $('#tourtop td.left').html("Use Cases");
+ $("#productpage #tourleftnav li").removeClass("selected");
+ $("#productpage #tourleftnav li.tour"+tourNum).addClass("selected");
+ });
+ });
+ }
+ else {
+ switchCardsIfNecessary("usecases", "pageshot", function() {
+ $('#tourtop td.left').html(getProseTitle(0));
+ setNavLink("right", clientVars.screenshotNextLink, "uses1");
+ });
+ }
+}
diff --git a/etherpad/src/static/js/jquery-1.2.6.js b/etherpad/src/static/js/jquery-1.2.6.js
new file mode 100644
index 0000000..88e661e
--- /dev/null
+++ b/etherpad/src/static/js/jquery-1.2.6.js
@@ -0,0 +1,3549 @@
+(function(){
+/*
+ * jQuery 1.2.6 - 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-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+ _$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+};
+
+// 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
+ isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+ undefined;
+
+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
+ 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
+ return jQuery( elem );
+ }
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(jQuery.makeArray(selector));
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.6",
+
+ // 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
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ 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[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ 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 this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector == 'string' ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ 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;
+ }
+
+ if( value.constructor == Number )
+ value += '';
+
+ 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 = jQuery.makeArray(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[0] ?
+ 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 === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && 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.fn.init.prototype = jQuery.fn;
+
+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 );
+}
+
+function now(){
+ return +new Date;
+}
+
+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 == i ) {
+ target = this;
+ --i;
+ }
+
+ 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 ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+ // exclude the following css properties to add px
+ exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {};
+
+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 && /^[\s[]?function/.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 ) );
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ 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 ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var 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 hasClass("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, style = elem.style;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ // defaultView is cached
+ var ret = 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( 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 = style.outline;
+ style.outline = "0 solid black";
+ 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 && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle && !color( elem ) )
+ ret = computedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [], a = elem, i = 0;
+
+ // Locate all of the parent display: none elements
+ for ( ; 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 ( ; 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" :
+ ( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( 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 left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ 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 += '';
+
+ // 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 notxml = !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined,
+ msie = jQuery.browser.msie;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ // IE elem.getAttribute passes even for style
+ if ( elem.tagName ) {
+
+ // These attributes require special treatment
+ var special = /href|src|style/.test( name );
+
+ // 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;
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ){
+ // 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";
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ return elem[ name ];
+ }
+
+ if ( msie && notxml && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ if ( set )
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+
+ var attr = msie && notxml && special
+ // Some attributes require a special call on IE
+ ? elem.getAttribute( name, 2 )
+ : elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+
+ // IE uses filters for opacity
+ if ( msie && name == "opacity" ) {
+ if ( set ) {
+ // 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\([^)]*\)/, "" ) +
+ (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( set )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ if( array != null ){
+ var i = array.length;
+ //the window, strings and functions also have 'length'
+ if( i == null || array.split || array.setInterval || array.call )
+ ret[0] = array;
+ else
+ while( i )
+ ret[--i] = array[i];
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ // Use === because on IE, window == document
+ 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
+ var i = 0, elem, pos = first.length;
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( jQuery.browser.msie ) {
+ while ( elem = second[ i++ ] )
+ if ( elem.nodeType != 8 )
+ first[ pos++ ] = elem;
+
+ } else
+ while ( elem = second[ i++ ] )
+ first[ pos++ ] = elem;
+
+ 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 ) )
+ 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 )
+ ret[ ret.length ] = value;
+ }
+
+ return ret.concat.apply( [], 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,
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing"
+ }
+});
+
+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" );
+ };
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+ return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}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-child, #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
+ re = quickChild,
+
+ 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 = [],
+ 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 && 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 )
+ 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 = this.proxy( fn, function() {
+ // Pass arguments and context to original handler
+ return fn.apply(this, arguments);
+ });
+
+ // Store data in unique handler
+ handler.data = data;
+ }
+
+ // 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(){
+ // 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 jQuery.event.handle.apply(arguments.callee.elem, arguments);
+ });
+ // 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 separated 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({
+ type: type,
+ target: elem,
+ preventDefault: function(){},
+ stopPropagation: function(){},
+ timeStamp: now()
+ });
+ data[0][expando] = true; // no need to fix fake event
+ }
+
+ // Enforce the right trigger type
+ data[0].type = type;
+ if ( exclusive )
+ data[0].exclusive = true;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data(elem, "handle");
+ if ( handle )
+ val = handle.apply( elem, data );
+
+ // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+ if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && 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, ret, namespace, all, handlers;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+
+ // Namespaced event handlers
+ namespace = event.type.split(".");
+ event.type = namespace[0];
+ namespace = namespace[1];
+ // Cache this now, all = true means, any handler
+ all = !namespace && !event.exclusive;
+
+ handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+
+ // Filter the functions by class
+ if ( all || handler.type == namespace ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handler;
+ event.data = handler.data;
+
+ ret = handler.apply( this, arguments );
+
+ if ( val !== false )
+ val = ret;
+
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+
+ return val;
+ },
+
+ fix: function(event) {
+ if ( event[expando] == true )
+ return event;
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = { originalEvent: originalEvent };
+ var props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");
+ for ( var i=props.length; i; i-- )
+ event[ props[i] ] = originalEvent[ props[i] ];
+
+ // Mark it as fixed
+ event[expando] = true;
+
+ // 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 timeStamp
+ event.timeStamp = event.timeStamp || now();
+
+ // 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 = event.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;
+ },
+
+ proxy: function( fn, proxy ){
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ 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
+ event.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
+ event.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 ) {
+ var one = jQuery.event.proxy( fn || data, function(event) {
+ jQuery(this).unbind(event, one);
+ return (fn || data).apply( this, arguments );
+ });
+ return this.each(function(){
+ jQuery.event.add( this, type, one, 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 ) {
+ return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while( i < args.length )
+ jQuery.event.proxy( fn, args[i++] );
+
+ return this.click( jQuery.event.proxy( fn, function(event) {
+ // Figure out which function to execute
+ this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+ // 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.call( 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({
+ // Keep a copy of the old load
+ _load: jQuery.fn.load,
+
+ load: function( url, params, callback ) {
+ if ( typeof url != 'string' )
+ return this._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 = now();
+
+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: {
+ url: location.href,
+ 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 ) {
+ // 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));
+
+ var jsonp, jsre = /=\?(&|$)/g, status, data,
+ type = s.type.toUpperCase();
+
+ // 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 ( type == "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 && type == "GET" ) {
+ var ts = now();
+ // 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 && type == "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" );
+
+ // Matches an absolute URL, and saves the domain
+ var remote = /^(?:\w+:)?\/\/([^\/?#]+)/;
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType == "script" && type == "GET"
+ && remote.test(s.url) && remote.exec(s.url)[1] != location.host ){
+ 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 xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if( s.username )
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ else
+ xhr.open(type, s.url, s.async);
+
+ // 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 )
+ xhr.setRequestHeader("Content-Type", s.contentType);
+
+ // Set the If-Modified-Since header, if ifModified mode.
+ if ( s.ifModified )
+ xhr.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
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.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(xhr, s) === false ) {
+ // cleanup active request counter
+ s.global && jQuery.active--;
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xhr, 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 && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+ requestDone = true;
+
+ // clear poll interval
+ if (ival) {
+ clearInterval(ival);
+ ival = null;
+ }
+
+ status = isTimeout == "timeout" && "timeout" ||
+ !jQuery.httpSuccess( xhr ) && "error" ||
+ s.ifModified && jQuery.httpNotModified( xhr, 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( xhr, s.dataType, s.dataFilter );
+ } 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 = xhr.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, xhr, status);
+
+ // Fire the complete handlers
+ complete();
+
+ // Stop memory leaks
+ if ( s.async )
+ xhr = 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 ( xhr ) {
+ // Cancel the request
+ xhr.abort();
+
+ if( !requestDone )
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send(s.data);
+ } catch(e) {
+ jQuery.handleError(s, xhr, 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", [xhr, s] );
+ }
+
+ function complete(){
+ // Process result
+ if ( s.complete )
+ s.complete(xhr, status);
+
+ // The request was completed
+ if ( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) s.error( xhr, status, e );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol == "file:" ||
+ ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223 ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ try {
+ var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+ // Firefox always returns 200. check Last-Modified date
+ return xhr.status == 304 || xhrRes == jQuery.lastModified[url] ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ httpData: function( xhr, type, filter ) {
+ var ct = xhr.getResponseHeader("content-type"),
+ xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.tagName == "parsererror" )
+ throw "parsererror";
+
+ // Allow a pre-filtering function to sanitize the response
+ if( filter )
+ data = filter( data, type );
+
+ // 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( jQuery.isFunction(a[j]) ? a[j]() : 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.apply( this, arguments ) :
+ 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), p,
+ hidden = jQuery(this).is(":hidden"), self = this;
+
+ for ( p in prop ) {
+ if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+ return opt.complete.call(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.call(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 ){
+
+ type = type || "fx";
+
+ var q = jQuery.data( elem, type + "queue" );
+
+ if ( !q || array )
+ q = jQuery.data( elem, type + "queue", 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].call( 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 :
+ jQuery.fx.speeds[opt.duration]) || jQuery.fx.speeds.def;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function(){
+ if ( opt.queue !== false )
+ jQuery(this).dequeue();
+ if ( jQuery.isFunction( opt.old ) )
+ opt.old.call( 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.call( 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 = now();
+ 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 = now();
+
+ 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 ( done )
+ // Execute the complete function
+ this.options.complete.call( 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.extend( jQuery.fx, {
+ speeds:{
+ slow: 600,
+ fast: 200,
+ // Default speed
+ def: 400
+ },
+ 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),
+ css = jQuery.curCSS,
+ fixed = 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 && 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(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 && 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 || css(offsetChild, "position") == "absolute")) ||
+ (mozilla && 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, 10) || 0;
+ top += parseInt(t, 10) || 0;
+ }
+
+ return results;
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ var left = 0, top = 0, results;
+
+ if ( this[0] ) {
+ // Get *real* offsetParent
+ var offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= num( this, 'marginTop' );
+ offset.left -= num( this, 'marginLeft' );
+
+ // Add offsetParent borders
+ parentOffset.top += num( offsetParent, 'borderTopWidth' );
+ parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+ // Subtract the two offsets
+ results = {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ }
+
+ return results;
+ },
+
+ offsetParent: function() {
+ var offsetParent = this[0].offsetParent;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+ offsetParent = offsetParent.offsetParent;
+ return jQuery(offsetParent);
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+ var method = 'scroll' + name;
+
+ jQuery.fn[ method ] = function(val) {
+ if (!this[0]) return;
+
+ return val != undefined ?
+
+ // Set the scroll offset
+ this.each(function() {
+ this == window || this == document ?
+ window.scrollTo(
+ !i ? val : jQuery(window).scrollLeft(),
+ i ? val : jQuery(window).scrollTop()
+ ) :
+ this[ method ] = val;
+ }) :
+
+ // Return the scroll offset
+ this[0] == window || this[0] == document ?
+ self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+ jQuery.boxModel && document.documentElement[ method ] ||
+ document.body[ method ] :
+ this[0][ method ];
+ };
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+ var tl = i ? "Left" : "Top", // top or left
+ br = i ? "Right" : "Bottom"; // bottom or right
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function(){
+ return this[ name.toLowerCase() ]() +
+ num(this, "padding" + tl) +
+ num(this, "padding" + br);
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function(margin) {
+ return this["inner" + name]() +
+ num(this, "border" + tl + "Width") +
+ num(this, "border" + br + "Width") +
+ (margin ?
+ num(this, "margin" + tl) + num(this, "margin" + br) : 0);
+ };
+
+});})();
diff --git a/etherpad/src/static/js/jquery-1.3.2.js b/etherpad/src/static/js/jquery-1.3.2.js
new file mode 100644
index 0000000..9263574
--- /dev/null
+++ b/etherpad/src/static/js/jquery-1.3.2.js
@@ -0,0 +1,4376 @@
+/*!
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function(){
+
+var
+ // Will speed up references to window, and allows munging its name.
+ window = this,
+ // Will speed up references to undefined, and allows munging its name.
+ undefined,
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // A simple way to check for HTML strings or ID strings
+ // (both of which we optimize for)
+ quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
+ // Is it a simple selector
+ 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;
+ this.context = selector;
+ return this;
+ }
+ // Handle HTML strings
+ 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] );
+
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem && elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ var ret = jQuery( elem || [] );
+ ret.context = document;
+ ret.selector = selector;
+ return ret;
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document ).ready( selector );
+
+ // Make sure that old selector state is passed along
+ if ( selector.selector && selector.context ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return this.setArray(jQuery.isArray( selector ) ?
+ selector :
+ jQuery.makeArray(selector));
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.3.2",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // 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
+ Array.prototype.slice.call( 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, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" )
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ else if ( name )
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+
+ // 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 ) {
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( typeof name === "string" )
+ if ( value === undefined )
+ return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ 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
+ var wrap = jQuery( html, this[0].ownerDocument ).clone();
+
+ if ( this[0].parentNode )
+ wrap.insertBefore( this[0] );
+
+ wrap.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, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: [].push,
+ sort: [].sort,
+ splice: [].splice,
+
+ find: function( selector ) {
+ if ( this.length === 1 ) {
+ var ret = this.pushStack( [], "find", selector );
+ ret.length = 0;
+ jQuery.find( selector, this[0], ret );
+ return ret;
+ } else {
+ return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ })), "find", selector );
+ }
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( !jQuery.support.noCloneEvent && !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 html = this.outerHTML;
+ if ( !html ) {
+ var div = this.ownerDocument.createElement("div");
+ div.appendChild( this.cloneNode(true) );
+ html = div.innerHTML;
+ }
+
+ return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true ) {
+ var orig = this.find("*").andSelf(), i = 0;
+
+ ret.find("*").andSelf().each(function(){
+ if ( this.nodeName !== orig[i].nodeName )
+ return;
+
+ var events = jQuery.data( orig[i], "events" );
+
+ for ( var type in events ) {
+ for ( var handler in events[ type ] ) {
+ jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+ }
+ }
+
+ i++;
+ });
+ }
+
+ // 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, jQuery.grep(this, function(elem){
+ return elem.nodeType === 1;
+ }) ), "filter", selector );
+ },
+
+ closest: function( selector ) {
+ var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
+ closer = 0;
+
+ return this.map(function(){
+ var cur = this;
+ while ( cur && cur.ownerDocument ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
+ jQuery.data(cur, "closest", closer);
+ return cur;
+ }
+ cur = cur.parentNode;
+ closer++;
+ }
+ });
+ },
+
+ not: function( selector ) {
+ if ( typeof selector === "string" )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
+ 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 this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector === "string" ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ hasClass: function( selector ) {
+ return !!selector && this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value === undefined ) {
+ var elem = this[0];
+
+ if ( elem ) {
+ if( jQuery.nodeName( elem, 'option' ) )
+ return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+
+ // 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(option).val();
+
+ // 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
+ return (elem.value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if ( typeof value === "number" )
+ value += '';
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( jQuery.isArray(value) && /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 = jQuery.makeArray(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[0] ?
+ this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
+ 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 ),
+ "slice", Array.prototype.slice.call(arguments).join(",") );
+ },
+
+ 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 );
+ },
+
+ domManip: function( args, table, callback ) {
+ if ( this[0] ) {
+ var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
+ scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
+ first = fragment.firstChild;
+
+ if ( first )
+ for ( var i = 0, l = this.length; i < l; i++ )
+ callback.call( root(this[i], first), this.length > 1 || i > 0 ?
+ fragment.cloneNode(true) : fragment );
+
+ if ( scripts )
+ jQuery.each( scripts, evalScript );
+ }
+
+ return this;
+
+ function root( elem, cur ) {
+ return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+ }
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+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 );
+}
+
+function now(){
+ return +new Date;
+}
+
+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 ( typeof target === "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" && !jQuery.isFunction(target) )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == i ) {
+ target = this;
+ --i;
+ }
+
+ 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 ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy === "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {},
+ toString = Object.prototype.toString;
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return toString.call(obj) === "[object Function]";
+ },
+
+ isArray: function( obj ) {
+ return toString.call(obj) === "[object Array]";
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ if ( data && /\S/.test(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.support.scriptEval )
+ script.appendChild( document.createTextNode( data ) );
+ else
+ script.text = data;
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length === undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length === undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var 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 typeof value === "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 hasClass("class")
+ has: function( elem, className ) {
+ return elem && 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, extra ) {
+ 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;
+
+ if ( extra === "border" )
+ return;
+
+ jQuery.each( which, function() {
+ if ( !extra )
+ val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ if ( extra === "margin" )
+ val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
+ else
+ val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ }
+
+ if ( elem.offsetWidth !== 0 )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, Math.round(val));
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style;
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && !jQuery.support.opacity ) {
+ ret = jQuery.attr( style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle )
+ ret = computedStyle.getPropertyValue( name );
+
+ // 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 left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context, fragment ) {
+ 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;
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
+ var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
+ if ( match )
+ return [ context.createElement( match[1] ) ];
+ }
+
+ var ret = [], scripts = [], div = context.createElement("div");
+
+ jQuery.each(elems, function(i, elem){
+ if ( typeof elem === "number" )
+ elem += '';
+
+ if ( !elem )
+ return;
+
+ // 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 = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
+
+ 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.support.htmlSerialize &&
+ [ 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.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = /<tbody/i.test(elem),
+ tbody = !tags.indexOf("<table") && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] == "<table>" && !hasBody ?
+ 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 ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
+ div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+ elem = jQuery.makeArray( div.childNodes );
+ }
+
+ if ( elem.nodeType )
+ ret.push( elem );
+ else
+ ret = jQuery.merge( ret, elem );
+
+ });
+
+ if ( fragment ) {
+ for ( var i = 0; ret[i]; i++ ) {
+ if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+ } else {
+ if ( ret[i].nodeType === 1 )
+ ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+ fragment.appendChild( ret[i] );
+ }
+ }
+
+ return scripts;
+ }
+
+ 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 notxml = !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ // IE elem.getAttribute passes even for style
+ if ( elem.tagName ) {
+
+ // These attributes require special treatment
+ var special = /href|src|style/.test( name );
+
+ // Safari mis-reports the default selected property of a hidden option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name == "selected" && elem.parentNode )
+ elem.parentNode.selectedIndex;
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ){
+ // 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";
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ if ( name == "tabIndex" ) {
+ var attributeNode = elem.getAttributeNode( "tabIndex" );
+ return attributeNode && attributeNode.specified
+ ? attributeNode.value
+ : elem.nodeName.match(/(button|input|object|select|textarea)/i)
+ ? 0
+ : elem.nodeName.match(/^(a|area)$/i) && elem.href
+ ? 0
+ : undefined;
+ }
+
+ return elem[ name ];
+ }
+
+ if ( !jQuery.support.style && notxml && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ if ( set )
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+
+ var attr = !jQuery.support.hrefNormalized && notxml && special
+ // Some attributes require a special call on IE
+ ? elem.getAttribute( name, 2 )
+ : elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+
+ // IE uses filters for opacity
+ if ( !jQuery.support.opacity && name == "opacity" ) {
+ if ( set ) {
+ // 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\([^)]*\)/, "" ) +
+ (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( set )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ if( array != null ){
+ var i = array.length;
+ // The window, strings (and functions) also have 'length'
+ if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
+ ret[0] = array;
+ else
+ while( i )
+ ret[--i] = array[i];
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ // Use === because on IE, window == document
+ 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
+ var i = 0, elem, pos = first.length;
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( !jQuery.support.getAll ) {
+ while ( (elem = second[ i++ ]) != null )
+ if ( elem.nodeType != 8 )
+ first[ pos++ ] = elem;
+
+ } else
+ while ( (elem = second[ i++ ]) != null )
+ first[ pos++ ] = elem;
+
+ 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 ) )
+ 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 )
+ ret[ ret.length ] = value;
+ }
+
+ return ret.concat.apply( [], ret );
+ }
+});
+
+// Use of jQuery.browser is deprecated.
+// It's included for backwards compatibility and plugins,
+// although they should work to migrate away.
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+ version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
+ safari: /webkit/.test( userAgent ),
+ opera: /opera/.test( userAgent ),
+ msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+ mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+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 ), name, selector );
+ };
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function(name, original){
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [], insert = jQuery( selector );
+
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, selector );
+ };
+});
+
+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, state ) {
+ if( typeof state !== "boolean" )
+ state = !jQuery.className.has( this, classNames );
+ jQuery.className[ state ? "add" : "remove" ]( this, classNames );
+ },
+
+ remove: function( selector ) {
+ if ( !selector || jQuery.filter( selector, [ this ] ).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).children().remove();
+
+ // Remove any remaining nodes
+ while ( this.firstChild )
+ this.removeChild( this.firstChild );
+ }
+}, function(name, fn){
+ jQuery.fn[ name ] = function(){
+ return this.each( fn, arguments );
+ };
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+ return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}
+var expando = "jQuery" + now(), uuid = 0, windowData = {};
+
+jQuery.extend({
+ 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 ];
+ }
+ },
+ queue: function( elem, type, data ) {
+ if ( elem ){
+
+ type = (type || "fx") + "queue";
+
+ var q = jQuery.data( elem, type );
+
+ if ( !q || jQuery.isArray(data) )
+ q = jQuery.data( elem, type, jQuery.makeArray(data) );
+ else if( data )
+ q.push( data );
+
+ }
+ return q;
+ },
+
+ dequeue: function( elem, type ){
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift();
+
+ if( !type || type === "fx" )
+ fn = queue[0];
+
+ if( fn !== undefined )
+ fn.call(elem);
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ){
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && 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 );
+ });
+ },
+ queue: function(type, data){
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined )
+ return jQuery.queue( this[0], type );
+
+ return this.each(function(){
+ var queue = jQuery.queue( this, type, data );
+
+ if( type == "fx" && queue.length == 1 )
+ queue[0].call(this);
+ });
+ },
+ dequeue: function(type){
+ return this.each(function(){
+ jQuery.dequeue( this, type );
+ });
+ }
+});/*!
+ * Sizzle CSS Selector Engine - v0.9.3
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
+ done = 0,
+ toString = Object.prototype.toString;
+
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 )
+ return [];
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true;
+
+ // Reset the position of the chunker regexp (start from head)
+ chunker.lastIndex = 0;
+
+ while ( (m = chunker.exec(selector)) !== null ) {
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = RegExp.rightContext;
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
+ set = Sizzle.filter( ret.expr, ret.set );
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, isXML(context) );
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || selector);
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, context, results, seed );
+
+ if ( sortOrder ) {
+ hasDuplicate = false;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.match[ type ].exec( expr )) ) {
+ var left = RegExp.leftContext;
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: " + expr;
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+ },
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !part.match(/\W/) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !part.match(/\W/) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while (node = node.previousSibling) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while (node = node.nextSibling) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes );
+
+// Provide a fallback method if it does not work
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.selectNode(a);
+ aRange.collapse(true);
+ bRange.selectNode(b);
+ bRange.collapse(true);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("form"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<input name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ Sizzle.find = oldSizzle.find;
+ Sizzle.filter = oldSizzle.filter;
+ Sizzle.selectors = oldSizzle.selectors;
+ Sizzle.matches = oldSizzle.matches;
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ if ( div.getElementsByClassName("e").length === 0 )
+ return;
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 )
+ return;
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+var contains = document.compareDocumentPosition ? function(a, b){
+ return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && isXML( elem.ownerDocument );
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.filter = Sizzle.filter;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+
+Sizzle.selectors.filters.hidden = function(elem){
+ return elem.offsetWidth === 0 || elem.offsetHeight === 0;
+};
+
+Sizzle.selectors.filters.visible = function(elem){
+ return elem.offsetWidth > 0 || elem.offsetHeight > 0;
+};
+
+Sizzle.selectors.filters.animated = function(elem){
+ return jQuery.grep(jQuery.timers, function(fn){
+ return elem === fn.elem;
+ }).length;
+};
+
+jQuery.multiFilter = function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return Sizzle.matches(expr, elems);
+};
+
+jQuery.dir = function( elem, dir ){
+ var matched = [], cur = elem[dir];
+ while ( cur && cur != document ) {
+ if ( cur.nodeType == 1 )
+ matched.push( cur );
+ cur = cur[dir];
+ }
+ return matched;
+};
+
+jQuery.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;
+};
+
+jQuery.sibling = function(n, elem){
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType == 1 && n != elem )
+ r.push( n );
+ }
+
+ return r;
+};
+
+return;
+
+window.Sizzle = Sizzle;
+
+})();
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated 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 ( elem.setInterval && elem != window )
+ 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 = this.proxy( fn );
+
+ // Store data in unique handler
+ handler.data = data;
+ }
+
+ // 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(){
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+ jQuery.event.handle.apply(arguments.callee.elem, arguments) :
+ undefined;
+ });
+ // 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 separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type) {
+ // Namespaced event handlers
+ var namespaces = type.split(".");
+ type = namespaces.shift();
+ handler.type = namespaces.slice().sort().join(".");
+
+ // Get the current list of functions bound to this event
+ var handlers = events[type];
+
+ if ( jQuery.event.specialAll[type] )
+ jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
+
+ // 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, data, namespaces) === 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 namespaces = type.split(".");
+ type = namespaces.shift();
+ var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+ 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 ( var handle in events[type] )
+ // Handle the removal of namespaced events
+ if ( namespace.test(events[type][handle].type) )
+ delete events[type][handle];
+
+ if ( jQuery.event.specialAll[type] )
+ jQuery.event.specialAll[type].teardown.call(elem, namespaces);
+
+ // 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, namespaces) === 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" );
+ }
+ }
+ },
+
+ // bubbling is internal
+ trigger: function( event, data, elem, bubbling ) {
+ // Event object or event type
+ var type = event.type || event;
+
+ if( !bubbling ){
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[expando] ? event :
+ // Object literal
+ jQuery.extend( jQuery.Event(type), event ) :
+ // Just the event type (string)
+ jQuery.Event(type);
+
+ if ( type.indexOf("!") >= 0 ) {
+ event.type = type = type.slice(0, -1);
+ event.exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Don't bubble custom events when global (to avoid too much overhead)
+ event.stopPropagation();
+ // Only trigger if we've ever bound an event for it
+ if ( this.global[type] )
+ jQuery.each( jQuery.cache, function(){
+ if ( this.events && this.events[type] )
+ jQuery.event.trigger( event, data, this.handle.elem );
+ });
+ }
+
+ // Handle triggering a single element
+
+ // don't do events on text and comment nodes
+ if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
+ return undefined;
+
+ // Clean up in case it is reused
+ event.result = undefined;
+ event.target = elem;
+
+ // Clone the incoming data, if any
+ data = jQuery.makeArray(data);
+ data.unshift( event );
+ }
+
+ event.currentTarget = elem;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data(elem, "handle");
+ if ( handle )
+ handle.apply( elem, data );
+
+ // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+ if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+ event.result = false;
+
+ // Trigger the native events (except for clicks on links)
+ if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(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;
+
+ if ( !event.isPropagationStopped() ) {
+ var parent = elem.parentNode || elem.ownerDocument;
+ if ( parent )
+ jQuery.event.trigger(event, data, parent, true);
+ }
+ },
+
+ handle: function(event) {
+ // returned undefined or false
+ var all, handlers;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+ event.currentTarget = this;
+
+ // Namespaced event handlers
+ var namespaces = event.type.split(".");
+ event.type = namespaces.shift();
+
+ // Cache this now, all = true means, any handler
+ all = !namespaces.length && !event.exclusive;
+
+ var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+ handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+
+ // Filter the functions by class
+ if ( all || namespace.test(handler.type) ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handler;
+ event.data = handler.data;
+
+ var ret = handler.apply(this, arguments);
+
+ if( ret !== undefined ){
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if( event.isImmediatePropagationStopped() )
+ break;
+
+ }
+ }
+ },
+
+ props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+ fix: function(event) {
+ if ( event[expando] )
+ return event;
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = jQuery.Event( originalEvent );
+
+ for ( var i = this.props.length, prop; i; ){
+ prop = this.props[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // 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 = event.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;
+ },
+
+ proxy: function( fn, proxy ){
+ proxy = proxy || function(){ return fn.apply(this, arguments); };
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: bindReady,
+ teardown: function() {}
+ }
+ },
+
+ specialAll: {
+ live: {
+ setup: function( selector, namespaces ){
+ jQuery.event.add( this, namespaces[0], liveHandler );
+ },
+ teardown: function( namespaces ){
+ if ( namespaces.length ) {
+ var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
+
+ jQuery.each( (jQuery.data(this, "events").live || {}), function(){
+ if ( name.test(this.type) )
+ remove++;
+ });
+
+ if ( remove < 1 )
+ jQuery.event.remove( this, namespaces[0], liveHandler );
+ }
+ }
+ }
+ }
+};
+
+jQuery.Event = function( src ){
+ // Allow instantiation without the 'new' keyword
+ if( !this.preventDefault )
+ return new jQuery.Event(src);
+
+ // Event object
+ if( src && src.type ){
+ this.originalEvent = src;
+ this.type = src.type;
+ // Event type
+ }else
+ this.type = src;
+
+ // timeStamp is buggy for some events on Firefox(#3843)
+ // So we won't rely on the native value
+ this.timeStamp = now();
+
+ // Mark it as fixed
+ this[expando] = true;
+};
+
+function returnFalse(){
+ return false;
+}
+function returnTrue(){
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if( !e )
+ return;
+ // if preventDefault exists run it on the original event
+ if (e.preventDefault)
+ e.preventDefault();
+ // otherwise set the returnValue property of the original event to false (IE)
+ e.returnValue = false;
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if( !e )
+ return;
+ // if stopPropagation exists run it on the original event
+ if (e.stopPropagation)
+ e.stopPropagation();
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation:function(){
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+ // Traverse up the tree
+ while ( parent && parent != this )
+ try { parent = parent.parentNode; }
+ catch(e) { parent = this; }
+
+ if( parent != this ){
+ // set the correct event type
+ event.type = event.data;
+ // handle event if we actually just moused on to a non sub-element
+ jQuery.event.handle.apply( this, arguments );
+ }
+};
+
+jQuery.each({
+ mouseover: 'mouseenter',
+ mouseout: 'mouseleave'
+}, function( orig, fix ){
+ jQuery.event.special[ fix ] = {
+ setup: function(){
+ jQuery.event.add( this, orig, withinElement, fix );
+ },
+ teardown: function(){
+ jQuery.event.remove( this, orig, withinElement );
+ }
+ };
+});
+
+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 ) {
+ var one = jQuery.event.proxy( fn || data, function(event) {
+ jQuery(this).unbind(event, one);
+ return (fn || data).apply( this, arguments );
+ });
+ return this.each(function(){
+ jQuery.event.add( this, type, one, fn && data);
+ });
+ },
+
+ unbind: function( type, fn ) {
+ return this.each(function(){
+ jQuery.event.remove( this, type, fn );
+ });
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function(){
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+
+ triggerHandler: function( type, data ) {
+ if( this[0] ){
+ var event = jQuery.Event(type);
+ event.preventDefault();
+ event.stopPropagation();
+ jQuery.event.trigger( event, data, this[0] );
+ return event.result;
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while( i < args.length )
+ jQuery.event.proxy( fn, args[i++] );
+
+ return this.click( jQuery.event.proxy( fn, function(event) {
+ // Figure out which function to execute
+ this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+ // 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.mouseenter(fnOver).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( fn );
+
+ return this;
+ },
+
+ live: function( type, fn ){
+ var proxy = jQuery.event.proxy( fn );
+ proxy.guid += this.selector + type;
+
+ jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
+
+ return this;
+ },
+
+ die: function( type, fn ){
+ jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
+ return this;
+ }
+});
+
+function liveHandler( event ){
+ var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
+ stop = true,
+ elems = [];
+
+ jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
+ if ( check.test(fn.type) ) {
+ var elem = jQuery(event.target).closest(fn.data)[0];
+ if ( elem )
+ elems.push({ elem: elem, fn: fn });
+ }
+ });
+
+ elems.sort(function(a,b) {
+ return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
+ });
+
+ jQuery.each(elems, function(){
+ if ( this.fn.call(this.elem, event, this.fn.data) === false )
+ return (stop = false);
+ });
+
+ return stop;
+}
+
+function liveConvert(type, selector){
+ return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
+}
+
+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.call( document, jQuery );
+ });
+
+ // 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 and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", function(){
+ document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
+ jQuery.ready();
+ }, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent("onreadystatechange", function(){
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", arguments.callee );
+ jQuery.ready();
+ }
+ });
+
+ // If IE and not an iframe
+ // continually check to see if the document is ready
+ if ( document.documentElement.doScroll && window == 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();
+ })();
+ }
+
+ // 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,mouseenter,mouseleave," +
+ "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);
+ };
+});
+
+// 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(){
+ for ( var id in jQuery.cache )
+ // Skip the window
+ if ( id != 1 && jQuery.cache[ id ].handle )
+ jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+});
+(function(){
+
+ jQuery.support = {};
+
+ var root = document.documentElement,
+ script = document.createElement("script"),
+ div = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+
+ div.style.display = "none";
+ div.innerHTML = ' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';
+
+ var all = div.getElementsByTagName("*"),
+ a = div.getElementsByTagName("a")[0];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return;
+ }
+
+ jQuery.support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: div.firstChild.nodeType == 3,
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that you can get all elements in an <object> element
+ // IE 7 always returns no results
+ objectAll: !!div.getElementsByTagName("object")[0]
+ .getElementsByTagName("*").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText insted)
+ style: /red/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: a.getAttribute("href") === "/a",
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ opacity: a.style.opacity === "0.5",
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Will be defined later
+ scriptEval: false,
+ noCloneEvent: true,
+ boxModel: null
+ };
+
+ script.type = "text/javascript";
+ try {
+ script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+ } catch(e){}
+
+ root.insertBefore( script, root.firstChild );
+
+ // Make sure that the execution of code works by injecting a script
+ // tag with appendChild/createTextNode
+ // (IE doesn't support this, fails, and uses .text instead)
+ if ( window[ id ] ) {
+ jQuery.support.scriptEval = true;
+ delete window[ id ];
+ }
+
+ root.removeChild( script );
+
+ if ( div.attachEvent && div.fireEvent ) {
+ div.attachEvent("onclick", function(){
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ jQuery.support.noCloneEvent = false;
+ div.detachEvent("onclick", arguments.callee);
+ });
+ div.cloneNode(true).fireEvent("onclick");
+ }
+
+ // Figure out if the W3C box model works as expected
+ // document.body must exist before we can do this
+ jQuery(function(){
+ var div = document.createElement("div");
+ div.style.width = div.style.paddingLeft = "1px";
+
+ document.body.appendChild( div );
+ jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+ document.body.removeChild( div ).style.display = 'none';
+ });
+})();
+
+var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";
+
+jQuery.props = {
+ "for": "htmlFor",
+ "class": "className",
+ "float": styleFloat,
+ cssFloat: styleFloat,
+ styleFloat: styleFloat,
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ rowspan: "rowSpan",
+ tabindex: "tabIndex"
+};
+jQuery.fn.extend({
+ // Keep a copy of the old load
+ _load: jQuery.fn.load,
+
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" )
+ return this._load( url );
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ // 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 if( typeof params === "object" ) {
+ 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 );
+
+ if( callback )
+ self.each( callback, [res.responseText, status, res] );
+ }
+ });
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ (this.checked || /select|textarea/i.test(this.nodeName) ||
+ /text|hidden|password|search/i.test(this.type));
+ })
+ .map(function(i, elem){
+ var val = jQuery(this).val();
+ return val == null ? null :
+ jQuery.isArray(val) ?
+ 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 = now();
+
+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: {
+ url: location.href,
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ username: null,
+ password: null,
+ */
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+ // This function can be overriden by calling jQuery.ajaxSetup
+ xhr:function(){
+ return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+ },
+ 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 ) {
+ // 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));
+
+ var jsonp, jsre = /=\?(&|$)/g, status, data,
+ type = s.type.toUpperCase();
+
+ // 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 ( type == "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 && type == "GET" ) {
+ var ts = now();
+ // 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 && type == "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" );
+
+ // Matches an absolute URL, and saves the domain
+ var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType == "script" && type == "GET" && parts
+ && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){
+
+ 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();
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+ head.removeChild( script );
+ }
+ };
+ }
+
+ head.appendChild(script);
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object
+ var xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if( s.username )
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ else
+ xhr.open(type, s.url, s.async);
+
+ // 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 )
+ xhr.setRequestHeader("Content-Type", s.contentType);
+
+ // Set the If-Modified-Since header, if ifModified mode.
+ if ( s.ifModified )
+ xhr.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
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+ s.accepts[ s.dataType ] + ", */*" :
+ s.accepts._default );
+ } catch(e){}
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+ // Wait for a response to come back
+ var onreadystatechange = function(isTimeout){
+ // The request was aborted, clear the interval and decrement jQuery.active
+ if (xhr.readyState == 0) {
+ if (ival) {
+ // clear poll interval
+ clearInterval(ival);
+ ival = null;
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ // The transfer is complete and the data is available, or the request timed out
+ } else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+ requestDone = true;
+
+ // clear poll interval
+ if (ival) {
+ clearInterval(ival);
+ ival = null;
+ }
+
+ status = isTimeout == "timeout" ? "timeout" :
+ !jQuery.httpSuccess( xhr ) ? "error" :
+ s.ifModified && jQuery.httpNotModified( xhr, 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( xhr, s.dataType, s );
+ } 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 = xhr.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, xhr, status);
+
+ // Fire the complete handlers
+ complete();
+
+ if ( isTimeout )
+ xhr.abort();
+
+ // Stop memory leaks
+ if ( s.async )
+ xhr = 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 ( xhr && !requestDone )
+ onreadystatechange( "timeout" );
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send(s.data);
+ } catch(e) {
+ jQuery.handleError(s, xhr, 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", [xhr, s] );
+ }
+
+ function complete(){
+ // Process result
+ if ( s.complete )
+ s.complete(xhr, status);
+
+ // The request was completed
+ if ( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) s.error( xhr, status, e );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol == "file:" ||
+ ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
+ } catch(e){}
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ try {
+ var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+ // Firefox always returns 200. check Last-Modified date
+ return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
+ } catch(e){}
+ return false;
+ },
+
+ httpData: function( xhr, type, s ) {
+ var ct = xhr.getResponseHeader("content-type"),
+ xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.tagName == "parsererror" )
+ throw "parsererror";
+
+ // Allow a pre-filtering function to sanitize the response
+ // s != null is checked to keep backwards compatibility
+ if( s && s.dataFilter )
+ data = s.dataFilter( data, type );
+
+ // The filter can actually parse the response
+ if( typeof data === "string" ){
+
+ // 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 = window["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 = [ ];
+
+ function add( key, value ){
+ s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+ };
+
+ // If an array was passed in, assume that it is an array
+ // of form elements
+ if ( jQuery.isArray(a) || a.jquery )
+ // Serialize the form elements
+ jQuery.each( a, function(){
+ add( this.name, 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 ( jQuery.isArray(a[j]) )
+ jQuery.each( a[j], function(){
+ add( j, this );
+ });
+ else
+ add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
+
+ // Return the resulting serialization
+ return s.join("&").replace(/%20/g, "+");
+ }
+
+});
+var elemdisplay = {},
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ];
+
+function genFx( type, num ){
+ var obj = {};
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
+ obj[ this ] = type;
+ });
+ return obj;
+}
+
+jQuery.fn.extend({
+ show: function(speed,callback){
+ if ( speed ) {
+ return this.animate( genFx("show", 3), speed, callback);
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ){
+ var old = jQuery.data(this[i], "olddisplay");
+
+ this[i].style.display = old || "";
+
+ if ( jQuery.css(this[i], "display") === "none" ) {
+ var tagName = this[i].tagName, display;
+
+ if ( elemdisplay[ tagName ] ) {
+ display = elemdisplay[ tagName ];
+ } else {
+ var elem = jQuery("<" + tagName + " />").appendTo("body");
+
+ display = elem.css("display");
+ if ( display === "none" )
+ display = "block";
+
+ elem.remove();
+
+ elemdisplay[ tagName ] = display;
+ }
+
+ jQuery.data(this[i], "olddisplay", display);
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var i = 0, l = this.length; i < l; i++ ){
+ this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
+ }
+
+ return this;
+ }
+ },
+
+ hide: function(speed,callback){
+ if ( speed ) {
+ return this.animate( genFx("hide", 3), speed, callback);
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ){
+ var old = jQuery.data(this[i], "olddisplay");
+ if ( !old && old !== "none" )
+ jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var i = 0, l = this.length; i < l; i++ ){
+ this[i].style.display = "none";
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ){
+ var bool = typeof fn === "boolean";
+
+ return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+ this._toggle.apply( this, arguments ) :
+ fn == null || bool ?
+ this.each(function(){
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ }) :
+ this.animate(genFx("toggle", 3), fn, fn2);
+ },
+
+ 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(){
+
+ var opt = jQuery.extend({}, optall), p,
+ hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
+ self = this;
+
+ for ( p in prop ) {
+ if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+ return opt.complete.call(this);
+
+ if ( ( p == "height" || p == "width" ) && this.style ) {
+ // 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;
+ });
+ },
+
+ 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;
+ }
+
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show", 1),
+ slideUp: genFx("hide", 1),
+ slideToggle: genFx("toggle", 1),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" }
+}, function( name, props ){
+ jQuery.fn[ name ] = function( speed, callback ){
+ return this.animate( props, speed, callback );
+ };
+});
+
+jQuery.extend({
+
+ speed: function(speed, easing, fn) {
+ var opt = typeof speed === "object" ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function(){
+ if ( opt.queue !== false )
+ jQuery(this).dequeue();
+ if ( jQuery.isFunction( opt.old ) )
+ opt.old.call( 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: [],
+
+ 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.call( 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 )
+ this.elem.style.display = "block";
+ },
+
+ // Get the current size
+ cur: function(force){
+ if ( this.elem[this.prop] != null && (!this.elem.style || 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 = now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ var self = this;
+ function t(gotoEnd){
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ 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( timerId );
+ timerId = undefined;
+ }
+ }, 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
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
+
+ // 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 = now();
+
+ 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 )
+ jQuery(this.elem).hide();
+
+ // 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]);
+
+ // Execute the complete function
+ this.options.complete.call( 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.extend( jQuery.fx, {
+ speeds:{
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+ step: {
+
+ opacity: function(fx){
+ jQuery.attr(fx.elem.style, "opacity", fx.now);
+ },
+
+ _default: function(fx){
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ else
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+});
+if ( document.documentElement["getBoundingClientRect"] )
+ jQuery.fn.offset = function() {
+ if ( !this[0] ) return { top: 0, left: 0 };
+ if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+ var box = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
+ clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ top = box.top + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop,
+ left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+ return { top: top, left: left };
+ };
+else
+ jQuery.fn.offset = function() {
+ if ( !this[0] ) return { top: 0, left: 0 };
+ if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+ jQuery.offset.initialized || jQuery.offset.initialize();
+
+ var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
+ doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+ body = doc.body, defaultView = doc.defaultView,
+ prevComputedStyle = defaultView.getComputedStyle(elem, null),
+ top = elem.offsetTop, left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ computedStyle = defaultView.getComputedStyle(elem, null);
+ top -= elem.scrollTop, left -= elem.scrollLeft;
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop, left += elem.offsetLeft;
+ if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
+ top += parseInt( computedStyle.borderTopWidth, 10) || 0,
+ left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+ prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
+ }
+ if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
+ top += parseInt( computedStyle.borderTopWidth, 10) || 0,
+ left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
+ top += body.offsetTop,
+ left += body.offsetLeft;
+
+ if ( prevComputedStyle.position === "fixed" )
+ top += Math.max(docElem.scrollTop, body.scrollTop),
+ left += Math.max(docElem.scrollLeft, body.scrollLeft);
+
+ return { top: top, left: left };
+ };
+
+jQuery.offset = {
+ initialize: function() {
+ if ( this.initialized ) return;
+ var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
+ html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
+
+ rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
+ for ( prop in rules ) container.style[prop] = rules[prop];
+
+ container.innerHTML = html;
+ body.insertBefore(container, body.firstChild);
+ innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;
+
+ this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+ this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+ innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
+ this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+ body.style.marginTop = '1px';
+ this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
+ body.style.marginTop = bodyMarginTop;
+
+ body.removeChild(container);
+ this.initialized = true;
+ },
+
+ bodyOffset: function(body) {
+ jQuery.offset.initialized || jQuery.offset.initialize();
+ var top = body.offsetTop, left = body.offsetLeft;
+ if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
+ top += parseInt( jQuery.curCSS(body, 'marginTop', true), 10 ) || 0,
+ left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
+ return { top: top, left: left };
+ }
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ var left = 0, top = 0, results;
+
+ if ( this[0] ) {
+ // Get *real* offsetParent
+ var offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= num( this, 'marginTop' );
+ offset.left -= num( this, 'marginLeft' );
+
+ // Add offsetParent borders
+ parentOffset.top += num( offsetParent, 'borderTopWidth' );
+ parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+ // Subtract the two offsets
+ results = {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ }
+
+ return results;
+ },
+
+ offsetParent: function() {
+ var offsetParent = this[0].offsetParent || document.body;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+ offsetParent = offsetParent.offsetParent;
+ return jQuery(offsetParent);
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+ var method = 'scroll' + name;
+
+ jQuery.fn[ method ] = function(val) {
+ if (!this[0]) return null;
+
+ return val !== undefined ?
+
+ // Set the scroll offset
+ this.each(function() {
+ this == window || this == document ?
+ window.scrollTo(
+ !i ? val : jQuery(window).scrollLeft(),
+ i ? val : jQuery(window).scrollTop()
+ ) :
+ this[ method ] = val;
+ }) :
+
+ // Return the scroll offset
+ this[0] == window || this[0] == document ?
+ self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+ jQuery.boxModel && document.documentElement[ method ] ||
+ document.body[ method ] :
+ this[0][ method ];
+ };
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+ var tl = i ? "Left" : "Top", // top or left
+ br = i ? "Right" : "Bottom", // bottom or right
+ lower = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function(){
+ return this[0] ?
+ jQuery.css( this[0], lower, false, "padding" ) :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function(margin) {
+ return this[0] ?
+ jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
+ null;
+ };
+
+ var type = name.toLowerCase();
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ return this[0] == window ?
+ // 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(
+ document.documentElement["client" + name],
+ document.body["scroll" + name], document.documentElement["scroll" + name],
+ 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, typeof size === "string" ? size : size + "px" );
+ };
+
+});
+})();
diff --git a/etherpad/src/static/js/json2.js b/etherpad/src/static/js/json2.js
new file mode 100644
index 0000000..32988c2
--- /dev/null
+++ b/etherpad/src/static/js/json2.js
@@ -0,0 +1,498 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+/*
+ http://www.JSON.org/json2.js
+ 2008-09-01
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the object holding the key.
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array, then it will be used to
+ select the members to be serialized. It filters the results such
+ that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*global JSON */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
+ charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
+ getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
+ parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ JSON = {};
+}
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ // APPJET: escape all characters except non-control 7-bit ASCII (changed cx and escapeable)
+ var cx = /[\u0000-\u001f\u007f-\uffff]/g,
+ escapeable = /[\\\"\u0000-\u001f\u007f-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapeable.lastIndex = 0;
+ return escapeable.test(string) ?
+ '"' + string.replace(escapeable, function (a) {
+ var c = meta[a];
+ if (typeof c === 'string') {
+ return c;
+ }
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// If the object has a dontEnum length property, we'll treat it as an array.
+
+ if (typeof value.length === 'number' &&
+ !value.propertyIsEnumerable('length')) {
+
+// The object is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = window['ev'+'al']('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+})();
diff --git a/etherpad/src/static/js/lib/jquery.contextmenu.js b/etherpad/src/static/js/lib/jquery.contextmenu.js
new file mode 100644
index 0000000..42cc17e
--- /dev/null
+++ b/etherpad/src/static/js/lib/jquery.contextmenu.js
@@ -0,0 +1,284 @@
+/**
+ * Copyright (c)2005-2009 Matt Kruse (javascripttoolbox.com)
+ *
+ * Dual licensed under the MIT and GPL licenses.
+ * This basically means you can use this code however you want for
+ * free, but don't claim to have written it yourself!
+ * Donations always accepted: http://www.JavascriptToolbox.com/donate/
+ *
+ * Please do not link to the .js files on javascripttoolbox.com from
+ * your site. Copy the files locally to your server instead.
+ *
+ */
+/**
+ * jquery.contextmenu.js
+ * jQuery Plugin for Context Menus
+ * http://www.JavascriptToolbox.com/lib/contextmenu/
+ *
+ * Copyright (c) 2008 Matt Kruse (javascripttoolbox.com)
+ * Dual licensed under the MIT and GPL licenses.
+ *
+ * @version 1.0
+ * @history 1.0 2008-10-20 Initial Release
+ * @todo slideUp doesn't work in IE - because of iframe?
+ * @todo Hide all other menus when contextmenu is shown?
+ * @todo More themes
+ * @todo Nested context menus
+ */
+;(function($){
+ $.contextMenu = {
+ shadow:true,
+ shadowOffset:0,
+ shadowOffsetX:5,
+ shadowOffsetY:5,
+ shadowWidthAdjust:-3,
+ shadowHeightAdjust:-3,
+ shadowOpacity:.2,
+ shadowClass:'context-menu-shadow',
+ shadowColor:'black',
+
+ offsetX:0,
+ offsetY:0,
+ appendTo:'body',
+ direction:'down',
+ constrainToScreen:true,
+
+ showTransition:'show',
+ hideTransition:'hide',
+ showSpeed:'',
+ hideSpeed:'',
+ showCallback:null,
+ hideCallback:null,
+
+ className:'context-menu',
+ itemClassName:'context-menu-item',
+ itemHoverClassName:'context-menu-item-hover',
+ disabledItemClassName:'context-menu-item-disabled',
+ disabledItemHoverClassName:'context-menu-item-disabled-hover',
+ separatorClassName:'context-menu-separator',
+ innerDivClassName:'context-menu-item-inner',
+ themePrefix:'context-menu-theme-',
+ theme:'default',
+
+ separator:'context-menu-separator', // A specific key to identify a separator
+ target:null, // The target of the context click, to be populated when triggered
+ menu:null, // The jQuery object containing the HTML object that is the menu itself
+ shadowObj:null, // Shadow object
+ bgiframe:null, // The iframe object for IE6
+ shown:false, // Currently being shown?
+ useIframe:/*@cc_on @*//*@if (@_win32) true, @else @*/false,/*@end @*/ // This is a better check than looking at userAgent!
+
+ bindTarget: 'contextmenu',
+
+ // Create the menu instance
+ create: function(menu,opts) {
+ var cmenu = $.extend({},this,opts); // Clone all default properties to created object
+
+ // If a selector has been passed in, then use that as the menu
+ if (typeof menu=="string") {
+ cmenu.menu = $(menu);
+ }
+ // If a function has been passed in, call it each time the menu is shown to create the menu
+ else if (typeof menu=="function") {
+ cmenu.menuFunction = menu;
+ }
+ // Otherwise parse the Array passed in
+ else {
+ cmenu.menu = cmenu.createMenu(menu,cmenu);
+ }
+ if (cmenu.menu) {
+ cmenu.menu.css({display:'none'});
+ $(cmenu.appendTo).append(cmenu.menu);
+ }
+
+ // Create the shadow object if shadow is enabled
+ if (cmenu.shadow) {
+ cmenu.createShadow(cmenu); // Extracted to method for extensibility
+ if (cmenu.shadowOffset) { cmenu.shadowOffsetX = cmenu.shadowOffsetY = cmenu.shadowOffset; }
+ }
+
+ // when to hide the menu:
+ // click anywhere in the body:
+ $('body').bind('click',function(){cmenu.hide();});
+ // right-click anywhere in the body:
+ $('body').bind('contextmenu',function(){cmenu.hide();});
+ // escape key:
+ $(document).keyup(function(e) { if (e.keyCode == 27) { cmenu.hide(); } });
+
+ return cmenu;
+ },
+
+ // Create an iframe object to go behind the menu
+ createIframe: function() {
+ return $('<iframe frameborder="0" tabindex="-1" src="javascript:false" style="display:block;position:absolute;z-index:-1;filter:Alpha(Opacity=0);"/>');
+ },
+
+ // Accept an Array representing a menu structure and turn it into HTML
+ createMenu: function(menu,cmenu) {
+ var className = cmenu.className;
+ $.each(cmenu.theme.split(","),function(i,n){className+=' '+cmenu.themePrefix+n});
+ var $t = $('<table cellspacing=0 cellpadding=0></table>').click(function(){cmenu.hide(); return false;}); // We wrap a table around it so width can be flexible
+ var $tr = $('<tr></tr>');
+ var $td = $('<td></td>');
+ var $div = $('<div class="'+className+'"></div>');
+
+ // Each menu item is specified as either:
+ // title:function
+ // or title: { property:value ... }
+ for (var i=0; i<menu.length; i++) {
+ var m = menu[i];
+ if (m==$.contextMenu.separator) {
+ $div.append(cmenu.createSeparator());
+ }
+ else {
+ for (var opt in menu[i]) {
+ $div.append(cmenu.createMenuItem(opt,menu[i][opt])); // Extracted to method for extensibility
+ }
+ }
+ }
+ if ( cmenu.useIframe ) {
+ $td.append(cmenu.createIframe());
+ }
+ $t.append($tr.append($td.append($div)))
+ return $t;
+ },
+
+ // Create an individual menu item
+ createMenuItem: function(label,obj) {
+ var cmenu = this;
+ if (typeof obj=="function") { obj={onclick:obj}; } // If passed a simple function, turn it into a property of an object
+ // Default properties, extended in case properties are passed
+ var o = $.extend({
+ onclick:function() { },
+ className:'',
+ hoverClassName:cmenu.itemHoverClassName,
+ icon:'',
+ disabled:false,
+ title:'',
+ hoverItem:cmenu.hoverItem,
+ hoverItemOut:cmenu.hoverItemOut
+ },obj);
+ // If an icon is specified, hard-code the background-image style. Themes that don't show images should take this into account in their CSS
+ var iconStyle = (o.icon)?'background-image:url('+o.icon+');':'';
+ var $div = $('<div class="'+cmenu.itemClassName+' '+o.className+((o.disabled)?' '+cmenu.disabledItemClassName:'')+'" title="'+o.title+'"></div>')
+ // If the item is disabled, don't do anything when it is clicked
+ .click(function(e){if(cmenu.isItemDisabled(this)){return false;}else{return o.onclick.call(cmenu.target,this,cmenu,e)}})
+ // Change the class of the item when hovered over
+ .hover( function(){ o.hoverItem.call(this,(cmenu.isItemDisabled(this))?cmenu.disabledItemHoverClassName:o.hoverClassName); }
+ ,function(){ o.hoverItemOut.call(this,(cmenu.isItemDisabled(this))?cmenu.disabledItemHoverClassName:o.hoverClassName); }
+ );
+ var $idiv = $('<div class="'+cmenu.innerDivClassName+'" style="'+iconStyle+'">'+label+'</div>');
+ $div.append($idiv);
+ return $div;
+ },
+
+ // Create a separator row
+ createSeparator: function() {
+ return $('<div class="'+this.separatorClassName+'"></div>');
+ },
+
+ // Determine if an individual item is currently disabled. This is called each time the item is hovered or clicked because the disabled status may change at any time
+ isItemDisabled: function(item) { return $(item).is('.'+this.disabledItemClassName); },
+
+ // Functions to fire on hover. Extracted to methods for extensibility
+ hoverItem: function(c) { $(this).addClass(c); },
+ hoverItemOut: function(c) { $(this).removeClass(c); },
+
+ // Create the shadow object
+ createShadow: function(cmenu) {
+ cmenu.shadowObj = $('<div class="'+cmenu.shadowClass+'"></div>').css( {display:'none',position:"absolute", zIndex:9998, opacity:cmenu.shadowOpacity, backgroundColor:cmenu.shadowColor } );
+ $(cmenu.appendTo).append(cmenu.shadowObj);
+ },
+
+ // Display the shadow object, given the position of the menu itself
+ showShadow: function(x,y,e) {
+ var cmenu = this;
+ if (cmenu.shadow) {
+ cmenu.shadowObj.css( {
+ width:(cmenu.menu.width()+cmenu.shadowWidthAdjust)+"px",
+ height:(cmenu.menu.height()+cmenu.shadowHeightAdjust)+"px",
+ top:(y+cmenu.shadowOffsetY)+"px",
+ left:(x+cmenu.shadowOffsetX)+"px"
+ }).addClass(cmenu.shadowClass)[cmenu.showTransition](cmenu.showSpeed);
+ }
+ },
+
+ // A hook to call before the menu is shown, in case special processing needs to be done.
+ // Return false to cancel the default show operation
+ beforeShow: function() { return true; },
+
+ // Show the context menu
+ show: function(t,e) {
+ var cmenu=this, x=e.pageX, y=e.pageY;
+ cmenu.target = t; // Preserve the object that triggered this context menu so menu item click methods can see it
+ if (cmenu.beforeShow()!==false) {
+ // If the menu content is a function, call it to populate the menu each time it is displayed
+ if (cmenu.menuFunction) {
+ if (cmenu.menu) { $(cmenu.menu).remove(); }
+ cmenu.menu = cmenu.createMenu(cmenu.menuFunction(cmenu,t),cmenu);
+ cmenu.menu.css({display:'none'});
+ $(cmenu.appendTo).append(cmenu.menu);
+ }
+ var $c = cmenu.menu;
+ x+=cmenu.offsetX; y+=cmenu.offsetY;
+ var pos = cmenu.getPosition(x,y,cmenu,e); // Extracted to method for extensibility
+ cmenu.showShadow(pos.x,pos.y,e);
+ // Resize the iframe if needed
+ if (cmenu.useIframe) {
+ $c.find('iframe').css({width:$c.width()+cmenu.shadowOffsetX+cmenu.shadowWidthAdjust,height:$c.height()+cmenu.shadowOffsetY+cmenu.shadowHeightAdjust});
+ }
+ $c.css( {top:pos.y+"px", left:pos.x+"px", position:"absolute",zIndex:9999} )[cmenu.showTransition](cmenu.showSpeed,((cmenu.showCallback)?function(){cmenu.showCallback.call(cmenu);}:null));
+ cmenu.shown=true;
+ $(document).one('click',null,function(){cmenu.hide()}); // Handle a single click to the document to hide the menu
+ }
+ },
+
+ // Find the position where the menu should appear, given an x,y of the click event
+ getPosition: function(clickX,clickY,cmenu,e) {
+ var x = clickX+cmenu.offsetX;
+ var y = clickY+cmenu.offsetY
+ var h = $(cmenu.menu).height();
+ var w = $(cmenu.menu).width();
+ var dir = cmenu.direction;
+ if (cmenu.constrainToScreen) {
+ var $w = $(window);
+ var wh = $w.height();
+ var ww = $w.width();
+ if (dir=="down" && (y+h-$w.scrollTop() > wh)) { dir = "up"; }
+ var maxRight = x+w-$w.scrollLeft();
+ if (maxRight > ww) { x -= (maxRight-ww); }
+ }
+ if (dir=="up") { y -= h; }
+ return {'x':x,'y':y};
+ },
+
+ // Hide the menu, of course
+ hide: function() {
+ var cmenu=this;
+ if (cmenu.shown) {
+ if (cmenu.iframe) {
+ $(cmenu.iframe).hide();
+ }
+ if (cmenu.menu) {
+ cmenu.menu[cmenu.hideTransition](cmenu.hideSpeed,null);
+ }
+ if (cmenu.shadow) {
+ cmenu.shadowObj[cmenu.hideTransition](cmenu.hideSpeed);
+ }
+ if (cmenu.hideCallback) {
+ cmenu.hideCallback.call(cmenu);
+ }
+ }
+ cmenu.shown = false;
+ }
+ };
+
+ // This actually adds the .contextMenu() function to the jQuery namespace
+ $.fn.contextMenu = function(menu,options) {
+ var cmenu = $.contextMenu.create(menu,options);
+ return this.each(function(){
+ $(this).bind(cmenu.bindTarget,function(e){cmenu.show(this,e);return false;});
+ });
+ };
+})(jQuery);
+
diff --git a/etherpad/src/static/js/pad2.js b/etherpad/src/static/js/pad2.js
new file mode 100644
index 0000000..14ac762
--- /dev/null
+++ b/etherpad/src/static/js/pad2.js
@@ -0,0 +1,591 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+/* global $, window */
+
+$(document).ready(function() {
+ pad.init();
+});
+
+$(window).unload(function() {
+ pad.dispose();
+});
+
+var pad = {
+ // don't access these directly from outside this file, except
+ // for debugging
+ collabClient: null,
+ myUserInfo: null,
+ diagnosticInfo: {},
+ initTime: 0,
+ clientTimeOffset: (+new Date()) - clientVars.serverTimestamp,
+ preloadedImages: false,
+ padOptions: {},
+ resizeInited: false,
+
+ // these don't require init; clientVars should all go through here
+ getPadId: function() { return clientVars.padId; },
+ getClientIp: function() { return clientVars.clientIp; },
+ getIsProPad: function() { return clientVars.isProPad; },
+ getColorPalette: function() { return clientVars.colorPalette; },
+ getDisplayUserAgent: function() {
+ return padutils.uaDisplay(clientVars.userAgent);
+ },
+ getIsDebugEnabled: function() { return clientVars.debugEnabled; },
+ getPrivilege: function(name) { return clientVars.accountPrivs[name]; },
+ getUserIsGuest: function() { return clientVars.userIsGuest; },
+ //
+
+ getUserId: function() { return pad.myUserInfo.userId; },
+ getUserName: function() { return pad.myUserInfo.name; },
+ sendClientMessage: function(msg) {
+ pad.collabClient.sendClientMessage(msg);
+ },
+
+ initResize: function() {
+ $(window).bind("resize", pad.resizePage);
+ pad.resizeInited = true;
+ pad.resizePage();
+ // just in case, periodically check size:
+ setInterval(function() { pad.resizePage(); }, 2000);
+ },
+ init: function() {
+ pad.diagnosticInfo.uniqueId = padutils.uniqueId();
+ pad.initTime = +(new Date());
+ pad.padOptions = clientVars.initialOptions;
+
+ if ((! $.browser.msie) &&
+ (! ($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) {
+ document.domain = document.domain; // for comet
+ }
+
+ // for IE
+ if ($.browser.msie) {
+ try {
+ doc.execCommand("BackgroundImageCache", false, true);
+ } catch (e) {}
+ }
+
+ // order of inits is important here:
+
+ padcookie.init(clientVars.cookiePrefsToSet);
+
+ $("#widthprefcheck").click(pad.toggleWidthPref);
+ $("#sidebarcheck").click(pad.toggleSidebar);
+
+ pad.myUserInfo = {
+ userId: clientVars.userId,
+ name: clientVars.userName,
+ ip: pad.getClientIp(),
+ colorId: clientVars.userColor,
+ userAgent: pad.getDisplayUserAgent()
+ };
+ if (clientVars.specialKey) {
+ pad.myUserInfo.specialKey = clientVars.specialKey;
+ if (clientVars.specialKeyTranslation) {
+ $("#specialkeyarea").html("mode: "+
+ String(clientVars.specialKeyTranslation).toUpperCase());
+ }
+ }
+ paddocbar.init({isTitleEditable: pad.getIsProPad(),
+ initialTitle:clientVars.initialTitle,
+ initialPassword:clientVars.initialPassword,
+ guestPolicy: pad.padOptions.guestPolicy
+ });
+ padimpexp.init();
+ padsavedrevs.init(clientVars.initialRevisionList);
+
+ padeditor.init(postAceInit, pad.padOptions.view || {});
+ sidebarSplit.init();
+ pad.initResize();
+
+ paduserlist.init(pad.myUserInfo);
+ padchat.init(clientVars.chatHistory, pad.myUserInfo);
+ padconnectionstatus.init();
+ padmodals.init();
+
+ pad.collabClient =
+ getCollabClient(padeditor.ace,
+ clientVars.collab_client_vars,
+ pad.myUserInfo,
+ { colorPalette: pad.getColorPalette() });
+ pad.collabClient.setOnUserJoin(pad.handleUserJoin);
+ pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate);
+ pad.collabClient.setOnUserLeave(pad.handleUserLeave);
+ pad.collabClient.setOnClientMessage(pad.handleClientMessage);
+ pad.collabClient.setOnServerMessage(pad.handleServerMessage);
+ pad.collabClient.setOnChannelStateChange(pad.handleChannelStateChange);
+ pad.collabClient.setOnInternalAction(pad.handleCollabAction);
+
+ function postAceInit() {
+ padeditbar.init();
+ setTimeout(function() { padeditor.ace.focus(); }, 0);
+ }
+
+ pad.resizePage();
+ },
+ dispose: function() {
+ padeditor.dispose();
+ },
+ resizePage: function() {
+ if (! pad.resizeInited) {
+ return;
+ }
+ // requires padeditor and sidebarSplit
+ var pageHeight = $(window).height();
+ if ($("#djs").length > 0) {
+ pageHeight -= $("#djs").outerHeight();
+ }
+ var MIN_PAGE_HEIGHT = 400;
+ if (pageHeight < MIN_PAGE_HEIGHT) {
+ pageHeight = MIN_PAGE_HEIGHT;
+ }
+ var bottomAreaHeight = 28;
+ padeditor.setBottom(pageHeight - bottomAreaHeight);
+ sidebarSplit.setBottom(pageHeight - bottomAreaHeight);
+ paddocbar.handleResizePage();
+ padmodals.relayoutWithBottom(pageHeight);
+ pad.handleWidthChange();
+ },
+ notifyChangeName: function(newName) {
+ pad.myUserInfo.name = newName;
+ pad.collabClient.updateUserInfo(pad.myUserInfo);
+ padchat.handleUserJoinOrUpdate(pad.myUserInfo);
+ },
+ notifyChangeColor: function(newColorId) {
+ pad.myUserInfo.colorId = newColorId;
+ pad.collabClient.updateUserInfo(pad.myUserInfo);
+ padchat.handleUserJoinOrUpdate(pad.myUserInfo);
+ },
+ notifyChangeTitle: function(newTitle) {
+ pad.collabClient.sendClientMessage({
+ type: 'padtitle',
+ title: newTitle,
+ changedBy: pad.myUserInfo.name || "unnamed"
+ });
+ },
+ notifyChangePassword: function(newPass) {
+ pad.collabClient.sendClientMessage({
+ type: 'padpassword',
+ password: newPass,
+ changedBy: pad.myUserInfo.name || "unnamed"
+ });
+ },
+ changePadOption: function(key, value) {
+ var options = {};
+ options[key] = value;
+ pad.handleOptionsChange(options);
+ pad.collabClient.sendClientMessage({
+ type: 'padoptions',
+ options: options,
+ changedBy: pad.myUserInfo.name || "unnamed"
+ });
+ },
+ changeViewOption: function(key, value) {
+ var options = {view: {}};
+ options.view[key] = value;
+ pad.handleOptionsChange(options);
+ pad.collabClient.sendClientMessage({
+ type: 'padoptions',
+ options: options,
+ changedBy: pad.myUserInfo.name || "unnamed"
+ });
+ },
+ handleOptionsChange: function(opts) {
+ // opts object is a full set of options or just
+ // some options to change
+ if (opts.view) {
+ if (! pad.padOptions.view) {
+ pad.padOptions.view = {};
+ }
+ for(var k in opts.view) {
+ pad.padOptions.view[k] = opts.view[k];
+ }
+ padeditor.setViewOptions(pad.padOptions.view);
+ }
+ if (opts.guestPolicy) {
+ // order important here
+ pad.padOptions.guestPolicy = opts.guestPolicy;
+ paddocbar.setGuestPolicy(opts.guestPolicy);
+ }
+ },
+ getPadOptions: function() {
+ // caller shouldn't mutate the object
+ return pad.padOptions;
+ },
+ isPadPublic: function() {
+ return (! pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow');
+ },
+ suggestUserName: function(userId, name) {
+ pad.collabClient.sendClientMessage({
+ type: 'suggestUserName',
+ unnamedId: userId,
+ newName: name
+ });
+ },
+ handleUserJoin: function(userInfo) {
+ paduserlist.userJoinOrUpdate(userInfo);
+ padchat.handleUserJoinOrUpdate(userInfo);
+ },
+ handleUserUpdate: function(userInfo) {
+ paduserlist.userJoinOrUpdate(userInfo);
+ padchat.handleUserJoinOrUpdate(userInfo);
+ },
+ handleUserLeave: function(userInfo) {
+ paduserlist.userLeave(userInfo);
+ padchat.handleUserLeave(userInfo);
+ },
+ handleClientMessage: function(msg) {
+ if (msg.type == 'suggestUserName') {
+ if (msg.unnamedId == pad.myUserInfo.userId && msg.newName &&
+ ! pad.myUserInfo.name) {
+ pad.notifyChangeName(msg.newName);
+ paduserlist.setMyUserInfo(pad.myUserInfo);
+ }
+ }
+ else if (msg.type == 'chat') {
+ padchat.receiveChat(msg);
+ }
+ else if (msg.type == 'padtitle') {
+ paddocbar.changeTitle(msg.title);
+ }
+ else if (msg.type == 'padpassword') {
+ paddocbar.changePassword(msg.password);
+ }
+ else if (msg.type == 'newRevisionList') {
+ padsavedrevs.newRevisionList(msg.revisionList);
+ }
+ else if (msg.type == 'revisionLabel') {
+ padsavedrevs.newRevisionList(msg.revisionList);
+ }
+ else if (msg.type == 'padoptions') {
+ var opts = msg.options;
+ pad.handleOptionsChange(opts);
+ }
+ else if (msg.type == 'guestanswer') {
+ // someone answered a prompt, remove it
+ paduserlist.removeGuestPrompt(msg.guestId);
+ }
+ },
+ editbarClick: function(cmd) {
+ if (padeditbar) {
+ padeditbar.toolbarClick(cmd);
+ }
+ },
+ dmesg: function(m) {
+ if (pad.getIsDebugEnabled()) {
+ var djs = $('#djs').get(0);
+ var wasAtBottom = (djs.scrollTop - (djs.scrollHeight - $(djs).height())
+ >= -20);
+ $('#djs').append('<p>'+m+'</p>');
+ if (wasAtBottom) {
+ djs.scrollTop = djs.scrollHeight;
+ }
+ }
+ },
+ handleServerMessage: function(m) {
+ if (m.type == 'NOTICE') {
+ if (m.text) {
+ alertBar.displayMessage(function (abar) {
+ abar.find("#servermsgdate").html(" ("+padutils.simpleDateTime(new Date)+")");
+ abar.find("#servermsgtext").html(m.text);
+ });
+ }
+ if (m.js) {
+ window['ev'+'al'](m.js);
+ }
+ }
+ else if (m.type == 'GUEST_PROMPT') {
+ paduserlist.showGuestPrompt(m.userId, m.displayName);
+ }
+ },
+ handleChannelStateChange: function(newState, message) {
+ var oldFullyConnected = !! padconnectionstatus.isFullyConnected();
+ var wasConnecting = (padconnectionstatus.getStatus().what == 'connecting');
+ if (newState == "CONNECTED") {
+ padconnectionstatus.connected();
+ }
+ else if (newState == "RECONNECTING") {
+ padconnectionstatus.reconnecting();
+ }
+ else if (newState == "DISCONNECTED") {
+ pad.diagnosticInfo.disconnectedMessage = message;
+ pad.diagnosticInfo.padInitTime = pad.initTime;
+ pad.asyncSendDiagnosticInfo();
+ if (typeof window.ajlog == "string") { window.ajlog += ("Disconnected: "+message+'\n'); }
+ padeditor.disable();
+ padeditbar.disable();
+ paddocbar.disable();
+ padimpexp.disable();
+
+ padconnectionstatus.disconnected(message);
+ }
+ var newFullyConnected = !! padconnectionstatus.isFullyConnected();
+ if (newFullyConnected != oldFullyConnected) {
+ pad.handleIsFullyConnected(newFullyConnected, wasConnecting);
+ }
+ },
+ handleIsFullyConnected: function(isConnected, isInitialConnect) {
+ // load all images referenced from CSS, one at a time,
+ // starting one second after connection is first established.
+ if (isConnected && ! pad.preloadedImages) {
+ window.setTimeout(function() {
+ if (! pad.preloadedImages) {
+ pad.preloadImages();
+ pad.preloadedImages = true;
+ }
+ }, 1000);
+ }
+
+ padsavedrevs.handleIsFullyConnected(isConnected);
+
+ pad.determineSidebarVisibility(isConnected && ! isInitialConnect);
+ },
+ determineSidebarVisibility: function(asNowConnectedFeedback) {
+ if (pad.isFullyConnected()) {
+ var setSidebarVisibility =
+ padutils.getCancellableAction(
+ "set-sidebar-visibility",
+ function() {
+ $("body").toggleClass('hidesidebar',
+ !! padcookie.getPref('hideSidebar'));
+ });
+ window.setTimeout(setSidebarVisibility,
+ asNowConnectedFeedback ? 3000 : 0);
+ }
+ else {
+ padutils.cancelActions("set-sidebar-visibility");
+ $("body").removeClass('hidesidebar');
+ }
+ pad.resizePage();
+ },
+ handleCollabAction: function(action) {
+ if (action == "commitPerformed") {
+ padeditbar.setSyncStatus("syncing");
+ }
+ else if (action == "newlyIdle") {
+ padeditbar.setSyncStatus("done");
+ }
+ },
+ hideServerMessage: function() {
+ alertBar.hideMessage();
+ },
+ asyncSendDiagnosticInfo: function() {
+ pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo();
+ window.setTimeout(function() {
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/connection-diagnostic-info',
+ data: {padId: pad.getPadId(), diagnosticInfo: JSON.stringify(pad.diagnosticInfo)},
+ success: function() {},
+ error: function() {}
+ });
+ }, 0);
+ },
+ forceReconnect: function() {
+ $('form#reconnectform input.padId').val(pad.getPadId());
+ pad.diagnosticInfo.collabDiagnosticInfo = pad.collabClient.getDiagnosticInfo();
+ $('form#reconnectform input.diagnosticInfo').val(JSON.stringify(pad.diagnosticInfo));
+ $('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges()));
+ $('form#reconnectform').submit();
+ },
+ toggleWidthPref: function() {
+ var newValue = ! padcookie.getPref('fullWidth');
+ padcookie.setPref('fullWidth', newValue);
+ $("#widthprefcheck").toggleClass('widthprefchecked', !!newValue).toggleClass(
+ 'widthprefunchecked', !newValue);
+ pad.handleWidthChange();
+ },
+ toggleSidebar: function() {
+ var newValue = ! padcookie.getPref('hideSidebar');
+ padcookie.setPref('hideSidebar', newValue);
+ $("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass(
+ 'sidebarunchecked', !!newValue);
+ pad.determineSidebarVisibility();
+ },
+ handleWidthChange: function() {
+ var isFullWidth = padcookie.getPref('fullWidth');
+ if (isFullWidth) {
+ $("body").addClass('fullwidth').removeClass('limwidth').removeClass(
+ 'squish1width').removeClass('squish2width');
+ }
+ else {
+ $("body").addClass('limwidth').removeClass('fullwidth');
+
+ var pageWidth = $(window).width();
+ $("body").toggleClass('squish1width', (pageWidth < 912 && pageWidth > 812)).toggleClass(
+ 'squish2width', (pageWidth <= 812));
+ }
+ },
+ // this is called from code put into a frame from the server:
+ handleImportExportFrameCall: function(callName, varargs) {
+ padimpexp.handleFrameCall.call(padimpexp, callName,
+ Array.prototype.slice.call(arguments, 1));
+ },
+ callWhenNotCommitting: function(f) {
+ pad.collabClient.callWhenNotCommitting(f);
+ },
+ getCollabRevisionNumber: function() {
+ return pad.collabClient.getCurrentRevisionNumber();
+ },
+ isFullyConnected: function() {
+ return padconnectionstatus.isFullyConnected();
+ },
+ addHistoricalAuthors: function(data) {
+ if (! pad.collabClient) {
+ window.setTimeout(function() { pad.addHistoricalAuthors(data); },
+ 1000);
+ }
+ else {
+ pad.collabClient.addHistoricalAuthors(data);
+ }
+ },
+ preloadImages: function() {
+ var images = [
+ '/static/img/jun09/pad/feedbackbox2.gif',
+ '/static/img/jun09/pad/sharebox4.gif',
+ '/static/img/jun09/pad/sharedistri.gif',
+ '/static/img/jun09/pad/colorpicker.gif',
+ '/static/img/jun09/pad/docbarstates.png',
+ '/static/img/jun09/pad/overlay.png'
+ ];
+ function loadNextImage() {
+ if (images.length == 0) {
+ return;
+ }
+ var img = new Image();
+ img.src = images.shift();
+ if (img.complete) {
+ scheduleLoadNextImage();
+ }
+ else {
+ $(img).bind('error load onreadystatechange', scheduleLoadNextImage);
+ }
+ }
+ function scheduleLoadNextImage() {
+ window.setTimeout(loadNextImage, 0);
+ }
+ scheduleLoadNextImage();
+ }
+};
+
+var sidebarSplit = (function(){
+ var MIN_SIZED_BOX_HEIGHT = 75;
+
+ function relayout(heightDelta) {
+ heightDelta = (heightDelta || 0);
+
+ var sizedBox1 = $("#otherusers");
+ var sizedBox2 = $("#chatlines");
+ var height1 = sizedBox1.height();
+ var height2 = sizedBox2.height();
+ var newTotalHeight = height1 + height2 + heightDelta;
+ var newHeight1 = height1;
+ var newHeight2 = height2;
+
+ if (newTotalHeight >= MIN_SIZED_BOX_HEIGHT*2) {
+ // room for both panes to be at least min height
+ if (newTotalHeight >= self.desiredUsersBoxHeight + MIN_SIZED_BOX_HEIGHT) {
+ // room for users pane to be desiredUsersBoxHeight
+ newHeight1 = self.desiredUsersBoxHeight;
+ newHeight2 = newTotalHeight - newHeight1;
+ }
+ else {
+ newHeight2 = MIN_SIZED_BOX_HEIGHT;
+ newHeight1 = newTotalHeight - newHeight2;
+ }
+ }
+ else {
+ newHeight1 = Math.round(newTotalHeight/2);
+ newHeight2 = newTotalHeight - newHeight1;
+ }
+
+ sizedBox1.height(newHeight1);
+ sizedBox2.height(newHeight2);
+
+ $("#connectionbox").height(
+ $("#myuser").outerHeight() + $("#userlistbuttonarea").outerHeight() +
+ height1
+ );
+ }
+
+ var self = {
+ desiredUsersBoxHeight: MIN_SIZED_BOX_HEIGHT,
+ init: function() {
+ self.desiredUsersBoxHeight = Math.max(
+ $("#otherusers").height(), MIN_SIZED_BOX_HEIGHT);
+ makeDraggable($("#hdraggie"), function(eType, evt, state) {
+ if (eType == 'dragstart') {
+ state.startY = evt.pageY;
+ state.startHeight = $("#otherusers").height();
+ }
+ else if (eType == 'dragupdate') {
+ var newHeight = state.startHeight + (evt.pageY - state.startY);
+ if (newHeight < MIN_SIZED_BOX_HEIGHT) {
+ newHeight = MIN_SIZED_BOX_HEIGHT;
+ }
+ self.desiredUsersBoxHeight = newHeight;
+ relayout();
+ }
+ });
+ },
+ setBottom: function(bottomPx) {
+ var curBottom = $("#padsidebar").offset().top + $("#padsidebar").height();
+ var deltaBottom = bottomPx - curBottom;
+
+ if (deltaBottom != 0) {
+ relayout(deltaBottom);
+ }
+ }
+ };
+ return self;
+}());
+
+var alertBar = (function() {
+
+ var animator = padutils.makeShowHideAnimator(arriveAtAnimationState, false, 25, 400);
+
+ function arriveAtAnimationState(state) {
+ if (state == -1) {
+ $("#alertbar").css('opacity', 0).css('display', 'block');
+ pad.resizePage();
+ }
+ else if (state == 0) {
+ $("#alertbar").css('opacity', 1);
+ }
+ else if (state == 1) {
+ $("#alertbar").css('opacity', 0).css('display', 'none');
+ pad.resizePage();
+ }
+ else if (state < 0) {
+ $("#alertbar").css('opacity', state+1);
+ }
+ else if (state > 0) {
+ $("#alertbar").css('opacity', 1 - state);
+ }
+ }
+
+ var self = {
+ displayMessage: function(setupFunc) {
+ animator.show();
+ setupFunc($("#alertbar"));
+ },
+ hideMessage: function() {
+ animator.hide();
+ }
+ };
+ return self;
+}());
diff --git a/etherpad/src/static/js/pad_chat.js b/etherpad/src/static/js/pad_chat.js
new file mode 100644
index 0000000..35903c2
--- /dev/null
+++ b/etherpad/src/static/js/pad_chat.js
@@ -0,0 +1,295 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padchat = (function(){
+
+ var numToAuthorMap = [''];
+ var authorColorArray = [null];
+ var authorToNumMap = {};
+ var chatLinesByDay = []; // {day:'2009-06-17', lines: [...]}
+ var oldestHistoricalLine = 0;
+
+ var loadingMoreHistory = false;
+ var HISTORY_LINES_TO_LOAD_AT_A_TIME = 50;
+
+ function authorToNum(author, dontAddIfAbsent) {
+ if ((typeof authorToNumMap[author]) == "number") {
+ return authorToNumMap[author];
+ }
+ else if (dontAddIfAbsent) {
+ return -1;
+ }
+ else {
+ var n = numToAuthorMap.length;
+ numToAuthorMap.push(author);
+ authorToNumMap[author] = n;
+ return n;
+ }
+ }
+ function getDateNumCSSDayString(dateNum) {
+ var d = new Date(+dateNum);
+ var year = String(d.getFullYear());
+ var month = ("0"+String(d.getMonth()+1)).slice(-2);
+ var day = ("0"+String(d.getDate())).slice(-2);
+ return year+"-"+month+"-"+day;
+ }
+ function getDateNumHumanDayString(dateNum) {
+ var d = new Date(+dateNum);
+ var monthName = (["January", "February", "March",
+ "April", "May", "June", "July", "August", "September",
+ "October", "November", "December"])[d.getMonth()];
+ var dayOfMonth = d.getDate();
+ var year = d.getFullYear();
+ return monthName+" "+dayOfMonth+", "+year;
+ }
+ function ensureChatDay(time) {
+ var day = getDateNumCSSDayString(time);
+ var dayIndex = padutils.binarySearch(chatLinesByDay.length, function(n) {
+ return chatLinesByDay[n].day >= day;
+ });
+ if (dayIndex >= chatLinesByDay.length ||
+ chatLinesByDay[dayIndex].day != day) {
+ // add new day to chat display!
+
+ chatLinesByDay.splice(dayIndex, 0, {day: day, lines: []});
+ var dayHtml = '<div class="chatday" id="chatday'+day+'">'+
+ '<h2 class="dayheader">'+getDateNumHumanDayString(time)+
+ '</h2></div>';
+ var dayDivs = $("#chatlines .chatday");
+ if (dayIndex == dayDivs.length) {
+ $("#chatlines").append(dayHtml);
+ }
+ else {
+ dayDivs.eq(dayIndex).before(dayHtml);
+ }
+ }
+
+ return dayIndex;
+ }
+ function addChatLine(userId, time, name, lineText, addBefore) {
+ var dayIndex = ensureChatDay(time);
+ var dayDiv = $("#chatday"+getDateNumCSSDayString(time));
+ var d = new Date(+time);
+ var hourmin = d.getHours()+":"+("0"+d.getMinutes()).slice(-2);
+ var nameHtml;
+ if (name) {
+ nameHtml = padutils.escapeHtml(name);
+ }
+ else {
+ nameHtml = "<i>unnamed</i>";
+ }
+ var chatlineClass = "chatline";
+ if (userId) {
+ var authorNum = authorToNum(userId);
+ chatlineClass += " chatauthor"+authorNum;
+ }
+ var textHtml = padutils.escapeHtmlWithClickableLinks(lineText, '_blank');
+ var lineNode = $('<div class="'+chatlineClass+'">'+
+ '<span class="chatlinetime">'+hourmin+' </span>'+
+ '<span class="chatlinename">'+nameHtml+': </span>'+
+ '<span class="chatlinetext">'+textHtml+'</span></div>');
+ var linesArray = chatLinesByDay[dayIndex].lines;
+ var lineObj = {userId:userId, time:time, name:name, lineText:lineText};
+ if (addBefore) {
+ dayDiv.find("h2").after(lineNode);
+ linesArray.splice(0, 0, lineObj);
+ }
+ else {
+ dayDiv.append(lineNode);
+ linesArray.push(lineObj);
+ }
+ if (userId) {
+ var color = getAuthorCSSColor(userId);
+ if (color) {
+ lineNode.css('background', color);
+ }
+ }
+
+ return {lineNode:lineNode};
+ }
+ function receiveChatHistoryBlock(block) {
+ for(var a in block.historicalAuthorData) {
+ var data = block.historicalAuthorData[a];
+ var n = authorToNum(a);
+ if (! authorColorArray[n]) {
+ // no data about this author, use historical info
+ authorColorArray[n] = { colorId: data.colorId, faded: true };
+ }
+ }
+
+ oldestHistoricalLine = block.start;
+
+ var lines = block.lines;
+ for(var i=lines.length-1; i>=0; i--) {
+ var line = lines[i];
+ addChatLine(line.userId, line.time, line.name, line.lineText, true);
+ }
+
+ if (oldestHistoricalLine > 0) {
+ $("a#chatloadmore").css('display', 'block');
+ }
+ else {
+ $("a#chatloadmore").css('display', 'none');
+ }
+ }
+ function fadeColor(colorCSS) {
+ var color = colorutils.css2triple(colorCSS);
+ color = colorutils.blend(color, [1,1,1], 0.5);
+ return colorutils.triple2css(color);
+ }
+ function getAuthorCSSColor(author) {
+ var n = authorToNum(author, true);
+ if (n < 0) {
+ return '';
+ }
+ else {
+ var cdata = authorColorArray[n];
+ if (! cdata) {
+ return '';
+ }
+ else {
+ var c = pad.getColorPalette()[cdata.colorId];
+ if (cdata.faded) {
+ c = fadeColor(c);
+ }
+ return c;
+ }
+ }
+ }
+ function changeAuthorColorData(author, cdata) {
+ var n = authorToNum(author);
+ authorColorArray[n] = cdata;
+ var cssColor = getAuthorCSSColor(author);
+ if (cssColor) {
+ $("#chatlines .chatauthor"+n).css('background',cssColor);
+ }
+ }
+
+ function sendChat() {
+ var lineText = $("#chatentrybox").val();
+ if (lineText) {
+ $("#chatentrybox").val('').focus();
+ var msg = {
+ type: 'chat',
+ userId: pad.getUserId(),
+ lineText: lineText,
+ senderName: pad.getUserName(),
+ authId: pad.getUserId()
+ };
+ pad.sendClientMessage(msg);
+ self.receiveChat(msg);
+ self.scrollToBottom();
+ }
+ }
+
+ var self = {
+ init: function(chatHistoryBlock, initialUserInfo) {
+ ensureChatDay(+new Date); // so that current date shows up right away
+
+ $("a#chatloadmore").click(self.loadMoreHistory);
+
+ self.handleUserJoinOrUpdate(initialUserInfo);
+ receiveChatHistoryBlock(chatHistoryBlock);
+
+ padutils.bindEnterAndEscape($("#chatentrybox"), function(evt) {
+ // return/enter
+ sendChat();
+ }, null);
+
+ self.scrollToBottom();
+ },
+ receiveChat: function(msg) {
+ var box = $("#chatlines").get(0);
+ var wasAtBottom = (box.scrollTop -
+ (box.scrollHeight - $(box).height()) >= -5);
+ addChatLine(msg.userId, +new Date, msg.senderName, msg.lineText, false);
+ if (wasAtBottom) {
+ window.setTimeout(function() {
+ self.scrollToBottom();
+ }, 0);
+ }
+ },
+ handleUserJoinOrUpdate: function(userInfo) {
+ changeAuthorColorData(userInfo.userId,
+ { colorId: userInfo.colorId, faded: false });
+ },
+ handleUserLeave: function(userInfo) {
+ changeAuthorColorData(userInfo.userId,
+ { colorId: userInfo.colorId, faded: true });
+ },
+ scrollToBottom: function() {
+ var box = $("#chatlines").get(0);
+ box.scrollTop = box.scrollHeight;
+ },
+ scrollToTop: function() {
+ var box = $("#chatlines").get(0);
+ box.scrollTop = 0;
+ },
+ loadMoreHistory: function() {
+ if (loadingMoreHistory) {
+ return;
+ }
+
+ var end = oldestHistoricalLine;
+ var start = Math.max(0, end - HISTORY_LINES_TO_LOAD_AT_A_TIME);
+ var padId = pad.getPadId();
+
+ loadingMoreHistory = true;
+ $("#padchat #chatloadmore").css('display', 'none');
+ $("#padchat #chatloadingmore").css('display', 'block');
+
+ $.ajax({
+ type: 'get',
+ url: '/ep/pad/chathistory',
+ data: { padId: padId, start: start, end: end },
+ success: success,
+ error: error
+ });
+
+ function success(text) {
+ notLoading();
+
+ var result = JSON.parse(text);
+
+ // try to keep scrolled to the same place...
+ var scrollBox = $("#chatlines").get(0);
+ var scrollDeterminer = function() { return 0; };
+ var topLine = $("#chatlines .chatday:first .chatline:first").children().eq(0);
+ if (topLine.length > 0) {
+ var posTop = topLine.position().top;
+ var scrollTop = scrollBox.scrollTop;
+ scrollDeterminer = function() {
+ var newPosTop = topLine.position().top;
+ return newPosTop + (scrollTop - posTop);
+ };
+ }
+ receiveChatHistoryBlock(result);
+
+ scrollBox.scrollTop = Math.max(0, Math.min(scrollBox.scrollHeight, scrollDeterminer()));
+ }
+ function error() {
+ notLoading();
+ }
+ function notLoading() {
+ loadingMoreHistory = false;
+ $("#padchat #chatloadmore").css('display', 'block');
+ $("#padchat #chatloadingmore").css('display', 'none');
+ }
+ }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_connectionstatus.js b/etherpad/src/static/js/pad_connectionstatus.js
new file mode 100644
index 0000000..cc06728
--- /dev/null
+++ b/etherpad/src/static/js/pad_connectionstatus.js
@@ -0,0 +1,63 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padconnectionstatus = (function() {
+
+ var status = {what: 'connecting'};
+
+ var showHideAnimator = padutils.makeShowHideAnimator(function(state) {
+ $("#connectionbox").css('opacity', 1 - Math.abs(state));
+ if (state == -1) {
+ $("#connectionbox").css('display', 'block');
+ }
+ else if (state == 1) {
+ $("#connectionbox").css('display', 'none');
+ }
+ }, true, 25, 200);
+
+ var self = {
+ init: function() {
+ $('button#forcereconnect').click(function() {
+ pad.forceReconnect();
+ });
+ },
+ connected: function() {
+ status = {what: 'connected'};
+ showHideAnimator.hide();
+ },
+ reconnecting: function() {
+ status = {what: 'reconnecting'};
+ $("#connectionbox").get(0).className = 'cboxreconnecting';
+ showHideAnimator.show();
+ },
+ disconnected: function(msg) {
+ status = {what: 'disconnected', why: msg};
+ var k = String(msg).toLowerCase(); // known reason why
+ if (!(k == 'userdup' || k == 'looping' || k == 'slowcommit' ||
+ k == 'initsocketfail' || k == 'unauth')) {
+ k = 'unknown';
+ }
+ var cls = 'cboxdisconnected cboxdisconnected_'+k;
+ $("#connectionbox").get(0).className = cls;
+ showHideAnimator.show();
+ },
+ isFullyConnected: function() {
+ return status.what == 'connected';
+ },
+ getStatus: function() { return status; }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_cookie.js b/etherpad/src/static/js/pad_cookie.js
new file mode 100644
index 0000000..3cc31ed
--- /dev/null
+++ b/etherpad/src/static/js/pad_cookie.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padcookie = (function(){
+ function getRawCookie() {
+ // returns null if can't get cookie text
+ if (! document.cookie) {
+ return null;
+ }
+ // look for (start of string OR semicolon) followed by whitespace followed by prefs=(something);
+ var regexResult = document.cookie.match(/(?:^|;)\s*prefs=([^;]*)(?:;|$)/);
+ if ((! regexResult) || (! regexResult[1])) {
+ return null;
+ }
+ return regexResult[1];
+ }
+ function setRawCookie(safeText) {
+ var expiresDate = new Date();
+ expiresDate.setFullYear(3000);
+ document.cookie = ('prefs='+safeText+';expires='+expiresDate.toGMTString());
+ }
+ function parseCookie(text) {
+ // returns null if can't parse cookie.
+
+ try {
+ var cookieData = JSON.parse(unescape(text));
+ return cookieData;
+ }
+ catch (e) {
+ return null;
+ }
+ }
+ function stringifyCookie(data) {
+ return escape(JSON.stringify(data));
+ }
+ function saveCookie() {
+ if (! inited) {
+ return;
+ }
+ setRawCookie(stringifyCookie(cookieData));
+
+ if (pad.getIsProPad() && (! getRawCookie()) && (! alreadyWarnedAboutNoCookies)) {
+ alert("Warning: it appears that your browser does not have cookies enabled."+
+ " EtherPad uses cookies to keep track of unique users for the purpose"+
+ " of putting a quota on the number of active users. Using EtherPad without "+
+ " cookies may fill up your server's user quota faster than expected.");
+ alreadyWarnedAboutNoCookies = true;
+ }
+ }
+
+ var wasNoCookie = true;
+ var cookieData = {};
+ var alreadyWarnedAboutNoCookies = false;
+ var inited = false;
+
+ var self = {
+ init: function(prefsToSet) {
+ var rawCookie = getRawCookie();
+ if (rawCookie) {
+ var cookieObj = parseCookie(rawCookie);
+ if (cookieObj) {
+ wasNoCookie = false; // there was a cookie
+ delete cookieObj.userId;
+ delete cookieObj.name;
+ delete cookieObj.colorId;
+ cookieData = cookieObj;
+ }
+ }
+
+ for(var k in prefsToSet) {
+ cookieData[k] = prefsToSet[k];
+ }
+
+ inited = true;
+ saveCookie();
+ },
+ wasNoCookie: function() { return wasNoCookie; },
+ getPref: function(prefName) {
+ return cookieData[prefName];
+ },
+ setPref: function(prefName, value) {
+ cookieData[prefName] = value;
+ saveCookie();
+ }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_docbar.js b/etherpad/src/static/js/pad_docbar.js
new file mode 100644
index 0000000..586b20f
--- /dev/null
+++ b/etherpad/src/static/js/pad_docbar.js
@@ -0,0 +1,347 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 paddocbar = (function() {
+ var isTitleEditable = false;
+ var isEditingTitle = false;
+ var isEditingPassword = false;
+ var enabled = false;
+
+ function getPanelOpenCloseAnimator(panelName, panelHeight) {
+ var wrapper = $("#"+panelName+"-wrapper");
+ var openingClass = "docbar"+panelName+"-opening";
+ var openClass = "docbar"+panelName+"-open";
+ var closingClass = "docbar"+panelName+"-closing";
+ function setPanelState(action) {
+ $("#docbar").removeClass(openingClass).removeClass(openClass).
+ removeClass(closingClass);
+ if (action != "closed") {
+ $("#docbar").addClass("docbar"+panelName+"-"+action);
+ }
+ }
+
+ function openCloseAnimate(state) {
+ function pow(x) { x = 1-x; x *= x*x; return 1-x; }
+
+ if (state == -1) {
+ // startng to open
+ setPanelState("opening");
+ wrapper.css('height', '0');
+ }
+ else if (state < 0) {
+ // opening
+ var height = Math.round(pow(state+1)*(panelHeight-1))+'px';
+ wrapper.css('height', height);
+ }
+ else if (state == 0) {
+ // open
+ setPanelState("open");
+ wrapper.css('height', panelHeight-1);
+ }
+ else if (state < 1) {
+ // closing
+ setPanelState("closing");
+ var height = Math.round((1-pow(state))*(panelHeight-1))+'px';
+ wrapper.css('height', height);
+ }
+ else if (state == 1) {
+ // closed
+ setPanelState("closed");
+ wrapper.css('height', '0');
+ }
+ }
+
+ return padutils.makeShowHideAnimator(openCloseAnimate, false, 25, 500);
+ }
+
+
+ var currentPanel = null;
+ function setCurrentPanel(newCurrentPanel) {
+ if (currentPanel != newCurrentPanel) {
+ currentPanel = newCurrentPanel;
+ padutils.cancelActions("hide-docbar-panel");
+ }
+ }
+ var panels;
+
+ function changePassword(newPass) {
+ if ((newPass || null) != (self.password || null)) {
+ self.password = (newPass || null);
+ pad.notifyChangePassword(newPass);
+ }
+ self.renderPassword();
+ }
+
+ var self = {
+ title: null,
+ password: null,
+ init: function(opts) {
+ panels = {
+ impexp: { animator: getPanelOpenCloseAnimator("impexp", 160) },
+ savedrevs: { animator: getPanelOpenCloseAnimator("savedrevs", 79) },
+ options: { animator: getPanelOpenCloseAnimator(
+ "options", 114) },
+ security: { animator: getPanelOpenCloseAnimator("security", 130) }
+ };
+
+ isTitleEditable = opts.isTitleEditable;
+ self.title = opts.initialTitle;
+ self.password = opts.initialPassword;
+
+ $("#docbarimpexp").click(function() {self.togglePanel("impexp");});
+ $("#docbarsavedrevs").click(function() {self.togglePanel("savedrevs");});
+ $("#docbaroptions").click(function() {self.togglePanel("options");});
+ $("#docbarsecurity").click(function() {self.togglePanel("security");});
+
+ $("#docbarrenamelink").click(self.editTitle);
+ $("#padtitlesave").click(function() { self.closeTitleEdit(true); });
+ $("#padtitlecancel").click(function() { self.closeTitleEdit(false); });
+ padutils.bindEnterAndEscape($("#padtitleedit"),
+ function() {
+ $("#padtitlesave").trigger('click'); },
+ function() {
+ $("#padtitlecancel").trigger('click'); });
+
+ $("#options-close").click(function() {self.setShownPanel(null);});
+ $("#security-close").click(function() {self.setShownPanel(null);});
+
+ if (pad.getIsProPad()) {
+ self.initPassword();
+ }
+
+ enabled = true;
+ self.render();
+
+ // public/private
+ $("#security-access input").bind("change click", function(evt) {
+ pad.changePadOption('guestPolicy',
+ $("#security-access input[name='padaccess']:checked").val());
+ });
+ self.setGuestPolicy(opts.guestPolicy);
+ },
+ setGuestPolicy: function(newPolicy) {
+ $("#security-access input[value='"+newPolicy+"']").attr("checked",
+ "checked");
+ self.render();
+ },
+ initPassword: function() {
+ self.renderPassword();
+ $("#password-clearlink").click(function() {
+ changePassword(null);
+ });
+ $("#password-setlink, #password-display").click(function() {
+ self.enterPassword();
+ });
+ $("#password-cancellink").click(function() {
+ self.exitPassword(false);
+ });
+ $("#password-savelink").click(function() {
+ self.exitPassword(true);
+ });
+ padutils.bindEnterAndEscape($("#security-passwordedit"),
+ function() {
+ self.exitPassword(true);
+ },
+ function() {
+ self.exitPassword(false);
+ });
+ },
+ enterPassword: function() {
+ isEditingPassword = true;
+ $("#security-passwordedit").val(self.password || '');
+ self.renderPassword();
+ $("#security-passwordedit").focus().select();
+ },
+ exitPassword: function(accept) {
+ isEditingPassword = false;
+ if (accept) {
+ changePassword($("#security-passwordedit").val());
+ }
+ else {
+ self.renderPassword();
+ }
+ },
+ renderPassword: function() {
+ if (isEditingPassword) {
+ $("#password-nonedit").hide();
+ $("#password-inedit").show();
+ }
+ else {
+ $("#password-nonedit").toggleClass('nopassword', ! self.password);
+ $("#password-setlink").html(self.password ? "Change..." : "Set...");
+ if (self.password) {
+ $("#password-display").html(self.password.replace(/./g, '&#8226;'));
+ }
+ else {
+ $("#password-display").html("None");
+ }
+ $("#password-inedit").hide();
+ $("#password-nonedit").show();
+ }
+ },
+ togglePanel: function(panelName) {
+ if (panelName in panels) {
+ if (currentPanel == panelName) {
+ self.setShownPanel(null);
+ }
+ else {
+ self.setShownPanel(panelName);
+ }
+ }
+ },
+ setShownPanel: function(panelName) {
+ function animateHidePanel(panelName, next) {
+ var delay = 0;
+ if (panelName == 'options' && isEditingPassword) {
+ // give user feedback that the password they've
+ // typed in won't actually take effect
+ self.exitPassword(false);
+ delay = 500;
+ }
+
+ window.setTimeout(function() {
+ panels[panelName].animator.hide();
+ if (next) {
+ next();
+ }
+ }, delay);
+ }
+
+ if (! panelName) {
+ if (currentPanel) {
+ animateHidePanel(currentPanel);
+ setCurrentPanel(null);
+ }
+ }
+ else if (panelName in panels) {
+ if (currentPanel != panelName) {
+ if (currentPanel) {
+ animateHidePanel(currentPanel, function() {
+ panels[panelName].animator.show();
+ setCurrentPanel(panelName);
+ });
+ }
+ else {
+ panels[panelName].animator.show();
+ setCurrentPanel(panelName);
+ }
+ }
+ }
+ },
+ isPanelShown: function(panelName) {
+ if (! panelName) {
+ return ! currentPanel;
+ }
+ else {
+ return (panelName == currentPanel);
+ }
+ },
+ changeTitle: function(newTitle) {
+ self.title = newTitle;
+ self.render();
+ },
+ editTitle: function() {
+ if (! enabled) {
+ return;
+ }
+ $("#padtitleedit").val(self.title);
+ isEditingTitle = true;
+ self.render();
+ $("#padtitleedit").focus().select();
+ },
+ closeTitleEdit: function(accept) {
+ if (! enabled) {
+ return;
+ }
+ if (accept) {
+ var newTitle = $("#padtitleedit").val();
+ if (newTitle) {
+ newTitle = newTitle.substring(0, 80);
+ self.title = newTitle;
+
+ pad.notifyChangeTitle(newTitle);
+ }
+ }
+
+ isEditingTitle = false;
+ self.render();
+ },
+ changePassword: function(newPass) {
+ if (newPass) {
+ self.password = newPass;
+ }
+ else {
+ self.password = null;
+ }
+ self.renderPassword();
+ },
+ render: function() {
+ if (isEditingTitle) {
+ $("#docbarpadtitle").hide();
+ $("#docbarrenamelink").hide();
+ $("#padtitleedit").show();
+ $("#padtitlebuttons").show();
+ if (! enabled) {
+ $("#padtitleedit").attr('disabled', 'disabled');
+ }
+ else {
+ $("#padtitleedit").removeAttr('disabled');
+ }
+ }
+ else {
+ $("#padtitleedit").hide();
+ $("#padtitlebuttons").hide();
+
+ var titleSpan = $("#docbarpadtitle span");
+ titleSpan.html(padutils.escapeHtml(self.title));
+ $("#docbarpadtitle").attr('title',
+ (pad.isPadPublic() ? "Public Pad: " : "")+
+ self.title);
+ $("#docbarpadtitle").show();
+
+ if (isTitleEditable) {
+ var titleRight = $("#docbarpadtitle").position().left +
+ $("#docbarpadtitle span").position().left +
+ Math.min($("#docbarpadtitle").width(),
+ $("#docbarpadtitle span").width());
+ $("#docbarrenamelink").css('left', titleRight + 10).show();
+ }
+
+ if (pad.isPadPublic()) {
+ $("#docbar").addClass("docbar-public");
+ }
+ else {
+ $("#docbar").removeClass("docbar-public");
+ }
+ }
+ },
+ disable: function() {
+ enabled = false;
+ self.render();
+ },
+ handleResizePage: function() {
+ padsavedrevs.handleResizePage();
+ },
+ hideLaterIfNoOtherInteraction: function() {
+ return padutils.getCancellableAction('hide-docbar-panel',
+ function() {
+ self.setShownPanel(null);
+ });
+ }
+ };
+ return self;
+}());
diff --git a/etherpad/src/static/js/pad_editbar.js b/etherpad/src/static/js/pad_editbar.js
new file mode 100644
index 0000000..34b774a
--- /dev/null
+++ b/etherpad/src/static/js/pad_editbar.js
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padeditbar = (function(){
+
+ var syncAnimation = (function() {
+ var SYNCING = -100;
+ var DONE = 100;
+ var state = DONE;
+ var fps = 25;
+ var step = 1/fps;
+ var T_START = -0.5;
+ var T_FADE = 1.0;
+ var T_GONE = 1.5;
+ var animator = padutils.makeAnimationScheduler(function() {
+ if (state == SYNCING || state == DONE) {
+ return false;
+ }
+ else if (state >= T_GONE) {
+ state = DONE;
+ $("#syncstatussyncing").css('display', 'none');
+ $("#syncstatusdone").css('display', 'none');
+ return false;
+ }
+ else if (state < 0) {
+ state += step;
+ if (state >= 0) {
+ $("#syncstatussyncing").css('display', 'none');
+ $("#syncstatusdone").css('display', 'block').css('opacity', 1);
+ }
+ return true;
+ }
+ else {
+ state += step;
+ if (state >= T_FADE) {
+ $("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
+ }
+ return true;
+ }
+ }, step*1000);
+ return {
+ syncing: function() {
+ state = SYNCING;
+ $("#syncstatussyncing").css('display', 'block');
+ $("#syncstatusdone").css('display', 'none');
+ },
+ done: function() {
+ state = T_START;
+ animator.scheduleAnimation();
+ }
+ };
+ }());
+
+ var self = {
+ init: function() {
+ $("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
+ $("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar");
+ },
+ isEnabled: function() {
+ return ! $("#editbar").hasClass('disabledtoolbar');
+ },
+ disable: function() {
+ $("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
+ },
+ toolbarClick: function(cmd) {
+ if (self.isEnabled()) {
+ if (cmd == 'save') {
+ padsavedrevs.saveNow();
+ }
+ else if (cmd == 'clearauthorship') {
+ padeditor.ace.execCommand('clearauthorship', function() {
+ if (window.confirm("Clear authorship colors on entire document?")) {
+ padeditor.ace.execCommand('clearauthorship');
+ }
+ });
+ }
+ else {
+ padeditor.ace.execCommand(cmd);
+ }
+ }
+ padeditor.ace.focus();
+ },
+ setSyncStatus: function(status) {
+ if (status == "syncing") {
+ syncAnimation.syncing();
+ }
+ else if (status == "done") {
+ syncAnimation.done();
+ }
+ }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_editor.js b/etherpad/src/static/js/pad_editor.js
new file mode 100644
index 0000000..f2fab26
--- /dev/null
+++ b/etherpad/src/static/js/pad_editor.js
@@ -0,0 +1,136 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padeditor = (function(){
+ var self = {
+ ace: null, // this is accessed directly from other files
+ viewZoom: 100,
+ init: function(readyFunc, initialViewOptions) {
+
+ function aceReady() {
+ $("#editorloadingbox").hide();
+ if (readyFunc) {
+ readyFunc();
+ }
+ }
+
+ self.ace = new Ace2Editor();
+ self.ace.init("editorcontainer", "", aceReady);
+ self.ace.setProperty("wraps", true);
+ if (pad.getIsDebugEnabled()) {
+ self.ace.setProperty("dmesg", pad.dmesg);
+ }
+ self.initViewOptions();
+ self.setViewOptions(initialViewOptions);
+
+ // view bar
+ self.initViewZoom();
+ $("#viewbarcontents").show();
+ },
+ initViewOptions: function() {
+ padutils.bindCheckboxChange($("#options-linenoscheck"), function() {
+ pad.changeViewOption('showLineNumbers',
+ padutils.getCheckbox($("#options-linenoscheck")));
+ });
+ padutils.bindCheckboxChange($("#options-colorscheck"), function() {
+ pad.changeViewOption('showAuthorColors',
+ padutils.getCheckbox("#options-colorscheck"));
+ });
+ $("#viewfontmenu").change(function() {
+ pad.changeViewOption('useMonospaceFont',
+ $("#viewfontmenu").val() == 'monospace');
+ });
+ },
+ setViewOptions: function(newOptions) {
+ function getOption(key, defaultValue) {
+ var value = String(newOptions[key]);
+ if (value == "true") return true;
+ if (value == "false") return false;
+ return defaultValue;
+ }
+ var v;
+
+ v = getOption('showLineNumbers', true);
+ self.ace.setProperty("showslinenumbers", v);
+ padutils.setCheckbox($("#options-linenoscheck"), v);
+
+ v = getOption('showAuthorColors', true);
+ self.ace.setProperty("showsauthorcolors", v);
+ padutils.setCheckbox($("#options-colorscheck"), v);
+
+ v = getOption('useMonospaceFont', false);
+ self.ace.setProperty("textface",
+ (v ? "monospace" : "Arial, sans-serif"));
+ $("#viewfontmenu").val(v ? "monospace" : "normal");
+ },
+ initViewZoom: function() {
+ var viewZoom = Number(padcookie.getPref('viewZoom'));
+ if ((! viewZoom) || isNaN(viewZoom)) {
+ viewZoom = 100;
+ }
+ self.setViewZoom(viewZoom);
+ $("#viewzoommenu").change(function(evt) {
+ // strip initial 'z' from val
+ self.setViewZoom(Number($("#viewzoommenu").val().substring(1)));
+ });
+ },
+ setViewZoom: function(percent) {
+ if (! (percent >= 50 && percent <= 1000)) {
+ // percent is out of sane range or NaN (which fails comparisons)
+ return;
+ }
+
+ self.viewZoom = percent;
+ $("#viewzoommenu").val('z'+percent);
+
+ var baseSize = 13;
+ self.ace.setProperty('textsize',
+ Math.round(baseSize * self.viewZoom / 100));
+
+ padcookie.setPref('viewZoom', percent);
+ },
+ dispose: function() {
+ if (self.ace) {
+ self.ace.destroy();
+ }
+ },
+ setBottom: function(bottomPx) {
+ var myTop = $("#padeditor").offset().top;
+ var myHeight = $("#padeditor").height();
+ var myBottom = myTop + myHeight;
+ var sizedBoxHeight = $("#editorcontainerbox").height();
+
+ var deltaBottom = bottomPx - myBottom;
+ if (deltaBottom != 0) {
+ $("#editorcontainerbox").height(sizedBoxHeight + deltaBottom);
+ }
+ self.ace.adjustSize();
+ },
+ disable: function() {
+ if (self.ace) {
+ self.ace.setProperty("grayedOut", true);
+ self.ace.setEditable(false);
+ }
+ },
+ restoreRevisionText: function(dataFromServer) {
+ pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
+ self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
+ }
+ };
+ return self;
+}());
+
diff --git a/etherpad/src/static/js/pad_impexp.js b/etherpad/src/static/js/pad_impexp.js
new file mode 100644
index 0000000..e928332
--- /dev/null
+++ b/etherpad/src/static/js/pad_impexp.js
@@ -0,0 +1,187 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padimpexp = (function() {
+
+ ///// import
+
+ var currentImportTimer = null;
+ var hidePanelCall = null;
+
+ function addImportFrames() {
+ $("#impexp-import .importframe").remove();
+ $('#impexp-import').append(
+ $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>'));
+ }
+ function fileInputUpdated() {
+ $('#importformfilediv').addClass('importformenabled');
+ $('#importsubmitinput').removeAttr('disabled');
+ $('#importmessagefail').fadeOut("fast");
+ $('#importarrow').show();
+ $('#importarrow').animate({paddingLeft:"0px"}, 500)
+ .animate({paddingLeft:"10px"}, 150, 'swing')
+ .animate({paddingLeft:"0px"}, 150, 'swing')
+ .animate({paddingLeft:"10px"}, 150, 'swing')
+ .animate({paddingLeft:"0px"}, 150, 'swing')
+ .animate({paddingLeft:"10px"}, 150, 'swing')
+ .animate({paddingLeft:"0px"}, 150, 'swing');
+ }
+ function fileInputSubmit() {
+ $('#importmessagefail').fadeOut("fast");
+ var ret = window.confirm(
+ "Importing a file will overwrite the current text of the pad."+
+ " Are you sure you want to proceed?");
+ if (ret) {
+ hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction();
+ currentImportTimer = window.setTimeout(function() {
+ if (! currentImportTimer) {
+ return;
+ }
+ currentImportTimer = null;
+ importFailed("Request timed out.");
+ }, 25000); // time out after some number of seconds
+ $('#importsubmitinput').attr({disabled: true}).val("Importing...");
+ window.setTimeout(function() {
+ $('#importfileinput').attr({disabled: true}); }, 0);
+ $('#importarrow').stop(true, true).hide();
+ $('#importstatusball').show();
+ }
+ return ret;
+ }
+ function importFailed(msg) {
+ importErrorMessage(msg);
+ importDone();
+ addImportFrames();
+ }
+ function importDone() {
+ $('#importsubmitinput').removeAttr('disabled').val("Import Now");
+ window.setTimeout(function() {
+ $('#importfileinput').removeAttr('disabled'); }, 0);
+ $('#importstatusball').hide();
+ importClearTimeout();
+ }
+ function importClearTimeout() {
+ if (currentImportTimer) {
+ window.clearTimeout(currentImportTimer);
+ currentImportTimer = null;
+ }
+ }
+ function importErrorMessage(msg) {
+ function showError(fade) {
+ $('#importmessagefail').html(
+ '<strong style="color: red">Import failed:</strong> '+
+ (msg || 'Please try a different file.'))[(fade?"fadeIn":"show")]();
+ }
+
+ if ($('#importexport .importmessage').is(':visible')) {
+ $('#importmessagesuccess').fadeOut("fast");
+ $('#importmessagefail').fadeOut("fast", function() {
+ showError(true); });
+ } else {
+ showError();
+ }
+ }
+ function importSuccessful(token) {
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/impexp/import2',
+ data: {token: token, padId: pad.getPadId()},
+ success: importApplicationSuccessful,
+ error: importApplicationFailed,
+ timeout: 25000
+ });
+ addImportFrames();
+ }
+ function importApplicationFailed(xhr, textStatus, errorThrown) {
+ importErrorMessage("Error during conversion.");
+ importDone();
+ }
+ function importApplicationSuccessful(data, textStatus) {
+ if (data.substr(0, 2) == "ok") {
+ if ($('#importexport .importmessage').is(':visible')) {
+ $('#importexport .importmessage').hide();
+ }
+ $('#importmessagesuccess').html(
+ '<strong style="color: green">Import successful!</strong>').show();
+ $('#importformfilediv').hide();
+ window.setTimeout(function() {
+ $('#importmessagesuccess').fadeOut("slow", function() {
+ $('#importformfilediv').show();
+ });
+ if (hidePanelCall) {
+ hidePanelCall();
+ }
+ }, 3000);
+ } else if (data.substr(0, 4) == "fail") {
+ importErrorMessage(
+ "Couldn't update pad contents. This can happen if your web browser has \"cookies\" disabled.");
+ } else if (data.substr(0, 4) == "msg:") {
+ importErrorMessage(data.substr(4));
+ }
+ importDone();
+ }
+
+ ///// export
+
+ function cantExport() {
+ var type = $(this);
+ if (type.hasClass("exporthrefpdf")) {
+ type = "PDF";
+ } else if (type.hasClass("exporthrefdoc")) {
+ type = "Microsoft Word";
+ } else if (type.hasClass("exporthrefodt")) {
+ type = "OpenDocument";
+ } else {
+ type = "this file";
+ }
+ alert("Exporting as "+type+" format is disabled. Please contact your"+
+ " system administrator for details.");
+ return false;
+ }
+
+ /////
+
+ var self = {
+ init: function() {
+ $("#impexp-close").click(function() {paddocbar.setShownPanel(null);});
+
+ addImportFrames();
+ $("#importfileinput").change(fileInputUpdated);
+ $('#importform').submit(fileInputSubmit);
+ $('.disabledexport').click(cantExport);
+ },
+ handleFrameCall: function(callName, argsArray) {
+ if (callName == 'importFailed') {
+ importFailed(argsArray[0]);
+ }
+ else if (callName == 'importSuccessful') {
+ importSuccessful(argsArray[0]);
+ }
+ },
+ disable: function() {
+ $("#impexp-disabled-clickcatcher").show();
+ $("#impexp-import").css('opacity', 0.5);
+ $("#impexp-export").css('opacity', 0.5);
+ },
+ enable: function() {
+ $("#impexp-disabled-clickcatcher").hide();
+ $("#impexp-import").css('opacity', 1);
+ $("#impexp-export").css('opacity', 1);
+ }
+ };
+ return self;
+}());
diff --git a/etherpad/src/static/js/pad_modals.js b/etherpad/src/static/js/pad_modals.js
new file mode 100644
index 0000000..c9f48b5
--- /dev/null
+++ b/etherpad/src/static/js/pad_modals.js
@@ -0,0 +1,364 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padmodals = (function() {
+
+ /*var clearFeedbackEmail = function() {};
+ function clearFeedback() {
+ clearFeedbackEmail();
+ $("#feedbackbox-message").val('');
+ }
+
+ var sendingFeedback = false;
+ function setSendingFeedback(v) {
+ v = !! v;
+ if (sendingFeedback != v) {
+ sendingFeedback = v;
+ if (v) {
+ $("#feedbackbox-send").css('opacity', 0.75);
+ }
+ else {
+ $("#feedbackbox-send").css('opacity', 1);
+ }
+ }
+ }*/
+
+ var sendingInvite = false;
+ function setSendingInvite(v) {
+ v = !! v;
+ if (sendingInvite != v) {
+ sendingInvite = v;
+ if (v) {
+ $("#sharebox-send").css('opacity', 0.75);
+ }
+ else {
+ $("#sharebox-send").css('opacity', 1);
+ }
+ }
+ }
+
+ var clearShareBoxTo = function() {};
+ function clearShareBox() {
+ clearShareBoxTo();
+ }
+
+ var allModals = $("#feedbackbox, #sharebox");
+
+ var shareboxExpanded = false;
+ var shareExpander = padutils.makeShowHideAnimator(function(state) {
+ function pow(x) { x = 1-x; x *= x*x; return 1-x; }
+ function ease(x) { return (x < 0) ? pow(x+1) : 1-pow(x); }
+ var smallHeight = 110 + (pad.getUserIsGuest() ? 0 : 50);
+ var bigHeight = 326 + (pad.getUserIsGuest() ? 0 : 50);
+ var boxHeight = (Math.abs(ease(state)))*(bigHeight-smallHeight) + smallHeight;
+ $("#sharebox").height(boxHeight);
+ // sharebox-open controls the disclosure triangle
+ // and also the visibility of any response (error/success)
+ // messages, which must be hidden during animation as well
+ // as when closed.
+ if (state == 0) {
+ $("#sharebox").addClass('sharebox-open');
+ }
+ else if (state > 0) {
+ $("#sharebox").removeClass('sharebox-open');
+ $("#sharebox-response").hide();
+ }
+ }, false, 20, 500);
+
+ //var overlayNode = null;
+
+ /*function adjustOverlay() {
+ if (overlayNode) {
+ var nodeWidth = overlayNode.width();
+ var nodeHeight = overlayNode.height();
+ var nodePos = overlayNode.position();
+ var outset = 10;
+ $("#modaloverlay").css({width: nodeWidth+outset*2,
+ height: nodeHeight+outset*2,
+ left: nodePos.left-outset,
+ top: nodePos.top-outset});
+ }
+ }*/
+
+ function showOverlay(forNode) {
+ //overlayNode = forNode;
+ //adjustOverlay();
+ $("#modaloverlay").show();
+ }
+ function hideOverlay() {
+ $("#modaloverlay").hide();
+ }
+
+ var self = {
+ shareExpander: shareExpander,
+ init: function() {
+ self.initFeedback();
+ self.initShareBox();
+ },
+ initFeedback: function() {
+ /*var emailField = $("#feedbackbox-email");
+ clearFeedbackEmail =
+ padutils.makeFieldLabeledWhenEmpty(emailField, '(your email address)').clear;
+ clearFeedback();*/
+
+ $("#feedbackbox-hide").click(function() {
+ self.hideModal();
+ });
+ /*$("#feedbackbox-send").click(function() {
+ self.sendFeedbackEmail();
+ });*/
+
+ $("#feedbackbutton").click(function() {
+ self.showFeedback();
+ });
+
+ $("#uservoicelinks a").click(function() {
+ self.hideModal();
+ return true;
+ });
+ $("#feedbackemails a").each(function() {
+ var node = $(this);
+ node.attr('href', "mailto:"+node.attr('href')+"@pad.spline.inf.fu-berlin.de");
+ });
+ },
+ initShareBox: function() {
+ $("#sharebutton, #nootherusers a").click(function() {
+ self.showShareBox();
+ });
+ $("#sharebox-hide").click(function() {
+ self.hideModal();
+ });
+ $("#sharebox-send").click(function() {
+ self.sendInvite();
+ });
+
+ $("#sharebox-url").click(function() {
+ $("#sharebox-url").focus().select();
+ });
+
+ clearShareBoxTo =
+ padutils.makeFieldLabeledWhenEmpty($("#sharebox-to"),
+ "(email addresses)").clear;
+ clearShareBox();
+
+ $("#sharebox-subject").val(self.getDefaultShareBoxSubjectForName(pad.getUserName()));
+ $("#sharebox-message").val(self.getDefaultShareBoxMessageForName(pad.getUserName()));
+
+ $("#sharebox-dislink").click(function() {
+ if (shareboxExpanded) {
+ shareboxExpanded = false;
+ shareExpander.hide();
+ }
+ else {
+ shareboxExpanded = true;
+ shareExpander.show();
+ }
+ });
+
+ $("#sharebox-stripe .setsecurity").click(function() {
+ self.hideModal();
+ paddocbar.setShownPanel('security');
+ });
+ },
+ getDefaultShareBoxMessageForName: function(name) {
+ return (name || "Somebody")+" has shared an EtherPad document with you."+
+ "\n\n"+"View it here:\n\n"+
+ padutils.escapeHtml($("#sharebox-url").val()+"\n");
+ },
+ getDefaultShareBoxSubjectForName: function(name) {
+ return (name || "Somebody")+" invited you to an EtherPad document";
+ },
+ relayoutWithBottom: function(px) {
+ $("#modaloverlay").height(px);
+ $("#sharebox").css('left',
+ Math.floor(($(window).width() -
+ $("#sharebox").outerWidth())/2));
+ $("#feedbackbox").css('left',
+ Math.floor(($(window).width() -
+ $("#feedbackbox").outerWidth())/2));
+ },
+ showFeedback: function() {
+ allModals.hide();
+ $("#feedbackbox").show();
+ showOverlay($("#feedbackbox"));
+ },
+ showShareBox: function() {
+ // when showing the dialog, if it still says "Somebody" invited you
+ // then we fill in the updated username if there is one;
+ // otherwise, we don't touch it, perhaps the user is happy with it
+ var msgbox = $("#sharebox-message");
+ if (msgbox.val() == self.getDefaultShareBoxMessageForName(null)) {
+ msgbox.val(self.getDefaultShareBoxMessageForName(pad.getUserName()));
+ }
+ var subjBox = $("#sharebox-subject");
+ if (subjBox.val() == self.getDefaultShareBoxSubjectForName(null)) {
+ subjBox.val(self.getDefaultShareBoxSubjectForName(pad.getUserName()));
+ }
+
+ if (pad.isPadPublic()) {
+ $("#sharebox-stripe").get(0).className = 'sharebox-stripe-public';
+ }
+ else {
+ $("#sharebox-stripe").get(0).className = 'sharebox-stripe-private';
+ }
+
+ allModals.hide();
+ $("#sharebox").show();
+ showOverlay($("#sharebox"));
+ $("#sharebox-url").focus().select();
+ },
+ hideModal: function() {
+ padutils.cancelActions('hide-feedbackbox');
+ padutils.cancelActions('hide-sharebox');
+ $("#sharebox-response").hide();
+ allModals.hide();
+ hideOverlay();
+ },
+ hideFeedbackLaterIfNoOtherInteraction: function() {
+ return padutils.getCancellableAction('hide-feedbackbox',
+ function() {
+ self.hideModal();
+ });
+ },
+ hideShareboxLaterIfNoOtherInteraction: function() {
+ return padutils.getCancellableAction('hide-sharebox',
+ function() {
+ self.hideModal();
+ });
+ },
+/* sendFeedbackEmail: function() {
+ if (sendingFeedback) {
+ return;
+ }
+ var message = $("#feedbackbox-message").val();
+ if (! message) {
+ return;
+ }
+ var email = ($("#feedbackbox-email").hasClass('editempty') ? '' :
+ $("#feedbackbox-email").val());
+ var padId = pad.getPadId();
+ var username = pad.getUserName();
+ setSendingFeedback(true);
+ $("#feedbackbox-response").html("Sending...").get(0).className = '';
+ $("#feedbackbox-response").show();
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/feedback',
+ data: {
+ feedback: message,
+ padId: padId,
+ username: username,
+ email: email
+ },
+ success: success,
+ error: error
+ });
+ var hideCall = self.hideFeedbackLaterIfNoOtherInteraction();
+ function success(msg) {
+ setSendingFeedback(false);
+ clearFeedback();
+ $("#feedbackbox-response").html("Thanks for your feedback").get(0).className = 'goodresponse';
+ $("#feedbackbox-response").show();
+ window.setTimeout(function() {
+ $("#feedbackbox-response").fadeOut('slow', function() {
+ hideCall();
+ });
+ }, 1500);
+ }
+ function error(e) {
+ setSendingFeedback(false);
+ $("#feedbackbox-response").html("Could not send feedback. Please email us at feedback"+"@"+"pad.spline.inf.fu-berlin.de instead.").get(0).className = 'badresponse';
+ $("#feedbackbox-response").show();
+ }
+ },*/
+ sendInvite: function() {
+ if (sendingInvite) {
+ return;
+ }
+ if (! pad.isFullyConnected()) {
+ displayErrorMessage("Error: Connection to the server is down or flaky.");
+ return;
+ }
+ var message = $("#sharebox-message").val();
+ if (! message) {
+ displayErrorMessage("Please enter a message body before sending.");
+ return;
+ }
+ var emails = ($("#sharebox-to").hasClass('editempty') ? '' :
+ $("#sharebox-to").val()) || '';
+ // find runs of characters that aren't obviously non-email punctuation
+ var emailArray = emails.match(/[^\s,:;<>\"\'\/\(\)\[\]{}]+/g) || [];
+ if (emailArray.length == 0) {
+ displayErrorMessage('Please enter at least one "To:" address.');
+ $("#sharebox-to").focus().select();
+ return;
+ }
+ for(var i=0;i<emailArray.length;i++) {
+ var addr = emailArray[i];
+ if (! addr.match(/^[\w\.\_\+\-]+\@[\w\_\-]+\.[\w\_\-\.]+$/)) {
+ displayErrorMessage('"'+padutils.escapeHtml(addr) +
+ '" does not appear to be a valid email address.');
+ return;
+ }
+ }
+ var subject = $("#sharebox-subject").val();
+ if (! subject) {
+ subject = self.getDefaultShareBoxSubjectForName(pad.getUserName());
+ $("#sharebox-subject").val(subject); // force the default subject
+ }
+
+ var padId = pad.getPadId();
+ var username = pad.getUserName();
+ setSendingInvite(true);
+ $("#sharebox-response").html("Sending...").get(0).className = '';
+ $("#sharebox-response").show();
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/emailinvite',
+ data: {
+ message: message,
+ toEmails: emailArray.join(','),
+ subject: subject,
+ username: username,
+ padId: padId
+ },
+ success: success,
+ error: error
+ });
+ var hideCall = self.hideShareboxLaterIfNoOtherInteraction();
+ function success(msg) {
+ setSendingInvite(false);
+ $("#sharebox-response").html("Email invitation sent!").get(0).className = 'goodresponse';
+ $("#sharebox-response").show();
+ window.setTimeout(function() {
+ $("#sharebox-response").fadeOut('slow', function() {
+ hideCall();
+ });
+ }, 1500);
+ }
+ function error(e) {
+ setSendingFeedback(false);
+ $("#sharebox-response").html("An error occurred; no email was sent.").get(0).className = 'badresponse';
+ $("#sharebox-response").show();
+ }
+ function displayErrorMessage(msgHtml) {
+ $("#sharebox-response").html(msgHtml).get(0).className = 'badresponse';
+ $("#sharebox-response").show();
+ }
+ }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_savedrevs.js b/etherpad/src/static/js/pad_savedrevs.js
new file mode 100644
index 0000000..ec06a1f
--- /dev/null
+++ b/etherpad/src/static/js/pad_savedrevs.js
@@ -0,0 +1,408 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padsavedrevs = (function() {
+
+ function reversedCopy(L) {
+ var L2 = L.slice();
+ L2.reverse();
+ return L2;
+ }
+
+ function makeRevisionBox(revisionInfo, rnum) {
+ var box = $('<div class="srouterbox">'+
+ '<div class="srinnerbox">'+
+ '<a href="javascript:void(0)" class="srname"><!-- --></a>'+
+ '<div class="sractions"><a class="srview" href="javascript:void(0)" target="_blank">view</a> | <a class="srrestore" href="javascript:void(0)">restore</a></div>'+
+ '<div class="srtime"><!-- --></div>'+
+ '<div class="srauthor"><!-- --></div>'+
+ '<img class="srtwirly" src="/static/img/misc/status-ball.gif">'+
+ '</div></div>');
+ setBoxLabel(box, revisionInfo.label);
+ setBoxTimestamp(box, revisionInfo.timestamp);
+ box.find(".srauthor").html("by "+padutils.escapeHtml(revisionInfo.savedBy));
+ var viewLink = '/ep/pad/view/'+pad.getPadId()+'/'+revisionInfo.id;
+ box.find(".srview").attr('href', viewLink);
+ var restoreLink = 'javascript:void padsavedrevs.restoreRevision('+rnum+');';
+ box.find(".srrestore").attr('href', restoreLink);
+ box.find(".srname").click(function(evt) {
+ editRevisionLabel(rnum, box);
+ });
+ return box;
+ }
+ function setBoxLabel(box, label) {
+ box.find(".srname").html(padutils.escapeHtml(label)).attr('title', label);
+ }
+ function setBoxTimestamp(box, timestamp) {
+ box.find(".srtime").html(padutils.escapeHtml(
+ padutils.timediff(new Date(timestamp))));
+ }
+ function getNthBox(n) {
+ return $("#savedrevisions .srouterbox").eq(n);
+ }
+ function editRevisionLabel(rnum, box) {
+ var input = $('<input type="text" class="srnameedit"/>');
+ box.find(".srnameedit").remove(); // just in case
+ var label = box.find(".srname");
+ input.width(label.width());
+ input.height(label.height());
+ input.css('top', label.position().top);
+ input.css('left', label.position().left);
+ label.after(input);
+ label.css('opacity', 0);
+ function endEdit() {
+ input.remove();
+ label.css('opacity', 1);
+ }
+ var rev = currentRevisionList[rnum];
+ var oldLabel = rev.label;
+ input.blur(function() {
+ var newLabel = input.val();
+ if (newLabel && newLabel != oldLabel) {
+ relabelRevision(rnum, newLabel);
+ }
+ endEdit();
+ });
+ input.val(rev.label).focus().select();
+ padutils.bindEnterAndEscape(input, function onEnter() {
+ input.blur();
+ }, function onEscape() {
+ input.val('').blur();
+ });
+ }
+ function relabelRevision(rnum, newLabel) {
+ var rev = currentRevisionList[rnum];
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/saverevisionlabel',
+ data: {userId: pad.getUserId(),
+ padId: pad.getPadId(),
+ revId: rev.id,
+ newLabel: newLabel},
+ success: success,
+ error: error
+ });
+ function success(text) {
+ var newRevisionList = JSON.parse(text);
+ self.newRevisionList(newRevisionList);
+ pad.sendClientMessage({
+ type: 'revisionLabel',
+ revisionList: reversedCopy(currentRevisionList),
+ savedBy: pad.getUserName(),
+ newLabel: newLabel
+ });
+ }
+ function error(e) {
+ alert("Oops! There was an error saving that revision label. Please try again later.");
+ }
+ }
+
+ var currentRevisionList = [];
+ function setRevisionList(newRevisionList, noAnimation) {
+ // deals with changed labels and new added revisions
+ for(var i=0; i<currentRevisionList.length; i++) {
+ var a = currentRevisionList[i];
+ var b = newRevisionList[i];
+ if (b.label != a.label) {
+ setBoxLabel(getNthBox(i), b.label);
+ }
+ }
+ for(var j=currentRevisionList.length; j<newRevisionList.length; j++) {
+ var newBox = makeRevisionBox(newRevisionList[j], j);
+ $("#savedrevs-scrollinner").append(newBox);
+ newBox.css('left', j * REVISION_BOX_WIDTH);
+ }
+ var newOnes = (newRevisionList.length > currentRevisionList.length);
+ currentRevisionList = newRevisionList;
+ if (newOnes) {
+ setDesiredScroll(getMaxScroll());
+ if (noAnimation) {
+ setScroll(desiredScroll);
+ }
+
+ if (! noAnimation) {
+ var nameOfLast = currentRevisionList[currentRevisionList.length-1].label;
+ displaySavedTip(nameOfLast);
+ }
+ }
+ }
+ function refreshRevisionList() {
+ for(var i=0;i<currentRevisionList.length; i++) {
+ var r = currentRevisionList[i];
+ var box = getNthBox(i);
+ setBoxTimestamp(box, r.timestamp);
+ }
+ }
+
+ var savedTipAnimator = padutils.makeShowHideAnimator(function(state) {
+ if (state == -1) {
+ $("#revision-notifier").css('opacity', 0).css('display', 'block');
+ }
+ else if (state == 0) {
+ $("#revision-notifier").css('opacity', 1);
+ }
+ else if (state == 1) {
+ $("#revision-notifier").css('opacity', 0).css('display', 'none');
+ }
+ else if (state < 0) {
+ $("#revision-notifier").css('opacity', 1);
+ }
+ else if (state > 0) {
+ $("#revision-notifier").css('opacity', 1 - state);
+ }
+ }, false, 25, 300);
+
+ function displaySavedTip(text) {
+ $("#revision-notifier .name").html(padutils.escapeHtml(text));
+ savedTipAnimator.show();
+ padutils.cancelActions("hide-revision-notifier");
+ var hideLater = padutils.getCancellableAction("hide-revision-notifier",
+ function() {
+ savedTipAnimator.hide();
+ });
+ window.setTimeout(hideLater, 3000);
+ }
+
+ var REVISION_BOX_WIDTH = 120;
+ var curScroll = 0; // distance between left of revisions and right of view
+ var desiredScroll = 0;
+ function getScrollWidth() {
+ return REVISION_BOX_WIDTH * currentRevisionList.length;
+ }
+ function getViewportWidth() {
+ return $("#savedrevs-scrollouter").width();
+ }
+ function getMinScroll() {
+ return Math.min(getViewportWidth(), getScrollWidth());
+ }
+ function getMaxScroll() {
+ return getScrollWidth();
+ }
+ function setScroll(newScroll) {
+ curScroll = newScroll;
+ $("#savedrevs-scrollinner").css('right', newScroll);
+ updateScrollArrows();
+ }
+ function setDesiredScroll(newDesiredScroll, dontUpdate) {
+ desiredScroll = Math.min(getMaxScroll(), Math.max(getMinScroll(),
+ newDesiredScroll));
+ if (! dontUpdate) {
+ updateScroll();
+ }
+ }
+ function updateScroll() {
+ updateScrollArrows();
+ scrollAnimator.scheduleAnimation();
+ }
+ function updateScrollArrows() {
+ $("#savedrevs-scrollleft").toggleClass("disabledscrollleft",
+ desiredScroll <= getMinScroll());
+ $("#savedrevs-scrollright").toggleClass("disabledscrollright",
+ desiredScroll >= getMaxScroll());
+ }
+ var scrollAnimator = padutils.makeAnimationScheduler(function() {
+ setDesiredScroll(desiredScroll, true); // re-clamp
+ if (Math.abs(desiredScroll - curScroll) < 1) {
+ setScroll(desiredScroll);
+ return false;
+ }
+ else {
+ setScroll(curScroll + (desiredScroll - curScroll)*0.5);
+ return true;
+ }
+ }, 50, 2);
+
+ var isSaving = false;
+ function setIsSaving(v) {
+ isSaving = v;
+ rerenderButton();
+ }
+
+ function haveReachedRevLimit() {
+ var mv = pad.getPrivilege('maxRevisions');
+ return (!(mv < 0 || mv > currentRevisionList.length));
+ }
+ function rerenderButton() {
+ if (isSaving || (! pad.isFullyConnected()) ||
+ haveReachedRevLimit()) {
+ $("#savedrevs-savenow").css('opacity', 0.75);
+ }
+ else {
+ $("#savedrevs-savenow").css('opacity', 1);
+ }
+ }
+
+ var scrollRepeatTimer = null;
+ var scrollStartTime = 0;
+ function setScrollRepeatTimer(dir) {
+ clearScrollRepeatTimer();
+ scrollStartTime = +new Date;
+ scrollRepeatTimer = window.setTimeout(function f() {
+ if (! scrollRepeatTimer) {
+ return;
+ }
+ self.scroll(dir);
+ var scrollTime = (+new Date) - scrollStartTime;
+ var delay = (scrollTime > 2000 ? 50 : 300);
+ scrollRepeatTimer = window.setTimeout(f, delay);
+ }, 300);
+ $(document).bind('mouseup', clearScrollRepeatTimer);
+ }
+ function clearScrollRepeatTimer() {
+ if (scrollRepeatTimer) {
+ window.clearTimeout(scrollRepeatTimer);
+ scrollRepeatTimer = null;
+ }
+ $(document).unbind('mouseup', clearScrollRepeatTimer);
+ }
+
+ var self = {
+ init: function(initialRevisions) {
+ self.newRevisionList(initialRevisions, true);
+
+ $("#savedrevs-savenow").click(function() { self.saveNow(); });
+ $("#savedrevs-scrollleft").mousedown(function() {
+ self.scroll('left');
+ setScrollRepeatTimer('left');
+ });
+ $("#savedrevs-scrollright").mousedown(function() {
+ self.scroll('right');
+ setScrollRepeatTimer('right');
+ });
+ $("#savedrevs-close").click(function() {paddocbar.setShownPanel(null);});
+
+ // update "saved n minutes ago" times
+ window.setInterval(function() {
+ refreshRevisionList();
+ }, 60*1000);
+ },
+ restoreRevision: function(rnum) {
+ var rev = currentRevisionList[rnum];
+ var warning = ("Restoring this revision will overwrite the current"
+ + " text of the pad. "+
+ "Are you sure you want to continue?");
+ var hidePanel = paddocbar.hideLaterIfNoOtherInteraction();
+ var box = getNthBox(rnum);
+ if (confirm(warning)) {
+ box.find(".srtwirly").show();
+ $.ajax({
+ type: 'get',
+ url: '/ep/pad/getrevisionatext',
+ data: {padId: pad.getPadId(), revId: rev.id},
+ success: success,
+ error: error
+ });
+ }
+ function success(resultJson) {
+ untwirl();
+ var result = JSON.parse(resultJson);
+ padeditor.restoreRevisionText(result);
+ window.setTimeout(function() {
+ hidePanel();
+ }, 0);
+ }
+ function error(e) {
+ untwirl();
+ alert("Oops! There was an error retreiving the text (revNum= "+
+ rev.revNum+"; padId="+pad.getPadId());
+ }
+ function untwirl() {
+ box.find(".srtwirly").hide();
+ }
+ },
+ showReachedLimit: function() {
+ alert("Sorry, you do not have privileges to save more than "+
+ pad.getPrivilege('maxRevisions')+" revisions.");
+ },
+ newRevisionList: function(lst, noAnimation) {
+ // server gives us list with newest first;
+ // we want chronological order
+ var L = reversedCopy(lst);
+ setRevisionList(L, noAnimation);
+ rerenderButton();
+ },
+ saveNow: function() {
+ if (isSaving) {
+ return;
+ }
+ if (! pad.isFullyConnected()) {
+ return;
+ }
+ if (haveReachedRevLimit()) {
+ self.showReachedLimit();
+ return;
+ }
+ setIsSaving(true);
+ var savedBy = pad.getUserName() || "unnamed";
+ pad.callWhenNotCommitting(submitSave);
+
+ function submitSave() {
+ $.ajax({
+ type: 'post',
+ url: '/ep/pad/saverevision',
+ data: {
+ padId: pad.getPadId(),
+ savedBy: savedBy,
+ savedById: pad.getUserId(),
+ revNum: pad.getCollabRevisionNumber()
+ },
+ success: success,
+ error: error
+ });
+ }
+ function success(text) {
+ setIsSaving(false);
+ var newRevisionList = JSON.parse(text);
+ self.newRevisionList(newRevisionList);
+ pad.sendClientMessage({
+ type: 'newRevisionList',
+ revisionList: newRevisionList,
+ savedBy: savedBy
+ });
+ }
+ function error(e) {
+ setIsSaving(false);
+ alert("Oops! The server failed to save the revision. Please try again later.");
+ }
+ },
+ handleResizePage: function() {
+ updateScrollArrows();
+ },
+ handleIsFullyConnected: function(isConnected) {
+ rerenderButton();
+ },
+ scroll: function(dir) {
+ var minScroll = getMinScroll();
+ var maxScroll = getMaxScroll();
+ if (dir == 'left') {
+ if (desiredScroll > minScroll) {
+ var n = Math.floor((desiredScroll - 1 - minScroll) /
+ REVISION_BOX_WIDTH);
+ setDesiredScroll(Math.max(0, n)*REVISION_BOX_WIDTH + minScroll);
+ }
+ }
+ else if (dir == 'right') {
+ if (desiredScroll < maxScroll) {
+ var n = Math.floor((maxScroll - desiredScroll - 1) /
+ REVISION_BOX_WIDTH);
+ setDesiredScroll(maxScroll - Math.max(0, n)*REVISION_BOX_WIDTH);
+ }
+ }
+ }
+ };
+ return self;
+}()); \ No newline at end of file
diff --git a/etherpad/src/static/js/pad_userlist.js b/etherpad/src/static/js/pad_userlist.js
new file mode 100644
index 0000000..13bfd6a
--- /dev/null
+++ b/etherpad/src/static/js/pad_userlist.js
@@ -0,0 +1,604 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 paduserlist = (function() {
+
+ var rowManager = (function() {
+ // The row manager handles rendering rows of the user list and animating
+ // their insertion, removal, and reordering. It manipulates TD height
+ // and TD opacity.
+
+ function nextRowId() {
+ return "usertr"+(nextRowId.counter++);
+ }
+ nextRowId.counter = 1;
+ // objects are shared; fields are "domId","data","animationStep"
+ var rowsFadingOut = []; // unordered set
+ var rowsFadingIn = []; // unordered set
+ var rowsPresent = []; // in order
+
+ var ANIMATION_START = -12; // just starting to fade in
+ var ANIMATION_END = 12; // just finishing fading out
+ function getAnimationHeight(step, power) {
+ var a = Math.abs(step/12);
+ if (power == 2) a = a*a;
+ else if (power == 3) a = a*a*a;
+ else if (power == 4) a = a*a*a*a;
+ else if (power >= 5) a = a*a*a*a*a;
+ return Math.round(26*(1-a));
+ }
+ var OPACITY_STEPS = 6;
+
+ var ANIMATION_STEP_TIME = 20;
+ var LOWER_FRAMERATE_FACTOR = 2;
+ var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME,
+ LOWER_FRAMERATE_FACTOR).scheduleAnimation;
+
+ var NUMCOLS = 4;
+
+ // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
+ // IE's poor handling when manipulating the DOM directly.
+
+ function getEmptyRowHtml(height) {
+ return '<td colspan="'+NUMCOLS+'" style="border:0;height:'+height+'px"><!-- --></td>';
+ }
+ function isNameEditable(data) {
+ return (! data.name) && (data.status != 'Disconnected');
+ }
+ function replaceUserRowContents(tr, height, data) {
+ var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
+ if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) {
+ // preserve input field node
+ for(var i=0; i<tds.length; i++) {
+ var oldTd = $(tr.find("td").get(i));
+ if (! oldTd.hasClass('usertdname')) {
+ oldTd.replaceWith(tds[i]);
+ }
+ }
+ }
+ else {
+ tr.html(tds.join(''));
+ }
+ return tr;
+ }
+ function getUserRowHtml(height, data) {
+ var nameHtml;
+ var isGuest = (data.id.charAt(0) != 'p');
+ if (data.name) {
+ nameHtml = padutils.escapeHtml(data.name);
+ if (isGuest && pad.getIsProPad()) {
+ nameHtml += ' (Guest)';
+ }
+ }
+ else {
+ nameHtml = '<input type="text" class="editempty newinput" value="unnamed" '+
+ (isNameEditable(data) ? '' : 'disabled="disabled" ')+
+ '/>';
+ }
+
+ return ['<td style="height:',height,'px" class="usertdswatch"><div class="swatch" style="background:'+data.color+'">&nbsp;</div></td>',
+ '<td style="height:',height,'px" class="usertdname">',nameHtml,'</td>',
+ '<td style="height:',height,'px" class="usertdstatus">',padutils.escapeHtml(data.status),'</td>',
+ '<td style="height:',height,'px" class="activity">',padutils.escapeHtml(data.activity),'</td>'].join('');
+ }
+ function getRowHtml(id, innerHtml) {
+ return '<tr id="'+id+'">'+innerHtml+'</tr>';
+ }
+ function rowNode(row) {
+ return $("#"+row.domId);
+ }
+ function handleRowData(row) {
+ if (row.data && row.data.status == 'Disconnected') {
+ row.opacity = 0.5;
+ }
+ else {
+ delete row.opacity;
+ }
+ }
+ function handleRowNode(tr, data) {
+ if (data.titleText) {
+ tr.attr('title', data.titleText);
+ }
+ else {
+ tr.removeAttr('title');
+ }
+ }
+ function handleOtherUserInputs() {
+ // handle 'INPUT' elements for naming other unnamed users
+ $("#otheruserstable input.newinput").each(function() {
+ var input = $(this);
+ var tr = input.closest("tr");
+ if (tr.length > 0) {
+ var index = tr.parent().children().index(tr);
+ if (index >= 0) {
+ var userId = rowsPresent[index].data.id;
+ rowManagerMakeNameEditor($(this), userId);
+ }
+ }
+ }).removeClass('newinput');
+ }
+
+ // animationPower is 0 to skip animation, 1 for linear, 2 for quadratic, etc.
+ function insertRow(position, data, animationPower) {
+ position = Math.max(0, Math.min(rowsPresent.length, position));
+ animationPower = (animationPower === undefined ? 4 : animationPower);
+
+ var domId = nextRowId();
+ var row = {data: data, animationStep: ANIMATION_START, domId: domId,
+ animationPower: animationPower};
+ handleRowData(row);
+ rowsPresent.splice(position, 0, row);
+ var tr;
+ if (animationPower == 0) {
+ tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data)));
+ row.animationStep = 0;
+ }
+ else {
+ rowsFadingIn.push(row);
+ tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START))));
+ }
+ handleRowNode(tr, data);
+ if (position == 0) {
+ $("table#otheruserstable").prepend(tr);
+ }
+ else {
+ rowNode(rowsPresent[position-1]).after(tr);
+ }
+
+ if (animationPower != 0) {
+ scheduleAnimation();
+ }
+
+ handleOtherUserInputs();
+
+ return row;
+ }
+
+ function updateRow(position, data) {
+ var row = rowsPresent[position];
+ if (row) {
+ row.data = data;
+ handleRowData(row);
+ if (row.animationStep == 0) {
+ // not currently animating
+ var tr = rowNode(row);
+ replaceUserRowContents(tr, getAnimationHeight(0), row.data).find(
+ "td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
+ handleRowNode(tr, data);
+ handleOtherUserInputs();
+ }
+ }
+ }
+
+ function removeRow(position, animationPower) {
+ animationPower = (animationPower === undefined ? 4 : animationPower);
+ var row = rowsPresent[position];
+ if (row) {
+ rowsPresent.splice(position, 1); // remove
+ if (animationPower == 0) {
+ rowNode(row).remove();
+ }
+ else {
+ row.animationStep = - row.animationStep; // use symmetry
+ row.animationPower = animationPower;
+ rowsFadingOut.push(row);
+ scheduleAnimation();
+ }
+ }
+ }
+
+ // newPosition is position after the row has been removed
+ function moveRow(oldPosition, newPosition, animationPower) {
+ animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
+ var row = rowsPresent[oldPosition];
+ if (row && oldPosition != newPosition) {
+ var rowData = row.data;
+ removeRow(oldPosition, animationPower);
+ insertRow(newPosition, rowData, animationPower);
+ }
+ }
+
+ function animateStep() {
+ // animation must be symmetrical
+ for(var i=rowsFadingIn.length-1;i>=0;i--) { // backwards to allow removal
+ var row = rowsFadingIn[i];
+ var step = ++row.animationStep;
+ var animHeight = getAnimationHeight(step, row.animationPower);
+ var node = rowNode(row);
+ var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
+ if (step <= -OPACITY_STEPS) {
+ node.find("td").height(animHeight);
+ }
+ else if (step == -OPACITY_STEPS+1) {
+ node.html(getUserRowHtml(animHeight, row.data)).find("td").css(
+ 'opacity', baseOpacity*1/OPACITY_STEPS);
+ handleRowNode(node, row.data);
+ }
+ else if (step < 0) {
+ node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS-(-step))/OPACITY_STEPS).height(animHeight);
+ }
+ else if (step == 0) {
+ // set HTML in case modified during animation
+ node.html(getUserRowHtml(animHeight, row.data)).find("td").css(
+ 'opacity', baseOpacity*1).height(animHeight);
+ handleRowNode(node, row.data);
+ rowsFadingIn.splice(i, 1); // remove from set
+ }
+ }
+ for(var i=rowsFadingOut.length-1;i>=0;i--) { // backwards to allow removal
+ var row = rowsFadingOut[i];
+ var step = ++row.animationStep;
+ var node = rowNode(row);
+ var animHeight = getAnimationHeight(step, row.animationPower);
+ var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
+ if (step < OPACITY_STEPS) {
+ node.find("td").css('opacity', baseOpacity*(OPACITY_STEPS - step)/OPACITY_STEPS).height(animHeight);
+ }
+ else if (step == OPACITY_STEPS) {
+ node.html(getEmptyRowHtml(animHeight));
+ }
+ else if (step <= ANIMATION_END) {
+ node.find("td").height(animHeight);
+ }
+ else {
+ rowsFadingOut.splice(i, 1); // remove from set
+ node.remove();
+ }
+ }
+
+ handleOtherUserInputs();
+
+ return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
+ }
+
+ var self = {
+ insertRow: insertRow,
+ removeRow: removeRow,
+ moveRow: moveRow,
+ updateRow: updateRow
+ };
+ return self;
+ }()); ////////// rowManager
+
+ var myUserInfo = {};
+ var otherUsersInfo = [];
+ var otherUsersData = [];
+ var colorPickerOpen = false;
+
+ function rowManagerMakeNameEditor(jnode, userId) {
+ setUpEditable(jnode, function() {
+ var existingIndex = findExistingIndex(userId);
+ if (existingIndex >= 0) {
+ return otherUsersInfo[existingIndex].name || '';
+ }
+ else {
+ return '';
+ }
+ }, function(newName) {
+ if (! newName) {
+ jnode.addClass("editempty");
+ jnode.val("unnamed");
+ }
+ else {
+ jnode.attr('disabled', 'disabled');
+ pad.suggestUserName(userId, newName);
+ }
+ });
+ }
+
+ function renderMyUserInfo() {
+ if (myUserInfo.name) {
+ $("#myusernameedit").removeClass("editempty").val(
+ myUserInfo.name);
+ }
+ else {
+ $("#myusernameedit").addClass("editempty").val(
+ "< enter your name >");
+ }
+ if (colorPickerOpen) {
+ $("#myswatchbox").addClass('myswatchboxunhoverable').removeClass(
+ 'myswatchboxhoverable');
+ }
+ else {
+ $("#myswatchbox").addClass('myswatchboxhoverable').removeClass(
+ 'myswatchboxunhoverable');
+ }
+ $("#myswatch").css('background', pad.getColorPalette()[myUserInfo.colorId]);
+ }
+
+ function findExistingIndex(userId) {
+ var existingIndex = -1;
+ for(var i=0;i<otherUsersInfo.length;i++) {
+ if (otherUsersInfo[i].userId == userId) {
+ existingIndex = i;
+ break;
+ }
+ }
+ return existingIndex;
+ }
+
+ function setUpEditable(jqueryNode, valueGetter, valueSetter) {
+ jqueryNode.bind('focus', function(evt) {
+ var oldValue = valueGetter();
+ if (jqueryNode.val() !== oldValue) {
+ jqueryNode.val(oldValue);
+ }
+ jqueryNode.addClass("editactive").removeClass("editempty");
+ });
+ jqueryNode.bind('blur', function(evt) {
+ var newValue = jqueryNode.removeClass("editactive").val();
+ valueSetter(newValue);
+ });
+ padutils.bindEnterAndEscape(jqueryNode, function onEnter() {
+ jqueryNode.blur();
+ }, function onEscape() {
+ jqueryNode.val(valueGetter()).blur();
+ });
+ jqueryNode.removeAttr('disabled').addClass('editable');
+ }
+
+ function showColorPicker() {
+ if (! colorPickerOpen) {
+ var palette = pad.getColorPalette();
+ for(var i=0;i<palette.length;i++) {
+ $("#mycolorpicker .n"+(i+1)+" .pickerswatch").css(
+ 'background', palette[i]);
+ }
+ $("#mycolorpicker").css('display', 'block');
+ colorPickerOpen = true;
+ renderMyUserInfo();
+ }
+ // this part happens even if color picker is already open
+ $("#mycolorpicker .pickerswatchouter").removeClass('picked');
+ $("#mycolorpicker .pickerswatchouter:eq("+(myUserInfo.colorId||0)+")").
+ addClass('picked');
+ }
+ function getColorPickerSwatchIndex(jnode) {
+ return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
+ }
+ function closeColorPicker(accept) {
+ if (accept) {
+ var newColorId = getColorPickerSwatchIndex($("#mycolorpicker .picked"));
+ if (newColorId >= 0) { // fails on NaN
+ myUserInfo.colorId = newColorId;
+ pad.notifyChangeColor(newColorId);
+ }
+ }
+ colorPickerOpen = false;
+ $("#mycolorpicker").css('display', 'none');
+ renderMyUserInfo();
+ }
+
+ function updateInviteNotice() {
+ if (otherUsersInfo.length == 0) {
+ $("#otheruserstable").hide();
+ $("#nootherusers").show();
+ }
+ else {
+ $("#nootherusers").hide();
+ $("#otheruserstable").show();
+ }
+ }
+
+ var knocksToIgnore = {};
+ var guestPromptFlashState = 0;
+ var guestPromptFlash = padutils.makeAnimationScheduler(
+ function () {
+ var prompts = $("#guestprompts .guestprompt");
+ if (prompts.length == 0) {
+ return false; // no more to do
+ }
+
+ guestPromptFlashState = 1 - guestPromptFlashState;
+ if (guestPromptFlashState) {
+ prompts.css('background', '#ffa');
+ }
+ else {
+ prompts.css('background', '#ffe');
+ }
+
+ return true;
+ }, 1000);
+
+ var self = {
+ init: function(myInitialUserInfo) {
+ self.setMyUserInfo(myInitialUserInfo);
+
+ $("#otheruserstable tr").remove();
+
+ if (pad.getUserIsGuest()) {
+ $("#myusernameedit").addClass('myusernameedithoverable');
+ setUpEditable($("#myusernameedit"),
+ function() {
+ return myUserInfo.name || '';
+ },
+ function(newValue) {
+ myUserInfo.name = newValue;
+ pad.notifyChangeName(newValue);
+ // wrap with setTimeout to do later because we get
+ // a double "blur" fire in IE...
+ window.setTimeout(function() {
+ renderMyUserInfo();
+ }, 0);
+ });
+ }
+
+ // color picker
+ $("#myswatchbox").click(showColorPicker);
+ $("#mycolorpicker .pickerswatchouter").click(function() {
+ $("#mycolorpicker .pickerswatchouter").removeClass('picked');
+ $(this).addClass('picked');
+ });
+ $("#mycolorpickersave").click(function() {
+ closeColorPicker(true);
+ });
+ $("#mycolorpickercancel").click(function() {
+ closeColorPicker(false);
+ });
+ //
+
+ },
+ setMyUserInfo: function(info) {
+ myUserInfo = $.extend({}, info);
+
+ renderMyUserInfo();
+ },
+ userJoinOrUpdate: function(info) {
+ if ((! info.userId) || (info.userId == myUserInfo.userId)) {
+ // not sure how this would happen
+ return;
+ }
+
+ var userData = {};
+ userData.color = pad.getColorPalette()[info.colorId];
+ userData.name = info.name;
+ userData.status = '';
+ userData.activity = '';
+ userData.id = info.userId;
+ // Firefox ignores \n in title text; Safari does a linebreak
+ userData.titleText = [info.userAgent||'', info.ip||''].join(' \n');
+
+ var existingIndex = findExistingIndex(info.userId);
+
+ var numUsersBesides = otherUsersInfo.length;
+ if (existingIndex >= 0) {
+ numUsersBesides--;
+ }
+ var newIndex = padutils.binarySearch(numUsersBesides, function(n) {
+ if (existingIndex >= 0 && n >= existingIndex) {
+ // pretend existingIndex isn't there
+ n++;
+ }
+ var infoN = otherUsersInfo[n];
+ var nameN = (infoN.name||'').toLowerCase();
+ var nameThis = (info.name||'').toLowerCase();
+ var idN = infoN.userId;
+ var idThis = info.userId;
+ return (nameN > nameThis) || (nameN == nameThis &&
+ idN > idThis);
+ });
+
+ if (existingIndex >= 0) {
+ // update
+ if (existingIndex == newIndex) {
+ otherUsersInfo[existingIndex] = info;
+ otherUsersData[existingIndex] = userData;
+ rowManager.updateRow(existingIndex, userData);
+ }
+ else {
+ otherUsersInfo.splice(existingIndex, 1);
+ otherUsersData.splice(existingIndex, 1);
+ otherUsersInfo.splice(newIndex, 0, info);
+ otherUsersData.splice(newIndex, 0, userData);
+ rowManager.updateRow(existingIndex, userData);
+ rowManager.moveRow(existingIndex, newIndex);
+ }
+ }
+ else {
+ otherUsersInfo.splice(newIndex, 0, info);
+ otherUsersData.splice(newIndex, 0, userData);
+ rowManager.insertRow(newIndex, userData);
+ }
+
+ updateInviteNotice();
+ },
+ userLeave: function(info) {
+ var existingIndex = findExistingIndex(info.userId);
+ if (existingIndex >= 0) {
+ var userData = otherUsersData[existingIndex];
+ userData.status = 'Disconnected';
+ rowManager.updateRow(existingIndex, userData);
+ if (userData.leaveTimer) {
+ window.clearTimeout(userData.leaveTimer);
+ }
+ // set up a timer that will only fire if no leaves,
+ // joins, or updates happen for this user in the
+ // next N seconds, to remove the user from the list.
+ var thisUserId = info.userId;
+ var thisLeaveTimer = window.setTimeout(function() {
+ var newExistingIndex = findExistingIndex(thisUserId);
+ if (newExistingIndex >= 0) {
+ var newUserData = otherUsersData[newExistingIndex];
+ if (newUserData.status == 'Disconnected' &&
+ newUserData.leaveTimer == thisLeaveTimer) {
+ otherUsersInfo.splice(newExistingIndex, 1);
+ otherUsersData.splice(newExistingIndex, 1);
+ rowManager.removeRow(newExistingIndex);
+ updateInviteNotice();
+ }
+ }
+ }, 8000); // how long to wait
+ userData.leaveTimer = thisLeaveTimer;
+ }
+ updateInviteNotice();
+ },
+ showGuestPrompt: function(userId, displayName) {
+ if (knocksToIgnore[userId]) {
+ return;
+ }
+
+ var encodedUserId = padutils.encodeUserId(userId);
+
+ var actionName = 'hide-guest-prompt-'+encodedUserId;
+ padutils.cancelActions(actionName);
+
+ var box = $("#guestprompt-"+encodedUserId);
+ if (box.length == 0) {
+ // make guest prompt box
+ box = $('<div id="guestprompt-'+encodedUserId+'" class="guestprompt"><div class="choices"><a href="javascript:void(paduserlist.answerGuestPrompt(\''+encodedUserId+'\',false))">Deny</a> <a href="javascript:void(paduserlist.answerGuestPrompt(\''+encodedUserId+'\',true))">Approve</a></div><div class="guestname"><strong>Guest:</strong> '+padutils.escapeHtml(displayName)+'</div></div>');
+ $("#guestprompts").append(box);
+ }
+ else {
+ // update display name
+ box.find(".guestname").html('<strong>Guest:</strong> '+padutils.escapeHtml(displayName));
+ }
+ var hideLater = padutils.getCancellableAction(actionName, function() {
+ self.removeGuestPrompt(userId);
+ });
+ window.setTimeout(hideLater, 15000); // time-out with no knock
+
+ guestPromptFlash.scheduleAnimation();
+ },
+ removeGuestPrompt: function(userId) {
+ var box = $("#guestprompt-"+padutils.encodeUserId(userId));
+ // remove ID now so a new knock by same user gets new, unfaded box
+ box.removeAttr('id').fadeOut("fast", function() {
+ box.remove();
+ });
+
+ knocksToIgnore[userId] = true;
+ window.setTimeout(function() {
+ delete knocksToIgnore[userId];
+ }, 5000);
+ },
+ answerGuestPrompt: function(encodedUserId, approve) {
+ var guestId = padutils.decodeUserId(encodedUserId);
+
+ var msg = {
+ type: 'guestanswer',
+ authId: pad.getUserId(),
+ guestId: guestId,
+ answer: (approve ? "approved" : "denied")
+ };
+ pad.sendClientMessage(msg);
+
+ self.removeGuestPrompt(guestId);
+ }
+ };
+ return self;
+}());
+
diff --git a/etherpad/src/static/js/pad_utils.js b/etherpad/src/static/js/pad_utils.js
new file mode 100644
index 0000000..de606ad
--- /dev/null
+++ b/etherpad/src/static/js/pad_utils.js
@@ -0,0 +1,359 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 padutils = {
+ escapeHtml: function(x) {
+ return String(x).replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
+ },
+ uniqueId: function() {
+ function encodeNum(n, width) {
+ // returns string that is exactly 'width' chars, padding with zeros
+ // and taking rightmost digits
+ return (Array(width+1).join('0') + Number(n).toString(35)).slice(-width);
+ }
+ return [pad.getClientIp(),
+ encodeNum(+new Date, 7),
+ encodeNum(Math.floor(Math.random()*1e9), 4)].join('.');
+ },
+ uaDisplay: function(ua) {
+ var m;
+
+ function clean(a) {
+ var maxlen = 16;
+ a = a.replace(/[^a-zA-Z0-9\.]/g, '');
+ if (a.length > maxlen) {
+ a = a.substr(0,maxlen);
+ }
+ return a;
+ }
+
+ function checkver(name) {
+ var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
+ if (m && m.length > 1) {
+ return clean(name+m[1]);
+ }
+ return null;
+ }
+
+ // firefox
+ if (checkver('Firefox')) { return checkver('Firefox'); }
+
+ // misc browsers, including IE
+ m = ua.match(/compatible; ([^;]+);/);
+ if (m && m.length > 1) {
+ return clean(m[1]);
+ }
+
+ // iphone
+ if (ua.match(/\(iPhone;/)) {
+ return 'iPhone';
+ }
+
+ // chrome
+ if (checkver('Chrome')) { return checkver('Chrome'); }
+
+ // safari
+ m = ua.match(/Safari\/[\d\.]+/);
+ if (m) {
+ var v = '?';
+ m = ua.match(/Version\/([\d\.]+)/);
+ if (m && m.length > 1) {
+ v = m[1];
+ }
+ return clean('Safari'+v);
+ }
+
+ // everything else
+ var x = ua.split(' ')[0];
+ return clean(x);
+ },
+ // "func" is a function over 0..(numItems-1) that is monotonically
+ // "increasing" with index (false, then true). Finds the boundary
+ // between false and true, a number between 0 and numItems inclusive.
+ binarySearch: function (numItems, func) {
+ if (numItems < 1) return 0;
+ if (func(0)) return 0;
+ if (! func(numItems-1)) return numItems;
+ var low = 0; // func(low) is always false
+ var high = numItems-1; // func(high) is always true
+ while ((high - low) > 1) {
+ var x = Math.floor((low+high)/2); // x != low, x != high
+ if (func(x)) high = x;
+ else low = x;
+ }
+ return high;
+ },
+ // e.g. "Thu Jun 18 2009 13:09"
+ simpleDateTime: function(date) {
+ var d = new Date(+date); // accept either number or date
+ var dayOfWeek = (['Sun','Mon','Tue','Wed','Thu','Fri','Sat'])[d.getDay()];
+ var month = (['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'])[d.getMonth()];
+ var dayOfMonth = d.getDate();
+ var year = d.getFullYear();
+ var hourmin = d.getHours()+":"+("0"+d.getMinutes()).slice(-2);
+ return dayOfWeek+' '+month+' '+dayOfMonth+' '+year+' '+hourmin;
+ },
+ findURLs: function(text) {
+ // copied from ACE
+ var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
+ var _REGEX_URLCHAR = new RegExp('('+/[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source+'|'+_REGEX_WORDCHAR.source+')');
+ var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source+_REGEX_URLCHAR.source+'*(?![:.,;])'+_REGEX_URLCHAR.source, 'g');
+
+ // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
+ function _findURLs(text) {
+ _REGEX_URL.lastIndex = 0;
+ var urls = null;
+ var execResult;
+ while ((execResult = _REGEX_URL.exec(text))) {
+ urls = (urls || []);
+ var startIndex = execResult.index;
+ var url = execResult[0];
+ urls.push([startIndex, url]);
+ }
+
+ return urls;
+ }
+
+ return _findURLs(text);
+ },
+ escapeHtmlWithClickableLinks: function(text, target) {
+ var idx = 0;
+ var pieces = [];
+ var urls = padutils.findURLs(text);
+ function advanceTo(i) {
+ if (i > idx) {
+ pieces.push(padutils.escapeHtml(text.substring(idx, i)));
+ idx = i;
+ }
+ }
+ if (urls) {
+ for(var j=0;j<urls.length;j++) {
+ var startIndex = urls[j][0];
+ var href = urls[j][1];
+ advanceTo(startIndex);
+ pieces.push('<a ', (target?'target="'+target+'" ':''),
+ 'href="', href.replace(/\"/g, '&quot;'), '">');
+ advanceTo(startIndex + href.length);
+ pieces.push('</a>');
+ }
+ }
+ advanceTo(text.length);
+ return pieces.join('');
+ },
+ bindEnterAndEscape: function(node, onEnter, onEscape) {
+ function handleKey(evt) {
+ if (evt.which == 27 && onEscape) {
+ // "escape" key
+ if (evt.type == 'keydown') {
+ onEscape(evt);
+ }
+ evt.preventDefault();
+ }
+ else if (evt.which == 13 && onEnter) {
+ // return/enter
+ if (evt.type == 'keyup') {
+ onEnter(evt);
+ }
+ evt.preventDefault();
+ }
+ }
+ $(node).bind('keyup keypress keydown', handleKey);
+ },
+ timediff: function(d) {
+ function format(n, word) {
+ n = Math.round(n);
+ return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
+ }
+ d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
+ if (d < 60) { return format(d, 'second'); }
+ d /= 60;
+ if (d < 60) { return format(d, 'minute'); }
+ d /= 60;
+ if (d < 24) { return format(d, 'hour'); }
+ d /= 24;
+ return format(d, 'day');
+ },
+ makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) {
+ if (stepsAtOnce === undefined) {
+ stepsAtOnce = 1;
+ }
+
+ var animationTimer = null;
+
+ function scheduleAnimation() {
+ if (! animationTimer) {
+ animationTimer = window.setTimeout(function() {
+ animationTimer = null;
+ var n = stepsAtOnce;
+ var moreToDo = true;
+ while (moreToDo && n > 0) {
+ moreToDo = funcToAnimateOneStep();
+ n--;
+ }
+ if (moreToDo) {
+ // more to do
+ scheduleAnimation();
+ }
+ }, stepTime*stepsAtOnce);
+ }
+ }
+ return { scheduleAnimation: scheduleAnimation };
+ },
+ makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) {
+ var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
+ var animationFrameDelay = 1000 / fps;
+ var animationStep = animationFrameDelay / totalMs;
+
+ var scheduleAnimation =
+ padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
+
+ function doShow() {
+ animationState = -1;
+ funcToArriveAtState(animationState);
+ scheduleAnimation();
+ }
+
+ function doQuickShow() { // start showing without losing any fade-in progress
+ if (animationState < -1) {
+ animationState = -1;
+ }
+ else if (animationState <= 0) {
+ animationState = animationState;
+ }
+ else {
+ animationState = Math.max(-1, Math.min(0, - animationState));
+ }
+ funcToArriveAtState(animationState);
+ scheduleAnimation();
+ }
+
+ function doHide() {
+ if (animationState >= -1 && animationState <= 0) {
+ animationState = 1e-6;
+ scheduleAnimation();
+ }
+ }
+
+ function animateOneStep() {
+ if (animationState < -1 || animationState == 0) {
+ return false;
+ }
+ else if (animationState < 0) {
+ // animate show
+ animationState += animationStep;
+ if (animationState >= 0) {
+ animationState = 0;
+ funcToArriveAtState(animationState);
+ return false;
+ }
+ else {
+ funcToArriveAtState(animationState);
+ return true;
+ }
+ }
+ else if (animationState > 0) {
+ // animate hide
+ animationState += animationStep;
+ if (animationState >= 1) {
+ animationState = 1;
+ funcToArriveAtState(animationState);
+ animationState = -2;
+ return false;
+ }
+ else {
+ funcToArriveAtState(animationState);
+ return true;
+ }
+ }
+ }
+
+ return {show: doShow, hide: doHide, quickShow: doQuickShow};
+ },
+ _nextActionId: 1,
+ uncanceledActions: {},
+ getCancellableAction: function(actionType, actionFunc) {
+ var o = padutils.uncanceledActions[actionType];
+ if (! o) {
+ o = {};
+ padutils.uncanceledActions[actionType] = o;
+ }
+ var actionId = (padutils._nextActionId++);
+ o[actionId] = true;
+ return function() {
+ var p = padutils.uncanceledActions[actionType];
+ if (p && p[actionId]) {
+ actionFunc();
+ }
+ };
+ },
+ cancelActions: function(actionType) {
+ var o = padutils.uncanceledActions[actionType];
+ if (o) {
+ // clear it
+ delete padutils.uncanceledActions[actionType];
+ }
+ },
+ makeFieldLabeledWhenEmpty: function(field, labelText) {
+ field = $(field);
+ function clear() {
+ field.addClass('editempty');
+ field.val(labelText);
+ }
+ field.focus(function() {
+ if (field.hasClass('editempty')) {
+ field.val('');
+ }
+ field.removeClass('editempty');
+ });
+ field.blur(function() {
+ if (! field.val()) {
+ clear();
+ }
+ });
+ return {clear:clear};
+ },
+ getCheckbox: function(node) {
+ return $(node).is(':checked');
+ },
+ setCheckbox: function(node, value) {
+ if (value) {
+ $(node).attr('checked', 'checked');
+ }
+ else {
+ $(node).removeAttr('checked');
+ }
+ },
+ bindCheckboxChange: function(node, func) {
+ $(node).bind("click change", func);
+ },
+ encodeUserId: function(userId) {
+ return userId.replace(/[^a-y0-9]/g, function(c) {
+ if (c == ".") return "-";
+ return 'z'+c.charCodeAt(0)+'z';
+ });
+ },
+ decodeUserId: function(encodedUserId) {
+ return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) {
+ if (cc == '-') return '.';
+ else if (cc.charAt(0) == 'z') {
+ return String.fromCharCode(Number(cc.slice(1,-1)));
+ }
+ else {
+ return cc;
+ }
+ });
+ }
+};
diff --git a/etherpad/src/static/js/plugins.js b/etherpad/src/static/js/plugins.js
new file mode 100644
index 0000000..f7a5990
--- /dev/null
+++ b/etherpad/src/static/js/plugins.js
@@ -0,0 +1,22 @@
+plugins = {
+ callHook: function (hookName, args) {
+ var hook = clientVars.hooks[hookName];
+ if (hook === undefined)
+ return [];
+ var res = [];
+ for (var i = 0, N=hook.length; i < N; i++) {
+ var plugin = hook[i];
+ var pluginRes = eval(plugin.plugin)[plugin.original || hookName](args);
+ if (pluginRes != undefined && pluginRes != null)
+ res = res.concat(pluginRes);
+ }
+ return res;
+ },
+
+ callHookStr: function (hookName, args, sep, pre, post) {
+ if (sep == undefined) sep = '';
+ if (pre == undefined) pre = '';
+ if (post == undefined) post = '';
+ return callHook(hookName, args).map(function (x) { return pre + x + post}).join(sep || "");
+ }
+};
diff --git a/etherpad/src/static/js/pricing.js b/etherpad/src/static/js/pricing.js
new file mode 100644
index 0000000..913d5ce
--- /dev/null
+++ b/etherpad/src/static/js/pricing.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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() {
+ $('#buylink').click(function() { window.location = "/ep/store/eepnet-checkout"; return false; });
+}); \ No newline at end of file
diff --git a/etherpad/src/static/js/pro/guest-knock-client.js b/etherpad/src/static/js/pro/guest-knock-client.js
new file mode 100644
index 0000000..bace225
--- /dev/null
+++ b/etherpad/src/static/js/pro/guest-knock-client.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 knock() {
+ $.ajax({
+ type: "POST",
+ url: "/ep/account/guest-knock",
+ cache: false,
+ data: {
+ padId: clientVars.localPadId,
+ guestDisplayName: clientVars.guestDisplayName
+ },
+ success: knockReply,
+ error: knockError
+ });
+}
+
+function knockReply(responseText) {
+ //console.log("knockReply: "+responseText);
+ if (responseText == "approved") {
+ window.location.href = clientVars.padUrl;
+ }
+ if (responseText == "denied") {
+ $("#guest-knock-box").hide();
+ $("#guest-knock-denied").show();
+ }
+ if (responseText == "wait") {
+ setTimeout(knock, 1000);
+ }
+}
+
+function knockError() {
+ alert("There was an error requesting access to the pad. Kindly report this by sending email to bugs@pad.spline.inf.fu-berlin.de.");
+}
+
+$(document).ready(function() {
+ knock();
+});
+
diff --git a/etherpad/src/static/js/pro/pro-padlist-client.js b/etherpad/src/static/js/pro/pro-padlist-client.js
new file mode 100644
index 0000000..ba50d95
--- /dev/null
+++ b/etherpad/src/static/js/pro/pro-padlist-client.js
@@ -0,0 +1,104 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+if (!window.etherpad) {
+ etherpad = {};
+}
+if (!window.etherpad.pro) {
+ etherpad.pro = {};
+}
+
+etherpad.pro.padlist = {};
+
+$(document).ready(function() {
+
+ function getTargetPadId(target) {
+ var padmetaId = $(target).attr('id').split('-')[2];
+ //console.log("padmetaId = "+padmetaId);
+ return clientVars.localPadIds[padmetaId];
+ }
+
+ var padActionsMenu = [
+ {"View Read-Only": {
+ onclick: function(menuItem, menu) {
+ var localPadId = getTargetPadId(menu.target);
+ window.location.href = ("/ep/pad/view/"+localPadId+"/latest");
+ },
+ icon: '/static/img/pro/padlist/paper-icon.gif'
+ }
+ },
+ $.contextMenu.separator,
+ {"Archive": {
+ onclick: function(menuItem, menu) {
+ var localPadId = getTargetPadId(menu.target);
+ etherpad.pro.padlist.toggleArchivePad(localPadId);
+ }
+ }
+ },
+ {"Delete": {
+ onclick: function(menuItem, menu) {
+ var localPadId = getTargetPadId(menu.target);
+ etherpad.pro.padlist.deletePad(localPadId);
+ },
+ icon: '/static/img/pro/padlist/trash-icon.gif'
+ }
+ }
+ ];
+
+ if (clientVars.showingArchivedPads) {
+ padActionsMenu[2]["Un-archive"] = padActionsMenu[2]["Archive"];
+ delete padActionsMenu[2]["Archive"];
+ }
+
+ $('.gear-drop').contextMenu(padActionsMenu, {
+ theme: 'gloss,gloss-cyan',
+ bindTarget: 'click',
+ beforeShow: function() {
+ var localPadId = getTargetPadId(this.target);
+ $('tr.selected').removeClass('selected');
+ $('tr#pad-row-'+localPadId).addClass('selected');
+ return true;
+ },
+ hideCallback: function() {
+ var localPadId = getTargetPadId(this.target);
+ $('tr#pad-row-'+localPadId).removeClass('selected');
+ }
+ });
+});
+
+etherpad.pro.padlist.deletePad = function(localPadId) {
+ if (!confirm("Are you sure you want to delete the pad \""+clientVars.padTitles[localPadId]+"\"?")) {
+ return;
+ }
+
+ var inp = $("#padIdToDelete");
+ inp.val(localPadId);
+
+ // sanity check
+ if (! (inp.val() == localPadId)) {
+ alert("Error: "+inp.val());
+ return;
+ }
+
+ $("#delete-pad").submit();
+};
+
+etherpad.pro.padlist.toggleArchivePad = function(localPadId) {
+ var inp = $("#padIdToToggleArchive");
+ inp.val(localPadId);
+ $("#toggle-archive-pad").submit();
+};
+
diff --git a/etherpad/src/static/js/pro/signin-client.js b/etherpad/src/static/js/pro/signin-client.js
new file mode 100644
index 0000000..62847e5
--- /dev/null
+++ b/etherpad/src/static/js/pro/signin-client.js
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+
+$(document).ready(function() {
+ if ($("#signin-form").length > 0) {
+ $("#email").focus();
+ }
+ if ($("#guest-signin-form").length > 0) {
+ $("#guestDisplayName").focus();
+ }
+});
+
+
diff --git a/etherpad/src/static/js/pulse.jquery.js b/etherpad/src/static/js/pulse.jquery.js
new file mode 100644
index 0000000..b23aede
--- /dev/null
+++ b/etherpad/src/static/js/pulse.jquery.js
@@ -0,0 +1,105 @@
+/**
+ * jQuery.pulse
+ * Copyright (c) 2008 James Padolsey - jp(at)qd9(dot)co.uk | http://james.padolsey.com / http://enhance.qd-creative.co.uk
+ * Dual licensed under MIT and GPL.
+ * Date: 05/11/08
+ *
+ * @projectDescription Applies a continual pulse to any element specified
+ * http://enhance.qd-creative.co.uk/demos/pulse/
+ * Tested successfully with jQuery 1.2.6. On FF 2/3, IE 6/7, Opera 9.5 and Safari 3. on Windows XP.
+ *
+ * @author James Padolsey
+ * @version 1.11
+ *
+ * @id jQuery.pulse
+ * @id jQuery.recover
+ * @id jQuery.fn.pulse
+ * @id jQuery.fn.recover
+ */
+(function($){
+ $.fn.recover = function() {
+ /* Empty inline styles - i.e. set element back to previous state */
+ /* Note, the recovery might not work properly if you had inline styles set before pulse initiation */
+ return this.each(function(){$(this).stop().css({backgroundColor:'',color:'',borderLeftColor:'',borderRightColor:'',borderTopColor:'',borderBottomColor:'',opacity:1});});
+ }
+ $.fn.pulse = function(options){
+ var defaultOptions = {
+ textColors: [],
+ backgroundColors: [],
+ borderColors: [],
+ opacityPulse: true,
+ opacityRange: [],
+ speed: 1000,
+ duration: false,
+ runLength: false
+ }, o = $.extend(defaultOptions,options);
+ /* Validate custom options */
+ if(o.textColors.length===1||o.backgroundColors.length===1||o.borderColors.length===1) {return false;}
+ /* Begin: */
+ return this.each(function(){
+ var $t = $(this), pulseCount=1, pulseLimit = (o.runLength&&o.runLength>0) ? o.runLength*largestArrayLength([o.textColors.length,o.backgroundColors.length,o.borderColors.length,o.opacityRange.length]) : false;
+ clearTimeout(recover);
+ if(o.duration) {
+ setTimeout(recover,o.duration);
+ }
+ function nudgePulse(textColorIndex,bgColorIndex,borderColorIndex,opacityIndex) {
+ if(pulseLimit&&pulseCount===pulseLimit) {
+ return $t.recover();
+ }
+ pulseCount++;
+ /* Initiate color change - on callback continue */
+ return $t.animate(getColorsAtIndex(textColorIndex,bgColorIndex,borderColorIndex,opacityIndex),o.speed,function(){
+ /* Callback of each step */
+ nudgePulse(
+ getNextIndex(o.textColors,textColorIndex),
+ getNextIndex(o.backgroundColors,bgColorIndex),
+ getNextIndex(o.borderColors,borderColorIndex),
+ getNextIndex(o.opacityRange,opacityIndex)
+ );
+ });
+ }
+ /* Set CSS to first step (no animation) */
+ $t.css(getColorsAtIndex(0,0,0,0));
+ /* Then animate to second step */
+ nudgePulse(1,1,1,1);
+ function getColorsAtIndex(textColorIndex,bgColorIndex,borderColorIndex,opacityIndex) {
+ /* Prepare animation object - get's all property names/values from passed indexes */
+ var params = {};
+ if(o.backgroundColors.length) {
+ params['backgroundColor'] = o.backgroundColors[bgColorIndex];
+ }
+ if(o.textColors.length) {
+ params['color'] = o.textColors[textColorIndex];
+ }
+ if(o.borderColors.length) {
+ params['borderLeftColor'] = o.borderColors[borderColorIndex];
+ params['borderRightColor'] = o.borderColors[borderColorIndex];
+ params['borderTopColor'] = o.borderColors[borderColorIndex];
+ params['borderBottomColor'] = o.borderColors[borderColorIndex];
+ }
+ if(o.opacityPulse&&o.opacityRange.length) {
+ params['opacity'] = o.opacityRange[opacityIndex];
+ }
+ return params;
+ }
+ function getNextIndex(property,currentIndex) {
+ if (property.length>currentIndex+1) {return currentIndex+1;}
+ else {return 0;}
+ }
+ function largestArrayLength(arrayOfArrays) {
+ return Math.max.apply( Math, arrayOfArrays );
+ }
+ function recover() {
+ $t.recover();
+ }
+ });
+ }
+})(jQuery);
+/* The below code extends the animate function so that it works with color animations */
+/* By John Resig */
+(function(jQuery){
+jQuery.each(['backgroundColor','borderBottomColor','borderLeftColor','borderRightColor','borderTopColor','color','outlineColor'],function(i,attr){jQuery.fx.step[attr]=function(fx){if(fx.state==0){fx.start=getColor(fx.elem,attr);fx.end=getRGB(fx.end)}fx.elem.style[attr]="rgb("+[Math.max(Math.min(parseInt((fx.pos*(fx.end[0]-fx.start[0]))+fx.start[0]),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[1]-fx.start[1]))+fx.start[1]),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[2]-fx.start[2]))+fx.start[2]),255),0)].join(",")+")"}});
+function getRGB(color){var result;if(color&&color.constructor==Array&&color.length==3)return color;if(result=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)){return[parseInt(result[1]),parseInt(result[2]),parseInt(result[3])]}if(result=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)){return[parseFloat(result[1])*2.55,parseFloat(result[2])*2.55,parseFloat(result[3])*2.55]}if(result=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)){return[parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)]}if(result=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)){return[parseInt(result[1]+result[1],16),parseInt(result[2]+result[2],16),parseInt(result[3]+result[3],16)]}return colors[jQuery.trim(color).toLowerCase()]}
+function getColor(elem,attr){var color;do{color=jQuery.curCSS(elem,attr);if(color!=''&&color!='transparent'||jQuery.nodeName(elem,"body")){break}attr="backgroundColor"}while(elem=elem.parentNode);return getRGB(color)};
+var colors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};
+})(jQuery); \ No newline at end of file
diff --git a/etherpad/src/static/js/statpage.js b/etherpad/src/static/js/statpage.js
new file mode 100644
index 0000000..be2948c
--- /dev/null
+++ b/etherpad/src/static/js/statpage.js
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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() {
+ $('.title').click(function() {
+ var id = $(this).parent().attr("id")
+ toggleId(id);
+ // $(this).parent().children('.statbody').toggle();
+ });
+ $('.statbody').each(function() {
+ if (isVisible($(this).parent().attr("id"))) {
+ $(this).show();
+ }
+ });
+ var cat = window.location.hash.slice(1);
+ if (! cat) {
+ cat = "health";
+ }
+ $('.navlink').click(function() {
+ var cat = $(this).attr("id").slice(4);
+ showCategory(cat);
+ });
+ showCategory(cat);
+});
+
+function showCategory(cat) {
+ $('#fragment').val(cat);
+ $('.categorywrapper').each(function() {
+ var localCat = $(this).attr("id").slice(3);
+ if (localCat == cat) {
+ $('#link'+localCat).parent().addClass("selected");
+ $(this).show();
+ } else {
+ $('#link'+localCat).parent().removeClass("selected");
+ $(this).hide();
+ }
+ })
+}
+
+function formChanged() {
+ document.forms[0].submit();
+}
+
+if (! String.prototype.startsWith) {
+ String.prototype.startsWith = function(s) {
+ if (this.length < s.length) { return false; }
+ return this.substr(0, s.length) == s;
+ }
+}
+
+if (! String.prototype.trim) {
+ String.prototype.trim = function() {
+ var firstNonSpace;
+ for (var i = 0; i < this.length; ++i) {
+ if (this[i] != ' ') {
+ firstNonSpace = i;
+ break;
+ }
+ }
+ var s = this;
+ if (firstNonSpace) {
+ s = this.substr(firstNonSpace);
+ }
+ var lastNonSpace;
+ for (var i = this.length-1; i >= 0; --i) {
+ if (this[i] != ' ') {
+ lastNonSpace = i;
+ break;
+ }
+ }
+ if (lastNonSpace !== undefined) {
+ s = s.substr(0, lastNonSpace+1);
+ }
+ return s;
+ }
+}
+
+if (! Array.prototype.contains) {
+ Array.prototype.contains = function(obj) {
+ for (var i = 0; i < this.length; ++i) {
+ if (this[i] == obj) return true;
+ }
+ return false;
+ }
+}
+
+if (! Array.prototype.first) {
+ Array.prototype.first = function(f) {
+ for (var i = 0; i < this.length; ++i) {
+ if (f(this[i])) {
+ return this[i];
+ }
+ }
+ }
+}
+
+var cookieprefix = "visiblestats="
+
+function statsCookieValue() {
+ return (document.cookie.split(";").map(function(s) { return s.trim() }).first(function(str) {
+ return str.startsWith(cookieprefix);
+ }) || cookieprefix).split("=")[1];
+}
+
+function isVisible(id) {
+ var cookieValue = statsCookieValue();
+ return ! (cookieValue.split("-").contains(id));
+}
+
+function rememberHidden(id) {
+ if (! isVisible(id)) { return; }
+ document.cookie = cookieprefix+
+ statsCookieValue().split("-").concat([id]).join("-");
+}
+
+function rememberVisible(id) {
+ if (isVisible(id)) { return; }
+ document.cookie = cookieprefix+
+ statsCookieValue().split("-").filter(function(obj) { return obj != id }).join("-");
+}
+
+function toggleId(id) {
+ var body = $('#'+id).children('.statbody');
+ body.toggle();
+ if (body.is(":visible")) {
+ rememberVisible(id);
+ } else {
+ rememberHidden(id);
+ }
+} \ No newline at end of file
diff --git a/etherpad/src/static/js/store.js b/etherpad/src/static/js/store.js
new file mode 100644
index 0000000..5750f42
--- /dev/null
+++ b/etherpad/src/static/js/store.js
@@ -0,0 +1,116 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+
+store = {};
+
+$(document).ready(function() {
+ if ($('#downloadpage').size() > 0) {
+ $("#license_agree, #license_agree_label").click(function() {
+ if ($("#license_agree").attr("checked")) {
+ $("a.downloadbutton_disabled").removeClass("downloadbutton_disabled")
+ .addClass("downloadbutton")
+ .attr('href', '/ep/store/eepnet-download-nextsteps');
+ } else {
+ $("a.downloadbutton").removeClass("downloadbutton")
+ .addClass("downloadbutton_disabled")
+ .attr('href', 'javascript:void store.mustAgree()');
+ }
+ });
+ }
+
+ if ($('#eepnet_trial_signup_page').size() > 0) {
+ store.eepnetTrial.init();
+ }
+
+});
+
+store.mustAgree = function() {
+ alert("You must first click 'Accept License' before downloading this software.");
+};
+
+//----------------------------------------------------------------
+// trial download page
+//----------------------------------------------------------------
+
+store.eepnetTrial = {};
+
+store.eepnetTrial.init = function() {
+ $("#submit").attr("disabled", false);
+ $("input.signupData").keydown(function() {
+ $("#submit").attr("disabled", false);
+ });
+ $("input.signupData").change(function() {
+ $("#submit").attr("disabled", false);
+ });
+};
+
+store.eepnetTrial.handleError = function(msg) {
+ $('#processingmsg').hide();
+ $('#dlsignup').show();
+ $("#errormsg").hide().html(msg).fadeIn("fast");
+ var href = window.location.href;
+ href = href.split("#")[0];
+ window.location.href = (href + "#toph2");
+ $('#submit').attr('disabled', false);
+};
+
+store.eepnetTrial.submit = function() {
+
+ $("#errormsg").hide();
+ $('#dlsignup').hide();
+ $('#processingmsg').fadeIn('fast');
+
+ // first submit...
+ var data = {};
+ $(".signupData").each(function() {
+ data[$(this).attr("id")] = $(this).val();
+ });
+ data.industry = $('#industry').val();
+
+ $('#submit').attr('disabled', true);
+
+ $.ajax({
+ type: 'post',
+ url: '/ep/store/eepnet-eval-signup',
+ data: data,
+ success: success,
+ error: error
+ });
+
+ function success(text) {
+ var responseData = eval("("+text+")");
+ if (responseData.error) {
+ store.eepnetTrial.handleError(responseData.error);
+ return;
+ }
+
+ store.eepnetTrial.submitWebToLead(responseData);
+ }
+
+ function error(e) {
+ store.eepnetTrial.handleError("Oops! There was an error processing your request.");
+ }
+};
+
+store.eepnetTrial.submitWebToLead = function(data) {
+ for (k in data) {
+ $('#wl_'+k).val(data[k]);
+ }
+ setTimeout(function() { $('#wlform').submit(); }, 50);
+};
+
+
diff --git a/etherpad/src/static/js/swfobject.js b/etherpad/src/static/js/swfobject.js
new file mode 100644
index 0000000..b741304
--- /dev/null
+++ b/etherpad/src/static/js/swfobject.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+/**
+ * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
+ *
+ * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject; \ No newline at end of file
diff --git a/etherpad/src/static/js/timeslider.js b/etherpad/src/static/js/timeslider.js
new file mode 100644
index 0000000..552a971
--- /dev/null
+++ b/etherpad/src/static/js/timeslider.js
@@ -0,0 +1,663 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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 repeatString(str, times) {
+ if (times <= 0) return "";
+ var s = repeatString(str, times >> 1);
+ s += s;
+ if (times & 1) s += str;
+ return s;
+}
+function chr(n) { return String.fromCharCode(n+48); }
+function ord(c) { return c.charCodeAt(0)-48; }
+
+function map(array, func) {
+ var result = [];
+ // must remain compatible with "arguments" pseudo-array
+ for(var i=0;i<array.length;i++) {
+ if (func) result.push(func(array[i], i));
+ else result.push(array[i]);
+ }
+ return result;
+}
+
+function forEach(array, func) {
+ for(var i=0;i<array.length;i++) {
+ var result = func(array[i], i);
+ if (result) break;
+ }
+}
+
+function getText(padOpaqueRef, r, func/*(text, optErrorData)*/) {
+ doAjaxGet('/ep/pad/history/'+padOpaqueRef+'/text/'+Number(r),
+ function(data, optErrorData) {
+ if (optErrorData) {
+ func(null, optErrorData);
+ }
+ else {
+ var text = data.text;
+ func({text: text});
+ }
+ });
+}
+
+function getChanges(padOpaqueRef, first, last, func/*(data, optErrorData)*/) {
+ doAjaxGet('/ep/pad/history/'+padOpaqueRef+'/changes/'+Number(first)+'-'+Number(last),
+ function(data, optErrorData) {
+ if (optErrorData) {
+ func(null, optErrorData);
+ }
+ else {
+ func(uncompressChangesBlock({charPool: data.charPool,
+ changes: data.changes,
+ firstRev: first}));
+ }
+ });
+}
+
+function statPad(padOpaqueRef, func/*(atext, optErrorData)*/) {
+ doAjaxGet('/ep/pad/history/'+padOpaqueRef+'/stat',
+ function(data, optErrorData) {
+ if (optErrorData) {
+ func(null, optErrorData);
+ }
+ else {
+ var obj = {exists: data.exists};
+ if (obj.exists) {
+ obj.latestRev = data.latestRev;
+ }
+
+ func(obj);
+ }
+ });
+}
+
+function doAjaxGet(url, func/*(data, optErrorData)*/) {
+ $.ajax({
+ type: 'get',
+ dataType: 'json',
+ url: url,
+ success: function(data) {
+ if (data.error) {
+ func(null, {serverError: data});
+ }
+ else {
+ func(data);
+ }
+ },
+ error: function(xhr, textStatus, errorThrown) {
+ func(null, {clientError: { textStatus:textStatus, errorThrown: errorThrown }});
+ }
+ });
+}
+
+function uncompressChangesBlock(data) {
+ var charPool = data.charPool;
+ var changesArray = data.changes.split(',');
+ var firstRev = data.firstRev;
+
+ var changesBlock = {};
+ var changeStructs = [];
+ var charPoolIndex = 0;
+ var lastTimestamp = 0;
+ for(var i=0;i<changesArray.length;i++) {
+ var receiver = [null, 0];
+ var curString = changesArray[i];
+ function nextChar() {
+ return curString.charAt(receiver[1]);
+ }
+ function readChar() {
+ var c = nextChar();
+ receiver[1]++;
+ return c;
+ }
+ function readNum() {
+ return decodeVarInt(curString, receiver[1], receiver);
+ }
+ function readString() {
+ var len = readNum();
+ var str = charPool.substr(charPoolIndex, len);
+ charPoolIndex += len;
+ return str;
+ }
+ function readTimestamp() {
+ var absolute = false;
+ if (nextChar() == "+") {
+ readChar();
+ absolute = true;
+ }
+ var t = readNum()*1000;
+ if (! absolute) {
+ t += lastTimestamp;
+ }
+ lastTimestamp = t;
+ return t;
+ }
+ function atEnd() {
+ return receiver[1] >= curString.length;
+ }
+ var timestamp = readTimestamp();
+ var authorNum = readNum();
+ var splices = [];
+ while (! atEnd()) {
+ var spliceType = readChar();
+ var startChar = readNum();
+ var oldText = "";
+ var newText = "";
+ if (spliceType != '+') {
+ oldText = readString();
+ }
+ if (spliceType != '-') {
+ newText = readString();
+ }
+ splices.push([startChar,oldText,newText]);
+ }
+ changeStructs.push({t:timestamp, a:authorNum, splices:splices});
+ }
+
+ changesBlock.firstRev = firstRev;
+ changesBlock.changeStructs = changeStructs;
+
+ return changesBlock;
+}
+
+var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
+var BASE64_DIGIT_TO_NUM = (function() {
+ var map = {};
+ for(var i=0;i<BASE64_DIGITS.length;i++) {
+ map[BASE64_DIGITS.charAt(i)] = i;
+ }
+ return map;
+})();
+
+function decodeVarInt(stringIn, indexIn, numAndIndexOut) {
+ var n = 0;
+ var done = false;
+ var i = indexIn;
+ while (! done) {
+ var d = + BASE64_DIGIT_TO_NUM[stringIn.charAt(i++)];
+ if (isNaN(d)) return -1;
+ if ((d & 32) == 0) {
+ done = true;
+ }
+ n = n*32 + (d & 31);
+ }
+ if (numAndIndexOut) {
+ numAndIndexOut[0] = n;
+ numAndIndexOut[1] = i;
+ }
+ return n;
+}
+
+function escapeHTML(s) {
+ var re = /[&<>\n]/g;
+ if (! re.MAP) {
+ // persisted across function calls!
+ re.MAP = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '\n': '<br/>'
+ };
+ }
+ return s.replace(re, function(c) { return re.MAP[c]; });
+}
+
+var padOpaqueRef = clientVars.padOpaqueRef;
+var keyframes = []; // [rev, atext] pairs
+var changesBlocks = []; // [first, last, changesBlock]
+var lastRev;
+var lastRevLoaded = -1;
+var problemData = null;
+var curRev = -1;
+var curText = { lines: [/*string, length+1*/] };
+
+function setLastRevLoaded(r) {
+ lastRevLoaded = r;
+ //$("#sliderui").slider('option', 'max', lastRevLoaded);
+ $("#currevdisplay .max").html(String(lastRevLoaded));
+}
+
+function initialStat(continuation) {
+ statPad(padOpaqueRef, function(data, errorData) {
+ if (errorData) {
+ reportProblem(errorData);
+ continuation(false);
+ }
+ else {
+ if (! data.exists) {
+ reportProblem({msg: "Pad not found."});
+ continuation(false);
+ }
+ else {
+ lastRev = data.latestRev;
+ continuation(true);
+ return;
+ }
+ }
+ });
+}
+
+function loadKeyframe(r, continuation) {
+ getText(padOpaqueRef, r, function(data, errorData) {
+ if (errorData) {
+ reportProblem(errorData);
+ continuation(false);
+ }
+ else {
+ keyframes.push([r, data]);
+ keyframes.sort(function(a, b) {
+ return a[0] - b[0];
+ });
+ continuation(true);
+ }
+ });
+}
+
+function loadChangesBlock(first, last, continuation) {
+ getChanges(padOpaqueRef, first, last, function(data, errorData) {
+ if (errorData) {
+ reportProblem(errorData);
+ continuation(false);
+ }
+ else {
+ changesBlocks.push([first, last, data]);
+ continuation(true);
+ }
+ });
+}
+
+function loadThroughZero(continuation) {
+ initialStat(function(success) {
+ if (success) {
+ loadKeyframe(0, function(success) {
+ if (success) {
+ setLastRevLoaded(0);
+ continuation(true);
+ }
+ else continuation(false);
+ });
+ }
+ else continuation(false);
+ });
+}
+
+function loadMoreRevs(continuation) {
+ if (lastRevLoaded >= lastRev) {
+ continuation(true);
+ }
+ else {
+ var first = lastRevLoaded+1;
+ var last = first + 499;
+ if (last > lastRev) {
+ last = lastRev;
+ }
+ loadChangesBlock(first, last, function(success) {
+ if (success) {
+ loadKeyframe(last, function(success) {
+ if (success) {
+ setLastRevLoaded(last);
+ continuation(true);
+ }
+ else continuation(false);
+ });
+ }
+ else continuation(false);
+ });
+ }
+}
+
+function getDocTextForText(text) {
+ var lines = map(text.split('\n').slice(0, -1), function(s) {
+ return [s, s.length+1];
+ });
+ return { lines: lines };
+}
+
+function getLineAndChar(docText, charIndex) {
+ // returns [lineIndex, charIndexIntoLine];
+ // if the charIndex is after the final newline of the document,
+ // lineIndex may be == docText.lines.length.
+ // Otherwise, lneIndex is an actual line and charIndex
+ // is between 0 and the line's length inclusive.
+ var startLine = 0;
+ var startLineStartChar = 0;
+ var lines = docText.lines;
+ var done = false;
+ while (!done) {
+ if (startLine >= lines.length) {
+ done = true;
+ }
+ else {
+ var lineLength = lines[startLine][1];
+ var nextLineStart = startLineStartChar + lineLength;
+ if (nextLineStart <= charIndex) {
+ startLine++;
+ startLineStartChar = nextLineStart;
+ }
+ else {
+ done = true;
+ }
+ }
+ }
+ return [startLine, charIndex - startLineStartChar];
+}
+
+function applySplice(docText, splice, forward) {
+ var startChar = splice[0];
+ var oldText = splice[1];
+ var newText = splice[2];
+ if (! forward) {
+ var tmp = oldText;
+ oldText = newText;
+ newText = tmp;
+ }
+
+ //var OLD_FULL_TEXT = map(docText.lines, function(L) { return L[0]; }).join('\n')+'\n';
+ //var OLD_NUM_LINES = docText.lines.length;
+
+ var lines = docText.lines;
+ var startLineAndChar = getLineAndChar(docText, startChar);
+ var endLineAndChar = getLineAndChar(docText, startChar+oldText.length);
+
+ var lineSpliceStart = startLineAndChar[0];
+ var lineSpliceEnd = endLineAndChar[0];
+ var newLines = newText.split('\n');
+ // we want to splice in entire lines, so adjust start to include beginning of line
+ // we're starting to insert into
+ if (startLineAndChar[1] > 0) {
+ newLines[0] = lines[startLineAndChar[0]][0].substring(0, startLineAndChar[1]) + newLines[0];
+ }
+ // adjust end to include entire last line that will be changed
+ if (endLineAndChar[1] > 0 || newLines[newLines.length-1].length > 0) {
+ newLines[newLines.length-1] += lines[endLineAndChar[0]][0].substring(endLineAndChar[1]);
+ lineSpliceEnd += 1;
+ }
+ else {
+ // the splice is ok as is, except for an extra newline
+ newLines.pop();
+ }
+
+ var newLineEntries = map(newLines, function(s) {
+ return [s, s.length+1];
+ });
+
+ Array.prototype.splice.apply(lines,
+ [lineSpliceStart, lineSpliceEnd-lineSpliceStart].concat(newLineEntries));
+
+ // check it
+ //var EXPECTED_FULL_TEXT = OLD_FULL_TEXT.substring(0, startChar) + newText +
+ //OLD_FULL_TEXT.substring(startChar + oldText.length, OLD_FULL_TEXT.length);
+ //var ACTUAL_FULL_TEXT = map(docText.lines, function(L) { return L[0]; }).join('\n')+'\n';
+
+ //console.log("%o %o %o %d %d %d %d %d",
+ //docText.lines, startLineAndChar, endLineAndChar, OLD_NUM_LINES,
+ //lines.length, lineSpliceStart, lineSpliceEnd-lineSpliceStart, newLineEntries.length);
+
+ //if (EXPECTED_FULL_TEXT != ACTUAL_FULL_TEXT) {
+ //console.log(escapeHTML("mismatch: "+EXPECTED_FULL_TEXT+" / "+ACTUAL_FULL_TEXT));
+ //}
+
+ return [lineSpliceStart, lineSpliceEnd-lineSpliceStart, newLines];
+}
+
+function lineHTML(line) {
+ return (escapeHTML(line) || '&nbsp;');
+}
+
+function setCurText(docText, dontSetDom) {
+ curText = docText;
+ if (! dontSetDom) {
+ var docNode = $("#stuff");
+ var html = map(docText.lines, function(line) {
+ return '<div>'+lineHTML(line[0])+'</div>';
+ });
+ docNode.html(html.join(''));
+ }
+}
+
+function spliceDom(splice) {
+ var index = splice[0];
+ var numRemoved = splice[1];
+ var newLines = splice[2];
+
+ var overlap = Math.min(numRemoved, newLines.length);
+ var container = $("#stuff").get(0);
+ var oldNumNodes = container.childNodes.length;
+ var i = 0;
+ for(;i<overlap;i++) {
+ var n = container.childNodes.item(index+i);
+ $(n).html(lineHTML(newLines[i]));
+ }
+ for(;i<newLines.length;i++) {
+ var insertIndex = index+i;
+ var content = '<div>'+lineHTML(newLines[i])+'</div>';
+ if (insertIndex >= container.childNodes.length) {
+ $(container).append(content);
+ }
+ else {
+ $(container.childNodes.item(insertIndex)).before(content);
+ }
+ }
+ for(;i<numRemoved;i++) {
+ var deleteIndex = index+overlap;
+ $(container.childNodes.item(deleteIndex)).remove();
+ }
+
+ //console.log("%d %d %d %d %d", splice[0], splice[1], splice[2].length,
+ //oldNumNodes + newLines.length - numRemoved,
+ //container.childNodes.length);
+}
+
+function seekToRev(r) {
+ // precond: r is reachable
+
+ var isStep = false;
+
+ var bestKeyFrameIndex = -1;
+ var bestKeyFrameDistance = -1;
+ function considerKeyframe(index, kr) {
+ var dist = Math.abs(r - kr);
+ if (bestKeyFrameDistance < 0 || dist < bestKeyFrameDistance) {
+ bestKeyFrameDistance = dist;
+ bestKeyFrameIndex = index;
+ }
+ }
+ for(var i=0;i<keyframes.length;i++) {
+ considerKeyframe(i, keyframes[i][0]);
+ }
+ if (curRev >= 0) {
+ if (Math.abs(r - curRev) == 1) {
+ isStep = true;
+ bestKeyFrameIndex = -2; // -2 to mean "current revision"
+ }
+ else {
+ considerKeyframe(-2, curRev);
+ }
+ }
+
+ var docText = curText;
+ var docRev = curRev;
+ if (bestKeyFrameIndex >= 0) {
+ // some keyframe is better than moving from the current location;
+ // move to that keyframe
+ var keyframe = keyframes[bestKeyFrameIndex];
+ docRev = keyframe[0];
+ docText = getDocTextForText(keyframe[1].text);
+ }
+
+ var startRev = docRev;
+ var destRev = r;
+
+ var curChangesBlock = null;
+ function findChangesBlockFor(n) {
+ function changesBlockWorks(arr) {
+ return n >= arr[0] && n <= arr[1];
+ }
+ if (curChangesBlock == null || ! changesBlockWorks(curChangesBlock)) {
+ curChangesBlock = null;
+ for(var i=0;i<changesBlocks.length;i++) {
+ var cba = changesBlocks[i];
+ if (changesBlockWorks(cba)) {
+ curChangesBlock = cba;
+ break;
+ }
+ }
+ }
+ }
+
+ //var DEBUG_REVS_APPLIED = [];
+
+ function applyRev(n, forward) {
+ findChangesBlockFor(n);
+ var cb = curChangesBlock[2];
+ var idx = n - curChangesBlock[0];
+ var chng = cb.changeStructs[idx];
+
+ var splices = chng.splices;
+ if (forward) {
+ for(var i=0;i<splices.length;i++) {
+ var splice = applySplice(docText, splices[i], true);
+ if (isStep) spliceDom(splice);
+ }
+ }
+ else {
+ for(var i=splices.length-1;i>=0;i--) {
+ var splice = applySplice(docText, splices[i], false);
+ if (isStep) spliceDom(splice);
+ }
+ }
+
+ //DEBUG_REVS_APPLIED.push(n);
+ }
+
+ if (destRev > startRev) {
+ for (var j=startRev+1; j<=destRev; j++) {
+ applyRev(j, true);
+ }
+ }
+ else if (destRev < startRev) {
+ for(var j=startRev; j >= destRev+1; j--) {
+ applyRev(j, false);
+ }
+ }
+
+ docRev = destRev;
+
+ setCurText(docText, isStep);
+ curRev = docRev;
+ $("#currevdisplay .cur").html(String(curRev));
+}
+
+function reportProblem(probData) {
+ problemData = probData;
+ if (probData.msg) {
+ $("#stuff").html(escapeHTML(probData.msg));
+ }
+}
+
+var playTimer = null;
+
+$(function() {
+ /*$("#sliderui").slider({min: 0, max: 0, value: 0, step: 1, change: slidechange});
+ function slidechange(event, ui) {
+ alert("HELLO");
+ var value = ui.value;
+ console.log(value);
+ }*/
+
+ $("#controls .next").click(function() {
+ if (curRev < lastRevLoaded) {
+ seekToRev(curRev+1);
+ }
+ return false;
+ });
+
+ $("#controls .prev").click(function() {
+ if (curRev > 0) {
+ seekToRev(curRev-1);
+ }
+ return false;
+ });
+
+ function stop() {
+ if (playTimer) {
+ clearInterval(playTimer);
+ playTimer = null;
+ }
+ }
+
+ function play() {
+ stop();
+ playTimer = setInterval(function() {
+ if (curRev < lastRevLoaded) {
+ seekToRev(curRev+1);
+ }
+ else {
+ stop();
+ }
+ }, 60);
+ return false;
+ }
+
+ $("#controls .play").click(play);
+
+ $("#controls .stop").click(function() {
+ stop();
+ return false;
+ });
+
+ $("#controls .entry").change(function() {
+ var value = $("#controls .entry").val();
+ value = Number(value || 0);
+ if (isNaN(value)) value = 0;
+ if (value < 0) value = 0;
+ if (value > lastRevLoaded) {
+ value = lastRevLoaded;
+ }
+ $("#controls .entry").val('');
+ seekToRev(value);
+ });
+ $("#controls .entry").val('');
+
+ var useAutoplay = true;
+ var hasAutoplayed = false;
+
+ loadThroughZero(function(success) {
+ if (success) {
+ seekToRev(0);
+
+ function loadMoreRevsIfNecessary(continuation) {
+ if (lastRevLoaded < lastRev) {
+ loadMoreRevs(continuation);
+ }
+ }
+ loadMoreRevsIfNecessary(function cont(success) {
+ if (success) {
+ if (lastRevLoaded > 0 && useAutoplay && ! hasAutoplayed) {
+ hasAutoplayed = true;
+ play();
+ }
+ loadMoreRevsIfNecessary(cont);
+ }
+ });
+ }
+ });
+});
+
diff --git a/etherpad/src/static/js/undo-xpopup.js b/etherpad/src/static/js/undo-xpopup.js
new file mode 100644
index 0000000..89cfb4d
--- /dev/null
+++ b/etherpad/src/static/js/undo-xpopup.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * 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.
+ */
+
+if (window._orig_windowOpen) {
+ window.open = _orig_windowOpen;
+}
+if (window._orig_windowSetTimeout) {
+ window.setTimeout = _orig_windowSetTimeout;
+}
+if (window._orig_windowSetInterval) {
+ window.setInterval = _orig_windowSetInterval;
+}
diff --git a/etherpad/src/static/robots.txt b/etherpad/src/static/robots.txt
new file mode 100644
index 0000000..4f9540b
--- /dev/null
+++ b/etherpad/src/static/robots.txt
@@ -0,0 +1 @@
+User-agent: * \ No newline at end of file