mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-02-01 03:12:42 +01:00
Merge branch 'develop' of github.com:ether/etherpad-lite into develop
Conflicts: README.md
This commit is contained in:
commit
8d0d71eddb
66 changed files with 11576 additions and 287 deletions
22
.travis.yml
Normal file
22
.travis.yml
Normal file
|
@ -0,0 +1,22 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
install:
|
||||
- "bin/installDeps.sh"
|
||||
- "export GIT_HASH=$(cat .git/HEAD | head -c 7)"
|
||||
before_script:
|
||||
- "tests/frontend/travis/sauce_tunnel.sh"
|
||||
script:
|
||||
- "tests/frontend/travis/runner.sh"
|
||||
env:
|
||||
global:
|
||||
- secure: "OxZ2s724S96xu02746LUN+4lBckAe1BOICJjfA4jnFPNpiNU6XoMH52f+LgG\nZzAwu6xMTv+NsaLGp6Avm3cx4GZ+jIiHe4NB9XOgYPa0r0TBIi3ueWYPDyVv\nCniS/4qX68DoFNV4lh7zMBXn0IIPxT4Wppm3desBpjWDP/SdoRs="
|
||||
- SAUCE_USER=pita
|
||||
jdk:
|
||||
- oraclejdk6
|
||||
notifications:
|
||||
email:
|
||||
- petermartischka@googlemail.com
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#etherpad-lite-dev"
|
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -1,3 +1,34 @@
|
|||
# v1.2
|
||||
* Internationalization / Language / Translation support (i18n) with support for German/French
|
||||
* A frontend/client side testing framework and backend build tests
|
||||
* Customizable robots.txt
|
||||
* Customizable app title (finally you can name your epl instance!)
|
||||
* eejs render arguments are now passed on to eejs hooks through the newly introduced `renderContext` argument.
|
||||
* Plugin-specific settings in settings.json (finally allowing for things like a google analytics plugin)
|
||||
* Serve admin dashboard at /admin (still very limited, though)
|
||||
* Modify your settings.json through the newly created UI at /admin/settings
|
||||
* Fix: Import <ol>'s as <ol>'s and not as <ul>'s!
|
||||
* Added solaris compatibility (bin/installDeps.sh was broken on solaris)
|
||||
* Fix a bug with IE9 and Password Protected Pads using HTTPS
|
||||
|
||||
# v1.1.5
|
||||
* We updated to express v3 (please [make sure](https://github.com/visionmedia/express/wiki/Migrating-from-2.x-to-3.x) your plugin works under express v3)
|
||||
* `userColor` URL parameter which sets the initial author color
|
||||
* Hooks for "padCreate", "padRemove", "padUpdate" and "padLoad" events
|
||||
* Security patches concerning the handling of messages originating from clients
|
||||
* Our database abstraction layer now natively supports couchDB, levelDB, mongoDB, postgres, and redis!
|
||||
* We now provide a script helping you to migrate from dirtyDB to MySQL
|
||||
* Support running Etherpad Lite behind IIS, using [iisnode](https://github.com/tjanczuk/iisnode/wiki)
|
||||
* LibreJS Licensing information in headers of HTML templates
|
||||
* Default port number to PORT env var, if port isn't specified in settings
|
||||
* Fix for `convert.js`
|
||||
* Raise upper char limit in chat to 999 characters
|
||||
* Fixes for mobile layout
|
||||
* Fixes for usage behind reverse proxy
|
||||
* Improved documentation
|
||||
* Fixed some opera style bugs
|
||||
* Update npm and fix some bugs, this introduces
|
||||
|
||||
# v1.1
|
||||
* Introduced Plugin framework
|
||||
* Many bugfixes
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Developer Guidelines
|
||||
(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/Pita/etherpad-lite#get-in-touch))
|
||||
(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad-lite#get-in-touch))
|
||||
|
||||
**Our goal is to iterate in small steps. Release often, release early. Evolution instead of a revolution**
|
||||
|
||||
|
|
133
README.md
133
README.md
|
@ -1,133 +0,0 @@
|
|||
<<<<<<< HEAD
|
||||
# Making collaborative editing the standard on the web
|
||||
|
||||
# About
|
||||
Etherpad lite is a really-real time collaborative editor spawned from the Hell fire of Etherpad.
|
||||
We're reusing the well tested Etherpad easysync library to make it really realtime. Etherpad Lite
|
||||
is based on node.js ergo is much lighter and more stable than the original Etherpad. Our hope
|
||||
is that this will encourage more users to use and install a realtime collaborative editor. A smaller, manageable and well
|
||||
documented codebase makes it easier for developers to improve the code and contribute towards the project.
|
||||
|
||||
**Etherpad vs Etherpad lite**
|
||||
<table>
|
||||
<tr>
|
||||
<td> </td><td><b>Etherpad</b></td><td><b>Etherpad Lite</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Size of the folder (without git history)</td><td>30 MB</td><td>1.5 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Languages used server side</td><td>Javascript (Rhino), Java, Scala</td><td>Javascript (node.js)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">Lines of server side Javascript code</td><td>~101k</td><td>~9k</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">RAM Usage immediately after start</td><td>257 MB (grows to ~1GB)</td><td>16 MB (grows to ~30MB)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/Pita/etherpad-lite/wiki/HTTP-API)
|
||||
that allows your web application to manage pads, users and groups. It is recommended to use the client implementations available for this API, listed on [this wiki page](https://github.com/Pita/etherpad-lite/wiki/HTTP-API-client-libraries).
|
||||
There is also a [jQuery plugin](https://github.com/johnyma22/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website
|
||||
|
||||
**Visit [beta.etherpad.org](http://beta.etherpad.org) to test it live**
|
||||
|
||||
Also, check out the **[FAQ](https://github.com/Pita/etherpad-lite/wiki/FAQ)**, really!
|
||||
|
||||
# Installation
|
||||
|
||||
## Windows
|
||||
|
||||
### Prebuilt windows package
|
||||
This package works out of the box on any windows machine, but it's not very useful for developing purposes...
|
||||
|
||||
1. Download the windows package <https://github.com/Pita/etherpad-lite/downloads>
|
||||
2. Extract the folder
|
||||
|
||||
Now, run `start.bat` and open <http://localhost:9001> in your browser. You like it? [Next steps](#next-steps).
|
||||
|
||||
### Fancy install
|
||||
You'll need [node.js](http://nodejs.org) and (optionally, though recommended) git.
|
||||
|
||||
1. Grab the source, either
|
||||
- download <https://github.com/Pita/etherpad-lite/zipball/master>
|
||||
- or `git clone https://github.com/Pita/etherpad-lite.git` (for this you need git, obviously)
|
||||
2. start `bin\installOnWindows.bat`
|
||||
|
||||
Now, run `start.bat` and open <http://localhost:9001> in your browser.
|
||||
|
||||
Update to the latest version with `git pull origin`, then run `bin\installOnWindows.bat`, again.
|
||||
|
||||
[Next steps](#next-steps).
|
||||
|
||||
## Linux
|
||||
You'll need gzip, git, curl, libssl develop libraries, python and gcc.
|
||||
*For Debian/Ubuntu*: `apt-get install gzip git-core curl python libssl-dev pkg-config build-essential`
|
||||
*For Fedora/CentOS*: `yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"`
|
||||
|
||||
Additionally, you'll need [node.js](http://nodejs.org).
|
||||
|
||||
**As any user (we recommend creating a separate user called etherpad-lite):**
|
||||
|
||||
1. Move to a folder where you want to install Etherpad Lite. Clone the git repository `git clone git://github.com/Pita/etherpad-lite.git`
|
||||
2. Change into the new directory containing the cloned source code `cd etherpad-lite`
|
||||
|
||||
Now, run `bin/run.sh` and open <http://127.0.0.1:9001> in your browser.
|
||||
|
||||
Update to the latest version with `git pull origin`. The next start with bin/run.sh will update the dependencies.
|
||||
|
||||
You like it? [Next steps](#next-steps).
|
||||
|
||||
# Next Steps
|
||||
|
||||
## Tweak the settings
|
||||
You can modify the settings in `settings.json`. (If you need to handle multiple settings files, you can pass the path to a settings file 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, since the "dirtyDB" database driver is only for testing and/or development purposes.
|
||||
|
||||
## Helpful resources
|
||||
The [wiki](https://github.com/Pita/etherpad-lite/wiki) is your one-stop resource for Tutorials and How-to's, really check it out! Also, feel free to improve these wiki pages.
|
||||
|
||||
Documentation can be found in `docs/`.
|
||||
|
||||
# Development
|
||||
|
||||
## Things you should know
|
||||
Read this [git guide](http://learn.github.com/p/intro.html) and watch this [video on getting started with Etherpad Lite Development](http://youtu.be/67-Q26YH97E).
|
||||
|
||||
If you're new to node.js, start with Ryan Dahl's [Introduction to Node.js](http://youtu.be/jo_B4LTHi3I).
|
||||
|
||||
You can debug Etherpad lite using `bin/debugRun.sh`.
|
||||
|
||||
If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/Pita/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading).
|
||||
|
||||
## Getting started
|
||||
You know all this and just want to know how you can help?
|
||||
|
||||
Look at the [TODO list](https://github.com/Pita/etherpad-lite/wiki/TODO) and our [Issue tracker](https://github.com/Pita/etherpad-lite/issues). (Please consider using [jshint](http://www.jshint.com/about/), if you plan to contribute code.)
|
||||
|
||||
Also, and most importantly, read our [**Developer Guidelines**](https://github.com/Pita/etherpad-lite/wiki/Developer-Guidelines), really!
|
||||
|
||||
# Get in touch
|
||||
Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)!
|
||||
|
||||
# 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"
|
||||
|
||||
# Donate!
|
||||
* [Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation)
|
||||
* Paypal - Press the donate button on [etherpad.org](http://etherpad.org)
|
||||
|
||||
# License
|
||||
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
=======
|
||||
We moved!
|
||||
=============
|
||||
|
||||
You can now find Etherpad Lite under [github.com/ether/etherpad-lite](https://github.com/ether/etherpad-lite)
|
||||
>>>>>>> a5c4fb154f7414cd5dca9e50c3f2ad00e42672f5
|
|
@ -52,6 +52,13 @@ echo "download windows node..."
|
|||
cd bin
|
||||
wget "http://nodejs.org/dist/v$NODE_VERSION/node.exe" -O ../node.exe
|
||||
|
||||
echo "remove git history to reduce folder size"
|
||||
rm -rf .git/objects
|
||||
|
||||
echo "remove windows jsdom-nocontextify/test folder"
|
||||
rm -rf /tmp/etherpad-lite-win/node_modules/ep_etherpad-lite/node_modules/jsdom-nocontextifiy/test/
|
||||
rm -rf /tmp/etherpad-lite-win/src/node_modules/jsdom-nocontextifiy/test/
|
||||
|
||||
echo "create the zip..."
|
||||
cd /tmp
|
||||
zip -9 -r etherpad-lite-win.zip etherpad-lite-win
|
||||
|
|
|
@ -69,7 +69,7 @@ echo "Ensure that all dependencies are up to date..."
|
|||
cd node_modules
|
||||
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
|
||||
cd ep_etherpad-lite
|
||||
npm install
|
||||
npm install --loglevel warn
|
||||
) || {
|
||||
rm -rf node_modules
|
||||
exit 1
|
||||
|
@ -79,7 +79,7 @@ echo "Ensure jQuery is downloaded and up to date..."
|
|||
DOWNLOAD_JQUERY="true"
|
||||
NEEDED_VERSION="1.7.1"
|
||||
if [ -f "src/static/js/jquery.js" ]; then
|
||||
if [ $(uname) = "SunOS"]; then
|
||||
if [ $(uname) = "SunOS" ]; then
|
||||
VERSION=$(cat src/static/js/jquery.js | head -n 3 | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");
|
||||
else
|
||||
VERSION=$(cat src/static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");
|
||||
|
|
|
@ -13,7 +13,7 @@ cmd /C node -e %check_version% || exit /B 1
|
|||
|
||||
echo _
|
||||
echo Installing etherpad-lite and dependencies...
|
||||
cmd /C npm install src/ || exit /B 1
|
||||
cmd /C npm install src/ --loglevel warn || exit /B 1
|
||||
|
||||
echo _
|
||||
echo Copying custom templates...
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
var dirty = require("../src/node_modules/ueberDB/node_modules/dirty")('var/dirty.db');
|
||||
var db = require("../src/node/db/DB");
|
||||
require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) {
|
||||
|
||||
db.init(function() {
|
||||
db = db.db;
|
||||
dirty.on("load", function() {
|
||||
dirty.forEach(function(key, value) {
|
||||
db.set(key, value);
|
||||
process.chdir(npm.root+'/..')
|
||||
|
||||
var dirty = require("ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty")('var/dirty.db');
|
||||
var db = require("ep_etherpad-lite/node/db/DB");
|
||||
|
||||
db.init(function() {
|
||||
db = db.db;
|
||||
dirty.on("load", function() {
|
||||
dirty.forEach(function(key, value) {
|
||||
db.set(key, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -52,3 +52,11 @@ Default: false
|
|||
* Boolean
|
||||
|
||||
Default: false
|
||||
|
||||
## lang
|
||||
* String
|
||||
|
||||
Default: en
|
||||
|
||||
Example: `lang=ar` (translates the interface into Arabic)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@include documentation
|
||||
@include cusotm_static
|
||||
@include localization
|
||||
@include custom_static
|
||||
@include api/api
|
||||
@include plugins
|
||||
@include database
|
||||
|
|
19
doc/localization.md
Normal file
19
doc/localization.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Localization
|
||||
Etherpad lite provides a multi-language user interface, that's apart from your users' content, so users from different countries can collaborate on a single document, while still having the user interface displayed in their mother tongue.
|
||||
|
||||
## Translating
|
||||
`/src/locales` contains files for all supported languages which contain the translated strings. To add support for a new language, copy the English language file named `en.ini` and translate it.
|
||||
Translation files are simply `*.ini` files and look like this:
|
||||
|
||||
```
|
||||
pad.modals.connected = Connecté.
|
||||
pad.modals.uderdup = Ouvrir dans une nouvelle fenêtre.
|
||||
pad.toolbar.unindent.title = Désindenter
|
||||
pad.toolbar.undo.title = Annuler (Ctrl-Z)
|
||||
timeslider.pageTitle = {{appTitle}} Curseur temporel
|
||||
```
|
||||
|
||||
There must be only one translation per line. Each translation consists of a key (the id of the string that is to be translated), an equal sign and the translated string. Anything after the equa sign will be used as the translated string (you may put some spaces after `=` for better readability, though). Terms in curly braces must not be touched but left as they are, since they represent a dynamically changing part of the string like a variable. Imagine a message welcoming a user: `Welcome, {{userName}}!` would be translated as `Ahoy, {{userName}}!` in pirate.
|
||||
|
||||
## Under the hood
|
||||
We use a `language` cookie to save your language settings if you change them. If you don't, we autodetect your locale using information from your browser. Now, that we know your preferred language this information is feeded into a very nice library called [webL10n](https://github.com/fabi1cazenave/webL10n), which loads the appropriate translations and applies them to our templates, providing translation params, pluralization, include rules and even a nice javascript API along the way.
|
|
@ -5,6 +5,7 @@
|
|||
"restartServer": "ep_etherpad-lite/node/hooks/express:restartServer"
|
||||
} },
|
||||
{ "name": "static", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/static:expressCreateServer" } },
|
||||
{ "name": "i18n", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/i18n:expressCreateServer" } },
|
||||
{ "name": "specialpages", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages:expressCreateServer" } },
|
||||
{ "name": "padurlsanitize", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padurlsanitize:expressCreateServer" } },
|
||||
{ "name": "padreadonly", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/padreadonly:expressCreateServer" } },
|
||||
|
@ -13,6 +14,7 @@
|
|||
{ "name": "importexport", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/importexport:expressCreateServer" } },
|
||||
{ "name": "errorhandling", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/errorhandling:expressCreateServer" } },
|
||||
{ "name": "socketio", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/socketio:expressCreateServer" } },
|
||||
{ "name": "tests", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/tests:expressCreateServer" } },
|
||||
{ "name": "admin", "hooks": { "expressCreateServer": "ep_etherpad-lite/node/hooks/express/admin:expressCreateServer" } },
|
||||
{ "name": "adminplugins", "hooks": {
|
||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins:expressCreateServer",
|
||||
|
|
32
src/locales/bn.ini
Normal file
32
src/locales/bn.ini
Normal file
|
@ -0,0 +1,32 @@
|
|||
; Exported from translatewiki.net
|
||||
; Author: Nasir8891
|
||||
[bn]
|
||||
index.newPad = নতুন প্যাড
|
||||
index.createOpenPad = অথবা নাম লিখে প্যাড খুলুন/তৈরী করুন:
|
||||
pad.toolbar.bold.title = গাড় করা (Ctrl-B)
|
||||
pad.toolbar.italic.title = বাঁকা করা (Ctrl-I)
|
||||
pad.toolbar.settings.title = সেটিং
|
||||
pad.colorpicker.save = সংরক্ষণ
|
||||
pad.colorpicker.cancel = বাতিল
|
||||
pad.loading = লোডিং...
|
||||
pad.settings.language = ভাষা:
|
||||
pad.importExport.successful = সফল!
|
||||
; Fuzzy
|
||||
pad.importExport.export = এই প্যাডটি এক্সপোর্ট করুন
|
||||
pad.importExport.exporthtml = এইচটিএমএল
|
||||
pad.importExport.exportplain = সাধারণ লেখা
|
||||
pad.importExport.exportword = মাইক্রোসফট ওয়ার্ড
|
||||
pad.importExport.exportpdf = পিডিএফ
|
||||
pad.importExport.exportopen = ওডিএফ (ওপেন ডকুমেন্ট ফরম্যাট)
|
||||
pad.modals.deleted = অপসারিত।
|
||||
pad.modals.deleted.explanation = এই প্যাডটি অপসারণ করা হয়েছে।
|
||||
pad.modals.disconnected.explanation = সার্ভারের সাথে যোগাযোগ করা যাচ্ছে না
|
||||
pad.share = শেয়ার করুন
|
||||
pad.share.link = লিংক
|
||||
pad.share.emebdcode = ইউআরএল সংযোজন
|
||||
pad.chat = চ্যাট
|
||||
pad.chat.title = এই প্যাডের জন্য চ্যাট চালু করুন।
|
||||
timeslider.toolbar.returnbutton = প্যাডে ফিরে যাও
|
||||
timeslider.toolbar.authors = লেখকগণ:
|
||||
timeslider.toolbar.authorsList = কোনো লেখক নেই
|
||||
timeslider.exportCurrent = বর্তমান সংস্করণটি এক্সপোর্ট করুন:
|
78
src/locales/de.ini
Normal file
78
src/locales/de.ini
Normal file
|
@ -0,0 +1,78 @@
|
|||
; Exported from translatewiki.net
|
||||
[de]
|
||||
index.newPad = Neues Pad
|
||||
index.createOpenPad = Pad mit folgendem Namen öffnen
|
||||
pad.toolbar.bold.title = Fett (Strg-B)
|
||||
pad.toolbar.italic.title = Kursiv (Strg-I)
|
||||
pad.toolbar.underline.title = Unterstrichen (Strg-U)
|
||||
pad.toolbar.strikethrough.title = Durchgestrichen
|
||||
pad.toolbar.ol.title = Nummerierte Liste
|
||||
pad.toolbar.ul.title = Ungeordnete Liste
|
||||
pad.toolbar.indent.title = Einrücken
|
||||
pad.toolbar.unindent.title = Ausrücken
|
||||
pad.toolbar.undo.title = Rückgängig (Strg-Z)
|
||||
pad.toolbar.redo.title = Wiederholen (Strg-Y)
|
||||
pad.toolbar.clearAuthorship.title = Autorenfarben zurücksetzen
|
||||
pad.toolbar.import_export.title = Import/Export von verschiedenen Dateiformaten
|
||||
pad.toolbar.timeslider.title = Pad-Geschichte anzeigen
|
||||
pad.toolbar.savedRevision.title = Diese Revision markieren
|
||||
pad.toolbar.settings.title = Einstellungen
|
||||
pad.toolbar.embed.title = Dieses Pad teilen oder einbetten
|
||||
pad.toolbar.showusers.title = Verbundene Benutzer anzeigen
|
||||
pad.colorpicker.save = Speichern
|
||||
pad.colorpicker.cancel = Abbrechen
|
||||
pad.loading = Laden...
|
||||
pad.settings.padSettings = Pad Einstellungen
|
||||
pad.settings.myView = Eigene Ansicht
|
||||
pad.settings.stickychat = Chat immer anzeigen
|
||||
pad.settings.colorcheck = Autorenfarben anzeigen
|
||||
pad.settings.linenocheck = Zeilennummern
|
||||
pad.settings.fontType = Schriftart:
|
||||
pad.settings.fontType.normal = Normal
|
||||
pad.settings.fontType.monospaced = Monospace
|
||||
pad.settings.globalView = Gemeinsame Ansicht
|
||||
pad.settings.language = Sprache:
|
||||
pad.importExport.import_export = Import/Export
|
||||
pad.importExport.import = Datei oder Dokument hochladen
|
||||
pad.importExport.successful = Erfolgreich!
|
||||
; Fuzzy
|
||||
pad.importExport.export = Dieses Pad exportieren
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Reiner Text
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDf
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Verbunden.
|
||||
pad.modals.reconnecting = Wiederherstellen der Verbindung...
|
||||
pad.modals.forcereconnect = Erneut Verbinden
|
||||
pad.modals.uderdup = In einem anderen Fenster geöffnet
|
||||
pad.modals.userdup.explanation = Dieses Pad scheint in mehr als einem Browser-Fenster auf diesem Computer geöffnet zu sein.
|
||||
pad.modals.userdup.advice = Um dieses Fenster zu benutzen, verbinden Sie bitte erneut.
|
||||
pad.modals.unauth = Nicht Authorisiert.
|
||||
pad.modals.unauth.explanation = Ihre Befugnisse auf dieses Pad zuzugreifen haben sich geädert. Versuchen Sie, erneut zu verbinden.
|
||||
pad.modals.looping = Verbindung unterbrochen.
|
||||
pad.modals.looping.explanation = Es gibt Probleme bei der Kommunikation mit dem Synchronisationsserver.
|
||||
pad.modals.looping.cause = Möglicherweise verläuft Ihre Verbindung durch eine inkompatible Firewall oder einen inkompatiblen Proxy.
|
||||
pad.modals.initsocketfail = Server nicht erreichbar.
|
||||
pad.modals.initsocketfail.explanation = Es konnte keine Verbindung zum Synchronisationsserver hergestellt werden.
|
||||
pad.modals.initsocketfail.cause = Dies könnte an Ihrem Browser oder Ihrer Internet-Verbindung liegen.
|
||||
pad.modals.slowcommit = Verbindung unterbrochen.
|
||||
pad.modals.slowcommit.explanation = Der Server reagiert nicht.
|
||||
pad.modals.slowcommit.cause = Dies könnte an Problemen mit Netzwerk-Konnektivität liegen. Möglicherweise ist der Server aber auch überlastet.
|
||||
pad.modals.deleted = Entfernt.
|
||||
pad.modals.deleted.explanation = Dieses Pad wurde entfernt.
|
||||
pad.modals.disconnected = Verbindung unterbrochen.
|
||||
pad.modals.disconnected.explanation = Die Verbindung zum Synchronisationsserver wurde unterbrochen.
|
||||
pad.modals.disconnected.cause = Möglicherweise ist der Server nicht erreichbar. Bitte benachrichtigen Sie uns, falls dies weiterhin passiert.
|
||||
pad.share = Dieses Pad teilen
|
||||
pad.share.readonly = Eingeschränkter zugriff (Nur lesen)
|
||||
pad.share.link = Link
|
||||
pad.share.emebdcode = In Webseite einbetten
|
||||
pad.chat = Chat
|
||||
pad.chat.title = Den Chat für dieses Pad öffnen
|
||||
timeslider.pageTitle = {{appTitle}} Pad-Geschichte
|
||||
timeslider.toolbar.returnbutton = Zurück zum Pad
|
||||
timeslider.toolbar.authors = Autoren:
|
||||
timeslider.toolbar.authorsList = keine Autoren
|
||||
timeslider.exportCurrent = Exportiere diese Version als:
|
77
src/locales/en.ini
Normal file
77
src/locales/en.ini
Normal file
|
@ -0,0 +1,77 @@
|
|||
[*]
|
||||
index.newPad = New Pad
|
||||
index.createOpenPad = or create/open a Pad with the name:
|
||||
pad.toolbar.bold.title = Bold (Ctrl-B)
|
||||
pad.toolbar.italic.title = Italic (Ctrl-I)
|
||||
pad.toolbar.underline.title = Underline (Ctrl-U)
|
||||
pad.toolbar.strikethrough.title = Strikethrough
|
||||
pad.toolbar.ol.title = Ordered list
|
||||
pad.toolbar.ul.title = Unordered List
|
||||
pad.toolbar.indent.title = Indent
|
||||
pad.toolbar.unindent.title = Outdent
|
||||
pad.toolbar.undo.title = Undo (Ctrl-Z)
|
||||
pad.toolbar.redo.title = Redo (Ctrl-Y)
|
||||
pad.toolbar.clearAuthorship.title = Clear Authorship Colors
|
||||
pad.toolbar.import_export.title = Import/Export from/to different file formats
|
||||
pad.toolbar.timeslider.title = Timeslider
|
||||
pad.toolbar.savedRevision.title = Saved Revisions
|
||||
pad.toolbar.settings.title = Settings
|
||||
pad.toolbar.embed.title = Embed this pad
|
||||
pad.toolbar.showusers.title = Show the users on this pad
|
||||
pad.colorpicker.save = Save
|
||||
pad.colorpicker.cancel = Cancel
|
||||
pad.loading = Loading...
|
||||
pad.settings.padSettings = Pad Settings
|
||||
pad.settings.myView = My View
|
||||
pad.settings.stickychat = Chat always on screen
|
||||
pad.settings.colorcheck = Authorship colors
|
||||
pad.settings.linenocheck = Line numbers
|
||||
pad.settings.fontType = Font type:
|
||||
pad.settings.fontType.normal = Normal
|
||||
pad.settings.fontType.monospaced = Monospace
|
||||
pad.settings.globalView = Global View
|
||||
pad.settings.language = Language:
|
||||
pad.importExport.import_export = Import/Export
|
||||
pad.importExport.import = Upload any text file or document
|
||||
pad.importExport.successful = Successful!
|
||||
pad.importExport.export = Export current pad as:
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Plain text
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDF
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Connected.
|
||||
pad.modals.reconnecting = Reconnecting to your pad..
|
||||
pad.modals.forcereconnect = Force reconnect
|
||||
pad.modals.uderdup = Opened in another window
|
||||
pad.modals.userdup.explanation = This pad seems to be opened in more than one browser window on this computer.
|
||||
pad.modals.userdup.advice = Reconnect to use this window instead.
|
||||
pad.modals.unauth = Not authorized
|
||||
pad.modals.unauth.explanation = Your permissions have changed while viewing this page. Try to reconnect.
|
||||
pad.modals.looping = Disconnected.
|
||||
pad.modals.looping.explanation = There are communication problems with the synchronization server.
|
||||
pad.modals.looping.cause = Perhaps you connected through an incompatible firewall or proxy.
|
||||
pad.modals.initsocketfail = Server is unreachable.
|
||||
pad.modals.initsocketfail.explanation = Couldn't connect to the synchronization server.
|
||||
pad.modals.initsocketfail.cause = This is probably due to a problem with your browser or your internet connection.
|
||||
pad.modals.slowcommit = Disconnected.
|
||||
pad.modals.slowcommit.explanation = The server is not responding.
|
||||
pad.modals.slowcommit.cause = This could be due to problems with network connectivity.
|
||||
pad.modals.deleted = Deleted.
|
||||
pad.modals.deleted.explanation = This pad has been removed.
|
||||
pad.modals.disconnected = You have been disconnected.
|
||||
pad.modals.disconnected.explanation = The connection to the server was lost
|
||||
pad.modals.disconnected.cause = The server may be unavailable. Please notify us if this continues to happen.
|
||||
pad.share = Share this pad
|
||||
pad.share.readonly = Read only
|
||||
pad.share.link = Link
|
||||
pad.share.emebdcode = Embed URL
|
||||
pad.chat = Chat
|
||||
pad.chat.title = Open the chat for this pad.
|
||||
|
||||
timeslider.pageTitle = {{appTitle}} Timeslider
|
||||
timeslider.toolbar.returnbutton = Return to pad
|
||||
timeslider.toolbar.authors = Authors:
|
||||
timeslider.toolbar.authorsList = No Authors
|
||||
timeslider.exportCurrent = Export current version as:
|
78
src/locales/es.ini
Normal file
78
src/locales/es.ini
Normal file
|
@ -0,0 +1,78 @@
|
|||
; Exported from translatewiki.net
|
||||
[es]
|
||||
index.newPad = Nuevo Pad
|
||||
index.createOpenPad = o puedes crear/abrir un Pad con el nombre:
|
||||
pad.toolbar.bold.title = Negrita (Ctrl-B)
|
||||
pad.toolbar.italic.title = Cursiva (Ctrl-I)
|
||||
pad.toolbar.underline.title = Subrayado (Ctrl-U)
|
||||
pad.toolbar.strikethrough.title = Tachado
|
||||
pad.toolbar.ol.title = Lista ordenada
|
||||
pad.toolbar.ul.title = Lista desordenada
|
||||
pad.toolbar.indent.title = Sangrar
|
||||
pad.toolbar.unindent.title = Desangrar
|
||||
pad.toolbar.undo.title = Deshacer (Ctrl-Z)
|
||||
pad.toolbar.redo.title = Rehacer (Ctrl-Y)
|
||||
pad.toolbar.clearAuthorship.title = Eliminar los colores de los autores
|
||||
pad.toolbar.import_export.title = Importar/Exportar a diferentes formatos de archivos
|
||||
pad.toolbar.timeslider.title = Línea de tiempo
|
||||
pad.toolbar.savedRevision.title = Revisiones guardadas
|
||||
pad.toolbar.settings.title = Configuración
|
||||
pad.toolbar.embed.title = Incrustar este pad
|
||||
pad.toolbar.showusers.title = Mostrar los usuarios de este pad
|
||||
pad.colorpicker.save = Guardar
|
||||
pad.colorpicker.cancel = Cancelar
|
||||
pad.loading = Cargando...
|
||||
pad.settings.padSettings = Configuración del Pad
|
||||
pad.settings.myView = Mi vista
|
||||
pad.settings.stickychat = Chat siempre encima
|
||||
pad.settings.colorcheck = Color de autoría
|
||||
pad.settings.linenocheck = Números de línea
|
||||
pad.settings.fontType = Tipografía:
|
||||
pad.settings.fontType.normal = Normal
|
||||
pad.settings.fontType.monospaced = Monoespacio
|
||||
pad.settings.globalView = Vista global
|
||||
pad.settings.language = Idioma:
|
||||
pad.importExport.import_export = Importar/Exportar
|
||||
pad.importExport.import = Subir cualquier texto o documento
|
||||
pad.importExport.successful = ¡Operación exitosa!
|
||||
; Fuzzy
|
||||
pad.importExport.export = Exporta el pad actual como
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Texto plano
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDF
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Conectado.
|
||||
pad.modals.reconnecting = Reconectando a tu pad..
|
||||
pad.modals.forcereconnect = Reconexión forzosa
|
||||
pad.modals.uderdup = Abrir en otra ventana
|
||||
pad.modals.userdup.explanation = Este pad parece estar abierto en más de una ventana de tu navegador.
|
||||
pad.modals.userdup.advice = Reconectar para usar esta ventana.
|
||||
pad.modals.unauth = No autorizado.
|
||||
pad.modals.unauth.explanation = Los permisos han cambiado mientras estabas viendo esta página. Intenta reconectar de nuevo.
|
||||
pad.modals.looping = Desconectado.
|
||||
pad.modals.looping.explanation = Estamos teniendo problemas con la sincronización en el servidor.
|
||||
pad.modals.looping.cause = Quizás su conexión fluya a través de un proxy o un cortafuegos incompatible.
|
||||
pad.modals.initsocketfail = Servidor incalcanzable.
|
||||
pad.modals.initsocketfail.explanation = No se pudo conectar al sevidor de sincronización.
|
||||
pad.modals.initsocketfail.cause = Puede ser a causa de tu navegador o de una caída en tu conexión de Internet.
|
||||
pad.modals.slowcommit = Desconectado.
|
||||
pad.modals.slowcommit.explanation = El servidor no responde.
|
||||
pad.modals.slowcommit.cause = Puede deberse a problemas con tu conexión de red.
|
||||
pad.modals.deleted = Borrado.
|
||||
pad.modals.deleted.explanation = Este pad ha sido borrado.
|
||||
pad.modals.disconnected = Has sido desconectado.
|
||||
pad.modals.disconnected.explanation = Se perdió la conexión con el servidor
|
||||
pad.modals.disconnected.cause = El servidor podría no estar disponible. Contacte con nosotros si esto continúa sucediendo.
|
||||
pad.share = Compatir el pad
|
||||
pad.share.readonly = Sólo lectura
|
||||
pad.share.link = Enlace
|
||||
pad.share.emebdcode = Incrustar URL
|
||||
pad.chat = Chat
|
||||
pad.chat.title = Abrir el chat para este pad.
|
||||
timeslider.pageTitle = {{appTitle}} Línea de tiempo
|
||||
timeslider.toolbar.returnbutton = Volver al pad
|
||||
timeslider.toolbar.authors = Autores:
|
||||
timeslider.toolbar.authorsList = Sin autores
|
||||
timeslider.exportCurrent = Exportar la versión actual como:
|
49
src/locales/fi.ini
Normal file
49
src/locales/fi.ini
Normal file
|
@ -0,0 +1,49 @@
|
|||
; Exported from translatewiki.net
|
||||
; Author: Nike
|
||||
[fi]
|
||||
index.newPad = Uusi muistio
|
||||
index.createOpenPad = tai avaa muistio nimellä:
|
||||
pad.toolbar.bold.title = Lihavointi (Ctrl-B)
|
||||
pad.toolbar.italic.title = Kursivointi (Ctrl-I)
|
||||
pad.toolbar.underline.title = Alleviivaus (Ctrl-U)
|
||||
pad.toolbar.strikethrough.title = Yliviivaus
|
||||
pad.toolbar.ol.title = Numeroitu lista
|
||||
pad.toolbar.ul.title = Numeroimaton lista
|
||||
pad.toolbar.indent.title = Sisennä
|
||||
pad.toolbar.unindent.title = Ulonna
|
||||
pad.toolbar.undo.title = Kumoa (Ctrl-Z)
|
||||
pad.toolbar.redo.title = Tee uudelleen (Ctrl-Y)
|
||||
pad.toolbar.clearAuthorship.title = Poista kirjoittavärit
|
||||
pad.toolbar.import_export.title = Tuo tai vie eri muotoihin
|
||||
pad.toolbar.savedRevision.title = Tallennetut versiot
|
||||
pad.toolbar.settings.title = Asetukset
|
||||
pad.toolbar.embed.title = Upota muistio
|
||||
pad.toolbar.showusers.title = Näytä muistion käyttäjät
|
||||
pad.colorpicker.save = Tallenna
|
||||
pad.colorpicker.cancel = Peru
|
||||
pad.loading = Ladataan…
|
||||
pad.settings.padSettings = Muistion asetukset
|
||||
pad.settings.myView = Oma näkymä
|
||||
pad.settings.stickychat = Keskustelu aina näkyvissä
|
||||
pad.settings.colorcheck = Kirjoittavärit
|
||||
pad.settings.linenocheck = Rivinumerot
|
||||
pad.settings.fontType = Kirjasintyyppi:
|
||||
pad.settings.fontType.normal = normaali
|
||||
pad.settings.fontType.monospaced = tasalevyinen
|
||||
pad.settings.language = Kieli:
|
||||
pad.importExport.import_export = Tuonti/vienti
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Muotoilematon teksti
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDF
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Yhdistetty.
|
||||
pad.modals.reconnecting = Herätellään yhteyttä muistioon...
|
||||
pad.modals.forcereconnect = Pakota uudelleenyhdistäminen
|
||||
pad.share = Jaa muistio
|
||||
pad.share.readonly = Vain luku
|
||||
pad.share.link = Linkki
|
||||
pad.share.emebdcode = Upotusosoite
|
||||
pad.chat = Keskustelu
|
||||
timeslider.toolbar.returnbutton = Palaa muistioon
|
78
src/locales/fr.ini
Normal file
78
src/locales/fr.ini
Normal file
|
@ -0,0 +1,78 @@
|
|||
; Exported from translatewiki.net
|
||||
[fr]
|
||||
index.newPad = Nouveau Pad
|
||||
index.createOpenPad = ou créer/ouvrir un Pad intitulé
|
||||
pad.toolbar.bold.title = Gras (Ctrl-B)
|
||||
pad.toolbar.italic.title = Italique (Ctrl-I)
|
||||
pad.toolbar.underline.title = Souligner (Ctrl-U)
|
||||
pad.toolbar.strikethrough.title = Barrer
|
||||
pad.toolbar.ol.title = Liste ordonnée
|
||||
pad.toolbar.ul.title = Liste non-ordonnée
|
||||
pad.toolbar.indent.title = Indenter
|
||||
pad.toolbar.unindent.title = Désindenter
|
||||
pad.toolbar.undo.title = Annuler (Ctrl-Z)
|
||||
pad.toolbar.redo.title = Rétablir (Ctrl-Y)
|
||||
pad.toolbar.clearAuthorship.title = Effacer les couleurs identifiant les auteurs
|
||||
pad.toolbar.import_export.title = Importer/Exporter de/vers un format de fichier différent
|
||||
pad.toolbar.timeslider.title = Navigateur d'historique
|
||||
pad.toolbar.savedRevision.title = Versions enregistrées
|
||||
pad.toolbar.settings.title = Paramètres
|
||||
pad.toolbar.embed.title = Intégrer ce Pad
|
||||
pad.toolbar.showusers.title = Afficher les utilisateurs du Pad
|
||||
pad.colorpicker.save = Sauver
|
||||
pad.colorpicker.cancel = Annuler
|
||||
pad.loading = Chargement...
|
||||
pad.settings.padSettings = Paramètres du Pad
|
||||
pad.settings.myView = Ma vue
|
||||
pad.settings.stickychat = Messagerie toujours affichée
|
||||
pad.settings.colorcheck = Couleurs d'identification
|
||||
pad.settings.linenocheck = Numéros des lignes
|
||||
pad.settings.fontType = Type de police:
|
||||
pad.settings.fontType.normal = Normal
|
||||
pad.settings.fontType.monospaced = Monospace
|
||||
pad.settings.globalView = Vue d'ensemble
|
||||
pad.settings.language = Langue:
|
||||
pad.importExport.import_export = Importer/Exporter
|
||||
pad.importExport.import = Charger un texte ou un document
|
||||
pad.importExport.successful = Traitement effectué!
|
||||
; Fuzzy
|
||||
pad.importExport.export = Exporter ce Pad vers
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Texte brut
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDf
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Connecté.
|
||||
pad.modals.reconnecting = Reconnexion vers votre Pad...
|
||||
pad.modals.forcereconnect = Forcer la reconnexion.
|
||||
pad.modals.uderdup = Ouvrir dans une nouvelle fenêtre
|
||||
pad.modals.userdup.explanation = Ce Pad semble avoir été ouvert dans plusieurs fenêtres de votre fureteur sur cet ordinateur.
|
||||
pad.modals.userdup.advice = Se reconnecter en utilisant cette fenêtre.
|
||||
pad.modals.unauth = Not authorized Non authorisé
|
||||
pad.modals.unauth.explanation = Vos permissions ont été changées lors de la visualisation de cette page. Essayer de vous reconnecter.
|
||||
pad.modals.looping = Disconnected. Déconnecté.
|
||||
pad.modals.looping.explanation = Nous éprouvons un problème de communication au serveur de synchronisation.
|
||||
pad.modals.looping.cause = Il est possible que leur connection soit protégée par un pare-feu incompatible ou un serveur proxy incompatible.
|
||||
pad.modals.initsocketfail = Le serveur est introuvable.
|
||||
pad.modals.initsocketfail.explanation = Impossible de se connecter au serveur de synchronisation.
|
||||
pad.modals.initsocketfail.cause = La cause de ce problème peut être liée à votre fureteur web.
|
||||
pad.modals.slowcommit = Disconnected. Déconnecté
|
||||
pad.modals.slowcommit.explanation = Le serveur ne répond pas.
|
||||
pad.modals.slowcommit.cause = La cause de ce problème peut être liée à une erreur de connectivité du réseau.
|
||||
pad.modals.deleted = Supprimé.
|
||||
pad.modals.deleted.explanation = Ce Pad a été supprimé.
|
||||
pad.modals.disconnected = Vous avez été déconnecté.
|
||||
pad.modals.disconnected.explanation = La connexion au serveur a échoué.
|
||||
pad.modals.disconnected.cause = Ce serveur est possiblement hors-ligne. Veuillez nous joindre si le problème persiste.
|
||||
pad.share = Partager ce Pad
|
||||
pad.share.readonly = Lecture seule
|
||||
pad.share.link = Lien
|
||||
pad.share.emebdcode = Lien à intégrer
|
||||
pad.chat = Messagerie
|
||||
pad.chat.title = Ouvrir la messagerie liée au Pad.
|
||||
timeslider.pageTitle = {{appTitle}} Curseur temporel
|
||||
timeslider.toolbar.returnbutton = Retour à ce Pad.
|
||||
timeslider.toolbar.authors = Auteurs:
|
||||
timeslider.toolbar.authorsList = Aucun auteurs
|
||||
timeslider.exportCurrent = Exporter version actuelle vers:
|
79
src/locales/nl.ini
Normal file
79
src/locales/nl.ini
Normal file
|
@ -0,0 +1,79 @@
|
|||
; Exported from translatewiki.net
|
||||
; Author: Siebrand
|
||||
[nl]
|
||||
index.newPad = Nieuw pad
|
||||
index.createOpenPad = Maak of open pad met de naam:
|
||||
pad.toolbar.bold.title = Vet (Ctrl-B)
|
||||
pad.toolbar.italic.title = Cursief (Ctrl-I)
|
||||
pad.toolbar.underline.title = Onderstrepen (Ctrl-U)
|
||||
pad.toolbar.strikethrough.title = Doorhalen
|
||||
pad.toolbar.ol.title = Geordende lijst
|
||||
pad.toolbar.ul.title = Ongeordende lijst
|
||||
pad.toolbar.indent.title = Inspringen
|
||||
pad.toolbar.unindent.title = Inspringing verkleinen
|
||||
pad.toolbar.undo.title = Ongedaan maken (Ctrl-Z)
|
||||
pad.toolbar.redo.title = Opnieuw uitvoeren (Ctrl-Y)
|
||||
pad.toolbar.clearAuthorship.title = Kleuren auteurs wissen
|
||||
pad.toolbar.import_export.title = Naar/van andere opmaak exporteren/importeren
|
||||
pad.toolbar.timeslider.title = Tijdlijn
|
||||
pad.toolbar.savedRevision.title = Opgeslagen versies
|
||||
pad.toolbar.settings.title = Instellingen
|
||||
pad.toolbar.embed.title = Pad insluiten
|
||||
pad.toolbar.showusers.title = Gebruikers van dit pad weergeven
|
||||
pad.colorpicker.save = Opslaan
|
||||
pad.colorpicker.cancel = Annuleren
|
||||
pad.loading = Bezig met laden…
|
||||
pad.settings.padSettings = Padinstellingen
|
||||
pad.settings.myView = Mijn overzicht
|
||||
pad.settings.stickychat = Chat altijd zichtbaar
|
||||
pad.settings.colorcheck = Kleuren auteurs
|
||||
pad.settings.linenocheck = Regelnummers
|
||||
pad.settings.fontType = Lettertype:
|
||||
pad.settings.fontType.normal = Normaal
|
||||
pad.settings.fontType.monospaced = Monospace
|
||||
pad.settings.globalView = Globaal overzicht
|
||||
pad.settings.language = Taal:
|
||||
pad.importExport.import_export = Importeren/exporteren
|
||||
pad.importExport.import = Upload een tekstbestand of document
|
||||
pad.importExport.successful = Afgerond
|
||||
; Fuzzy
|
||||
pad.importExport.export = Huidige pad exporteren als
|
||||
pad.importExport.exporthtml = HTML
|
||||
pad.importExport.exportplain = Tekst zonder opmaak
|
||||
pad.importExport.exportword = Microsoft Word
|
||||
pad.importExport.exportpdf = PDF
|
||||
pad.importExport.exportopen = ODF (Open Document Format)
|
||||
pad.importExport.exportdokuwiki = DokuWiki
|
||||
pad.modals.connected = Verbonden.
|
||||
pad.modals.reconnecting = Opnieuw verbinding maken met uw pad...
|
||||
pad.modals.forcereconnect = Opnieuw verbinden
|
||||
pad.modals.uderdup = Openen in ander venster
|
||||
pad.modals.userdup.explanation = Dit pad is meer dan één keer geopend in een browservenster op deze computer.
|
||||
pad.modals.userdup.advice = Opnieuw verbinden en dit venster gebruiken.
|
||||
pad.modals.unauth = Niet toegestaan
|
||||
pad.modals.unauth.explanation = Uw rechten zijn gewijzigd terwijl u de pagina aan het bekijken was. Probeer opnieuw te verbinden.
|
||||
pad.modals.looping = Verbinding verbroken.
|
||||
pad.modals.looping.explanation = Er is een probleem opgetreden tijdens de communicatie met de synchronisatieserver.
|
||||
pad.modals.looping.cause = Mogelijk gebruikt de server een niet compatibele firewall of proxy server.
|
||||
pad.modals.initsocketfail = Server is niet bereikbaar.
|
||||
pad.modals.initsocketfail.explanation = Het was niet mogelijk te verbinden met de synchronisatieserver.
|
||||
pad.modals.initsocketfail.cause = Mogelijk komt dit door uw browser of internetverbinding.
|
||||
pad.modals.slowcommit = Verbinding verbroken.
|
||||
pad.modals.slowcommit.explanation = De server reageert niet.
|
||||
pad.modals.slowcommit.cause = Dit komt mogelijk door netwerkproblemen.
|
||||
pad.modals.deleted = Verwijderd.
|
||||
pad.modals.deleted.explanation = Dit pad is verwijderd.
|
||||
pad.modals.disconnected = Uw verbinding is verbroken.
|
||||
pad.modals.disconnected.explanation = De verbinding met de server is verbroken
|
||||
pad.modals.disconnected.cause = De server is mogelijk niet beschikbaar. Stel alstublieft de beheerder op de hoogte.
|
||||
pad.share = Pad delen
|
||||
pad.share.readonly = Alleen-lezen
|
||||
pad.share.link = Verwijzing
|
||||
pad.share.emebdcode = URL insluiten
|
||||
pad.chat = Chatten
|
||||
pad.chat.title = Chat voor dit pad opnenen
|
||||
timeslider.pageTitle = Tijdlijn voor {{appTitle}}
|
||||
timeslider.toolbar.returnbutton = Terug naar pad
|
||||
timeslider.toolbar.authors = Auteurs:
|
||||
timeslider.toolbar.authorsList = Geen auteurs
|
||||
timeslider.exportCurrent = Huidige versie exporteren als:
|
|
@ -619,7 +619,7 @@ exports.updatePadClients = function(pad, callback)
|
|||
//https://github.com/caolan/async#whilst
|
||||
//send them all new changesets
|
||||
async.whilst(
|
||||
function (){ return sessioninfos[session].rev < pad.getHeadRevisionNumber()},
|
||||
function (){ return sessioninfos[session] && sessioninfos[session].rev < pad.getHeadRevisionNumber()},
|
||||
function(callback)
|
||||
{
|
||||
var author, revChangeset, currentTime;
|
||||
|
|
47
src/node/hooks/express/tests.js
Normal file
47
src/node/hooks/express/tests.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
var path = require("path")
|
||||
, npm = require("npm")
|
||||
, fs = require("fs");
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/tests/frontend/specs_list.js', function(req, res){
|
||||
fs.readdir('tests/frontend/specs', function(err, files){
|
||||
if(err){ return res.send(500); }
|
||||
|
||||
res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
|
||||
});
|
||||
});
|
||||
|
||||
var url2FilePath = function(url){
|
||||
var subPath = url.substr("/tests/frontend".length);
|
||||
if (subPath == ""){
|
||||
subPath = "index.html"
|
||||
}
|
||||
subPath = subPath.split("?")[0];
|
||||
|
||||
var filePath = path.normalize(npm.root + "/../tests/frontend/")
|
||||
filePath += subPath.replace("..", "");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
args.app.get('/tests/frontend/specs/*', function (req, res) {
|
||||
var specFilePath = url2FilePath(req.url);
|
||||
var specFileName = path.basename(specFilePath);
|
||||
|
||||
fs.readFile(specFilePath, function(err, content){
|
||||
if(err){ return res.send(500); }
|
||||
|
||||
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
|
||||
|
||||
res.send(content);
|
||||
});
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend/*', function (req, res) {
|
||||
var filePath = url2FilePath(req.url);
|
||||
res.sendfile(filePath);
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend', function (req, res) {
|
||||
res.redirect('/tests/frontend/');
|
||||
});
|
||||
}
|
35
src/node/hooks/i18n.js
Normal file
35
src/node/hooks/i18n.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
var languages = require('languages')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, express = require('express')
|
||||
|
||||
var localesPath = __dirname+"/../../locales";
|
||||
|
||||
// Serve English strings directly with /locales.ini
|
||||
var localeIndex = fs.readFileSync(localesPath+'/en.ini')+'\r\n';
|
||||
|
||||
// add language base 'en' to availableLangs
|
||||
exports.availableLangs = {en: languages.getLanguageInfo('en')}
|
||||
|
||||
fs.readdir(localesPath, function(er, files) {
|
||||
files.forEach(function(locale) {
|
||||
locale = locale.split('.')[0]
|
||||
if(locale.toLowerCase() == 'en') return;
|
||||
|
||||
// build locale index
|
||||
localeIndex += '['+locale+']\r\n@import url(locales/'+locale+'.ini)\r\n'
|
||||
|
||||
// add info language {name, nativeName, direction} to availableLangs
|
||||
exports.availableLangs[locale]=languages.getLanguageInfo(locale);
|
||||
})
|
||||
})
|
||||
|
||||
exports.expressCreateServer = function(n, args) {
|
||||
|
||||
args.app.use('/locales', express.static(localesPath));
|
||||
|
||||
args.app.get('/locales.ini', function(req, res) {
|
||||
res.send(localeIndex);
|
||||
})
|
||||
|
||||
}
|
|
@ -7,7 +7,7 @@ module.exports = function (req, res, callback) {
|
|||
// FIXME: Why is this ever undefined??
|
||||
if (req.cookies === undefined) req.cookies = {};
|
||||
|
||||
securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj) {
|
||||
securityManager.checkAccess(req.params.pad, req.cookies.sessionID, req.cookies.token, req.cookies.password, function(err, accessObj) {
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
//there is access, continue
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"resolve" : "0.2.x",
|
||||
"socket.io" : "0.9.x",
|
||||
"ueberDB" : "0.1.8",
|
||||
"async" : "0.1.22",
|
||||
"async" : "0.1.x",
|
||||
"express" : "3.x",
|
||||
"connect" : "2.4.x",
|
||||
"clean-css" : "0.3.2",
|
||||
|
@ -35,14 +35,16 @@
|
|||
"security" : "1.0.0",
|
||||
"tinycon" : "0.0.1",
|
||||
"underscore" : "1.3.1",
|
||||
"unorm" : "1.0.0"
|
||||
"unorm" : "1.0.0",
|
||||
"languages" : "0.1.1"
|
||||
},
|
||||
"bin": { "etherpad-lite": "./node/server.js" },
|
||||
"devDependencies": {
|
||||
"jshint" : "*"
|
||||
"jshint" : "*",
|
||||
"wd" : "0.0.26"
|
||||
},
|
||||
"engines" : { "node" : ">=0.6.0",
|
||||
"npm" : ">=1.0"
|
||||
},
|
||||
"version" : "1.1.4"
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ body.doesWrap {
|
|||
.sidedivdelayed { /* class set after sizes are set */
|
||||
background-color: #eee;
|
||||
color: #888 !important;
|
||||
border-right: 1px solid #999;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
.sidedivhidden {
|
||||
display: none;
|
||||
|
|
|
@ -748,6 +748,15 @@ input[type=checkbox] {
|
|||
.popup p {
|
||||
margin: 5px 0
|
||||
}
|
||||
.popup select {
|
||||
background: #fff;
|
||||
padding: 2px;
|
||||
height: 24px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ccc;
|
||||
outline: none;
|
||||
min-width: 105px;
|
||||
}
|
||||
.column {
|
||||
float: left;
|
||||
width: 50%;
|
||||
|
|
|
@ -150,7 +150,7 @@ var chat = (function()
|
|||
$("#chatinput").keypress(function(evt)
|
||||
{
|
||||
//if the user typed enter, fire the send
|
||||
if(evt.which == 13)
|
||||
if(evt.which == 13 || evt.which == 10)
|
||||
{
|
||||
evt.preventDefault();
|
||||
self.send();
|
||||
|
|
1028
src/static/js/l10n.js
Normal file
1028
src/static/js/l10n.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -111,6 +111,7 @@ function getParams()
|
|||
var IsnoColors = params["noColors"];
|
||||
var rtl = params["rtl"];
|
||||
var alwaysShowChat = params["alwaysShowChat"];
|
||||
var lang = params["lang"];
|
||||
|
||||
if(IsnoColors)
|
||||
{
|
||||
|
@ -173,6 +174,13 @@ function getParams()
|
|||
chat.stickToScreen();
|
||||
}
|
||||
}
|
||||
if(lang)
|
||||
{
|
||||
if(lang !== "")
|
||||
{
|
||||
document.webL10n.setLanguage(lang);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUrlVars()
|
||||
|
@ -389,6 +397,10 @@ function handshake()
|
|||
});
|
||||
// Bind the colorpicker
|
||||
var fb = $('#colorpicker').farbtastic({ callback: '#mycolorpickerpreview', width: 220});
|
||||
// Bind the read only button
|
||||
$('#readonlyinput').on('click',function(){
|
||||
padeditbar.setEmbedLinks();
|
||||
});
|
||||
}
|
||||
|
||||
var pad = {
|
||||
|
@ -447,6 +459,7 @@ var pad = {
|
|||
{
|
||||
pad.collabClient.sendClientMessage(msg);
|
||||
},
|
||||
createCookie: createCookie,
|
||||
|
||||
init: function()
|
||||
{
|
||||
|
|
|
@ -75,6 +75,11 @@ var padeditor = (function()
|
|||
{
|
||||
pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace');
|
||||
});
|
||||
$("#languagemenu").val(document.webL10n.getLanguage());
|
||||
$("#languagemenu").change(function() {
|
||||
pad.createCookie("language",$("#languagemenu").val(),null,'/');
|
||||
document.webL10n.setLanguage($("#languagemenu").val());
|
||||
});
|
||||
},
|
||||
setViewOptions: function(newOptions)
|
||||
{
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
|
||||
|
||||
<h1>Etherpad Lite Settings</h1>
|
||||
<a href='https://github.com/Pita/etherpad-lite/wiki/Example-Production-Settings.JSON'>Example production settings template</a>
|
||||
<a href='https://github.com/Pita/etherpad-lite/wiki/Example-Development-Settings.JSON'>Example development settings template</a>
|
||||
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON'>Example production settings template</a>
|
||||
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON'>Example development settings template</a>
|
||||
<textarea class="settings"></textarea>
|
||||
<input type="button" class="settingsButton" id="saveSettings" value="Save Settings">
|
||||
<input type="button" class="settingsButton" id="restartEtherpad" value="Restart Etherpad">
|
||||
|
|
|
@ -31,8 +31,17 @@
|
|||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
|
||||
|
||||
<link rel="resource" type="application/l10n" href="locales.ini" />
|
||||
<link rel="shortcut icon" href="<%=settings.favicon%>">
|
||||
|
||||
<script type="text/javascript">
|
||||
(function(document) {
|
||||
// Set language for l10n
|
||||
var language = document.cookie.match(/language=(\w{2})/);
|
||||
if(language) document.documentElement.lang = language[1];
|
||||
})(document)
|
||||
</script>
|
||||
<script type="text/javascript" src="static/js/l10n.js" async></script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
|
@ -148,8 +157,8 @@
|
|||
|
||||
<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>
|
||||
<div id="button" onclick="go2Random()" data-l10n-id="index.newPad"></div>
|
||||
<div id="label" data-l10n-id="index.createOpenPad"></div>
|
||||
<form action="#" onsubmit="go2Name();return false;">
|
||||
<input type="text" id="padname" autofocus x-webkit-speech>
|
||||
<button type="submit">OK</button>
|
||||
|
@ -187,5 +196,4 @@
|
|||
// start the custom js
|
||||
if (typeof customStart == "function") customStart();
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<%
|
||||
var settings = require("ep_etherpad-lite/node/utils/Settings");
|
||||
var settings = require("ep_etherpad-lite/node/utils/Settings")
|
||||
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
|
||||
%>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
@ -31,9 +32,18 @@
|
|||
<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">
|
||||
|
||||
<link rel="shortcut icon" href="<%=settings.favicon%>">
|
||||
|
||||
<link rel="resource" type="application/l10n" href="../locales.ini" />
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
// Set language for l10n
|
||||
var language = document.cookie.match(/language=(\w{2})/);
|
||||
if(language) document.documentElement.lang = language[1];
|
||||
})();
|
||||
</script>
|
||||
<script type="text/javascript" src="../static/js/l10n.js" async></script>
|
||||
|
||||
<% e.begin_block("styles"); %>
|
||||
<link href="../static/css/pad.css" rel="stylesheet">
|
||||
<link href="../static/custom/pad.css" rel="stylesheet">
|
||||
|
@ -50,60 +60,60 @@
|
|||
<ul class="menu_left">
|
||||
<% e.begin_block("editbarMenuLeft"); %>
|
||||
<li class="acl-write" id="bold" data-key="bold">
|
||||
<a class="grouped-left" title="Bold (ctrl-B)">
|
||||
<a class="grouped-left" data-l10n-id="pad.toolbar.bold">
|
||||
<span class="buttonicon buttonicon-bold"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="italic" data-key="italic">
|
||||
<a class="grouped-middle" title="Italics (ctrl-I)">
|
||||
<a class="grouped-middle" data-l10n-id="pad.toolbar.italic">
|
||||
<span class="buttonicon buttonicon-italic"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="underline" data-key="underline">
|
||||
<a class="grouped-middle" title="Underline (ctrl-U)">
|
||||
<a class="grouped-middle" data-l10n-id="pad.toolbar.underline">
|
||||
<span class="buttonicon buttonicon-underline"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="strikethrough" data-key="strikethrough">
|
||||
<a class="grouped-right" title="Strikethrough">
|
||||
<a class="grouped-right" data-l10n-id="pad.toolbar.strikethrough">
|
||||
<span class="buttonicon buttonicon-strikethrough"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="oderedlist" data-key="insertorderedlist">
|
||||
<a class="grouped-left" title="Toggle Ordered List">
|
||||
<a class="grouped-left" data-l10n-id="pad.toolbar.ol">
|
||||
<span class="buttonicon buttonicon-insertorderedlist"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="unoderedlist" data-key="insertunorderedlist">
|
||||
<a class="grouped-middle" title="Toggle Bullet List">
|
||||
<a class="grouped-middle" data-l10n-id="pad.toolbar.ul">
|
||||
<span class="buttonicon buttonicon-insertunorderedlist"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="indent" data-key="indent">
|
||||
<a class="grouped-middle" title="Indent">
|
||||
<a class="grouped-middle" data-l10n-id="pad.toolbar.indent">
|
||||
<span class="buttonicon buttonicon-indent"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="outdent" data-key="outdent">
|
||||
<a class="grouped-right" title="Unindent">
|
||||
<a class="grouped-right" data-l10n-id="pad.toolbar.unindent">
|
||||
<span class="buttonicon buttonicon-outdent"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="undo" data-key="undo">
|
||||
<a class="grouped-left" title="Undo (ctrl-Z)">
|
||||
<a class="grouped-left" data-l10n-id="pad.toolbar.undo">
|
||||
<span class="buttonicon buttonicon-undo"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" id="redo" data-key="redo">
|
||||
<a class="grouped-right" title="Redo (ctrl-Y)">
|
||||
<a class="grouped-right" data-l10n-id="pad.toolbar.redo">
|
||||
<span class="buttonicon buttonicon-redo"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" id="clearAuthorship" data-key="clearauthorship">
|
||||
<a title="Clear Authorship Colors">
|
||||
<a data-l10n-id="pad.toolbar.clearAuthorship">
|
||||
<span class="buttonicon buttonicon-clearauthorship"></span>
|
||||
</a>
|
||||
</li>
|
||||
|
@ -112,34 +122,34 @@
|
|||
<ul class="menu_right">
|
||||
<% e.begin_block("editbarMenuRight"); %>
|
||||
<li data-key="import_export">
|
||||
<a class="grouped-left" id="importexportlink" title="Import/Export from/to different document formats">
|
||||
<a class="grouped-left" id="importexportlink" data-l10n-id="pad.toolbar.import_export">
|
||||
<span class="buttonicon buttonicon-import_export"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li onClick="document.location = document.location.pathname+ '/timeslider'">
|
||||
<a id="timesliderlink" class="grouped-middle" title="Show the history of this pad">
|
||||
<a id="timesliderlink" class="grouped-middle" data-l10n-id="pad.toolbar.timeslider">
|
||||
<span class="buttonicon buttonicon-history"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write" data-key="savedRevision">
|
||||
<a class="grouped-right" id="revisionlink" title="Mark this revision as a saved revision">
|
||||
<a class="grouped-right" id="revisionlink" data-l10n-id="pad.toolbar.savedRevision">
|
||||
<span class="buttonicon buttonicon-savedRevision"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="acl-write separator"></li>
|
||||
<li class="acl-write" data-key="settings">
|
||||
<a class="grouped-left" id="settingslink" title="Settings of this pad">
|
||||
<a class="grouped-left" id="settingslink" data-l10n-id="pad.toolbar.settings">
|
||||
<span class="buttonicon buttonicon-settings"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li data-key="embed">
|
||||
<a class="grouped-right" id="embedlink" title="Share and Embed this pad">
|
||||
<a class="grouped-right" id="embedlink" data-l10n-id="pad.toolbar.embed">
|
||||
<span class="grouped-right buttonicon buttonicon-embed"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="separator"></li>
|
||||
<li id="usericon" data-key="showusers">
|
||||
<a title="Show connected users">
|
||||
<a data-l10n-id="pad.toolbar.showusers">
|
||||
<span class="buttonicon buttonicon-showusers"></span>
|
||||
<span id="online_count">1</span>
|
||||
</a>
|
||||
|
@ -153,8 +163,8 @@
|
|||
<div id="myuser">
|
||||
<div id="mycolorpicker">
|
||||
<div id="colorpicker"></div>
|
||||
<button id="mycolorpickersave">Save</button>
|
||||
<button id="mycolorpickercancel">Cancel</button>
|
||||
<button id="mycolorpickersave" data-l10n-id="pad.colorpicker.save"></button>
|
||||
<button id="mycolorpickercancel" data-l10n-id="pad.colorpicker.cancel"></button>
|
||||
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
|
||||
</div>
|
||||
<div id="myswatchbox"><div id="myswatch"></div></div>
|
||||
|
@ -174,56 +184,76 @@
|
|||
<div id="editorcontainerbox">
|
||||
<div id="editorcontainer"></div>
|
||||
<div id="editorloadingbox">
|
||||
<p>Loading...</p>
|
||||
<p data-l10n-id="pad.loading">Loading...</p>
|
||||
<noscript><strong>Sorry, you have to enable Javascript in order to use this.</strong></noscript>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settings" class="popup">
|
||||
<h1>Pad settings</h1>
|
||||
<h1 data-l10n-id="pad.settings.padSettings"></h1>
|
||||
<div class="column">
|
||||
<% e.begin_block("mySettings"); %>
|
||||
<h2>My view</h2>
|
||||
<h2 data-l10n-id="pad.settings.myView"></h2>
|
||||
<p>
|
||||
<input type="checkbox" id="options-stickychat" onClick="chat.stickToScreen();">
|
||||
<label for="options-stickychat">Chat always on screen</label>
|
||||
<label for="options-stickychat" data-l10n-id="pad.settings.stickychat"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-colorscheck">
|
||||
<label for="options-colorscheck">Authorship colors</label>
|
||||
<label for="options-colorscheck" data-l10n-id="pad.settings.colorcheck"></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>
|
||||
<label for="options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
|
||||
</p>
|
||||
<% e.end_block(); %>
|
||||
<table>
|
||||
<% e.begin_block("mySettings.dropdowns"); %>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label>
|
||||
</td>
|
||||
<td>
|
||||
<select id="viewfontmenu">
|
||||
<option value="normal" data-l10n-id="pad.settings.fontType.normal"></option>
|
||||
<option value="monospace" data-l10n-id="pad.settings.fontType.monospaced"></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="languagemenu" data-l10n-id="pad.settings.language">Language:</label>
|
||||
</td>
|
||||
<td>
|
||||
<select id="languagemenu">
|
||||
<% for (lang in langs) { %>
|
||||
<option value="<%=lang%>"><%=langs[lang].nativeName%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<% e.end_block(); %>
|
||||
</table>
|
||||
</div>
|
||||
<div class="column">
|
||||
<% e.begin_block("globalSettings"); %>
|
||||
<h2>Global view</h2>
|
||||
<h2 data-l10n-id="pad.settings.globalView"></h2>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="importexport" class="popup">
|
||||
<h1>Import/Export</h1>
|
||||
<h1 data-l10n-id="pad.importExport.import_export"></h1>
|
||||
<div class="column acl-write">
|
||||
<% e.begin_block("importColumn"); %>
|
||||
<h2>Upload any text file or document</h2><br>
|
||||
<h2 data-l10n-id="pad.importExport.import"></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="importmessage" id="importmessagesuccess" data-l10n-id="pad.importExport.successful"></div>
|
||||
<div class="importformdiv" id="importformsubmitdiv">
|
||||
<input type="hidden" name="padId" value="blpmaXT35R">
|
||||
<span class="nowrap">
|
||||
|
@ -236,14 +266,14 @@
|
|||
<% e.end_block(); %>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h2>Export current pad as</h2>
|
||||
<h2 data-l10n-id="pad.importExport.export"></h2>
|
||||
<% e.begin_block("exportColumn"); %>
|
||||
<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="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></div></a>
|
||||
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain" data-l10n-id="pad.importExport.exportplain"></div></a>
|
||||
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword" data-l10n-id="pad.importExport.exportword"></div></a>
|
||||
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></div></a>
|
||||
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen" data-l10n-id="pad.importExport.exportopen"></div></a>
|
||||
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki" data-l10n-id="pad.importExport.exportdokuwiki"></div></a>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -251,48 +281,48 @@
|
|||
<div id="connectivity" class="popup">
|
||||
<% e.begin_block("modals"); %>
|
||||
<div class="connected visible">
|
||||
<h2>Connected.</h2>
|
||||
<h2 data-l10n-id="pad.modals.connected"></h2>
|
||||
</div>
|
||||
<div class="reconnecting">
|
||||
<h1>Reestablishing connection...</h1>
|
||||
<h1 data-l10n-id="pad.modals.reconnecting"></h1>
|
||||
<p><img alt="" border="0" src="../static/img/connectingbar.gif" /></p>
|
||||
</div>
|
||||
<div class="userdup">
|
||||
<h1>Opened in another window.</h1>
|
||||
<h2>You seem to have opened this pad in another browser window.</h2>
|
||||
<p>If you'd like to use this window instead, you can reconnect.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.uderdup"></h1>
|
||||
<h2 data-l10n-id="pad.modals.userdup.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.connected.advice"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="unauth">
|
||||
<h1>No Authorization.</h1>
|
||||
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.unauth"></h1>
|
||||
<p data-l10n-id="pad.modals.unauth.explanation"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="looping">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>We're having trouble talking to the EtherPad lite synchronization server.</h2>
|
||||
<p>You may be connecting through an incompatible firewall or proxy server.</p>
|
||||
<h1 data-l10n-id="pad.modals.looping"></h1>
|
||||
<h2 data-l10n-id="pad.modals.looping.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.looping.cause"></p>
|
||||
</div>
|
||||
<div class="initsocketfail">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>We were unable to connect to the EtherPad lite synchronization server.</h2>
|
||||
<p>This may be due to an incompatibility with your web browser or internet connection.</p>
|
||||
<h1 data-l10n-id="pad.modals.initsocketfail"></h1>
|
||||
<h2 data-l10n-id="pad.modals.initsocketfail.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.initsocketfail.cause"></p>
|
||||
</div>
|
||||
<div class="slowcommit">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>Server not responding.</h2>
|
||||
<p>This may be due to network connectivity issues or high load on the server.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.slowcommit"></h1>
|
||||
<h2 data-l10n-id="pad.modals.slowcommit.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.slowcommit.cause"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="deleted">
|
||||
<h1>Disconnected.</h1>
|
||||
<p>This pad was deleted.</p>
|
||||
<h1 data-l10n-id="pad.modals.deleted"></h1>
|
||||
<p data-l10n-id="pad.modals.deleted.explanation"></p>
|
||||
</div>
|
||||
<div class="disconnected">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>Lost connection with the EtherPad lite synchronization server.</h2>
|
||||
<p>This may be due to a loss of network connectivity. If this continues to happen, please let us know</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.disconnected.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.disconnected.cause"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
|
||||
<input type="hidden" class="padId" name="padId">
|
||||
|
@ -305,17 +335,17 @@
|
|||
<div id="embed" class="popup">
|
||||
<% e.begin_block("embedPopup"); %>
|
||||
<div id="embedreadonly" class="right acl-write">
|
||||
<input type="checkbox" id="readonlyinput" onClick="padeditbar.setEmbedLinks();">
|
||||
<label for="readonlyinput">Read only</label>
|
||||
<input type="checkbox" id="readonlyinput">
|
||||
<label for="readonlyinput" data-l10n-id="pad.share.readonly"></label>
|
||||
</div>
|
||||
<h1>Share this pad</h1>
|
||||
<h1 data-l10n-id="pad.share"></h1>
|
||||
<div id="linkcode">
|
||||
<h2>Link</h2>
|
||||
<h2 data-l10n-id="pad.share.link"></h2>
|
||||
<input id="linkinput" type="text" value="">
|
||||
</div>
|
||||
<br>
|
||||
<div id="embedcode">
|
||||
<h2>Embed URL</h2>
|
||||
<h2 data-l10n-id="pad.share.emebdcode"></h2>
|
||||
<input id="embedinput" type="text" value="">
|
||||
</div>
|
||||
<% e.end_block(); %>
|
||||
|
@ -323,14 +353,14 @@
|
|||
|
||||
<div id="chatthrob"></div>
|
||||
|
||||
<div id="chaticon" title="Open the chat for this pad" onclick="chat.show();return false;">
|
||||
<span id="chatlabel">Chat</span>
|
||||
<div id="chaticon" data-l10n-id="pad.chat" onclick="chat.show();return false;">
|
||||
<span id="chatlabel" data-l10n-id="pad.chat"></span>
|
||||
<span class="buttonicon buttonicon-chat"></span>
|
||||
<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;">- </a></div>
|
||||
<div id="titlebar"><span id ="titlelabel" data-l10n-id="pad.chat"></span><a id="titlecross" onClick="chat.hide();return false;">- </a></div>
|
||||
<div id="chattext" class="authorColors"></div>
|
||||
<div id="chatinputbox">
|
||||
<form>
|
||||
|
@ -345,10 +375,9 @@
|
|||
|
||||
<% e.begin_block("scripts"); %>
|
||||
<script type="text/javascript">
|
||||
/* Display errors on page load to the user
|
||||
(Gets overridden by padutils.setupGlobalExceptionHandler)
|
||||
*/
|
||||
(function() {
|
||||
// Display errors on page load to the user
|
||||
// (Gets overridden by padutils.setupGlobalExceptionHandler)
|
||||
var originalHandler = window.onerror;
|
||||
window.onerror = function(msg, url, line) {
|
||||
var box = document.getElementById('editorloadingbox');
|
||||
|
@ -360,7 +389,7 @@
|
|||
};
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="../static/js/require-kernel.js"></script>
|
||||
<script type="text/javascript" src="../socket.io/socket.io.js"></script>
|
||||
|
||||
|
@ -374,8 +403,6 @@
|
|||
<script type="text/javascript">
|
||||
var clientVars = {};
|
||||
(function () {
|
||||
|
||||
|
||||
var pathComponents = location.pathname.split('/');
|
||||
|
||||
// Strip 'p' and the padname from the pathname and set as baseURL
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<%
|
||||
var settings = require("ep_etherpad-lite/node/utils/Settings");
|
||||
var settings = require("ep_etherpad-lite/node/utils/Settings")
|
||||
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
|
||||
%>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<title><%=settings.title%> Timeslider</title>
|
||||
<html>
|
||||
<title data-l10n-id="timeslider.pageTitle" data-l10n-args='{ "appTitle": "<%=settings.title%>" }'><%=settings.title%> Timeslider</title>
|
||||
<script>
|
||||
/*
|
||||
|@licstart The following is the entire license notice for the
|
||||
|
@ -31,6 +32,16 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="shortcut icon" href="<%=settings.favicon%>">
|
||||
<link rel="resource" type="application/l10n" href="../../locales.ini" />
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
// Set language for l10n
|
||||
var language = document.cookie.match(/language=(\w{2})/);
|
||||
if(language) document.documentElement.lang = language[1];
|
||||
})();
|
||||
</script>
|
||||
<script type="text/javascript" src="../../static/js/l10n.js" async></script>
|
||||
<link rel="stylesheet" href="../../static/css/pad.css">
|
||||
<link rel="stylesheet" href="../../static/css/timeslider.css">
|
||||
<link rel="stylesheet" href="../../static/custom/timeslider.css">
|
||||
|
@ -69,12 +80,12 @@
|
|||
<div class="editbarright toolbar" id="editbar">
|
||||
<ul>
|
||||
<li onClick="window.padeditbar.toolbarClick('import_export');return false;">
|
||||
<a id="exportlink" title="Export to different document formats">
|
||||
<a id="exportlink" data-l10n-id="pad.importExport.export">
|
||||
<div class="buttonicon buttonicon-import_export"></div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a id="returnbutton">Return to pad</a>
|
||||
<a id="returnbutton" data-l10n-id="timeslider.toolbar.returnbutton"></a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
@ -82,9 +93,8 @@
|
|||
<span id="revision_label"></span>
|
||||
<span id="revision_date"></span>
|
||||
</h1>
|
||||
<p>Authors:
|
||||
<span id="authorsList">
|
||||
<span>No Authors</span>
|
||||
<p data-l10n-id="timeslider.toolbar.authors">
|
||||
<span id="authorsList" data-l10n-id="timeslider.toolbar.authorsList"></span>
|
||||
</span> </p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -101,70 +111,69 @@
|
|||
</div><!-- /padpage -->
|
||||
|
||||
<div id="connectivity" class="popup">
|
||||
<% e.begin_block("modals"); %>
|
||||
<% e.begin_block("modals"); %>
|
||||
<div class="connected visible">
|
||||
<h2>Connected.</h2>
|
||||
<h2 data-l10n-id="pad.modals.connected"></h2>
|
||||
</div>
|
||||
<div class="reconnecting">
|
||||
<h1>Reestablishing connection...</h1>
|
||||
<h1 data-l10n-id="pad.modals.reconnecting"></h1>
|
||||
<p><img alt="" border="0" src="../../static/img/connectingbar.gif" /></p>
|
||||
</div>
|
||||
<div class="userdup">
|
||||
<h1>Opened in another window.</h1>
|
||||
<h2>You seem to have opened this pad in another browser window.</h2>
|
||||
<p>If you'd like to use this window instead, you can reconnect.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.uderdup"></h1>
|
||||
<h2 data-l10n-id="pad.modals.userdup.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.connected.advice"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="unauth">
|
||||
<h1>No Authorization.</h1>
|
||||
<p>Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.unauth"></h1>
|
||||
<p data-l10n-id="pad.modals.unauth.explanation"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="looping">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>We're having trouble talking to the EtherPad lite synchronization server.</h2>
|
||||
<p>You may be connecting through an incompatible firewall or proxy server.</p>
|
||||
<h1 data-l10n-id="pad.modals.looping"></h1>
|
||||
<h2 data-l10n-id="pad.modals.looping.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.looping.cause"></p>
|
||||
</div>
|
||||
<div class="initsocketfail">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>We were unable to connect to the EtherPad lite synchronization server.</h2>
|
||||
<p>This may be due to an incompatibility with your web browser or internet connection.</p>
|
||||
<h1 data-l10n-id="pad.modals.initsocketfail"></h1>
|
||||
<h2 data-l10n-id="pad.modals.initsocketfail.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.initsocketfail.cause"></p>
|
||||
</div>
|
||||
<div class="slowcommit">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>Server not responding.</h2>
|
||||
<p>This may be due to network connectivity issues or high load on the server.</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.slowcommit"></h1>
|
||||
<h2 data-l10n-id="pad.modals.slowcommit.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.slowcommit.cause"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="deleted">
|
||||
<h1>Disconnected.</h1>
|
||||
<p>This pad was deleted.</p>
|
||||
<h1 data-l10n-id="pad.modals.deleted"></h1>
|
||||
<p data-l10n-id="pad.modals.deleted.explanation"></p>
|
||||
</div>
|
||||
<div class="disconnected">
|
||||
<h1>Disconnected.</h1>
|
||||
<h2>Lost connection with the EtherPad lite synchronization server.</h2>
|
||||
<p>This may be due to a loss of network connectivity. If this continues to happen, please let us know</p>
|
||||
<button id="forcereconnect">Reconnect Now</button>
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.disconnected.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.disconnected.cause"></p>
|
||||
<button id="forcereconnect" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</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>
|
||||
<% e.end_block(); %>
|
||||
<% e.end_block(); %>
|
||||
</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>
|
||||
<div id="export" class="popup" data-l10n-id="timeslider.exportCurrent">
|
||||
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></div></a>
|
||||
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain" data-l10n-id="pad.importExport.exportplain"></div></a>
|
||||
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword" data-l10n-id="pad.importExport.exportword"></div></a>
|
||||
<a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></div></a>
|
||||
<a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen" data-l10n-id="pad.importExport.exportopen"></div></a>
|
||||
<a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki" data-l10n-id="pad.importExport.exportdokuwiki"></div></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -182,7 +191,6 @@
|
|||
var clientVars = {};
|
||||
|
||||
(function () {
|
||||
|
||||
var pathComponents = location.pathname.split('/');
|
||||
|
||||
// Strip 'p', the padname and 'timeslider' from the pathname and set as baseURL
|
||||
|
|
159
tests/frontend/helper.js
Normal file
159
tests/frontend/helper.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
var helper = {};
|
||||
|
||||
(function(){
|
||||
var $iframeContainer, $iframe, jsLibraries = {};
|
||||
|
||||
helper.init = function(cb){
|
||||
$iframeContainer = $("#iframe-container");
|
||||
|
||||
$.get('/static/js/jquery.js').done(function(code){
|
||||
// make sure we don't override existing jquery
|
||||
jsLibraries["jquery"] = "if(typeof $ === 'undefined') {\n" + code + "\n}";
|
||||
|
||||
$.get('/tests/frontend/lib/sendkeys.js').done(function(code){
|
||||
jsLibraries["sendkeys"] = code;
|
||||
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
helper.randomString = 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;
|
||||
}
|
||||
|
||||
var getFrameJQuery = function($iframe){
|
||||
/*
|
||||
I tried over 9000 ways to inject javascript into iframes.
|
||||
This is the only way I found that worked in IE 7+8+9, FF and Chrome
|
||||
*/
|
||||
|
||||
var win = $iframe[0].contentWindow;
|
||||
var doc = win.document;
|
||||
|
||||
//IE 8+9 Hack to make eval appear
|
||||
//http://stackoverflow.com/questions/2720444/why-does-this-window-object-not-have-the-eval-function
|
||||
win.execScript && win.execScript("null");
|
||||
|
||||
win.eval(jsLibraries["jquery"]);
|
||||
win.eval(jsLibraries["sendkeys"]);
|
||||
|
||||
win.$.window = win;
|
||||
win.$.document = doc;
|
||||
|
||||
return win.$;
|
||||
}
|
||||
|
||||
helper.clearCookies = function(){
|
||||
window.document.cookie = "";
|
||||
}
|
||||
|
||||
helper.newPad = function(){
|
||||
//build opts object
|
||||
var opts = {clearCookies: true}
|
||||
if(typeof arguments[0] === 'function'){
|
||||
opts.cb = arguments[0]
|
||||
} else {
|
||||
opts = _.defaults(arguments[0], opts);
|
||||
}
|
||||
|
||||
//clear cookies
|
||||
if(opts.clearCookies){
|
||||
helper.clearCookies();
|
||||
}
|
||||
|
||||
var padName = "FRONTEND_TEST_" + helper.randomString(20);
|
||||
$iframe = $("<iframe src='/p/" + padName + "'></iframe>");
|
||||
|
||||
//clean up inner iframe references
|
||||
helper.padChrome$ = helper.padOuter$ = helper.padInner$ = null;
|
||||
|
||||
//clean up iframes properly to prevent IE from memoryleaking
|
||||
$iframeContainer.find("iframe").purgeFrame().done(function(){
|
||||
$iframeContainer.append($iframe);
|
||||
$iframe.one('load', function(){
|
||||
helper.waitFor(function(){
|
||||
return !$iframe.contents().find("#editorloadingbox").is(":visible");
|
||||
}, 50000).done(function(){
|
||||
helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe'));
|
||||
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe.[name="ace_outer"]'));
|
||||
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe.[name="ace_inner"]'));
|
||||
|
||||
//disable all animations, this makes tests faster and easier
|
||||
helper.padChrome$.fx.off = true;
|
||||
helper.padOuter$.fx.off = true;
|
||||
helper.padInner$.fx.off = true;
|
||||
|
||||
opts.cb();
|
||||
}).fail(function(){
|
||||
throw new Error("Pad never loaded");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return padName;
|
||||
}
|
||||
|
||||
helper.waitFor = function(conditionFunc, _timeoutTime, _intervalTime){
|
||||
var timeoutTime = _timeoutTime || 1000;
|
||||
var intervalTime = _intervalTime || 10;
|
||||
|
||||
var deferred = $.Deferred();
|
||||
|
||||
var _fail = deferred.fail;
|
||||
var listenForFail = false;
|
||||
deferred.fail = function(){
|
||||
listenForFail = true;
|
||||
_fail.apply(this, arguments);
|
||||
}
|
||||
|
||||
var intervalCheck = setInterval(function(){
|
||||
var passed = false;
|
||||
|
||||
passed = conditionFunc();
|
||||
|
||||
if(passed){
|
||||
clearInterval(intervalCheck);
|
||||
clearTimeout(timeout);
|
||||
|
||||
deferred.resolve();
|
||||
}
|
||||
}, intervalTime);
|
||||
|
||||
var timeout = setTimeout(function(){
|
||||
clearInterval(intervalCheck);
|
||||
var error = new Error("wait for condition never became true " + conditionFunc.toString());
|
||||
deferred.reject(error);
|
||||
|
||||
if(!listenForFail){
|
||||
throw error;
|
||||
}
|
||||
}, timeoutTime);
|
||||
|
||||
return deferred;
|
||||
}
|
||||
|
||||
/* Ensure console.log doesn't blow up in IE, ugly but ok for a test framework imho*/
|
||||
window.console = window.console || {};
|
||||
window.console.log = window.console.log || function(){}
|
||||
|
||||
//force usage of callbacks in it
|
||||
var _it = it;
|
||||
it = function(name, func){
|
||||
if(func && func.length !== 1){
|
||||
func = function(){
|
||||
throw new Error("Please use always a callback with it() - " + func.toString());
|
||||
}
|
||||
}
|
||||
|
||||
_it(name, func);
|
||||
}
|
||||
})()
|
26
tests/frontend/index.html
Normal file
26
tests/frontend/index.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<title>Frontend tests</title>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" href="runner.css" />
|
||||
|
||||
<div id="console"></div>
|
||||
<div id="mocha"></div>
|
||||
<div id="iframe-container"></div>
|
||||
|
||||
<script src="/static/js/jquery.js"></script>
|
||||
<script src="lib/underscore.js"></script>
|
||||
|
||||
<script src="lib/mocha.js"></script>
|
||||
<script> mocha.setup('bdd') </script>
|
||||
<script src="lib/expect.js"></script>
|
||||
|
||||
<script src="lib/sendkeys.js"></script>
|
||||
<script src="lib/jquery.iframe.js"></script>
|
||||
<script src="helper.js"></script>
|
||||
|
||||
<script src="specs_list.js"></script>
|
||||
|
||||
<script src="runner.js"></script>
|
||||
</html>
|
1247
tests/frontend/lib/expect.js
Normal file
1247
tests/frontend/lib/expect.js
Normal file
File diff suppressed because it is too large
Load diff
39
tests/frontend/lib/jquery.iframe.js
Normal file
39
tests/frontend/lib/jquery.iframe.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
//copied from http://stackoverflow.com/questions/8407946/is-it-possible-to-use-iframes-in-ie-without-memory-leaks
|
||||
(function($) {
|
||||
$.fn.purgeFrame = function() {
|
||||
var deferred;
|
||||
|
||||
if ($.browser.msie && parseFloat($.browser.version, 10) < 9) {
|
||||
deferred = purge(this);
|
||||
} else {
|
||||
this.remove();
|
||||
deferred = $.Deferred();
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred;
|
||||
};
|
||||
|
||||
function purge($frame) {
|
||||
var sem = $frame.length
|
||||
, deferred = $.Deferred();
|
||||
|
||||
$frame.load(function() {
|
||||
var frame = this;
|
||||
frame.contentWindow.document.innerHTML = '';
|
||||
|
||||
sem -= 1;
|
||||
if (sem <= 0) {
|
||||
$frame.remove();
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
$frame.attr('src', 'about:blank');
|
||||
|
||||
if ($frame.length === 0) {
|
||||
deferred.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
})(jQuery);
|
4868
tests/frontend/lib/mocha.js
Normal file
4868
tests/frontend/lib/mocha.js
Normal file
File diff suppressed because it is too large
Load diff
467
tests/frontend/lib/sendkeys.js
Normal file
467
tests/frontend/lib/sendkeys.js
Normal file
|
@ -0,0 +1,467 @@
|
|||
// Cross-broswer implementation of text ranges and selections
|
||||
// documentation: http://bililite.com/blog/2011/01/11/cross-browser-.and-selections/
|
||||
// Version: 1.1
|
||||
// Copyright (c) 2010 Daniel Wachsstock
|
||||
// MIT license:
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function($){
|
||||
|
||||
bililiteRange = function(el, debug){
|
||||
var ret;
|
||||
if (debug){
|
||||
ret = new NothingRange(); // Easier to force it to use the no-selection type than to try to find an old browser
|
||||
}else if (document.selection && !document.addEventListener){
|
||||
// Internet Explorer 8 and lower
|
||||
ret = new IERange();
|
||||
}else if (window.getSelection && el.setSelectionRange){
|
||||
// Standards. Element is an input or textarea
|
||||
ret = new InputRange();
|
||||
}else if (window.getSelection){
|
||||
// Standards, with any other kind of element
|
||||
ret = new W3CRange()
|
||||
}else{
|
||||
// doesn't support selection
|
||||
ret = new NothingRange();
|
||||
}
|
||||
ret._el = el;
|
||||
ret._doc = el.ownerDocument;
|
||||
ret._win = 'defaultView' in ret._doc ? ret._doc.defaultView : ret._doc.parentWindow;
|
||||
ret._textProp = textProp(el);
|
||||
ret._bounds = [0, ret.length()];
|
||||
return ret;
|
||||
}
|
||||
|
||||
function textProp(el){
|
||||
// returns the property that contains the text of the element
|
||||
if (typeof el.value != 'undefined') return 'value';
|
||||
if (typeof el.text != 'undefined') return 'text';
|
||||
if (typeof el.textContent != 'undefined') return 'textContent';
|
||||
return 'innerText';
|
||||
}
|
||||
|
||||
// base class
|
||||
function Range(){}
|
||||
Range.prototype = {
|
||||
length: function() {
|
||||
return this._el[this._textProp].replace(/\r/g, '').length; // need to correct for IE's CrLf weirdness
|
||||
},
|
||||
bounds: function(s){
|
||||
if (s === 'all'){
|
||||
this._bounds = [0, this.length()];
|
||||
}else if (s === 'start'){
|
||||
this._bounds = [0, 0];
|
||||
}else if (s === 'end'){
|
||||
this._bounds = [this.length(), this.length()];
|
||||
}else if (s === 'selection'){
|
||||
this.bounds ('all'); // first select the whole thing for constraining
|
||||
this._bounds = this._nativeSelection();
|
||||
}else if (s){
|
||||
this._bounds = s; // don't error check now; the element may change at any moment, so constrain it when we need it.
|
||||
}else{
|
||||
var b = [
|
||||
Math.max(0, Math.min (this.length(), this._bounds[0])),
|
||||
Math.max(0, Math.min (this.length(), this._bounds[1]))
|
||||
];
|
||||
return b; // need to constrain it to fit
|
||||
}
|
||||
return this; // allow for chaining
|
||||
},
|
||||
select: function(){
|
||||
this._nativeSelect(this._nativeRange(this.bounds()));
|
||||
return this; // allow for chaining
|
||||
},
|
||||
text: function(text, select){
|
||||
if (arguments.length){
|
||||
this._nativeSetText(text, this._nativeRange(this.bounds()));
|
||||
if (select == 'start'){
|
||||
this.bounds ([this._bounds[0], this._bounds[0]]);
|
||||
this.select();
|
||||
}else if (select == 'end'){
|
||||
this.bounds ([this._bounds[0]+text.length, this._bounds[0]+text.length]);
|
||||
this.select();
|
||||
}else if (select == 'all'){
|
||||
this.bounds ([this._bounds[0], this._bounds[0]+text.length]);
|
||||
this.select();
|
||||
}
|
||||
return this; // allow for chaining
|
||||
}else{
|
||||
return this._nativeGetText(this._nativeRange(this.bounds()));
|
||||
}
|
||||
},
|
||||
insertEOL: function (){
|
||||
this._nativeEOL();
|
||||
this._bounds = [this._bounds[0]+1, this._bounds[0]+1]; // move past the EOL marker
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function IERange(){}
|
||||
IERange.prototype = new Range();
|
||||
IERange.prototype._nativeRange = function (bounds){
|
||||
var rng;
|
||||
if (this._el.tagName == 'INPUT'){
|
||||
// IE 8 is very inconsistent; textareas have createTextRange but it doesn't work
|
||||
rng = this._el.createTextRange();
|
||||
}else{
|
||||
rng = this._doc.body.createTextRange ();
|
||||
rng.moveToElementText(this._el);
|
||||
}
|
||||
if (bounds){
|
||||
if (bounds[1] < 0) bounds[1] = 0; // IE tends to run elements out of bounds
|
||||
if (bounds[0] > this.length()) bounds[0] = this.length();
|
||||
if (bounds[1] < rng.text.replace(/\r/g, '').length){ // correct for IE's CrLf wierdness
|
||||
// block-display elements have an invisible, uncounted end of element marker, so we move an extra one and use the current length of the range
|
||||
rng.moveEnd ('character', -1);
|
||||
rng.moveEnd ('character', bounds[1]-rng.text.replace(/\r/g, '').length);
|
||||
}
|
||||
if (bounds[0] > 0) rng.moveStart('character', bounds[0]);
|
||||
}
|
||||
return rng;
|
||||
};
|
||||
IERange.prototype._nativeSelect = function (rng){
|
||||
rng.select();
|
||||
};
|
||||
IERange.prototype._nativeSelection = function (){
|
||||
// returns [start, end] for the selection constrained to be in element
|
||||
var rng = this._nativeRange(); // range of the element to constrain to
|
||||
var len = this.length();
|
||||
if (this._doc.selection.type != 'Text') return [0,0]; // append to the end
|
||||
var sel = this._doc.selection.createRange();
|
||||
try{
|
||||
return [
|
||||
iestart(sel, rng),
|
||||
ieend (sel, rng)
|
||||
];
|
||||
}catch (e){
|
||||
// IE gets upset sometimes about comparing text to input elements, but the selections cannot overlap, so make a best guess
|
||||
return (sel.parentElement().sourceIndex < this._el.sourceIndex) ? [0,0] : [len, len];
|
||||
}
|
||||
};
|
||||
IERange.prototype._nativeGetText = function (rng){
|
||||
return rng.text.replace(/\r/g, ''); // correct for IE's CrLf weirdness
|
||||
};
|
||||
IERange.prototype._nativeSetText = function (text, rng){
|
||||
rng.text = text;
|
||||
};
|
||||
IERange.prototype._nativeEOL = function(){
|
||||
if (typeof this._el.value != 'undefined'){
|
||||
this.text('\n'); // for input and textarea, insert it straight
|
||||
}else{
|
||||
this._nativeRange(this.bounds()).pasteHTML('<br/>');
|
||||
}
|
||||
};
|
||||
// IE internals
|
||||
function iestart(rng, constraint){
|
||||
// returns the position (in character) of the start of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
|
||||
var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness
|
||||
if (rng.compareEndPoints ('StartToStart', constraint) <= 0) return 0; // at or before the beginning
|
||||
if (rng.compareEndPoints ('StartToEnd', constraint) >= 0) return len;
|
||||
for (var i = 0; rng.compareEndPoints ('StartToStart', constraint) > 0; ++i, rng.moveStart('character', -1));
|
||||
return i;
|
||||
}
|
||||
function ieend (rng, constraint){
|
||||
// returns the position (in character) of the end of rng within constraint. If it's not in constraint, returns 0 if it's before, length if it's after
|
||||
var len = constraint.text.replace(/\r/g, '').length; // correct for IE's CrLf wierdness
|
||||
if (rng.compareEndPoints ('EndToEnd', constraint) >= 0) return len; // at or after the end
|
||||
if (rng.compareEndPoints ('EndToStart', constraint) <= 0) return 0;
|
||||
for (var i = 0; rng.compareEndPoints ('EndToStart', constraint) > 0; ++i, rng.moveEnd('character', -1));
|
||||
return i;
|
||||
}
|
||||
|
||||
// an input element in a standards document. "Native Range" is just the bounds array
|
||||
function InputRange(){}
|
||||
InputRange.prototype = new Range();
|
||||
InputRange.prototype._nativeRange = function(bounds) {
|
||||
return bounds || [0, this.length()];
|
||||
};
|
||||
InputRange.prototype._nativeSelect = function (rng){
|
||||
this._el.setSelectionRange(rng[0], rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeSelection = function(){
|
||||
return [this._el.selectionStart, this._el.selectionEnd];
|
||||
};
|
||||
InputRange.prototype._nativeGetText = function(rng){
|
||||
return this._el.value.substring(rng[0], rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeSetText = function(text, rng){
|
||||
var val = this._el.value;
|
||||
this._el.value = val.substring(0, rng[0]) + text + val.substring(rng[1]);
|
||||
};
|
||||
InputRange.prototype._nativeEOL = function(){
|
||||
this.text('\n');
|
||||
};
|
||||
|
||||
function W3CRange(){}
|
||||
W3CRange.prototype = new Range();
|
||||
W3CRange.prototype._nativeRange = function (bounds){
|
||||
var rng = this._doc.createRange();
|
||||
rng.selectNodeContents(this._el);
|
||||
if (bounds){
|
||||
w3cmoveBoundary (rng, bounds[0], true, this._el);
|
||||
rng.collapse (true);
|
||||
w3cmoveBoundary (rng, bounds[1]-bounds[0], false, this._el);
|
||||
}
|
||||
return rng;
|
||||
};
|
||||
W3CRange.prototype._nativeSelect = function (rng){
|
||||
this._win.getSelection().removeAllRanges();
|
||||
this._win.getSelection().addRange (rng);
|
||||
};
|
||||
W3CRange.prototype._nativeSelection = function (){
|
||||
// returns [start, end] for the selection constrained to be in element
|
||||
var rng = this._nativeRange(); // range of the element to constrain to
|
||||
if (this._win.getSelection().rangeCount == 0) return [this.length(), this.length()]; // append to the end
|
||||
var sel = this._win.getSelection().getRangeAt(0);
|
||||
return [
|
||||
w3cstart(sel, rng),
|
||||
w3cend (sel, rng)
|
||||
];
|
||||
}
|
||||
W3CRange.prototype._nativeGetText = function (rng){
|
||||
return rng.toString();
|
||||
};
|
||||
W3CRange.prototype._nativeSetText = function (text, rng){
|
||||
rng.deleteContents();
|
||||
rng.insertNode (this._doc.createTextNode(text));
|
||||
this._el.normalize(); // merge the text with the surrounding text
|
||||
};
|
||||
W3CRange.prototype._nativeEOL = function(){
|
||||
var rng = this._nativeRange(this.bounds());
|
||||
rng.deleteContents();
|
||||
var br = this._doc.createElement('br');
|
||||
br.setAttribute ('_moz_dirty', ''); // for Firefox
|
||||
rng.insertNode (br);
|
||||
rng.insertNode (this._doc.createTextNode('\n'));
|
||||
rng.collapse (false);
|
||||
};
|
||||
// W3C internals
|
||||
function nextnode (node, root){
|
||||
// in-order traversal
|
||||
// we've already visited node, so get kids then siblings
|
||||
if (node.firstChild) return node.firstChild;
|
||||
if (node.nextSibling) return node.nextSibling;
|
||||
if (node===root) return null;
|
||||
while (node.parentNode){
|
||||
// get uncles
|
||||
node = node.parentNode;
|
||||
if (node == root) return null;
|
||||
if (node.nextSibling) return node.nextSibling;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function w3cmoveBoundary (rng, n, bStart, el){
|
||||
// move the boundary (bStart == true ? start : end) n characters forward, up to the end of element el. Forward only!
|
||||
// if the start is moved after the end, then an exception is raised
|
||||
if (n <= 0) return;
|
||||
var node = rng[bStart ? 'startContainer' : 'endContainer'];
|
||||
if (node.nodeType == 3){
|
||||
// we may be starting somewhere into the text
|
||||
n += rng[bStart ? 'startOffset' : 'endOffset'];
|
||||
}
|
||||
while (node){
|
||||
if (node.nodeType == 3){
|
||||
if (n <= node.nodeValue.length){
|
||||
rng[bStart ? 'setStart' : 'setEnd'](node, n);
|
||||
// special case: if we end next to a <br>, include that node.
|
||||
if (n == node.nodeValue.length){
|
||||
// skip past zero-length text nodes
|
||||
for (var next = nextnode (node, el); next && next.nodeType==3 && next.nodeValue.length == 0; next = nextnode(next, el)){
|
||||
rng[bStart ? 'setStartAfter' : 'setEndAfter'](next);
|
||||
}
|
||||
if (next && next.nodeType == 1 && next.nodeName == "BR") rng[bStart ? 'setStartAfter' : 'setEndAfter'](next);
|
||||
}
|
||||
return;
|
||||
}else{
|
||||
rng[bStart ? 'setStartAfter' : 'setEndAfter'](node); // skip past this one
|
||||
n -= node.nodeValue.length; // and eat these characters
|
||||
}
|
||||
}
|
||||
node = nextnode (node, el);
|
||||
}
|
||||
}
|
||||
var START_TO_START = 0; // from the w3c definitions
|
||||
var START_TO_END = 1;
|
||||
var END_TO_END = 2;
|
||||
var END_TO_START = 3;
|
||||
// from the Mozilla documentation, for range.compareBoundaryPoints(how, sourceRange)
|
||||
// -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before, equal to, or after the corresponding boundary-point of sourceRange.
|
||||
// * Range.END_TO_END compares the end boundary-point of sourceRange to the end boundary-point of range.
|
||||
// * Range.END_TO_START compares the end boundary-point of sourceRange to the start boundary-point of range.
|
||||
// * Range.START_TO_END compares the start boundary-point of sourceRange to the end boundary-point of range.
|
||||
// * Range.START_TO_START compares the start boundary-point of sourceRange to the start boundary-point of range.
|
||||
function w3cstart(rng, constraint){
|
||||
if (rng.compareBoundaryPoints (START_TO_START, constraint) <= 0) return 0; // at or before the beginning
|
||||
if (rng.compareBoundaryPoints (END_TO_START, constraint) >= 0) return constraint.toString().length;
|
||||
rng = rng.cloneRange(); // don't change the original
|
||||
rng.setEnd (constraint.endContainer, constraint.endOffset); // they now end at the same place
|
||||
return constraint.toString().length - rng.toString().length;
|
||||
}
|
||||
function w3cend (rng, constraint){
|
||||
if (rng.compareBoundaryPoints (END_TO_END, constraint) >= 0) return constraint.toString().length; // at or after the end
|
||||
if (rng.compareBoundaryPoints (START_TO_END, constraint) <= 0) return 0;
|
||||
rng = rng.cloneRange(); // don't change the original
|
||||
rng.setStart (constraint.startContainer, constraint.startOffset); // they now start at the same place
|
||||
return rng.toString().length;
|
||||
}
|
||||
|
||||
function NothingRange(){}
|
||||
NothingRange.prototype = new Range();
|
||||
NothingRange.prototype._nativeRange = function(bounds) {
|
||||
return bounds || [0,this.length()];
|
||||
};
|
||||
NothingRange.prototype._nativeSelect = function (rng){ // do nothing
|
||||
};
|
||||
NothingRange.prototype._nativeSelection = function(){
|
||||
return [0,0];
|
||||
};
|
||||
NothingRange.prototype._nativeGetText = function (rng){
|
||||
return this._el[this._textProp].substring(rng[0], rng[1]);
|
||||
};
|
||||
NothingRange.prototype._nativeSetText = function (text, rng){
|
||||
var val = this._el[this._textProp];
|
||||
this._el[this._textProp] = val.substring(0, rng[0]) + text + val.substring(rng[1]);
|
||||
};
|
||||
NothingRange.prototype._nativeEOL = function(){
|
||||
this.text('\n');
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
|
||||
// insert characters in a textarea or text input field
|
||||
// special characters are enclosed in {}; use {{} for the { character itself
|
||||
// documentation: http://bililite.com/blog/2008/08/20/the-fnsendkeys-plugin/
|
||||
// Version: 2.0
|
||||
// Copyright (c) 2010 Daniel Wachsstock
|
||||
// MIT license:
|
||||
// Permission is hereby granted, free of charge, to any person
|
||||
// obtaining a copy of this software and associated documentation
|
||||
// files (the "Software"), to deal in the Software without
|
||||
// restriction, including without limitation the rights to use,
|
||||
// copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(function($){
|
||||
|
||||
$.fn.sendkeys = function (x, opts){
|
||||
return this.each( function(){
|
||||
var localkeys = $.extend({}, opts, $(this).data('sendkeys')); // allow for element-specific key functions
|
||||
// most elements to not keep track of their selection when they lose focus, so we have to do it for them
|
||||
var rng = $.data (this, 'sendkeys.selection');
|
||||
if (!rng){
|
||||
rng = bililiteRange(this).bounds('selection');
|
||||
$.data(this, 'sendkeys.selection', rng);
|
||||
$(this).bind('mouseup.sendkeys', function(){
|
||||
// we have to update the saved range. The routines here update the bounds with each press, but actual keypresses and mouseclicks do not
|
||||
$.data(this, 'sendkeys.selection').bounds('selection');
|
||||
}).bind('keyup.sendkeys', function(evt){
|
||||
// restore the selection if we got here with a tab (a click should select what was clicked on)
|
||||
if (evt.which == 9){
|
||||
// there's a flash of selection when we restore the focus, but I don't know how to avoid that.
|
||||
$.data(this, 'sendkeys.selection').select();
|
||||
}else{
|
||||
$.data(this, 'sendkeys.selection').bounds('selection');
|
||||
}
|
||||
});
|
||||
}
|
||||
this.focus();
|
||||
if (typeof x === 'undefined') return; // no string, so we just set up the event handlers
|
||||
$.data(this, 'sendkeys.originalText', rng.text());
|
||||
x.replace(/\n/g, '{enter}'). // turn line feeds into explicit break insertions
|
||||
replace(/{[^}]*}|[^{]+/g, function(s){
|
||||
(localkeys[s] || $.fn.sendkeys.defaults[s] || $.fn.sendkeys.defaults.simplechar)(rng, s);
|
||||
});
|
||||
$(this).trigger({type: 'sendkeys', which: x});
|
||||
});
|
||||
}; // sendkeys
|
||||
|
||||
|
||||
// add the functions publicly so they can be overridden
|
||||
$.fn.sendkeys.defaults = {
|
||||
simplechar: function (rng, s){
|
||||
rng.text(s, 'end');
|
||||
for (var i =0; i < s.length; ++i){
|
||||
var x = s.charCodeAt(i);
|
||||
// a bit of cheating: rng._el is the element associated with rng.
|
||||
$(rng._el).trigger({type: 'keypress', keyCode: x, which: x, charCode: x});
|
||||
}
|
||||
},
|
||||
'{{}': function (rng){
|
||||
$.fn.sendkeys.defaults.simplechar (rng, '{')
|
||||
},
|
||||
'{enter}': function (rng){
|
||||
rng.insertEOL();
|
||||
rng.select();
|
||||
var x = '\n'.charCodeAt(0);
|
||||
$(rng._el).trigger({type: 'keypress', keyCode: x, which: x, charCode: x});
|
||||
},
|
||||
'{backspace}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) rng.bounds([b[0]-1, b[0]]); // no characters selected; it's just an insertion point. Remove the previous character
|
||||
rng.text('', 'end'); // delete the characters and update the selection
|
||||
},
|
||||
'{del}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) rng.bounds([b[0], b[0]+1]); // no characters selected; it's just an insertion point. Remove the next character
|
||||
rng.text('', 'end'); // delete the characters and update the selection
|
||||
},
|
||||
'{rightarrow}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) ++b[1]; // no characters selected; it's just an insertion point. Move to the right
|
||||
rng.bounds([b[1], b[1]]).select();
|
||||
},
|
||||
'{leftarrow}': function (rng){
|
||||
var b = rng.bounds();
|
||||
if (b[0] == b[1]) --b[0]; // no characters selected; it's just an insertion point. Move to the left
|
||||
rng.bounds([b[0], b[0]]).select();
|
||||
},
|
||||
'{selectall}' : function (rng){
|
||||
rng.bounds('all').select();
|
||||
},
|
||||
'{selection}': function (rng){
|
||||
$.fn.sendkeys.defaults.simplechar(rng, $.data(rng._el, 'sendkeys.originalText'));
|
||||
},
|
||||
'{mark}' : function (rng){
|
||||
var bounds = rng.bounds();
|
||||
$(rng._el).one('sendkeys', function(){
|
||||
// set up the event listener to change the selection after the sendkeys is done
|
||||
rng.bounds(bounds).select();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery)
|
1200
tests/frontend/lib/underscore.js
Normal file
1200
tests/frontend/lib/underscore.js
Normal file
File diff suppressed because it is too large
Load diff
228
tests/frontend/runner.css
Normal file
228
tests/frontend/runner.css
Normal file
|
@ -0,0 +1,228 @@
|
|||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#console {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#iframe-container {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
float:right;
|
||||
}
|
||||
|
||||
#iframe-container iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
border-right: 2px solid #999;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
float:left;
|
||||
}
|
||||
|
||||
#mocha #report {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#mocha ul, #mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1, #mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a:visited
|
||||
{
|
||||
color: #00E;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test:hover h2::after {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: -10px;
|
||||
content: '(view source)';
|
||||
font-size: 12px;
|
||||
font-family: arial;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #C09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #B94A48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: white;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre {
|
||||
display: inline-block;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
}
|
||||
|
||||
#report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#error {
|
||||
color: #c00;
|
||||
font-size: 1.5 em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 52%;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
code .comment { color: #ddd }
|
||||
code .init { color: #2F6FAD }
|
||||
code .string { color: #5890AD }
|
||||
code .keyword { color: #8A6343 }
|
||||
code .number { color: #2F6FAD }
|
199
tests/frontend/runner.js
Normal file
199
tests/frontend/runner.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
$(function(){
|
||||
function Base(runner) {
|
||||
var self = this
|
||||
, stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
|
||||
, failures = this.failures = [];
|
||||
|
||||
if (!runner) return;
|
||||
this.runner = runner;
|
||||
|
||||
runner.on('start', function(){
|
||||
stats.start = new Date;
|
||||
});
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
stats.suites = stats.suites || 0;
|
||||
suite.root || stats.suites++;
|
||||
});
|
||||
|
||||
runner.on('test end', function(test){
|
||||
stats.tests = stats.tests || 0;
|
||||
stats.tests++;
|
||||
});
|
||||
|
||||
runner.on('pass', function(test){
|
||||
stats.passes = stats.passes || 0;
|
||||
|
||||
var medium = test.slow() / 2;
|
||||
test.speed = test.duration > test.slow()
|
||||
? 'slow'
|
||||
: test.duration > medium
|
||||
? 'medium'
|
||||
: 'fast';
|
||||
|
||||
stats.passes++;
|
||||
});
|
||||
|
||||
runner.on('fail', function(test, err){
|
||||
stats.failures = stats.failures || 0;
|
||||
stats.failures++;
|
||||
test.err = err;
|
||||
failures.push(test);
|
||||
});
|
||||
|
||||
runner.on('end', function(){
|
||||
stats.end = new Date;
|
||||
stats.duration = new Date - stats.start;
|
||||
});
|
||||
|
||||
runner.on('pending', function(){
|
||||
stats.pending++;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
This reporter wraps the original html reporter plus reports plain text into a hidden div.
|
||||
This allows the webdriver client to pick up the test results
|
||||
*/
|
||||
var WebdriverAndHtmlReporter = function(html_reporter){
|
||||
return function(runner){
|
||||
Base.call(this, runner);
|
||||
|
||||
// Scroll down test display after each test
|
||||
mocha = $('#mocha')[0];
|
||||
runner.on('test', function(){
|
||||
mocha.scrollTop = mocha.scrollHeight;
|
||||
});
|
||||
|
||||
//initalize the html reporter first
|
||||
html_reporter(runner);
|
||||
|
||||
var $console = $("#console");
|
||||
var level = 0;
|
||||
var append = function(){
|
||||
var text = Array.prototype.join.apply(arguments, [" "]);
|
||||
var oldText = $console.text();
|
||||
|
||||
var space = "";
|
||||
for(var i=0;i<level*2;i++){
|
||||
space+=" ";
|
||||
}
|
||||
|
||||
var splitedText = "";
|
||||
_(text.split("\n")).each(function(line){
|
||||
while(line.length > 0){
|
||||
var split = line.substr(0,100);
|
||||
line = line.substr(100);
|
||||
if(splitedText.length > 0) splitedText+="\n";
|
||||
splitedText += split;
|
||||
}
|
||||
});
|
||||
|
||||
//indent all lines with the given amount of space
|
||||
var newText = _(splitedText.split("\n")).map(function(line){
|
||||
return space + line;
|
||||
}).join("\\n");
|
||||
|
||||
$console.text(oldText + newText + "\\n");
|
||||
}
|
||||
|
||||
runner.on('suite', function(suite){
|
||||
if (suite.root) return;
|
||||
|
||||
append(suite.title);
|
||||
level++;
|
||||
});
|
||||
|
||||
runner.on('suite end', function(suite){
|
||||
if (suite.root) return;
|
||||
level--;
|
||||
|
||||
if(level == 0) {
|
||||
append("");
|
||||
}
|
||||
});
|
||||
|
||||
var stringifyException = function(exception){
|
||||
var err = exception.stack || exception.toString();
|
||||
|
||||
// FF / Opera do not add the message
|
||||
if (!~err.indexOf(exception.message)) {
|
||||
err = exception.message + '\n' + err;
|
||||
}
|
||||
|
||||
// <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
|
||||
// check for the result of the stringifying.
|
||||
if ('[object Error]' == err) err = exception.message;
|
||||
|
||||
// Safari doesn't give you a stack. Let's at least provide a source line.
|
||||
if (!exception.stack && exception.sourceURL && exception.line !== undefined) {
|
||||
err += "\n(" + exception.sourceURL + ":" + exception.line + ")";
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
var killTimeout;
|
||||
runner.on('test end', function(test){
|
||||
if ('passed' == test.state) {
|
||||
append("->","[green]PASSED[clear] :", test.title);
|
||||
} else if (test.pending) {
|
||||
append("->","[yellow]PENDING[clear]:", test.title);
|
||||
} else {
|
||||
append("->","[red]FAILED[clear] :", test.title, stringifyException(test.err));
|
||||
}
|
||||
|
||||
if(killTimeout) clearTimeout(killTimeout);
|
||||
killTimeout = setTimeout(function(){
|
||||
append("FINISHED - [red]no test started since 3 minutes, tests stopped[clear]");
|
||||
}, 60000 * 3);
|
||||
});
|
||||
|
||||
var total = runner.total;
|
||||
runner.on('end', function(){
|
||||
if(stats.tests >= total){
|
||||
var minutes = Math.floor(stats.duration / 1000 / 60);
|
||||
var seconds = Math.round((stats.duration / 1000) % 60);
|
||||
|
||||
append("FINISHED -", stats.passes, "tests passed,", stats.failures, "tests failed, duration: " + minutes + ":" + seconds);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//allow cross iframe access
|
||||
if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) {
|
||||
document.domain = document.domain; // for comet
|
||||
}
|
||||
|
||||
//http://stackoverflow.com/questions/1403888/get-url-parameter-with-jquery
|
||||
var getURLParameter = function (name) {
|
||||
return decodeURI(
|
||||
(RegExp(name + '=' + '(.+?)(&|$)').exec(location.search)||[,null])[1]
|
||||
);
|
||||
}
|
||||
|
||||
//get the list of specs and filter it if requested
|
||||
var specs = specs_list.slice();
|
||||
|
||||
//inject spec scripts into the dom
|
||||
var $body = $('body');
|
||||
$.each(specs, function(i, spec){
|
||||
$body.append('<script src="specs/' + spec + '"></script>')
|
||||
});
|
||||
|
||||
//initalize the test helper
|
||||
helper.init(function(){
|
||||
//configure and start the test framework
|
||||
var grep = getURLParameter("grep");
|
||||
if(grep != "null"){
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
mocha.ignoreLeaks();
|
||||
|
||||
mocha.reporter(WebdriverAndHtmlReporter(mocha._reporter));
|
||||
|
||||
mocha.run();
|
||||
});
|
||||
});
|
36
tests/frontend/specs/button_bold.js
Normal file
36
tests/frontend/specs/button_bold.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
describe("bold button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text bold", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the bold button and click it
|
||||
var $boldButton = chrome$(".buttonicon-bold");
|
||||
$boldButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <b> element now?
|
||||
var isBold = $newFirstTextElement.find("b").length === 1;
|
||||
|
||||
//expect it to be bold
|
||||
expect(isBold).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
54
tests/frontend/specs/button_clear_authorship_colors.js
Normal file
54
tests/frontend/specs/button_clear_authorship_colors.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
describe("clear authorship colors button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text clear authorship colors", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// override the confirm dialogue functioon
|
||||
helper.padChrome$.window.confirm = function(){
|
||||
return true;
|
||||
}
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// Get the original text
|
||||
var originalText = inner$("div").first().text();
|
||||
|
||||
// Set some new text
|
||||
var sentText = "Hello";
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
$firstTextElement.sendkeys(sentText);
|
||||
$firstTextElement.sendkeys('{rightarrow}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().attr("class").indexOf("author") !== -1; // wait until we have the full value available
|
||||
}).done(function(){
|
||||
//IE hates you if you don't give focus to the inner frame bevore you do a clearAuthorship
|
||||
inner$("div").first().focus();
|
||||
|
||||
//get the clear authorship colors button and click it
|
||||
var $clearauthorshipcolorsButton = chrome$(".buttonicon-clearauthorship");
|
||||
$clearauthorshipcolorsButton.click();
|
||||
|
||||
// does the first divs span include an author class?
|
||||
console.log(inner$("div span").first().attr("class"));
|
||||
var hasAuthorClass = inner$("div span").first().attr("class").indexOf("author") !== -1;
|
||||
//expect(hasAuthorClass).to.be(false);
|
||||
|
||||
// does the first div include an author class?
|
||||
var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
|
||||
expect(hasAuthorClass).to.be(false);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
179
tests/frontend/specs/button_indentation.js
Normal file
179
tests/frontend/specs/button_indentation.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
describe("indentation button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("indent text", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $indentButton = chrome$(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("ul li").length === 1;
|
||||
}).done(done);
|
||||
});
|
||||
|
||||
it("keeps the indent on enter for the new line", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $indentButton = chrome$(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
//type a bit, make a line break and type again
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
$firstTextElement.sendkeys('line 1');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
$firstTextElement.sendkeys('line 2');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text().indexOf("line 2") === -1;
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var hasULElement = $newSecondLine.find("ul li").length === 1;
|
||||
|
||||
expect(hasULElement).to.be(true);
|
||||
expect($newSecondLine.text()).to.be("line 2");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
it("makes text indented and outdented", function() {
|
||||
|
||||
//get the inner iframe
|
||||
var $inner = testHelper.$getPadInner();
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = $inner.find("div").first();
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
//get the indentation button and click it
|
||||
var $indentButton = testHelper.$getPadChrome().find(".buttonicon-indent");
|
||||
$indentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
|
||||
//indent again
|
||||
$indentButton.click();
|
||||
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var hasListIndent2 = firstChild.hasClass('list-indent2');
|
||||
|
||||
//expect it to be part of a list
|
||||
expect(hasListIndent2).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
|
||||
|
||||
|
||||
// test outdent
|
||||
|
||||
//get the unindentation button and click it twice
|
||||
var $outdentButton = testHelper.$getPadChrome().find(".buttonicon-outdent");
|
||||
$outdentButton.click();
|
||||
$outdentButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var newFirstTextElement = $inner.find("div").first();
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = newFirstTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it not to be the beginning of a list
|
||||
expect(isUL).to.be(false);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to not be part of a list
|
||||
expect(isLI).to.be(false);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect(newFirstTextElement.text()).to.eql(firstTextElement.text());
|
||||
|
||||
|
||||
// Next test tests multiple line indentation
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
//indent twice
|
||||
$indentButton.click();
|
||||
$indentButton.click();
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = $inner.find("div").first();
|
||||
|
||||
//select this text element
|
||||
testHelper.selectText(firstTextElement[0], $inner);
|
||||
|
||||
/* this test creates the below content, both should have double indentation
|
||||
line1
|
||||
line2
|
||||
|
||||
|
||||
firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('line 1'); // simulate writing the first line
|
||||
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
|
||||
firstTextElement.sendkeys('line 2'); // simulate writing the second line
|
||||
|
||||
//get the second text element out of the inner iframe
|
||||
setTimeout(function(){ // THIS IS REALLY BAD
|
||||
var secondTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(1); // THIS IS UGLY
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = secondTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = secondChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var thirdTextElement = $('iframe').contents().find('iframe').contents().find('iframe').contents().find('body > div').get(2); // THIS IS UGLY TOO
|
||||
|
||||
// is there a list-indent class element now?
|
||||
var firstChild = thirdTextElement.children(":first");
|
||||
var isUL = firstChild.is('ul');
|
||||
|
||||
//expect it to be the beginning of a list
|
||||
expect(isUL).to.be(true);
|
||||
|
||||
var secondChild = firstChild.children(":first");
|
||||
var isLI = secondChild.is('li');
|
||||
|
||||
//expect it to be part of a list
|
||||
expect(isLI).to.be(true);
|
||||
},1000);
|
||||
});*/
|
||||
});
|
36
tests/frontend/specs/button_italic.js
Normal file
36
tests/frontend/specs/button_italic.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
describe("italic button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text italic", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the bold button and click it
|
||||
var $boldButton = chrome$(".buttonicon-italic");
|
||||
$boldButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <i> element now?
|
||||
var isItalic = $newFirstTextElement.find("i").length === 1;
|
||||
|
||||
//expect it to be bold
|
||||
expect(isItalic).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
47
tests/frontend/specs/button_ordered_list.js
Normal file
47
tests/frontend/specs/button_ordered_list.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
describe("assign ordered list", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("insert ordered list text", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
|
||||
$insertorderedlistButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("ol li").length === 1;
|
||||
}).done(done);
|
||||
});
|
||||
|
||||
xit("issue #1125 keeps the numbered list on enter for the new line - EMULATES PASTING INTO A PAD", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
var $insertorderedlistButton = chrome$(".buttonicon-insertorderedlist");
|
||||
$insertorderedlistButton.click();
|
||||
|
||||
//type a bit, make a line break and type again
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
$firstTextElement.sendkeys('line 1');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
$firstTextElement.sendkeys('line 2');
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text().indexOf("line 2") === -1;
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var hasOLElement = $newSecondLine.find("ol li").length === 1;
|
||||
console.log($newSecondLine.find("ol"));
|
||||
expect(hasOLElement).to.be(true);
|
||||
expect($newSecondLine.text()).to.be("line 2");
|
||||
var hasLineNumber = $newSecondLine.find("ol").attr("start") === 2;
|
||||
expect(hasLineNumber).to.be(true); // This doesn't work because pasting in content doesn't work
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
37
tests/frontend/specs/button_redo.js
Normal file
37
tests/frontend/specs/button_redo.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
describe("undo button then redo button", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("undo some typing", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
var newString = "Foo";
|
||||
|
||||
$firstTextElement.sendkeys(newString); // send line 1 to the pad
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
// get undo and redo buttons
|
||||
var $undoButton = chrome$(".buttonicon-undo");
|
||||
var $redoButton = chrome$(".buttonicon-redo");
|
||||
// click the buttons
|
||||
$undoButton.click(); // removes foo
|
||||
$redoButton.click(); // resends foo
|
||||
|
||||
helper.waitFor(function(){
|
||||
console.log(inner$("div span").first().text());
|
||||
return inner$("div span").first().text() === newString;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div").first().text();
|
||||
expect(finalValue).to.be(modifiedValue); // expect the value to change
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
36
tests/frontend/specs/button_strikethrough.js
Normal file
36
tests/frontend/specs/button_strikethrough.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
describe("strikethrough button", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text strikethrough", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
//select this text element
|
||||
$firstTextElement.sendkeys('{selectall}');
|
||||
|
||||
//get the strikethrough button and click it
|
||||
var $strikethroughButton = chrome$(".buttonicon-strikethrough");
|
||||
$strikethroughButton.click();
|
||||
|
||||
//ace creates a new dom element when you press a button, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// is there a <i> element now?
|
||||
var isstrikethrough = $newFirstTextElement.find("s").length === 1;
|
||||
|
||||
//expect it to be strikethrough
|
||||
expect(isstrikethrough).to.be(true);
|
||||
|
||||
//make sure the text hasn't changed
|
||||
expect($newFirstTextElement.text()).to.eql($firstTextElement.text());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
47
tests/frontend/specs/button_timeslider.js
Normal file
47
tests/frontend/specs/button_timeslider.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
//deactivated, we need a nice way to get the timeslider, this is ugly
|
||||
xdescribe("timeslider button takes you to the timeslider of a pad", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("timeslider contained in URL", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
var newValue = "Testing"+originalValue;
|
||||
$firstTextElement.sendkeys("Testing"); // send line 1 to the pad
|
||||
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
helper.waitFor(function(){
|
||||
return modifiedValue !== originalValue; // The value has changed so we can..
|
||||
}).done(function(){
|
||||
|
||||
var $timesliderButton = chrome$("#timesliderlink");
|
||||
$timesliderButton.click(); // So click the timeslider link
|
||||
|
||||
helper.waitFor(function(){
|
||||
var iFrameURL = chrome$.window.location.href;
|
||||
if(iFrameURL){
|
||||
return iFrameURL.indexOf("timeslider") !== -1;
|
||||
}else{
|
||||
return false; // the URL hasnt been set yet
|
||||
}
|
||||
}).done(function(){
|
||||
// click the buttons
|
||||
var iFrameURL = chrome$.window.location.href; // get the url
|
||||
var inTimeslider = iFrameURL.indexOf("timeslider") !== -1;
|
||||
expect(inTimeslider).to.be(true); // expect the value to change
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
33
tests/frontend/specs/button_undo.js
Normal file
33
tests/frontend/specs/button_undo.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
describe("undo button", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb); // creates a new pad
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("undo some typing", function(done){
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
// get the first text element inside the editable space
|
||||
var $firstTextElement = inner$("div span").first();
|
||||
var originalValue = $firstTextElement.text(); // get the original value
|
||||
|
||||
$firstTextElement.sendkeys("foo"); // send line 1 to the pad
|
||||
var modifiedValue = $firstTextElement.text(); // get the modified value
|
||||
expect(modifiedValue).not.to.be(originalValue); // expect the value to change
|
||||
|
||||
// get clear authorship button as a variable
|
||||
var $undoButton = chrome$(".buttonicon-undo");
|
||||
// click the button
|
||||
$undoButton.click();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div span").first().text() === originalValue;
|
||||
}).done(function(){
|
||||
var finalValue = inner$("div span").first().text();
|
||||
expect(finalValue).to.be(originalValue); // expect the value to change
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
72
tests/frontend/specs/change_user_name.js
Normal file
72
tests/frontend/specs/change_user_name.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
describe("change username value", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("Remembers the user name after a refresh", function(done) {
|
||||
this.timeout(60000);
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
$usernameInput.click();
|
||||
|
||||
$usernameInput.val('John McLear');
|
||||
$usernameInput.blur();
|
||||
|
||||
setTimeout(function(){ //give it a second to save the username on the server side
|
||||
helper.newPad({ // get a new pad, but don't clear the cookies
|
||||
clearCookies: false
|
||||
, cb: function(){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
expect($usernameInput.val()).to.be('John McLear')
|
||||
done();
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
|
||||
it("Own user name is shown when you enter a chat", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $userButton = chrome$(".buttonicon-showusers");
|
||||
$userButton.click();
|
||||
|
||||
var $usernameInput = chrome$("#myusernameedit");
|
||||
$usernameInput.click();
|
||||
|
||||
$usernameInput.val('John McLear');
|
||||
$usernameInput.blur();
|
||||
|
||||
//click on the chat button to make chat visible
|
||||
var $chatButton = chrome$("#chaticon");
|
||||
$chatButton.click();
|
||||
var $chatInput = chrome$("#chatinput");
|
||||
$chatInput.sendkeys('O hi'); // simulate a keypress of typing JohnMcLear
|
||||
$chatInput.sendkeys('{enter}'); // simulate a keypress of enter actually does evt.which = 10 not 13
|
||||
|
||||
//check if chat shows up
|
||||
helper.waitFor(function(){
|
||||
return chrome$("#chattext").children("p").length !== 0; // wait until the chat message shows up
|
||||
}).done(function(){
|
||||
var $firstChatMessage = chrome$("#chattext").children("p");
|
||||
var containsJohnMcLear = $firstChatMessage.text().indexOf("John McLear") !== -1; // does the string contain John McLear
|
||||
expect(containsJohnMcLear).to.be(true); // expect the first chat message to contain JohnMcLear
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
40
tests/frontend/specs/chat_always_on_screen.js
Normal file
40
tests/frontend/specs/chat_always_on_screen.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
describe("chat always ons creen select", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes chat stick to right side of the screen", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//get the chat selector
|
||||
var $stickychatCheckbox = chrome$("#options-stickychat");
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
$stickychatCheckbox.click();
|
||||
|
||||
//check if chat changed to get the stickychat Class
|
||||
var $chatbox = chrome$("#chatbox");
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(true);
|
||||
|
||||
//select chat always on screen and fire change event
|
||||
$stickychatCheckbox.attr('selected','selected');
|
||||
$stickychatCheckbox.change();
|
||||
$stickychatCheckbox.click();
|
||||
|
||||
//check if chat changed to remove the stickychat Class
|
||||
var hasStickyChatClass = $chatbox.hasClass("stickyChat");
|
||||
expect(hasStickyChatClass).to.be(false);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
133
tests/frontend/specs/embed_value.js
Normal file
133
tests/frontend/specs/embed_value.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
describe("embed links", function(){
|
||||
var objectify = function (str)
|
||||
{
|
||||
var hash = {};
|
||||
var parts = str.split('&');
|
||||
for(var i = 0; i < parts.length; i++)
|
||||
{
|
||||
var keyValue = parts[i].split('=');
|
||||
hash[keyValue[0]] = keyValue[1];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
var checkiFrameCode = function(embedCode, readonly){
|
||||
//turn the code into an html element
|
||||
var $embediFrame = $(embedCode);
|
||||
|
||||
//read and check the frame attributes
|
||||
var width = $embediFrame.attr("width");
|
||||
var height = $embediFrame.attr("height");
|
||||
var name = $embediFrame.attr("name");
|
||||
expect(width).to.be('600');
|
||||
expect(height).to.be('400');
|
||||
expect(name).to.be(readonly ? "embed_readonly" : "embed_readwrite");
|
||||
|
||||
//parse the url
|
||||
var src = $embediFrame.attr("src");
|
||||
var questionMark = src.indexOf("?");
|
||||
var url = src.substr(0,questionMark);
|
||||
var paramsStr = src.substr(questionMark+1);
|
||||
var params = objectify(paramsStr);
|
||||
|
||||
var expectedParams = {
|
||||
showControls: 'true'
|
||||
, showChat: 'true'
|
||||
, showLineNumbers: 'true'
|
||||
, useMonospaceFont: 'false'
|
||||
}
|
||||
|
||||
//check the url
|
||||
if(readonly){
|
||||
expect(url.indexOf("r.") > 0).to.be(true);
|
||||
} else {
|
||||
expect(url).to.be(helper.padChrome$.window.location.href);
|
||||
}
|
||||
|
||||
//check if all parts of the url are like expected
|
||||
expect(params).to.eql(expectedParams);
|
||||
}
|
||||
|
||||
describe("read and write", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
describe("the share link", function(){
|
||||
it("is the actual pad url", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var shareLink = chrome$("#linkinput").val();
|
||||
var padURL = chrome$.window.location.href;
|
||||
expect(shareLink).to.be(padURL);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the embed as iframe code", function(){
|
||||
it("is an iframe with the the correct url parameters and correct size", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var embedCode = chrome$("#embedinput").val();
|
||||
|
||||
checkiFrameCode(embedCode, false)
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when read only option is set", function(){
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
describe("the share link", function(){
|
||||
it("shows a read only url", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
//check read only checkbox, a bit hacky
|
||||
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var shareLink = chrome$("#linkinput").val();
|
||||
var containsReadOnlyLink = shareLink.indexOf("r.") > 0
|
||||
expect(containsReadOnlyLink).to.be(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the embed as iframe code", function(){
|
||||
it("is an iframe with the the correct url parameters and correct size", function(done){
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//open share dropdown
|
||||
chrome$(".buttonicon-embed").click();
|
||||
//check read only checkbox, a bit hacky
|
||||
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
|
||||
|
||||
//get the link of the share field + the actual pad url and compare them
|
||||
var embedCode = chrome$("#embedinput").val();
|
||||
|
||||
checkiFrameCode(embedCode, true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
30
tests/frontend/specs/font_type.js
Normal file
30
tests/frontend/specs/font_type.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
describe("font select", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text monospace", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//get the font menu and monospace option
|
||||
var $viewfontmenu = chrome$("#viewfontmenu");
|
||||
var $monospaceoption = $viewfontmenu.find("[value=monospace]");
|
||||
|
||||
//select monospace and fire change event
|
||||
$monospaceoption.attr('selected','selected');
|
||||
$viewfontmenu.change();
|
||||
|
||||
//check if font changed to monospace
|
||||
var fontFamily = inner$("body").css("font-family").toLowerCase();
|
||||
expect(fontFamily).to.be("monospace");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
99
tests/frontend/specs/helper.js
Normal file
99
tests/frontend/specs/helper.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
describe("the test helper", function(){
|
||||
describe("the newPad method", function(){
|
||||
xit("doesn't leak memory if you creates iframes over and over again", function(done){
|
||||
this.timeout(100000);
|
||||
|
||||
var times = 10;
|
||||
|
||||
var loadPad = function(){
|
||||
helper.newPad(function(){
|
||||
times--;
|
||||
if(times > 0){
|
||||
loadPad();
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadPad();
|
||||
});
|
||||
|
||||
it("gives me 3 jquery instances of chrome, outer and inner", function(done){
|
||||
this.timeout(5000);
|
||||
|
||||
helper.newPad(function(){
|
||||
//check if the jquery selectors have the desired elements
|
||||
expect(helper.padChrome$("#editbar").length).to.be(1);
|
||||
expect(helper.padOuter$("#outerdocbody").length).to.be(1);
|
||||
expect(helper.padInner$("#innerdocbody").length).to.be(1);
|
||||
|
||||
//check if the document object was set correctly
|
||||
expect(helper.padChrome$.window.document).to.be(helper.padChrome$.document);
|
||||
expect(helper.padOuter$.window.document).to.be(helper.padOuter$.document);
|
||||
expect(helper.padInner$.window.document).to.be(helper.padInner$.document);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("the waitFor method", function(){
|
||||
it("takes a timeout and waits long enough", function(done){
|
||||
this.timeout(2000);
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
}, 1500).fail(function(){
|
||||
var duration = new Date().getTime() - startTime;
|
||||
expect(duration).to.be.greaterThan(1400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("takes an interval and checks on every interval", function(done){
|
||||
this.timeout(4000);
|
||||
var checks = 0;
|
||||
|
||||
helper.waitFor(function(){
|
||||
checks++;
|
||||
return false;
|
||||
}, 2000, 100).fail(function(){
|
||||
expect(checks).to.be.greaterThan(10);
|
||||
expect(checks).to.be.lessThan(30);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("returns a deferred object", function(){
|
||||
it("it calls done after success", function(done){
|
||||
helper.waitFor(function(){
|
||||
return true;
|
||||
}).done(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("calls fail after failure", function(done){
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
},0).fail(function(){
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
xit("throws if you don't listen for fails", function(done){
|
||||
var onerror = window.onerror;
|
||||
window.onerror = function(){
|
||||
window.onerror = onerror;
|
||||
done();
|
||||
}
|
||||
|
||||
helper.waitFor(function(){
|
||||
return false;
|
||||
},100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
39
tests/frontend/specs/keystroke_chat.js
Normal file
39
tests/frontend/specs/keystroke_chat.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
describe("send chat message", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("opens chat, sends a message and makes sure it exists on the page", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
var chatValue = "JohnMcLear";
|
||||
|
||||
//click on the chat button to make chat visible
|
||||
var $chatButton = chrome$("#chaticon");
|
||||
$chatButton.click();
|
||||
var $chatInput = chrome$("#chatinput");
|
||||
$chatInput.sendkeys('JohnMcLear'); // simulate a keypress of typing JohnMcLear
|
||||
$chatInput.sendkeys('{enter}'); // simulate a keypress of enter actually does evt.which = 10 not 13
|
||||
|
||||
//check if chat shows up
|
||||
helper.waitFor(function(){
|
||||
return chrome$("#chattext").children("p").length !== 0; // wait until the chat message shows up
|
||||
}).done(function(){
|
||||
var $firstChatMessage = chrome$("#chattext").children("p");
|
||||
var containsMessage = $firstChatMessage.text().indexOf("JohnMcLear") !== -1; // does the string contain JohnMcLear?
|
||||
expect(containsMessage).to.be(true); // expect the first chat message to contain JohnMcLear
|
||||
|
||||
// do a slightly more thorough check
|
||||
var username = $firstChatMessage.children("b");
|
||||
var usernameValue = username.text();
|
||||
var time = $firstChatMessage.children(".time");
|
||||
var timeValue = time.text();
|
||||
var expectedStringIncludingUserNameAndTime = usernameValue + timeValue + " " + "JohnMcLear";
|
||||
expect(expectedStringIncludingUserNameAndTime).to.be($firstChatMessage.text());
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
37
tests/frontend/specs/keystroke_delete.js
Normal file
37
tests/frontend/specs/keystroke_delete.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
describe("delete keystroke", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text delete", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// get the original length of this element
|
||||
var elementLength = $firstTextElement.text().length;
|
||||
|
||||
// get the original string value minus the last char
|
||||
var originalTextValue = $firstTextElement.text();
|
||||
originalTextValueMinusFirstChar = originalTextValue.substring(1, originalTextValue.length );
|
||||
|
||||
// simulate key presses to delete content
|
||||
$firstTextElement.sendkeys('{leftarrow}'); // simulate a keypress of the left arrow key
|
||||
$firstTextElement.sendkeys('{del}'); // simulate a keypress of delete
|
||||
|
||||
//ace creates a new dom element when you press a keystroke, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
// get the new length of this element
|
||||
var newElementLength = $newFirstTextElement.text().length;
|
||||
|
||||
//expect it to be one char less in length
|
||||
expect(newElementLength).to.be((elementLength-1));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
34
tests/frontend/specs/keystroke_enter.js
Normal file
34
tests/frontend/specs/keystroke_enter.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
describe("enter keystroke", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("creates a enw line & puts cursor onto a new line", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var $firstTextElement = inner$("div").first();
|
||||
|
||||
// get the original string value minus the last char
|
||||
var originalTextValue = $firstTextElement.text();
|
||||
|
||||
// simulate key presses to enter content
|
||||
$firstTextElement.sendkeys('{enter}');
|
||||
|
||||
//ace creates a new dom element when you press a keystroke, so just get the first text element again
|
||||
var $newFirstTextElement = inner$("div").first();
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().text() === "";
|
||||
}).done(function(){
|
||||
var $newSecondLine = inner$("div").first().next();
|
||||
var newFirstTextElementValue = inner$("div").first().text();
|
||||
expect(newFirstTextElementValue).to.be(""); // expect the first line to be blank
|
||||
expect($newSecondLine.text()).to.be(originalTextValue); // expect the second line to be the same as the original first line.
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
24
tests/frontend/specs/keystroke_urls_become_clickable.js
Normal file
24
tests/frontend/specs/keystroke_urls_become_clickable.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
describe("urls", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("when you enter an url, it becomes clickable", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//get the first text element out of the inner iframe
|
||||
var firstTextElement = inner$("div").first();
|
||||
|
||||
// simulate key presses to delete content
|
||||
firstTextElement.sendkeys('{selectall}'); // select all
|
||||
firstTextElement.sendkeys('{del}'); // clear the first line
|
||||
firstTextElement.sendkeys('http://etherpad.org'); // insert a URL
|
||||
|
||||
helper.waitFor(function(){
|
||||
return inner$("div").first().find("a").length === 1;
|
||||
}, 2000).done(done);
|
||||
});
|
||||
});
|
81
tests/frontend/specs/language.js
Normal file
81
tests/frontend/specs/language.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
describe("Language select and change", function(){
|
||||
//create a new pad before each test run
|
||||
beforeEach(function(cb){
|
||||
helper.newPad(cb);
|
||||
this.timeout(60000);
|
||||
});
|
||||
|
||||
it("makes text german", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//click the language button
|
||||
var $language = chrome$("#languagemenu");
|
||||
var $languageoption = $language.find("[value=de]");
|
||||
|
||||
//select german
|
||||
$languageoption.attr('selected','selected');
|
||||
$language.change();
|
||||
|
||||
var localizedEventFired = false;
|
||||
$(chrome$.window).bind('localized', function() {
|
||||
localizedEventFired = true;
|
||||
})
|
||||
|
||||
helper.waitFor(function() { return localizedEventFired;})
|
||||
.done(function(){
|
||||
//get the value of the bold button
|
||||
var $boldButton = chrome$(".buttonicon-bold").parent();
|
||||
|
||||
//get the title of the bold button
|
||||
var boldButtonTitle = $boldButton[0]["title"];
|
||||
|
||||
//check if the language is now german
|
||||
expect(boldButtonTitle).to.be("Fett (Strg-B)");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("makes text English", function(done) {
|
||||
var inner$ = helper.padInner$;
|
||||
var chrome$ = helper.padChrome$;
|
||||
|
||||
//click on the settings button to make settings visible
|
||||
var $settingsButton = chrome$(".buttonicon-settings");
|
||||
$settingsButton.click();
|
||||
|
||||
//click the language button
|
||||
var $language = chrome$("#languagemenu");
|
||||
var $languageoption = $language.find("[value=en]");
|
||||
|
||||
//select german
|
||||
$languageoption.attr('selected','selected');
|
||||
$language.change();
|
||||
|
||||
var localizedEventFired = false;
|
||||
$(chrome$.window).bind('localized', function() {
|
||||
localizedEventFired = true;
|
||||
})
|
||||
|
||||
helper.waitFor(function() { return localizedEventFired;})
|
||||
.done(function(){
|
||||
|
||||
//get the value of the bold button
|
||||
var $boldButton = chrome$(".buttonicon-bold").parent();
|
||||
|
||||
//get the title of the bold button
|
||||
var boldButtonTitle = $boldButton[0]["title"];
|
||||
|
||||
//check if the language is now English
|
||||
expect(boldButtonTitle).to.be("Bold (Ctrl-B)");
|
||||
done();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
2
tests/frontend/travis/.gitignore
vendored
Normal file
2
tests/frontend/travis/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
sauce_connect.log
|
||||
sauce_connect.log.*
|
110
tests/frontend/travis/remote_runner.js
Normal file
110
tests/frontend/travis/remote_runner.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
var srcFolder = "../../../src/node_modules/";
|
||||
var wd = require(srcFolder + "wd");
|
||||
var async = require(srcFolder + "async");
|
||||
|
||||
var config = {
|
||||
host: "ondemand.saucelabs.com"
|
||||
, port: 80
|
||||
, username: process.env.SAUCE_USER
|
||||
, accessKey: process.env.SAUCE_KEY
|
||||
}
|
||||
|
||||
var allTestsPassed = true;
|
||||
|
||||
var sauceTestWorker = async.queue(function (testSettings, callback) {
|
||||
var browser = wd.remote(config.host, config.port, config.username, config.accessKey);
|
||||
var browserChain = browser.chain();
|
||||
var name = process.env.GIT_HASH + " - " + testSettings.browserName + " " + testSettings.version + ", " + testSettings.platform;
|
||||
testSettings.name = name;
|
||||
testSettings["public"] = true;
|
||||
testSettings["build"] = process.env.GIT_HASH;
|
||||
|
||||
browserChain.init(testSettings).get("http://localhost:9001/tests/frontend/", function(){
|
||||
var url = "https://saucelabs.com/jobs/" + browser.sessionID;
|
||||
console.log("Remote sauce test '" + name + "' started! " + url);
|
||||
|
||||
//tear down the test excecution
|
||||
var stopSauce = function(success){
|
||||
getStatusInterval && clearInterval(getStatusInterval);
|
||||
clearTimeout(timeout);
|
||||
|
||||
browserChain.quit();
|
||||
|
||||
if(!success){
|
||||
allTestsPassed = false;
|
||||
}
|
||||
|
||||
var testResult = knownConsoleText.replace(/\[red\]/g,'\x1B[31m').replace(/\[yellow\]/g,'\x1B[33m')
|
||||
.replace(/\[green\]/g,'\x1B[32m').replace(/\[clear\]/g, '\x1B[39m');
|
||||
testResult = testResult.split("\\n").map(function(line){
|
||||
return "[" + testSettings.browserName + (testSettings.version === "" ? '' : (" " + testSettings.version)) + "] " + line;
|
||||
}).join("\n");
|
||||
|
||||
console.log(testResult);
|
||||
console.log("Remote sauce test '" + name + "' finished! " + url);
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
//timeout for the case the test hangs
|
||||
var timeout = setTimeout(function(){
|
||||
stopSauce(false);
|
||||
}, 60000 * 10);
|
||||
|
||||
var knownConsoleText = "";
|
||||
var getStatusInterval = setInterval(function(){
|
||||
browserChain.eval("$('#console').text()", function(err, consoleText){
|
||||
if(!consoleText || err){
|
||||
return;
|
||||
}
|
||||
knownConsoleText = consoleText;
|
||||
|
||||
if(knownConsoleText.indexOf("FINISHED") > 0){
|
||||
var success = knownConsoleText.indexOf("FAILED") === -1;
|
||||
stopSauce(success);
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
}, 5); //run 5 tests in parrallel
|
||||
|
||||
// Firefox
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
, 'browserName' : 'firefox'
|
||||
, 'version' : ''
|
||||
});
|
||||
|
||||
// Chrome
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Linux'
|
||||
, 'browserName' : 'googlechrome'
|
||||
, 'version' : ''
|
||||
});
|
||||
|
||||
// IE 8
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2003'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '8'
|
||||
});
|
||||
|
||||
// IE 9
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2008'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '9'
|
||||
});
|
||||
|
||||
// IE 10
|
||||
sauceTestWorker.push({
|
||||
'platform' : 'Windows 2012'
|
||||
, 'browserName' : 'iexplore'
|
||||
, 'version' : '10'
|
||||
});
|
||||
|
||||
sauceTestWorker.drain = function() {
|
||||
setTimeout(function(){
|
||||
process.exit(allTestsPassed ? 0 : 1);
|
||||
}, 3000);
|
||||
}
|
18
tests/frontend/travis/runner.sh
Executable file
18
tests/frontend/travis/runner.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/bin/sh
|
||||
|
||||
#Move to the base folder
|
||||
cd `dirname $0`
|
||||
|
||||
#start etherpad lite
|
||||
../../../bin/run.sh > /dev/null &
|
||||
sleep 10
|
||||
|
||||
#start remote runner
|
||||
node remote_runner.js
|
||||
exit_code=$?
|
||||
|
||||
kill $!
|
||||
kill $(cat /tmp/sauce.pid)
|
||||
sleep 30
|
||||
|
||||
exit $exit_code
|
16
tests/frontend/travis/sauce_tunnel.sh
Executable file
16
tests/frontend/travis/sauce_tunnel.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
# download and unzip the sauce connector
|
||||
curl http://saucelabs.com/downloads/Sauce-Connect-latest.zip > /tmp/sauce.zip
|
||||
unzip /tmp/sauce.zip -d /tmp
|
||||
|
||||
# start the sauce connector in background and make sure it doesn't output the secret key
|
||||
(java -jar /tmp/Sauce-Connect.jar $SAUCE_USER $SAUCE_KEY -f /tmp/tunnel > /dev/null )&
|
||||
|
||||
# save the sauce pid in a file
|
||||
echo $! > /tmp/sauce.pid
|
||||
|
||||
# wait for the tunnel to build up
|
||||
while [ ! -e "/tmp/tunnel" ]
|
||||
do
|
||||
sleep 1
|
||||
done
|
Loading…
Reference in a new issue