Conflicts:
	static/favicon.ico
	static/index.html
	static/pad.html
This commit is contained in:
Eric Martindale 2012-03-01 05:39:08 -05:00
commit 326de3ad26
92 changed files with 4501 additions and 9505 deletions

6
.gitignore vendored
View file

@ -1,6 +1,7 @@
node_modules
settings.json
static/js/jquery.min.js
static/js/jquery.js
static/js/prefixfree.js
APIKEY.txt
bin/abiword.exe
bin/node.exe
@ -8,4 +9,5 @@ etherpad-lite-win.zip
var/dirty.db
bin/convertSettings.json
*~
*.patch
*.patch
*.DS_Store

202
LICENSE Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2012 THE ETHERPAD FOUNDATION
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.

View file

@ -58,7 +58,7 @@ Here is the **[FAQ](https://github.com/Pita/etherpad-lite/wiki/FAQ)**
<ol>
<li>Install the dependencies. We need gzip, git, curl, libssl develop libraries, python and gcc. <br><i>For Debian/Ubuntu</i> <code>apt-get install gzip git-core curl python libssl-dev build-essential</code><br>
<i>For Fedora/CentOS</i> <code>yum install gzip git-core curl python openssl-dev && yum groupinstall "Development Tools"</code>
<i>For Fedora/CentOS</i> <code>yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"</code>
</li><br>
<li>Install node.js
<ol type="a">
@ -81,6 +81,8 @@ Here is the **[FAQ](https://github.com/Pita/etherpad-lite/wiki/FAQ)**
## Next Steps
You can modify the settings in the file `settings.json`
If you have multiple settings files, you may pass one to `bin/run.sh` using the `-s|--settings` option. This allows you to run multiple Etherpad Lite instances from the same installation.
You should use a dedicated database such as "mysql" if you are planning on using etherpad-lite in a production environment, the "dirty" database driver is only for testing and/or development purposes.
You can update to the latest version with `git pull origin`. The next start with bin/run.sh will update the dependencies
@ -98,7 +100,7 @@ Look at this wiki pages:
You can find more information in the [wiki](https://github.com/Pita/etherpad-lite/wiki). Feel free to improve these wiki pages
# Develop
If you're new to git and github, start here <http://learn.github.com/p/intro.html>.
If you're new to git and github, start by watching [this video](http://youtu.be/67-Q26YH97E) then read this [git guide](http://learn.github.com/p/intro.html).
If you're new to node.js, start with this video <http://youtu.be/jo_B4LTHi3I>.
@ -111,11 +113,18 @@ You can join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev)
You also help the project, if you only host a Etherpad Lite instance and share your experience with us.
Please consider using [jshint](http://www.jshint.com/about/) if you plan to
contribute to Etherpad Lite.
# Modules created for this project
* [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access
* [channels](https://github.com/Pita/channels) "Event channels in node.js" - ensures that ueberDB operations are atomic and in series for each key
* [async-stacktrace](https://github.com/Pita/async-stacktrace) "Improves node.js stacktraces and makes it easier to handle errors"
# Donations
* [Etherpad Foundation Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation)
* [Paypal] (http://etherpad.org) <-- Click the donate button
# License
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)

View file

@ -15,7 +15,8 @@ var log4js = require("log4js");
log4js.setGlobalLogLevel("INFO");
var async = require("async");
var db = require('../node/db/DB');
var Changeset = require('../node/utils/Changeset');
var CommonCode = require('../node/utils/common_code');
var Changeset = CommonCode.require("/Changeset");
var padManager;
async.series([

View file

@ -1,10 +1,12 @@
var CommonCode = require('../node/utils/common_code');
var startTime = new Date().getTime();
var fs = require("fs");
var ueberDB = require("ueberDB");
var mysql = require("mysql");
var async = require("async");
var Changeset = require("../node/utils/Changeset");
var AttributePoolFactory = require("../node/utils/AttributePoolFactory");
var Changeset = CommonCode.require("/Changeset");
var randomString = CommonCode.require('/pad_utils').randomString;
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var settingsFile = process.argv[2];
var sqlOutputFile = process.argv[3];
@ -450,18 +452,3 @@ function parsePage(array, pageStart, offsets, data, json)
start+=unitLength;
}
}
/**
* Generates a random String with the given length. Is needed to generate the Author Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}

View file

@ -16,7 +16,7 @@ hash curl > /dev/null 2>&1 || {
#Is node installed?
hash node > /dev/null 2>&1 || {
echo "Please install node.js ( http://nodesjs.org )" >&2
echo "Please install node.js ( http://nodejs.org )" >&2
exit 1
}
@ -33,10 +33,25 @@ if [ ! $(echo $NPM_VERSION | cut -d "." -f 1) = "1" ]; then
exit 1
fi
#Does a settings.json exist? if no copy the template
if [ ! -f "settings.json" ]; then
echo "Copy the settings template to settings.json..."
cp -v settings.json.template settings.json || exit 1
#check node version
NODE_VERSION=$(node --version)
if [ ! $(echo $NODE_VERSION | cut -d "." -f 1-2) = "v0.6" ]; then
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x" >&2
exit 1
fi
#Get the name of the settings file
settings="settings.json"
a='';
for arg in $*; do
if [ "$a" = "--settings" ] || [ "$a" = "-s" ]; then settings=$arg; fi
a=$arg
done
#Does a $settings exist? if no copy the template
if [ ! -f $settings ]; then
echo "Copy the settings template to $settings..."
cp -v settings.json.template $settings || exit 1
fi
echo "Ensure that all dependencies are up to date..."
@ -47,9 +62,9 @@ npm install || {
echo "Ensure jQuery is downloaded and up to date..."
DOWNLOAD_JQUERY="true"
NEEDED_VERSION="1.7"
if [ -f "static/js/jquery.min.js" ]; then
VERSION=$(cat static/js/jquery.min.js | head -n 3 | grep -o "v[0-9].[0-9]");
NEEDED_VERSION="1.7.1"
if [ -f "static/js/jquery.js" ]; then
VERSION=$(cat static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");
if [ ${VERSION#v} = $NEEDED_VERSION ]; then
DOWNLOAD_JQUERY="false"
@ -57,7 +72,22 @@ if [ -f "static/js/jquery.min.js" ]; then
fi
if [ $DOWNLOAD_JQUERY = "true" ]; then
curl -lo static/js/jquery.min.js http://code.jquery.com/jquery-$NEEDED_VERSION.min.js || exit 1
curl -lo static/js/jquery.js http://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1
fi
echo "Ensure prefixfree is downloaded and up to date..."
DOWNLOAD_PREFIXFREE="true"
NEEDED_VERSION="1.0.4"
if [ -f "static/js/prefixfree.js" ]; then
VERSION=$(cat static/js/prefixfree.js | grep "PrefixFree" | grep -o "[0-9].[0-9].[0-9]");
if [ $VERSION = $NEEDED_VERSION ]; then
DOWNLOAD_PREFIXFREE="false"
fi
fi
if [ $DOWNLOAD_PREFIXFREE = "true" ]; then
curl -lo static/js/prefixfree.js -k https://raw.github.com/LeaVerou/prefixfree/master/prefixfree.js || exit 1
fi
#Remove all minified data to force node creating it new

9
bin/jshint.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
if [ -d "../bin" ]; then
cd "../"
fi
JSHINT=./node_modules/jshint/bin/hint
$JSHINT ./node/

75
bin/loadTesting/README Normal file
View file

@ -0,0 +1,75 @@
This load tester is extremely useful for testing how many dormant clients can connect to etherpad lite.
TODO:
Emulate characters being typed into a pad
HOW TO USE (from @mjd75) proper formatting at: https://github.com/Pita/etherpad-lite/issues/360
Server 1:
Installed Node.js (etc), EtherPad Lite and MySQL
Server 2:
Installed Xvfb and PhantomJS
I installed Xvfb following (roughly) this guide: http://blog.martin-lyness.com/archives/installing-xvfb-on-ubuntu-9-10-karmic-koala
#sudo apt-get install xvfb
#sudo apt-get install xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic
Launched two instances of Xvfb directly from the terminal:
#Xvfb :0 -ac
#Xvfb :1 -ac
I installed PhantomJS following this guide: http://code.google.com/p/phantomjs/wiki/Installation
#sudo add-apt-repository ppa:jerome-etienne/neoip
#sudo apt-get update
#sudo apt-get install phantomjs
I created a small JavaScript file for PhatomJS to use to control the browser instances:
### BEGIN JAVASCRIPT ###
var page = new WebPage(),
t, address;
if (phantom.args.length === 0) {
console.log('Usage: loader.js <some URL>');
phantom.exit();
} else {
t = Date.now();
address = phantom.args[0];
var page = new WebPage();
page.onResourceRequested = function (request) {
console.log('Request ' + JSON.stringify(request, undefined, 4));
};
page.onResourceReceived = function (response) {
console.log('Receive ' + JSON.stringify(response, undefined, 4));
};
page.open(address);
}
### END JAVASCRIPT ###
And finally a launcher script that uses screen to run 400 instances of PhantomJS with the above script:
### BEGIN SHELL SCRIPT ###
#!/bin/bash
# connect 200 instances to display :0
for i in {1..200}
do
DISPLAY=:0 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2
done
# connect 200 instances to display :1
for i in {1..200}
do
DISPLAY=:1 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2
done
### END SHELL SCRIPT ###

16
bin/loadTesting/launcher.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# connect 500 instances to display :0
for i in {1..500}
do
echo $i
echo "Displaying Some shit"
DISPLAY=:0 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2
done
# connect 500 instances to display :1
for i in {1..500}
do
echo $i
DISPLAY=:1 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2
done

20
bin/loadTesting/loader.js Normal file
View file

@ -0,0 +1,20 @@
var page = new WebPage(),
t, address;
if (phantom.args.length === 0) {
console.log('Usage: loader.js <some URL>');
phantom.exit();
} else {
t = Date.now();
address = phantom.args[0];
var page = new WebPage();
page.onResourceRequested = function (request) {
console.log('Request ' + JSON.stringify(request, undefined, 4));
};
page.onResourceReceived = function (response) {
console.log('Receive ' + JSON.stringify(response, undefined, 4));
};
page.open(address);
}

View file

@ -21,9 +21,9 @@ if [ "$(id -u)" -eq 0 ]; then
fi
#prepare the enviroment
bin/installDeps.sh || exit 1
bin/installDeps.sh $* || exit 1
#Move to the node folder and start
echo "start..."
cd "node"
node server.js
node server.js $*

View file

@ -6,7 +6,7 @@
# Module name conventions
Module file names starts with a capital letter and uses camelCase
Module file names start with a capital letter and uses camelCase
# Where does it start?

View file

@ -293,7 +293,7 @@ Example returns:
exports.createPad = function(padID, text, callback)
{
//ensure there is no $ in the padID
if(padID.indexOf("$") != -1)
if(padID && padID.indexOf("$") != -1)
{
callback(new customError("createPad can't create group pads","apierror"));
return;
@ -360,7 +360,7 @@ Example returns:
exports.setPublicStatus = function(padID, publicStatus, callback)
{
//ensure this is a group pad
if(padID.indexOf("$") == -1)
if(padID && padID.indexOf("$") == -1)
{
callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror"));
return;
@ -393,7 +393,7 @@ Example returns:
exports.getPublicStatus = function(padID, callback)
{
//ensure this is a group pad
if(padID.indexOf("$") == -1)
if(padID && padID.indexOf("$") == -1)
{
callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror"));
return;
@ -419,7 +419,7 @@ Example returns:
exports.setPassword = function(padID, password, callback)
{
//ensure this is a group pad
if(padID.indexOf("$") == -1)
if(padID && padID.indexOf("$") == -1)
{
callback(new customError("You can only get/set the password of pads that belong to a group","apierror"));
return;
@ -448,7 +448,7 @@ Example returns:
exports.isPasswordProtected = function(padID, callback)
{
//ensure this is a group pad
if(padID.indexOf("$") == -1)
if(padID && padID.indexOf("$") == -1)
{
callback(new customError("You can only get/set the password of pads that belong to a group","apierror"));
return;

View file

@ -18,9 +18,11 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
var randomString = CommonCode.require('/pad_utils').randomString;
/**
* Checks if the author exists
@ -177,18 +179,3 @@ exports.setAuthorName = function (author, name, callback)
{
db.setSub("globalAuthor:" + author, ["name"], name, callback);
}
/**
* Generates a random String with the given length. Is needed to generate the Author Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}

View file

@ -18,8 +18,10 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var padManager = require("./PadManager");
@ -247,26 +249,15 @@ exports.listPads = function(groupID, callback)
//group exists, let's get the pads
else
{
db.getSub("group:" + groupID, ["pads"], function(err, pads)
db.getSub("group:" + groupID, ["pads"], function(err, result)
{
if(ERR(err, callback)) return;
var pads = [];
for ( var padId in result ) {
pads.push(padId);
}
callback(null, {padIDs: pads});
});
}
});
}
/**
* Generates a random String with the given length. Is needed to generate the Author Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}

View file

@ -2,11 +2,11 @@
* The pad object, defined with joose
*/
require('joose');
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory");
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var settings = require('../utils/Settings');
@ -22,490 +22,451 @@ var crypto = require("crypto");
*/
exports.cleanText = function (txt) {
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
}
};
Class('Pad', {
// these are the properties
has : {
atext : {
is : 'rw', // readwrite
init : function() { return Changeset.makeAText("\n"); } // first value
}, // atext
pool : {
is: 'rw',
init : function() { return AttributePoolFactory.createAttributePool(); },
getterName : 'apool' // legacy
}, // pool
head : {
is : 'rw',
init : -1,
getterName : 'getHeadRevisionNumber'
}, // head
chatHead : {
is: 'rw',
init: -1
}, // chatHead
publicStatus : {
is: 'rw',
init: false,
getterName : 'getPublicStatus'
}, //publicStatus
passwordHash : {
is: 'rw',
init: null
}, // passwordHash
id : { is : 'r' }
},
var Pad = function Pad(id) {
methods : {
BUILD : function (id)
{
return {
'id' : id,
}
},
appendRevision : function(aChangeset, author)
{
if(!author)
author = '';
this.atext = Changeset.makeAText("\n");
this.pool = AttributePoolFactory.createAttributePool();
this.head = -1;
this.chatHead = -1;
this.publicStatus = false;
this.passwordHash = null;
this.id = id;
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
Changeset.copyAText(newAText, this.atext);
var newRev = ++this.head;
var newRevData = {};
newRevData.changeset = aChangeset;
newRevData.meta = {};
newRevData.meta.author = author;
newRevData.meta.timestamp = new Date().getTime();
//ex. getNumForAuthor
if(author != '')
this.pool.putAttrib(['author', author || '']);
if(newRev % 100 == 0)
{
newRevData.meta.atext = this.atext;
}
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
db.set("pad:"+this.id, {atext: this.atext,
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
publicStatus: this.publicStatus,
passwordHash: this.passwordHash});
}, //appendRevision
getRevisionChangeset : function(revNum, callback)
};
exports.Pad = Pad;
Pad.prototype.apool = function apool() {
return this.pool;
};
Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() {
return this.head;
};
Pad.prototype.getPublicStatus = function getPublicStatus() {
return this.publicStatus;
};
Pad.prototype.appendRevision = function appendRevision(aChangeset, author) {
if(!author)
author = '';
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
Changeset.copyAText(newAText, this.atext);
var newRev = ++this.head;
var newRevData = {};
newRevData.changeset = aChangeset;
newRevData.meta = {};
newRevData.meta.author = author;
newRevData.meta.timestamp = new Date().getTime();
//ex. getNumForAuthor
if(author != '')
this.pool.putAttrib(['author', author || '']);
if(newRev % 100 == 0)
{
newRevData.meta.atext = this.atext;
}
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
db.set("pad:"+this.id, {atext: this.atext,
pool: this.pool.toJsonable(),
head: this.head,
chatHead: this.chatHead,
publicStatus: this.publicStatus,
passwordHash: this.passwordHash});
};
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
};
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
};
Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) {
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
};
Pad.prototype.getAllAuthors = function getAllAuthors() {
var authors = [];
for(key in this.pool.numToAttrib)
{
if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "")
{
db.getSub("pad:"+this.id+":revs:"+revNum, ["changeset"], callback);
}, // getRevisionChangeset
getRevisionAuthor : function(revNum, callback)
{
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "author"], callback);
}, // getRevisionAuthor
getRevisionDate : function(revNum, callback)
{
db.getSub("pad:"+this.id+":revs:"+revNum, ["meta", "timestamp"], callback);
}, // getRevisionAuthor
getAllAuthors : function()
authors.push(this.pool.numToAttrib[key][1]);
}
}
return authors;
};
Pad.prototype.getInternalRevisionAText = function getInternalRevisionAText(targetRev, callback) {
var _this = this;
var keyRev = this.getKeyRevisionNumber(targetRev);
var atext;
var changesets = [];
//find out which changesets are needed
var neededChangesets = [];
var curRev = keyRev;
while (curRev < targetRev)
{
curRev++;
neededChangesets.push(curRev);
}
async.series([
//get all needed data out of the database
function(callback)
{
var authors = [];
for(key in this.pool.numToAttrib)
{
if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "")
async.parallel([
//get the atext of the key revision
function (callback)
{
authors.push(this.pool.numToAttrib[key][1]);
db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
{
if(ERR(err, callback)) return;
atext = Changeset.cloneAText(_atext);
callback();
});
},
//get all needed changesets
function (callback)
{
async.forEach(neededChangesets, function(item, callback)
{
_this.getRevisionChangeset(item, function(err, changeset)
{
if(ERR(err, callback)) return;
changesets[item] = changeset;
callback();
});
}, callback);
}
}
return authors;
], callback);
},
getInternalRevisionAText : function(targetRev, callback)
//apply all changesets to the key changeset
function(callback)
{
var _this = this;
var keyRev = this.getKeyRevisionNumber(targetRev);
var atext;
var changesets = [];
//find out which changesets are needed
var neededChangesets = [];
var apool = _this.apool();
var curRev = keyRev;
while (curRev < targetRev)
while (curRev < targetRev)
{
curRev++;
neededChangesets.push(curRev);
var cs = changesets[curRev];
atext = Changeset.applyToAText(cs, atext, apool);
}
async.series([
//get all needed data out of the database
function(callback)
{
async.parallel([
//get the atext of the key revision
function (callback)
{
db.getSub("pad:"+_this.id+":revs:"+keyRev, ["meta", "atext"], function(err, _atext)
{
if(ERR(err, callback)) return;
atext = Changeset.cloneAText(_atext);
callback();
});
},
//get all needed changesets
function (callback)
{
async.forEach(neededChangesets, function(item, callback)
{
_this.getRevisionChangeset(item, function(err, changeset)
{
if(ERR(err, callback)) return;
changesets[item] = changeset;
callback();
});
}, callback);
}
], callback);
},
//apply all changesets to the key changeset
function(callback)
{
var apool = _this.apool();
var curRev = keyRev;
while (curRev < targetRev)
{
curRev++;
var cs = changesets[curRev];
atext = Changeset.applyToAText(cs, atext, apool);
}
callback(null);
}
], function(err)
{
if(ERR(err, callback)) return;
callback(null, atext);
});
},
getKeyRevisionNumber : function(revNum)
{
return Math.floor(revNum / 100) * 100;
},
text : function()
{
return this.atext.text;
},
setText : function(newText)
{
//clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
//append the changeset
this.appendRevision(changeset);
},
appendChatMessage: function(text, userId, time)
{
callback(null);
}
], function(err)
{
if(ERR(err, callback)) return;
callback(null, atext);
});
};
Pad.prototype.getKeyRevisionNumber = function getKeyRevisionNumber(revNum) {
return Math.floor(revNum / 100) * 100;
};
Pad.prototype.text = function text() {
return this.atext.text;
};
Pad.prototype.setText = function setText(newText) {
//clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
var changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
//append the changeset
this.appendRevision(changeset);
};
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++;
//save the chat entry in the database
db.set("pad:"+this.id+":chat:"+this.chatHead, {"text": text, "userId": userId, "time": time});
//save the new chat head
db.setSub("pad:"+this.id, ["chatHead"], this.chatHead);
},
getChatMessage: function(entryNum, callback)
{
var _this = this;
var entry;
async.series([
//get the chat entry
function(callback)
{
db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry)
{
if(ERR(err, callback)) return;
entry = _entry;
callback();
});
},
//add the authorName
function(callback)
{
//this chat message doesn't exist, return null
if(entry == null)
{
callback();
return;
}
//get the authorName
authorManager.getAuthorName(entry.userId, function(err, authorName)
{
if(ERR(err, callback)) return;
entry.userName = authorName;
callback();
});
}
], function(err)
};
Pad.prototype.getChatMessage = function getChatMessage(entryNum, callback) {
var _this = this;
var entry;
async.series([
//get the chat entry
function(callback)
{
db.get("pad:"+_this.id+":chat:"+entryNum, function(err, _entry)
{
if(ERR(err, callback)) return;
callback(null, entry);
entry = _entry;
callback();
});
},
getLastChatMessages: function(count, callback)
//add the authorName
function(callback)
{
//return an empty array if there are no chat messages
if(this.chatHead == -1)
//this chat message doesn't exist, return null
if(entry == null)
{
callback(null, []);
callback();
return;
}
var _this = this;
//works only if we decrement the amount, for some reason
count--;
//set the startpoint
var start = this.chatHead-count;
if(start < 0)
start = 0;
//set the endpoint
var end = this.chatHead;
//collect the numbers of chat entries and in which order we need them
var neededEntries = [];
var order = 0;
for(var i=start;i<=end; i++)
{
neededEntries.push({entryNum:i, order: order});
order++;
}
//get all entries out of the database
var entries = [];
async.forEach(neededEntries, function(entryObject, callback)
{
_this.getChatMessage(entryObject.entryNum, function(err, entry)
{
if(ERR(err, callback)) return;
entries[entryObject.order] = entry;
callback();
});
}, function(err)
{
if(ERR(err, callback)) return;
//sort out broken chat entries
//it looks like in happend in the past that the chat head was
//incremented, but the chat message wasn't added
var cleanedEntries = [];
for(var i=0;i<entries.length;i++)
{
if(entries[i]!=null)
cleanedEntries.push(entries[i]);
else
console.warn("WARNING: Found broken chat entry in pad " + _this.id);
}
callback(null, cleanedEntries);
});
},
init : function (text, callback)
{
var _this = this;
//replace text with default text if text isn't set
if(text == null)
{
text = settings.defaultPadText;
}
//try to load the pad
db.get("pad:"+this.id, function(err, value)
{
if(ERR(err, callback)) return;
//if this pad exists, load it
if(value != null)
{
_this.head = value.head;
_this.atext = value.atext;
_this.pool = _this.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead != null)
_this.chatHead = value.chatHead;
else
_this.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
_this.publicStatus = value.publicStatus;
else
_this.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
_this.passwordHash = value.passwordHash;
else
_this.passwordHash = null;
}
//this pad doesn't exist, so create it
else
{
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
_this.appendRevision(firstChangeset, '');
}
callback(null);
});
},
remove: function(callback)
{
var padID = this.id;
var _this = this;
//kick everyone from this pad
padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
function(callback)
{
async.parallel([
//is it a group pad? -> delete the entry of this pad in the group
function(callback)
{
//is it a group pad?
if(padID.indexOf("$")!=-1)
{
var groupID = padID.substring(0,padID.indexOf("$"));
db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
//remove the pad entry
delete group.pads[padID];
//set the new value
db.set("group:" + groupID, group);
callback();
});
}
//its no group pad, nothing to do here
else
{
callback();
}
},
//remove the readonly entries
function(callback)
{
readOnlyManager.getReadOnlyId(padID, function(err, readonlyID)
{
if(ERR(err, callback)) return;
db.remove("pad2readonly:" + padID);
db.remove("readonly2pad:" + readonlyID);
callback();
});
},
//delete all chat messages
function(callback)
{
var chatHead = _this.chatHead;
for(var i=0;i<=chatHead;i++)
{
db.remove("pad:"+padID+":chat:"+i);
}
callback();
},
//delete all revisions
function(callback)
{
var revHead = _this.head;
for(var i=0;i<=revHead;i++)
{
db.remove("pad:"+padID+":revs:"+i);
}
callback();
}
], callback);
},
//delete the pad entry and delete pad from padManager
function(callback)
{
db.remove("pad:"+padID);
padManager.unloadPad(padID);
callback();
}
], function(err)
//get the authorName
authorManager.getAuthorName(entry.userId, function(err, authorName)
{
if(ERR(err, callback)) return;
entry.userName = authorName;
callback();
})
},
//set in db
setPublicStatus: function(publicStatus)
{
this.publicStatus = publicStatus;
db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
},
setPassword: function(password)
{
this.passwordHash = password == null ? null : hash(password, generateSalt());
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
},
isCorrectPassword: function(password)
{
return compare(this.passwordHash, password)
},
isPasswordProtected: function()
{
return this.passwordHash != null;
});
}
}, // methods
});
], function(err)
{
if(ERR(err, callback)) return;
callback(null, entry);
});
};
Pad.prototype.getLastChatMessages = function getLastChatMessages(count, callback) {
//return an empty array if there are no chat messages
if(this.chatHead == -1)
{
callback(null, []);
return;
}
var _this = this;
//works only if we decrement the amount, for some reason
count--;
//set the startpoint
var start = this.chatHead-count;
if(start < 0)
start = 0;
//set the endpoint
var end = this.chatHead;
//collect the numbers of chat entries and in which order we need them
var neededEntries = [];
var order = 0;
for(var i=start;i<=end; i++)
{
neededEntries.push({entryNum:i, order: order});
order++;
}
//get all entries out of the database
var entries = [];
async.forEach(neededEntries, function(entryObject, callback)
{
_this.getChatMessage(entryObject.entryNum, function(err, entry)
{
if(ERR(err, callback)) return;
entries[entryObject.order] = entry;
callback();
});
}, function(err)
{
if(ERR(err, callback)) return;
//sort out broken chat entries
//it looks like in happend in the past that the chat head was
//incremented, but the chat message wasn't added
var cleanedEntries = [];
for(var i=0;i<entries.length;i++)
{
if(entries[i]!=null)
cleanedEntries.push(entries[i]);
else
console.warn("WARNING: Found broken chat entry in pad " + _this.id);
}
callback(null, cleanedEntries);
});
};
Pad.prototype.init = function init(text, callback) {
var _this = this;
//replace text with default text if text isn't set
if(text == null)
{
text = settings.defaultPadText;
}
//try to load the pad
db.get("pad:"+this.id, function(err, value)
{
if(ERR(err, callback)) return;
//if this pad exists, load it
if(value != null)
{
_this.head = value.head;
_this.atext = value.atext;
_this.pool = _this.pool.fromJsonable(value.pool);
//ensure we have a local chatHead variable
if(value.chatHead != null)
_this.chatHead = value.chatHead;
else
_this.chatHead = -1;
//ensure we have a local publicStatus variable
if(value.publicStatus != null)
_this.publicStatus = value.publicStatus;
else
_this.publicStatus = false;
//ensure we have a local passwordHash variable
if(value.passwordHash != null)
_this.passwordHash = value.passwordHash;
else
_this.passwordHash = null;
}
//this pad doesn't exist, so create it
else
{
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
_this.appendRevision(firstChangeset, '');
}
callback(null);
});
};
Pad.prototype.remove = function remove(callback) {
var padID = this.id;
var _this = this;
//kick everyone from this pad
padMessageHandler.kickSessionsFromPad(padID);
async.series([
//delete all relations
function(callback)
{
async.parallel([
//is it a group pad? -> delete the entry of this pad in the group
function(callback)
{
//is it a group pad?
if(padID.indexOf("$")!=-1)
{
var groupID = padID.substring(0,padID.indexOf("$"));
db.get("group:" + groupID, function (err, group)
{
if(ERR(err, callback)) return;
//remove the pad entry
delete group.pads[padID];
//set the new value
db.set("group:" + groupID, group);
callback();
});
}
//its no group pad, nothing to do here
else
{
callback();
}
},
//remove the readonly entries
function(callback)
{
readOnlyManager.getReadOnlyId(padID, function(err, readonlyID)
{
if(ERR(err, callback)) return;
db.remove("pad2readonly:" + padID);
db.remove("readonly2pad:" + readonlyID);
callback();
});
},
//delete all chat messages
function(callback)
{
var chatHead = _this.chatHead;
for(var i=0;i<=chatHead;i++)
{
db.remove("pad:"+padID+":chat:"+i);
}
callback();
},
//delete all revisions
function(callback)
{
var revHead = _this.head;
for(var i=0;i<=revHead;i++)
{
db.remove("pad:"+padID+":revs:"+i);
}
callback();
}
], callback);
},
//delete the pad entry and delete pad from padManager
function(callback)
{
db.remove("pad:"+padID);
padManager.unloadPad(padID);
callback();
}
], function(err)
{
if(ERR(err, callback)) return;
callback();
});
};
//set in db
Pad.prototype.setPublicStatus = function setPublicStatus(publicStatus) {
this.publicStatus = publicStatus;
db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
};
Pad.prototype.setPassword = function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt());
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
};
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
return compare(this.passwordHash, password);
};
Pad.prototype.isPasswordProtected = function isPasswordProtected() {
return this.passwordHash != null;
};
/* Crypto helper methods */
@ -518,18 +479,10 @@ function hash(password, salt)
function generateSalt()
{
var len = 86;
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
return randomString(86);
}
function compare(hashStr, password)
{
return hash(password, hashStr.split("$")[1]) === hashStr;
return hash(password, hashStr.split("$")[1]) === hashStr;
}

View file

@ -20,7 +20,7 @@
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
require("../db/Pad");
var Pad = require("../db/Pad").Pad;
var db = require("./DB").db;
/**
@ -38,6 +38,15 @@ var globalPads = {
remove: function (name) { delete this[':'+name]; }
};
/**
* An array of padId transformations. These represent changes in pad name policy over
* time, and allow us to "play back" these changes so legacy padIds can be found.
*/
var padIdTransforms = [
[/\s+/g, '_'],
[/:+/g, '_']
];
/**
* Returns a Pad Object with the callback
* @param id A String with the id of the pad
@ -106,10 +115,43 @@ exports.doesPadExists = function(padId, callback)
db.get("pad:"+padId, function(err, value)
{
if(ERR(err, callback)) return;
callback(null, value != null);
callback(null, value != null && value.atext);
});
}
//returns a sanitized padId, respecting legacy pad id formats
exports.sanitizePadId = function(padId, callback) {
var transform_index = arguments[2] || 0;
//we're out of possible transformations, so just return it
if(transform_index >= padIdTransforms.length)
{
callback(padId);
}
//check if padId exists
else
{
exports.doesPadExists(padId, function(junk, exists)
{
if(exists)
{
callback(padId);
}
else
{
//get the next transformation *that's different*
var transformedPadId = padId;
while(transformedPadId == padId && transform_index < padIdTransforms.length)
{
transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]);
transform_index += 1;
}
//check the next transform
exports.sanitizePadId(transformedPadId, callback, transform_index);
}
});
}
}
exports.isValidPadId = function(padId)
{
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);

View file

@ -18,9 +18,11 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
var randomString = CommonCode.require('/pad_utils').randomString;
/**
* returns a read only id for a pad
@ -70,18 +72,3 @@ exports.getPadId = function(readOnlyId, callback)
{
db.get("readonly2pad:" + readOnlyId, callback);
}
/**
* Generates a random String with the given length. Is needed to generate the read only ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}

View file

@ -18,6 +18,7 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var db = require("./DB").db;
var async = require("async");
@ -25,6 +26,7 @@ var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings")
var randomString = CommonCode.require('/pad_utils').randomString;
/**
* This function controlls the access to a pad, it checks if the user can access a pad.

View file

@ -18,8 +18,10 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var customError = require("../utils/customError");
var randomString = CommonCode.require('/pad_utils').randomString;
var db = require("./DB").db;
var async = require("async");
var groupMangager = require("./GroupManager");
@ -358,21 +360,6 @@ function listSessionsWithDBKey (dbkey, callback)
});
}
/**
* Generates a random String with the given length. Is needed to generate the SessionIDs
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
//checks if a number is an int
function is_int(value)
{

View file

@ -20,8 +20,9 @@
* limitations under the License.
*/
var Changeset = require('./utils/Changeset');
var AttributePoolFactory = require("./utils/AttributePoolFactory");
var CommonCode = require('./utils/common_code');
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
function random() {
this.nextInt = function (maxValue) {

View file

@ -18,9 +18,12 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var fs = require("fs");
var api = require("../db/API");
var padManager = require("../db/PadManager");
var randomString = CommonCode.require('/pad_utils').randomString;
//ensure we have an apikey
var apikey = null;
@ -95,7 +98,33 @@ exports.handle = function(functionName, fields, req, res)
res.send({code: 3, message: "no such function", data: null});
return;
}
//sanitize any pad id's before continuing
if(fields["padID"])
{
padManager.sanitizePadId(fields["padID"], function(padId)
{
fields["padID"] = padId;
callAPI(functionName, fields, req, res);
});
}
else if(fields["padName"])
{
padManager.sanitizePadId(fields["padName"], function(padId)
{
fields["padName"] = padId;
callAPI(functionName, fields, req, res);
});
}
else
{
callAPI(functionName, fields, req, res);
}
}
//calls the api function
function callAPI(functionName, fields, req, res)
{
//put the function parameters in an array
var functionParams = [];
for(var i=0;i<functions[functionName].length;i++)
@ -130,18 +159,3 @@ exports.handle = function(functionName, fields, req, res)
//call the api function
api[functionName](functionParams[0],functionParams[1],functionParams[2],functionParams[3],functionParams[4]);
}
/**
* Generates a random String with the given length. Is needed to generate the Author Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}

View file

@ -53,8 +53,16 @@ exports.doExport = function(req, res, padId, type)
padManager.getPad(padId, function(err, pad)
{
ERR(err);
res.send(pad.text());
if(req.params.rev){
pad.getInternalRevisionAText(req.params.rev, function(junk, text)
{
res.send(text.text ? text.text : null);
});
}
else
{
res.send(pad.text());
}
});
}
else if(type == 'dokuwiki')
@ -66,7 +74,7 @@ exports.doExport = function(req, res, padId, type)
//render the dokuwiki document
function(callback)
{
exportdokuwiki.getPadDokuWikiDocument(padId, null, function(err, dokuwiki)
exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki)
{
res.send(dokuwiki);
callback("stop");
@ -87,7 +95,7 @@ exports.doExport = function(req, res, padId, type)
//render the html document
function(callback)
{
exporthtml.getPadHTMLDocument(padId, null, false, function(err, _html)
exporthtml.getPadHTMLDocument(padId, req.params.rev, false, function(err, _html)
{
if(ERR(err, callback)) return;
html = _html;

View file

@ -82,7 +82,7 @@ exports.doImport = function(req, res, padId)
//this allows us to accept source code files like .c or .java
function(callback)
{
var fileEnding = srcFile.split(".")[1].toLowerCase();
var fileEnding = (srcFile.split(".")[1] || "").toLowerCase();
var knownFileEndings = ["txt", "doc", "docx", "pdf", "odt", "html", "htm"];
//find out if this is a known file ending
@ -115,7 +115,15 @@ exports.doImport = function(req, res, padId)
{
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
destFile = tempDirectory + "eplite_import_" + randNum + ".txt";
abiword.convertFile(srcFile, destFile, "txt", callback);
abiword.convertFile(srcFile, destFile, "txt", function(err){
//catch convert errors
if(err){
console.warn("Converting Error:", err);
return callback("convertFailed");
} else {
callback();
}
});
},
//get the pad object
@ -176,16 +184,18 @@ exports.doImport = function(req, res, padId)
}
], function(err)
{
//the upload failed, there is nothing we can do, send a 500
if(err == "uploadFailed")
var status = "ok";
//check for known errors and replace the status
if(err == "uploadFailed" || err == "convertFailed")
{
res.send(500);
return;
status = err;
err = null;
}
ERR(err);
//close the connection
res.send("ok");
res.send("<script>document.domain = document.domain; var impexp = window.top.require('/pad_impexp').padimpexp.handleFrameCall('" + status + "'); </script>", 200);
});
}

View file

@ -18,17 +18,17 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory");
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var authorManager = require("../db/AuthorManager");
var readOnlyManager = require("../db/ReadOnlyManager");
var settings = require('../utils/Settings');
var securityManager = require("../db/SecurityManager");
var log4js = require('log4js');
var os = require("os");
var messageLogger = log4js.getLogger("message");
/**
@ -517,7 +517,12 @@ exports.updatePadClients = function(pad, callback)
], function(err)
{
if(ERR(err, callback)) return;
// next if session has not been deleted
if(sessioninfos[session] == null)
{
callback(null);
return;
}
if(author == sessioninfos[session].author)
{
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
@ -539,7 +544,10 @@ exports.updatePadClients = function(pad, callback)
callback
);
sessioninfos[session].rev = pad.getHeadRevisionNumber();
if(sessioninfos[session] != null)
{
sessioninfos[session].rev = pad.getHeadRevisionNumber();
}
},callback);
}
@ -755,13 +763,6 @@ function handleClientReady(client, message)
var apool = attribsForWire.pool.toJsonable();
atext.attribs = attribsForWire.translated;
//check if abiword is avaiable
var abiwordAvailable = settings.abiword != null ? "yes" : "no";
if(settings.abiword != null && os.type().indexOf("Windows") != -1)
{
abiwordAvailable = "withoutPDF";
}
var clientVars = {
"accountPrivs": {
"maxRevisions": 100
@ -798,7 +799,7 @@ function handleClientReady(client, message)
"fullWidth": false,
"hideSidebar": false
},
"abiwordAvailable": abiwordAvailable,
"abiwordAvailable": settings.abiwordAvailable(),
"hooks": {}
}
@ -852,8 +853,19 @@ function handleClientReady(client, message)
//Run trough all sessions of this pad
async.forEach(pad2sessions[message.padId], function(sessionID, callback)
{
var sessionAuthorName, sessionAuthorColorId;
var author, socket, sessionAuthorName, sessionAuthorColorId;
//Since sessioninfos might change while being enumerated, check if the
//sessionID is still assigned to a valid session
if(sessioninfos[sessionID] !== undefined &&
socketio.sockets.sockets[sessionID] !== undefined){
author = sessioninfos[sessionID].author;
socket = socketio.sockets.sockets[sessionID];
}else {
// If the sessionID is not valid, callback();
callback();
return;
}
async.series([
//get the authorname & colorId
function(callback)
@ -861,7 +873,7 @@ function handleClientReady(client, message)
async.parallel([
function(callback)
{
authorManager.getAuthorColorId(sessioninfos[sessionID].author, function(err, value)
authorManager.getAuthorColorId(author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorColorId = value;
@ -870,7 +882,7 @@ function handleClientReady(client, message)
},
function(callback)
{
authorManager.getAuthorName(sessioninfos[sessionID].author, function(err, value)
authorManager.getAuthorName(author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorName = value;
@ -885,7 +897,7 @@ function handleClientReady(client, message)
if(sessionID != client.id)
{
//Send this Session the Notification about the new user
socketio.sockets.sockets[sessionID].json.send(messageToTheOtherUsers);
socket.json.send(messageToTheOtherUsers);
//Send the new User a Notification about this other user
var messageToNotifyTheClientAboutTheOthers = {
@ -897,7 +909,7 @@ function handleClientReady(client, message)
"colorId": sessionAuthorColorId,
"name": sessionAuthorName,
"userAgent": "Anonymous",
"userId": sessioninfos[sessionID].author
"userId": author
}
}
};

View file

@ -18,11 +18,13 @@
* limitations under the License.
*/
var CommonCode = require('../utils/common_code');
var ERR = require("async-stacktrace");
var async = require("async");
var padManager = require("../db/PadManager");
var Changeset = require("../utils/Changeset");
var AttributePoolFactory = require("../utils/AttributePoolFactory");
var Changeset = CommonCode.require("/Changeset");
var AttributePoolFactory = CommonCode.require("/AttributePoolFactory");
var settings = require('../utils/Settings');
var authorManager = require("../db/AuthorManager");
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
@ -160,6 +162,7 @@ function createTimesliderClientVars (padId, callback)
fullWidth: false,
disableRightBar: false,
initialChangesets: [],
abiwordAvailable: settings.abiwordAvailable(),
hooks: [],
initialStyledContents: {}
};

View file

@ -31,6 +31,8 @@ var async = require('async');
var express = require('express');
var path = require('path');
var minify = require('./utils/Minify');
var CachingMiddleware = require('./utils/caching_middleware');
var Yajsml = require('yajsml');
var formidable = require('formidable');
var apiHandler;
var exportHandler;
@ -45,8 +47,9 @@ var socketIORouter;
var version = "";
try
{
var ref = fs.readFileSync("../.git/HEAD", "utf-8");
var refPath = "../.git/" + ref.substring(5, ref.indexOf("\n"));
var rootPath = path.normalize(__dirname + "/../")
var ref = fs.readFileSync(rootPath + ".git/HEAD", "utf-8");
var refPath = rootPath + ".git/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7);
console.log("Your Etherpad Lite git version is " + version);
@ -60,8 +63,7 @@ console.log("Report bugs at https://github.com/Pita/etherpad-lite/issues")
var serverName = "Etherpad-Lite " + version + " (http://j.mp/ep-lite)";
//cache 6 hours
exports.maxAge = 1000*60*60*6;
exports.maxAge = settings.maxAge;
//set loglevel
log4js.setGlobalLogLevel(settings.loglevel);
@ -77,7 +79,39 @@ async.waterfall([
{
//create server
var app = express.createServer();
app.use(function (req, res, next) {
res.header("Server", serverName);
next();
});
//redirects browser to the pad's sanitized url if needed. otherwise, renders the html
app.param('pad', function (req, res, next, padId) {
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(padId) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
}
else
{
padManager.sanitizePadId(padId, function(sanitizedPadId) {
//the pad id was sanitized, so we redirect to the sanitized version
if(sanitizedPadId != padId)
{
var real_path = req.path.replace(/^\/p\/[^\/]+/, './' + sanitizedPadId);
res.header('Location', real_path);
res.send('You should be redirected to <a href="' + real_path + '">' + real_path + '</a>', 302);
}
//the pad id was fine, so just render it
else
{
next();
}
});
}
});
//load modules that needs a initalized db
readOnlyManager = require("./db/ReadOnlyManager");
exporthtml = require("./utils/ExportHtml");
@ -108,31 +142,26 @@ async.waterfall([
gracefulShutdown();
});
//serve static files
app.get('/static/*', function(req, res)
{
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/.." +
req.url.replace(/\.\./g, '').split("?")[0]);
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve minified files
app.get('/minified/:id', function(req, res, next)
{
res.header("Server", serverName);
var id = req.params.id;
if(id == "pad.js" || id == "timeslider.js")
{
minify.minifyJS(req,res,id);
}
else
{
next();
}
// Cache both minified and static.
var assetCache = new CachingMiddleware;
app.all('/(minified|static)/*', assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has
// file-specific hacks for ace/require-kernel/etc.
app.all('/static/:filename(*)', minify.minify);
// Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side.
var jsServer = new (Yajsml.Server)({
rootPath: 'minified/'
, rootURI: 'http://localhost:' + settings.port + '/static/js/'
});
var StaticAssociator = Yajsml.associators.StaticAssociator;
var associations =
Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations);
jsServer.setAssociator(associator);
app.use(jsServer);
//checks for padAccess
function hasPadAccess(req, res, callback)
@ -177,8 +206,6 @@ async.waterfall([
//serve read only pad
app.get('/ro/:id', function(req, res)
{
res.header("Server", serverName);
var html;
var padId;
var pad;
@ -232,18 +259,10 @@ async.waterfall([
res.send(html);
});
});
//serve pad.html under /p
app.get('/p/:pad', function(req, res, next)
{
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
return;
}
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/pad.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
@ -251,28 +270,13 @@ async.waterfall([
//serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/timeslider', function(req, res, next)
{
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
return;
}
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/timeslider.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
//serve timeslider.html under /p/$padname/timeslider
app.get('/p/:pad/export/:type', function(req, res, next)
app.get('/p/:pad/:rev?/export/:type', function(req, res, next)
{
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
return;
}
var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"];
//send a 404 if we don't support this filetype
if(types.indexOf(req.params.type) == -1)
@ -290,7 +294,6 @@ async.waterfall([
}
res.header("Access-Control-Allow-Origin", "*");
res.header("Server", serverName);
hasPadAccess(req, res, function()
{
@ -301,22 +304,13 @@ async.waterfall([
//handle import requests
app.post('/p/:pad/import', function(req, res, next)
{
//ensure the padname is valid and the url doesn't end with a /
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
{
res.send('Such a padname is forbidden', 404);
return;
}
//if abiword is disabled, skip handling this request
if(settings.abiword == null)
{
next();
return;
}
res.header("Server", serverName);
hasPadAccess(req, res, function()
{
importHandler.doImport(req, res, req.params.pad);
@ -326,8 +320,8 @@ async.waterfall([
var apiLogger = log4js.getLogger("API");
//This is for making an api call, collecting all post information and passing it to the apiHandler
var apiCaller = function(req, res, fields) {
res.header("Server", serverName);
var apiCaller = function(req, res, fields)
{
res.header("Content-Type", "application/json; charset=utf-8");
apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields));
@ -388,7 +382,6 @@ async.waterfall([
//serve index.html under /
app.get('/', function(req, res)
{
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/index.html");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
@ -396,7 +389,6 @@ async.waterfall([
//serve robots.txt
app.get('/robots.txt', function(req, res)
{
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/robots.txt");
res.sendfile(filePath, { maxAge: exports.maxAge });
});
@ -404,7 +396,6 @@ async.waterfall([
//serve favicon.ico
app.get('/favicon.ico', function(req, res)
{
res.header("Server", serverName);
var filePath = path.normalize(__dirname + "/../static/custom/favicon.ico");
res.sendfile(filePath, { maxAge: exports.maxAge }, function(err)
{

View file

@ -53,7 +53,7 @@ if(os.type().indexOf("Windows") > -1)
abiword.on('exit', function (code)
{
if(code != 0) {
throw "Abiword died with exit code " + code;
return callback("Abiword died with exit code " + code);
}
if(stdoutBuffer != "")
@ -75,52 +75,54 @@ if(os.type().indexOf("Windows") > -1)
else
{
//spawn the abiword process
var abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
//append error messages to the buffer
abiword.stderr.on('data', function (data)
{
stdoutBuffer += data.toString();
});
//throw exceptions if abiword is dieing
abiword.on('exit', function (code)
{
throw "Abiword died with exit code " + code;
});
//delegate the processing of stdout to a other function
abiword.stdout.on('data',onAbiwordStdout);
var abiword;
var stdoutCallback = null;
var stdoutBuffer = "";
var firstPrompt = true;
var spawnAbiword = function (){
abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
var stdoutBuffer = "";
var firstPrompt = true;
function onAbiwordStdout(data)
{
//add data to buffer
stdoutBuffer+=data.toString();
//we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1)
//append error messages to the buffer
abiword.stderr.on('data', function (data)
{
//filter the feedback message
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
stdoutBuffer += data.toString();
});
//abiword died, let's restart abiword and return an error with the callback
abiword.on('exit', function (code)
{
spawnAbiword();
stdoutCallback("Abiword died with exit code " + code);
});
//delegate the processing of stdout to a other function
abiword.stdout.on('data',function (data)
{
//add data to buffer
stdoutBuffer+=data.toString();
//reset the buffer
stdoutBuffer = "";
//call the callback with the error message
//skip the first prompt
if(stdoutCallback != null && !firstPrompt)
//we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1)
{
stdoutCallback(err);
stdoutCallback = null;
//filter the feedback message
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
//reset the buffer
stdoutBuffer = "";
//call the callback with the error message
//skip the first prompt
if(stdoutCallback != null && !firstPrompt)
{
stdoutCallback(err);
stdoutCallback = null;
}
firstPrompt = false;
}
firstPrompt = false;
}
});
}
spawnAbiword();
doConvertTask = function(task, callback)
{
@ -130,6 +132,7 @@ else
stdoutCallback = function (err)
{
callback();
console.log("queue continue");
task.callback(err);
};
}

38
node/utils/Cli.js Normal file
View file

@ -0,0 +1,38 @@
/**
* The CLI module handles command line parameters
*/
/*
* 2012 Jordan Hollinger
*
* 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.
*/
// An object containing the parsed command-line options
exports.argv = {};
var argv = process.argv.slice(2);
var arg, prevArg;
// Loop through args
for ( var i = 0; i < argv.length; i++ ) {
arg = argv[i];
// Override location of settings.json file
if ( prevArg == '--settings' || prevArg == '-s' ) {
exports.argv.settings = arg;
}
prevArg = arg;
}

View file

@ -15,7 +15,8 @@
*/
var async = require("async");
var Changeset = require("./Changeset");
var CommonCode = require('./common_code');
var Changeset = CommonCode.require("/Changeset");
var padManager = require("../db/PadManager");
function getPadDokuWiki(pad, revNum, callback)

View file

@ -14,10 +14,12 @@
* limitations under the License.
*/
var CommonCode = require('./common_code');
var async = require("async");
var Changeset = require("./Changeset");
var Changeset = CommonCode.require("/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = CommonCode.require('/security');
function getPadPlainText(pad, revNum)
{
@ -269,7 +271,7 @@ function getHTMLFromAtext(pad, atext)
//from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), "");
assem.append(_escapeHTML(s));
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
} // end iteration over spans in line
var tags2close = [];
@ -292,7 +294,7 @@ function getHTMLFromAtext(pad, atext)
var url = urlData[1];
var urlLength = url.length;
processNextChars(startIndex - idx);
assem.append('<a href="' + url.replace(/\"/g, '&quot;') + '">');
assem.append('<a href="' + Security.escapeHTMLAttribute(url) + '">');
processNextChars(urlLength);
assem.append('</a>');
});
@ -309,13 +311,14 @@ function getHTMLFromAtext(pad, atext)
// People might use weird indenting, e.g. skip a level,
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel || lists.length > 0)
if (line.listLevel)//If we are inside a list
{
// do list stuff
var whichList = -1; // index into lists or -1
@ -331,41 +334,89 @@ function getHTMLFromAtext(pad, atext)
}
}
if (whichList >= lists.length)
if (whichList >= lists.length)//means we are on a deeper level of indentation than the previous line
{
lists.push([line.listLevel, line.listTypeName]);
pieces.push('<ul><li>', lineContent || '<br>');
if(line.listTypeName == "number")
{
pieces.push('<ol class="'+line.listTypeName+'"><li>', lineContent || '<br>');
}
else
{
pieces.push('<ul class="'+line.listTypeName+'"><li>', lineContent || '<br>');
}
}
else if (whichList == -1)
//the following code *seems* dead after my patch.
//I keep it just in case I'm wrong...
/*else if (whichList == -1)//means we are not inside a list
{
if (line.text)
{
console.log('trace 1');
// non-blank line, end all lists
pieces.push(new Array(lists.length + 1).join('</li></ul\n>'));
if(line.listTypeName == "number")
{
pieces.push(new Array(lists.length + 1).join('</li></ol>'));
}
else
{
pieces.push(new Array(lists.length + 1).join('</li></ul>'));
}
lists.length = 0;
pieces.push(lineContent, '<br>');
}
else
{
console.log('trace 2');
pieces.push('<br><br>');
}
}
else
}*/
else//means we are getting closer to the lowest level of indentation
{
while (whichList < lists.length - 1)
{
pieces.push('</li></ul>');
if(lists[lists.length - 1][1] == "number")
{
pieces.push('</li></ol>');
}
else
{
pieces.push('</li></ul>');
}
lists.length--;
}
pieces.push('</li><li>', lineContent || '<br>');
}
}
else
else//outside any list
{
while (lists.length > 0)//if was in a list: close it before
{
if(lists[lists.length - 1][1] == "number")
{
pieces.push('</li></ol>');
}
else
{
pieces.push('</li></ul>');
}
lists.length--;
}
pieces.push(lineContent, '<br>');
}
}
pieces.push(new Array(lists.length + 1).join('</li></ul>'));
for (var k = lists.length - 1; k >= 0; k--)
{
if(lists[k][1] == "number")
{
pieces.push('</li></ol>');
}
else
{
pieces.push('</li></ul>');
}
}
return pieces.join('');
}
@ -415,7 +466,24 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
{
if(ERR(err, callback)) return;
var head = (noDocType ? '' : '<!doctype html>\n') + '<html lang="en">\n' + (noDocType ? '' : '<head>\n' + '<meta charset="utf-8">\n' + '<style> * { font-family: arial, sans-serif;\n' + 'font-size: 13px;\n' + 'line-height: 17px; }</style>\n' + '</head>\n') + '<body>';
var head =
(noDocType ? '' : '<!doctype html>\n') +
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
'<meta charset="utf-8">\n' +
'<style> * { font-family: arial, sans-serif;\n' +
'font-size: 13px;\n' +
'line-height: 17px; }' +
'ul.indent { list-style-type: none; }' +
'ol { list-style-type: decimal; }' +
'ol ol { list-style-type: lower-latin; }' +
'ol ol ol { list-style-type: lower-roman; }' +
'ol ol ol ol { list-style-type: decimal; }' +
'ol ol ol ol ol { list-style-type: lower-latin; }' +
'ol ol ol ol ol ol{ list-style-type: lower-roman; }' +
'ol ol ol ol ol ol ol { list-style-type: decimal; }' +
'ol ol ol ol ol ol ol ol{ list-style-type: lower-latin; }' +
'</style>\n' + '</head>\n') +
'<body>';
var foot = '</body>\n</html>\n';
@ -427,24 +495,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
});
}
function _escapeHTML(s)
{
var re = /[&<>]/g;
if (!re.MAP)
{
// persisted across function calls!
re.MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
};
}
s = s.replace(re, function (c)
{
return re.MAP[c];
});
function _encodeWhitespace(s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
{
return "&#" +c.charCodeAt(0) + ";"

View file

@ -17,9 +17,10 @@
var jsdom = require('jsdom-nocontextifiy').jsdom;
var log4js = require('log4js');
var Changeset = require("./Changeset");
var contentcollector = require("./contentcollector");
var map = require("../../static/js/ace2_common.js").map;
var CommonCode = require('../utils/common_code');
var Changeset = CommonCode.require("/Changeset");
var contentcollector = CommonCode.require("/contentcollector");
var map = CommonCode.require("/ace2_common").map;
function setPadHTML(pad, html, callback)
{

View file

@ -27,285 +27,257 @@ var cleanCSS = require('clean-css');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var path = require('path');
var Buffer = require('buffer').Buffer;
var gzip = require('gzip');
var RequireKernel = require('require-kernel');
var server = require('../server');
var os = require('os');
var padJS = ["jquery.min.js", "pad_utils.js", "plugins.js", "undo-xpopup.js", "json2.js", "pad_cookie.js", "pad_editor.js", "pad_editbar.js", "pad_docbar.js", "pad_modals.js", "ace.js", "collab_client.js", "pad_userlist.js", "pad_impexp.js", "pad_savedrevs.js", "pad_connectionstatus.js", "pad2.js", "jquery-ui.js", "chat.js", "excanvas.js", "farbtastic.js"];
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
var TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
var timesliderJS = ["jquery.min.js", "plugins.js", "undo-xpopup.js", "json2.js", "colorutils.js", "draggable.js", "pad_utils.js", "pad_cookie.js", "pad_editor.js", "pad_editbar.js", "pad_docbar.js", "pad_modals.js", "easysync2_client.js", "domline_client.js", "linestylefilter_client.js", "cssmanager_client.js", "broadcast.js", "broadcast_slider.js", "broadcast_revisions.js"];
// Rewrite tar to include modules with no extensions and proper rooted paths.
exports.tar = {};
for (var key in tar) {
exports.tar['/' + key] =
tar[key].map(function (p) {return '/' + p}).concat(
tar[key].map(function (p) {return '/' + p.replace(/\.js$/, '')})
);
}
/**
* creates the minifed javascript for the given minified name
* @param req the Express request
* @param res the Express response
*/
exports.minifyJS = function(req, res, jsFilename)
exports.minify = function(req, res, next)
{
res.header("Content-Type","text/javascript");
//choose the js files we need
if(jsFilename == "pad.js")
{
jsFiles = padJS;
var filename = req.params['filename'];
// No relative paths, especially if they may go up the file hierarchy.
filename = path.normalize(path.join(ROOT_DIR, filename));
if (filename.indexOf(ROOT_DIR) == 0) {
filename = filename.slice(ROOT_DIR.length);
filename = filename.replace(/\\/g, '/'); // Windows (safe generally?)
} else {
res.writeHead(404, {});
res.end();
return;
}
else if(jsFilename == "timeslider.js")
{
jsFiles = timesliderJS;
// What content type should this be?
// TODO: This should use a MIME module.
var contentType;
if (filename.match(/\.js$/)) {
contentType = "text/javascript";
} else if (filename.match(/\.css$/)) {
contentType = "text/css";
} else if (filename.match(/\.html$/)) {
contentType = "text/html";
} else if (filename.match(/\.txt$/)) {
contentType = "text/plain";
} else if (filename.match(/\.png$/)) {
contentType = "image/png";
} else if (filename.match(/\.gif$/)) {
contentType = "image/gif";
} else if (filename.match(/\.ico$/)) {
contentType = "image/x-icon";
} else {
contentType = "application/octet-stream";
}
else
{
throw new Error("there is no profile for creating " + name);
}
//minifying is enabled
if(settings.minify)
{
var fileValues = {};
var embeds = {};
var latestModification = 0;
async.series([
//find out the highest modification date
function(callback)
{
var folders2check = ["../static/css","../static/js"];
//go trough this two folders
async.forEach(folders2check, function(path, callback)
{
//read the files in the folder
fs.readdir(path, function(err, files)
{
if(ERR(err, callback)) return;
//we wanna check the directory itself for changes too
files.push(".");
//go trough all files in this folder
async.forEach(files, function(filename, callback)
{
//get the stat data of this file
fs.stat(path + "/" + filename, function(err, stats)
{
if(ERR(err, callback)) return;
//get the modification time
var modificationTime = stats.mtime.getTime();
//compare the modification time to the highest found
if(modificationTime > latestModification)
{
latestModification = modificationTime;
}
callback();
});
}, callback);
});
}, callback);
},
function(callback)
{
//check the modification time of the minified js
fs.stat("../var/minified_" + jsFilename, function(err, stats)
{
if(err && err.code != "ENOENT")
{
ERR(err, callback);
return;
}
//there is no minfied file or there new changes since this file was generated, so continue generating this file
if((err && err.code == "ENOENT") || stats.mtime.getTime() < latestModification)
{
callback();
}
//the minified file is still up to date, stop minifying
else
{
callback("stop");
}
statFile(filename, function (error, date, exists) {
if (date) {
date = new Date(date);
res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString());
if (server.maxAge) {
var expiresDate = new Date((new Date()).getTime()+server.maxAge*1000);
res.setHeader('expires', expiresDate.toUTCString());
res.setHeader('cache-control', 'max-age=' + server.maxAge);
}
}
if (error) {
res.writeHead(500, {});
res.end();
} else if (!exists) {
res.writeHead(404, {});
res.end();
} else if (new Date(req.headers['if-modified-since']) >= date) {
res.writeHead(304, {});
res.end();
} else {
if (req.method == 'HEAD') {
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.end();
} else if (req.method == 'GET') {
getFileCompressed(filename, contentType, function (error, content) {
if(ERR(error)) return;
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.write(content);
res.end();
});
},
//load all js files
function (callback)
{
async.forEach(jsFiles, function (item, callback)
{
fs.readFile("../static/js/" + item, "utf-8", function(err, data)
{
if(ERR(err, callback)) return;
fileValues[item] = data;
callback();
});
}, callback);
},
//find all includes in ace.js and embed them
function(callback)
{
//if this is not the creation of pad.js, skip this part
if(jsFilename != "pad.js")
{
callback();
return;
} else {
res.writeHead(405, {'allow': 'HEAD, GET'});
res.end();
}
}
});
}
// find all includes in ace.js and embed them.
function getAceFile(callback) {
fs.readFile(ROOT_DIR + 'js/ace.js', "utf8", function(err, data) {
if(ERR(err, callback)) return;
// Find all includes in ace.js and embed them
var founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
if (!settings.minify) {
founds = [];
}
// Always include the require kernel.
founds.push('$$INCLUDE_JS("../static/js/require-kernel.js")');
data += ';\n';
data += 'Ace2Editor.EMBEDED = Ace2Editor.EMBEDED || {};\n';
// Request the contents of the included file on the server-side and write
// them into the file.
async.forEach(founds, function (item, callback) {
var filename = item.match(/"([^"]*)"/)[1];
var request = require('request');
var baseURI = 'http://localhost:' + settings.port
request(baseURI + path.normalize(path.join('/static/', filename)), function (error, response, body) {
if (!error && response.statusCode == 200) {
data += 'Ace2Editor.EMBEDED[' + JSON.stringify(filename) + '] = '
+ JSON.stringify(body || '') + ';\n';
} else {
// Silence?
}
var founds = fileValues["ace.js"].match(/\$\$INCLUDE_[a-zA-Z_]+\([a-zA-Z0-9.\/_"]+\)/gi);
//go trough all includes
async.forEach(founds, function (item, callback)
{
var filename = item.match(/"[^"]*"/g)[0].substr(1);
filename = filename.substr(0,filename.length-1);
var type = item.match(/INCLUDE_[A-Z]+/g)[0].substr("INCLUDE_".length);
var quote = item.search("_Q") != -1;
//read the included file
fs.readFile(filename, "utf-8", function(err, data)
{
if(ERR(err, callback)) return;
//compress the file
if(type == "JS")
{
embeds[item] = "<script>\n" + compressJS([data])+ "\n\\x3c/script>";
}
else
{
embeds[item] = "<style>" + compressCSS([data])+ "</style>";
}
//do the first escape
embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\\"/g, "\"");
embeds[item] = embeds[item].substr(1);
embeds[item] = embeds[item].substr(0, embeds[item].length-1);
//add quotes, if wished
if(quote)
{
embeds[item] = "'" + embeds[item] + "'";
}
//do the second escape
embeds[item] = JSON.stringify(embeds[item]).replace(/'/g, "\\'").replace(/\"/g, "\"");
embeds[item] = embeds[item].substr(1);
embeds[item] = embeds[item].substr(0, embeds[item].length-1);
embeds[item] = "'" + embeds[item] + "'";
callback();
});
}, function(err)
{
if(ERR(err, callback)) return;
//replace the include command with the include
for(var i in embeds)
{
fileValues["ace.js"]=fileValues["ace.js"].replace(i, embeds[i]);
}
callback();
});
},
//put all together and write it into a file
function(callback)
{
//put all javascript files in an array
var values = [];
for(var i in jsFiles)
{
values.push(fileValues[jsFiles[i]]);
}
//minify all javascript files to one
var result = compressJS(values);
async.parallel([
//write the results plain in a file
function(callback)
{
fs.writeFile("../var/minified_" + jsFilename, result, "utf8", callback);
},
//write the results compressed in a file
function(callback)
{
//spawn a gzip process if we're on a unix system
if(os.type().indexOf("Windows") == -1)
{
gzip(result, 9, function(err, compressedResult){
//weird gzip bug that returns 0 instead of null if everything is ok
err = err === 0 ? null : err;
if(ERR(err, callback)) return;
fs.writeFile("../var/minified_" + jsFilename + ".gz", compressedResult, callback);
});
}
//skip this step on windows
else
{
callback();
}
}
],callback);
}
], function(err)
{
if(err && err != "stop")
{
if(ERR(err)) return;
}
//check if gzip is supported by this browser
var gzipSupport = req.header('Accept-Encoding', '').indexOf('gzip') != -1;
var pathStr;
if(gzipSupport && os.type().indexOf("Windows") == -1)
{
pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename + ".gz");
res.header('Content-Encoding', 'gzip');
}
else
{
pathStr = path.normalize(__dirname + "/../../var/minified_" + jsFilename );
}
res.sendfile(pathStr, { maxAge: server.maxAge });
})
}
//minifying is disabled, so put the files together in one file
else
{
var fileValues = {};
//read all js files
async.forEach(jsFiles, function (item, callback)
{
fs.readFile("../static/js/" + item, "utf-8", function(err, data)
{
if(ERR(err, callback)) return;
fileValues[item] = data;
callback();
});
},
//send all files together
function(err)
{
if(ERR(err)) return;
for(var i=0;i<jsFiles.length;i++)
{
var fileName = jsFiles[i];
res.write("\n\n\n/*** File: static/js/" + fileName + " ***/\n\n\n");
res.write(fileValues[fileName]);
}
res.end();
}, function(error) {
callback(error, data);
});
});
}
// Check for the existance of the file and get the last modification date.
function statFile(filename, callback) {
if (filename == 'js/ace.js') {
// Sometimes static assets are inlined into this file, so we have to stat
// everything.
lastModifiedDateOfEverything(function (error, date) {
callback(error, date, !error);
});
} else if (filename == 'js/require-kernel.js') {
callback(null, requireLastModified(), true);
} else {
fs.stat(ROOT_DIR + filename, function (error, stats) {
if (error) {
if (error.code == "ENOENT") {
// Stat the directory instead.
fs.stat(path.dirname(ROOT_DIR + filename), function (error, stats) {
if (error) {
if (error.code == "ENOENT") {
callback(null, null, false);
} else {
callback(error);
}
} else {
callback(null, stats.mtime.getTime(), false);
}
});
} else {
callback(error);
}
} else {
callback(null, stats.mtime.getTime(), true);
}
});
}
}
function lastModifiedDateOfEverything(callback) {
var folders2check = [ROOT_DIR + 'js/', ROOT_DIR + 'css/'];
var latestModification = 0;
//go trough this two folders
async.forEach(folders2check, function(path, callback)
{
//read the files in the folder
fs.readdir(path, function(err, files)
{
if(ERR(err, callback)) return;
//we wanna check the directory itself for changes too
files.push(".");
//go trough all files in this folder
async.forEach(files, function(filename, callback)
{
//get the stat data of this file
fs.stat(path + "/" + filename, function(err, stats)
{
if(ERR(err, callback)) return;
//get the modification time
var modificationTime = stats.mtime.getTime();
//compare the modification time to the highest found
if(modificationTime > latestModification)
{
latestModification = modificationTime;
}
callback();
});
}, callback);
});
}, function () {
callback(null, latestModification);
});
}
// This should be provided by the module, but until then, just use startup
// time.
var _requireLastModified = new Date();
function requireLastModified() {
return _requireLastModified.toUTCString();
}
function requireDefinition() {
return 'var require = ' + RequireKernel.kernelSource + ';\n';
}
function getFileCompressed(filename, contentType, callback) {
getFile(filename, function (error, content) {
if (error || !content) {
callback(error, content);
} else {
if (settings.minify) {
if (contentType == 'text/javascript') {
try {
content = compressJS([content]);
} catch (error) {
// silence
}
} else if (contentType == 'text/css') {
content = compressCSS([content]);
}
}
callback(null, content);
}
});
}
function getFile(filename, callback) {
if (filename == 'js/ace.js') {
getAceFile(callback);
} else if (filename == 'js/require-kernel.js') {
callback(undefined, requireDefinition());
} else {
fs.readFile(ROOT_DIR + filename, callback);
}
}

View file

@ -20,6 +20,9 @@
*/
var fs = require("fs");
var os = require("os");
var path = require('path');
var argv = require('./Cli').argv;
/**
* The IP ep-lite should listen to
@ -53,6 +56,11 @@ exports.requireSession = false;
*/
exports.editOnly = false;
/**
* Max age that responses will have (affects caching layer).
*/
exports.maxAge = 1000*60*60*6; // 6 hours
/**
* A flag that shows if minification is enabled or not
*/
@ -73,8 +81,25 @@ exports.loglevel = "INFO";
*/
exports.httpAuth = null;
//checks if abiword is avaiable
exports.abiwordAvailable = function()
{
if(exports.abiword != null)
{
return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
}
else
{
return "no";
}
}
// Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json";
var settingsPath = settingsFilename.charAt(0) == '/' ? '' : path.normalize(__dirname + "/../../");
//read the settings sync
var settingsStr = fs.readFileSync("../settings.json").toString();
var settingsStr = fs.readFileSync(settingsPath + settingsFilename).toString();
//remove all comments
settingsStr = settingsStr.replace(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/gm,"").replace(/#.*/g,"").replace(/\/\/.*/g,"");

View file

@ -0,0 +1,177 @@
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* 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 async = require('async');
var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
var server = require('../server');
var zlib = require('zlib');
var util = require('util');
var ROOT_DIR = path.normalize(__dirname + "/../");
var CACHE_DIR = ROOT_DIR + '../var/';
var responseCache = {};
/*
This caches and compresses 200 and 404 responses to GET and HEAD requests.
TODO: Caching and compressing are solved problems, a middleware configuration
should replace this.
*/
function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
function handle(req, res, next) {
if (!(req.method == "GET" || req.method == "HEAD")) {
return next(undefined, req, res);
}
var old_req = {};
var old_res = {};
var supportsGzip =
req.header('Accept-Encoding', '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path;
var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
var modifiedSince = (req.headers['if-modified-since']
&& new Date(req.headers['if-modified-since']));
var lastModifiedCache = !error && stats.mtime;
if (lastModifiedCache) {
req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
} else {
delete req.headers['if-modified-since'];
}
// Always issue get to downstream.
old_req.method = req.method;
req.method = 'GET';
var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
if (expirationDate > new Date()) {
// Our cached version is still valid.
return respond();
}
var _headers = {};
old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) {
_headers[key.toLowerCase()] = value;
old_res.setHeader.call(res, key, value);
};
old_res.writeHead = res.writeHead;
res.writeHead = function (status, headers) {
var lastModified = (res.getHeader('last-modified')
&& new Date(res.getHeader('last-modified')));
res.writeHead = old_res.writeHead;
if (status == 200 || status == 404) {
// Update cache
var buffer = '';
Object.keys(headers || {}).forEach(function (key) {
res.setHeader(key, headers[key]);
});
headers = _headers;
old_res.write = res.write;
old_res.end = res.end;
res.write = function(data, encoding) {
buffer += data.toString(encoding);
};
res.end = function(data, encoding) {
async.parallel([
function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey;
fs.writeFile(path, buffer, function (error, stats) {
callback();
});
}
, function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
zlib.gzip(buffer, function(error, content) {
if (error) {
callback();
} else {
fs.writeFile(path, content, function (error, stats) {
callback();
});
}
});
}
], function () {
responseCache[cacheKey] = {statusCode: status, headers: headers};
respond();
});
};
} else if (status == 304) {
// Nothing new changed from the cached version.
old_res.write = res.write;
old_res.end = res.end;
res.write = function(data, encoding) {};
res.end = function(data, encoding) { respond() };
} else {
res.writeHead(status, headers);
}
};
next(undefined, req, res);
// This handles read/write synchronization as well as its predecessor,
// which is to say, not at all.
// TODO: Implement locking on write or ditch caching of gzip and use
// existing middlewares.
function respond() {
req.method = old_req.method || req.method;
res.write = old_res.write || res.write;
res.end = old_res.end || res.end;
var headers = responseCache[cacheKey].headers;
var statusCode = responseCache[cacheKey].statusCode;
var pathStr = CACHE_DIR + 'minified_' + cacheKey;
if (supportsGzip && (headers['content-type'] || '').match(/^text\//)) {
pathStr = pathStr + '.gz';
headers['content-encoding'] = 'gzip';
}
var lastModified = (headers['last-modified']
&& new Date(headers['last-modified']));
if (statusCode == 200 && lastModified <= modifiedSince) {
res.writeHead(304, headers);
res.end();
} else if (req.method == 'GET') {
var readStream = fs.createReadStream(pathStr);
res.writeHead(statusCode, headers);
util.pump(readStream, res);
} else {
res.writeHead(statusCode, headers);
res.end();
}
}
});
}
this.handle = handle;
}();
module.exports = CachingMiddleware;

24
node/utils/common_code.js Normal file
View file

@ -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.
*/
var RequireKernel = require('require-kernel/');
var path = require('path');
var CLIENT_JS_SRC = path.normalize(__dirname + '/../../static/js/');
var client_require = RequireKernel.requireForPaths(
'file://' + (CLIENT_JS_SRC.charAt(0) == '/' ? '' : '/') + encodeURI(CLIENT_JS_SRC));
exports.require = client_require;

View file

@ -1,692 +0,0 @@
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.contentcollector
// %APPJET%: import("etherpad.collab.ace.easysync2.Changeset");
// %APPJET%: import("etherpad.admin.plugins");
/**
* 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 Changeset = require("../utils/Changeset");
var _MAX_LIST_LEVEL = 8;
function sanitizeUnicode(s)
{
return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
}
function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author)
{
browser = browser || {};
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = {callHook: function () {}};
}
var dom = domInterface || {
isNodeText: function(n)
{
return (n.nodeType == 3);
},
nodeTagName: function(n)
{
return n.tagName;
},
nodeValue: function(n)
{
return n.nodeValue;
},
nodeNumChildren: function(n)
{
return n.childNodes.length;
},
nodeChild: function(n, i)
{
return n.childNodes.item(i);
},
nodeProp: function(n, p)
{
return n[p];
},
nodeAttr: function(n, a)
{
return n.getAttribute(a);
},
optNodeInnerHTML: function(n)
{
return n.innerHTML;
}
};
var _blockElems = {
"div": 1,
"p": 1,
"pre": 1,
"li": 1
};
function isBlockElement(n)
{
return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()];
}
function textify(str)
{
return sanitizeUnicode(
str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
}
function getAssoc(node, name)
{
return dom.nodeProp(node, "_magicdom_" + name);
}
var lines = (function()
{
var textArray = [];
var attribsArray = [];
var attribsBuilder = null;
var op = Changeset.newOp('+');
var self = {
length: function()
{
return textArray.length;
},
atColumnZero: function()
{
return textArray[textArray.length - 1] === "";
},
startNew: function()
{
textArray.push("");
self.flush(true);
attribsBuilder = Changeset.smartOpAssembler();
},
textOfLine: function(i)
{
return textArray[i];
},
appendText: function(txt, attrString)
{
textArray[textArray.length - 1] += txt;
//dmesg(txt+" / "+attrString);
op.attribs = attrString;
op.chars = txt.length;
attribsBuilder.append(op);
},
textLines: function()
{
return textArray.slice();
},
attribLines: function()
{
return attribsArray;
},
// call flush only when you're done
flush: function(withNewline)
{
if (attribsBuilder)
{
attribsArray.push(attribsBuilder.toString());
attribsBuilder = null;
}
}
};
self.startNew();
return self;
}());
var cc = {};
function _ensureColumnZero(state)
{
if (!lines.atColumnZero())
{
cc.startNewLine(state);
}
}
var selection, startPoint, endPoint;
var selStart = [-1, -1],
selEnd = [-1, -1];
var blockElems = {
"div": 1,
"p": 1,
"pre": 1
};
function _isEmpty(node, state)
{
// consider clean blank lines pasted in IE to be empty
if (dom.nodeNumChildren(node) == 0) return true;
if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") && dom.optNodeInnerHTML(node) == "&nbsp;" && !getAssoc(node, "unpasted"))
{
if (state)
{
var child = dom.nodeChild(node, 0);
_reachPoint(child, 0, state);
_reachPoint(child, 1, state);
}
return true;
}
return false;
}
function _pointHere(charsAfter, state)
{
var ln = lines.length() - 1;
var chr = lines.textOfLine(ln).length;
if (chr == 0 && state.listType && state.listType != 'none')
{
chr += 1; // listMarker
}
chr += charsAfter;
return [ln, chr];
}
function _reachBlockPoint(nd, idx, state)
{
if (!dom.isNodeText(nd)) _reachPoint(nd, idx, state);
}
function _reachPoint(nd, idx, state)
{
if (startPoint && nd == startPoint.node && startPoint.index == idx)
{
selStart = _pointHere(0, state);
}
if (endPoint && nd == endPoint.node && endPoint.index == idx)
{
selEnd = _pointHere(0, state);
}
}
cc.incrementFlag = function(state, flagName)
{
state.flags[flagName] = (state.flags[flagName] || 0) + 1;
}
cc.decrementFlag = function(state, flagName)
{
state.flags[flagName]--;
}
cc.incrementAttrib = function(state, attribName)
{
if (!state.attribs[attribName])
{
state.attribs[attribName] = 1;
}
else
{
state.attribs[attribName]++;
}
_recalcAttribString(state);
}
cc.decrementAttrib = function(state, attribName)
{
state.attribs[attribName]--;
_recalcAttribString(state);
}
function _enterList(state, listType)
{
var oldListType = state.listType;
state.listLevel = (state.listLevel || 0) + 1;
if (listType != 'none')
{
state.listNesting = (state.listNesting || 0) + 1;
}
state.listType = listType;
_recalcAttribString(state);
return oldListType;
}
function _exitList(state, oldListType)
{
state.listLevel--;
if (state.listType != 'none')
{
state.listNesting--;
}
state.listType = oldListType;
_recalcAttribString(state);
}
function _enterAuthor(state, author)
{
var oldAuthor = state.author;
state.authorLevel = (state.authorLevel || 0) + 1;
state.author = author;
_recalcAttribString(state);
return oldAuthor;
}
function _exitAuthor(state, oldAuthor)
{
state.authorLevel--;
state.author = oldAuthor;
_recalcAttribString(state);
}
function _recalcAttribString(state)
{
var lst = [];
for (var a in state.attribs)
{
if (state.attribs[a])
{
lst.push([a, 'true']);
}
}
if (state.authorLevel > 0)
{
var authorAttrib = ['author', state.author];
if (apool.putAttrib(authorAttrib, true) >= 0)
{
// require that author already be in pool
// (don't add authors from other documents, etc.)
lst.push(authorAttrib);
}
}
state.attribString = Changeset.makeAttribsString('+', lst, apool);
}
function _produceListMarker(state)
{
lines.appendText('*', Changeset.makeAttribsString('+', [
['list', state.listType],
['insertorder', 'first']
], apool));
}
cc.startNewLine = function(state)
{
if (state)
{
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
if (atBeginningOfLine && state.listType && state.listType != 'none')
{
_produceListMarker(state);
}
}
lines.startNew();
}
cc.notifySelection = function(sel)
{
if (sel)
{
selection = sel;
startPoint = selection.startPoint;
endPoint = selection.endPoint;
}
};
cc.doAttrib = function(state, na)
{
state.localAttribs = (state.localAttribs || []);
state.localAttribs.push(na);
cc.incrementAttrib(state, na);
};
cc.collectContent = function(node, state)
{
if (!state)
{
state = {
flags: { /*name -> nesting counter*/
},
localAttribs: null,
attribs: { /*name -> nesting counter*/
},
attribString: ''
};
}
var localAttribs = state.localAttribs;
state.localAttribs = null;
var isBlock = isBlockElement(node);
var isEmpty = _isEmpty(node, state);
if (isBlock) _ensureColumnZero(state);
var startLine = lines.length() - 1;
_reachBlockPoint(node, 0, state);
if (dom.isNodeText(node))
{
var txt = dom.nodeValue(node);
var rest = '';
var x = 0; // offset into original text
if (txt.length == 0)
{
if (startPoint && node == startPoint.node)
{
selStart = _pointHere(0, state);
}
if (endPoint && node == endPoint.node)
{
selEnd = _pointHere(0, state);
}
}
while (txt.length > 0)
{
var consumed = 0;
if (state.flags.preMode)
{
var firstLine = txt.split('\n', 1)[0];
consumed = firstLine.length + 1;
rest = txt.substring(consumed);
txt = firstLine;
}
else
{ /* will only run this loop body once */
}
if (startPoint && node == startPoint.node && startPoint.index - x <= txt.length)
{
selStart = _pointHere(startPoint.index - x, state);
}
if (endPoint && node == endPoint.node && endPoint.index - x <= txt.length)
{
selEnd = _pointHere(endPoint.index - x, state);
}
var txt2 = txt;
if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt))
{
// prevents textnodes containing just "\n" from being significant
// in safari when pasting text, now that we convert them to
// spaces instead of removing them, because in other cases
// removing "\n" from pasted HTML will collapse words together.
txt2 = "";
}
var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0;
if (atBeginningOfLine)
{
// newlines in the source mustn't become spaces at beginning of line box
txt2 = txt2.replace(/^\n*/, '');
}
if (atBeginningOfLine && state.listType && state.listType != 'none')
{
_produceListMarker(state);
}
lines.appendText(textify(txt2), state.attribString);
x += consumed;
txt = rest;
if (txt.length > 0)
{
cc.startNewLine(state);
}
}
}
else
{
var tname = (dom.nodeTagName(node) || "").toLowerCase();
if (tname == "br")
{
cc.startNewLine(state);
}
else if (tname == "script" || tname == "style")
{
// ignore
}
else if (!isEmpty)
{
var styl = dom.nodeAttr(node, "style");
var cls = dom.nodeProp(node, "className");
var isPre = (tname == "pre");
if ((!isPre) && browser.safari)
{
isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl));
}
if (isPre) cc.incrementFlag(state, 'preMode');
var oldListTypeOrNull = null;
var oldAuthorOrNull = null;
if (collectStyles)
{
plugins_.callHook('collectContentPre', {
cc: cc,
state: state,
tname: tname,
styl: styl,
cls: cls
});
if (tname == "b" || (styl && /\bfont-weight:\s*bold\b/i.exec(styl)) || tname == "strong")
{
cc.doAttrib(state, "bold");
}
if (tname == "i" || (styl && /\bfont-style:\s*italic\b/i.exec(styl)) || tname == "em")
{
cc.doAttrib(state, "italic");
}
if (tname == "u" || (styl && /\btext-decoration:\s*underline\b/i.exec(styl)) || tname == "ins")
{
cc.doAttrib(state, "underline");
}
if (tname == "s" || (styl && /\btext-decoration:\s*line-through\b/i.exec(styl)) || tname == "del")
{
cc.doAttrib(state, "strikethrough");
}
if (tname == "ul")
{
var type;
var rr = cls && /(?:^| )list-(bullet[12345678])\b/.exec(cls);
type = rr && rr[1] || "bullet" + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1));
oldListTypeOrNull = (_enterList(state, type) || 'none');
}
else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/))
{
oldListTypeOrNull = (_enterList(state, type) || 'none');
}
if (className2Author && cls)
{
var classes = cls.match(/\S+/g);
if (classes && classes.length > 0)
{
for (var i = 0; i < classes.length; i++)
{
var c = classes[i];
var a = className2Author(c);
if (a)
{
oldAuthorOrNull = (_enterAuthor(state, a) || 'none');
break;
}
}
}
}
}
var nc = dom.nodeNumChildren(node);
for (var i = 0; i < nc; i++)
{
var c = dom.nodeChild(node, i);
cc.collectContent(c, state);
}
if (collectStyles)
{
plugins_.callHook('collectContentPost', {
cc: cc,
state: state,
tname: tname,
styl: styl,
cls: cls
});
}
if (isPre) cc.decrementFlag(state, 'preMode');
if (state.localAttribs)
{
for (var i = 0; i < state.localAttribs.length; i++)
{
cc.decrementAttrib(state, state.localAttribs[i]);
}
}
if (oldListTypeOrNull)
{
_exitList(state, oldListTypeOrNull);
}
if (oldAuthorOrNull)
{
_exitAuthor(state, oldAuthorOrNull);
}
}
}
if (!browser.msie)
{
_reachBlockPoint(node, 1, state);
}
if (isBlock)
{
if (lines.length() - 1 == startLine)
{
cc.startNewLine(state);
}
else
{
_ensureColumnZero(state);
}
}
if (browser.msie)
{
// in IE, a point immediately after a DIV appears on the next line
_reachBlockPoint(node, 1, state);
}
state.localAttribs = localAttribs;
};
// can pass a falsy value for end of doc
cc.notifyNextNode = function(node)
{
// an "empty block" won't end a line; this addresses an issue in IE with
// typing into a blank line at the end of the document. typed text
// goes into the body, and the empty line div still looks clean.
// it is incorporated as dirty by the rule that a dirty region has
// to end a line.
if ((!node) || (isBlockElement(node) && !_isEmpty(node)))
{
_ensureColumnZero(null);
}
};
// each returns [line, char] or [-1,-1]
var getSelectionStart = function()
{
return selStart;
};
var getSelectionEnd = function()
{
return selEnd;
};
// returns array of strings for lines found, last entry will be "" if
// last line is complete (i.e. if a following span should be on a new line).
// can be called at any point
cc.getLines = function()
{
return lines.textLines();
};
cc.finish = function()
{
lines.flush();
var lineAttribs = lines.attribLines();
var lineStrings = cc.getLines();
lineStrings.length--;
lineAttribs.length--;
var ss = getSelectionStart();
var se = getSelectionEnd();
function fixLongLines()
{
// design mode does not deal with with really long lines!
var lineLimit = 2000; // chars
var buffer = 10; // chars allowed over before wrapping
var linesWrapped = 0;
var numLinesAfter = 0;
for (var i = lineStrings.length - 1; i >= 0; i--)
{
var oldString = lineStrings[i];
var oldAttribString = lineAttribs[i];
if (oldString.length > lineLimit + buffer)
{
var newStrings = [];
var newAttribStrings = [];
while (oldString.length > lineLimit)
{
//var semiloc = oldString.lastIndexOf(';', lineLimit-1);
//var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit);
lengthToTake = lineLimit;
newStrings.push(oldString.substring(0, lengthToTake));
oldString = oldString.substring(lengthToTake);
newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake));
oldAttribString = Changeset.subattribution(oldAttribString, lengthToTake);
}
if (oldString.length > 0)
{
newStrings.push(oldString);
newAttribStrings.push(oldAttribString);
}
function fixLineNumber(lineChar)
{
if (lineChar[0] < 0) return;
var n = lineChar[0];
var c = lineChar[1];
if (n > i)
{
n += (newStrings.length - 1);
}
else if (n == i)
{
var a = 0;
while (c > newStrings[a].length)
{
c -= newStrings[a].length;
a++;
}
n += a;
}
lineChar[0] = n;
lineChar[1] = c;
}
fixLineNumber(ss);
fixLineNumber(se);
linesWrapped++;
numLinesAfter += newStrings.length;
newStrings.unshift(i, 1);
lineStrings.splice.apply(lineStrings, newStrings);
newAttribStrings.unshift(i, 1);
lineAttribs.splice.apply(lineAttribs, newAttribStrings);
}
}
return {
linesWrapped: linesWrapped,
numLinesAfter: numLinesAfter
};
}
var wrapData = fixLongLines();
return {
selStart: ss,
selEnd: se,
linesWrapped: wrapData.linesWrapped,
numLinesAfter: wrapData.numLinesAfter,
lines: lineStrings,
lineAttribs: lineAttribs
};
}
return cc;
}
exports.makeContentCollector = makeContentCollector;

70
node/utils/tar.json Normal file
View file

@ -0,0 +1,70 @@
{
"pad.js": [
"jquery.js"
, "security.js"
, "pad.js"
, "ace2_common.js"
, "pad_utils.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
, "pad_cookie.js"
, "pad_editor.js"
, "pad_editbar.js"
, "pad_docbar.js"
, "pad_modals.js"
, "ace.js"
, "collab_client.js"
, "pad_userlist.js"
, "pad_impexp.js"
, "pad_savedrevs.js"
, "pad_connectionstatus.js"
, "chat.js"
, "excanvas.js"
, "farbtastic.js"
, "prefixfree.js"
]
, "timeslider.js": [
"jquery.js"
, "security.js"
, "plugins.js"
, "undo-xpopup.js"
, "json2.js"
, "colorutils.js"
, "draggable.js"
, "ace2_common.js"
, "pad_utils.js"
, "pad_cookie.js"
, "pad_editor.js"
, "pad_editbar.js"
, "pad_docbar.js"
, "pad_modals.js"
, "pad_savedrevs.js"
, "pad_impexp.js"
, "AttributePoolFactory.js"
, "Changeset.js"
, "domline.js"
, "linestylefilter.js"
, "cssmanager.js"
, "broadcast.js"
, "broadcast_slider.js"
, "broadcast_revisions.js"
, "timeslider.js"
]
, "ace2_inner.js": [
"ace2_common.js"
, "AttributePoolFactory.js"
, "Changeset.js"
, "security.js"
, "skiplist.js"
, "virtual_lines.js"
, "cssmanager.js"
, "colorutils.js"
, "undomodule.js"
, "contentcollector.js"
, "changesettracker.js"
, "linestylefilter.js"
, "domline.js"
, "ace2_inner.js"
]
}

View file

@ -1,27 +1,34 @@
{
"name" : "etherpad-lite",
"description" : "A Etherpad based on node.js",
"homepage" : "https://github.com/Pita/etherpad-lite",
"keywords" : ["etherpad", "realtime", "collaborative", "editor"],
"author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd",
"contributors": [
{ "name": "John McLear",
"name": "Hans Pinckaers",
"name": "Robin Buse"}
],
"dependencies" : {
"name" : "etherpad-lite",
"description" : "A Etherpad based on node.js",
"homepage" : "https://github.com/Pita/etherpad-lite",
"keywords" : ["etherpad", "realtime", "collaborative", "editor"],
"author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd",
"contributors" : [
{ "name": "John McLear",
"name": "Hans Pinckaers",
"name": "Robin Buse" }
],
"dependencies" : {
"yajsml" : "1.1.2",
"request" : "2.9.100",
"require-kernel" : "1.0.3",
"socket.io" : "0.8.7",
"ueberDB" : "0.1.3",
"async" : "0.1.15",
"joose" : "3.50.0",
"express" : "2.5.0",
"clean-css" : "0.2.4",
"uglify-js" : "1.1.1",
"gzip" : "0.1.0",
"formidable" : "1.0.7",
"log4js" : "0.3.9",
"ueberDB" : "0.1.7",
"async" : "0.1.18",
"express" : "2.5.8",
"clean-css" : "0.3.2",
"uglify-js" : "1.2.5",
"formidable" : "1.0.9",
"log4js" : "0.4.1",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2"
},
"version" : "1.0.0"
},
"devDependencies": {
"jshint" : "*"
},
"engines" : { "node" : ">=0.6.0",
"npm" : ">=1.0"
},
"version" : "1.0.0"
}

View file

@ -38,6 +38,10 @@
/* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
but makes it impossible to debug the javascript/css */
"minify" : true,
/* How long may clients use served javascript code? Without versioning this
is may cause problems during deployment. */
"maxAge" : 21600000, // 6 hours
/* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/

View file

@ -32,6 +32,25 @@ ul.list-bullet6 { list-style-type: square; }
ul.list-bullet7 { list-style-type: disc; }
ul.list-bullet8 { list-style-type: circle; }
ol.list-number1 { margin-left: 1.5em; }
ol.list-number2 { margin-left: 3em; }
ol.list-number3 { margin-left: 4.5em; }
ol.list-number4 { margin-left: 6em; }
ol.list-number5 { margin-left: 7.5em; }
ol.list-number6 { margin-left: 9em; }
ol.list-number7 { margin-left: 10.5em; }
ol.list-number8 { margin-left: 12em; }
ol { list-style-type: decimal; }
ol.list-number1 { list-style-type: decimal; }
ol.list-number2 { list-style-type: lower-latin; }
ol.list-number3 { list-style-type: lower-roman; }
ol.list-number4 { list-style-type: decimal; }
ol.list-number5 { list-style-type: lower-latin; }
ol.list-number6 { list-style-type: lower-roman; }
ol.list-number7 { list-style-type: decimal; }
ol.list-number8 { list-style-type: lower-latin; }
ul.list-indent1 { margin-left: 1.5em; }
ul.list-indent2 { margin-left: 3em; }
ul.list-indent3 { margin-left: 4.5em; }
@ -74,7 +93,7 @@ body.doesWrap {
padding-top: 1px; /* important for some reason? */
padding-right: 10px;
padding-bottom: 8px;
padding-left: 1px /* prevents characters from looking chopped off in FF3 */;
padding-left: 1px /* prevents characters from looking chopped off in FF3 -- Removed because it added too much whitespace */;
overflow: hidden;
/* blank 1x1 gif, so that IE8 doesn't consider the body transparent */
background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);
@ -151,34 +170,3 @@ p {
}
#overlaysdiv { position: absolute; left: -1000px; top: -1000px; }
/* ---------- Used by JavaScript Lexer ---------- */
.syntax .c { color: #bd3f00; font-style: italic } /* Comment */
.syntax .o { font-weight: bold; } /* Operator */
.syntax .p { font-weight: bold; } /* Punctuation */
.syntax .k { color: blue; } /* Keyword */
.syntax .kc { color: purple } /* Keyword.Constant */
.syntax .nx { } /* Name.Other */
.syntax .mf { color: purple } /* Literal.Number.Float */
.syntax .mh { color: purple } /* Literal.Number.Hex */
.syntax .mi { color: purple } /* Literal.Number.Integer */
.syntax .sr { color: purple } /* Literal.String.Regex */
.syntax .s2 { color: purple } /* Literal.String.Double */
.syntax .s1 { color: purple } /* Literal.String.Single */
.syntax .sd { color: purple } /* Literal.String.Doc */
.syntax .cs { color: #00aa33; font-weight: bold; font-style: italic } /* Comment.Special */
.syntax .err { color: #cc0000; font-weight: bold; text-decoration: underline; } /* Error */
/* css */
.syntax .nt { font-weight: bold; } /* tag */
.syntax .nc { color: #336; } /* class */
.syntax .nf { color: #336; } /* id */
.syntax .nd { color: #999; } /* :foo */
.syntax .m { color: purple } /* number */
.syntax .nb { color: purple } /* built-in */
.syntax .cp { color: #bd3f00; } /* !important */
.syntax .flash { background-color: #adf !important; }
.syntax .flashbad { background-color: #f55 !important; }

View file

@ -1,13 +1,13 @@
*,html,body,p{ margin: 0; padding: 0; }
.clear { clear: both; }
html { font-size: 62.5%; }
html { font-size: 62.5%; width: 100%; }
body, textarea { font-family: Helvetica, Arial, sans-serif; }
iframe {position:absolute;}
#users
{
position: absolute;
z-index: 10;
z-index:500;
background-color: #000;
background-color: rgba(0,0,0,0.7);
width: 160px;
@ -15,7 +15,6 @@ iframe {position:absolute;}
top: 40px;
color: #fff;
padding: 5px;
-moz-border-radius: 6px;
border-radius: 6px;
}
@ -39,10 +38,7 @@ a img
#editbar
{
background: #f7f7f7;
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
border-bottom: 1px solid #ccc;
height: 32px;
overflow: hidden;
@ -53,10 +49,7 @@ a img
#editbar ul li
{
background: #fff;
background: -moz-linear-gradient(#fff, #f0f0f0);
background: -ms-linear-gradient(#fff, #f0f0f0);
background: -o-linear-gradient(#fff, #f0f0f0);
background: -webkit-linear-gradient(#fff, #f0f0f0);
background: linear-gradient(#fff, #f0f0f0);
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
@ -72,6 +65,7 @@ a img
{
text-decoration: none;
color: #ccc;
position: absolute;
}
#editbar ul li a span
@ -82,18 +76,13 @@ a img
#editbar ul li:hover {
background: #fff;
background: linear-gradient(#f4f4f4, #e4e4e4);
}
#editbar ul li:active {
background: #eee;
background: -moz-linear-gradient(#ddd, #fff);
background: -ms-linear-gradient(#ddd, #fff);
background: -o-linear-gradient(#ddd, #fff);
background: -webkit-linear-gradient(#ddd, #fff);
}
input[type="file"] {
color: #000;
background: linear-gradient(#ddd, #fff);
box-shadow: 0 0 8px rgba(0,0,0,.1) inset;
}
#editbar ul li.separator
@ -194,7 +183,6 @@ a#backtoprosite { padding-left: 20px; left: 6px;
#alertbar {
margin-top: 6px;
opacity: 0;
filter: alpha(opacity = 0); /* IE */
display: none;
position:absolute;
left:0;
@ -388,10 +376,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; }
#mycolorpickersave, #mycolorpickercancel {
background: #fff;
background: -moz-linear-gradient(#fff, #ccc);
background: -ms-linear-gradient(#fff, #ccc);
background: -o-linear-gradient(#fff, #ccc);
background: -webkit-linear-gradient(#fff, #ccc);
background: linear-gradient(#fff, #ccc);
border: 1px solid #ccc;
border-radius: 4px;
font-size:12px;
@ -564,28 +549,6 @@ table#otheruserstable { display: none; }
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. */
#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; font-size: .7em;
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; }
#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;}
@ -751,14 +714,7 @@ a#topbarmaximize {
text-decoration: none;
padding: 50pt;
font-size: 20pt;
-moz-border-radius-topleft: 3pt;
-moz-border-radius-topright: 3pt;
-moz-border-radius-bottomleft: 3pt;
-moz-border-radius-bottomright: 3pt;
-webkit-border-top-left-radius: 3pt;
-webkit-border-top-right-radius: 3pt;
-webkit-border-bottom-left-radius: 3pt;
-webkit-border-bottom-right-radius: 3pt;
border-radius: 3pt;
}
.modaldialog .bigbutton {
@ -767,38 +723,8 @@ a#topbarmaximize {
width: 100%;
}
#embed, #readonly {
display:none;
position:absolute;
top:40px;
font-size:14px;
width:400px;
right: 20px;
z-index: 500;
background-color: #000;
color: white;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.7);
padding: 10px;
-moz-border-radius: 6px;
border-radius: 6px;
}
#embedreadonly {
float:right;
}
#embedcode, #readonlyUrl, #linkcode {
margin-left:10px;
}
#embedinput, #readonlyInput, #linkinput {
width:375px;
height:24px;
display:inline;
margin-top: 10px;
padding-left:4px;
}
ul#colorpickerswatches
{
@ -854,6 +780,7 @@ ul#colorpickerswatches li:hover
left:0px;
top:25px;
bottom:25px;
z-index:1002;
}
#chattext p
@ -874,12 +801,11 @@ ul#colorpickerswatches li:hover
#chatlabel
{
font-size:13px;
line-height:16px;
font-weight:bold;
color:#555;
text-decoration: none;
position: relative;
bottom: 3px;
margin-right: 3px;
vertical-align: middle;
}
#chatinput
@ -892,8 +818,8 @@ ul#colorpickerswatches li:hover
#chaticon
{
z-index: 400;
position:absolute;
bottom:0px;
position: fixed;
bottom: 0px;
right: 20px;
padding: 5px;
border-left: 1px solid #999;
@ -914,8 +840,7 @@ ul#colorpickerswatches li:hover
{
color:#555;
font-size:9px;
position:relative;
bottom: 2px;
vertical-align: middle;
}
#titlebar
@ -930,8 +855,7 @@ ul#colorpickerswatches li:hover
#titlelabel
{
font-size:13px;
margin-left:20px;
padding-top:3px;
margin:4px 0 0 4px;
position:absolute;
}
@ -954,85 +878,20 @@ ul#colorpickerswatches li:hover
margin-left: 3px;
margin-right: 3px;
margin-top:2px;
}
/* resizable stuff for chat */
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
z-index: 99999;
display: block;
}
.ui-resizable-nw {
background-image: url("../img/nw-resize.png");
background-repeat: no-repeat;
background-size: 100% auto;
cursor: nw-resize;
height: 22px;
left: 0;
top: 0;
width: 22px;
}
.ui-resizable-ne
{
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
#importexport{
position:absolute;
top:40px;
font-size:14px;
width:450px;
right: 20px;
z-index: 500;
background-color: #000;
color: white;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.7);
padding: 10px;
-moz-border-radius: 6px;
border-radius: 6px;
height:190px;
display:none;
}
#import{
position:absolute;
width:250px;
left:10px;
line-height:20px;
}
#export{
position:absolute;
width:180px;
right:10px;
line-height:20px;
}
.exporttype{
line-height:25px;
margin-top: 2px;
background-repeat:no-repeat;
padding-left:25px;
background-image: url("../img/fileicons.gif");
background-image: url("../../static/img/etherpad_lite_icons.png");
color:#fff;
text-decoration:none;
}
#importexportline{
border: dotted 1px;
height: 185px;
border-left: 1px solid #fff;
height: 190px;
position:absolute;
width:0px;
left:260px;
@ -1040,40 +899,36 @@ position: relative;
}
.impexpbutton{
background-image: -moz-linear-gradient(center top , #EEEEEE, white 20%, white 20%);
background-image: linear-gradient(center top , #EEEEEE, white 20%, white 20%);
padding:3px;
}
#exporthtml{
background-position: 2px -25px;
background-position: 0px -299px;
}
#exportplain{
background-position: 2px -121px;
background-position: 0px -395px;
}
#exportword{
background-position: 2px -0px;
background-position: 0px -275px;
}
#exportpdf{
background-position: 2px -97px;
background-position: 0px -371px;
}
#exportopen{
background-position: 2px -74px;
background-position: 0px -347px;
}
#exportwordle{
background-position: 2px -49px;
background-position: 0px -323px;
}
#exportdokuwiki{
background-position: 2px -144px;
}
#export a{
text-decoration: none;
background-position: 0px -459px;
}
#importstatusball{
@ -1112,7 +967,6 @@ color: white;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.7);
padding: 10px;
-moz-border-radius: 6px;
border-radius: 6px;
opacity:.8;
}
@ -1125,6 +979,61 @@ background-repeat: no-repeat;
margin-left: 1px;
margin-top: 1px;
}
.buttonicon-bold {
background-position: 0px -116px;
}
.buttonicon-italic {
background-position: 0px 0px;
}
.buttonicon-underline {
background-position: 0px -236px;
}
.buttonicon-strikethrough {
background-position: 0px -200px;
}
.buttonicon-insertorderedlist {
background-position: 0px -477px
}
.buttonicon-insertunorderedlist {
background-position: 0px -34px;
}
.buttonicon-indent {
background-position: 0px -52px;
}
.buttonicon-outdent {
background-position: 0px -134px;
}
.buttonicon-undo {
background-position: 0px -255px;
}
.buttonicon-redo {
background-position :0px -166px;
}
.buttonicon-clearauthorship {
background-position: 0px -86px;
}
.buttonicon-settings {
background-position: 0px -436px;
}
.buttonicon-import_export {
background-position: 0px -68px;
}
.buttonicon-embed {
background-position: 0px -18px;
}
.buttonicon-history {
background-position: 0px -218px;
}
.buttonicon-chat {
background-position: 0px -102px;
display: inline-block;
vertical-align: middle;
margin: 0 !important;
}
.buttonicon-showusers {
background-position: 0px -183px;
display: inline-block;
}
#usericon
{
@ -1145,21 +1054,112 @@ width:33px !important;
}
#online_count{
color: #999;
color: #888;
font-size: 11px;
line-height: 18px;
position: fixed;
}
label[for=readonlyinput] {
margin: 0 10px 0 2px;
}
#qr_center {
margin: 10px 10px auto 0;
text-align: center;
}
#qrcode{
margin-left:10px;
#embedreadonlyqr {
box-shadow: 0 0 10px #000;
border-radius: 3px;
transition: all .2s ease-in-out;
}
#embedreadonlyqr:hover {
cursor: none;
transform: scale(1.5);
}
.rtl{
direction:RTL;
}
#chattext p {
word-wrap: break-word;
}
/* fix for misaligned checkboxes */
input[type=checkbox] {
vertical-align: -1px;
}
.right {
float:right;
}
.popup {
font-size: 14px;
width: 450px;
z-index: 500;
padding: 10px;
border-radius: 6px;
background: #222;
background: rgba(0,0,0,.7);
background: linear-gradient(rgba(0,0,0,.6), rgba(0,0,0,.7) 35px, rgba(0,0,0,.6));
box-shadow: 0 0 8px #888;
color: #fff;
}
.popup input[type=text] {
width: 100%;
padding: 5px;
box-sizing: border-box;
display:block;
margin-top: 10px;
}
.popup a {
text-decoration: none;
}
.popup h1 {
font-size: 18px;
}
.popup h2 {
font-size: 15px;
}
.popup p {
margin: 5px 0;
}
.column {
float: left;
width: 50%;
}
#settingsmenu, #importexport, #embed {
position: absolute;
top: 55px;
right: 20px;
display: none;
}
.note {
color: #ddd;
font-size: 11px;
font-weight: bold;
}
.selected {
background: #eee !important;
background: linear-gradient(#EEE, #F0F0F0) !important;
}
.stickyChat {
background-color: #f1f1f1 !important;
right: 0px !important;
top: 36px;
border-radius: 0px !important;
height: auto !important;
border: none !important;
border-left: 1px solid #ccc !important;
width: 185px !important;
}
@media screen and (max-width: 960px) {
@ -1178,34 +1178,25 @@ label[for=readonlyinput] {
}
}
@media only screen and (min-device-width: 320px) and (max-device-width: 600px) {
@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
#editbar ul li {
padding: 4px 3px;
}
#editbar ul#menu_right > li {
padding: 4px 8px;
margin-top: 2px;
}
#chaticon {
opacity: .8;
}
#users {
right: none;
left: 30px;
right: 0;
top: 36px;
bottom: 33px;
border-radius: none;
}
#mycolorpicker {
right: 0;
left: 0 !important;
left: -72px; /* #mycolorpicker:width - #users:width */
}
#editorcontainer {
margin-bottom: 33px;
}
#editbar ul#menu_right {
background: #f7f7f7;
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
width: 100%;
overflow: hidden;
height: 32px;
@ -1213,9 +1204,6 @@ label[for=readonlyinput] {
bottom: 0;
border-top: 1px solid #ccc;
}
#editbar ul#menu_right li:not(:last-child) {
display: none;
}
#editbar ul#menu_right li:last-child {
height: 24px;
border-radius: 0;
@ -1224,21 +1212,19 @@ label[for=readonlyinput] {
float: right;
}
#chaticon {
bottom: 0;
bottom: 3px;
right: 55px;
border-right: none;
border-radius: 0;
background: #f7f7f7;
background: -moz-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -o-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%);
background: linear-gradient(#f7f7f7, #f1f1f1 80%);
border: 0;
}
#chatbox {
bottom: 32px;
right: 0;
border-top-right-radius: 0;
border-right: none;
}
#editbar ul li a span {
top: -3px;
@ -1246,10 +1232,39 @@ label[for=readonlyinput] {
#usericonback {
margin-top: 4px;
}
#sidediv {
display:none;
#qrcode {
display: none;
}
}
.rtl{
direction:RTL;
}
#editbar ul#menu_right li:not(:last-child) {
display: block;
}
#editbar ul#menu_right > li {
background: none;
border: none;
margin-top: 4px;
padding: 4px 8px;
}
.selected {
background: none !important;
}
#timesliderlink {
display: none !important;
}
.popup {
border-radius: 0;
box-sizing: border-box;
width: 100%;
}
#settingsmenu, #importexport, #embed {
left: 0;
top: 0;
bottom: 33px;
right: 0;
}
.separator {
display: none;
}
#online_count {
line-height: 24px;
}
}

View file

@ -1,218 +1,106 @@
#editorcontainerbox {
overflow: auto;
#editorcontainerbox {overflow:auto; top:40px;}
#padcontent {font-size:12px; padding:10px;}
#timeslider-wrapper {left:0; position:relative; right:0; top:0;}
#timeslider-left {background-image:url(../../static/img/timeslider_left.png); height:63px; left:0; position:absolute; width:134px;}
#timeslider-right {background-image:url(../../static/img/timeslider_right.png); height:63px; position:absolute; right:0; top:0; width:155px;}
#timeslider {background-image:url(../../static/img/timeslider_background.png); height:63px; margin:0 9px;}
#timeslider #timeslider-slider {height:61px; left:0; position:absolute; top:1px; width:100%;}
#ui-slider-handle {
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
-webkit-user-select:none;
background-image:url(../../static/img/crushed_current_location.png);
cursor:pointer;
height:61px;
left:0;
position:absolute;
top:0;
user-select:none;
width:13px;
}
#ui-slider-bar {
-khtml-user-select:none;
-moz-user-select:none;
-ms-user-select:none;
-webkit-user-select:none;
cursor:pointer;
height:35px;
margin-left:5px;
margin-right:148px;
position:relative;
top:20px;
user-select:none;
}
#playpause_button, #playpause_button_icon {height:47px; position:absolute; width:47px;}
#playpause_button {background-image:url(../../static/img/crushed_button_undepressed.png); right:77px; top:9px;}
#playpause_button_icon {background-image:url(../../static/img/play.png); left:0; top:0;}
.pause#playpause_button_icon {background-image:url(../../static/img/pause.png);}
#leftstar, #rightstar, #leftstep, #rightstep
{background:url(../../static/img/stepper_buttons.png) 0 0 no-repeat; height:21px; overflow:hidden; position:absolute;}
#leftstar {background-position:0 44px; right:34px; top:8px; width:30px;}
#rightstar {background-position:29px 44px; right:5px; top:8px; width:29px;}
#leftstep {background-position:0 22px; right:34px; top:20px; width:30px;}
#rightstep {background-position:29px 22px; right:5px; top:20px; width:29px;}
#timeslider .star {
background-image:url(../../static/img/star.png);
cursor:pointer;
height:16px;
position:absolute;
top:40px;
width:15px;
}
#padcontent {
padding: 10px;
font-size: 12px;
#timeslider #timer {
color:#fff;
font-family:Arial, sans-serif;
font-size:11px;
left:7px;
position:absolute;
text-align:center;
top:9px;
width:122px;
}
#timeslider-wrapper {
position: relative;
left: 0px;
right: 0px;
top: 0px;
}
.topbarcenter, #docbar {display:none;}
#padmain {top:30px;}
#editbarright {float:right;}
#returnbutton {color:#222; font-size:16px; line-height:29px; margin-top:0; padding-right:6px;}
#importexport {top:118px;}
#importexport .popup {width:185px;}
#timeslider-left {
position: absolute;
left:0px;
background-image: url(../../static/img/timeslider_left.png);
width: 134px;
height: 63px;
}
/* lists */
.list-bullet2, .list-indent2, .list-number2 {margin-left:3em;}
.list-bullet3, .list-indent3, .list-number3 {margin-left:4.5em;}
.list-bullet4, .list-indent4, .list-number4 {margin-left:6em;}
.list-bullet5, .list-indent5, .list-number5 {margin-left:7.5em;}
.list-bullet6, .list-indent6, .list-number6 {margin-left:9em;}
.list-bullet7, .list-indent7, .list-number7 {margin-left:10.5em;}
.list-bullet8, .list-indent8, .list-number8 {margin-left:12em;}
#timeslider-right {
position: absolute;
top:0px;
right:0px;
background-image: url(../../static/img/timeslider_right.png);
width: 155px;
height: 63px;
}
/* unordered lists */
UL {list-style-type:disc; margin-left:1.5em;}
UL UL {margin-left:0 !important;}
.list-bullet2, .list-bullet5, .list-bullet8 {list-style-type:circle;}
.list-bullet3, .list-bullet6 {list-style-type:square;}
.list-indent1, .list-indent2, .list-indent3, .list-indent5, .list-indent5, .list-indent6, .list-indent7, .list-indent8 {list-style-type:none;}
/* ordered lists */
OL {list-style-type:decimal; margin-left:1.5em;}
.list-number2, .list-number5, .list-number8 {list-style-type:lower-latin;}
.list-number3, .list-number6 {list-style-type:lower-roman;}
#timeslider {
/* margin:7px;*/
margin-bottom: 0px;
height: 63px;
margin-left: 9px;
margin-right: 9px;
background-image: url(../../static/img/timeslider_background.png);
}
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/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/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/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/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/play.gif); /* IE 6/7 */
}
div#timeslider div#playpause_button div.pause#playpause_button_icon {
background-image: url(../../static/img/pause.png);
}
* html div#timeslider div#playpause_button div.pause#playpause_button_icon {
background-image: url(../../static/img/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/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/stepper_buttons.png) 29px 44px;
overflow:hidden;
}
div #timeslider div#steppers div#leftstep {
position: absolute;
right: 34px;
top: 20px;
width:30px;
height:21px;
background: url(../../static/img/stepper_buttons.png) 0px 22px;
overflow:hidden;
}
div #timeslider div#steppers div#rightstep {
position: absolute;
right: 5px;
top: 20px;
width:29px;
height:21px;
background: url(../../static/img/stepper_buttons.png) 29px 22px;
overflow:hidden;
}
#timeslider div.star {
position: absolute;
top: 40px;
background-image: url(../../static/img/star.png);
width: 15px;
height: 16px;
cursor: pointer;
}
* html #timeslider div.star {
background-image: url(../../static/img/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;
}
.topbarcenter{
display:none;
}
#docbar{
display:none;
}
#padmain{
top:30px;
}
#editbarright{
float:right;
}
#returnbutton
{
font-size: 16px;
line-height: 29px;
margin-top: 0;
padding-right: 6pt;
color: #222;
}
ul { margin-left: 1.5em; }
ul ul { margin-left: 0 !important; }
ul.list-bullet1 { margin-left: 1.5em; }
ul.list-bullet2 { margin-left: 3em; }
ul.list-bullet3 { margin-left: 4.5em; }
ul.list-bullet4 { margin-left: 6em; }
ul.list-bullet5 { margin-left: 7.5em; }
ul.list-bullet6 { margin-left: 9em; }
ul.list-bullet7 { margin-left: 10.5em; }
ul.list-bullet8 { margin-left: 12em; }
ul { list-style-type: disc; }
ul.list-bullet1 { list-style-type: disc; }
ul.list-bullet2 { list-style-type: circle; }
ul.list-bullet3 { list-style-type: square; }
ul.list-bullet4 { list-style-type: disc; }
ul.list-bullet5 { list-style-type: circle; }
ul.list-bullet6 { list-style-type: square; }
ul.list-bullet7 { list-style-type: disc; }
ul.list-bullet8 { list-style-type: circle; }
/* IE 6/7 fixes ################################################################ */
* HTML #ui-slider-handle {background-image:url(../../static/img/current_location.gif);}
* HTML #timeslider .star {background-image:url(../../static/img/star.gif);}
* HTML #playpause_button_icon {background-image:url(../../static/img/play.gif);}
* HTML .pause#playpause_button_icon {background-image:url(../../static/img/pause.gif);}

View file

@ -1,5 +1,8 @@
/*
You may have to use !important to override css attributs, for example:
* {color: blue !important;}
custom css files are loaded after core css files. Simply use the same selector to override a style.
Example:
#editbar LI {border:1px solid #000;}
overrides
#editbar LI {border:1px solid #d5d5d5;}
from pad.css
*/

View file

@ -1,6 +1,6 @@
function costumStart()
function customStart()
{
//define your javascript here
//jquery is avaiable - except index.js
//jquery is available - except index.js
//you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

View file

@ -1,27 +1,25 @@
<!doctype html>
<html>
<title>RPG Write</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<style>
*{ margin:0;padding:0; }
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<style>
body {
background: rgba(0,0,0,.05);
margin: 0;
height: 100%;
color: #333;
font: 14px helvetica,sans-serif;
background: #ccc;
font: 14px helvetica, sans-serif;
background: #ddd;
background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
overflow-x: hidden;
border-top: 8px solid rgba(51,51,51,.8);
}
#container {
text-shadow: 0 1px 1px #fff;
#wrapper {
border-top: 1px solid #999;
margin-top: 160px;
text-align: center;
padding: 15px;
background: #eee;
background: -webkit-linear-gradient(#fff,#ccc);
@ -31,6 +29,10 @@
opacity: .9;
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
}
#inner {
width: 300px;
margin: 0 auto;
}
#button {
margin: 0 auto;
border-radius: 3px;
@ -40,7 +42,6 @@
text-shadow: 0 -1px 0 rgba(0,0,0,.8);
height: 70px;
line-height: 70px;
width: 300px;
background: #555;
background: -webkit-linear-gradient(#5F5F5F,#565656 50%,#4C4C4C 51%,#373737);
background: -moz-linear-gradient(#5F5F5F,#565656 50%,#4C4C4C 51%,#373737);
@ -62,61 +63,70 @@
}
#label {
text-align: left;
margin: 0 auto;
width: 300px;
text-shadow: 0 1px 1px #fff;
margin: 16px auto 0;
}
input {
vertical-align: middle;
form {
height: 38px;
background: #fff;
border: 1px solid #bbb;
border-radius: 3px;
position: relative;
}
button, input {
font-weight: bold;
font-size: 15px;
}
input[type="text"] {
width: 243px;
padding: 10px 47px 10px 10px;
background: #fff;
border: 1px solid #bbb;
outline: none;
border-radius: 3px;
text-shadow: 0 0 1px #fff;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 0 45px 0 10px;
*padding: 0; /* IE7 hack */
width: 100%;
height: 100%;
outline: none;
border: none;
position: absolute;
}
input[type="submit"] {
button[type="submit"] {
position: absolute;
right: 0;
width: 45px;
margin-left: -50px;
padding: 8px;
height: 38px;
}
input[type="submit"]::-moz-focus-inner { border: 0 }
@-moz-document url-prefix() { input[type="submit"] { padding: 7px } }
@media only screen and (min-device-width: 320px) and (max-device-width: 600px) {
@media only screen and (min-device-width: 320px) and (max-device-width: 720px) {
body {
background: #bbb;
background: -webkit-linear-gradient(#aaa,#eee 60%) center fixed;
height: 100%;
background: -moz-linear-gradient(#aaa,#eee 60%) center fixed;
background: -ms-linear-gradient(#aaa,#eee 60%) center fixed;
}
#container {
#wrapper {
margin-top: 0;
text-align: left;
}
#button, #label {
text-align: center;
#inner {
width: 95%;
}
form {
text-align: center;
}
input[type=text] {
width: 75%;
#label {
text-align: center;
}
}
</style>
</style>
<link href="static/custom/index.css" rel="stylesheet">
<div id="wrapper">
<div id="inner">
<div id="button" onclick="go2Random()" class="translate">New Pad</div>
<div id="label" class="translate">or create/open a Pad with the name</div>
<form action="#" onsubmit="go2Name();return false;">
<input type="text" id="padname" autofocus x-webkit-speech>
<button type="submit">OK</button>
</form>
</div>
</div>
<script src="static/custom/index.js"></script>
<div id="container">
<div id="button" onclick="go2Random()" class="translate">New Pad</div><br><div id="label" class="translate">or create/open a Pad with the name</div>
<form action="#" onsubmit="go2Name();return false;">
<input type="text" id="padname" autofocus>
<input type="submit" value="OK">
</form>
</div>
<script>
function go2Name()
{
@ -142,9 +152,16 @@
return randomstring;
}
<<<<<<< HEAD
//start the costum js
if(typeof costumStart == "function") costumStart();
</script>
</body>
</html>
=======
// start the custom js
if (typeof customStart == "function") customStart();
</script>
>>>>>>> 6fd73ecfda27fd6639c30a4c05c6149d66f669ee
</html>

View file

@ -1,8 +1,8 @@
/**
* This code represents the Attribute Pool Object of the original Etherpad.
* This code represents the Attribute Pool Object of the original Etherpad.
* 90% of the code is still like in the original Etherpad
* Look at https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
* You can find a explanation what a attribute pool is here:
* You can find a explanation what a attribute pool is here:
* https://github.com/Pita/etherpad-lite/blob/master/doc/easysync/easysync-notes.txt
*/

View file

@ -1,10 +1,10 @@
/*
* This is the Changeset library copied from the old Etherpad with some modifications to use it in node.js
* Can be found in https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
*/
*/
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
@ -25,16 +25,30 @@
* limitations under the License.
*/
var AttributePoolFactory = require("./AttributePoolFactory");
var AttributePoolFactory = require("/AttributePoolFactory");
var _opt = null;
//var exports = {};
/**
* ==================== General Util Functions =======================
*/
/**
* This method is called whenever there is an error in the sync process
* @param msg {string} Just some message
*/
exports.error = function error(msg) {
var e = new Error(msg);
e.easysync = true;
throw e;
};
/**
* This method is user for assertions with Messages
* if assert fails, the error function called.
* @param b {boolean} assertion condition
* @param msgParts {string} error to be passed if it fails
*/
exports.assert = function assert(b, msgParts) {
if (!b) {
var msg = Array.prototype.slice.call(arguments, 1).join('');
@ -42,12 +56,30 @@ exports.assert = function assert(b, msgParts) {
}
};
/**
* Parses a number from string base 36
* @param str {string} string of the number in base 36
* @returns {int} number
*/
exports.parseNum = function (str) {
return parseInt(str, 36);
};
/**
* Writes a number in base 36 and puts it in a string
* @param num {int} number
* @returns {string} string
*/
exports.numToString = function (num) {
return num.toString(36).toLowerCase();
};
/**
* Converts stuff before $ to base 10
* @obsolete not really used anywhere??
* @param cs {string} the string
* @return integer
*/
exports.toBaseTen = function (cs) {
var dollarIndex = cs.indexOf('$');
var beforeDollar = cs.substring(0, dollarIndex);
@ -57,13 +89,34 @@ exports.toBaseTen = function (cs) {
}) + fromDollar;
};
/**
* ==================== Changeset Functions =======================
*/
/**
* returns the required length of the text before changeset
* can be applied
* @param cs {string} String representation of the Changeset
*/
exports.oldLen = function (cs) {
return exports.unpack(cs).oldLen;
};
/**
* returns the length of the text after changeset is applied
* @param cs {string} String representation of the Changeset
*/
exports.newLen = function (cs) {
return exports.unpack(cs).newLen;
};
/**
* this function creates an iterator which decodes string changeset operations
* @param opsStr {string} String encoding of the change operations to be performed
* @param optStartIndex {int} from where in the string should the iterator start
* @return {Op} type object iterator
*/
exports.opIterator = function (opsStr, optStartIndex) {
//print(opsStr);
var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
@ -129,12 +182,21 @@ exports.opIterator = function (opsStr, optStartIndex) {
};
};
/**
* Cleans an Op object
* @param {Op} object to be cleared
*/
exports.clearOp = function (op) {
op.opcode = '';
op.chars = 0;
op.lines = 0;
op.attribs = '';
};
/**
* Creates a new Op object
* @param optOpcode the type operation of the Op object
*/
exports.newOp = function (optOpcode) {
return {
opcode: (optOpcode || ''),
@ -143,6 +205,11 @@ exports.newOp = function (optOpcode) {
attribs: ''
};
};
/**
* Clones an Op
* @param op Op to be cloned
*/
exports.cloneOp = function (op) {
return {
opcode: op.opcode,
@ -151,12 +218,22 @@ exports.cloneOp = function (op) {
attribs: op.attribs
};
};
/**
* Copies op1 to op2
* @param op1 src Op
* @param op2 dest Op
*/
exports.copyOp = function (op1, op2) {
op2.opcode = op1.opcode;
op2.chars = op1.chars;
op2.lines = op1.lines;
op2.attribs = op1.attribs;
};
/**
* Writes the Op in a string the way that changesets need it
*/
exports.opString = function (op) {
// just for debugging
if (!op.opcode) return 'null';
@ -164,11 +241,19 @@ exports.opString = function (op) {
assem.append(op);
return assem.toString();
};
/**
* Used just for debugging
*/
exports.stringOp = function (str) {
// just for debugging
return exports.opIterator(str).next();
};
/**
* Used to check if a Changeset if valid
* @param cs {Changeset} Changeset to be checked
*/
exports.checkRep = function (cs) {
// doesn't check things that require access to attrib pool (e.g. attribute order)
// or original string (e.g. newline positions)
@ -218,6 +303,15 @@ exports.checkRep = function (cs) {
return cs;
}
/**
* ==================== Util Functions =======================
*/
/**
* creates an object that allows you to append operations (type Op) and also
* compresses them if possible
*/
exports.smartOpAssembler = function () {
// Like opAssembler but able to produce conforming exportss
// from slightly looser input, at the cost of speed.
@ -474,6 +568,10 @@ if (_opt) {
};
}
/**
* A custom made String Iterator
* @param str {string} String to be iterated over
*/
exports.stringIterator = function (str) {
var curIndex = 0;
@ -510,6 +608,9 @@ exports.stringIterator = function (str) {
};
};
/**
* A custom made StringBuffer
*/
exports.stringAssembler = function () {
var pieces = [];
@ -526,7 +627,11 @@ exports.stringAssembler = function () {
};
};
// "lines" need not be an array as long as it supports certain calls (lines_foo inside).
/**
* This class allows to iterate and modify texts which have several lines
* It is used for applying Changesets on arrays of lines
* Note from prev docs: "lines" need not be an array as long as it supports certain calls (lines_foo inside).
*/
exports.textLinesMutator = function (lines) {
// Mutates lines, an array of strings, in place.
// Mutation operations have the same constraints as exports operations
@ -781,6 +886,21 @@ exports.textLinesMutator = function (lines) {
return self;
};
/**
* Function allowing iterating over two Op strings.
* @params in1 {string} first Op string
* @params idx1 {int} integer where 1st iterator should start
* @params in2 {string} second Op string
* @params idx2 {int} integer where 2nd iterator should start
* @params func {function} which decides how 1st or 2nd iterator
* advances. When opX.opcode = 0, iterator X advances to
* next element
* func has signature f(op1, op2, opOut)
* op1 - current operation of the first iterator
* op2 - current operation of the second iterator
* opOut - result operator to be put into Changeset
* @return {string} the integrated changeset
*/
exports.applyZip = function (in1, idx1, in2, idx2, func) {
var iter1 = exports.opIterator(in1, idx1);
var iter2 = exports.opIterator(in2, idx2);
@ -802,6 +922,11 @@ exports.applyZip = function (in1, idx1, in2, idx2, func) {
return assem.toString();
};
/**
* Unpacks a string encoded Changeset into a proper Changeset object
* @params cs {string} String encoded Changeset
* @returns {Changeset} a Changeset class
*/
exports.unpack = function (cs) {
var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
var headerMatch = headerRegex.exec(cs);
@ -823,6 +948,14 @@ exports.unpack = function (cs) {
};
};
/**
* Packs Changeset object into a string
* @params oldLen {int} Old length of the Changeset
* @params newLen {int] New length of the Changeset
* @params opsStr {string} String encoding of the changes to be made
* @params bank {string} Charbank of the Changeset
* @returns {Changeset} a Changeset class
*/
exports.pack = function (oldLen, newLen, opsStr, bank) {
var lenDiff = newLen - oldLen;
var lenDiffStr = (lenDiff >= 0 ? '>' + exports.numToString(lenDiff) : '<' + exports.numToString(-lenDiff));
@ -831,6 +964,11 @@ exports.pack = function (oldLen, newLen, opsStr, bank) {
return a.join('');
};
/**
* Applies a Changeset to a string
* @params cs {string} String encoded Changeset
* @params str {string} String to which a Changeset should be applied
*/
exports.applyToText = function (cs, str) {
var unpacked = exports.unpack(cs);
exports.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen);
@ -856,6 +994,11 @@ exports.applyToText = function (cs, str) {
return assem.toString();
};
/**
* applies a changeset on an array of lines
* @param CS {Changeset} the changeset to be applied
* @param lines The lines to which the changeset needs to be applied
*/
exports.mutateTextLines = function (cs, lines) {
var unpacked = exports.unpack(cs);
var csIter = exports.opIterator(unpacked.ops);
@ -878,6 +1021,13 @@ exports.mutateTextLines = function (cs, lines) {
mut.close();
};
/**
* Composes two attribute strings (see below) into one.
* @param att1 {string} first attribute string
* @param att2 {string} second attribue string
* @param resultIsMutaton {boolean}
* @param pool {AttribPool} attribute pool
*/
exports.composeAttributes = function (att1, att2, resultIsMutation, pool) {
// att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
// Sometimes attribute (key,value) pairs are treated as attribute presence
@ -935,6 +1085,10 @@ exports.composeAttributes = function (att1, att2, resultIsMutation, pool) {
return buf.toString();
};
/**
* Function used as parameter for applyZip to apply a Changeset to an
* attribute
*/
exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) {
// attOp is the op from the sequence that is being operated on, either an
// attribution string or the earlier of two exportss being composed.
@ -1021,6 +1175,12 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) {
}
};
/**
* Applies a Changeset to the attribs string of a AText.
* @param cs {string} Changeset
* @param astr {string} the attribs string of a AText
* @param pool {AttribsPool} the attibutes pool
*/
exports.applyToAttribution = function (cs, astr, pool) {
var unpacked = exports.unpack(cs);
@ -1129,6 +1289,11 @@ exports.mutateAttributionLines = function (cs, lines, pool) {
//dmesg("-> "+lines.toSource());
};
/**
* joins several Attribution lines
* @param theAlines collection of Attribution lines
* @returns {string} joined Attribution lines
*/
exports.joinAttributionLines = function (theAlines) {
var assem = exports.mergingOpAssembler();
for (var i = 0; i < theAlines.length; i++) {
@ -1179,10 +1344,20 @@ exports.splitAttributionLines = function (attrOps, text) {
return lines;
};
/**
* splits text into lines
* @param {string} text to be splitted
*/
exports.splitTextLines = function (text) {
return text.match(/[^\n]*(?:\n|[^\n]$)/g);
};
/**
* compose two Changesets
* @param cs1 {Changeset} first Changeset
* @param cs2 {Changeset} second Changeset
* @param pool {AtribsPool} Attribs pool
*/
exports.compose = function (cs1, cs2, pool) {
var unpacked1 = exports.unpack(cs1);
var unpacked2 = exports.unpack(cs2);
@ -1225,10 +1400,14 @@ exports.compose = function (cs1, cs2, pool) {
return exports.pack(len1, len3, newOps, bankAssem.toString());
};
/**
* returns a function that tests if a string of attributes
* (e.g. *3*4) contains a given attribute key,value that
* is already present in the pool.
* @param attribPair array [key,value] of the attribute
* @param pool {AttribPool} Attribute pool
*/
exports.attributeTester = function (attribPair, pool) {
// returns a function that tests if a string of attributes
// (e.g. *3*4) contains a given attribute key,value that
// is already present in the pool.
if (!pool) {
return never;
}
@ -1247,10 +1426,27 @@ exports.attributeTester = function (attribPair, pool) {
}
};
/**
* creates the identity Changeset of length N
* @param N {int} length of the identity changeset
*/
exports.identity = function (N) {
return exports.pack(N, N, "", "");
};
/**
* creates a Changeset which works on oldFullText and removes text
* from spliceStart to spliceStart+numRemoved and inserts newText
* instead. Also gives possibility to add attributes optNewTextAPairs
* for the new text.
* @param oldFullText {string} old text
* @param spliecStart {int} where splicing starts
* @param numRemoved {int} number of characters to be removed
* @param newText {string} string to be inserted
* @param optNewTextAPairs {string} new pairs to be inserted
* @param pool {AttribPool} Attribution Pool
*/
exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) {
var oldLen = oldFullText.length;
@ -1271,8 +1467,14 @@ exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, op
return exports.pack(oldLen, newLen, assem.toString(), newText);
};
/**
* Transforms a changeset into a list of splices in the form
* [startChar, endChar, newText] meaning replace text from
* startChar to endChar with newText
* @param cs Changeset
*/
exports.toSplices = function (cs) {
// get a list of splices, [startChar, endChar, newText]
//
var unpacked = exports.unpack(cs);
var splices = [];
@ -1302,6 +1504,9 @@ exports.toSplices = function (cs) {
return splices;
};
/**
*
*/
exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) {
var newStartChar = startChar;
var newEndChar = endChar;
@ -1346,6 +1551,14 @@ exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter
return [newStartChar, newEndChar];
};
/**
* Iterate over attributes in a changeset and move them from
* oldPool to newPool
* @param cs {Changeset} Chageset/attribution string to be iterated over
* @param oldPool {AttribPool} old attributes pool
* @param newPool {AttribPool} new attributes pool
* @return {string} the new Changeset
*/
exports.moveOpsToNewPool = function (cs, oldPool, newPool) {
// works on exports or attribution string
var dollarPos = cs.indexOf('$');
@ -1363,13 +1576,22 @@ exports.moveOpsToNewPool = function (cs, oldPool, newPool) {
}) + fromDollar;
};
/**
* create an attribution inserting a text
* @param text {string} text to be inserted
*/
exports.makeAttribution = function (text) {
var assem = exports.smartOpAssembler();
assem.appendOpWithText('+', text);
return assem.toString();
};
// callable on a exports, attribution string, or attribs property of an op
/**
* Iterates over attributes in exports, attribution string, or attribs property of an op
* and runs function func on them
* @param cs {Changeset} changeset
* @param func {function} function to be called
*/
exports.eachAttribNumber = function (cs, func) {
var dollarPos = cs.indexOf('$');
if (dollarPos < 0) {
@ -1383,12 +1605,21 @@ exports.eachAttribNumber = function (cs, func) {
});
};
// callable on a exports, attribution string, or attribs property of an op,
// though it may easily create adjacent ops that can be merged.
/**
* Filter attributes which should remain in a Changeset
* callable on a exports, attribution string, or attribs property of an op,
* though it may easily create adjacent ops that can be merged.
* @param cs {Changeset} changeset to be filtered
* @param filter {function} fnc which returns true if an
* attribute X (int) should be kept in the Changeset
*/
exports.filterAttribNumbers = function (cs, filter) {
return exports.mapAttribNumbers(cs, filter);
};
/**
* does exactly the same as exports.filterAttribNumbers
*/
exports.mapAttribNumbers = function (cs, func) {
var dollarPos = cs.indexOf('$');
if (dollarPos < 0) {
@ -1410,6 +1641,12 @@ exports.mapAttribNumbers = function (cs, func) {
return newUpToDollar + cs.substring(dollarPos);
};
/**
* Create a Changeset going from Identity to a certain state
* @params text {string} text of the final change
* @attribs attribs {string} optional, operations which insert
* the text and also puts the right attributes
*/
exports.makeAText = function (text, attribs) {
return {
text: text,
@ -1417,6 +1654,12 @@ exports.makeAText = function (text, attribs) {
};
};
/**
* Apply a Changeset to a AText
* @param cs {Changeset} Changeset to be applied
* @param atext {AText}
* @param pool {AttribPool} Attribute Pool to add to
*/
exports.applyToAText = function (cs, atext, pool) {
return {
text: exports.applyToText(cs, atext.text),
@ -1424,6 +1667,10 @@ exports.applyToAText = function (cs, atext, pool) {
};
};
/**
* Clones a AText structure
* @param atext {AText}
*/
exports.cloneAText = function (atext) {
return {
text: atext.text,
@ -1431,11 +1678,20 @@ exports.cloneAText = function (atext) {
};
};
/**
* Copies a AText structure from atext1 to atext2
* @param atext {AText}
*/
exports.copyAText = function (atext1, atext2) {
atext2.text = atext1.text;
atext2.attribs = atext1.attribs;
};
/**
* Append the set of operations from atext to an assembler
* @param atext {AText}
* @param assem Assembler like smartOpAssembler
*/
exports.appendATextToAssembler = function (atext, assem) {
// intentionally skips last newline char of atext
var iter = exports.opIterator(atext.attribs);
@ -1469,6 +1725,11 @@ exports.appendATextToAssembler = function (atext, assem) {
}
};
/**
* Creates a clone of a Changeset and it's APool
* @param cs {Changeset}
* @param pool {AtributePool}
*/
exports.prepareForWire = function (cs, pool) {
var newPool = AttributePoolFactory.createAttributePool();;
var newCs = exports.moveOpsToNewPool(cs, pool, newPool);
@ -1478,15 +1739,32 @@ exports.prepareForWire = function (cs, pool) {
};
};
/**
* Checks if a changeset s the identity changeset
*/
exports.isIdentity = function (cs) {
var unpacked = exports.unpack(cs);
return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
};
/**
* returns all the values of attributes with a certain key
* in an Op attribs string
* @param attribs {string} Attribute string of a Op
* @param key {string} string to be seached for
* @param pool {AttribPool} attribute pool
*/
exports.opAttributeValue = function (op, key, pool) {
return exports.attribsAttributeValue(op.attribs, key, pool);
};
/**
* returns all the values of attributes with a certain key
* in an attribs string
* @param attribs {string} Attribute string
* @param key {string} string to be seached for
* @param pool {AttribPool} attribute pool
*/
exports.attribsAttributeValue = function (attribs, key, pool) {
var value = '';
if (attribs) {
@ -1499,6 +1777,11 @@ exports.attribsAttributeValue = function (attribs, key, pool) {
return value;
};
/**
* Creates a Changeset builder for a string with initial
* length oldLen. Allows to add/remove parts of it
* @param oldLen {int} Old length
*/
exports.builder = function (oldLen) {
var assem = exports.smartOpAssembler();
var o = exports.newOp();

View file

@ -28,9 +28,10 @@ Ace2Editor.registry = {
nextId: 1
};
var plugins = require('/plugins').plugins;
function Ace2Editor()
{
var thisFunctionsName = "Ace2Editor";
var ace2 = Ace2Editor;
var editor = {};
@ -48,8 +49,7 @@ function Ace2Editor()
{
var that = this;
var args = arguments;
function action()
var action = function()
{
func.apply(that, args);
}
@ -70,78 +70,47 @@ function Ace2Editor()
function doActionsPendingInit()
{
for (var i = 0; i < actionsPendingInit.length; i++)
{
actionsPendingInit[i]();
}
$.each(actionsPendingInit, function(i,fn){
fn()
});
actionsPendingInit = [];
}
ace2.registry[info.id] = info;
editor.importText = pendingInit(function(newCode, undoable)
{
info.ace_importText(newCode, undoable);
});
editor.importAText = pendingInit(function(newCode, apoolJsonObj, undoable)
{
info.ace_importAText(newCode, apoolJsonObj, undoable);
// The following functions (prefixed by 'ace_') are exposed by editor, but
// execution is delayed until init is complete
var aceFunctionsPendingInit = ['importText', 'importAText', 'focus',
'setEditable', 'getFormattedCode', 'setOnKeyPress', 'setOnKeyDown',
'setNotifyDirty', 'setProperty', 'setBaseText', 'setBaseAttributedText',
'applyChangesToBase', 'applyPreparedChangesetToBase',
'setUserChangeNotificationCallback', 'setAuthorInfo',
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange'];
$.each(aceFunctionsPendingInit, function(i,fnName){
var prefix = 'ace_';
var name = prefix + fnName;
editor[fnName] = pendingInit(function(){
info[prefix + fnName].apply(this, arguments);
});
});
editor.exportText = function()
{
if (!loaded) return "(awaiting init)\n";
return info.ace_exportText();
};
editor.getFrame = function()
{
return info.frame || null;
};
editor.focus = pendingInit(function()
{
info.ace_focus();
});
editor.setEditable = pendingInit(function(newVal)
{
info.ace_setEditable(newVal);
});
editor.getFormattedCode = function()
{
return info.ace_getFormattedCode();
};
editor.setOnKeyPress = pendingInit(function(handler)
{
info.ace_setOnKeyPress(handler);
});
editor.setOnKeyDown = pendingInit(function(handler)
{
info.ace_setOnKeyDown(handler);
});
editor.setNotifyDirty = pendingInit(function(handler)
{
info.ace_setNotifyDirty(handler);
});
editor.setProperty = pendingInit(function(key, value)
{
info.ace_setProperty(key, value);
});
editor.getDebugProperty = function(prop)
{
return info.ace_getDebugProperty(prop);
};
editor.setBaseText = pendingInit(function(txt)
{
info.ace_setBaseText(txt);
});
editor.setBaseAttributedText = pendingInit(function(atxt, apoolJsonObj)
{
info.ace_setBaseAttributedText(atxt, apoolJsonObj);
});
editor.applyChangesToBase = pendingInit(function(changes, optAuthor, apoolJsonObj)
{
info.ace_applyChangesToBase(changes, optAuthor, apoolJsonObj);
});
// prepareUserChangeset:
// Returns null if no new changes or ACE not ready. Otherwise, bundles up all user changes
// to the latest base text into a Changeset, which is returned (as a string if encodeAsString).
@ -156,24 +125,6 @@ function Ace2Editor()
if (!loaded) return null;
return info.ace_prepareUserChangeset();
};
editor.applyPreparedChangesetToBase = pendingInit(
function()
{
info.ace_applyPreparedChangesetToBase();
});
editor.setUserChangeNotificationCallback = pendingInit(function(callback)
{
info.ace_setUserChangeNotificationCallback(callback);
});
editor.setAuthorInfo = pendingInit(function(author, authorInfo)
{
info.ace_setAuthorInfo(author, authorInfo);
});
editor.setAuthorSelectionRange = pendingInit(function(author, start, end)
{
info.ace_setAuthorSelectionRange(author, start, end);
});
editor.getUnhandledErrors = function()
{
@ -182,45 +133,71 @@ function Ace2Editor()
return info.ace_getUnhandledErrors();
};
editor.callWithAce = pendingInit(function(fn, callStack, normalize)
{
return info.ace_callWithAce(fn, callStack, normalize);
});
editor.execCommand = pendingInit(function(cmd, arg1)
{
info.ace_execCommand(cmd, arg1);
});
editor.replaceRange = pendingInit(function(start, end, text)
{
info.ace_replaceRange(start, end, text);
});
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the
// source here.
var $$INCLUDE_CSS = function(fileName)
{
return '<link rel="stylesheet" type="text/css" href="' + fileName + '"/>';
};
var $$INCLUDE_JS = function(fileName)
{
return '\x3cscript type="text/javascript" src="' + fileName + '">\x3c/script>';
};
var $$INCLUDE_JS_DEV = $$INCLUDE_JS;
var $$INCLUDE_CSS_DEV = $$INCLUDE_CSS;
function sortFilesByEmbeded(files) {
var embededFiles = [];
var remoteFiles = [];
var $$INCLUDE_CSS_Q = function(fileName)
{
return '\'<link rel="stylesheet" type="text/css" href="' + fileName + '"/>\'';
};
var $$INCLUDE_JS_Q = function(fileName)
{
return '\'\\x3cscript type="text/javascript" src="' + fileName + '">\\x3c/script>\'';
};
var $$INCLUDE_JS_Q_DEV = $$INCLUDE_JS_Q;
var $$INCLUDE_CSS_Q_DEV = $$INCLUDE_CSS_Q;
if (Ace2Editor.EMBEDED) {
for (var i = 0, ii = files.length; i < ii; i++) {
var file = files[i];
if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) {
embededFiles.push(file);
} else {
remoteFiles.push(file);
}
}
} else {
remoteFiles = files;
}
return {embeded: embededFiles, remote: remoteFiles};
}
function pushRequireScriptTo(buffer) {
var KERNEL_SOURCE = '../static/js/require-kernel.js';
var KERNEL_BOOT = 'require.setRootURI("../minified/");\nrequire.setGlobalKeyPath("require");'
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[KERNEL_SOURCE]);
buffer.push(KERNEL_BOOT);
buffer.push('<\/script>');
}
}
function pushScriptsTo(buffer) {
/* Folling is for packaging regular expression. */
/* $$INCLUDE_JS("../minified/ace2_inner.js?callback=require.define"); */
var ACE_SOURCE = '../minified/ace2_inner.js?callback=require.define';
if (Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[ACE_SOURCE]) {
buffer.push('<script type="text/javascript">');
buffer.push(Ace2Editor.EMBEDED[ACE_SOURCE]);
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
} else {
buffer.push('<script type="application/javascript" src="' + ACE_SOURCE + '"><\/script>');
buffer.push('<script type="text/javascript">');
buffer.push('require("/ace2_inner");');
buffer.push('<\/script>');
}
}
function pushStyleTagsFor(buffer, files) {
var sorted = sortFilesByEmbeded(files);
var embededFiles = sorted.embeded;
var remoteFiles = sorted.remote;
if (embededFiles.length > 0) {
buffer.push('<style type="text/css">');
for (var i = 0, ii = embededFiles.length; i < ii; i++) {
var file = embededFiles[i];
buffer.push(Ace2Editor.EMBEDED[file].replace(/<\//g, '<\\/'));
}
buffer.push('<\/style>');
}
for (var i = 0, ii = remoteFiles.length; i < ii; i++) {
var file = remoteFiles[i];
buffer.push('<link rel="stylesheet" type="text/css" href="' + file + '"\/>');
}
}
editor.destroy = pendingInit(function()
{
@ -246,52 +223,69 @@ function Ace2Editor()
{
var doctype = "<!doctype html>";
var iframeHTML = ["'" + doctype + "<html><head>'"];
var iframeHTML = [];
iframeHTML.push(doctype);
iframeHTML.push("<html><head>");
// For compatability's sake transform in and out.
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.stringify(iframeHTML[i]);
}
plugins.callHook("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
});
for (var i = 0, ii = iframeHTML.length; i < ii; i++) {
iframeHTML[i] = JSON.parse(iframeHTML[i]);
}
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the
// source here.
// these lines must conform to a specific format because they are passed by the build script:
iframeHTML.push($$INCLUDE_CSS_Q("../static/css/iframe_editor.css"));
iframeHTML.push($$INCLUDE_CSS_Q("../static/css/pad.css"));
iframeHTML.push($$INCLUDE_CSS_Q("../static/custom/pad.css"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/ace2_common.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/skiplist.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/virtual_lines.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/easysync2.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/cssmanager.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/colorutils.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/undomodule.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/contentcollector.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/changesettracker.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/linestylefilter.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/domline.js"));
iframeHTML.push($$INCLUDE_JS_Q("../static/js/ace2_inner.js"));
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css");
pushStyleTagsFor(iframeHTML, includedCSS);
iframeHTML.push('\'\\n<style type="text/css" title="dynamicsyntax"></style>\\n\'');
iframeHTML.push('\'</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>\'');
var includedJS = [];
var $$INCLUDE_JS = function(filename) {includedJS.push(filename)};
pushRequireScriptTo(iframeHTML);
// Inject my plugins into my child.
iframeHTML.push('\
<script type="text/javascript">\
require.define("/plugins", null);\n\
require.define("/plugins.js", function (require, exports, module) {\
module.exports = parent.parent.require("/plugins");\
});\
</script>\
');
pushScriptsTo(iframeHTML);
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false">&nbsp;</body></html>');
// Expose myself to global for my child frame.
var thisFunctionsName = "ChildAccessibleAce2Editor";
(function () {return this}())[thisFunctionsName] = Ace2Editor;
var outerScript = 'editorId = "' + info.id + '"; editorInfo = parent.' + thisFunctionsName + '.registry[editorId]; ' + 'window.onload = function() ' + '{ window.onload = null; setTimeout' + '(function() ' + '{ var iframe = document.createElement("IFRAME"); ' + 'iframe.scrolling = "no"; var outerdocbody = document.getElementById("outerdocbody"); ' + 'iframe.frameBorder = 0; iframe.allowTransparency = true; ' + // for IE
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + iframeHTML.join('+') + ').replace(/\\\\x3c/g, \'<\');doc.write(text); doc.close(); ' + '}, 0); }';
'outerdocbody.insertBefore(iframe, outerdocbody.firstChild); ' + 'iframe.ace_outerWin = window; ' + 'readyFunc = function() { editorInfo.onEditorReady(); readyFunc = null; editorInfo = null; }; ' + 'var doc = iframe.contentWindow.document; doc.open(); var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');doc.write(text); doc.close(); ' + '}, 0); }';
var outerHTML = [doctype, '<html><head>']
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css");
$$INCLUDE_CSS("../static/custom/pad.css");
pushStyleTagsFor(outerHTML, includedCSS);
var outerHTML = [doctype, '<html><head>', $$INCLUDE_CSS("../static/css/iframe_editor.css"), $$INCLUDE_CSS("../static/css/pad.css"), $$INCLUDE_CSS("../static/custom/pad.css"),
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing)
'<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript, '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>'];
if (!Array.prototype.map) Array.prototype.map = function(fun)
{ //needed for IE
if (typeof fun != "function") throw new TypeError();
var len = this.length;
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;
};
outerHTML.push('<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '\x3cscript>\n', outerScript.replace(/<\//g, '<\\/'), '\n\x3c/script>', '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div><div id="overlaysdiv"><!-- --></div></body></html>');
var outerFrame = document.createElement("IFRAME");
outerFrame.frameBorder = 0; // for IE
@ -308,3 +302,5 @@ function Ace2Editor()
return editor;
}
exports.Ace2Editor = Ace2Editor;

View file

@ -20,6 +20,7 @@
* limitations under the License.
*/
var Security = require('/security');
function isNodeText(node)
{
@ -80,14 +81,8 @@ function isArray(testObject)
return testObject && typeof testObject === 'object' && !(testObject.propertyIsEnumerable('length')) && typeof testObject.length === 'number';
}
if (typeof exports !== "undefined")
{
userAgent = "node-js";
}
else
{
userAgent = navigator.userAgent.toLowerCase();
}
var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase();
// Figure out what browser is being used (stolen from jquery 1.2.1)
var browser = {
version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
@ -95,7 +90,8 @@ var browser = {
opera: /opera/.test(userAgent),
msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent),
windows: /windows/.test(userAgent) // dgreensp
windows: /windows/.test(userAgent),
mobile: /mobile/.test(userAgent) || /android/.test(userAgent)
};
@ -142,10 +138,25 @@ function binarySearchInfinite(expectedLength, func)
function htmlPrettyEscape(str)
{
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\r?\n/g, '\\n');
return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
}
if (typeof exports !== "undefined")
{
exports.map = map;
}
var noop = function(){};
var identity = function(x){return x};
exports.isNodeText = isNodeText;
exports.object = object;
exports.extend = extend;
exports.forEach = forEach;
exports.map = map;
exports.filter = filter;
exports.isArray = isArray;
exports.browser = browser;
exports.getAssoc = getAssoc;
exports.setAssoc = setAssoc;
exports.binarySearch = binarySearch;
exports.binarySearchInfinite = binarySearchInfinite;
exports.htmlPrettyEscape = htmlPrettyEscape;
exports.map = map;
exports.noop = noop;
exports.identity = identity;

File diff suppressed because it is too large Load diff

View file

@ -20,45 +20,22 @@
* limitations under the License.
*/
var global = this;
var makeCSSManager = require('/cssmanager').makeCSSManager;
var domline = require('/domline').domline;
var AttribPool = require('/AttributePoolFactory').createAttributePool;
var Changeset = require('/Changeset');
var linestylefilter = require('/linestylefilter').linestylefilter;
var colorutils = require('/colorutils').colorutils;
var Ace2Common = require('./ace2_common');
function loadBroadcastJS()
var map = Ace2Common.map;
var forEach = Ace2Common.forEach;
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider)
{
// 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);
}
};
}
var changesetLoader = undefined;
// 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)
@ -91,11 +68,6 @@ function loadBroadcastJS()
}
}
function randomString()
{
return "_" + Math.floor(Math.random() * 1000000);
}
// for IE
if ($.browser.msie)
{
@ -107,7 +79,7 @@ function loadBroadcastJS()
{}
}
var userId = "hiddenUser" + randomString();
var socketId;
//var socket;
var channelState = "DISCONNECTED";
@ -183,10 +155,7 @@ function loadBroadcastJS()
// splice the lines
splice: function(start, numRemoved, newLinesVA)
{
var newLines = Array.prototype.slice.call(arguments, 2).map(
function(s)
{
var newLines = map(Array.prototype.slice.call(arguments, 2), function(s) {
return s;
});
@ -308,10 +277,13 @@ function loadBroadcastJS()
padContents.currentTime += timeDelta * 1000;
debugLog('Time Delta: ', timeDelta)
updateTimer();
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
var authors = map(padContents.getActiveAuthors(), function(name)
{
return authorData[name];
}));
});
BroadcastSlider.setAuthors(authors);
}
function updateTimer()
@ -411,13 +383,14 @@ function loadBroadcastJS()
changesetLoader.queueUp(start, 1, update);
}
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{
var authors = map(padContents.getActiveAuthors(), function(name){
return authorData[name];
}));
});
BroadcastSlider.setAuthors(authors);
}
global.changesetLoader = {
changesetLoader = {
running: false,
resolved: [],
requestQueue1: [],
@ -553,10 +526,12 @@ function loadBroadcastJS()
var authorMap = {};
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
BroadcastSlider.setAuthors(padContents.getActiveAuthors().map(function(name)
{
var authors = map(padContents.getActiveAuthors(),function(name) {
return authorData[name];
}));
});
BroadcastSlider.setAuthors(authors);
}
else if (obj['type'] == "NEW_SAVEDREV")
{
@ -608,53 +583,6 @@ function loadBroadcastJS()
}));
}
/*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)
{
@ -683,7 +611,7 @@ function loadBroadcastJS()
window.onload = function ()
{
window['isloaded'] = true;
window['onloadFuncts'].forEach(function (funct)
forEach(window['onloadFuncts'],function (funct)
{
funct();
});
@ -750,11 +678,17 @@ function loadBroadcastJS()
var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS)
{
dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author)).backgroundColor = bgcolor;
var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author));
selector.backgroundColor = bgcolor
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part
}
authorData[author] = data;
}
}
receiveAuthorData(clientVars.historicalAuthorData);
return changesetLoader;
}
exports.loadBroadcastJS = loadBroadcastJS;

View file

@ -22,7 +22,6 @@
// 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.
var global = this;
function loadBroadcastRevisionsJS()
{
@ -125,3 +124,5 @@ function loadBroadcastRevisionsJS()
};
}
}
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;

View file

@ -19,10 +19,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var global = this;
function loadBroadcastSliderJS()
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{
var BroadcastSlider;
(function()
{ // wrap this code in its own namespace
@ -203,7 +205,7 @@ function loadBroadcastSliderJS()
}
}
global.BroadcastSlider = {
BroadcastSlider = {
onSlider: onSlider,
getSliderPosition: getSliderPosition,
setSliderPosition: setSliderPosition,
@ -495,4 +497,8 @@ function loadBroadcastSliderJS()
{
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
})
return BroadcastSlider;
}
exports.loadBroadcastSliderJS = loadBroadcastSliderJS;

View file

@ -20,6 +20,8 @@
* limitations under the License.
*/
var AttribPool = require('/AttributePoolFactory').createAttributePool;
var Changeset = require('/Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
{
@ -207,3 +209,5 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider)
};
}
exports.makeChangesetTracker = makeChangesetTracker;

View file

@ -20,62 +20,45 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var padcookie = require('/pad_cookie').padcookie;
var chat = (function()
{
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1;
var isMobileSafari = ua.indexOf("mobile") > -1;
var bottomMargin = "0px";
var sDuration = 500;
var hDuration = 750;
var isStuck = false;
var chatMentions = 0;
var title = document.title;
if (isAndroid || isMobileSafari){
sDuration = 0;
hDuration = 0;
}
var self = {
show: function ()
{
$("#chaticon").hide("slide", {
direction: "down"
}, hDuration, function ()
{
$("#chatbox").show("slide", {
direction: "down"
}, sDuration, self.scrollDown);
$("#chatbox").resizable(
{
handles: 'nw',
minHeight: 40,
minWidth: 80,
start: function (event, ui)
{
$("#focusprotector").show();
},
stop: function (event, ui)
{
$("#focusprotector").hide();
if(isAndroid || isMobileSafari)
bottommargin = "32px";
$("#chatbox").css({right: "20px", bottom: bottomMargin, left: "", top: ""});
self.scrollDown();
}
});
});
$("#chaticon").hide();
$("#chatbox").show();
self.scrollDown();
chatMentions = 0;
document.title = title;
},
stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen
{
chat.show();
if(!isStuck || fromInitialCall) { // Stick it to
padcookie.setPref("chatAlwaysVisible", true);
$('#chatbox').addClass("stickyChat");
$('#chattext').css({"top":"0px"});
$('#editorcontainer').css({"right":"192px", "width":"auto"});
isStuck = true;
} else { // Unstick it
padcookie.setPref("chatAlwaysVisible", false);
$('#chatbox').removeClass("stickyChat");
$('#chattext').css({"top":"25px"});
$('#editorcontainer').css({"right":"0px", "width":"100%"});
isStuck = false;
}
},
hide: function ()
{
$("#chatcounter").text("0");
$("#chatbox").hide("slide", { direction: "down" }, sDuration, function()
{
$("#chaticon").show("slide", { direction: "down" }, hDuration);
});
$("#chaticon").show();
$("#chatbox").hide();
},
scrollDown: function()
{
@ -85,13 +68,13 @@ var chat = (function()
send: function()
{
var text = $("#chatinput").val();
pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text});
this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text});
$("#chatinput").val("");
},
addMessage: function(msg, increment)
{
//correct the time
msg.time += pad.clientTimeOffset;
msg.time += this._pad.clientTimeOffset;
//create the time string
var minutes = "" + new Date(msg.time).getMinutes();
@ -109,7 +92,7 @@ var chat = (function()
return 'z' + c.charCodeAt(0) + 'z';
});
var text = padutils.escapeHtmlWithClickableLinks(padutils.escapeHtml(msg.text), "_blank");
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank");
/* Performs an action if your name is mentioned */
var myName = $('#myusernameedit').val();
@ -123,7 +106,7 @@ var chat = (function()
var authorName = msg.userName == null ? "unnamed" : padutils.escapeHtml(msg.userName);
var html = "<p class='" + authorClass + "'><b>" + authorName + ":</b><span class='time'>" + timeStr + "</span> " + text + "</p>";
var html = "<p class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + timeStr + "</span> " + text + "</p>";
$("#chattext").append(html);
//should we increment the counter??
@ -138,23 +121,22 @@ var chat = (function()
if (chatMentions == 0){
title = document.title;
}
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text);
$('#chatthrob').effect("pulsate", {times:1,mode:"hide"},4000);
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(4000).hide(400);
chatMentions++;
document.title = "("+chatMentions+") " + title;
}
else
{
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text);
$('#chatthrob').effect("pulsate", {times:1,mode:"hide"},2000);
$('#chatthrob').html("<b>"+authorName+"</b>" + ": " + text).show().delay(2000).hide(400);
}
}
self.scrollDown();
},
init: function()
init: function(pad)
{
this._pad = pad;
$("#chatinput").keypress(function(evt)
{
//if the user typed enter, fire the send
@ -175,3 +157,6 @@ var chat = (function()
return self;
}());
exports.chat = chat;

View file

@ -20,17 +20,22 @@
* limitations under the License.
*/
$(window).bind("load", function()
{
getCollabClient.windowLoaded = true;
});
var chat = require('/chat').chat;
// Dependency fill on init. This exists for `pad.socket` only.
// TODO: bind directly to the socket.
var pad = undefined;
function getSocket() {
return pad && pad.socket;
}
/** 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)
function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{
var editor = ace2editor;
pad = _pad; // Inject pad to avoid a circular dependency.
var rev = serverVars.rev;
var padId = serverVars.padId;
@ -79,13 +84,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
{}
};
$(window).bind("unload", function()
{
if (socket)
{
setChannelState("DISCONNECTED", "unload");
}
});
if ($.browser.mozilla)
{
// Prevent "escape" from taking effect and canceling a comet connection;
@ -111,7 +109,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
function handleUserChanges()
{
if ((!socket) || channelState == "CONNECTING")
if ((!getSocket()) || channelState == "CONNECTING")
{
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
{
@ -258,19 +256,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
}*/
}
function setUpSocketWhenWindowLoaded()
{
if (getCollabClient.windowLoaded)
{
setUpSocket();
}
else
{
setTimeout(setUpSocketWhenWindowLoaded, 200);
}
}
setTimeout(setUpSocketWhenWindowLoaded, 0);
var hiccupCount = 0;
function handleCometHiccup(params)
@ -295,7 +280,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
function sendMessage(msg)
{
socket.json.send(
getSocket().json.send(
{
type: "COLLABROOM",
component: "pad",
@ -337,7 +322,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
{
if (window.console) console.log(evt);
if (!socket) return;
if (!getSocket()) return;
if (!evt.data) return;
var wrapper = evt;
if (wrapper.type != "COLLABROOM") return;
@ -442,7 +427,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
userInfo.userId = userId;
userSet[userId] = userInfo;
tellAceActiveAuthorInfo(userInfo);
if (!socket) return;
if (!getSocket()) return;
sendMessage(
{
type: "USERINFO_UPDATE",
@ -644,8 +629,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
}, 0);
}
var self;
return (self = {
var self = {
setOnUserJoin: function(cb)
{
callbacks.onUserJoin = cb;
@ -688,7 +672,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options)
callWhenNotCommitting: callWhenNotCommitting,
addHistoricalAuthors: tellAceAboutHistoricalAuthors,
setChannelState: setChannelState
});
};
$(document).ready(setUpSocket);
return self;
}
function selectElementContents(elem)
@ -714,3 +701,6 @@ function selectElementContents(elem)
}
}
}
exports.getCollabClient = getCollabClient;
exports.selectElementContents = selectElementContents;

View file

@ -119,3 +119,20 @@ colorutils.blend = function(c1, c2, t)
{
return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
}
colorutils.invert = function(c)
{
return [1 - c[0], 1 - c[1], 1- c[2]];
}
colorutils.complementary = function(c)
{
var inv = colorutils.invert(c);
return [
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11)
];
}
exports.colorutils = colorutils;

View file

@ -25,6 +25,9 @@
var _MAX_LIST_LEVEL = 8;
var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
function sanitizeUnicode(s)
{
return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?');
@ -34,15 +37,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{
browser = browser || {};
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
var plugins_ = plugins;
var dom = domInterface || {
isNodeText: function(n)
@ -476,7 +471,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
{
cc.doAttrib(state, "strikethrough");
}
if (tname == "ul")
if (tname == "ul" || tname == "ol")
{
var type;
var rr = cls && /(?:^| )list-([a-z]+[12345678])\b/.exec(cls);
@ -692,3 +687,6 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class
return cc;
}
exports.sanitizeUnicode = sanitizeUnicode;
exports.makeContentCollector = makeContentCollector;

View file

@ -118,3 +118,5 @@ function makeCSSManager(emptyStylesheetTitle, top)
}
};
}
exports.makeCSSManager = makeCSSManager;

View file

@ -1,116 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/cssmanager.js
/**
* 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 makeCSSManager(emptyStylesheetTitle)
{
function getSheetByTitle(title)
{
var allSheets = document.styleSheets;
for (var i = 0; i < allSheets.length; i++)
{
var s = allSheets[i];
if (s.title == title)
{
return s;
}
}
return null;
}
/*function getSheetTagByTitle(title) {
var allStyleTags = document.getElementsByTagName("style");
for(var i=0;i<allStyleTags.length;i++) {
var t = allStyleTags[i];
if (t.title == title) {
return t;
}
}
return null;
}*/
var browserSheet = getSheetByTitle(emptyStylesheetTitle);
//var browserTag = getSheetTagByTitle(emptyStylesheetTitle);
function browserRules()
{
return (browserSheet.cssRules || browserSheet.rules);
}
function browserDeleteRule(i)
{
if (browserSheet.deleteRule) browserSheet.deleteRule(i);
else browserSheet.removeRule(i);
}
function browserInsertRule(i, selector)
{
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
else browserSheet.addRule(selector, null, i);
}
var selectorList = [];
function indexOfSelector(selector)
{
for (var i = 0; i < selectorList.length; i++)
{
if (selectorList[i] == selector)
{
return i;
}
}
return -1;
}
function selectorStyle(selector)
{
var i = indexOfSelector(selector);
if (i < 0)
{
// add selector
browserInsertRule(0, selector);
selectorList.splice(0, 0, selector);
i = 0;
}
return browserRules().item(i).style;
}
function removeSelectorStyle(selector)
{
var i = indexOfSelector(selector);
if (i >= 0)
{
browserDeleteRule(i);
selectorList.splice(i, 1);
}
}
return {
selectorStyle: selectorStyle,
removeSelectorStyle: removeSelectorStyle,
info: function()
{
return selectorList.length + ":" + browserRules().length;
}
};
}

View file

@ -25,13 +25,15 @@
// requires: top
// requires: plugins
// requires: undefined
var Security = require('/security');
var Ace2Common = require('/ace2_common');
var plugins = require('/plugins').plugins;
var map = Ace2Common.map;
var noop = Ace2Common.noop;
var identity = Ace2Common.identity;
var domline = {};
domline.noop = function()
{};
domline.identity = function(x)
{
return x;
};
domline.addToLineClass = function(lineClass, cls)
{
@ -55,11 +57,11 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
var result = {
node: null,
appendSpan: domline.noop,
prepareForAdd: domline.noop,
notifyAdded: domline.noop,
clearSpans: domline.noop,
finishUpdate: domline.noop,
appendSpan: noop,
prepareForAdd: noop,
notifyAdded: noop,
clearSpans: noop,
finishUpdate: noop,
lineMarker: 0
};
@ -86,7 +88,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
return domline.processSpaces(s, doesWrap);
}
var identity = domline.identity;
var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line';
@ -95,13 +97,23 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
if (cls.indexOf('list') >= 0)
{
var listType = /(?:^| )list:(\S+)/.exec(cls);
var start = /(?:^| )start:(\S+)/.exec(cls);
if (listType)
{
listType = listType[1];
start = start?'start="'+Security.escapeHTMLAttribute(start[1])+'"':'';
if (listType)
{
preHtml = '<ul class="list-' + listType + '"><li>';
postHtml = '</li></ul>';
if(listType.indexOf("number") < 0)
{
preHtml = '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
postHtml = '</li></ul>';
}
else
{
preHtml = '<ol '+start+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
postHtml = '</li></ol>';
}
}
result.lineMarker += txt.length;
return; // don't append any text
@ -130,20 +142,12 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
var extraOpenTags = "";
var extraCloseTags = "";
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
var plugins_ = plugins;
plugins_.callHook("aceCreateDomLine", {
map(plugins_.callHook("aceCreateDomLine", {
domline: domline,
cls: cls
}).map(function(modifier)
}), function(modifier)
{
cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
@ -162,7 +166,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
href = "http://"+href;
}
extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '&quot;') + '">';
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '">';
extraCloseTags = '</a>' + extraCloseTags;
}
if (simpleTags)
@ -172,7 +176,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
}
html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
};
result.clearSpans = function()
@ -218,27 +222,6 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
return result;
};
domline.escapeHTML = function(s)
{
var re = /[&<>'"]/g;
/']/; // stupid indentation thing
if (!re.MAP)
{
// persisted across function calls!
re.MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
}
return s.replace(re, function(c)
{
return re.MAP[c];
});
};
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
@ -300,3 +283,5 @@ domline.processSpaces = function(s, doesWrap)
}
return parts.join('');
};
exports.domline = domline;

View file

@ -1,298 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/domline.js
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.domline
/**
* 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.
*/
// requires: top
// requires: plugins
// requires: undefined
var domline = {};
domline.noop = function()
{};
domline.identity = function(x)
{
return x;
};
domline.addToLineClass = function(lineClass, cls)
{
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
cls.replace(/\S+/g, function(c)
{
if (c.indexOf("line:") == 0)
{
// add class to line
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
}
});
return lineClass;
}
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
{
var result = {
node: null,
appendSpan: domline.noop,
prepareForAdd: domline.noop,
notifyAdded: domline.noop,
clearSpans: domline.noop,
finishUpdate: domline.noop,
lineMarker: 0
};
var browser = (optBrowser || {});
var document = optDocument;
if (document)
{
result.node = document.createElement("div");
}
else
{
result.node = {
innerHTML: '',
className: ''
};
}
var html = [];
var preHtml, postHtml;
var curHTML = null;
function processSpaces(s)
{
return domline.processSpaces(s, doesWrap);
}
var identity = domline.identity;
var perTextNodeProcess = (doesWrap ? identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : identity);
var lineClass = 'ace-line';
result.appendSpan = function(txt, cls)
{
if (cls.indexOf('list') >= 0)
{
var listType = /(?:^| )list:(\S+)/.exec(cls);
if (listType)
{
listType = listType[1];
if (listType)
{
preHtml = '<ul class="list-' + listType + '"><li>';
postHtml = '</li></ul>';
}
result.lineMarker += txt.length;
return; // don't append any text
}
}
var href = null;
var simpleTags = null;
if (cls.indexOf('url') >= 0)
{
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url)
{
href = url;
return space + "url";
});
}
if (cls.indexOf('tag') >= 0)
{
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag)
{
if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
var extraOpenTags = "";
var extraCloseTags = "";
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
plugins_.callHook("aceCreateDomLine", {
domline: domline,
cls: cls,
document: document
}).map(function(modifier)
{
cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls)
{
lineClass = domline.addToLineClass(lineClass, cls);
}
else if (txt)
{
if (href)
{
extraOpenTags = extraOpenTags + '<a href="' + href.replace(/\"/g, '&quot;') + '">';
extraCloseTags = '</a>' + extraCloseTags;
}
if (simpleTags)
{
simpleTags.sort();
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
}
html.push('<span class="', cls || '', '">', extraOpenTags, perTextNodeProcess(domline.escapeHTML(txt)), extraCloseTags, '</span>');
}
};
result.clearSpans = function()
{
html = [];
lineClass = ''; // non-null to cause update
result.lineMarker = 0;
};
function writeHTML()
{
var newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML)
{
if ((!document) || (!optBrowser))
{
newHTML += '&nbsp;';
}
else if (!browser.msie)
{
newHTML += '<br/>';
}
}
if (nonEmpty)
{
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = null; // free memory
if (newHTML !== curHTML)
{
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass !== null) result.node.className = lineClass;
}
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
result.getInnerHTML = function()
{
return curHTML || '';
};
return result;
};
domline.escapeHTML = function(s)
{
var re = /[&<>'"]/g;
/']/; // stupid indentation thing
if (!re.MAP)
{
// persisted across function calls!
re.MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
}
return s.replace(re, function(c)
{
return re.MAP[c];
});
};
domline.processSpaces = function(s, doesWrap)
{
if (s.indexOf("<") < 0 && !doesWrap)
{
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m)
{
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true;
var beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--)
{
var p = parts[i];
if (p == " ")
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<")
{
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<")
{
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
};

View file

@ -193,3 +193,5 @@ function makeResizableHPane(left, sep, right, minLeft, minRight, sepWidth, sepOf
}
});
}
exports.makeDraggable = makeDraggable;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

157
static/js/jquery-ui.js vendored
View file

@ -1,157 +0,0 @@
/*!
* jQuery UI 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI
*/
(function(c,j){function k(a,b){var d=a.nodeName.toLowerCase();if("area"===d){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&l(a)}return(/input|select|textarea|button|object/.test(d)?!a.disabled:"a"==d?a.href||b:b)&&l(a)}function l(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.14",
keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();
b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,
"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",
function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,m,n){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(m)g-=parseFloat(c.curCSS(f,"border"+this+"Width",true))||0;if(n)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,
outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h,d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){return k(a,!isNaN(c.attr(a,"tabindex")))},tabbable:function(a){var b=c.attr(a,"tabindex"),d=isNaN(b);
return(d||b>=0)&&k(a,!d)}});c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=
0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery);
;/*!
* jQuery UI Widget 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Widget
*/
(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,
a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h;
e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options,
this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},
widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},
enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);
;/*!
* jQuery UI Mouse 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Mouse
*
* Depends:
* jquery.ui.widget.js
*/
(function(b){var d=false;b(document).mousedown(function(){d=false});b.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(c){return a._mouseDown(c)}).bind("click."+this.widgetName,function(c){if(true===b.data(c.target,a.widgetName+".preventClickEvent")){b.removeData(c.target,a.widgetName+".preventClickEvent");c.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+
this.widgetName)},_mouseDown:function(a){if(!d){this._mouseStarted&&this._mouseUp(a);this._mouseDownEvent=a;var c=this,f=a.which==1,g=typeof this.options.cancel=="string"?b(a.target).closest(this.options.cancel).length:false;if(!f||g||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){c.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==
false;if(!this._mouseStarted){a.preventDefault();return true}}true===b.data(a.target,this.widgetName+".preventClickEvent")&&b.removeData(a.target,this.widgetName+".preventClickEvent");this._mouseMoveDelegate=function(e){return c._mouseMove(e)};this._mouseUpDelegate=function(e){return c._mouseUp(e)};b(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);a.preventDefault();return d=true}},_mouseMove:function(a){if(b.browser.msie&&
!(document.documentMode>=9)&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){b(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=
false;a.target==this._mouseDownEvent.target&&b.data(a.target,this.widgetName+".preventClickEvent",true);this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);
;/*
* jQuery UI Resizable 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Resizables
*
* Depends:
* jquery.ui.core.js
* jquery.ui.mouse.js
* jquery.ui.widget.js
*/
(function(e){e.widget("ui.resizable",e.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:false,animate:false,animateDuration:"slow",animateEasing:"swing",aspectRatio:false,autoHide:false,containment:false,ghost:false,grid:false,handles:"e,s,se",helper:false,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1E3},_create:function(){var b=this,a=this.options;this.element.addClass("ui-resizable");e.extend(this,{_aspectRatio:!!a.aspectRatio,aspectRatio:a.aspectRatio,originalElement:this.element,
_proportionallyResizeElements:[],_helper:a.helper||a.ghost||a.animate?a.helper||"ui-resizable-helper":null});if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)){/relative/.test(this.element.css("position"))&&e.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"});this.element.wrap(e('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),
top:this.element.css("top"),left:this.element.css("left")}));this.element=this.element.parent().data("resizable",this.element.data("resizable"));this.elementIsWrapper=true;this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")});this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0});this.originalResizeStyle=
this.originalElement.css("resize");this.originalElement.css("resize","none");this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"}));this.originalElement.css({margin:this.originalElement.css("margin")});this._proportionallyResize()}this.handles=a.handles||(!e(".ui-resizable-handle",this.element).length?"e,s,se":{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",
nw:".ui-resizable-nw"});if(this.handles.constructor==String){if(this.handles=="all")this.handles="n,e,s,w,se,sw,ne,nw";var c=this.handles.split(",");this.handles={};for(var d=0;d<c.length;d++){var f=e.trim(c[d]),g=e('<div class="ui-resizable-handle '+("ui-resizable-"+f)+'"></div>');/sw|se|ne|nw/.test(f)&&g.css({zIndex:++a.zIndex});"se"==f&&g.addClass("ui-icon ui-icon-gripsmall-diagonal-se");this.handles[f]=".ui-resizable-"+f;this.element.append(g)}}this._renderAxis=function(h){h=h||this.element;for(var i in this.handles){if(this.handles[i].constructor==
String)this.handles[i]=e(this.handles[i],this.element).show();if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var j=e(this.handles[i],this.element),l=0;l=/sw|ne|nw|se|n|s/.test(i)?j.outerHeight():j.outerWidth();j=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join("");h.css(j,l);this._proportionallyResize()}e(this.handles[i])}};this._renderAxis(this.element);this._handles=e(".ui-resizable-handle",this.element).disableSelection();
this._handles.mouseover(function(){if(!b.resizing){if(this.className)var h=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=h&&h[1]?h[1]:"se"}});if(a.autoHide){this._handles.hide();e(this.element).addClass("ui-resizable-autohide").hover(function(){if(!a.disabled){e(this).removeClass("ui-resizable-autohide");b._handles.show()}},function(){if(!a.disabled)if(!b.resizing){e(this).addClass("ui-resizable-autohide");b._handles.hide()}})}this._mouseInit()},destroy:function(){this._mouseDestroy();
var b=function(c){e(c).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var a=this.element;a.after(this.originalElement.css({position:a.css("position"),width:a.outerWidth(),height:a.outerHeight(),top:a.css("top"),left:a.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle);b(this.originalElement);return this},_mouseCapture:function(b){var a=
false;for(var c in this.handles)if(e(this.handles[c])[0]==b.target)a=true;return!this.options.disabled&&a},_mouseStart:function(b){var a=this.options,c=this.element.position(),d=this.element;this.resizing=true;this.documentScroll={top:e(document).scrollTop(),left:e(document).scrollLeft()};if(d.is(".ui-draggable")||/absolute/.test(d.css("position")))d.css({position:"absolute",top:c.top,left:c.left});e.browser.opera&&/relative/.test(d.css("position"))&&d.css({position:"relative",top:"auto",left:"auto"});
this._renderProxy();c=m(this.helper.css("left"));var f=m(this.helper.css("top"));if(a.containment){c+=e(a.containment).scrollLeft()||0;f+=e(a.containment).scrollTop()||0}this.offset=this.helper.offset();this.position={left:c,top:f};this.size=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalSize=this._helper?{width:d.outerWidth(),height:d.outerHeight()}:{width:d.width(),height:d.height()};this.originalPosition={left:c,top:f};this.sizeDiff=
{width:d.outerWidth()-d.width(),height:d.outerHeight()-d.height()};this.originalMousePosition={left:b.pageX,top:b.pageY};this.aspectRatio=typeof a.aspectRatio=="number"?a.aspectRatio:this.originalSize.width/this.originalSize.height||1;a=e(".ui-resizable-"+this.axis).css("cursor");e("body").css("cursor",a=="auto"?this.axis+"-resize":a);d.addClass("ui-resizable-resizing");this._propagate("start",b);return true},_mouseDrag:function(b){var a=this.helper,c=this.originalMousePosition,d=this._change[this.axis];
if(!d)return false;c=d.apply(this,[b,b.pageX-c.left||0,b.pageY-c.top||0]);this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)c=this._updateRatio(c,b);c=this._respectSize(c,b);this._propagate("resize",b);a.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"});!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize();this._updateCache(c);this._trigger("resize",b,this.ui());return false},
_mouseStop:function(b){this.resizing=false;var a=this.options,c=this;if(this._helper){var d=this._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName);d=f&&e.ui.hasScroll(d[0],"left")?0:c.sizeDiff.height;f=f?0:c.sizeDiff.width;f={width:c.helper.width()-f,height:c.helper.height()-d};d=parseInt(c.element.css("left"),10)+(c.position.left-c.originalPosition.left)||null;var g=parseInt(c.element.css("top"),10)+(c.position.top-c.originalPosition.top)||null;a.animate||this.element.css(e.extend(f,
{top:g,left:d}));c.helper.height(c.size.height);c.helper.width(c.size.width);this._helper&&!a.animate&&this._proportionallyResize()}e("body").css("cursor","auto");this.element.removeClass("ui-resizable-resizing");this._propagate("stop",b);this._helper&&this.helper.remove();return false},_updateVirtualBoundaries:function(b){var a=this.options,c,d,f;a={minWidth:k(a.minWidth)?a.minWidth:0,maxWidth:k(a.maxWidth)?a.maxWidth:Infinity,minHeight:k(a.minHeight)?a.minHeight:0,maxHeight:k(a.maxHeight)?a.maxHeight:
Infinity};if(this._aspectRatio||b){b=a.minHeight*this.aspectRatio;d=a.minWidth/this.aspectRatio;c=a.maxHeight*this.aspectRatio;f=a.maxWidth/this.aspectRatio;if(b>a.minWidth)a.minWidth=b;if(d>a.minHeight)a.minHeight=d;if(c<a.maxWidth)a.maxWidth=c;if(f<a.maxHeight)a.maxHeight=f}this._vBoundaries=a},_updateCache:function(b){this.offset=this.helper.offset();if(k(b.left))this.position.left=b.left;if(k(b.top))this.position.top=b.top;if(k(b.height))this.size.height=b.height;if(k(b.width))this.size.width=
b.width},_updateRatio:function(b){var a=this.position,c=this.size,d=this.axis;if(k(b.height))b.width=b.height*this.aspectRatio;else if(k(b.width))b.height=b.width/this.aspectRatio;if(d=="sw"){b.left=a.left+(c.width-b.width);b.top=null}if(d=="nw"){b.top=a.top+(c.height-b.height);b.left=a.left+(c.width-b.width)}return b},_respectSize:function(b){var a=this._vBoundaries,c=this.axis,d=k(b.width)&&a.maxWidth&&a.maxWidth<b.width,f=k(b.height)&&a.maxHeight&&a.maxHeight<b.height,g=k(b.width)&&a.minWidth&&
a.minWidth>b.width,h=k(b.height)&&a.minHeight&&a.minHeight>b.height;if(g)b.width=a.minWidth;if(h)b.height=a.minHeight;if(d)b.width=a.maxWidth;if(f)b.height=a.maxHeight;var i=this.originalPosition.left+this.originalSize.width,j=this.position.top+this.size.height,l=/sw|nw|w/.test(c);c=/nw|ne|n/.test(c);if(g&&l)b.left=i-a.minWidth;if(d&&l)b.left=i-a.maxWidth;if(h&&c)b.top=j-a.minHeight;if(f&&c)b.top=j-a.maxHeight;if((a=!b.width&&!b.height)&&!b.left&&b.top)b.top=null;else if(a&&!b.top&&b.left)b.left=
null;return b},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var b=this.helper||this.element,a=0;a<this._proportionallyResizeElements.length;a++){var c=this._proportionallyResizeElements[a];if(!this.borderDif){var d=[c.css("borderTopWidth"),c.css("borderRightWidth"),c.css("borderBottomWidth"),c.css("borderLeftWidth")],f=[c.css("paddingTop"),c.css("paddingRight"),c.css("paddingBottom"),c.css("paddingLeft")];this.borderDif=e.map(d,function(g,h){g=parseInt(g,10)||
0;h=parseInt(f[h],10)||0;return g+h})}e.browser.msie&&(e(b).is(":hidden")||e(b).parents(":hidden").length)||c.css({height:b.height()-this.borderDif[0]-this.borderDif[2]||0,width:b.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var b=this.options;this.elementOffset=this.element.offset();if(this._helper){this.helper=this.helper||e('<div style="overflow:hidden;"></div>');var a=e.browser.msie&&e.browser.version<7,c=a?1:0;a=a?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+
a,height:this.element.outerHeight()+a,position:"absolute",left:this.elementOffset.left-c+"px",top:this.elementOffset.top-c+"px",zIndex:++b.zIndex});this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(b,a){return{width:this.originalSize.width+a}},w:function(b,a){return{left:this.originalPosition.left+a,width:this.originalSize.width-a}},n:function(b,a,c){return{top:this.originalPosition.top+c,height:this.originalSize.height-c}},s:function(b,a,c){return{height:this.originalSize.height+
c}},se:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},sw:function(b,a,c){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,a,c]))},ne:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,a,c]))},nw:function(b,a,c){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,a,c]))}},_propagate:function(b,a){e.ui.plugin.call(this,b,[a,this.ui()]);
b!="resize"&&this._trigger(b,a,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}});e.extend(e.ui.resizable,{version:"1.8.14"});e.ui.plugin.add("resizable","alsoResize",{start:function(){var b=e(this).data("resizable").options,a=function(c){e(c).each(function(){var d=e(this);d.data("resizable-alsoresize",{width:parseInt(d.width(),
10),height:parseInt(d.height(),10),left:parseInt(d.css("left"),10),top:parseInt(d.css("top"),10),position:d.css("position")})})};if(typeof b.alsoResize=="object"&&!b.alsoResize.parentNode)if(b.alsoResize.length){b.alsoResize=b.alsoResize[0];a(b.alsoResize)}else e.each(b.alsoResize,function(c){a(c)});else a(b.alsoResize)},resize:function(b,a){var c=e(this).data("resizable");b=c.options;var d=c.originalSize,f=c.originalPosition,g={height:c.size.height-d.height||0,width:c.size.width-d.width||0,top:c.position.top-
f.top||0,left:c.position.left-f.left||0},h=function(i,j){e(i).each(function(){var l=e(this),q=e(this).data("resizable-alsoresize"),p={},r=j&&j.length?j:l.parents(a.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(r,function(n,o){if((n=(q[o]||0)+(g[o]||0))&&n>=0)p[o]=n||null});if(e.browser.opera&&/relative/.test(l.css("position"))){c._revertToRelativePosition=true;l.css({position:"absolute",top:"auto",left:"auto"})}l.css(p)})};typeof b.alsoResize=="object"&&!b.alsoResize.nodeType?
e.each(b.alsoResize,function(i,j){h(i,j)}):h(b.alsoResize)},stop:function(){var b=e(this).data("resizable"),a=b.options,c=function(d){e(d).each(function(){var f=e(this);f.css({position:f.data("resizable-alsoresize").position})})};if(b._revertToRelativePosition){b._revertToRelativePosition=false;typeof a.alsoResize=="object"&&!a.alsoResize.nodeType?e.each(a.alsoResize,function(d){c(d)}):c(a.alsoResize)}e(this).removeData("resizable-alsoresize")}});e.ui.plugin.add("resizable","animate",{stop:function(b){var a=
e(this).data("resizable"),c=a.options,d=a._proportionallyResizeElements,f=d.length&&/textarea/i.test(d[0].nodeName),g=f&&e.ui.hasScroll(d[0],"left")?0:a.sizeDiff.height;f={width:a.size.width-(f?0:a.sizeDiff.width),height:a.size.height-g};g=parseInt(a.element.css("left"),10)+(a.position.left-a.originalPosition.left)||null;var h=parseInt(a.element.css("top"),10)+(a.position.top-a.originalPosition.top)||null;a.element.animate(e.extend(f,h&&g?{top:h,left:g}:{}),{duration:c.animateDuration,easing:c.animateEasing,
step:function(){var i={width:parseInt(a.element.css("width"),10),height:parseInt(a.element.css("height"),10),top:parseInt(a.element.css("top"),10),left:parseInt(a.element.css("left"),10)};d&&d.length&&e(d[0]).css({width:i.width,height:i.height});a._updateCache(i);a._propagate("resize",b)}})}});e.ui.plugin.add("resizable","containment",{start:function(){var b=e(this).data("resizable"),a=b.element,c=b.options.containment;if(a=c instanceof e?c.get(0):/parent/.test(c)?a.parent().get(0):c){b.containerElement=
e(a);if(/document/.test(c)||c==document){b.containerOffset={left:0,top:0};b.containerPosition={left:0,top:0};b.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}}else{var d=e(a),f=[];e(["Top","Right","Left","Bottom"]).each(function(i,j){f[i]=m(d.css("padding"+j))});b.containerOffset=d.offset();b.containerPosition=d.position();b.containerSize={height:d.innerHeight()-f[3],width:d.innerWidth()-f[1]};c=b.containerOffset;
var g=b.containerSize.height,h=b.containerSize.width;h=e.ui.hasScroll(a,"left")?a.scrollWidth:h;g=e.ui.hasScroll(a)?a.scrollHeight:g;b.parentData={element:a,left:c.left,top:c.top,width:h,height:g}}}},resize:function(b){var a=e(this).data("resizable"),c=a.options,d=a.containerOffset,f=a.position;b=a._aspectRatio||b.shiftKey;var g={top:0,left:0},h=a.containerElement;if(h[0]!=document&&/static/.test(h.css("position")))g=d;if(f.left<(a._helper?d.left:0)){a.size.width+=a._helper?a.position.left-d.left:
a.position.left-g.left;if(b)a.size.height=a.size.width/c.aspectRatio;a.position.left=c.helper?d.left:0}if(f.top<(a._helper?d.top:0)){a.size.height+=a._helper?a.position.top-d.top:a.position.top;if(b)a.size.width=a.size.height*c.aspectRatio;a.position.top=a._helper?d.top:0}a.offset.left=a.parentData.left+a.position.left;a.offset.top=a.parentData.top+a.position.top;c=Math.abs((a._helper?a.offset.left-g.left:a.offset.left-g.left)+a.sizeDiff.width);d=Math.abs((a._helper?a.offset.top-g.top:a.offset.top-
d.top)+a.sizeDiff.height);f=a.containerElement.get(0)==a.element.parent().get(0);g=/relative|absolute/.test(a.containerElement.css("position"));if(f&&g)c-=a.parentData.left;if(c+a.size.width>=a.parentData.width){a.size.width=a.parentData.width-c;if(b)a.size.height=a.size.width/a.aspectRatio}if(d+a.size.height>=a.parentData.height){a.size.height=a.parentData.height-d;if(b)a.size.width=a.size.height*a.aspectRatio}},stop:function(){var b=e(this).data("resizable"),a=b.options,c=b.containerOffset,d=b.containerPosition,
f=b.containerElement,g=e(b.helper),h=g.offset(),i=g.outerWidth()-b.sizeDiff.width;g=g.outerHeight()-b.sizeDiff.height;b._helper&&!a.animate&&/relative/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g});b._helper&&!a.animate&&/static/.test(f.css("position"))&&e(this).css({left:h.left-d.left-c.left,width:i,height:g})}});e.ui.plugin.add("resizable","ghost",{start:function(){var b=e(this).data("resizable"),a=b.options,c=b.size;b.ghost=b.originalElement.clone();b.ghost.css({opacity:0.25,
display:"block",position:"relative",height:c.height,width:c.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof a.ghost=="string"?a.ghost:"");b.ghost.appendTo(b.helper)},resize:function(){var b=e(this).data("resizable");b.ghost&&b.ghost.css({position:"relative",height:b.size.height,width:b.size.width})},stop:function(){var b=e(this).data("resizable");b.ghost&&b.helper&&b.helper.get(0).removeChild(b.ghost.get(0))}});e.ui.plugin.add("resizable","grid",{resize:function(){var b=
e(this).data("resizable"),a=b.options,c=b.size,d=b.originalSize,f=b.originalPosition,g=b.axis;a.grid=typeof a.grid=="number"?[a.grid,a.grid]:a.grid;var h=Math.round((c.width-d.width)/(a.grid[0]||1))*(a.grid[0]||1);a=Math.round((c.height-d.height)/(a.grid[1]||1))*(a.grid[1]||1);if(/^(se|s|e)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a}else if(/^(ne)$/.test(g)){b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}else{if(/^(sw)$/.test(g)){b.size.width=d.width+h;b.size.height=
d.height+a}else{b.size.width=d.width+h;b.size.height=d.height+a;b.position.top=f.top-a}b.position.left=f.left-h}}});var m=function(b){return parseInt(b,10)||0},k=function(b){return!isNaN(parseInt(b,10))}})(jQuery);
;/*
* jQuery UI Effects 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/
*/
jQuery.effects||function(f,j){function m(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1],
16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return n.transparent;return n[f.trim(c).toLowerCase()]}function s(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return m(b)}function o(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,
a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function p(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in t||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function u(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d=
a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:b in f.fx.speeds?f.fx.speeds[b]:f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}function l(c){if(!c||typeof c==="number"||f.fx.speeds[c])return true;if(typeof c==="string"&&!f.effects[c])return true;return false}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor",
"borderTopColor","borderColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=s(b.elem,a);b.end=m(b.end);b.colorInit=true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var n={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],transparent:[255,255,255]},q=["add","remove","toggle"],t={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b,
d){if(f.isFunction(b)){d=b;b=null}return this.queue(function(){var e=f(this),g=e.attr("style")||" ",h=p(o.call(this)),r,v=e.attr("class");f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});r=p(o.call(this));e.attr("class",v);e.animate(u(h,r),{queue:false,duration:a,easing:b,complete:function(){f.each(q,function(w,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments);f.dequeue(this)}})})};
f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,
[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.14",save:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.data("ec.storage."+a[b],c[0].style[a[b]])},restore:function(c,a){for(var b=0;b<a.length;b++)a[b]!==null&&c.css(a[b],c.data("ec.storage."+a[b]))},setMode:function(c,a){if(a=="toggle")a=c.is(":hidden")?"show":"hide";return a},getBaseline:function(c,a){var b;switch(c[0]){case "top":b=
0;break;case "middle":b=0.5;break;case "bottom":b=1;break;default:b=c[0]/a.height}switch(c[1]){case "left":c=0;break;case "center":c=0.5;break;case "right":c=1;break;default:c=c[1]/a.width}return{x:c,y:b}},createWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent();var a={width:c.outerWidth(true),height:c.outerHeight(true),"float":c.css("float")},b=f("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});
c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"});c.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,
a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments),b={options:a[1],duration:a[2],callback:a[3]};a=b.options.mode;var d=f.effects[c];if(f.fx.off||!d)return a?this[a](b.duration,b.callback):this.each(function(){b.callback&&b.callback.call(this)});return d.call(this,b)},_show:f.fn.show,show:function(c){if(l(c))return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);
a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(l(c))return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(l(c)||typeof c==="boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%",
"pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c,a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*
((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=
e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=
e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/
h);return-(h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g))+b},easeOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*a)*Math.sin((a*e-c)*2*Math.PI/g)+d+b},easeInOutElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e/2)==2)return b+d;g||(g=e*0.3*1.5);if(h<Math.abs(d)){h=d;c=g/4}else c=g/(2*Math.PI)*Math.asin(d/h);if(a<1)return-0.5*
h*Math.pow(2,10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)+b;return h*Math.pow(2,-10*(a-=1))*Math.sin((a*e-c)*2*Math.PI/g)*0.5+d+b},easeInBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*(a/=e)*a*((g+1)*a-g)+b},easeOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;return d*((a=a/e-1)*a*((g+1)*a+g)+1)+b},easeInOutBack:function(c,a,b,d,e,g){if(g==j)g=1.70158;if((a/=e/2)<1)return d/2*a*a*(((g*=1.525)+1)*a-g)+b;return d/2*((a-=2)*a*(((g*=1.525)+1)*a+g)+2)+b},easeInBounce:function(c,a,b,d,e){return d-f.easing.easeOutBounce(c,
e-a,0,d,e)+b},easeOutBounce:function(c,a,b,d,e){return(a/=e)<1/2.75?d*7.5625*a*a+b:a<2/2.75?d*(7.5625*(a-=1.5/2.75)*a+0.75)+b:a<2.5/2.75?d*(7.5625*(a-=2.25/2.75)*a+0.9375)+b:d*(7.5625*(a-=2.625/2.75)*a+0.984375)+b},easeInOutBounce:function(c,a,b,d,e){if(a<e/2)return f.easing.easeInBounce(c,a*2,0,d,e)*0.5+b;return f.easing.easeOutBounce(c,a*2-e,0,d,e)*0.5+d*0.5+b}})}(jQuery);
;/*
* jQuery UI Effects Pulsate 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Pulsate
*
* Depends:
* jquery.effects.core.js
*/
(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c<times;c++){b.animate({opacity:animateTo},duration,a.options.easing);animateTo=(animateTo+1)%2}b.animate({opacity:animateTo},duration,
a.options.easing,function(){animateTo==0&&b.hide();a.callback&&a.callback.apply(this,arguments)});b.queue("fx",function(){b.dequeue()}).dequeue()})}})(jQuery);
;/*
* jQuery UI Effects Slide 1.8.14
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Effects/Slide
*
* Depends:
* jquery.effects.core.js
*/
(function(c){c.effects.slide=function(d){return this.queue(function(){var a=c(this),h=["position","top","bottom","left","right"],f=c.effects.setMode(a,d.options.mode||"show"),b=d.options.direction||"left";c.effects.save(a,h);a.show();c.effects.createWrapper(a).css({overflow:"hidden"});var g=b=="up"||b=="down"?"top":"left";b=b=="up"||b=="left"?"pos":"neg";var e=d.options.distance||(g=="top"?a.outerHeight({margin:true}):a.outerWidth({margin:true}));if(f=="show")a.css(g,b=="pos"?isNaN(e)?"-"+e:-e:e);
var i={};i[g]=(f=="show"?b=="pos"?"+=":"-=":b=="pos"?"-=":"+=")+e;a.animate(i,{queue:false,duration:d.duration,easing:d.options.easing,complete:function(){f=="hide"&&a.hide();c.effects.restore(a,h);c.effects.removeWrapper(a);d.callback&&d.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery);
;

View file

@ -469,3 +469,5 @@ if (!JSON)
};
}
}());
module.exports = JSON;

View file

@ -27,6 +27,11 @@
// requires: top
// requires: plugins
// requires: undefined
var Changeset = require('/Changeset');
var plugins = require('/plugins').plugins;
var map = require('/ace2_common').map;
var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = {
@ -50,15 +55,7 @@ linestylefilter.getAuthorClassName = function(author)
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
{
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
var plugins_ = plugins;
if (lineLength == 0) return textAndClassFunc;
@ -90,6 +87,10 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
{
classes += ' list:' + value;
}
else if (key == 'start')
{
classes += ' start:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
@ -299,21 +300,13 @@ linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
var plugins_ = plugins;
var hookFilters = plugins_.callHook("aceGetFilterStack", {
linestylefilter: linestylefilter,
browser: browser
});
hookFilters.map(function(hookFilter)
map(hookFilters, function(hookFilter)
{
func = hookFilter(lineText, func);
});
@ -348,3 +341,5 @@ linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
};
exports.linestylefilter = linestylefilter;

View file

@ -1,348 +0,0 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
// DO NOT EDIT THIS FILE, edit infrastructure/ace/www/linestylefilter.js
// THIS FILE IS ALSO AN APPJET MODULE: etherpad.collab.ace.linestylefilter
/**
* 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.
*/
// requires: easysync2.Changeset
// requires: top
// requires: plugins
// requires: undefined
var linestylefilter = {};
linestylefilter.ATTRIB_CLASSES = {
'bold': 'tag:b',
'italic': 'tag:i',
'underline': 'tag:u',
'strikethrough': 'tag:s'
};
linestylefilter.getAuthorClassName = function(author)
{
return "author-" + author.replace(/[^a-y0-9]/g, function(c)
{
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
};
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool)
{
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
if (lineLength == 0) return textAndClassFunc;
var nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function()
{
var lineEnd = lineLength;
var curIndex = 0;
var extraClasses;
var leftInAuthor;
function attribsToClasses(attribs)
{
var classes = '';
Changeset.eachAttribNumber(attribs, function(n)
{
var key = apool.getAttribKey(n);
if (key)
{
var value = apool.getAttribValue(n);
if (value)
{
if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
}
else if (key == 'list')
{
classes += ' list:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
}
else
{
classes += plugins_.callHookStr("aceAttribsToClasses", {
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
}
}
}
});
return classes.substring(1);
}
var attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses;
function goNextOp()
{
nextOp = attributionIter.next();
nextOpClasses = (nextOp.opcode && attribsToClasses(nextOp.attribs));
}
goNextOp();
function nextClasses()
{
if (curIndex < lineEnd)
{
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses)
{
leftInAuthor += nextOp.chars;
goNextOp();
}
}
}
nextClasses();
return function(txt, cls)
{
while (txt.length > 0)
{
if (leftInAuthor <= 0)
{
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor)
{
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0)
{
nextClasses();
}
}
};
})();
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc)
{
var at = /@/g;
at.lastIndex = 0;
var splitPoints = null;
var execResult;
while ((execResult = at.exec(lineText)))
{
if (!splitPoints)
{
splitPoints = [];
}
splitPoints.push(execResult.index);
}
if (!splitPoints) return textAndClassFunc;
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = function(regExp, tag)
{
return function(lineText, textAndClassFunc)
{
regExp.lastIndex = 0;
var regExpMatchs = null;
var splitPoints = null;
var execResult;
while ((execResult = regExp.exec(lineText)))
{
if (!regExpMatchs)
{
regExpMatchs = [];
splitPoints = [];
}
var startIndex = execResult.index;
var regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx)
{
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1];
}
}
return false;
}
var handleRegExpMatchsAfterSplit = (function()
{
var curIndex = 0;
return function(txt, cls)
{
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch)
{
newCls += " " + tag + ":" + regExpMatch;
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
};
})();
return linestylefilter.textAndClassFuncSplitter(handleRegExpMatchsAfterSplit, splitPoints);
};
};
linestylefilter.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]/;
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt)
{
var nextPointIndex = 0;
var idx = 0;
// don't split at 0
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
{
nextPointIndex++;
}
function spanHandler(txt, cls)
{
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
{
func(txt, cls);
idx += txt.length;
}
else
{
var splitPoints = splitPointsOpt;
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
var txtlen = txt.length;
if (pointLocInSpan >= txtlen)
{
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen)
{
nextPointIndex++;
}
}
else
{
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
nextPointIndex++;
// recurse
spanHandler(txt.substring(pointLocInSpan), cls);
}
}
}
return spanHandler;
};
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser)
{
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var plugins_;
if (typeof(plugins) != 'undefined')
{
plugins_ = plugins;
}
else
{
plugins_ = parent.parent.plugins;
}
var hookFilters = plugins_.callHook("aceGetFilterStack", {
linestylefilter: linestylefilter,
browser: browser
});
hookFilters.map(function(hookFilter)
{
func = hookFilter(lineText, func);
});
if (browser !== undefined && browser.msie)
{
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
// We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign.
func = linestylefilter.getAtSignSplitterFilter(
lineText, func);
}
return func;
};
// domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj)
{
// remove final newline from text if any
var text = textLine;
if (text.slice(-1) == '\n')
{
text = text.substring(0, text.length - 1);
}
function textAndClassFunc(tokenText, tokenClass)
{
domLineObj.appendSpan(tokenText, tokenClass);
}
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
};

View file

@ -23,67 +23,32 @@
/* global $, window */
var socket;
var LineNumbersDisabled = false;
var noColors = false;
var useMonospaceFontGlobal = false;
var globalUserName = false;
var hideQRCode = false;
var rtlIsTrue = false;
$(document).ready(function()
{
//start the costum js
if(typeof costumStart == "function") costumStart();
getParams();
handshake();
});
// These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins.
require('/jquery');
require('/farbtastic');
require('/excanvas');
JSON = require('/json2');
require('/undo-xpopup');
require('/prefixfree');
$(window).unload(function()
{
pad.dispose();
});
var chat = require('/chat').chat;
var getCollabClient = require('/collab_client').getCollabClient;
var padconnectionstatus = require('/pad_connectionstatus').padconnectionstatus;
var padcookie = require('/pad_cookie').padcookie;
var paddocbar = require('/pad_docbar').paddocbar;
var padeditbar = require('/pad_editbar').padeditbar;
var padeditor = require('/pad_editor').padeditor;
var padimpexp = require('/pad_impexp').padimpexp;
var padmodals = require('/pad_modals').padmodals;
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
var paduserlist = require('/pad_userlist').paduserlist;
var padutils = require('/pad_utils').padutils;
function createCookie(name, value, days, path)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
if(!path)
path = "/";
document.cookie = name + "=" + value + expires + "; path=" + path;
}
function readCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function randomString()
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var string_length = 20;
var randomstring = '';
for (var i = 0; i < string_length; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return "t." + randomstring;
}
var createCookie = require('/pad_utils').createCookie;
var readCookie = require('/pad_utils').readCookie;
var randomString = require('/pad_utils').randomString;
function getParams()
{
@ -96,12 +61,13 @@ function getParams()
var IsnoColors = params["noColors"];
var hideQRCode = params["hideQRCode"];
var rtl = params["rtl"];
var alwaysShowChat = params["alwaysShowChat"];
if(IsnoColors)
{
if(IsnoColors == "true")
{
noColors = true;
settings.noColors = true;
$('#clearAuthorship').hide();
}
}
@ -124,20 +90,20 @@ function getParams()
{
if(showLineNumbers == "false")
{
LineNumbersDisabled = true;
settings.LineNumbersDisabled = true;
}
}
if(useMonospaceFont)
{
if(useMonospaceFont == "true")
{
useMonospaceFontGlobal = true;
settings.useMonospaceFontGlobal = true;
}
}
if(userName)
{
// If the username is set as a parameter we should set a global value that we can call once we have initiated the pad.
globalUserName = unescape(userName);
settings.globalUserName = decodeURIComponent(userName);
}
if(hideQRCode)
{
@ -147,7 +113,14 @@ function getParams()
{
if(rtl == "true")
{
rtlIsTrue = true
settings.rtlIsTrue = true
}
}
if(alwaysShowChat)
{
if(alwaysShowChat == "true")
{
chat.stickToScreen();
}
}
}
@ -173,6 +146,12 @@ function savePassword()
document.location=document.location;
}
function ieTestXMLHTTP(){
// Test for IE known XML HTTP issue
if ($.browser.msie && !window.XMLHttpRequest){
$("#editorloadingbox").html("You do not have XML HTTP enabled in your browser. <a target='_blank' href='https://github.com/Pita/etherpad-lite/wiki/How-to-enable-native-XMLHTTP-support-in-IE'>Fix this issue</a>");
}
}
function handshake()
{
var loc = document.location;
@ -183,9 +162,10 @@ function handshake()
//find out in which subfolder we are
var resource = loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "socket.io";
//connect
socket = io.connect(url, {
socket = pad.socket = io.connect(url, {
resource: resource,
'max reconnection attempts': 3
'max reconnection attempts': 3,
'sync disconnect on unload' : false
});
function sendClientReady(isReconnect)
@ -194,12 +174,12 @@ function handshake()
padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces
if(!isReconnect)
document.title = document.title + " | " + padId;
document.title = padId.replace(/_+/g, ' ') + " | " + document.title;
var token = readCookie("token");
if (token == null)
{
token = randomString();
token = "t." + randomString();
createCookie("token", token, 60);
}
@ -243,15 +223,19 @@ function handshake()
sendClientReady(true);
});
socket.on('disconnect', function () {
function disconnectEvent()
{
pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout");
socket.on('disconnect', function (reason) {
if(reason == "booted"){
pad.collabClient.setChannelState("DISCONNECTED");
} else {
function disconnectEvent()
{
pad.collabClient.setChannelState("DISCONNECTED", "reconnect_timeout");
}
pad.collabClient.setChannelState("RECONNECTING");
disconnectTimeout = setTimeout(disconnectEvent, 10000);
}
pad.collabClient.setChannelState("RECONNECTING");
disconnectTimeout = setTimeout(disconnectEvent, 10000);
});
var receivedClientVars = false;
@ -270,13 +254,13 @@ function handshake()
{
$("#editorloadingbox").html("<b>You need a password to access this pad</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick='savePassword()'>ok</button>");
"<button type='button' onclick=\"" + padutils.escapeHtml('require('+JSON.stringify(module.id)+").savePassword()") + "\">ok</button>");
}
else if(obj.accessStatus == "wrongPassword")
{
$("#editorloadingbox").html("<b>You're password was wrong</b><br>" +
"<input id='passwordinput' type='password' name='password'>"+
"<button type='button' onclick='savePassword()'>ok</button>");
"<button type='button' onclick=\"" + padutils.escapeHtml('require('+JSON.stringify(module.id)+").savePassword()") + "\">ok</button>");
}
}
@ -294,37 +278,37 @@ function handshake()
clientVars.collab_client_vars.clientAgent = "Anonymous";
//initalize the pad
pad.init();
pad._afterHandshake();
initalized = true;
// If the LineNumbersDisabled value is set to true then we need to hide the Line Numbers
if (LineNumbersDisabled == true)
if (settings.LineNumbersDisabled == true)
{
pad.changeViewOption('showLineNumbers', false);
}
// If the noColors value is set to true then we need to hide the backround colors on the ace spans
if (noColors == true)
// If the noColors value is set to true then we need to hide the background colors on the ace spans
if (settings.noColors == true)
{
pad.changeViewOption('noColors', true);
}
if (rtlIsTrue == true)
if (settings.rtlIsTrue == true)
{
pad.changeViewOption('rtl', true);
}
// If the Monospacefont value is set to true then change it to monospace.
if (useMonospaceFontGlobal == true)
if (settings.useMonospaceFontGlobal == true)
{
pad.changeViewOption('useMonospaceFont', true);
}
// if the globalUserName value is set we need to tell the server and the client about the new authorname
if (globalUserName !== false)
if (settings.globalUserName !== false)
{
pad.notifyChangeName(globalUserName); // Notifies the server
pad.myUserInfo.name = globalUserName;
$('#myusernameedit').attr({"value":globalUserName}); // Updates the current users UI
pad.notifyChangeName(settings.globalUserName); // Notifies the server
pad.myUserInfo.name = settings.globalUserName;
$('#myusernameedit').attr({"value":settings.globalUserName}); // Updates the current users UI
}
}
//This handles every Message after the clientVars
@ -343,7 +327,6 @@ function handshake()
}
}
});
// Bind the colorpicker
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
}
@ -392,7 +375,6 @@ var pad = {
{
return clientVars.userIsGuest;
},
//
getUserId: function()
{
return pad.myUserInfo.userId;
@ -407,11 +389,25 @@ var pad = {
},
init: function()
{
padutils.setupGlobalExceptionHandler();
$(document).ready(function()
{
// test for XML HTTP capabiites
ieTestXMLHTTP();
// start the custom js
if (typeof customStart == "function") customStart();
getParams();
handshake();
});
},
_afterHandshake: function()
{
pad.clientTimeOffset = new Date().getTime() - clientVars.serverTimestamp;
//initialize the chat
chat.init();
chat.init(this);
pad.initTime = +(new Date());
pad.padOptions = clientVars.initialOptions;
@ -432,10 +428,10 @@ var pad = {
}
// order of inits is important here:
padcookie.init(clientVars.cookiePrefsToSet);
padcookie.init(clientVars.cookiePrefsToSet, this);
$("#widthprefcheck").click(pad.toggleWidthPref);
$("#sidebarcheck").click(pad.toggleSidebar);
// $("#sidebarcheck").click(pad.togglewSidebar);
pad.myUserInfo = {
userId: clientVars.userId,
@ -459,20 +455,20 @@ var pad = {
initialTitle: clientVars.initialTitle,
initialPassword: clientVars.initialPassword,
guestPolicy: pad.padOptions.guestPolicy
});
padimpexp.init();
padsavedrevs.init(clientVars.initialRevisionList);
}, this);
padimpexp.init(this);
padsavedrevs.init(clientVars.initialRevisionList, this);
padeditor.init(postAceInit, pad.padOptions.view || {});
padeditor.init(postAceInit, pad.padOptions.view || {}, this);
paduserlist.init(pad.myUserInfo);
paduserlist.init(pad.myUserInfo, this);
// padchat.init(clientVars.chatHistory, pad.myUserInfo);
padconnectionstatus.init();
padmodals.init();
padmodals.init(this);
pad.collabClient = getCollabClient(padeditor.ace, clientVars.collab_client_vars, pad.myUserInfo, {
colorPalette: pad.getColorPalette()
});
}, pad);
pad.collabClient.setOnUserJoin(pad.handleUserJoin);
pad.collabClient.setOnUpdateUserInfo(pad.handleUserUpdate);
pad.collabClient.setOnUserLeave(pad.handleUserLeave);
@ -488,6 +484,13 @@ var pad = {
{
padeditor.ace.focus();
}, 0);
if(padcookie.getPref("chatAlwaysVisible")){ // if we have a cookie for always showing chat then show it
chat.stickToScreen(true); // stick it to the screen
$('#options-stickychat').prop("checked", true); // set the checkbox to on
}
if(padcookie.getPref("showAuthorshipColors") == false){
pad.changeViewOption('showAuthorColors', false);
}
}
},
dispose: function()
@ -543,16 +546,6 @@ var pad = {
};
options.view[key] = value;
pad.handleOptionsChange(options);
// if the request isn't to hide line numbers then broadcast this to other users
if (key != "showLineNumbers" && key != "useMonospaceFont")
{
pad.collabClient.sendClientMessage(
{
type: 'padoptions',
options: options,
changedBy: pad.myUserInfo.name || "unnamed"
});
}
},
handleOptionsChange: function(opts)
{
@ -760,15 +753,18 @@ var pad = {
padsavedrevs.handleIsFullyConnected(isConnected);
pad.determineSidebarVisibility(isConnected && !isInitialConnect);
// pad.determineSidebarVisibility(isConnected && !isInitialConnect);
pad.determineChatVisibility(isConnected && !isInitialConnect);
pad.determineAuthorshipColorsVisibility();
},
determineSidebarVisibility: function(asNowConnectedFeedback)
/* determineSidebarVisibility: function(asNowConnectedFeedback)
{
if (pad.isFullyConnected())
{
var setSidebarVisibility = padutils.getCancellableAction("set-sidebar-visibility", function()
{
$("body").toggleClass('hidesidebar', !! padcookie.getPref('hideSidebar'));
// $("body").toggleClass('hidesidebar', !! padcookie.getPref('hideSidebar'));
});
window.setTimeout(setSidebarVisibility, asNowConnectedFeedback ? 3000 : 0);
}
@ -778,6 +774,27 @@ var pad = {
$("body").removeClass('hidesidebar');
}
},
*/
determineChatVisibility: function(asNowConnectedFeedback){
var chatVisCookie = padcookie.getPref('chatAlwaysVisible');
if(chatVisCookie){ // if the cookie is set for chat always visible
chat.stickToScreen(true); // stick it to the screen
$('#options-stickychat').prop("checked", true); // set the checkbox to on
}
else{
$('#options-stickychat').prop("checked", false); // set the checkbox for off
}
},
determineAuthorshipColorsVisibility: function(){
var authColCookie = padcookie.getPref('showAuthorshipColors');
if (authColCookie){
pad.changeViewOption('showAuthorColors', true);
$('#options-colorscheck').prop("checked", true);
}
else {
$('#options-colorscheck').prop("checked", false);
}
},
handleCollabAction: function(action)
{
if (action == "commitPerformed")
@ -826,6 +843,7 @@ var pad = {
$("#widthprefcheck").toggleClass('widthprefchecked', !! newValue).toggleClass('widthprefunchecked', !newValue);
pad.handleWidthChange();
},
/*
toggleSidebar: function()
{
var newValue = !padcookie.getPref('hideSidebar');
@ -833,6 +851,7 @@ var pad = {
$("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass('sidebarunchecked', !! newValue);
pad.determineSidebarVisibility();
},
*/
handleWidthChange: function()
{
var isFullWidth = padcookie.getPref('fullWidth');
@ -951,3 +970,31 @@ var alertBar = (function()
};
return self;
}());
function init() {
return pad.init();
}
var settings = {
LineNumbersDisabled: false
, noColors: false
, useMonospaceFontGlobal: false
, globalUserName: false
, hideQRCode: false
, rtlIsTrue: false
};
pad.settings = settings;
exports.settings = settings;
exports.createCookie = createCookie;
exports.readCookie = readCookie;
exports.randomString = randomString;
exports.getParams = getParams;
exports.getUrlVars = getUrlVars;
exports.savePassword = savePassword;
exports.handshake = handshake;
exports.pad = pad;
exports.init = init;
exports.alertBar = alertBar;

View file

@ -20,6 +20,8 @@
* limitations under the License.
*/
var padmodals = require('/pad_modals').padmodals;
var padconnectionstatus = (function()
{
@ -85,3 +87,5 @@ var padconnectionstatus = (function()
};
return self;
}());
exports.padconnectionstatus = padconnectionstatus;

View file

@ -85,9 +85,12 @@ var padcookie = (function()
var alreadyWarnedAboutNoCookies = false;
var inited = false;
var pad = undefined;
var self = {
init: function(prefsToSet)
init: function(prefsToSet, _pad)
{
pad = _pad;
var rawCookie = getRawCookie();
if (rawCookie)
{
@ -126,3 +129,5 @@ var padcookie = (function()
};
return self;
}());
exports.padcookie = padcookie;

View file

@ -20,6 +20,7 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var paddocbar = (function()
{
@ -113,11 +114,14 @@ var paddocbar = (function()
self.renderPassword();
}
var pad = undefined;
var self = {
title: null,
password: null,
init: function(opts)
init: function(opts, _pad)
{
pad = _pad;
panels = {
impexp: {
animator: getPanelOpenCloseAnimator("impexp", 160)
@ -444,6 +448,8 @@ var paddocbar = (function()
},
handleResizePage: function()
{
// Side-step circular reference. This should be injected.
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
padsavedrevs.handleResizePage();
},
hideLaterIfNoOtherInteraction: function()
@ -456,3 +462,5 @@ var paddocbar = (function()
};
return self;
}());
exports.paddocbar = paddocbar;

View file

@ -20,6 +20,19 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var padeditor = require('/pad_editor').padeditor;
var padsavedrevs = require('/pad_savedrevs').padsavedrevs;
function indexOf(array, value) {
for (var i = 0, ii = array.length; i < ii; i++) {
if (array[i] == value) {
return i;
}
}
return -1;
}
var padeditbar = (function()
{
@ -104,17 +117,20 @@ var padeditbar = (function()
{
self.toogleDropDown("users");
}
else if (cmd == 'settings')
{
self.toogleDropDown("settingsmenu");
}
else if (cmd == 'embed')
{
self.setEmbedLinks();
$('#embedinput').focus().select();
$('#linkinput').focus().select();
self.toogleDropDown("embed");
}
else if (cmd == 'import_export')
{
self.toogleDropDown("importexport");
}
else if (cmd == 'save')
{
padsavedrevs.saveNow();
@ -126,6 +142,7 @@ var padeditbar = (function()
if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough') ace.ace_toggleAttributeOnSelection(cmd);
else if (cmd == 'undo' || cmd == 'redo') ace.ace_doUndoRedo(cmd);
else if (cmd == 'insertunorderedlist') ace.ace_doInsertUnorderedList();
else if (cmd == 'insertorderedlist') ace.ace_doInsertOrderedList();
else if (cmd == 'indent')
{
if (!ace.ace_doIndentOutdent(false))
@ -156,15 +173,16 @@ var padeditbar = (function()
}, cmd, true);
}
}
padeditor.ace.focus();
if(padeditor.ace) padeditor.ace.focus();
},
toogleDropDown: function(moduleName)
{
var modules = ["embed", "users", "readonly", "importexport"];
var modules = ["settingsmenu", "importexport", "embed", "users"];
//hide all modules
if(moduleName == "none")
{
$("#editbar ul#menu_right > li").removeClass("selected");
for(var i=0;i<modules.length;i++)
{
//skip the userlist
@ -181,6 +199,11 @@ var padeditbar = (function()
}
else
{
var nth_child = indexOf(modules, moduleName) + 1;
if (nth_child > 0 && nth_child <= 3) {
$("#editbar ul#menu_right li:not(:nth-child(" + nth_child + "))").removeClass("selected");
$("#editbar ul#menu_right li:nth-child(" + nth_child + ")").toggleClass("selected");
}
//hide all modules that are not selected and show the selected one
for(var i=0;i<modules.length;i++)
{
@ -216,16 +239,18 @@ var padeditbar = (function()
var readonlyLink = basePath + "/ro/" + clientVars.readOnlyId;
$('#embedinput').val("<iframe src='" + readonlyLink + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
$('#linkinput').val(readonlyLink);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=H|0&chl=" + readonlyLink);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=|0&chl=" + readonlyLink);
}
else
{
var padurl = window.location.href.split("?")[0];
$('#embedinput').val("<iframe src='" + padurl + "?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false' width=600 height=400>");
$('#linkinput').val(padurl);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=H|0&chl=" + padurl);
$('#embedreadonlyqr').attr("src","https://chart.googleapis.com/chart?chs=200x200&cht=qr&chld=|0&chl=" + padurl);
}
}
};
return self;
}());
exports.padeditbar = padeditbar;

View file

@ -20,15 +20,23 @@
* limitations under the License.
*/
var padcookie = require('/pad_cookie').padcookie;
var padutils = require('/pad_utils').padutils;
var padeditor = (function()
{
var Ace2Editor = undefined;
var pad = undefined;
var settings = undefined;
var self = {
ace: null,
// this is accessed directly from other files
viewZoom: 100,
init: function(readyFunc, initialViewOptions)
init: function(readyFunc, initialViewOptions, _pad)
{
Ace2Editor = require('/ace').Ace2Editor;
pad = _pad;
settings = pad.settings;
function aceReady()
{
@ -61,14 +69,13 @@ var padeditor = (function()
});
padutils.bindCheckboxChange($("#options-colorscheck"), function()
{
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck"));
pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck"));
});
$("#viewfontmenu").change(function()
{
pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace');
});
noColors = !noColors; // Inversed so we can pass it to showauthorcolors
},
setViewOptions: function(newOptions)
{
@ -79,6 +86,11 @@ var padeditor = (function()
if (value == "false") return false;
return defaultValue;
}
self.ace.setProperty("showsauthorcolors", !settings.noColors);
self.ace.setProperty("rtlIsTrue", settings.rtlIsTrue);
var v;
v = getOption('showLineNumbers', true);
@ -92,10 +104,6 @@ var padeditor = (function()
v = getOption('useMonospaceFont', false);
self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));
$("#viewfontmenu").val(v ? "monospace" : "normal");
self.ace.setProperty("showsauthorcolors", noColors);
self.ace.setProperty("rtlIsTrue", rtlIsTrue);
},
initViewZoom: function()
{
@ -132,6 +140,7 @@ var padeditor = (function()
if (self.ace)
{
self.ace.destroy();
self.ace = null;
}
},
disable: function()
@ -150,3 +159,5 @@ var padeditor = (function()
};
return self;
}());
exports.padeditor = padeditor;

View file

@ -20,6 +20,7 @@
* limitations under the License.
*/
var paddocbar = require('/pad_docbar').paddocbar;
var padimpexp = (function()
{
@ -94,11 +95,6 @@ var padimpexp = (function()
}, 0);
$('#importarrow').stop(true, true).hide();
$('#importstatusball').show();
$("#import .importframe").load(function()
{
importDone();
});
}
return ret;
}
@ -106,8 +102,6 @@ var padimpexp = (function()
function importFailed(msg)
{
importErrorMessage(msg);
importDone();
addImportFrames();
}
function importDone()
@ -119,6 +113,7 @@ var padimpexp = (function()
}, 0);
$('#importstatusball').hide();
importClearTimeout();
addImportFrames();
}
function importClearTimeout()
@ -130,11 +125,19 @@ var padimpexp = (function()
}
}
function importErrorMessage(msg)
function importErrorMessage(status)
{
var msg="";
if(status === "convertFailed"){
msg = "We were not able to import this file. Please use a different document format or copy paste manually";
} else if(status === "uploadFailed"){
msg = "The upload failed, please try again";
}
function showError(fade)
{
$('#importmessagefail').html('<strong style="color: red">Import failed:</strong> ' + (msg || 'Please try a different file.'))[(fade ? "fadeIn" : "show")]();
$('#importmessagefail').html('<strong style="color: red">Import failed:</strong> ' + (msg || 'Please copy paste'))[(fade ? "fadeIn" : "show")]();
}
if ($('#importexport .importmessage').is(':visible'))
@ -174,39 +177,6 @@ var padimpexp = (function()
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()
@ -233,14 +203,22 @@ var padimpexp = (function()
}
/////
var pad = undefined;
var self = {
init: function()
init: function(_pad)
{
pad = _pad;
//get /p/padname
var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname)
//get http://example.com/p/padname
var pad_root_url = document.location.href.replace(document.location.pathname, pad_root_path)
// build the export links
$("#exporthtmla").attr("href", document.location.pathname + "/export/html");
$("#exportplaina").attr("href", document.location.pathname + "/export/txt");
$("#exportwordlea").attr("href", document.location.pathname + "/export/wordle");
$("#exportdokuwikia").attr("href", document.location.pathname + "/export/dokuwiki");
$("#exporthtmla").attr("href", pad_root_path + "/export/html");
$("#exportplaina").attr("href", pad_root_path + "/export/txt");
$("#exportwordlea").attr("href", pad_root_path + "/export/wordle");
$("#exportdokuwikia").attr("href", pad_root_path + "/export/dokuwiki");
//hide stuff thats not avaible if abiword is disabled
if(clientVars.abiwordAvailable == "no")
@ -248,29 +226,28 @@ var padimpexp = (function()
$("#exportworda").remove();
$("#exportpdfa").remove();
$("#exportopena").remove();
$("#importexport").css({"height":"115px"});
$("#importexportline").css({"height":"115px"});
$("#import").html("Import is not available");
$(".importformdiv").remove();
$("#import").html("Import is not available. To enable import please install abiword");
}
else if(clientVars.abiwordAvailable == "withoutPDF")
{
$("#exportpdfa").remove();
$("#exportworda").attr("href", document.location.pathname + "/export/doc");
$("#exportopena").attr("href", document.location.pathname + "/export/odt");
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$("#importexport").css({"height":"142px"});
$("#importexportline").css({"height":"142px"});
$("#importform").get(0).setAttribute('action', document.location.href + "/import");
$("#importform").attr('action', pad_root_url + "/import");
}
else
{
$("#exportworda").attr("href", document.location.pathname + "/export/doc");
$("#exportpdfa").attr("href", document.location.pathname + "/export/pdf");
$("#exportopena").attr("href", document.location.pathname + "/export/odt");
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportpdfa").attr("href", pad_root_path + "/export/pdf");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$("#importform").get(0).setAttribute('action', document.location.pathname + "/import");
$("#importform").attr('action', pad_root_path + "/import");
}
$("#impexp-close").click(function()
@ -283,16 +260,14 @@ var padimpexp = (function()
$('#importform').submit(fileInputSubmit);
$('.disabledexport').click(cantExport);
},
handleFrameCall: function(callName, argsArray)
handleFrameCall: function(status)
{
if (callName == 'importFailed')
if (status !== "ok")
{
importFailed(argsArray[0]);
}
else if (callName == 'importSuccessful')
{
importSuccessful(argsArray[0]);
importFailed(status);
}
importDone();
},
disable: function()
{
@ -308,7 +283,7 @@ var padimpexp = (function()
},
export2Wordle: function()
{
var padUrl = document.location.href + "/export/txt";
var padUrl = $('#exportwordlea').attr('href').replace(/\/wordle$/, '/txt')
$.get(padUrl, function(data)
{
@ -320,3 +295,5 @@ var padimpexp = (function()
};
return self;
}());
exports.padimpexp = padimpexp;

View file

@ -20,6 +20,9 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var paddocbar = require('/pad_docbar').paddocbar;
var padmodals = (function()
{
@ -70,9 +73,12 @@ var padmodals = (function()
clearShareBoxTo();
}
var pad = undefined;
var self = {
init: function()
init: function(_pad)
{
pad = _pad;
self.initFeedback();
self.initShareBox();
},
@ -364,3 +370,5 @@ var padmodals = (function()
};
return self;
}());
exports.padmodals = padmodals;

View file

@ -20,6 +20,8 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var paddocbar = require('/pad_docbar').paddocbar;
var padsavedrevs = (function()
{
@ -39,7 +41,7 @@ var padsavedrevs = (function()
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 + ');';
var restoreLink = 'javascript:void(require('+JSON.stringify(module.id)+').padsavedrevs.restoreRevision(' + JSON.stringify(rnum) + ');';
box.find(".srrestore").attr('href', restoreLink);
box.find(".srname").click(function(evt)
{
@ -345,9 +347,11 @@ var padsavedrevs = (function()
$(document).unbind('mouseup', clearScrollRepeatTimer);
}
var pad = undefined;
var self = {
init: function(initialRevisions)
init: function(initialRevisions, _pad)
{
pad = _pad;
self.newRevisionList(initialRevisions, true);
$("#savedrevs-savenow").click(function()
@ -518,3 +522,5 @@ var padsavedrevs = (function()
};
return self;
}());
exports.padsavedrevs = padsavedrevs;

View file

@ -20,6 +20,8 @@
* limitations under the License.
*/
var padutils = require('/pad_utils').padutils;
var myUserInfo = {};
var colorPickerOpen = false;
@ -460,9 +462,12 @@ var paduserlist = (function()
return true;
}, 1000);
var pad = undefined;
var self = {
init: function(myInitialUserInfo)
init: function(myInitialUserInfo, _pad)
{
pad = _pad;
self.setMyUserInfo(myInitialUserInfo);
$("#otheruserstable tr").remove();
@ -652,7 +657,7 @@ var paduserlist = (function()
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>');
box = $('<div id="'+padutils.escapeHtml('guestprompt-' + encodedUserId) + '" class="guestprompt"><div class="choices"><a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',false))')+'">Deny</a> <a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',true))') + '">Approve</a></div><div class="guestname"><strong>Guest:</strong> ' + padutils.escapeHtml(displayName) + '</div></div>');
$("#guestprompts").append(box);
}
else
@ -805,3 +810,5 @@ function showColorPicker()
$($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird
}
}
exports.paduserlist = paduserlist;

View file

@ -20,13 +20,62 @@
* limitations under the License.
*/
var Security = require('/security');
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
function randomString(len)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
len = len || 20
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
function createCookie(name, value, days, path)
{
if (days)
{
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
if(!path)
path = "/";
document.cookie = name + "=" + value + expires + "; path=" + path;
}
function readCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
var padutils = {
escapeHtml: function(x)
{
return String(x).replace(/\</g, '&lt;').replace(/\>/g, '&gt;');
return Security.escapeHTML(String(x));
},
uniqueId: function()
{
var pad = require('/pad').pad; // Sidestep circular dependency
function encodeNum(n, width)
{
// returns string that is exactly 'width' chars, padding with zeros
@ -102,24 +151,6 @@ var padutils = {
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)
{
@ -169,7 +200,7 @@ var padutils = {
{
if (i > idx)
{
pieces.push(padutils.escapeHtml(text.substring(idx, i)));
pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i;
}
}
@ -180,7 +211,7 @@ var padutils = {
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex);
pieces.push('<a ', (target ? 'target="' + target + '" ' : ''), 'href="', href.replace(/\"/g, '&quot;'), '">');
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '">');
advanceTo(startIndex + href.length);
pieces.push('</a>');
}
@ -219,6 +250,7 @@ var padutils = {
},
timediff: function(d)
{
var pad = require('/pad').pad; // Sidestep circular dependency
function format(n, word)
{
n = Math.round(n);
@ -468,14 +500,29 @@ var padutils = {
}
};
//send javascript errors to the server
window.onerror = function test (msg, url, linenumber)
{
var errObj = {errorInfo: JSON.stringify({msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
var loc = document.location;
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
var globalExceptionHandler = undefined;
function setupGlobalExceptionHandler() {
//send javascript errors to the server
if (!globalExceptionHandler) {
globalExceptionHandler = function test (msg, url, linenumber)
{
var errObj = {errorInfo: JSON.stringify({msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
var loc = document.location;
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";
$.post(url, errObj);
$.post(url, errObj);
return false;
};
return false;
};
window.onerror = globalExceptionHandler;
}
}
padutils.setupGlobalExceptionHandler = setupGlobalExceptionHandler;
padutils.binarySearch = require('/ace2_common').binarySearch;
exports.randomString = randomString;
exports.createCookie = createCookie;
exports.readCookie = readCookie;
exports.padutils = padutils;

View file

@ -4,10 +4,11 @@
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
plugins = {
var plugins = {
callHook: function(hookName, args)
{
var hook = clientVars.hooks[hookName];
var global = (function () {return this}());
var hook = ((global.clientVars || {}).hooks || {})[hookName];
if (hook === undefined) return [];
var res = [];
for (var i = 0, N = hook.length; i < N; i++)
@ -24,9 +25,13 @@ plugins = {
if (sep == undefined) sep = '';
if (pre == undefined) pre = '';
if (post == undefined) post = '';
return plugins.callHook(hookName, args).map(function(x)
{
return pre + x + post
}).join(sep || "");
var newCallhooks = [];
var callhooks = plugins.callHook(hookName, args);
for (var i = 0, ii = callhooks.length; i < ii; i++) {
newCallhooks[i] = pre + callhooks[i] + post;
}
return newCallhooks.join(sep || "");
}
};
exports.plugins = plugins;

421
static/js/prefixfree.js Normal file
View file

@ -0,0 +1,421 @@
/**
* StyleFix 1.0.1
* @author Lea Verou
* MIT license
*/
(function(){
if(!window.addEventListener) {
return;
}
var self = window.StyleFix = {
link: function(link) {
try {
// Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
if(link.rel !== 'stylesheet' || !link.sheet.cssRules || link.hasAttribute('data-noprefix')) {
return;
}
}
catch(e) {
return;
}
if(link.href == "data:text/css,"){
return false;
}
var url = link.href || link.getAttribute('data-href'),
base = url.replace(/[^\/]+$/, ''),
parent = link.parentNode,
xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
var css = xhr.responseText;
if(css && link.parentNode) {
css = self.fix(css, true, link);
// Convert relative URLs to absolute, if needed
if(base) {
css = css.replace(/url\((?:'|")?(.+?)(?:'|")?\)/gi, function($0, url) {
if(!/^([a-z]{3,10}:|\/|#)/i.test(url)) { // If url not absolute & not a hash
// May contain sequences like /../ and /./ but those DO work
return 'url("' + base + url + '")';
}
return $0;
});
// behavior URLs shoudnt be converted (Issue #19)
css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + base, 'gi'), '$1');
}
var style = document.createElement('style');
style.textContent = css;
style.media = link.media;
style.disabled = link.disabled;
style.setAttribute('data-href', link.getAttribute('href'));
parent.insertBefore(style, link);
parent.removeChild(link);
}
}
};
xhr.send(null);
link.setAttribute('data-inprogress', '');
},
styleElement: function(style) {
var disabled = style.disabled;
style.textContent = self.fix(style.textContent, true, style);
style.disabled = disabled;
},
styleAttribute: function(element) {
var css = element.getAttribute('style');
css = self.fix(css, false, element);
element.setAttribute('style', css);
},
process: function() {
// Linked stylesheets
$('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);
// Inline stylesheets
$('style').forEach(StyleFix.styleElement);
// Inline styles
$('[style]').forEach(StyleFix.styleAttribute);
},
register: function(fixer, index) {
(self.fixers = self.fixers || [])
.splice(index === undefined? self.fixers.length : index, 0, fixer);
},
fix: function(css, raw) {
for(var i=0; i<self.fixers.length; i++) {
css = self.fixers[i](css, raw) || css;
}
return css;
},
camelCase: function(str) {
return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-','');
},
deCamelCase: function(str) {
return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() });
}
};
/**************************************
* Process styles
**************************************/
(function(){
setTimeout(function(){
$('link[rel="stylesheet"]').forEach(StyleFix.link);
}, 10);
document.addEventListener('DOMContentLoaded', StyleFix.process, false);
})();
function $(expr, con) {
return [].slice.call((con || document).querySelectorAll(expr));
}
})();
/**
* PrefixFree 1.0.4
* @author Lea Verou
* MIT license
*/
(function(root, undefined){
if(!window.StyleFix || !window.getComputedStyle) {
return;
}
var self = window.PrefixFree = {
prefixCSS: function(css, raw) {
var prefix = self.prefix;
function fix(what, before, after, replacement) {
what = self[what];
if(what.length) {
var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi');
css = css.replace(regex, replacement);
}
}
fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(');
fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3');
fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:');
// Prefix properties *inside* values (issue #8)
if (self.properties.length) {
var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi');
fix('valueProperties', '\\b', ':(.+?);', function($0) {
return $0.replace(regex, prefix + "$1")
});
}
if(raw) {
fix('selectors', '', '\\b', self.prefixSelector);
fix('atrules', '@', '\\b', '@' + prefix + '$1');
}
// Fix double prefixing
css = css.replace(RegExp('-' + prefix, 'g'), '-');
return css;
},
// Warning: prefixXXX functions prefix no matter what, even if the XXX is supported prefix-less
prefixSelector: function(selector) {
return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix })
},
prefixProperty: function(property, camelCase) {
var prefixed = self.prefix + property;
return camelCase? StyleFix.camelCase(prefixed) : prefixed;
}
};
/**************************************
* Properties
**************************************/
(function() {
var prefixes = {},
properties = [],
shorthands = {},
style = getComputedStyle(document.documentElement, null),
dummy = document.createElement('div').style;
// Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
var iterate = function(property) {
if(property.charAt(0) === '-') {
properties.push(property);
var parts = property.split('-'),
prefix = parts[1];
// Count prefix uses
prefixes[prefix] = ++prefixes[prefix] || 1;
// This helps determining shorthands
while(parts.length > 3) {
parts.pop();
var shorthand = parts.join('-');
if(supported(shorthand) && properties.indexOf(shorthand) === -1) {
properties.push(shorthand);
}
}
}
},
supported = function(property) {
return StyleFix.camelCase(property) in dummy;
}
// Some browsers have numerical indices for the properties, some don't
if(style.length > 0) {
for(var i=0; i<style.length; i++) {
iterate(style[i])
}
}
else {
for(var property in style) {
iterate(StyleFix.deCamelCase(property));
}
}
// Find most frequently used prefix
var highest = {uses:0};
for(var prefix in prefixes) {
var uses = prefixes[prefix];
if(highest.uses < uses) {
highest = {prefix: prefix, uses: uses};
}
}
self.prefix = '-' + highest.prefix + '-';
self.Prefix = StyleFix.camelCase(self.prefix);
self.properties = [];
// Get properties ONLY supported with a prefix
for(var i=0; i<properties.length; i++) {
var property = properties[i];
if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
var unprefixed = property.slice(self.prefix.length);
if(!supported(unprefixed)) {
self.properties.push(unprefixed);
}
}
}
// IE fix
if(self.Prefix == 'Ms'
&& !('transform' in dummy)
&& !('MsTransform' in dummy)
&& ('msTransform' in dummy)) {
self.properties.push('transform', 'transform-origin');
}
self.properties.sort();
})();
/**************************************
* Values
**************************************/
(function() {
// Values that might need prefixing
var functions = {
'linear-gradient': {
property: 'backgroundImage',
params: 'red, teal'
},
'calc': {
property: 'width',
params: '1px + 5%'
},
'element': {
property: 'backgroundImage',
params: '#foo'
}
};
functions['repeating-linear-gradient'] =
functions['repeating-radial-gradient'] =
functions['radial-gradient'] =
functions['linear-gradient'];
var keywords = {
'initial': 'color',
'zoom-in': 'cursor',
'zoom-out': 'cursor',
'box': 'display',
'flexbox': 'display',
'inline-flexbox': 'display'
};
self.functions = [];
self.keywords = [];
var style = document.createElement('div').style;
function supported(value, property) {
style[property] = '';
style[property] = value;
return !!style[property];
}
for (var func in functions) {
var test = functions[func],
property = test.property,
value = func + '(' + test.params + ')';
if (!supported(value, property)
&& supported(self.prefix + value, property)) {
// It's supported, but with a prefix
self.functions.push(func);
}
}
for (var keyword in keywords) {
var property = keywords[keyword];
if (!supported(keyword, property)
&& supported(self.prefix + keyword, property)) {
// It's supported, but with a prefix
self.keywords.push(keyword);
}
}
})();
/**************************************
* Selectors and @-rules
**************************************/
(function() {
var
selectors = {
':read-only': null,
':read-write': null,
':any-link': null,
'::selection': null
},
atrules = {
'keyframes': 'name',
'viewport': null,
'document': 'regexp(".")'
};
self.selectors = [];
self.atrules = [];
var style = root.appendChild(document.createElement('style'));
function supported(selector) {
style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
return !!style.sheet.cssRules.length;
}
for(var selector in selectors) {
var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : '');
if(!supported(test) && supported(self.prefixSelector(test))) {
self.selectors.push(selector);
}
}
for(var atrule in atrules) {
var test = atrule + ' ' + (atrules[atrule] || '');
if(!supported('@' + test) && supported('@' + self.prefix + test)) {
self.atrules.push(atrule);
}
}
root.removeChild(style);
})();
// Properties that accept properties as their value
self.valueProperties = [
'transition',
'transition-property'
]
// Add class for current prefix
root.className += ' ' + self.prefix;
StyleFix.register(self.prefixCSS);
})(document.documentElement);

54
static/js/security.js Normal file
View file

@ -0,0 +1,54 @@
/**
* 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 HTML_ENTITY_MAP = {
'&': '&amp;'
, '<': '&lt;'
, '>': '&gt;'
, '"': '&quot;'
, "'": '&#x27;'
, '/': '&#x2F;'
};
// OSWASP Guidlines: &, <, >, ", ' plus forward slash.
var HTML_CHARACTERS_EXPRESSION = /[&"'<>\/]/g;
function escapeHTML(text) {
return text && text.replace(HTML_CHARACTERS_EXPRESSION, function (c) {
return HTML_ENTITY_MAP[c] || c;
});
}
// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
var HTML_ATTRIBUTE_CHARACTERS_EXPRESSION =
/[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
function escapeHTMLAttribute(text) {
return text && text.replace(HTML_ATTRIBUTE_CHARACTERS_EXPRESSION, function (c) {
return "&#x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ";";
});
};
// OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
var JAVASCRIPT_CHARACTERS_EXPRESSION =
/[\x00-\x2F\x3A-\x40\5B-\x60\x7B-\xFF]/g;
function escapeJavaScriptData(text) {
return text && text.replace(JAVASCRIPT_CHARACTERS_EXPRESSION, function (c) {
return "\\x" + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
});
}
exports.escapeHTML = escapeHTML;
exports.escapeHTMLAttribute = escapeHTMLAttribute;
exports.escapeJavaScriptData = escapeJavaScriptData;

View file

@ -21,7 +21,7 @@
*/
var noop = require('./ace2_common').noop;
function newSkipList()
@ -41,9 +41,6 @@ function newSkipList()
};
}
function noop()
{}
// if there are N elements in the skiplist, "start" is element -1 and "end" is element N
var start = {
key: null,
@ -488,3 +485,5 @@ that is a string.
}
return self;
}
exports.newSkipList = newSkipList;

154
static/js/timeslider.js Normal file
View file

@ -0,0 +1,154 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
/**
* 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.
*/
// These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins.
require('/jquery');
JSON = require('/json2');
require('/undo-xpopup');
var createCookie = require('/pad_utils').createCookie;
var readCookie = require('/pad_utils').readCookie;
var randomString = require('/pad_utils').randomString;
var socket, token, padId, export_links;
function init() {
$(document).ready(function ()
{
// start the custom js
if (typeof customStart == "function") customStart();
//get the padId out of the url
var urlParts= document.location.pathname.split("/");
padId = decodeURIComponent(urlParts[urlParts.length-2]);
//set the title
document.title = padId.replace(/_+/g, ' ') + " | " + document.title;
//ensure we have a token
token = readCookie("token");
if(token == null)
{
token = "t." + randomString();
createCookie("token", token, 60);
}
var loc = document.location;
//get the correct port
var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port;
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io";
//build up the socket io connection
socket = io.connect(url, {resource: resource});
//send the ready message once we're connected
socket.on('connect', function()
{
sendSocketMsg("CLIENT_READY", {});
});
//route the incoming messages
socket.on('message', function(message)
{
if(window.console) console.log(message);
if(message.type == "CLIENT_VARS")
{
handleClientVars(message);
}
else if(message.type == "CHANGESET_REQ")
{
changesetLoader.handleSocketResponse(message);
}
else if(message.accessStatus)
{
$("body").html("<h2>You have no permission to access this pad</h2>")
}
});
//get all the export links
export_links = $('#export > .exportlink')
if(document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1,document.referrer.lastIndexOf("/")) === "p") {
$("#returnbutton").attr("href", document.referrer);
} else {
$("#returnbutton").attr("href", document.location.href.substring(0,document.location.href.lastIndexOf("/")));
}
});
}
//sends a message over the socket
function sendSocketMsg(type, data)
{
var sessionID = readCookie("sessionID");
var password = readCookie("password");
var msg = { "component" : "timeslider",
"type": type,
"data": data,
"padId": padId,
"token": token,
"sessionID": sessionID,
"password": password,
"protocolVersion": 2};
socket.json.send(msg);
}
var fireWhenAllScriptsAreLoaded = [];
var BroadcastSlider, changesetLoader;
function handleClientVars(message)
{
//save the client Vars
clientVars = message.data;
//load all script that doesn't work without the clientVars
BroadcastSlider = require('/broadcast_slider').loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded);
require('/broadcast_revisions').loadBroadcastRevisionsJS();
changesetLoader = require('/broadcast').loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider);
//initialize export ui
require('/pad_impexp').padimpexp.init();
//change export urls when the slider moves
var export_rev_regex = /(\/\d+)?\/export/
BroadcastSlider.onSlider(function(revno)
{
export_links.each(function()
{
this.setAttribute('href', this.href.replace(export_rev_regex, '/' + revno + '/export'));
});
});
//fire all start functions of these scripts, formerly fired with window.load
for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++)
{
fireWhenAllScriptsAreLoaded[i]();
}
}
exports.init = init;

View file

@ -20,8 +20,10 @@
* limitations under the License.
*/
var Changeset = require('/Changeset');
var extend = require('/ace2_common').extend;
undoModule = (function()
var undoModule = (function()
{
var stack = (function()
{
@ -329,3 +331,5 @@ undoModule = (function()
apool: null
}; // apool is filled in by caller
})();
exports.undoModule = undoModule;

View file

@ -384,3 +384,5 @@ function makeVirtualLineView(lineNode)
}
}
exports.makeVirtualLineView = makeVirtualLineView;

View file

@ -1,335 +1,278 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<title>RPG Write</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<!-- CSS -->
<link href="../static/css/pad.css" rel="stylesheet">
<!-- javascript -->
<script type="text/javascript">
// <![CDATA[
var clientVars = {}; // ]]>
</script>
<script src="../socket.io/socket.io.js"></script>
<script src="../minified/pad.js"></script>
<link href="../static/custom/pad.css" rel="stylesheet">
<script src="../static/custom/pad.js"></script>
<style type="text/css" title="dynamicsyntax"></style>
</head>
<html>
<title>RPG Write</title>
<body>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<div id="editbar">
<link href="../static/css/pad.css" rel="stylesheet">
<link href="../static/custom/pad.css" rel="stylesheet">
<style title="dynamicsyntax"></style>
<ul id="menu_left">
<li onClick="window.pad&&pad.editbarClick('bold');return false" >
<a title="Bold (ctrl-B)">
<div class="buttonicon" style="background-position:0px -116px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('italic'); return false;" >
<a title="Italics (ctrl-I)">
<div class="buttonicon" style="background-position:0px 0px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('underline');return false;" >
<a title="Underline (ctrl-U)">
<div class="buttonicon" style="background-position:0px -236px;margin-top:1px;"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('strikethrough');return false;" >
<a title="Strikethrough">
<div class="buttonicon" style="background-position:0px -200px"></div>
</a>
</li>
<li class="separator"></li>
<li onClick="window.pad&&pad.editbarClick('insertunorderedlist');return false;" >
<a title="Toggle Bullet List">
<div class="buttonicon" style="background-position:0px -34px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('indent');return false;" >
<a title="Indent">
<div class="buttonicon" style="background-position:0px -52px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('outdent');return false;" >
<a title="Unindent">
<div class="buttonicon" style="background-position:0px -134px"></div>
</a>
</li>
<li class="separator"></li>
<li onClick="window.pad&&pad.editbarClick('undo');return false;" >
<a title="Undo (ctrl-Z)">
<div class="buttonicon" style="background-position:0px -255px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('redo');return false;" >
<a title="Redo (ctrl-Y)">
<div class="buttonicon" style="background-position:0px -166px"></div>
</a>
</li>
<li class="separator"></li>
<li id="clearAuthorship" onClick="window.pad&&pad.editbarClick('clearauthorship');return false;" >
<a title="Clear Authorship Colors">
<div class="buttonicon" style="background-position:0px -86px"></div>
</a>
</li>
</ul>
<!-- head and body had been removed intentionally -->
<ul id="menu_right">
<div id="editbar">
<ul id="menu_left">
<li id="bold" onClick="window.pad&amp;&amp;pad.editbarClick('bold');return false">
<a class="buttonicon buttonicon-bold" title="Bold (ctrl-B)"></a>
</li>
<li id="italic" onClick="window.pad&amp;&amp;pad.editbarClick('italic'); return false;">
<a class="buttonicon buttonicon-italic" title="Italics (ctrl-I)"></a>
</li>
<li id="underline" onClick="window.pad&amp;&amp;pad.editbarClick('underline');return false;" >
<a class="buttonicon buttonicon-underline" title="Underline (ctrl-U)"></a>
</li>
<li id="strikethrough" onClick="window.pad&amp;&amp;pad.editbarClick('strikethrough');return false;">
<a class="buttonicon buttonicon-strikethrough" title="Strikethrough"></a>
</li>
<li class="separator"></li>
<li id="oderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertorderedlist');return false;">
<a class="buttonicon buttonicon-insertorderedlist" title="Toggle Ordered List"></a>
</li>
<li id="unoderedlist" onClick="window.pad&amp;&amp;pad.editbarClick('insertunorderedlist');return false;">
<a class="buttonicon buttonicon-insertunorderedlist" title="Toggle Bullet List"></a>
</li>
<li id="indent" onClick="window.pad&amp;&amp;pad.editbarClick('indent');return false;">
<a class="buttonicon buttonicon-indent" title="Indent"></a>
</li>
<li id="outdent" onClick="window.pad&amp;&amp;pad.editbarClick('outdent');return false;">
<a class="buttonicon buttonicon-outdent" title="Unindent"></a>
</li>
<li class="separator"></li>
<li id="undo" onClick="window.pad&amp;&amp;pad.editbarClick('undo');return false;">
<a class="buttonicon buttonicon-undo" title="Undo (ctrl-Z)"></a>
</li>
<li id="redo" onClick="window.pad&amp;&amp;pad.editbarClick('redo');return false;">
<a class="buttonicon buttonicon-redo" title="Redo (ctrl-Y)"></a>
</li>
<li class="separator"></li>
<li id="clearAuthorship" onClick="window.pad&amp;&amp;pad.editbarClick('clearauthorship');return false;">
<a class="buttonicon buttonicon-clearauthorship" title="Clear Authorship Colors"></a>
</li>
</ul>
<ul id="menu_right">
<li id="settingslink" onClick="window.pad&amp;&amp;pad.editbarClick('settings');return false;">
<a class="buttonicon buttonicon-settings" id="settingslink" title="Settings of this pad"></a>
</li>
<li id="importexportlink" onClick="window.pad&amp;&amp;pad.editbarClick('import_export');return false;">
<a class="buttonicon buttonicon-import_export" id="exportlink" title="Import/Export from/to different document formats"></a>
</li>
<li id="embedlink" onClick="window.pad&amp;&amp;pad.editbarClick('embed');return false;" >
<a class="buttonicon buttonicon-embed" id="embedlink" title="Share and Embed this pad"></a>
</li>
<li class="separator"></li>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
<a class="buttonicon buttonicon-history" title="Show the history of this pad"></a>
</li>
<li id="usericon" onClick="window.pad&amp;&amp;pad.editbarClick('showusers');return false;" title="Show connected users">
<span class="buttonicon buttonicon-showusers" id="usericonback"></span>
<span id="online_count">1</span>
</li>
</ul>
</div>
<li onClick="window.pad&&pad.editbarClick('import_export');return false;">
<a id="exportlink" title="Import/Export from/to different document formats">
<div class="buttonicon" style="background-position:0px -68px"></div>
</a>
</li>
<li onClick="window.pad&&pad.editbarClick('embed');return false;" >
<a id="embedlink" title="Share and Embed this pad">
<div class="buttonicon" style="background-position:0px -18px"></div>
</a>
</li>
<li class="separator"></li>
<li id="timesliderlink" onClick="document.location = document.location.pathname+ '/timeslider'">
<a title="Show the history of this pad">
<div class="buttonicon" style="background-position:0px -218px"></div>
</a>
</li>
<li id="usericon" onClick="window.pad&&pad.editbarClick('showusers');return false;" >
<a title="Show connected users">
<div class="buttonicon" id="usericonback" style="background-position:0px -184px;display:inline-block;"></div>
<span id="online_count">1</span>
</a>
</li>
</ul>
<div id="users">
<div id="connectionstatus"></div>
<div id="myuser">
<div id="mycolorpicker">
<div id="colorpicker"></div>
<button id="mycolorpickersave">Save</button>
<button id="mycolorpickercancel">Cancel</button>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
</div>
<div id="myswatchbox"><div id="myswatch"></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled"></div>
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
</div>
<div id="otherusers">
<div id="guestprompts"></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr><td></td></tr>
</table>
<div id="nootherusers"></div>
</div>
<div id="userlistbuttonarea"></div>
</div>
</div>
<div id="editorcontainerbox">
<div id="editorcontainer"></div>
<div id="editorloadingbox">Loading...</div>
</div>
<div id="users">
<div id="connectionstatus">
<!-- -->
</div>
<div id="settingsmenu" class="popup">
<h1>Pad settings</h1>
<div class="column">
<h2>My view</h2>
<p>
<input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
<label for="options-stickychat">Chat always on screen</label>
</p>
<p>
<input type="checkbox" id="options-colorscheck">
<label for="options-colorscheck">Authorship colors</label>
</p>
<p>
<input type="checkbox" id="options-linenoscheck" checked>
<label for="options-linenoscheck">Line numbers</label>
</p>
<p>
Font type:
<select id="viewfontmenu">
<option value="normal">Normal</option>
<option value="monospace">Monospaced</option>
</select>
</p>
</div>
<div class="column">
<h2>Global view</h2>
<p>Currently nothing.</p>
<p class="note">These options affect everyone viewing this pad.</p>
</div>
</div>
<div id="myuser">
<div id="mycolorpicker">
<div id="colorpicker"></div>
<!--
<ul id="colorpickerswatches">
</ul>
-->
<span id="mycolorpickersave">
<a onclick="closeColorPicker()">Save</a>
</span>
<span id="mycolorpickercancel">
<a onclick="closeColorPicker()">Cancel</a>
</span>
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
</div>
<div id="importexport" class="popup">
<div class="column">
<h2>Import from text file, HTML, PDF, Word, ODT or RTF</h2><br>
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="15" id="importfileinput">
<div class="importmessage" id="importmessagefail"></div>
</div>
<div id="import"></div>
<div class="importmessage" id="importmessagesuccess">Successful!</div>
<div class="importformdiv" id="importformsubmitdiv">
<input type="hidden" name="padId" value="blpmaXT35R">
<span class="nowrap">
<input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
<img alt="" id="importstatusball" src="../static/img/loading.gif" align="top">
<img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top">
</span>
</div>
</form>
</div>
<div class="column">
<h2>Export current pad as</h2>
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
<a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
</div>
</div>
<div id="myswatchbox"><div id="myswatch"><!-- --></div></div>
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled" /></div>
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled" /></div>
</div>
<div id="otherusers">
<div id="guestprompts"><!-- --></div>
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
<tr>
<td>
</td>
</tr>
</table>
<div id="nootherusers">
</div>
</div>
<div id="userlistbuttonarea">
<!--<a href="javascript:void(0)" id="sharebutton">Share</a>-->
</div>
</div>
<!-- /padusers -->
<!--<div id="users">
<div id="embed" class="popup">
<div id="embedreadonly" class="right">
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
<label for="readonlyinput">Read only</label>
</div>
<h1>Share this pad</h1>
<div id="linkcode">
<h2>Link</h2>
<input id="linkinput" type="text" value="">
</div>
<br>
<div id="embedcode">
<h2>Embed URL</h2>
<input id="embedinput" type="text" value="">
</div>
<br>
<div id="qrcode">
<h2>QR code</h2>
<div id="qr_center"><img id="embedreadonlyqr"></div>
</div>
</div>
<!-- some example code so I can make the css --*>
</div>-->
<div id="chatthrob"></div>
<div id="editorcontainerbox">
<div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
<span id="chatlabel">Chat</span>
<span class="buttonicon buttonicon-chat"></span>
<span id="chatcounter">0</span>
</div>
<div id="editorcontainer">
<!-- -->
</div>
<div id="editorloadingbox">
Loading...
</div>
<div id="chatbox">
<div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="chattext" class="authorColors"></div>
<div id="chatinputbox">
<form>
<input id="chatinput" type="text" maxlength="140">
</form>
</div>
</div>
</div>
<div id="focusprotector">&nbsp;</div>
<!-- import export code -->
<div id="importexport">
<div id="modaloverlay">
<div id="modaloverlay-inner"></div>
</div>
<div id="mainmodals">
<div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">Connecting...</div>
<div class="reconnecting">Reestablishing connection...</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p><b>We're having trouble talking to the EtherPad lite synchronization server.</b> You may be connecting through an incompatible firewall or proxy server.</p>
</div>
<div id="disconnected_initsocketfail">
<p><b>We were unable to connect to the EtherPad lite synchronization server.</b> This may be due to an incompatibility with your web browser or internet connection.</p>
</div>
<div id="disconnected_userdup">
<p><b>You seem to have opened this pad in another browser window.</b> If you'd like to use this window instead, you can reconnect.</p>
</div>
<div id="disconnected_unknown">
<p><b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.</p>
</div>
<div id="disconnected_slowcommit">
<p><b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.</p>
</div>
<div id="disconnected_unauth">
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
</div>
<div id="disconnected_deleted">
<p>This pad was deleted.</p>
</div>
<div id="reconnect_advise">
<p>If this continues to happen, please let us know</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button>
</div>
</div>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId">
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
<input type="hidden" class="missedChanges" name="missedChanges">
</form>
</div>
</div>
<div id="import">
Import from text file, HTML, PDF, Word, ODT or RTF:<br/><br/>
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
<div class="importformdiv" id="importformfilediv">
<input type="file" name="file" size="15" id="importfileinput" />
<div class="importmessage" id="importmessagefail"></div>
</div>
<div class="importmessage" id="importmessagesuccess">Successful!</div>
<div class="importformdiv" id="importformsubmitdiv">
<input type="hidden" name="padId" value="blpmaXT35R" />
<span class="nowrap">
<input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput" />
<img alt="" id="importstatusball" src="../static/img/loading.gif" align="top" />
<img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top" />
</span>
</div>
</form>
</div>
<script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../minified/pad.js?callback=require.define"></script>
<script type="text/javascript" src="../static/custom/pad.js"></script>
<script type="text/javascript">
var clientVars = {};
(function () {
require.setRootURI("../minified/");
require.setGlobalKeyPath("require");
require('/pad').init();
<div id="importexportline"></div>
<div id="export">
Export current pad as:
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
<a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
<form id="wordlepost" name="wall" action="http://wordle.net/advanced" method="POST" style="margin-left:0px;">
<div id="hidetext" style=""><textarea id="text" name="text" id="text" style="display:none;">Coming soon!</textarea></div>
</form>
</div>
</div>
<!-- the embed code -->
<div id="embed">
<div id="embedreadonly">
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();"/><label for="readonlyinput">Read only</label>
</div>
Share:
<br/>
<div id="linkcode">
<label for="linkinput">Link:</label><input id="linkinput" type="text" value="">
</div><br/>
<div id="embedcode">
<label for="embedinput">Embed code:</label><input id="embedinput" type="text" value="">
</div><br/>
<div id="qrcode">
<label for="embedreadonlyqr">QR code:</label><br/>
<div id="qr_center"><img id="embedreadonlyqr"></div>
</div>
</div>
<div id="chatthrob">
</div>
<div id="chaticon">
<a onClick="chat.show();return false;"
title="Open the chat for this pad">
<span id="chatlabel">Chat</span>
<div class="buttonicon" style="background-position:0px -102px;display:inline-block;"></div>
</a>
<span id="chatcounter">0</span>
</div>
<div id="chatbox">
<div id="titlebar"><span id ="titlelabel">Chat</span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="chattext" class="authorColors"></div>
<div id="chatinputbox">
<form>
<input id="chatinput" type="text" maxlength="140"/>
</form>
</div>
</div>
<div id="focusprotector">&nbsp;</div>
<!-- /padeditor -->
<div id="modaloverlay">
<div id="modaloverlay-inner">
<!-- -->
</div>
</div>
<div id="mainmodals">
<div id="connectionbox" class="modaldialog">
<div id="connectionboxinner" class="modaldialog-inner">
<div class="connecting">
Connecting...
</div>
<div class="reconnecting">
Reestablishing connection...
</div>
<div class="disconnected">
<h2 class="h2_disconnect">Disconnected.</h2>
<h2 class="h2_userdup">Opened in another window.</h2>
<h2 class="h2_unauth">No Authorization.</h2>
<div id="disconnected_looping">
<p>
<b>We're having trouble talking to the EtherPad lite synchronization server.</b>
You may be connecting through an incompatible firewall or proxy server.
</p>
</div>
<div id="disconnected_initsocketfail">
<p>
<b>We were unable to connect to the EtherPad lite synchronization server.</b>
This may be due to an incompatibility with your web browser or internet connection.
</p>
</div>
<div id="disconnected_userdup">
<p>
<b>You seem to have opened this pad in another browser window.</b>
If you'd like to use this window instead, you can reconnect.
</p>
</div>
<div id="disconnected_unknown">
<p>
<b>Lost connection with the EtherPad lite synchronization server.</b> This may be due to a loss of network connectivity.
</p>
</div>
<div id="disconnected_slowcommit">
<p>
<b>Server not responding.</b> This may be due to network connectivity issues or high load on the server.
</p>
</div>
<div id="disconnected_unauth">
<p>
Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.
</p>
</div>
<div id="disconnected_deleted">
<p>
This pad was deleted.
</p>
</div>
<div id="reconnect_advise">
<p>
If this continues to happen, please let us know
</p>
</div>
<div id="reconnect_form">
<button id="forcereconnect">Reconnect Now</button>
</div>
</div>
</div>
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
<input type="hidden" class="padId" name="padId"/>
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo"/>
<input type="hidden" class="missedChanges" name="missedChanges"/>
</form>
</body>
/* TODO: These globals shouldn't exist. */
pad = require('/pad').pad;
chat = require('/chat').chat;
padeditbar = require('/pad_editbar').padeditbar;
padimpexp = require('/pad_impexp').padimpexp;
}());
</script>
</html>

View file

@ -7,150 +7,8 @@
<title>RPG Write Timeslider</title>
<link rel="stylesheet" href="../../static/css/pad.css">
<link rel="stylesheet" href="../../static/css/timeslider.css">
<link rel="stylesheet" href="../../static/custom/timeslider.css">
<style type="text/css" title="dynamicsyntax"></style>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../minified/timeslider.js"></script>
<link href="../../static/custom/timeslider.css" rel="stylesheet">
<script src="../../static/custom/timeslider.js"></script>
<script>
// <![CDATA[
var clientVars = {};
function createCookie(name,value,days)
{
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/";
}
function readCookie(name)
{
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function randomString() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var string_length = 20;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
return "t." + randomstring;
}
var socket, token, padId;
$(document).ready(function ()
{
//start the costum js
if(typeof costumStart == "function") costumStart();
//get the padId out of the url
var urlParts= document.location.pathname.split("/");
padId = decodeURIComponent(urlParts[urlParts.length-2]);
//set the title
document.title = document.title + " | " + padId;
//ensure we have a token
token = readCookie("token");
if(token == null)
{
token = randomString();
createCookie("token", token, 60);
}
var loc = document.location;
//get the correct port
var port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port;
//create the url
var url = loc.protocol + "//" + loc.hostname + ":" + port + "/";
//find out in which subfolder we are
var resource = loc.pathname.substr(1,loc.pathname.indexOf("/p/")) + "socket.io";
//build up the socket io connection
socket = io.connect(url, {resource: resource});
//send the ready message once we're connected
socket.on('connect', function()
{
sendSocketMsg("CLIENT_READY", {});
});
//route the incoming messages
socket.on('message', function(message)
{
if(window.console) console.log(message);
if(message.type == "CLIENT_VARS")
{
handleClientVars(message);
}
else if(message.type == "CHANGESET_REQ")
{
changesetLoader.handleSocketResponse(message);
}
else if(message.accessStatus)
{
$("body").html("<h2>You have no permission to access this pad</h2>")
}
});
});
//sends a message over the socket
function sendSocketMsg(type, data)
{
var sessionID = readCookie("sessionID");
var password = readCookie("password");
var msg = { "component" : "timeslider",
"type": type,
"data": data,
"padId": padId,
"token": token,
"sessionID": sessionID,
"password": password,
"protocolVersion": 2};
socket.json.send(msg);
}
var fireWhenAllScriptsAreLoaded = [];
function handleClientVars(message)
{
//save the client Vars
clientVars = message.data;
//load all script that doesn't work without the clientVars
loadBroadcastSliderJS();
loadBroadcastRevisionsJS();
loadBroadcastJS();
//fire all start functions of these scripts, formerly fired with window.load
for(var i=0;i < fireWhenAllScriptsAreLoaded.length;i++)
{
fireWhenAllScriptsAreLoaded[i]();
}
}
// ]]>
</script>
</head>
<body id="padbody" class="timeslider limwidth nonpropad nonprouser">
@ -269,14 +127,14 @@
<div id="editbarright" class="editbarright">
<!-- termporary place holder-->
<ul>
<li onClick="window.padeditbar.toolbarClick('import_export');return false;">
<a id="exportlink" title="Export to different document formats">
<div class="buttonicon buttonicon-import_export"></div>
</a>
</li>
</ul>
<a id = "returnbutton">Return to pad</a>
<script>
if(document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1,document.referrer.lastIndexOf("/")) === "p") {
$("#returnbutton").attr("href", document.referrer);
} else {
$("#returnbutton").attr("href", document.location.href.substring(0,document.location.href.lastIndexOf("/")));
}
</script>
</div>
<div id="editbarinner" class="editbarinner">
@ -320,6 +178,41 @@
</div>
<div id="mainmodals"></div>
<!-- export code -->
<div id="importexport">
<div id="export" class="popup">
Export current version as:
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml">HTML</div></a>
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain">Plain text</div></a>
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword">Microsoft Word</div></a>
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf">PDF</div></a>
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen">OpenDocument</div></a>
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki">DokuWiki text</div></a>
<a id="exportwordlea" target="_blank" onClick="padimpexp.export2Wordle();return false;" class="exportlink"><div class="exporttype" id="exportwordle">Wordle</div></a>
<form id="wordlepost" name="wall" action="http://wordle.net/advanced" method="POST" style="margin-left:0px;">
<div id="hidetext" style=""><textarea id="text" name="text" id="text" style="display:none;">Coming soon!</textarea></div>
</form>
</div>
</div>
<script type="text/javascript" src="../../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../minified/timeslider.js?callback=require.define"></script>
<script type="text/javascript" src="../../static/custom/timeslider.js"></script>
<script type="text/javascript" >
var clientVars = {};
(function () {
require.setRootURI("../minified/");
require.setGlobalKeyPath("require");
require('/timeslider').init();
/* TODO: These globals shouldn't exist. */
padeditbar = require('/pad_editbar').padeditbar;
padimpexp = require('/pad_impexp').padimpexp;
})();
</script>
</body>
</html>