Merge branch 'release/1.2.91'

This commit is contained in:
John McLear 2013-04-06 14:40:02 +01:00
commit 069319fda1
68 changed files with 1392 additions and 674 deletions

View file

@ -1,3 +1,27 @@
# 1.2.91
* NEW: Authors can now send custom object messages to other Authors making 3 way conversations possible. This introduces WebRTC plugin support.
* NEW: Hook for Chat Messages Allows for Desktop Notification support
* NEW: FreeBSD installation docs
* NEW: Ctrl S for save revision makes the Icon glow for a few sconds.
* NEW: Various hooks and expose the document ACE object
* NEW: Plugin page revamp makes finding and installing plugins more sane.
* NEW: Icon to enable sticky chat from the Chat box
* Fix: Cookies inside of plugins
* Fix: Don't leak event emitters when accessing admin/plugins
* Fix: Don't allow user to send messages after they have been "kicked" from a pad
* Fix: Refactor Caret navigation with Arrow and Pageup/down keys stops cursor being lost
* Fix: Long lines in Firefox now wrap properly
* Fix: Session Disconnect limit is increased from 10 to 20 to support slower restarts
* Fix: Support Node 0.10
* Fix: Log HTTP on DEBUG log level
* Fix: Server wont crash on import fails on 0 file import.
* Fix: Import no longer fails consistantly
* Fix: Language support for non existing languages
* Fix: Mobile support for chat notifications are now usable
* Fix: Re-Enable Editbar buttons on reconnect
* Fix: Clearing authorship colors no longer disconnects all clients
* Other: New debug information for sessions
# 1.2.9
* Fix: MAJOR Security issue, where a hacker could submit content as another user
* Fix: security issue due to unescaped user input
@ -6,7 +30,7 @@
* Fix: PadUsers API endpoint
* NEW: A script to import data to all dbms
* NEW: Add authorId to chat and userlist as a data attribute
* NEW Refactor and fix our frontend tests
* NEW: Refactor and fix our frontend tests
* NEW: Localisation updates

View file

@ -11,8 +11,8 @@ To make sure everybody is going in the same direction:
* easy to install for admins and easy to use for people
* easy to integrate into other apps, but also usable as standalone
* using less resources on server side
* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core
Also, keep it maintainable. We don't wanna end ob as the monster Etherpad was!
* extensible, as much functionality should be extendable with plugins so changes don't have to be done in core.
Also, keep it maintainable. We don't wanna end up as the monster Etherpad was!
## How to work with git?
* Don't work in your master branch.
@ -62,4 +62,4 @@ The docs are in the `doc/` folder in the git repository, so people can easily fi
Documentation should be kept up-to-date. This means, whenever you add a new API method, add a new hook or change the database model, pack the relevant changes to the docs in the same pull request.
You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.
You can build the docs e.g. produce html, using `make docs`. At some point in the future we will provide an online documentation. The current documentation in the github wiki should always reflect the state of `master` (!), since there are no docs in master, yet.

View file

@ -1,36 +1,19 @@
# Making collaborative editing the standard on the web
# A really-real time collaborative word processor for the web
![alt text](http://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad")
# 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 is a really-real time collaborative editor maintained by the Etherpad Community.
**Etherpad vs Etherpad lite**
<table>
<tr>
<td>&nbsp;</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 is written in Javascript(99.9%) on both the server and client so it's easy for developers to maintain and add new features. Because of this Etherpad has tons of customizations that you can leverage.
Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API.
Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
There's also a full-featured plugin framework, allowing you to easily add your own features.
Finally, Etherpad Lite comes with translations into tons of different languages!
There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparce and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control.
Finally, Etherpad comes with translations into most languages! Users are automatically delivered the correct language for their local settings.
**Visit [beta.etherpad.org](http://beta.etherpad.org) to test it live**
@ -38,6 +21,8 @@ Also, check out the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**,
# Installation
Etherpad works with node v0.8 and v0.10, only. (We don't suppot v0.6)
## Windows
### Prebuilt windows package
@ -62,10 +47,11 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind
[Next steps](#next-steps).
## Linux
## GNU/Linux and other UNIX-like systems
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"`
*For FreeBSD*: `portinstall node node, npm and git (optional)`
Additionally, you'll need [node.js](http://nodejs.org) installed, Ideally the latest stable version, be careful of installing nodejs from apt.
@ -83,9 +69,9 @@ 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 can initially 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 instances from the same installation.) Once you have access to your /admin section settings can be modified through the web browser.
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.
You should use a dedicated database such as "mysql", if you are planning on using etherpad-in a production environment, since the "dirtyDB" database driver is only for testing and/or development purposes.
## Helpful resources
The [wiki](https://github.com/ether/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.
@ -95,11 +81,11 @@ 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).
Read this [git guide](http://learn.github.com/p/intro.html) and watch this [video on getting started with Etherpad 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`.
You can debug Etherpad 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/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading).
@ -111,7 +97,7 @@ Look at the [TODO list](https://github.com/ether/etherpad-lite/wiki/TODO) and ou
Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), 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)!
Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our busy freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)!
# Modules created for this project

View file

@ -44,8 +44,8 @@ fi
#check node version
NODE_VERSION=$(node --version)
NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ]; then
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.6.x or v0.8.x" >&2
if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ]; then
echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.8.x or v0.10.x" >&2
exit 1
fi

View file

@ -8,7 +8,7 @@ cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && ex
echo _
echo Checking node version...
set check_version="if(['6','8'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.6.x or v0.8.x'); process.exit(1) }"
set check_version="if(['8','10'].indexOf(process.version.split('.')[1].toString()) === -1) { console.log('You are running a wrong version of Node. Etherpad Lite requires v0.8.x or v0.10.x'); process.exit(1) }"
cmd /C node -e %check_version% || exit /B 1
echo _

View file

@ -143,6 +143,20 @@ Things in context:
This hook is called on the client side whenever a user joins or changes. This can be used to create notifications or an alternate user list.
## chatNewMessage
Called from: src/static/js/chat.js
Things in context:
1. authorName - The user that wrote this message
2. author - The authorID of the user that wrote the message
2. text - the message text
3. sticky (boolean) - if you want the gritter notification bubble to fade out on its own or just sit there
3. timestamp - the timestamp of the chat message
4. timeStr - the timestamp as a formatted string
This hook is called on the client side whenever a chat message is received from the server. It can be used to create different notifications for chat messages.
## collectContentPre
Called from: src/static/js/contentcollector.js

View file

@ -458,4 +458,4 @@ returns ok when the current api token is valid
lists all pads on this epl instance
*Example returns:*
* `{code: 0, message:"ok", data: ["testPad", "thePadsOfTheOthers"]}`
* `{code: 0, message:"ok", data: {padIDs: ["testPad", "thePadsOfTheOthers"]}}`

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Llimpiar los colores d'autor\u00eda",
"pad.toolbar.import_export.title": "Importar\/Esportar ente distintos formatos de ficheru",
"pad.toolbar.timeslider.title": "Eslizador de tiempu",
"pad.toolbar.savedRevision.title": "Revisiones guardaes",
"pad.toolbar.savedRevision.title": "Guardar revisi\u00f3n",
"pad.toolbar.settings.title": "Configuraci\u00f3n",
"pad.toolbar.embed.title": "Incrustar esti bloc",
"pad.toolbar.showusers.title": "Amosar los usuarios d'esti bloc",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Alderique en pantalla siempres",
"pad.settings.colorcheck": "Colores d'autor\u00eda",
"pad.settings.linenocheck": "N\u00famberos de llinia",
"pad.settings.rtlcheck": "\u00bfLleer el conten\u00edu de drecha a izquierda?",
"pad.settings.fontType": "Tipograf\u00eda:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monoespaciada",

View file

@ -0,0 +1,61 @@
{
"@metadata": {
"authors": [
"Jim-by",
"Wizardist"
]
},
"index.newPad": "\u0421\u0442\u0432\u0430\u0440\u044b\u0446\u044c",
"index.createOpenPad": "\u0446\u0456 \u0442\u0432\u0430\u0440\u044b\u0446\u044c\/\u0430\u0434\u043a\u0440\u044b\u0446\u044c \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442 \u0437 \u043d\u0430\u0437\u0432\u0430\u0439:",
"pad.toolbar.bold.title": "\u0422\u043e\u045e\u0441\u0442\u044b (Ctrl-B)",
"pad.toolbar.italic.title": "\u041a\u0443\u0440\u0441\u0456\u045e (Ctrl-I)",
"pad.toolbar.underline.title": "\u041f\u0430\u0434\u043a\u0440\u044d\u0441\u044c\u043b\u0456\u0432\u0430\u043d\u044c\u043d\u0435 (Ctrl-U)",
"pad.toolbar.strikethrough.title": "\u0417\u0430\u043a\u0440\u044d\u0441\u044c\u043b\u0456\u0432\u0430\u043d\u044c\u043d\u0435",
"pad.toolbar.ol.title": "\u0423\u043f\u0430\u0440\u0430\u0434\u043a\u0430\u0432\u0430\u043d\u044b \u0441\u044c\u043f\u0456\u0441",
"pad.toolbar.ul.title": "\u041d\u0435\u045e\u043f\u0430\u0440\u0430\u0434\u043a\u0430\u0432\u0430\u043d\u044b \u0441\u044c\u043f\u0456\u0441",
"pad.toolbar.indent.title": "\u0412\u043e\u0434\u0441\u0442\u0443\u043f",
"pad.toolbar.unindent.title": "\u0412\u044b\u0441\u0442\u0443\u043f",
"pad.toolbar.undo.title": "\u0421\u043a\u0430\u0441\u0430\u0432\u0430\u0446\u044c(Ctrl-Z)",
"pad.toolbar.redo.title": "\u0412\u044f\u0440\u043d\u0443\u0446\u044c (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "\u041f\u0440\u044b\u0431\u0440\u0430\u0446\u044c \u043a\u043e\u043b\u0435\u0440 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0443",
"pad.toolbar.import_export.title": "\u0406\u043c\u043f\u0430\u0440\u0442\/\u042d\u043a\u0441\u043f\u0430\u0440\u0442 \u0437 \u0432\u044b\u043a\u0430\u0440\u044b\u0441\u0442\u0430\u043d\u044c\u043d\u0435 \u0440\u043e\u0437\u043d\u044b\u0445 \u0444\u0430\u0440\u043c\u0430\u0442\u0430\u045e \u0444\u0430\u0439\u043b\u0430\u045e",
"pad.toolbar.timeslider.title": "\u0428\u043a\u0430\u043b\u0430 \u0447\u0430\u0441\u0443",
"pad.toolbar.savedRevision.title": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c \u0432\u044d\u0440\u0441\u0456\u044e",
"pad.toolbar.settings.title": "\u041d\u0430\u043b\u0430\u0434\u044b",
"pad.toolbar.embed.title": "\u0423\u0431\u0443\u0434\u0430\u0432\u0430\u0446\u044c \u0433\u044d\u0442\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442",
"pad.toolbar.showusers.title": "\u041f\u0430\u043a\u0430\u0437\u0430\u0446\u044c \u043a\u0430\u0440\u044b\u0441\u0442\u0430\u043b\u044c\u043d\u0456\u043a\u0430\u045e \u0443 \u0433\u044d\u0442\u044b\u043c \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0446\u0435",
"pad.colorpicker.save": "\u0417\u0430\u0445\u0430\u0432\u0430\u0446\u044c",
"pad.colorpicker.cancel": "\u0421\u043a\u0430\u0441\u0430\u0432\u0430\u0446\u044c",
"pad.loading": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430...",
"pad.passwordRequired": "\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0443 \u0434\u0430 \u0433\u044d\u0442\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430 \u043f\u0430\u0442\u0440\u044d\u0431\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u044c",
"pad.permissionDenied": "\u0412\u044b \u043d\u044f \u043c\u0430\u0435\u0446\u0435 \u0434\u0430\u0437\u0432\u043e\u043b\u0443 \u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f \u0434\u0430 \u0433\u044d\u0442\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430",
"pad.wrongPassword": "\u0412\u044b \u045e\u0432\u044f\u043b\u0456 \u043d\u044f\u0441\u043b\u0443\u0448\u043d\u044b \u043f\u0430\u0440\u043e\u043b\u044c",
"pad.settings.padSettings": "\u041d\u0430\u043b\u0430\u0434\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430",
"pad.settings.myView": "\u041c\u043e\u0439 \u0432\u044b\u0433\u043b\u044f\u0434",
"pad.settings.stickychat": "\u0417\u0430\u045e\u0441\u0451\u0434\u044b \u043f\u0430\u043a\u0430\u0437\u0432\u0430\u0446\u044c \u0447\u0430\u0442",
"pad.settings.colorcheck": "\u041a\u043e\u043b\u0435\u0440\u044b \u0430\u045e\u0442\u0430\u0440\u0441\u0442\u0432\u0430",
"pad.settings.linenocheck": "\u041d\u0443\u043c\u0430\u0440\u044b \u0440\u0430\u0434\u043a\u043e\u045e",
"pad.settings.rtlcheck": "\u0422\u044d\u043a\u0441\u0442 \u0441\u043f\u0440\u0430\u0432\u0430-\u043d\u0430\u043b\u0435\u0432\u0430",
"pad.settings.fontType": "\u0422\u044b\u043f \u0448\u0440\u044b\u0444\u0442\u0443:",
"pad.settings.fontType.normal": "\u0417\u0432\u044b\u0447\u0430\u0439\u043d\u044b",
"pad.settings.fontType.monospaced": "\u041c\u043e\u043d\u0430\u0448\u044b\u0440\u044b\u043d\u043d\u044b",
"pad.settings.globalView": "\u0410\u0433\u0443\u043b\u044c\u043d\u044b \u0432\u044b\u0433\u043b\u044f\u0434",
"pad.settings.language": "\u041c\u043e\u0432\u0430:",
"pad.importExport.import_export": "\u0406\u043c\u043f\u0430\u0440\u0442\/\u042d\u043a\u0441\u043f\u0430\u0440\u0442",
"pad.importExport.import": "\u0417\u0430\u0433\u0440\u0443\u0437\u0456\u0436\u0430\u0439\u0446\u0435 \u043b\u044e\u0431\u044b\u044f \u0442\u044d\u043a\u0441\u0442\u0430\u0432\u044b\u044f \u0444\u0430\u0439\u043b\u044b \u0430\u0431\u043e \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u044b",
"pad.importExport.importSuccessful": "\u041f\u0430\u0441\u044c\u043f\u044f\u0445\u043e\u0432\u0430!",
"pad.importExport.export": "\u042d\u043a\u0441\u043f\u0430\u0440\u0442\u0430\u0432\u0430\u0446\u044c \u0431\u044f\u0433\u0443\u0447\u044b \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442 \u044f\u043a:",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "\u041f\u0440\u043e\u0441\u0442\u044b \u0442\u044d\u043a\u0441\u0442",
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.modals.connected": "\u041f\u0430\u0434\u043b\u0443\u0447\u044b\u043b\u0456\u0441\u044f.",
"pad.modals.reconnecting": "\u041f\u0435\u0440\u0430\u043f\u0430\u0434\u043b\u0443\u0447\u044d\u043d\u044c\u043d\u0435 \u0434\u0430 \u0432\u0430\u0448\u0430\u0433\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430...",
"pad.modals.forcereconnect": "\u041f\u0440\u044b\u043c\u0443\u0441\u043e\u0432\u0430\u0435 \u043f\u0435\u0440\u0430\u043f\u0430\u0434\u043b\u0443\u0447\u044d\u043d\u044c\u043d\u0435",
"pad.share": "\u041f\u0430\u0434\u0437\u044f\u043b\u0456\u0446\u0446\u0430 \u0434\u0430\u043a\u0443\u043c\u044d\u043d\u0442\u0430\u043c",
"pad.share.readonly": "\u0422\u043e\u043b\u044c\u043a\u0456 \u0434\u043b\u044f \u0447\u044b\u0442\u0430\u043d\u044c\u043d\u044f",
"pad.share.link": "\u0421\u043f\u0430\u0441\u044b\u043b\u043a\u0430",
"pad.chat": "\u0427\u0430\u0442"
}

View file

@ -37,6 +37,7 @@
"pad.settings.stickychat": "Diskwel ar flap bepred",
"pad.settings.colorcheck": "Livio\u00f9 anaout",
"pad.settings.linenocheck": "Niverenno\u00f9 linenno\u00f9",
"pad.settings.rtlcheck": "Lenn an danvez a-zehou da gleiz ?",
"pad.settings.fontType": "Seurt font :",
"pad.settings.fontType.normal": "Reizh",
"pad.settings.fontType.monospaced": "Monospas",

View file

@ -28,6 +28,7 @@
"pad.settings.stickychat": "Xateja sempre a la pantalla",
"pad.settings.colorcheck": "Colors d'autoria",
"pad.settings.linenocheck": "N\u00fameros de l\u00ednia",
"pad.settings.rtlcheck": "Llegir el contingut de dreta a esquerra?",
"pad.settings.fontType": "Tipus de lletra:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "D'amplada fixa",

View file

@ -1,9 +1,10 @@
{
"@metadata": {
"authors": [
"Christian List",
"Peter Alberti"
]
"authors": {
"0": "Christian List",
"1": "Peter Alberti",
"3": "Steenth"
}
},
"index.newPad": "Ny Pad",
"index.createOpenPad": "eller opret\/\u00e5bn en Pad med navnet:",
@ -20,7 +21,7 @@
"pad.toolbar.clearAuthorship.title": "Fjern farver for forfatterskab",
"pad.toolbar.import_export.title": "Import\/eksport fra\/til forskellige filformater",
"pad.toolbar.timeslider.title": "Timeslider",
"pad.toolbar.savedRevision.title": "Gemte revisioner",
"pad.toolbar.savedRevision.title": "Gem Revision",
"pad.toolbar.settings.title": "Indstillinger",
"pad.toolbar.embed.title": "Integrer denne pad",
"pad.toolbar.showusers.title": "Vis brugere p\u00e5 denne pad",
@ -35,6 +36,7 @@
"pad.settings.stickychat": "Chat altid p\u00e5 sk\u00e6rmen",
"pad.settings.colorcheck": "Forfatterskabsfarver",
"pad.settings.linenocheck": "Linjenumre",
"pad.settings.rtlcheck": "L\u00e6se indhold fra h\u00f8jre mod venstre?",
"pad.settings.fontType": "Skrifttype:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Fastbredde",

View file

@ -37,6 +37,7 @@
"pad.settings.stickychat": "Chat immer anzeigen",
"pad.settings.colorcheck": "Autorenfarben anzeigen",
"pad.settings.linenocheck": "Zeilennummern",
"pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?",
"pad.settings.fontType": "Schriftart:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "\u039a\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03a7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd \u03a3\u03c5\u03bd\u03c4\u03b1\u03ba\u03c4\u03ce\u03bd",
"pad.toolbar.import_export.title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03b1\u03c0\u03cc\/\u03c3\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03bf\u03cd\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c5\u03c2 \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd",
"pad.toolbar.timeslider.title": "\u03a7\u03c1\u03bf\u03bd\u03bf\u03b4\u03b9\u03ac\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1",
"pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b5\u03c2 \u0391\u03bd\u03b1\u03b8\u03b5\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2",
"pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u0391\u03bd\u03b1\u03b8\u03b5\u03ce\u03c1\u03b7\u03c3\u03b7\u03c2",
"pad.toolbar.settings.title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2",
"pad.toolbar.embed.title": "\u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 pad",
"pad.toolbar.showusers.title": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 pad",
@ -37,6 +37,7 @@
"pad.settings.stickychat": "\u0397 \u03a3\u03c5\u03bd\u03bf\u03bc\u03b9\u03bb\u03af\u03b1 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03ac\u03bd\u03c4\u03b1 \u03bf\u03c1\u03b1\u03c4\u03ae",
"pad.settings.colorcheck": "\u03a7\u03c1\u03ce\u03bc\u03b1\u03c4\u03b1 \u03c3\u03c5\u03bd\u03c4\u03ac\u03ba\u03c4\u03b7",
"pad.settings.linenocheck": "\u0391\u03c1\u03b9\u03b8\u03bc\u03bf\u03af \u03b3\u03c1\u03b1\u03bc\u03bc\u03ae\u03c2",
"pad.settings.rtlcheck": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b2\u03ac\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03c0\u03b5\u03c1\u03b9\u03b5\u03c7\u03cc\u03bc\u03b5\u03bd\u03bf \u03b1\u03c0\u03cc \u03b4\u03b5\u03be\u03b9\u03ac \u03c0\u03c1\u03bf\u03c2 \u03c4\u03b1 \u03b1\u03c1\u03b9\u03c3\u03c4\u03b5\u03c1\u03ac;",
"pad.settings.fontType": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b3\u03c1\u03b1\u03bc\u03bc\u03b1\u03c4\u03bf\u03c3\u03b5\u03b9\u03c1\u03ac\u03c2:",
"pad.settings.fontType.normal": "\u039a\u03b1\u03bd\u03bf\u03bd\u03b9\u03ba\u03ae",
"pad.settings.fontType.monospaced": "\u039a\u03b1\u03b8\u03bf\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c5 \u03c0\u03bb\u03ac\u03c4\u03bf\u03c5\u03c2",

View file

@ -16,7 +16,7 @@
"pad.toolbar.timeslider.title": "Timeslider",
"pad.toolbar.savedRevision.title": "Save Revision",
"pad.toolbar.settings.title": "Settings",
"pad.toolbar.embed.title": "Embed this pad",
"pad.toolbar.embed.title": "Share and Embed this pad",
"pad.toolbar.showusers.title": "Show the users on this pad",
"pad.colorpicker.save": "Save",
"pad.colorpicker.cancel": "Cancel",
@ -113,4 +113,4 @@
"pad.impexp.importfailed": "Import failed",
"pad.impexp.copypaste": "Please copy paste",
"pad.impexp.exportdisabled": "Exporting as {{type}} format is disabled. Please contact your system administrator for details."
}
}

View file

@ -3,7 +3,8 @@
"authors": {
"0": "BMRG14",
"1": "Dalba",
"3": "ZxxZxxZ"
"3": "ZxxZxxZ",
"4": "\u0627\u0644\u0646\u0627\u0632"
}
},
"index.newPad": "\u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a \u062a\u0627\u0632\u0647",
@ -21,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u06cc",
"pad.toolbar.import_export.title": "\u062f\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc\/\u0628\u0631\u0648\u0646\u200c\u0631\u06cc\u0632\u06cc \u0627\u0632\/\u0628\u0647 \u0642\u0627\u0644\u0628\u200c\u0647\u0627\u06cc \u0645\u062e\u062a\u0644\u0641",
"pad.toolbar.timeslider.title": "\u0627\u0633\u0644\u0627\u06cc\u062f\u0631 \u0632\u0645\u0627\u0646",
"pad.toolbar.savedRevision.title": "\u0630\u062e\u06cc\u0631\u0647\u200c\u0633\u0627\u0632\u06cc \u0646\u0633\u062e\u0647",
"pad.toolbar.savedRevision.title": "\u0630\u062e\u06cc\u0631\u0647\u200c\u06cc \u0628\u0627\u0632\u0646\u0648\u06cc\u0633\u06cc",
"pad.toolbar.settings.title": "\u062a\u0646\u0638\u06cc\u0645\u0627\u062a",
"pad.toolbar.embed.title": "\u062c\u0627\u0633\u0627\u0632\u06cc \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
"pad.toolbar.showusers.title": "\u0646\u0645\u0627\u06cc\u0634 \u06a9\u0627\u0631\u0628\u0631\u0627\u0646 \u062f\u0631 \u0627\u06cc\u0646 \u062f\u0641\u062a\u0631\u0686\u0647 \u06cc\u0627\u062f\u062f\u0627\u0634\u062a",
@ -36,6 +37,7 @@
"pad.settings.stickychat": "\u06af\u0641\u062a\u06af\u0648 \u0647\u0645\u06cc\u0634\u0647 \u0631\u0648\u06cc \u0635\u0641\u062d\u0647 \u0646\u0645\u0627\u06cc\u0634 \u0628\u0627\u0634\u062f",
"pad.settings.colorcheck": "\u0631\u0646\u06af\u200c\u0647\u0627\u06cc \u0646\u0648\u06cc\u0633\u0646\u062f\u06af\u06cc",
"pad.settings.linenocheck": "\u0634\u0645\u0627\u0631\u0647\u200c\u06cc \u062e\u0637\u0648\u0637",
"pad.settings.rtlcheck": "\u062e\u0648\u0627\u0646\u062f\u0646 \u0645\u062d\u062a\u0648\u0627 \u0627\u0632 \u0631\u0627\u0633\u062a \u0628\u0647 \u0686\u067e\u061f",
"pad.settings.fontType": "\u0646\u0648\u0639 \u0642\u0644\u0645:",
"pad.settings.fontType.normal": "\u0633\u0627\u062f\u0647",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -39,6 +39,7 @@
"pad.settings.stickychat": "Keskustelu aina n\u00e4kyviss\u00e4",
"pad.settings.colorcheck": "Kirjoittajav\u00e4rit",
"pad.settings.linenocheck": "Rivinumerot",
"pad.settings.rtlcheck": "Luetaanko sis\u00e4lt\u00f6 oikealta vasemmalle?",
"pad.settings.fontType": "Kirjasintyyppi:",
"pad.settings.fontType.normal": "normaali",
"pad.settings.fontType.monospaced": "tasalevyinen",

View file

@ -28,7 +28,7 @@
"pad.toolbar.clearAuthorship.title": "Effacer les couleurs identifiant les auteurs",
"pad.toolbar.import_export.title": "Importer\/Exporter de\/vers un format de fichier diff\u00e9rent",
"pad.toolbar.timeslider.title": "Historique dynamique",
"pad.toolbar.savedRevision.title": "Versions enregistr\u00e9es",
"pad.toolbar.savedRevision.title": "Enregistrer la r\u00e9vision",
"pad.toolbar.settings.title": "Param\u00e8tres",
"pad.toolbar.embed.title": "Int\u00e9grer ce Pad",
"pad.toolbar.showusers.title": "Afficher les utilisateurs du Pad",
@ -43,6 +43,7 @@
"pad.settings.stickychat": "Toujours afficher le chat",
"pad.settings.colorcheck": "Couleurs d\u2019identification",
"pad.settings.linenocheck": "Num\u00e9ros de lignes",
"pad.settings.rtlcheck": "Lire le contenu de la droite vers la gauche?",
"pad.settings.fontType": "Type de police :",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Limpar as cores de identificaci\u00f3n dos autores",
"pad.toolbar.import_export.title": "Importar\/Exportar desde\/a diferentes formatos de ficheiro",
"pad.toolbar.timeslider.title": "Li\u00f1a do tempo",
"pad.toolbar.savedRevision.title": "Revisi\u00f3ns gardadas",
"pad.toolbar.savedRevision.title": "Gardar a revisi\u00f3n",
"pad.toolbar.settings.title": "Configuraci\u00f3ns",
"pad.toolbar.embed.title": "Incorporar este documento",
"pad.toolbar.showusers.title": "Mostrar os usuarios deste documento",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chat sempre visible",
"pad.settings.colorcheck": "Cores de identificaci\u00f3n",
"pad.settings.linenocheck": "N\u00fameros de li\u00f1a",
"pad.settings.rtlcheck": "Quere ler o contido da dereita \u00e1 esquerda?",
"pad.settings.fontType": "Tipo de letra:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monoespazada",

View file

@ -20,7 +20,7 @@
"pad.toolbar.clearAuthorship.title": "\u05e0\u05d9\u05e7\u05d5\u05d9 \u05e6\u05d1\u05e2\u05d9\u05dd",
"pad.toolbar.import_export.title": "\u05d9\u05d9\u05d1\u05d5\u05d0\/\u05d9\u05d9\u05e6\u05d0 \u05d1\u05ea\u05e1\u05d3\u05d9\u05e8\u05d9 \u05e7\u05d1\u05e6\u05d9\u05dd \u05e9\u05d5\u05e0\u05d9\u05dd",
"pad.toolbar.timeslider.title": "\u05d2\u05d5\u05dc\u05dc \u05d6\u05de\u05df",
"pad.toolbar.savedRevision.title": "\u05d2\u05e8\u05e1\u05d0\u05d5\u05ea \u05e9\u05de\u05d5\u05e8\u05d5\u05ea",
"pad.toolbar.savedRevision.title": "\u05e9\u05de\u05d9\u05e8\u05ea \u05d2\u05e8\u05e1\u05d4",
"pad.toolbar.settings.title": "\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea",
"pad.toolbar.embed.title": "\u05d4\u05d8\u05de\u05e2\u05ea \u05d4\u05e4\u05e0\u05e7\u05e1 \u05d4\u05d6\u05d4",
"pad.toolbar.showusers.title": "\u05d4\u05e6\u05d2\u05ea \u05d4\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd \u05d1\u05e4\u05e0\u05e7\u05e1 \u05d4\u05d6\u05d4",
@ -35,6 +35,7 @@
"pad.settings.stickychat": "\u05d4\u05e9\u05d9\u05d7\u05d4 \u05ea\u05de\u05d9\u05d3 \u05e2\u05dc \u05d4\u05de\u05e1\u05da",
"pad.settings.colorcheck": "\u05e6\u05d1\u05d9\u05e2\u05d4 \u05dc\u05e4\u05d9 \u05de\u05d7\u05d1\u05e8",
"pad.settings.linenocheck": "\u05de\u05e1\u05e4\u05e8\u05d9 \u05e9\u05d5\u05e8\u05d5\u05ea",
"pad.settings.rtlcheck": "\u05dc\u05e7\u05e8\u05d5\u05d0 \u05d0\u05ea \u05d4\u05ea\u05d5\u05db\u05df \u05de\u05d9\u05de\u05d9\u05df \u05dc\u05e9\u05de\u05d0\u05dc?",
"pad.settings.fontType": "\u05e1\u05d5\u05d2 \u05d2\u05d5\u05e4\u05df:",
"pad.settings.fontType.normal": "\u05e8\u05d2\u05d9\u05dc",
"pad.settings.fontType.monospaced": "\u05d1\u05e8\u05d5\u05d7\u05d1 \u05e7\u05d1\u05d5\u05e2",

View file

@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chat sempre visibile",
"pad.settings.colorcheck": "Colores de autor",
"pad.settings.linenocheck": "Numeros de linea",
"pad.settings.rtlcheck": "Leger le contento de dextra a sinistra?",
"pad.settings.fontType": "Typo de litteras:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospatial",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "Elimina i colori che indicano gli autori",
"pad.toolbar.import_export.title": "Importa\/esporta da\/a diversi formati di file",
"pad.toolbar.timeslider.title": "Presentazione cronologia",
"pad.toolbar.savedRevision.title": "Revisioni salvate",
"pad.toolbar.savedRevision.title": "Versione salvata",
"pad.toolbar.settings.title": "Impostazioni",
"pad.toolbar.embed.title": "Incorpora questo Pad",
"pad.toolbar.showusers.title": "Visualizza gli utenti su questo Pad",
@ -37,6 +37,7 @@
"pad.settings.stickychat": "Chat sempre sullo schermo",
"pad.settings.colorcheck": "Colori che indicano gli autori",
"pad.settings.linenocheck": "Numeri di riga",
"pad.settings.rtlcheck": "Leggere il contenuto da destra a sinistra?",
"pad.settings.fontType": "Tipo di carattere:",
"pad.settings.fontType.normal": "Normale",
"pad.settings.fontType.monospaced": "A larghezza fissa",

View file

@ -34,6 +34,7 @@
"pad.settings.stickychat": "\u753b\u9762\u306b\u30c1\u30e3\u30c3\u30c8\u3092\u5e38\u306b\u8868\u793a",
"pad.settings.colorcheck": "\u4f5c\u8005\u306e\u8272\u5206\u3051",
"pad.settings.linenocheck": "\u884c\u756a\u53f7",
"pad.settings.rtlcheck": "\u53f3\u6a2a\u66f8\u304d\u306b\u3059\u308b",
"pad.settings.fontType": "\u30d5\u30a9\u30f3\u30c8\u306e\u7a2e\u985e:",
"pad.settings.fontType.normal": "\u901a\u5e38",
"pad.settings.fontType.monospaced": "\u56fa\u5b9a\u5e45",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "\uc800\uc790\uc758 \uc0c9 \uc9c0\uc6b0\uae30",
"pad.toolbar.import_export.title": "\ub2e4\ub978 \ud30c\uc77c \ud615\uc2dd\uc73c\ub85c \uac00\uc838\uc624\uae30\/\ub0b4\ubcf4\ub0b4\uae30",
"pad.toolbar.timeslider.title": "\uc2dc\uac04\uc2ac\ub77c\uc774\ub354",
"pad.toolbar.savedRevision.title": "\uc800\uc7a5\ud55c \ud310",
"pad.toolbar.savedRevision.title": "\ud310 \uc800\uc7a5",
"pad.toolbar.settings.title": "\uc124\uc815",
"pad.toolbar.embed.title": "\uc774 \ud328\ub4dc \ud3ec\ud568\ud558\uae30",
"pad.toolbar.showusers.title": "\uc774 \ud328\ub4dc\uc5d0 \uc0ac\uc6a9\uc790 \ubcf4\uae30",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "\ud654\uba74\uc5d0 \ud56d\uc0c1 \ub300\ud654 \ubcf4\uae30",
"pad.settings.colorcheck": "\uc800\uc790 \uc0c9",
"pad.settings.linenocheck": "\uc904 \ubc88\ud638",
"pad.settings.rtlcheck": "\uc6b0\ud6a1\uc11c(\uc624\ub978\ucabd\uc5d0\uc11c \uc67c\ucabd\uc73c\ub85c)\uc785\ub2c8\uae4c?",
"pad.settings.fontType": "\uae00\uaf34 \uc885\ub958:",
"pad.settings.fontType.normal": "\ubcf4\ud1b5",
"pad.settings.fontType.monospaced": "\uace0\uc815 \ud3ed",

View file

@ -20,7 +20,7 @@
"pad.toolbar.clearAuthorship.title": "\u041f\u043e\u043d\u0438\u0448\u0442\u0438 \u0433\u0438 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u0438\u0442\u0435 \u0431\u043e\u0438",
"pad.toolbar.import_export.title": "\u0423\u0432\u043e\u0437\/\u0418\u0437\u0432\u043e\u0437 \u043e\u0434\/\u0432\u043e \u0440\u0430\u0437\u043d\u0438 \u043f\u043e\u0434\u0430\u0442\u043e\u0442\u0435\u0447\u043d\u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u0438",
"pad.toolbar.timeslider.title": "\u0418\u0441\u0442\u043e\u0440\u0438\u0441\u043a\u0438 \u043f\u0440\u0435\u0433\u043b\u0435\u0434",
"pad.toolbar.savedRevision.title": "\u0417\u0430\u0447\u0443\u0432\u0430\u043d\u0438 \u0440\u0435\u0432\u0438\u0437\u0438\u0438",
"pad.toolbar.savedRevision.title": "\u0417\u0430\u0447\u0443\u0432\u0430\u0458 \u0440\u0435\u0432\u0438\u0437\u0438\u0458\u0430",
"pad.toolbar.settings.title": "\u041f\u043e\u0441\u0442\u0430\u0432\u043a\u0438",
"pad.toolbar.embed.title": "\u0412\u043c\u0435\u0442\u043d\u0438 \u0458\u0430 \u0442\u0435\u0442\u0440\u0430\u0442\u043a\u0430\u0432\u0430",
"pad.toolbar.showusers.title": "\u041f\u0440\u0438\u043a\u0430\u0436. \u043a\u043e\u0440\u0438\u0441\u043d\u0438\u0446\u0438\u0442\u0435 \u043d\u0430 \u0442\u0435\u0442\u0440\u0430\u0442\u043a\u0430\u0432\u0430",
@ -35,6 +35,7 @@
"pad.settings.stickychat": "\u0420\u0430\u0437\u0433\u043e\u0432\u043e\u0440\u0438\u0442\u0435 \u0441\u0435\u043a\u043e\u0433\u0430\u0448 \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u043e\u0442",
"pad.settings.colorcheck": "\u0410\u0432\u0442\u043e\u0440\u0441\u043a\u0438 \u0431\u043e\u0438",
"pad.settings.linenocheck": "\u0411\u0440\u043e\u0435\u0432\u0438 \u043d\u0430 \u0440\u0435\u0434\u043e\u0432\u0438\u0442\u0435",
"pad.settings.rtlcheck": "\u0421\u043e\u0434\u0440\u0436\u0438\u043d\u0438\u0442\u0435 \u0434\u0430 \u0441\u0435 \u0447\u0438\u0442\u0430\u0430\u0442 \u043e\u0434 \u0434\u0435\u0441\u043d\u043e \u043d\u0430 \u043b\u0435\u0432\u043e?",
"pad.settings.fontType": "\u0422\u0438\u043f \u043d\u0430 \u0444\u043e\u043d\u0442:",
"pad.settings.fontType.normal": "\u041d\u043e\u0440\u043c\u0430\u043b\u0435\u043d",
"pad.settings.fontType.monospaced": "\u041d\u0435\u043f\u0440\u043e\u043f\u043e\u0440\u0446\u0438\u043e\u043d\u0430\u043b\u0435\u043d",

View file

@ -21,7 +21,7 @@
"pad.toolbar.clearAuthorship.title": "\u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7e\u0d15\u0d4d\u0d15\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d31\u0d02 \u0d15\u0d33\u0d2f\u0d41\u0d15",
"pad.toolbar.import_export.title": "\u0d35\u0d4d\u0d2f\u0d24\u0d4d\u0d2f\u0d38\u0d4d\u0d24 \u0d2b\u0d2f\u0d7d \u0d24\u0d30\u0d19\u0d4d\u0d19\u0d33\u0d3f\u0d32\u0d47\u0d15\u0d4d\u0d15\u0d4d\/\u0d24\u0d30\u0d19\u0d4d\u0d19\u0d33\u0d3f\u0d7d \u0d28\u0d3f\u0d28\u0d4d\u0d28\u0d4d \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f\/\u0d15\u0d2f\u0d31\u0d4d\u0d31\u0d41\u0d2e\u0d24\u0d3f \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15",
"pad.toolbar.timeslider.title": "\u0d38\u0d2e\u0d2f\u0d30\u0d47\u0d16",
"pad.toolbar.savedRevision.title": "\u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d24\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d41\u0d15\u0d7e",
"pad.toolbar.savedRevision.title": "\u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d4d \u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15",
"pad.toolbar.settings.title": "\u0d38\u0d1c\u0d4d\u0d1c\u0d40\u0d15\u0d30\u0d23\u0d19\u0d4d\u0d19\u0d7e",
"pad.toolbar.embed.title": "\u0d08 \u0d2a\u0d3e\u0d21\u0d4d \u0d0e\u0d02\u0d2c\u0d46\u0d21\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15",
"pad.toolbar.showusers.title": "\u0d08 \u0d2a\u0d3e\u0d21\u0d3f\u0d32\u0d41\u0d33\u0d4d\u0d33 \u0d09\u0d2a\u0d2f\u0d4b\u0d15\u0d4d\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d33\u0d46 \u0d2a\u0d4d\u0d30\u0d26\u0d7c\u0d36\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15",
@ -36,6 +36,7 @@
"pad.settings.stickychat": "\u0d24\u0d24\u0d4d\u0d38\u0d2e\u0d2f\u0d02 \u0d38\u0d02\u0d35\u0d3e\u0d26\u0d02 \u0d0e\u0d2a\u0d4d\u0d2a\u0d4b\u0d34\u0d41\u0d02 \u0d38\u0d4d\u0d15\u0d4d\u0d30\u0d40\u0d28\u0d3f\u0d7d \u0d15\u0d3e\u0d23\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15",
"pad.settings.colorcheck": "\u0d0e\u0d34\u0d41\u0d24\u0d4d\u0d24\u0d41\u0d15\u0d3e\u0d7c\u0d15\u0d4d\u0d15\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3f\u0d31\u0d19\u0d4d\u0d19\u0d7e",
"pad.settings.linenocheck": "\u0d35\u0d30\u0d3f\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d15\u0d4d\u0d30\u0d2e\u0d38\u0d02\u0d16\u0d4d\u0d2f",
"pad.settings.rtlcheck": "\u0d09\u0d33\u0d4d\u0d33\u0d1f\u0d15\u0d4d\u0d15\u0d02 \u0d35\u0d32\u0d24\u0d4d\u0d24\u0d41\u0d28\u0d3f\u0d28\u0d4d\u0d28\u0d4d \u0d07\u0d1f\u0d24\u0d4d\u0d24\u0d4b\u0d1f\u0d4d\u0d1f\u0d3e\u0d23\u0d4b \u0d35\u0d3e\u0d2f\u0d3f\u0d15\u0d4d\u0d15\u0d47\u0d23\u0d4d\u0d1f\u0d24\u0d4d?",
"pad.settings.fontType": "\u0d2b\u0d4b\u0d23\u0d4d\u0d1f\u0d4d \u0d24\u0d30\u0d02:",
"pad.settings.fontType.normal": "\u0d38\u0d3e\u0d27\u0d3e\u0d30\u0d23\u0d02",
"pad.settings.fontType.monospaced": "\u0d2e\u0d4b\u0d23\u0d4b\u0d38\u0d4d\u0d2a\u0d47\u0d38\u0d4d",
@ -51,6 +52,7 @@
"pad.importExport.exportpdf": "\u0d2a\u0d3f.\u0d21\u0d3f.\u0d0e\u0d2b\u0d4d.",
"pad.importExport.exportopen": "\u0d12.\u0d21\u0d3f.\u0d0e\u0d2b\u0d4d. (\u0d13\u0d2a\u0d4d\u0d2a\u0d7a \u0d21\u0d4b\u0d15\u0d4d\u0d2f\u0d41\u0d2e\u0d46\u0d28\u0d4d\u0d31\u0d4d \u0d2b\u0d4b\u0d7c\u0d2e\u0d3e\u0d31\u0d4d\u0d31\u0d4d)",
"pad.importExport.exportdokuwiki": "\u0d21\u0d4b\u0d15\u0d41\u0d35\u0d3f\u0d15\u0d4d\u0d15\u0d3f",
"pad.importExport.abiword.innerHTML": "\u0d2a\u0d4d\u0d32\u0d46\u0d2f\u0d3f\u0d7b \u0d1f\u0d46\u0d15\u0d4d\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4b \u0d0e\u0d1a\u0d4d\u0d1a\u0d4d.\u0d31\u0d4d\u0d31\u0d3f.\u0d0e\u0d02.\u0d0e\u0d7d. \u0d24\u0d30\u0d2e\u0d4b \u0d2e\u0d3e\u0d24\u0d4d\u0d30\u0d2e\u0d47 \u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d7e\u0d15\u0d4d\u0d15\u0d4d \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d3e\u0d28\u0d3e\u0d35\u0d42. \u0d15\u0d42\u0d1f\u0d41\u0d24\u0d7d \u0d35\u0d3f\u0d2a\u0d41\u0d32\u0d40\u0d15\u0d43\u0d24 \u0d07\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d2e\u0d24\u0d3f \u0d38\u0d57\u0d15\u0d30\u0d4d\u0d2f\u0d19\u0d4d\u0d19\u0d7e\u0d15\u0d4d\u0d15\u0d3e\u0d2f\u0d3f \u0d26\u0d2f\u0d35\u0d3e\u0d2f\u0d3f <a href=\"https:\/\/github.com\/ether\/etherpad-lite\/wiki\/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">\u0d05\u0d2c\u0d3f\u0d35\u0d47\u0d21\u0d4d \u0d07\u0d7b\u0d38\u0d4d\u0d31\u0d4d\u0d31\u0d4b\u0d7e \u0d1a\u0d46\u0d2f\u0d4d\u0d2f\u0d41\u0d15<\/a>.",
"pad.modals.connected": "\u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d1a\u0d4d\u0d1a\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41.",
"pad.modals.reconnecting": "\u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d3e\u0d21\u0d3f\u0d32\u0d47\u0d2f\u0d4d\u0d15\u0d4d\u0d15\u0d4d \u0d35\u0d40\u0d23\u0d4d\u0d1f\u0d41\u0d02 \u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41...",
"pad.modals.forcereconnect": "\u0d0e\u0d28\u0d4d\u0d24\u0d3e\u0d2f\u0d3e\u0d32\u0d41\u0d02 \u0d2c\u0d28\u0d4d\u0d27\u0d3f\u0d2a\u0d4d\u0d2a\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d15",
@ -101,6 +103,8 @@
"timeslider.month.october": "\u0d12\u0d15\u0d4d\u0d1f\u0d4b\u0d2c\u0d7c",
"timeslider.month.november": "\u0d28\u0d35\u0d02\u0d2c\u0d7c",
"timeslider.month.december": "\u0d21\u0d3f\u0d38\u0d02\u0d2c\u0d7c",
"timeslider.unnamedauthor": "{{num}} \u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24 \u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d35\u0d4d",
"timeslider.unnamedauthors": "{{num}} \u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24 \u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7e",
"pad.savedrevs.marked": "\u0d08 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d4d \u0d38\u0d47\u0d35\u0d4d \u0d1a\u0d46\u0d2f\u0d4d\u0d24\u0d3f\u0d1f\u0d4d\u0d1f\u0d41\u0d33\u0d4d\u0d33 \u0d28\u0d3e\u0d7e\u0d2a\u0d4d\u0d2a\u0d24\u0d3f\u0d2a\u0d4d\u0d2a\u0d3e\u0d2f\u0d3f \u0d05\u0d1f\u0d2f\u0d3e\u0d33\u0d2a\u0d4d\u0d2a\u0d46\u0d1f\u0d41\u0d24\u0d4d\u0d24\u0d3f\u0d2f\u0d3f\u0d30\u0d3f\u0d15\u0d4d\u0d15\u0d41\u0d28\u0d4d\u0d28\u0d41",
"pad.userlist.entername": "\u0d24\u0d3e\u0d19\u0d4d\u0d15\u0d33\u0d41\u0d1f\u0d46 \u0d2a\u0d47\u0d30\u0d4d \u0d28\u0d7d\u0d15\u0d41\u0d15",
"pad.userlist.unnamed": "\u0d2a\u0d47\u0d30\u0d3f\u0d32\u0d4d\u0d32\u0d3e\u0d24\u0d4d\u0d24",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Padamkan Warna Pengarang",
"pad.toolbar.import_export.title": "Import\/Eksport dari\/ke format-format fail berbeza",
"pad.toolbar.timeslider.title": "Gelangsar masa",
"pad.toolbar.savedRevision.title": "Semakan Tersimpan",
"pad.toolbar.savedRevision.title": "Simpan Semakan",
"pad.toolbar.settings.title": "Tetapan",
"pad.toolbar.embed.title": "Benamkan pad ini",
"pad.toolbar.showusers.title": "Tunjukkan pengguna pada pad ini",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Sentiasa bersembang pada skrin",
"pad.settings.colorcheck": "Warna pengarang",
"pad.settings.linenocheck": "Nombor baris",
"pad.settings.rtlcheck": "Membaca dari kanan ke kiri?",
"pad.settings.fontType": "Jenis fon:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chat altijd zichtbaar",
"pad.settings.colorcheck": "Kleuren auteurs",
"pad.settings.linenocheck": "Regelnummers",
"pad.settings.rtlcheck": "Inhoud van rechts naar links lezen?",
"pad.settings.fontType": "Lettertype:",
"pad.settings.fontType.normal": "Normaal",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0446\u0432\u0435\u0442\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430",
"pad.toolbar.import_export.title": "\u0418\u043c\u043f\u043e\u0440\u0442\/\u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432 \u0444\u0430\u0439\u043b\u043e\u0432",
"pad.toolbar.timeslider.title": "\u0428\u043a\u0430\u043b\u0430 \u0432\u0440\u0435\u043c\u0435\u043d\u0438",
"pad.toolbar.savedRevision.title": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438",
"pad.toolbar.savedRevision.title": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u044e",
"pad.toolbar.settings.title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"pad.toolbar.embed.title": "\u0412\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u043e\u0442 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442",
"pad.toolbar.showusers.title": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435",
@ -37,6 +37,7 @@
"pad.settings.stickychat": "\u0412\u0441\u0435\u0433\u0434\u0430 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0447\u0430\u0442",
"pad.settings.colorcheck": "\u0426\u0432\u0435\u0442\u0430 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430",
"pad.settings.linenocheck": "\u041d\u043e\u043c\u0435\u0440\u0430 \u0441\u0442\u0440\u043e\u043a",
"pad.settings.rtlcheck": "\u0427\u0438\u0442\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0441\u043f\u0440\u0430\u0432\u0430 \u043d\u0430\u043b\u0435\u0432\u043e?",
"pad.settings.fontType": "\u0422\u0438\u043f \u0448\u0440\u0438\u0444\u0442\u0430:",
"pad.settings.fontType.normal": "\u041e\u0431\u044b\u0447\u043d\u044b\u0439",
"pad.settings.fontType.monospaced": "\u041c\u043e\u043d\u043e\u0448\u0438\u0440\u0438\u043d\u043d\u044b\u0439",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Po\u010disti barvo avtorstva",
"pad.toolbar.import_export.title": "Izvozi\/Uvozi razli\u010dne oblike zapisov",
"pad.toolbar.timeslider.title": "Drsnik zgodovine",
"pad.toolbar.savedRevision.title": "Shranjene predelave",
"pad.toolbar.savedRevision.title": "Shrani predelavo",
"pad.toolbar.settings.title": "Nastavitve",
"pad.toolbar.embed.title": "Vstavi dokument",
"pad.toolbar.showusers.title": "Poka\u017ei uporabnike dokumenta",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Vsebina klepeta je vedno na zaslonu.",
"pad.settings.colorcheck": "Barve avtorstva",
"pad.settings.linenocheck": "\u0160tevilke vrstic",
"pad.settings.rtlcheck": "Ali naj se vsebina prebira od desne proti levi?",
"pad.settings.fontType": "Vrsta pisave:",
"pad.settings.fontType.normal": "Obi\u010dajno",
"pad.settings.fontType.monospaced": "Monospace",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Rensa f\u00f6rfattarf\u00e4rger",
"pad.toolbar.import_export.title": "Importera\/exportera fr\u00e5n\/till olika filformat",
"pad.toolbar.timeslider.title": "Tidsreglage",
"pad.toolbar.savedRevision.title": "Sparade revisioner",
"pad.toolbar.savedRevision.title": "Spara revision",
"pad.toolbar.settings.title": "Inst\u00e4llningar",
"pad.toolbar.embed.title": "B\u00e4dda in detta block",
"pad.toolbar.showusers.title": "Visa anv\u00e4ndarna p\u00e5 detta block",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chatten alltid p\u00e5 sk\u00e4rmen",
"pad.settings.colorcheck": "F\u00f6rfattarskapsf\u00e4rger",
"pad.settings.linenocheck": "Radnummer",
"pad.settings.rtlcheck": "Vill du l\u00e4sa inneh\u00e5llet fr\u00e5n h\u00f6ger till v\u00e4nster?",
"pad.settings.fontType": "Typsnitt:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Fast breddsteg",

View file

@ -26,7 +26,7 @@
"pad.colorpicker.save": "\u0c2d\u0c26\u0c4d\u0c30\u0c2a\u0c30\u0c1a\u0c41",
"pad.colorpicker.cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41\u0c1a\u0c47\u0c2f\u0c3f",
"pad.loading": "\u0c32\u0c4b\u0c21\u0c35\u0c41\u0c24\u0c4b\u0c02\u0c26\u0c3f...",
"pad.wrongPassword": "\u0c2e\u0c40 \u0c30\u0c39\u0c38\u0c4d\u0c2f\u0c2a\u0c26\u0c02 \u0c24\u0c2a\u0c41",
"pad.wrongPassword": "\u0c2e\u0c40 \u0c38\u0c02\u0c15\u0c47\u0c24\u0c2a\u0c26\u0c02 \u0c24\u0c2a\u0c4d\u0c2a\u0c41",
"pad.settings.padSettings": "\u0c2a\u0c32\u0c15 \u0c05\u0c2e\u0c30\u0c3f\u0c15\u0c32\u0c41",
"pad.settings.myView": "\u0c28\u0c3e \u0c09\u0c26\u0c4d\u0c26\u0c47\u0c36\u0c4d\u0c2f\u0c2e\u0c41",
"pad.settings.stickychat": "\u0c24\u0c46\u0c30\u0c2a\u0c48\u0c28\u0c47 \u0c2e\u0c3e\u0c1f\u0c3e\u0c2e\u0c02\u0c24\u0c3f\u0c28\u0c3f \u0c0e\u0c32\u0c4d\u0c32\u0c2a\u0c41\u0c21\u0c41 \u0c1a\u0c47\u0c2f\u0c41\u0c2e\u0c41",

View file

@ -35,6 +35,7 @@
"pad.settings.stickychat": "\u6c38\u9060\u5728\u5c4f\u5e55\u4e0a\u986f\u793a\u804a\u5929",
"pad.settings.colorcheck": "\u4f5c\u8005\u984f\u8272",
"pad.settings.linenocheck": "\u884c\u865f",
"pad.settings.rtlcheck": "\u5f9e\u53f3\u81f3\u5de6\u8b80\u53d6\u5167\u5bb9\uff1f",
"pad.settings.fontType": "\u5b57\u9ad4\u985e\u578b\uff1a",
"pad.settings.fontType.normal": "\u6b63\u5e38",
"pad.settings.fontType.monospaced": "\u7b49\u5bec",

View file

@ -27,6 +27,8 @@ var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var log4js = require('log4js');
var authLogger = log4js.getLogger("auth");
/**
* This function controlls the access to a pad, it checks if the user can access a pad.
@ -117,29 +119,41 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
//get information about all sessions contained in this cookie
function(callback)
{
if (!sessionCookie) {
if (!sessionCookie)
{
callback();
return;
}
var sessionIDs = sessionCookie.split(',');
async.forEach(sessionIDs, function(sessionID, callback) {
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) {
async.forEach(sessionIDs, function(sessionID, callback)
{
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
{
//skip session if it doesn't exist
if(err && err.message == "sessionID does not exist") return;
if(err && err.message == "sessionID does not exist")
{
authLogger.debug("Auth failed: unknown session");
callback();
return;
}
if(ERR(err, callback)) return;
var now = Math.floor(new Date().getTime()/1000);
//is it for this group?
if(sessionInfo.groupID != groupID) {
if(sessionInfo.groupID != groupID)
{
authLogger.debug("Auth failed: wrong group");
callback();
return;
}
//is validUntil still ok?
if(sessionInfo.validUntil <= now){
if(sessionInfo.validUntil <= now)
{
authLogger.debug("Auth failed: validUntil");
callback();
return;
}
@ -234,7 +248,11 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
//--> grant access
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
//--> deny access if user isn't allowed to create the pad
if(settings.editOnly) statusObject.accessStatus = "deny";
if(settings.editOnly)
{
authLogger.debug("Auth failed: valid session & pad does not exist");
statusObject.accessStatus = "deny";
}
}
// there is no valid session avaiable AND pad exists
else if(!validSession && padExists)
@ -266,6 +284,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
//- its not public
else if(!isPublic)
{
authLogger.debug("Auth failed: invalid session & pad is not public");
//--> deny access
statusObject = {accessStatus: "deny"};
}
@ -277,6 +296,7 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
// there is no valid session avaiable AND pad doesn't exists
else
{
authLogger.debug("Auth failed: invalid session & pad does not exist");
//--> deny access
statusObject = {accessStatus: "deny"};
}

View file

@ -22,7 +22,7 @@ SessionStore.prototype.get = function(sid, fn){
{
if (sess) {
sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires;
if (!sess.cookie.expires || new Date() < expires) {
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
fn(null, sess);
} else {
self.destroy(sid, fn);

View file

@ -60,7 +60,7 @@ exports.doImport = function(req, res, padId)
form.parse(req, function(err, fields, files) {
//the upload failed, stop at this point
if(err || files.file === undefined) {
console.warn("Uploading Error: " + err.stack);
if(err) console.warn("Uploading Error: " + err.stack);
callback("uploadFailed");
}
//everything ok, continue
@ -176,7 +176,7 @@ exports.doImport = function(req, res, padId)
ERR(err);
//close the connection
res.send("<head><script type='text/javascript' src='../../static/js/jquery.js'></script></head><script>$(window).load(function(){if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.parent.padimpexp.handleFrameCall('" + status + "');})</script>", 200);
res.send("<head><script type='text/javascript' src='../../static/js/jquery.js'></script><script type='text/javascript' src='../../static/js/jquery_browser.js'></script></head><script>$(window).load(function(){if ( (!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf(\"1.8.\") == 0)) ){document.domain = document.domain;}var impexp = window.parent.padimpexp.handleFrameCall('" + status + "');})</script>", 200);
});
}

View file

@ -151,12 +151,10 @@ exports.handleMessage = function(client, message)
var handleMessageHook = function(callback){
var dropMessage = false;
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
// handleMessage will be called, even if the client is not authorized
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) {
if(ERR(err, callback)) return;
_.each(messages, function(newMessage){
if ( newMessage === null ) {
dropMessage = true;
@ -205,17 +203,29 @@ exports.handleMessage = function(client, message)
//check permissions
function(callback)
{
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
if(!message.padId)
return callback();
// client tried to auth for the first time (first msg from the client)
if(message.type == "CLIENT_READY") {
// Remember this information since we won't
// have the cookie in further socket.io messages.
// This information will be used to check if
// the sessionId of this connection is still valid
// since it could have been deleted by the API.
sessioninfos[client.id].auth =
{
sessionID: message.sessionID,
padID: message.padId,
token : message.token,
password: message.password
};
}
// Note: message.sessionID is an entirely different kind of
// session from the sessions we use here! Beware! FIXME: Call
// our "sessions" "connections".
// session from the sessions we use here! Beware!
// FIXME: Call our "sessions" "connections".
// FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly
securityManager.checkAccess(message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
var auth = sessioninfos[client.id].auth;
securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, function(err, statusObject)
{
if(ERR(err, callback)) return;
@ -254,6 +264,25 @@ function handleSaveRevisionMessage(client, message){
});
}
/**
* Handles a custom message, different to the function below as it handles objects not strings and you can
* direct the message to specific sessionID
*
* @param msg {Object} the message we're sending
* @param sessionID {string} the socketIO session to which we're sending this message
*/
exports.handleCustomObjectMessage = function (msg, sessionID, cb) {
if(msg.data.type === "CUSTOM"){
if(sessionID){ // If a sessionID is targeted then send directly to this sessionID
socketio.sockets.socket(sessionID).json.send(msg); // send a targeted message
}else{
socketio.sockets.in(msg.data.payload.padId).json.send(msg); // broadcast to all clients on this pad
}
}
cb(null, {});
}
/**
* Handles a custom message (sent via HTTP API request)
*
@ -1478,3 +1507,5 @@ exports.padUsers = function (padID, callback) {
callback(null, {padUsers: result});
});
}
exports.sessioninfos = sessioninfos;

View file

@ -27,49 +27,84 @@ exports.socketio = function (hook_name, args, cb) {
io.on('connection', function (socket) {
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return;
socket.on("load", function (query) {
socket.on("getInstalled", function (query) {
// send currently installed plugins
socket.emit("installed-results", {results: plugins.plugins});
socket.emit("progress", {progress:1});
var installed = Object.keys(plugins.plugins).map(function(plugin) {
return plugins.plugins[plugin].package
})
socket.emit("results:installed", {installed: installed});
});
socket.on("checkUpdates", function() {
socket.emit("progress", {progress:0, message:'Checking for plugin updates...'});
// Check plugins for updates
installer.search({offset: 0, pattern: '', limit: 500}, /*useCache:*/true, function(data) { // hacky
if (!data.results) return;
installer.getAvailablePlugins(/*maxCacheAge:*/60*10, function(er, results) {
if(er) {
console.warn(er);
socket.emit("results:updatable", {updatable: {}});
return;
}
var updatable = _(plugins.plugins).keys().filter(function(plugin) {
if(!data.results[plugin]) return false;
var latestVersion = data.results[plugin]['dist-tags'].latest
if(!results[plugin]) return false;
var latestVersion = results[plugin].version
var currentVersion = plugins.plugins[plugin].package.version
return semver.gt(latestVersion, currentVersion)
});
socket.emit("updatable", {updatable: updatable});
socket.emit("progress", {progress:1});
socket.emit("results:updatable", {updatable: updatable});
});
})
socket.on("getAvailable", function (query) {
installer.getAvailablePlugins(/*maxCacheAge:*/false, function (er, results) {
if(er) {
console.error(er)
results = {}
}
socket.emit("results:available", results);
});
});
socket.on("search", function (query) {
socket.emit("progress", {progress:0, message:'Fetching results...'});
installer.search(query, true, function (progress) {
if (progress.results)
socket.emit("search-result", progress);
socket.emit("progress", progress);
installer.search(query.searchTerm, /*maxCacheAge:*/60*10, function (er, results) {
if(er) {
console.error(er)
results = {}
}
var res = Object.keys(results)
.map(function(pluginName) {
return results[pluginName]
})
.filter(function(plugin) {
return !plugins.plugins[plugin.name]
});
res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset+query.limit);
socket.emit("results:search", {results: res, query: query});
});
});
socket.on("install", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."});
installer.install(plugin_name, function (progress) {
socket.emit("progress", progress);
installer.install(plugin_name, function (er) {
if(er) console.warn(er)
socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null});
});
});
socket.on("uninstall", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."});
installer.uninstall(plugin_name, function (progress) {
socket.emit("progress", progress);
installer.uninstall(plugin_name, function (er) {
if(er) console.warn(er)
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
});
});
});
}
function sortPluginList(plugins, property, /*ASC?*/dir) {
return plugins.sort(function(a, b) {
if (a[property] < b[property])
return dir? -1 : 1;
if (a[property] > b[property])
return dir? 1 : -1;
// a must be equal to b
return 0;
})
}

View file

@ -28,6 +28,7 @@ exports.gracefulShutdown = function(err) {
}, 3000);
}
process.on('uncaughtException', exports.gracefulShutdown);
exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app;
@ -47,6 +48,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//https://github.com/joyent/node/issues/1553
process.on('SIGINT', exports.gracefulShutdown);
}
process.on('uncaughtException', exports.gracefulShutdown);
}
}

View file

@ -354,7 +354,6 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Let's put this under /rest for now
var subpath = express();
args.app.use(express.bodyParser());
args.app.use(basePath, subpath);
swagger.setAppHandler(subpath);

View file

@ -94,7 +94,7 @@ exports.expressConfigure = function (hook_name, args, cb) {
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.DEBUG, format: ':status, :method :url -- :response-timems'}));
/* Do not let express create the session, so that we can retain a
* reference to it for socket.io to use. Also, set the key (cookie

View file

@ -63,7 +63,7 @@ if(os.type().indexOf("Windows") > -1)
callback();
});
}
};
exports.convertFile = function(srcFile, destFile, type, callback)
{
@ -121,7 +121,7 @@ else
firstPrompt = false;
}
});
}
};
spawnAbiword();
doConvertTask = function(task, callback)
@ -135,7 +135,7 @@ else
console.log("queue continue");
task.callback(err);
};
}
};
//Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1);

View file

@ -316,7 +316,7 @@ exports.getPadDokuWikiDocument = function (padId, revNum, callback)
getPadDokuWiki(pad, revNum, callback);
});
}
};
function _escapeDokuWiki(s)
{

View file

@ -45,7 +45,7 @@ exports.getPadPlainText = function(pad, revNum){
}
return pieces.join('');
}
};
exports._analyzeLine = function(text, aline, apool){
@ -77,11 +77,11 @@ exports._analyzeLine = function(text, aline, apool){
line.aline = aline;
}
return line;
}
};
exports._encodeWhitespace = function(s){
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){
return "&#" +c.charCodeAt(0) + ";"
return "&#" +c.charCodeAt(0) + ";";
});
}
};

View file

@ -21,7 +21,7 @@ var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var getPadPlainText = require('./ExportHelper').getPadPlainText
var getPadPlainText = require('./ExportHelper').getPadPlainText;
var _analyzeLine = require('./ExportHelper')._analyzeLine;
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
@ -515,7 +515,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
callback(null, head + html + foot);
});
});
}
};
// copied from ACE
@ -595,4 +595,3 @@ function _processSpaces(s){
}
return parts.join('');
}

View file

@ -289,5 +289,4 @@ exports.getPadTXTDocument = function (padId, revNum, noDocType, callback)
callback(null, html);
});
});
}
};

View file

@ -125,11 +125,11 @@ function requestURIs(locations, method, headers, callback) {
}
function completed() {
var statuss = responses.map(function (x) {return x[0]});
var headerss = responses.map(function (x) {return x[1]});
var contentss = responses.map(function (x) {return x[2]});
var statuss = responses.map(function (x) {return x[0];});
var headerss = responses.map(function (x) {return x[1];});
var contentss = responses.map(function (x) {return x[2];});
callback(statuss, headerss, contentss);
};
}
}
/**
@ -263,7 +263,7 @@ function getAceFile(callback) {
var filename = item.match(/"([^"]*)"/)[1];
var request = require('request');
var baseURI = 'http://localhost:' + settings.port
var baseURI = 'http://localhost:' + settings.port;
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)

View file

@ -137,12 +137,12 @@ exports.abiwordAvailable = function()
{
return "no";
}
}
};
exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json";
settingsFilename = path.resolve(path.join(root, settingsFilename));
settingsFilename = path.resolve(path.join(exports.root, settingsFilename));
var settingsStr;
try{
@ -157,7 +157,7 @@ exports.reloadSettings = function reloadSettings() {
try {
if(settingsStr) {
settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json");
settings = JSON.parse(JSON.stringify(settings)) // fix objects having constructors of other vm.context
settings = JSON.parse(JSON.stringify(settings)); // fix objects having constructors of other vm.context
}
}catch(e){
console.error('There was an error processing your settings.json file: '+e.message);
@ -196,9 +196,9 @@ exports.reloadSettings = function reloadSettings() {
}
if(exports.dbType === "dirty"){
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.")
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.");
}
}
};
// initially load settings
exports.reloadSettings();

View file

@ -23,7 +23,7 @@ var util = require('util');
var settings = require('./Settings');
var semver = require('semver');
var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync
var existsSync = (semver.satisfies(process.version, '>=0.8.0')) ? fs.existsSync : path.existsSync;
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
@ -133,7 +133,7 @@ CachingMiddleware.prototype = new function () {
old_res.write = res.write;
old_res.end = res.end;
res.write = function(data, encoding) {};
res.end = function(data, encoding) { respond() };
res.end = function(data, encoding) { respond(); };
} else {
res.writeHead(status, headers);
}
@ -168,7 +168,7 @@ CachingMiddleware.prototype = new function () {
} else if (req.method == 'GET') {
var readStream = fs.createReadStream(pathStr);
res.writeHead(statusCode, headers);
util.pump(readStream, res);
readStream.pipe(res);
} else {
res.writeHead(statusCode, headers);
res.end();

View file

@ -68,7 +68,7 @@ PadDiff.prototype._isClearAuthorship = function(changeset){
return false;
return true;
}
};
PadDiff.prototype._createClearAuthorship = function(rev, callback){
var self = this;
@ -84,7 +84,7 @@ PadDiff.prototype._createClearAuthorship = function(rev, callback){
callback(null, changeset);
});
}
};
PadDiff.prototype._createClearStartAtext = function(rev, callback){
var self = this;
@ -107,7 +107,7 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){
callback(null, newAText);
});
});
}
};
PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
var self = this;
@ -124,7 +124,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
async.forEach(revisions, function(rev, callback){
self._pad.getRevision(rev, function(err, revision){
if(err){
return callback(err)
return callback(err);
}
var arrayNum = rev-startRev;
@ -137,7 +137,7 @@ PadDiff.prototype._getChangesetsInBulk = function(startRev, count, callback) {
}, function(err){
callback(err, changesets, authors);
});
}
};
PadDiff.prototype._addAuthors = function(authors) {
var self = this;
@ -147,7 +147,7 @@ PadDiff.prototype._addAuthors = function(authors) {
self._authors.push(author);
}
});
}
};
PadDiff.prototype._createDiffAtext = function(callback) {
var self = this;
@ -219,7 +219,7 @@ PadDiff.prototype._createDiffAtext = function(callback) {
}
);
});
}
};
PadDiff.prototype.getHtml = function(callback){
//cache the html
@ -279,7 +279,7 @@ PadDiff.prototype.getAuthors = function(callback){
} else {
callback(null, self._authors);
}
}
};
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) {
//unpack
@ -312,7 +312,7 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool
//return the modified changeset
return Changeset.pack(unpacked.oldLen, unpacked.newLen, assem.toString(), unpacked.charBank);
}
};
//this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
@ -463,7 +463,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
// If the text this operator applies to is only a star, than this is a false positive and should be ignored
if (csOp.attribs && textBank != "*") {
var deletedAttrib = apool.putAttrib(["removed", true]);
var authorAttrib = apool.putAttrib(["author", ""]);;
var authorAttrib = apool.putAttrib(["author", ""]);
attribKeys.length = 0;
attribValues.length = 0;
@ -473,7 +473,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
if(apool.getAttribKey(n) === "author"){
authorAttrib = n;
};
}
});
var undoBackToAttribs = cachedStrFunc(function (attribs) {

View file

@ -16,7 +16,7 @@
"require-kernel" : "1.0.5",
"resolve" : "0.2.x",
"socket.io" : "0.9.x",
"ueberDB" : "0.1.94",
"ueberDB" : "0.2.x",
"async" : "0.1.x",
"express" : "3.x",
"connect" : "2.4.x",
@ -24,10 +24,10 @@
"uglify-js" : "1.2.5",
"formidable" : "1.0.9",
"log4js" : "0.5.x",
"nodemailer" : "0.3.x",
"jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2",
"npm" : "1.1.x",
"npm-registry-client" : "0.2.10",
"npm" : "1.2.x",
"ejs" : "0.6.1",
"graceful-fs" : "1.1.5",
"slide" : "1.1.3",
@ -46,5 +46,5 @@
"engines" : { "node" : ">=0.6.3",
"npm" : ">=1.0"
},
"version" : "1.2.9"
"version" : "1.2.91"
}

View file

@ -43,7 +43,7 @@ div.innerwrapper {
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
margin: auto;
max-width: 1150px;
min-height: 100%;
min-height: 101%;/*always display a scrollbar*/
}
h1 {
@ -102,12 +102,26 @@ input[type="text"] {
max-width: 500px;
}
.sort {
cursor: pointer;
}
.sort:after {
content: '▲▼'
}
.sort.up:after {
content:'▲'
}
.sort.down:after {
content:'▼'
}
table {
border: 1px solid #ddd;
border-radius: 3px;
border-spacing: 0;
width: 100%;
margin: 20px 0;
position:relative; /* Allows us to position the loading indicator relative to the table */
}
table thead tr {
@ -122,13 +136,40 @@ td, th {
display: none;
}
#progress {
position: absolute;
bottom: 50px;
#installed-plugins td>div {
position: relative;/* Allows us to position the loading indicator relative to this row */
display: inline-block; /*make this fill the whole cell*/
width:100%;
}
#progress img {
vertical-align: top;
.messages td>* {
display: none;
text-align: center;
}
.messages .fetching {
display: block;
}
.progress {
position: absolute;
top: 0; left: 0; bottom:0; right:0;
padding: auto;
background: rgb(255,255,255);
display: none;
}
#search-progress.progress {
padding-top: 20%;
background: rgba(255,255,255,0.7);
}
.progress * {
display: block;
margin: 0 auto;
text-align: center;
color: #666;
}
.settings {
@ -147,7 +188,25 @@ a:link, a:visited, a:hover, a:focus {
}
a:focus, a:hover {
border-bottom: #333333 1px solid;
text-decoration: underline;
}
.installed-results a:link,
.search-results a:link,
.installed-results a:visited,
.search-results a:visited,
.installed-results a:hover,
.search-results a:hover,
.installed-results a:focus,
.search-results a:focus {
text-decoration: underline;
}
.installed-results a:focus,
.search-results a:focus,
.installed-results a:hover,
.search-results a:hover {
text-decoration: none;
}
pre {

View file

@ -78,6 +78,7 @@ ul.list-indent8 { list-style-type: none; }
body {
margin: 0;
white-space: nowrap;
word-wrap: normal;
}
#outerdocbody {
@ -93,6 +94,7 @@ body.grayedout { background-color: #eee !important }
body.doesWrap {
white-space: normal;
word-wrap: break-word; /* fix for issue #1648 - firefox not wrapping long lines (without spaces) correctly */
}
#innerdocbody {

View file

@ -559,6 +559,15 @@ table#otheruserstable {
margin: 4px 0 0 4px;
position: absolute;
}
#titlesticky{
font-size: 10px;
padding-top:2px;
float: right;
text-align: right;
text-decoration: none;
cursor: pointer;
color: #555;
}
#titlecross {
font-size: 25px;
float: right;
@ -828,7 +837,44 @@ input[type=checkbox] {
padding: 4px 1px
}
}
@media screen and (max-width: 400px){
@media all and (max-width: 400px){
#gritter-notice-wrapper{
max-height:172px;
overflow:hidden;
width:100% !important;
background-color: #ccc;
bottom:20px;
left:0px;
right:0px;
color:#000;
}
.gritter-close {
display:block !important;
left: auto !important;
right:5px;
}
#gritter-notice-wrapper.bottom-right{
left:0px !important;
bottom:30px !important;
right:0px !important;
}
.gritter-item p{
color:black;
font-size:16px;
}
.gritter-title{
text-shadow: none !important;
color:black;
}
.gritter-item{
padding:2px 11px 8px 4px;
}
.gritter-item-wrapper{
margin:0;
}
.gritter-item-wrapper > div{
background: none;
}
#editorcontainer {
top: 68px;
}

View file

@ -1013,6 +1013,11 @@ function Ace2Inner(){
return caughtErrors.slice();
};
editorInfo.ace_getDocument = function()
{
return doc;
};
editorInfo.ace_getDebugProperty = function(prop)
{
if (prop == "debugger")
@ -3644,6 +3649,11 @@ function Ace2Inner(){
if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey)) /* Do a saved revision on ctrl S */
{
evt.preventDefault();
var originalBackground = parent.parent.$('#revisionlink').css("background")
parent.parent.$('#revisionlink').css({"background":"lightyellow"});
scheduler.setTimeout(function(){
parent.parent.$('#revisionlink').css({"background":originalBackground});
}, 1000);
parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */
specialHandled = true;
}
@ -3712,6 +3722,9 @@ function Ace2Inner(){
specialHandled = true;
}
if((evt.which == 33 || evt.which == 34) && type == 'keydown'){
evt.preventDefault(); // This is required, browsers will try to do normal default behavior on page up / down and the default behavior SUCKS
var oldVisibleLineRange = getVisibleLineRange();
var topOffset = rep.selStart[0] - oldVisibleLineRange[0];
if(topOffset < 0 ){
@ -3722,29 +3735,38 @@ function Ace2Inner(){
var isPageUp = evt.which === 33;
scheduler.setTimeout(function(){
var newVisibleLineRange = getVisibleLineRange();
var linesCount = rep.lines.length();
var newVisibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10
var linesCount = rep.lines.length(); // total count of lines in pad IE 10
var numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now?
var newCaretRow = rep.selStart[0];
if(isPageUp){
newCaretRow = oldVisibleLineRange[0];
rep.selEnd[0] = rep.selEnd[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page)
rep.selStart[0] = rep.selStart[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page)
}
if(isPageDown){
newCaretRow = newVisibleLineRange[0] + topOffset;
if(isPageDown){ // if we hit page down
if(rep.selEnd[0] >= oldVisibleLineRange[0]){ // If the new viewpoint position is actually further than where we are right now
rep.selStart[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content
rep.selEnd[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content
}
}
//ensure min and max
if(newCaretRow < 0){
newCaretRow = 0;
if(rep.selEnd[0] < 0){
rep.selEnd[0] = 0;
}
if(newCaretRow >= linesCount){
newCaretRow = linesCount-1;
if(rep.selStart[0] < 0){
rep.selStart[0] = 0;
}
if(rep.selEnd[0] >= linesCount){
rep.selEnd[0] = linesCount-1;
}
rep.selStart[0] = newCaretRow;
rep.selEnd[0] = newCaretRow;
updateBrowserSelectionFromRep();
var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current
var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214
// top.console.log(caretOffsetTop);
setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document
}, 200);
}
@ -3752,32 +3774,44 @@ function Ace2Inner(){
We have to do this the way we do because rep. doesn't hold the value for keyheld events IE if the user
presses and holds the arrow key */
if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){
var newVisibleLineRange = getVisibleLineRange(); // get the current visible range -- This works great.
var lineHeight = textLineHeight(); // what Is the height of each line?
var viewport = getViewPortTopBottom();
var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current
var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214
var lineHeight = $(myselection.focusNode.parentNode).parent().height(); // get the line height of the caret line
var caretOffsetTopBottom = caretOffsetTop + lineHeight;
var visibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10
if(caretOffsetTop){ // sometimes caretOffsetTop bugs out and returns 0, not sure why, possible Chrome bug? Either way if it does we don't wanna mess with it
var lineNum = Math.round(caretOffsetTop / lineHeight) ; // Get the current Line Number IE 84
newVisibleLineRange[1] = newVisibleLineRange[1]-1;
var caretIsVisible = (lineNum > newVisibleLineRange[0] && lineNum < newVisibleLineRange[1]); // Is the cursor in the visible Range IE ie 84 > 14 and 84 < 90?
if(!caretIsVisible){ // is the cursor no longer visible to the user?
var caretIsNotVisible = (caretOffsetTop <= viewport.top || caretOffsetTopBottom >= viewport.bottom); // Is the Caret Visible to the user?
if(caretIsNotVisible){ // is the cursor no longer visible to the user?
// Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum.
// Get the new Y by getting the line number and multiplying by the height of each line.
if(evt.which == 37 || evt.which == 38){ // If left or up
var newY = lineHeight * (lineNum -1); // -1 to go to the line above
}else if(evt.which == 39 || evt.which == 40){ // if down or right
var newY = getScrollY() + (lineHeight*3); // the offset and one additional line
if(evt.which == 37 || evt.which == 38){ // If left or up arrow
var newY = caretOffsetTop; // That was easy!
}
if(evt.which == 39 || evt.which == 40){ // if down or right arrow
// only move the viewport if we're at the bottom of the viewport, if we hit down any other time the viewport shouldn't change
// NOTE: This behavior only fires if Chrome decides to break the page layout after a paste, it's annoying but nothing I can do
var selection = getSelection();
// top.console.log("line #", rep.selStart[0]); // the line our caret is on
// top.console.log("firstvisible", visibleLineRange[0]); // the first visiblel ine
// top.console.log("lastVisible", visibleLineRange[1]); // the last visible line
// Holding down arrow after a paste can lose the cursor -- This is the best fix I can find
if(rep.selStart[0] >= visibleLineRange[1] || rep.selStart[0] < visibleLineRange[0] ){ // if we're not at the bottom of the viewport
// top.console.log(viewport, lineHeight, myselection);
// TODO: Make it so chrome doesnt need to redraw the page by only applying this technique if required
var newY = caretOffsetTop;
}else{ // we're at the bottom of the viewport so snap to a "new viewport"
// top.console.log(viewport, lineHeight, myselection);
var newY = caretOffsetTopBottom; // Allow continuous holding of down arrow to redraw the screen so we can see what we are going to highlight
}
}
if(newY){
setScrollY(newY); // set the scrollY offset of the viewport on the document
}
setScrollY(newY); // set the scroll height of the browser
}
}
}
}
if (type == "keydown")
@ -5109,7 +5143,7 @@ function Ace2Inner(){
setLineListType(mod[0], mod[1]);
});
}
function doInsertUnorderedList(){
doInsertList('bullet');
}

View file

@ -12,176 +12,248 @@ $(document).ready(function () {
//connect
socket = io.connect(url, {resource : resource}).of("/pluginfw/installer");
$('.search-results').data('query', {
pattern: '',
offset: 0,
limit: 12,
});
var doUpdate = false;
var search = function () {
socket.emit("search", $('.search-results').data('query'));
tasks++;
function search(searchTerm, limit) {
if(search.searchTerm != searchTerm) {
search.offset = 0
search.results = []
search.end = false
}
limit = limit? limit : search.limit
search.searchTerm = searchTerm;
socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir});
search.offset += limit;
$('#search-progress').show()
}
search.offset = 0;
search.limit = 12;
search.results = [];
search.sortBy = 'name';
search.sortDir = /*DESC?*/true;
search.end = true;// have we received all results already?
search.messages = {
show: function(msg) {
$('.search-results .messages').show()
$('.search-results .messages .'+msg+'').show()
},
hide: function(msg) {
$('.search-results .messages').hide()
$('.search-results .messages .'+msg+'').hide()
}
}
function updateHandlers() {
$("form").submit(function(){
var query = $('.search-results').data('query');
query.pattern = $("#search-query").val();
query.offset = 0;
search();
return false;
});
$("#search-query").unbind('keyup').keyup(function () {
var query = $('.search-results').data('query');
query.pattern = $("#search-query").val();
query.offset = 0;
search();
});
$(".do-install, .do-update").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("install", row.find(".name").text());
tasks++;
});
$(".do-uninstall").unbind('click').click(function (e) {
var row = $(e.target).closest("tr");
doUpdate = true;
socket.emit("uninstall", row.find(".name").text());
tasks++;
});
$(".do-prev-page").unbind('click').click(function (e) {
var query = $('.search-results').data('query');
query.offset -= query.limit;
if (query.offset < 0) {
query.offset = 0;
var installed = {
progress: {
show: function(plugin, msg) {
$('.installed-results .'+plugin+' .progress').show()
$('.installed-results .'+plugin+' .progress .message').text(msg)
if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100)
},
hide: function(plugin) {
$('.installed-results .'+plugin+' .progress').hide()
$('.installed-results .'+plugin+' .progress .message').text('')
}
search();
});
$(".do-next-page").unbind('click').click(function (e) {
var query = $('.search-results').data('query');
var total = $('.search-results').data('total');
if (query.offset + query.limit < total) {
query.offset += query.limit;
},
messages: {
show: function(msg) {
$('.installed-results .messages').show()
$('.installed-results .messages .'+msg+'').show()
},
hide: function(msg) {
$('.installed-results .messages').hide()
$('.installed-results .messages .'+msg+'').hide()
}
search();
});
},
list: []
}
updateHandlers();
var tasks = 0;
socket.on('progress', function (data) {
$("#progress").show();
$('#progress').data('progress', data.progress);
var message = "Unknown status";
if (data.message) {
message = data.message.toString();
}
if (data.error) {
data.progress = 1;
}
$("#progress .message").html(message);
if (data.progress >= 1) {
tasks--;
if (tasks <= 0) {
// Hide the activity indicator once all tasks are done
$("#progress").hide();
tasks = 0;
}
if (data.error) {
alert('An error occurred: '+data.error+' -- the server log might know more...');
}else {
if (doUpdate) {
doUpdate = false;
socket.emit("load");
tasks++;
}
}
}
});
socket.on('search-result', function (data) {
var widget=$(".search-results");
widget.data('query', data.query);
widget.data('total', data.total);
widget.find('.offset').html(data.query.offset);
if (data.query.offset + data.query.limit > data.total){
widget.find('.limit').html(data.total);
}else{
widget.find('.limit').html(data.query.offset + data.query.limit);
}
widget.find('.total').html(data.total);
widget.find(".results *").remove();
for (plugin_name in data.results) {
var plugin = data.results[plugin_name];
var row = widget.find(".template tr").clone();
function displayPluginList(plugins, container, template) {
plugins.forEach(function(plugin) {
var row = template.clone();
for (attr in plugin) {
if(attr == "name"){ // Hack to rewrite URLS into name
row.find(".name").html("<a target='_blank' href='https://npmjs.org/package/"+plugin['name']+"'>"+plugin[attr]+"</a>");
row.find(".name").html("<a target='_blank' title='Plugin details' href='https://npmjs.org/package/"+plugin['name']+"'>"+plugin['name'].substr(3)+"</a>"); // remove 'ep_'
}else{
row.find("." + attr).html(plugin[attr]);
}
}
row.find(".version").html( data.results[plugin_name]['dist-tags'].latest );
widget.find(".results").append(row);
}
row.find(".version").html( plugin.version );
row.addClass(plugin.name)
row.data('plugin', plugin.name)
container.append(row);
})
updateHandlers();
}
function sortPluginList(plugins, property, /*ASC?*/dir) {
return plugins.sort(function(a, b) {
if (a[property] < b[property])
return dir? -1 : 1;
if (a[property] > b[property])
return dir? 1 : -1;
// a must be equal to b
return 0;
})
}
// Infinite scroll
$(window).scroll(checkInfiniteScroll)
function checkInfiniteScroll() {
if(search.end) return;// don't keep requesting if there are no more results
try{
var top = $('.search-results .results > tr:last').offset().top
if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm)
}catch(e){}
}
function updateHandlers() {
// Search
$("#search-query").unbind('keyup').keyup(function () {
search($("#search-query").val());
});
// update & install
$(".do-install, .do-update").unbind('click').click(function (e) {
var $row = $(e.target).closest("tr")
, plugin = $row.data('plugin');
if($(this).hasClass('do-install')) {
$row.remove().appendTo('#installed-plugins')
installed.progress.show(plugin, 'Installing')
}else{
installed.progress.show(plugin, 'Updating')
}
socket.emit("install", plugin);
installed.messages.hide("nothing-installed")
});
// uninstall
$(".do-uninstall").unbind('click').click(function (e) {
var $row = $(e.target).closest("tr")
, pluginName = $row.data('plugin');
socket.emit("uninstall", pluginName);
installed.progress.show(pluginName, 'Uninstalling')
installed.list = installed.list.filter(function(plugin) {
return plugin.name != pluginName
})
});
// Sort
$('.sort.up').unbind('click').click(function() {
search.sortBy = $(this).text().toLowerCase();
search.sortDir = false;
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
$('.sort.down, .sort.none').unbind('click').click(function() {
search.sortBy = $(this).text().toLowerCase();
search.sortDir = true;
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
}
socket.on('results:search', function (data) {
if(!data.results.length) search.end = true;
search.messages.hide('nothing-found')
search.messages.hide('fetching')
$("#search-query").removeAttr('disabled')
console.log('got search results', data)
// add to results
search.results = search.results.concat(data.results);
// Update sorting head
$('.sort')
.removeClass('up down')
.addClass('none');
$('.search-results thead th[data-label='+data.query.sortBy+']')
.removeClass('none')
.addClass(data.query.sortDir? 'up' : 'down');
// re-render search results
var searchWidget = $(".search-results");
searchWidget.find(".results *").remove();
if(search.results.length > 0) {
displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr"))
}else {
search.messages.show('nothing-found')
}
$('#search-progress').hide()
checkInfiniteScroll()
});
socket.on('installed-results', function (data) {
$("#installed-plugins *").remove();
socket.on('results:installed', function (data) {
installed.messages.hide("fetching")
installed.messages.hide("nothing-installed")
for (plugin_name in data.results) {
if (plugin_name == "ep_etherpad-lite") continue; // Hack...
var plugin = data.results[plugin_name];
var row = $("#installed-plugin-template").clone();
installed.list = data.installed
sortPluginList(installed.list, 'name', /*ASC?*/true);
for (attr in plugin.package) {
if(attr == "name"){ // Hack to rewrite URLS into name
row.find(".name").html("<a target='_blank' href='https://npmjs.org/package/"+plugin.package['name']+"'>"+plugin.package[attr]+"</a>");
}else{
row.find("." + attr).html(plugin.package[attr]);
}
}
$("#installed-plugins").append(row);
// filter out epl
installed.list = installed.list.filter(function(plugin) {
return plugin.name != 'ep_etherpad-lite'
})
// remove all installed plugins (leave plugins that are still being installed)
installed.list.forEach(function(plugin) {
$('#installed-plugins .'+plugin.name).remove()
})
if(installed.list.length > 0) {
displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template"));
socket.emit('checkUpdates');
}else {
installed.messages.show("nothing-installed")
}
updateHandlers();
socket.emit('checkUpdates');
tasks++;
});
socket.on('updatable', function(data) {
$('#installed-plugins>tr').each(function(i,tr) {
var pluginName = $(tr).find('.name').text()
if (data.updatable.indexOf(pluginName) >= 0) {
var actions = $(tr).find('.actions')
actions.append('<input class="do-update" type="button" value="Update" />')
actions.css('width', 200)
}
socket.on('results:updatable', function(data) {
data.updatable.forEach(function(pluginName) {
var $row = $('#installed-plugins > tr.'+pluginName)
, actions = $row.find('.actions')
actions.append('<input class="do-update" type="button" value="Update" />')
})
updateHandlers();
})
socket.emit("load");
tasks++;
search();
socket.on('finished:install', function(data) {
if(data.error) {
alert('An error occured while installing '+data.plugin+' \n'+data.error)
$('#installed-plugins .'+data.plugin).remove()
}
socket.emit("getInstalled");
// update search results
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
socket.on('finished:uninstall', function(data) {
if(data.error) alert('An error occured while uninstalling the '+data.plugin+' \n'+data.error)
// remove plugin from installed list
$('#installed-plugins .'+data.plugin).remove()
socket.emit("getInstalled");
// update search results
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
// init
updateHandlers();
socket.emit("getInstalled");
search('');
// check for updates every 5mins
setInterval(function() {
socket.emit('checkUpdates');
}, 1000*60*5)
});

View file

@ -17,6 +17,7 @@
var padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie;
var Tinycon = require('tinycon/tinycon');
var hooks = require('./pluginfw/hooks');
var chat = (function()
{
@ -77,7 +78,7 @@ var chat = (function()
$("#chatinput").val("");
},
addMessage: function(msg, increment, isHistoryAdd)
{
{
//correct the time
msg.time += this._pad.clientTimeOffset;
@ -99,74 +100,68 @@ var chat = (function()
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank");
/* Performs an action if your name is mentioned */
var myName = $('#myusernameedit').val();
myName = myName.toLowerCase();
var chatText = text.toLowerCase();
var wasMentioned = false;
if (chatText.indexOf(myName) !== -1 && myName != "undefined"){
wasMentioned = true;
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
// the hook args
var ctx = {
"authorName" : authorName,
"author" : msg.userId,
"text" : text,
"sticky" : false,
"timestamp" : msg.time,
"timeStr" : timeStr
}
/* End of new action */
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + timeStr + "</span> " + text + "</p>";
if(isHistoryAdd)
$(html).insertAfter('#chatloadmessagesbutton');
else
$("#chattext").append(html);
//should we increment the counter??
if(increment && !isHistoryAdd)
{
var count = Number($("#chatcounter").text());
count++;
// is the users focus already in the chatbox?
var alreadyFocused = $("#chatinput").is(":focus");
// does the user already have the chatbox open?
var chatOpen = $("#chatbox").is(":visible");
// is the users focus already in the chatbox?
var alreadyFocused = $("#chatinput").is(":focus");
$("#chatcounter").text(count);
// chat throb stuff -- Just make it throw for twice as long
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen)
{ // If the user was mentioned show for twice as long and flash the browser window
$.gritter.add({
// (string | mandatory) the heading of the notification
title: authorName,
// (string | mandatory) the text inside the notification
text: text,
// (bool | optional) if you want it to fade out on its own or just sit there
sticky: true,
// (int | optional) the time you want it to be alive for before fading out
time: '2000'
});
// does the user already have the chatbox open?
var chatOpen = $("#chatbox").is(":visible");
chatMentions++;
Tinycon.setBubble(chatMentions);
}
// does this message contain this user's name? (is the curretn user mentioned?)
var myName = $('#myusernameedit').val();
var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined");
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen)
{ // If the user was mentioned show for twice as long and flash the browser window
chatMentions++;
Tinycon.setBubble(chatMentions);
ctx.sticky = true;
}
// Call chat message hook
hooks.aCallAll("chatNewMessage", ctx, function() {
var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + ctx.timeStr + "</span> " + ctx.text + "</p>";
if(isHistoryAdd)
$(html).insertAfter('#chatloadmessagesbutton');
else
$("#chattext").append(html);
//should we increment the counter??
if(increment && !isHistoryAdd)
{
if(!chatOpen){
// Update the counter of unread messages
var count = Number($("#chatcounter").text());
count++;
$("#chatcounter").text(count);
if(!chatOpen) {
$.gritter.add({
// (string | mandatory) the heading of the notification
title: authorName,
title: ctx.authorName,
// (string | mandatory) the text inside the notification
text: text,
text: ctx.text,
// (bool | optional) if you want it to fade out on its own or just sit there
sticky: false,
sticky: ctx.sticky,
// (int | optional) the time you want it to be alive for before fading out
time: '4000'
});
Tinycon.setBubble(count);
}
}
}
// Clear the chat mentions when the user clicks on the chat input box
});
// Clear the chat mentions when the user clicks on the chat input box
$('#chatinput').click(function(){
chatMentions = 0;
Tinycon.setBubble(0);

View file

@ -278,8 +278,9 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (!getSocket()) return;
if (!evt.data) return;
var wrapper = evt;
if (wrapper.type != "COLLABROOM") return;
if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return;
var msg = wrapper.data;
if (msg.type == "NEW_CHANGES")
{
var newRev = msg.newRev;
@ -390,6 +391,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
callbacks.onUserLeave(userInfo);
}
}
else if (msg.type == "DISCONNECT_REASON")
{
appLevelDisconnectReason = msg.reason;

View file

@ -21,8 +21,6 @@
$.gritter.options = {
position: '',
class_name: '', // could be set to 'gritter-light' to use white notifications
fade_in_speed: 'medium', // how fast notifications fade in
fade_out_speed: 1000, // how fast the notices fade out
time: 6000 // hang on the screen for...
}

View file

@ -23,27 +23,27 @@
window.html10n = (function(window, document, undefined) {
// fix console
var console = window.console;
var console = window.console
function interceptConsole(method){
if (!console) return function() {};
if (!console) return function() {}
var original = console[method];
var original = console[method]
// do sneaky stuff
if (original.bind){
// Do this for normal browsers
return original.bind(console);
return original.bind(console)
}else{
return function() {
// Do this for IE
var message = Array.prototype.slice.apply(arguments).join(' ');
original(message);
var message = Array.prototype.slice.apply(arguments).join(' ')
original(message)
}
}
}
var consoleLog = interceptConsole('log')
, consoleWarn = interceptConsole('warn')
, consoleError = interceptConsole('warn');
, consoleError = interceptConsole('warn')
// fix Array.prototype.instanceOf in, guess what, IE! <3
@ -84,14 +84,14 @@ window.html10n = (function(window, document, undefined) {
* MicroEvent - to make any js object an event emitter (server or browser)
*/
var MicroEvent = function(){}
var MicroEvent = function(){}
MicroEvent.prototype = {
bind : function(event, fct){
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
@ -100,7 +100,7 @@ window.html10n = (function(window, document, undefined) {
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
}
}
};
@ -122,50 +122,50 @@ window.html10n = (function(window, document, undefined) {
* and caching all necessary resources
*/
function Loader(resources) {
this.resources = resources;
this.cache = {}; // file => contents
this.langs = {}; // lang => strings
this.resources = resources
this.cache = {} // file => contents
this.langs = {} // lang => strings
}
Loader.prototype.load = function(lang, cb) {
if(this.langs[lang]) return cb();
if(this.langs[lang]) return cb()
if (this.resources.length > 0) {
var reqs = 0;
for (var i=0, n=this.resources.length; i < n; i++) {
this.fetch(this.resources[i], lang, function(e) {
reqs++;
if(e) return setTimeout(function(){ throw e }, 0);
if(e) console.warn(e)
if (reqs < n) return;// Call back once all reqs are completed
cb && cb();
cb && cb()
})
}
}
}
Loader.prototype.fetch = function(href, lang, cb) {
var that = this;
var that = this
if (this.cache[href]) {
this.parse(lang, href, this.cache[href], cb)
return;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', href, /*async: */true);
var xhr = new XMLHttpRequest()
xhr.open('GET', href, /*async: */true)
if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json; charset=utf-8');
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) {
var data = JSON.parse(xhr.responseText);
that.cache[href] = data;
var data = JSON.parse(xhr.responseText)
that.cache[href] = data
// Pass on the contents for parsing
that.parse(lang, href, data, cb);
that.parse(lang, href, data, cb)
} else {
cb(new Error('Failed to load '+href));
cb(new Error('Failed to load '+href))
}
}
};
@ -174,39 +174,38 @@ window.html10n = (function(window, document, undefined) {
Loader.prototype.parse = function(lang, currHref, data, cb) {
if ('object' != typeof data) {
cb(new Error('A file couldn\'t be parsed as json.'));
return;
cb(new Error('A file couldn\'t be parsed as json.'))
return
}
if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-'));
if (!data[lang]) {
cb(new Error('Couldn\'t find translations for '+lang));
return;
cb(new Error('Couldn\'t find translations for '+lang))
return
}
if ('string' == typeof data[lang]) {
// Import rule
// absolute path
var importUrl = data[lang];
var importUrl = data[lang]
// relative path
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
importUrl = currHref+"/../"+data[lang];
importUrl = currHref+"/../"+data[lang]
}
this.fetch(importUrl, lang, cb);
return;
this.fetch(importUrl, lang, cb)
return
}
if ('object' != typeof data[lang]) {
cb(new Error('Translations should be specified as JSON objects!'));
return;
cb(new Error('Translations should be specified as JSON objects!'))
return
}
this.langs[lang] = data[lang];
this.langs[lang] = data[lang]
// TODO: Also store accompanying langs
cb();
cb()
}
@ -216,11 +215,11 @@ window.html10n = (function(window, document, undefined) {
var html10n =
{ language : null
}
MicroEvent.mixin(html10n);
MicroEvent.mixin(html10n)
html10n.macros = {};
html10n.macros = {}
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"];
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
/**
* Get rules for plural forms (shared with JetPack), see:
@ -664,14 +663,22 @@ window.html10n = (function(window, document, undefined) {
* @param langs An array of lang codes defining fallbacks
*/
html10n.localize = function(langs) {
var that = this;
var that = this
// if only one string => create an array
if ('string' == typeof langs) langs = [langs];
if ('string' == typeof langs) langs = [langs]
// Expand two-part locale specs
var i=0
langs.forEach(function(lang) {
if(!lang) return
langs[i++] = lang
if(~lang.indexOf('-')) langs[i++] = lang.substr(0, lang.indexOf('-'))
})
this.build(langs, function(er, translations) {
html10n.translations = translations;
html10n.translateElement(translations);
that.trigger('localized');
html10n.translations = translations
html10n.translateElement(translations)
that.trigger('localized')
})
}
@ -682,78 +689,78 @@ window.html10n = (function(window, document, undefined) {
* @param element A DOM element, if omitted, the document element will be used
*/
html10n.translateElement = function(translations, element) {
element = element || document.documentElement;
element = element || document.documentElement
var children = element? getTranslatableChildren(element) : document.childNodes;
for (var i=0, n=children.length; i < n; i++) {
this.translateNode(translations, children[i]);
this.translateNode(translations, children[i])
}
// translate element itself if necessary
this.translateNode(translations, element);
this.translateNode(translations, element)
}
function asyncForEach(list, iterator, cb) {
var i = 0
, n = list.length;
, n = list.length
iterator(list[i], i, function each(err) {
if(err) consoleLog(err);
i++;
if(err) consoleLog(err)
i++
if (i < n) return iterator(list[i],i, each);
cb();
cb()
})
}
function getTranslatableChildren(element) {
if(!document.querySelectorAll) {
if (!element) return [];
if (!element) return []
var nodes = element.getElementsByTagName('*')
, l10nElements = [];
, l10nElements = []
for (var i=0, n=nodes.length; i < n; i++) {
if (nodes[i].getAttribute('data-l10n-id'))
l10nElements.push(nodes[i]);
}
return l10nElements;
return l10nElements
}
return element.querySelectorAll('*[data-l10n-id]');
return element.querySelectorAll('*[data-l10n-id]')
}
html10n.get = function(id, args) {
var translations = html10n.translations;
if(!translations) return consoleWarn('No translations available (yet)');
if(!translations[id]) return consoleWarn('Could not find string '+id);
var translations = html10n.translations
if(!translations) return consoleWarn('No translations available (yet)')
if(!translations[id]) return consoleWarn('Could not find string '+id)
// apply args
var str = substArguments(translations[id], args);
var str = substArguments(translations[id], args)
// apply macros
return substMacros(id, str, args);
return substMacros(id, str, args)
// replace {{arguments}} with their values or the
// associated translation string (based on its key)
function substArguments(str, args) {
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
, match;
, match
while (match = reArgs.exec(str)) {
if (!match || match.length < 2)
return str; // argument key not found
return str // argument key not found
var arg = match[1]
, sub = '';
, sub = ''
if (arg in args) {
sub = args[arg];
sub = args[arg]
} else if (arg in translations) {
sub = translations[arg];
sub = translations[arg]
} else {
consoleWarn('Could not find argument {{' + arg + '}}');
return str;
consoleWarn('Could not find argument {{' + arg + '}}')
return str
}
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length);
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length)
}
return str;
return str
}
// replace {[macros]} with their values
@ -766,21 +773,21 @@ window.html10n = (function(window, document, undefined) {
// a macro has been found
// Note: at the moment, only one parameter is supported
var macroName = reMatch[1]
, paramName = reMatch[2];
, paramName = reMatch[2]
if (!(macroName in gMacros)) return str;
if (!(macroName in gMacros)) return str
var param;
var param
if (args && paramName in args) {
param = args[paramName];
param = args[paramName]
} else if (paramName in translations) {
param = translations[paramName];
param = translations[paramName]
}
// there's no macro parser yet: it has to be defined in gMacros
var macro = html10n.macros[macroName];
str = macro(translations, key, str, param);
return str;
var macro = html10n.macros[macroName]
str = macro(translations, key, str, param)
return str
}
}
@ -788,26 +795,26 @@ window.html10n = (function(window, document, undefined) {
* Applies translations to a DOM node (recursive)
*/
html10n.translateNode = function(translations, node) {
var str = {};
var str = {}
// get id
str.id = node.getAttribute('data-l10n-id');
if (!str.id) return;
str.id = node.getAttribute('data-l10n-id')
if (!str.id) return
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id);
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id)
// get args
if(window.JSON) {
str.args = JSON.parse(node.getAttribute('data-l10n-args'));
str.args = JSON.parse(node.getAttribute('data-l10n-args'))
}else{
try{
str.args = eval(node.getAttribute('data-l10n-args'));
str.args = eval(node.getAttribute('data-l10n-args'))
}catch(e) {
consoleWarn('Couldn\'t parse args for '+str.id);
consoleWarn('Couldn\'t parse args for '+str.id)
}
}
str.str = html10n.get(str.id, str.args);
str.str = html10n.get(str.id, str.args)
// get attribute name to apply str to
var prop
@ -817,31 +824,31 @@ window.html10n = (function(window, document, undefined) {
, "innerHTML": 1
, "alt": 1
, "textContent": 1
};
}
if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified
prop = str.id.substr(index + 1);
prop = str.id.substr(index + 1)
} else { // no attribute: assuming text content by default
prop = document.body.textContent ? 'textContent' : 'innerText';
prop = document.body.textContent ? 'textContent' : 'innerText'
}
// Apply translation
if (node.children.length === 0 || prop != 'textContent') {
node[prop] = str.str;
node[prop] = str.str
} else {
var children = node.childNodes,
found = false;
found = false
for (var i=0, n=children.length; i < n; i++) {
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
if (!found) {
children[i].nodeValue = str.str;
found = true;
children[i].nodeValue = str.str
found = true
} else {
children[i].nodeValue = '';
children[i].nodeValue = ''
}
}
}
if (!found) {
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node);
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node)
}
}
}
@ -852,32 +859,32 @@ window.html10n = (function(window, document, undefined) {
*/
html10n.build = function(langs, cb) {
var that = this
, build = {};
, build = {}
asyncForEach(langs, function (lang, i, next) {
if(!lang) return next();
that.loader.load(lang, next);
that.loader.load(lang, next)
}, function() {
var lang;
langs.reverse();
var lang
langs.reverse()
// loop through priority array...
for (var i=0, n=langs.length; i < n; i++) {
lang = langs[i];
lang = langs[i]
if(!lang || !(lang in that.loader.langs)) continue;
// ... and apply all strings of the current lang in the list
// to our build object
for (var string in that.loader.langs[lang]) {
build[string] = that.loader.langs[lang][string];
build[string] = that.loader.langs[lang][string]
}
// the last applied lang will be exposed as the
// lang the page was translated to
that.language = lang;
that.language = lang
}
cb(null, build);
cb(null, build)
})
}
@ -893,8 +900,8 @@ window.html10n = (function(window, document, undefined) {
* Returns the direction of the language returned be html10n#getLanguage
*/
html10n.getDirection = function() {
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'));
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl';
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'))
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'
}
/**
@ -903,28 +910,28 @@ window.html10n = (function(window, document, undefined) {
html10n.index = function () {
// Find all <link>s
var links = document.getElementsByTagName('link')
, resources = [];
, resources = []
for (var i=0, n=links.length; i < n; i++) {
if (links[i].type != 'application/l10n+json')
continue;
resources.push(links[i].href);
resources.push(links[i].href)
}
this.loader = new Loader(resources);
this.trigger('indexed');
this.loader = new Loader(resources)
this.trigger('indexed')
}
if (document.addEventListener) // modern browsers and IE9+
document.addEventListener('DOMContentLoaded', function() {
html10n.index();
}, false);
html10n.index()
}, false)
else if (window.attachEvent)
window.attachEvent('onload', function() {
html10n.index();
}, false);
html10n.index()
}, false)
// gettext-like shortcut
if (window._ === undefined)
window._ = html10n.get;
return html10n;
})(window, document);
return html10n
})(window, document)

View file

@ -191,7 +191,7 @@ function handshake()
createCookie("token", token, 60);
}
var sessionID = readCookie("sessionID");
var sessionID = decodeURIComponent(readCookie("sessionID"));
var password = readCookie("password");
var msg = {
@ -242,7 +242,7 @@ function handshake()
pad.collabClient.setChannelState("RECONNECTING");
disconnectTimeout = setTimeout(disconnectEvent, 10000);
disconnectTimeout = setTimeout(disconnectEvent, 20000);
}
});
@ -252,14 +252,22 @@ function handshake()
socket.on('message', function(obj)
{
//the access was not granted, give the user a message
if(!receivedClientVars && obj.accessStatus)
if(obj.accessStatus)
{
$('.passForm').submit(require(module.id).savePassword);
if(!receivedClientVars)
$('.passForm').submit(require(module.id).savePassword);
if(obj.accessStatus == "deny")
{
$('#loading').hide();
$("#permissionDenied").show();
if(receivedClientVars)
{
// got kicked
$("#editorcontainer").hide();
$("#editorloadingbox").show();
}
}
else if(obj.accessStatus == "needPassword")
{
@ -365,8 +373,7 @@ function handshake()
$.extend($.gritter.options, {
position: 'bottom-right', // defaults to 'top-right' but can be 'bottom-left', 'bottom-right', 'top-left', 'top-right' (added in 1.7.1)
fade_in_speed: 'medium', // how fast notifications fade in (string or int)
fade_out_speed: 2000, // how fast the notices fade out
fade: false, // dont fade, too jerky on mobile
time: 6000 // hang on the screen for...
});
@ -442,6 +449,7 @@ var pad = {
//initialize the chat
chat.init(this);
padcookie.init(); // initialize the cookies
pad.initTime = +(new Date());
pad.padOptions = clientVars.initialOptions;

View file

@ -76,7 +76,6 @@ var padconnectionstatus = (function()
},
isFullyConnected: function()
{
padmodals.hideOverlay();
return status.what == 'connected';
},
getStatus: function()

View file

@ -40,22 +40,10 @@ var padmodals = (function()
});
},
showOverlay: function(duration) {
$("#overlay").show().css(
{
'opacity': 0
}).animate(
{
'opacity': 1
}, duration);
$("#overlay").show();
},
hideOverlay: function(duration) {
$("#overlay").animate(
{
'opacity': 0
}, duration, function()
{
$("#modaloverlay").hide();
});
$("#overlay").hide();
}
};
return self;

View file

@ -1,118 +1,77 @@
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm");
var RegClient = require("npm-registry-client")
var registry = new RegClient(
{ registry: "http://registry.npmjs.org"
, cache: npm.cache }
);
var withNpm = function (npmfn, final, cb) {
var npmIsLoaded = false;
var withNpm = function (npmfn) {
if(npmIsLoaded) return npmfn();
npm.load({}, function (er) {
if (er) return cb({progress:1, error:er});
if (er) return npmfn(er);
npmIsLoaded = true;
npm.on("log", function (message) {
cb({progress: 0.5, message:message.msg + ": " + message.pref});
});
npmfn(function (er, data) {
if (er) {
console.error(er);
return cb({progress:1, error: er.message});
}
if (!data) data = {};
data.progress = 1;
data.message = "Done.";
cb(data);
final();
console.log('npm: ',message)
});
npmfn();
});
}
// All these functions call their callback multiple times with
// {progress:[0,1], message:STRING, error:object}. They will call it
// with progress = 1 at least once, and at all times will either
// message or error be present, not both. It can be called multiple
// times for all values of propgress except for 1.
exports.uninstall = function(plugin_name, cb) {
withNpm(
function (cb) {
npm.commands.uninstall([plugin_name], function (er) {
withNpm(function (er) {
if (er) return cb && cb(er);
npm.commands.uninstall([plugin_name], function (er) {
if (er) return cb && cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
plugins.update(cb);
hooks.aCallAll("restartServer", {}, function () {});
});
},
function () {
hooks.aCallAll("restartServer", {}, function () {});
},
cb
);
});
});
};
exports.install = function(plugin_name, cb) {
withNpm(
function (cb) {
npm.commands.install([plugin_name], function (er) {
withNpm(function (er) {
if (er) return cb && cb(er);
npm.commands.install([plugin_name], function (er) {
if (er) return cb && cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er);
plugins.update(cb);
});
plugins.update(cb);
hooks.aCallAll("restartServer", {}, function () {});
});
},
function () {
hooks.aCallAll("restartServer", {}, function () {});
},
cb
);
});
});
};
exports.searchCache = null;
exports.availablePlugins = null;
var cacheTimestamp = 0;
exports.search = function(query, cache, cb) {
withNpm(
function (cb) {
var getData = function (cb) {
if (cache && exports.searchCache) {
cb(null, exports.searchCache);
} else {
registry.get(
"/-/all", 600, false, true,
function (er, data) {
if (er) return cb(er);
exports.searchCache = data;
cb(er, data);
}
);
}
}
getData(
function (er, data) {
if (er) return cb(er);
var res = {};
var i = 0;
var pattern = query.pattern.toLowerCase();
for (key in data) { // for every plugin in the data from npm
if ( key.indexOf(plugins.prefix) == 0
&& key.indexOf(pattern) != -1
|| key.indexOf(plugins.prefix) == 0
&& data[key].description.indexOf(pattern) != -1
) { // If the name contains ep_ and the search string is in the name or description
i++;
if (i > query.offset
&& i <= query.offset + query.limit) {
res[key] = data[key];
}
}
}
cb(null, {results:res, query: query, total:i});
}
);
},
function () { },
cb
);
exports.getAvailablePlugins = function(maxCacheAge, cb) {
withNpm(function (er) {
if (er) return cb && cb(er);
if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) {
return cb && cb(null, exports.availablePlugins)
}
npm.commands.search(['ep_'], /*silent?*/true, function(er, results) {
if(er) return cb && cb(er);
exports.availablePlugins = results;
cacheTimestamp = Math.round(+new Date/1000);
cb && cb(null, results)
})
});
};
exports.search = function(searchTerm, maxCacheAge, cb) {
exports.getAvailablePlugins(maxCacheAge, function(er, results) {
if(er) return cb && cb(er);
var res = {};
searchTerm = searchTerm.toLowerCase();
for (var pluginName in results) { // for every available plugin
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
if(pluginName.indexOf(searchTerm) < 0 && results[pluginName].description.indexOf(searchTerm) < 0) continue;
res[pluginName] = results[pluginName];
}
cb && cb(null, res)
})
};

View file

@ -116,7 +116,7 @@ function init() {
//sends a message over the socket
function sendSocketMsg(type, data)
{
var sessionID = readCookie("sessionID");
var sessionID = decodeURIComponent(readCookie("sessionID"));
var password = readCookie("password");
var msg = { "component" : "pad", // FIXME: Remove this stupidity!

View file

@ -28,43 +28,11 @@
<li><a href="plugins/info">Troubleshooting information</a> </li>
<% e.end_block(); %>
</ul>
<div id="progress"><img src="../static/img/loading.gif">&nbsp;&nbsp;<span class="message"></span></div>
</div>
<div class="innerwrapper">
<h2>Installed plugins</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Version</th>
<td></td>
</tr>
</thead>
<tbody class="template">
<tr id="installed-plugin-template">
<td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td>
<td class="actions">
<input type="button" value="Uninstall" class="do-uninstall">
</td>
</tr>
</tbody>
<tbody id="installed-plugins">
</tbody>
</table>
<div class="paged listing search-results">
<div class="separator"></div>
<h2>Available plugins</h2>
<form>
<input type="text" name="search" placeholder="Search for plugins to install" id="search-query">
</form>
<table>
<h2>Installed plugins</h2>
<table class="installed-results">
<thead>
<tr>
<th>Name</th>
@ -74,23 +42,70 @@
</tr>
</thead>
<tbody class="template">
<tr>
<tr id="installed-plugin-template">
<td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td>
<td class="actions">
<input type="button" value="Install" class="do-install">
</td>
<td>
<div class="actions">
<input type="button" value="Uninstall" class="do-uninstall">
<div class="progress"><p><img src="../static/img/loading.gif"/></p><p><span class="message"></span></p></div>
</div>
</td>
</tr>
</tbody>
<tbody class="results">
<tbody id="installed-plugins">
</tbody>
<tbody class="messages">
<tr><td></td><td>
<p class="nothing-installed">You haven't installed any plugins yet.</p>
<p class="fetching"><img src="../static/img/loading.gif"/><br/>Fetching installed plugins...</p>
</td><td></td></tr>
</tbody>
</table>
<input type="button" value="<<" class="do-prev-page">
<span class="offset"></span>..<span class="limit"></span> of <span class="total"></span>.
<input type="button" value=">>" class="do-next-page">
</div>
<div class="paged listing search-results">
<div class="separator"></div>
<h2>Available plugins</h2>
<form>
<input type="text" name="search" disabled placeholder="Search for plugins to install" id="search-query">
</form>
<table>
<thead>
<tr>
<th class="sort up" data-label="name">Name</th>
<th class="sort none" data-label="description">Description</th>
<th class="sort none" data-label="version">Version</th>
<td></td>
</tr>
</thead>
<tbody class="template">
<tr>
<td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td>
<td>
<div class="actions">
<input type="button" value="Install" class="do-install">
<div class="progress"><p><img src="../static/img/loading.gif"/></p><p><span class="message"></span></p></div>
</div>
</td>
</tr>
</tbody>
<tbody class="results">
</tbody>
<tbody class="messages">
<tr><td></td><td>
<div class="search-progress" class="progress"><img src="../static/img/loading.gif"/></div>
<p class="nothing-found">No plugins found.</p>
<p class="fetching"><img src="../static/img/loading.gif"/><br/>Fetching catalogue...</p>
</td><td></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</body>

View file

@ -195,7 +195,9 @@
<div id="wrongPassword">
<p data-l10n-id="pad.wrongPassword">Your password was wrong</p>
</div>
<% e.begin_block("loading"); %>
<p data-l10n-id="pad.loading" id="loading">Loading...</p>
<% e.end_block(); %>
<noscript><strong>Sorry, you have to enable Javascript in order to use this.</strong></noscript>
</div>
</div>
@ -376,7 +378,10 @@
</div>
<div id="chatbox">
<div id="titlebar"><span id ="titlelabel" data-l10n-id="pad.chat"></span><a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a></div>
<div id="titlebar"><span id ="titlelabel" data-l10n-id="pad.chat"></span>
<a id="titlecross" onClick="chat.hide();return false;">-&nbsp;</a>
<a id="titlesticky" onClick="chat.stickToScreen(true);$('#options-stickychat').prop('checked', true);return false;" title="Stick chat to screen">&nbsp;&nbsp;</a>
</div>
<div id="chattext" class="authorColors">
<img alt="loading.." id="chatloadmessagesball" class="chatloadmessages" src="../static/img/loading.gif" align="top">
<button id="chatloadmessagesbutton" class="chatloadmessages" data-l10n-id="pad.chat.loadmessages"></button>

View file

@ -0,0 +1,334 @@
describe("As the caret is moved is the UI properly updated?", function(){
var padName;
var numberOfRows = 50;
it("creates a pad", function(done) {
padName = helper.newPad(done);
this.timeout(60000);
});
/* Tests to do
* Keystroke up (38), down (40), left (37), right (39) with and without special keys IE control / shift
* Page up (33) / down (34) with and without special keys
* Page up on the first line shouldn't move the viewport
* Down down on the last line shouldn't move the viewport
* Down arrow on any other line except the last lines shouldn't move the viewport
* Do all of the above tests after a copy/paste event
*/
/* Challenges
* How do we keep the authors focus on a line if the lines above the author are modified? We should only redraw the user to a location if they are typing and make sure shift and arrow keys aren't redrawing the UI else highlight - copy/paste would get broken
* How can we simulate an edit event in the test framework?
*/
// THIS DOESNT WORK AS IT DOESNT MOVE THE CURSOR!
it("down arrow", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
$newFirstTextElement.focus();
keyEvent(inner$, 37, false, false); // arrow down
keyEvent(inner$, 37, false, false); // arrow down
done();
});
/*
it("Creates N lines", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
var $newFirstTextElement = inner$("div").first();
prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target
helper.waitFor(function(){ // Wait for the DOM to register the new items
return inner$("div").first().text().length == 6;
}).done(function(){ // Once the DOM has registered the items
done();
});
});
it("Moves caret up a line", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.y;
var newCaretPos;
keyEvent(inner$, 38, false, false); // arrow up
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.y;
return (newCaretPos < originalPos);
}).done(function(){
expect(newCaretPos).to.be.lessThan(originalPos);
done();
});
});
it("Moves caret down a line", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.y;
var newCaretPos;
keyEvent(inner$, 40, false, false); // arrow down
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.y;
return (newCaretPos > originalPos);
}).done(function(){
expect(newCaretPos).to.be.moreThan(originalPos);
done();
});
});
it("Moves caret to top of doc", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.y;
var newCaretPos;
var i = 0;
while(i < numberOfRows){ // press pageup key N times
keyEvent(inner$, 33, false, false);
i++;
}
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.y;
return (newCaretPos < originalPos);
}).done(function(){
expect(newCaretPos).to.be.lessThan(originalPos);
done();
});
});
it("Moves caret right a position", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.x;
var newCaretPos;
keyEvent(inner$, 39, false, false); // arrow right
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.x;
return (newCaretPos > originalPos);
}).done(function(){
expect(newCaretPos).to.be.moreThan(originalPos);
done();
});
});
it("Moves caret left a position", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.x;
var newCaretPos;
keyEvent(inner$, 33, false, false); // arrow left
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.x;
return (newCaretPos < originalPos);
}).done(function(){
expect(newCaretPos).to.be.lessThan(originalPos);
done();
});
});
it("Moves caret to the next line using right arrow", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.y;
var newCaretPos;
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
keyEvent(inner$, 39, false, false); // arrow right
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.y;
return (newCaretPos > originalPos);
}).done(function(){
expect(newCaretPos).to.be.moreThan(originalPos);
done();
});
});
it("Moves caret to the previous line using left arrow", function(done){
var inner$ = helper.padInner$;
var $newFirstTextElement = inner$("div").first();
var originalCaretPosition = caretPosition(inner$);
var originalPos = originalCaretPosition.y;
var newCaretPos;
keyEvent(inner$, 33, false, false); // arrow left
helper.waitFor(function(){ // Wait for the DOM to register the new items
var newCaretPosition = caretPosition(inner$);
newCaretPos = newCaretPosition.y;
return (newCaretPos < originalPos);
}).done(function(){
expect(newCaretPos).to.be.lessThan(originalPos);
done();
});
});
/*
it("Creates N rows, changes height of rows, updates UI by caret key events", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
var numberOfRows = 50;
//ace creates a new dom element when you press a keystroke, so just get the first text element again
var $newFirstTextElement = inner$("div").first();
var originalDivHeight = inner$("div").first().css("height");
prepareDocument(numberOfRows, $newFirstTextElement); // N lines into the first div as a target
helper.waitFor(function(){ // Wait for the DOM to register the new items
return inner$("div").first().text().length == 6;
}).done(function(){ // Once the DOM has registered the items
inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc)
var random = Math.floor(Math.random() * (50)) + 20;
$(this).css("height", random+"px");
});
console.log(caretPosition(inner$));
var newDivHeight = inner$("div").first().css("height");
var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height
expect(heightHasChanged).to.be(true); // expect the first line to be blank
});
// Is this Element now visible to the pad user?
helper.waitFor(function(){ // Wait for the DOM to register the new items
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place
}).done(function(){ // Once the DOM has registered the items
inner$("div").each(function(index){ // Randomize the item heights (replicates images / headings etc)
var random = Math.floor(Math.random() * (80 - 20 + 1)) + 20;
$(this).css("height", random+"px");
});
var newDivHeight = inner$("div").first().css("height");
var heightHasChanged = originalDivHeight != newDivHeight; // has the new div height changed from the original div height
expect(heightHasChanged).to.be(true); // expect the first line to be blank
});
var i = 0;
while(i < numberOfRows){ // press down arrow
console.log("dwn");
keyEvent(inner$, 40, false, false);
i++;
}
// Does scrolling back up the pad with the up arrow show the correct contents?
helper.waitFor(function(){ // Wait for the new position to be in place
try{
return isScrolledIntoView(inner$("div:nth-child("+numberOfRows+")"), inner$); // Wait for the DOM to scroll into place
}catch(e){
return false;
}
}).done(function(){ // Once the DOM has registered the items
var i = 0;
while(i < numberOfRows){ // press down arrow
keyEvent(inner$, 33, false, false); // doesn't work
i++;
}
// Does scrolling back up the pad with the up arrow show the correct contents?
helper.waitFor(function(){ // Wait for the new position to be in place
try{
return isScrolledIntoView(inner$("div:nth-child(0)"), inner$); // Wait for the DOM to scroll into place
}catch(e){
return false;
}
}).done(function(){ // Once the DOM has registered the items
});
});
var i = 0;
while(i < numberOfRows){ // press down arrow
keyEvent(inner$, 33, false, false); // doesn't work
i++;
}
// Does scrolling back up the pad with the up arrow show the correct contents?
helper.waitFor(function(){ // Wait for the new position to be in place
return isScrolledIntoView(inner$("div:nth-child(1)"), inner$); // Wait for the DOM to scroll into place
}).done(function(){ // Once the DOM has registered the items
expect(true).to.be(true);
done();
});
*/
});
function prepareDocument(n, target){ // generates a random document with random content on n lines
var i = 0;
while(i < n){ // for each line
target.sendkeys(makeStr()); // generate a random string and send that to the editor
target.sendkeys('{enter}'); // generator an enter keypress
i++; // rinse n times
}
}
function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window
if(target.browser.mozilla){ // if it's a mozilla browser
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = target.Event(evtType);
console.log(e);
if(ctrl){
e.ctrlKey = true; // Control key
}
if(shift){
e.shiftKey = true; // Shift Key
}
e.which = charCode;
e.keyCode = charCode;
target("#innerdocbody").trigger(e);
}
function makeStr(){ // from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function isScrolledIntoView(elem, $){ // from http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top; // how far the element is from the top of it's container
var elemBottom = elemTop + $(elem).height(); // how far plus the height of the elem.. IE is it all in?
elemBottom = elemBottom - 16; // don't ask, sorry but this is needed..
return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
}
function caretPosition($){
var doc = $.window.document;
var pos = doc.getSelection();
pos.y = pos.anchorNode.parentElement.offsetTop;
pos.x = pos.anchorNode.parentElement.offsetLeft;
console.log(pos);
return pos;
}