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 # 1.2.9
* Fix: MAJOR Security issue, where a hacker could submit content as another user * Fix: MAJOR Security issue, where a hacker could submit content as another user
* Fix: security issue due to unescaped user input * Fix: security issue due to unescaped user input
@ -6,7 +30,7 @@
* Fix: PadUsers API endpoint * Fix: PadUsers API endpoint
* NEW: A script to import data to all dbms * NEW: A script to import data to all dbms
* NEW: Add authorId to chat and userlist as a data attribute * 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 * 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 install for admins and easy to use for people
* easy to integrate into other apps, but also usable as standalone * easy to integrate into other apps, but also usable as standalone
* using less resources on server side * 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 * 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! Also, keep it maintainable. We don't wanna end up as the monster Etherpad was!
## How to work with git? ## How to work with git?
* Don't work in your master branch. * Don't work in your master branch.

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 # About
Etherpad lite is a really-real time collaborative editor spawned from the Hell fire of Etherpad. Etherpad is a really-real time collaborative editor maintained by the Etherpad Community.
We're reusing the well tested Etherpad easysync library to make it really realtime. Etherpad Lite
is based on node.js ergo is much lighter and more stable than the original Etherpad. Our hope
is that this will encourage more users to use and install a realtime collaborative editor. A smaller, manageable and well
documented codebase makes it easier for developers to improve the code and contribute towards the project.
**Etherpad vs Etherpad lite** 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.
<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 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) There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
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. 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 Lite comes with translations into tons of different languages!
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** **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 # Installation
Etherpad works with node v0.8 and v0.10, only. (We don't suppot v0.6)
## Windows ## Windows
### Prebuilt windows package ### Prebuilt windows package
@ -62,10 +47,11 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind
[Next steps](#next-steps). [Next steps](#next-steps).
## Linux ## GNU/Linux and other UNIX-like systems
You'll need gzip, git, curl, libssl develop libraries, python and gcc. 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 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 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. 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 # Next Steps
## Tweak the settings ## 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 ## 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. 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 # Development
## Things you should know ## 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). 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). 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! Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), really!
# Get in touch # 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 # Modules created for this project

View file

@ -44,8 +44,8 @@ fi
#check node version #check node version
NODE_VERSION=$(node --version) NODE_VERSION=$(node --version)
NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.6" ]; then 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.6.x or v0.8.x" >&2 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 exit 1
fi fi

View file

@ -8,7 +8,7 @@ cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && ex
echo _ echo _
echo Checking node version... 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 cmd /C node -e %check_version% || exit /B 1
echo _ 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. 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 ## collectContentPre
Called from: src/static/js/contentcollector.js 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 lists all pads on this epl instance
*Example returns:* *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.clearAuthorship.title": "Llimpiar los colores d'autor\u00eda",
"pad.toolbar.import_export.title": "Importar\/Esportar ente distintos formatos de ficheru", "pad.toolbar.import_export.title": "Importar\/Esportar ente distintos formatos de ficheru",
"pad.toolbar.timeslider.title": "Eslizador de tiempu", "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.settings.title": "Configuraci\u00f3n",
"pad.toolbar.embed.title": "Incrustar esti bloc", "pad.toolbar.embed.title": "Incrustar esti bloc",
"pad.toolbar.showusers.title": "Amosar los usuarios d'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.stickychat": "Alderique en pantalla siempres",
"pad.settings.colorcheck": "Colores d'autor\u00eda", "pad.settings.colorcheck": "Colores d'autor\u00eda",
"pad.settings.linenocheck": "N\u00famberos de llinia", "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": "Tipograf\u00eda:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monoespaciada", "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.stickychat": "Diskwel ar flap bepred",
"pad.settings.colorcheck": "Livio\u00f9 anaout", "pad.settings.colorcheck": "Livio\u00f9 anaout",
"pad.settings.linenocheck": "Niverenno\u00f9 linenno\u00f9", "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": "Seurt font :",
"pad.settings.fontType.normal": "Reizh", "pad.settings.fontType.normal": "Reizh",
"pad.settings.fontType.monospaced": "Monospas", "pad.settings.fontType.monospaced": "Monospas",

View file

@ -28,6 +28,7 @@
"pad.settings.stickychat": "Xateja sempre a la pantalla", "pad.settings.stickychat": "Xateja sempre a la pantalla",
"pad.settings.colorcheck": "Colors d'autoria", "pad.settings.colorcheck": "Colors d'autoria",
"pad.settings.linenocheck": "N\u00fameros de l\u00ednia", "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": "Tipus de lletra:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "D'amplada fixa", "pad.settings.fontType.monospaced": "D'amplada fixa",

View file

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

View file

@ -37,6 +37,7 @@
"pad.settings.stickychat": "Chat immer anzeigen", "pad.settings.stickychat": "Chat immer anzeigen",
"pad.settings.colorcheck": "Autorenfarben anzeigen", "pad.settings.colorcheck": "Autorenfarben anzeigen",
"pad.settings.linenocheck": "Zeilennummern", "pad.settings.linenocheck": "Zeilennummern",
"pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?",
"pad.settings.fontType": "Schriftart:", "pad.settings.fontType": "Schriftart:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace", "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.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.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.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.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.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", "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.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.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.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": "\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.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", "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.timeslider.title": "Timeslider",
"pad.toolbar.savedRevision.title": "Save Revision", "pad.toolbar.savedRevision.title": "Save Revision",
"pad.toolbar.settings.title": "Settings", "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.toolbar.showusers.title": "Show the users on this pad",
"pad.colorpicker.save": "Save", "pad.colorpicker.save": "Save",
"pad.colorpicker.cancel": "Cancel", "pad.colorpicker.cancel": "Cancel",

View file

@ -3,7 +3,8 @@
"authors": { "authors": {
"0": "BMRG14", "0": "BMRG14",
"1": "Dalba", "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", "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.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.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.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.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.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", "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.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.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.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": "\u0646\u0648\u0639 \u0642\u0644\u0645:",
"pad.settings.fontType.normal": "\u0633\u0627\u062f\u0647", "pad.settings.fontType.normal": "\u0633\u0627\u062f\u0647",
"pad.settings.fontType.monospaced": "Monospace", "pad.settings.fontType.monospaced": "Monospace",

View file

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

View file

@ -28,7 +28,7 @@
"pad.toolbar.clearAuthorship.title": "Effacer les couleurs identifiant les auteurs", "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.import_export.title": "Importer\/Exporter de\/vers un format de fichier diff\u00e9rent",
"pad.toolbar.timeslider.title": "Historique dynamique", "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.settings.title": "Param\u00e8tres",
"pad.toolbar.embed.title": "Int\u00e9grer ce Pad", "pad.toolbar.embed.title": "Int\u00e9grer ce Pad",
"pad.toolbar.showusers.title": "Afficher les utilisateurs du Pad", "pad.toolbar.showusers.title": "Afficher les utilisateurs du Pad",
@ -43,6 +43,7 @@
"pad.settings.stickychat": "Toujours afficher le chat", "pad.settings.stickychat": "Toujours afficher le chat",
"pad.settings.colorcheck": "Couleurs d\u2019identification", "pad.settings.colorcheck": "Couleurs d\u2019identification",
"pad.settings.linenocheck": "Num\u00e9ros de lignes", "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": "Type de police :",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace", "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.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.import_export.title": "Importar\/Exportar desde\/a diferentes formatos de ficheiro",
"pad.toolbar.timeslider.title": "Li\u00f1a do tempo", "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.settings.title": "Configuraci\u00f3ns",
"pad.toolbar.embed.title": "Incorporar este documento", "pad.toolbar.embed.title": "Incorporar este documento",
"pad.toolbar.showusers.title": "Mostrar os usuarios deste documento", "pad.toolbar.showusers.title": "Mostrar os usuarios deste documento",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chat sempre visible", "pad.settings.stickychat": "Chat sempre visible",
"pad.settings.colorcheck": "Cores de identificaci\u00f3n", "pad.settings.colorcheck": "Cores de identificaci\u00f3n",
"pad.settings.linenocheck": "N\u00fameros de li\u00f1a", "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": "Tipo de letra:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monoespazada", "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.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.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.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.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.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", "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.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.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.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": "\u05e1\u05d5\u05d2 \u05d2\u05d5\u05e4\u05df:",
"pad.settings.fontType.normal": "\u05e8\u05d2\u05d9\u05dc", "pad.settings.fontType.normal": "\u05e8\u05d2\u05d9\u05dc",
"pad.settings.fontType.monospaced": "\u05d1\u05e8\u05d5\u05d7\u05d1 \u05e7\u05d1\u05d5\u05e2", "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.stickychat": "Chat sempre visibile",
"pad.settings.colorcheck": "Colores de autor", "pad.settings.colorcheck": "Colores de autor",
"pad.settings.linenocheck": "Numeros de linea", "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": "Typo de litteras:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospatial", "pad.settings.fontType.monospaced": "Monospatial",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "Elimina i colori che indicano gli autori", "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.import_export.title": "Importa\/esporta da\/a diversi formati di file",
"pad.toolbar.timeslider.title": "Presentazione cronologia", "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.settings.title": "Impostazioni",
"pad.toolbar.embed.title": "Incorpora questo Pad", "pad.toolbar.embed.title": "Incorpora questo Pad",
"pad.toolbar.showusers.title": "Visualizza gli utenti su 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.stickychat": "Chat sempre sullo schermo",
"pad.settings.colorcheck": "Colori che indicano gli autori", "pad.settings.colorcheck": "Colori che indicano gli autori",
"pad.settings.linenocheck": "Numeri di riga", "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": "Tipo di carattere:",
"pad.settings.fontType.normal": "Normale", "pad.settings.fontType.normal": "Normale",
"pad.settings.fontType.monospaced": "A larghezza fissa", "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.stickychat": "\u753b\u9762\u306b\u30c1\u30e3\u30c3\u30c8\u3092\u5e38\u306b\u8868\u793a",
"pad.settings.colorcheck": "\u4f5c\u8005\u306e\u8272\u5206\u3051", "pad.settings.colorcheck": "\u4f5c\u8005\u306e\u8272\u5206\u3051",
"pad.settings.linenocheck": "\u884c\u756a\u53f7", "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": "\u30d5\u30a9\u30f3\u30c8\u306e\u7a2e\u985e:",
"pad.settings.fontType.normal": "\u901a\u5e38", "pad.settings.fontType.normal": "\u901a\u5e38",
"pad.settings.fontType.monospaced": "\u56fa\u5b9a\u5e45", "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.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.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.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.settings.title": "\uc124\uc815",
"pad.toolbar.embed.title": "\uc774 \ud328\ub4dc \ud3ec\ud568\ud558\uae30", "pad.toolbar.embed.title": "\uc774 \ud328\ub4dc \ud3ec\ud568\ud558\uae30",
"pad.toolbar.showusers.title": "\uc774 \ud328\ub4dc\uc5d0 \uc0ac\uc6a9\uc790 \ubcf4\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.stickychat": "\ud654\uba74\uc5d0 \ud56d\uc0c1 \ub300\ud654 \ubcf4\uae30",
"pad.settings.colorcheck": "\uc800\uc790 \uc0c9", "pad.settings.colorcheck": "\uc800\uc790 \uc0c9",
"pad.settings.linenocheck": "\uc904 \ubc88\ud638", "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": "\uae00\uaf34 \uc885\ub958:",
"pad.settings.fontType.normal": "\ubcf4\ud1b5", "pad.settings.fontType.normal": "\ubcf4\ud1b5",
"pad.settings.fontType.monospaced": "\uace0\uc815 \ud3ed", "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.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.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.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.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.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", "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.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.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.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": "\u0422\u0438\u043f \u043d\u0430 \u0444\u043e\u043d\u0442:",
"pad.settings.fontType.normal": "\u041d\u043e\u0440\u043c\u0430\u043b\u0435\u043d", "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", "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.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.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.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.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.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", "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.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.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.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": "\u0d2b\u0d4b\u0d23\u0d4d\u0d1f\u0d4d \u0d24\u0d30\u0d02:",
"pad.settings.fontType.normal": "\u0d38\u0d3e\u0d27\u0d3e\u0d30\u0d23\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", "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.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.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.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.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.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", "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.october": "\u0d12\u0d15\u0d4d\u0d1f\u0d4b\u0d2c\u0d7c",
"timeslider.month.november": "\u0d28\u0d35\u0d02\u0d2c\u0d7c", "timeslider.month.november": "\u0d28\u0d35\u0d02\u0d2c\u0d7c",
"timeslider.month.december": "\u0d21\u0d3f\u0d38\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.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.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", "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.clearAuthorship.title": "Padamkan Warna Pengarang",
"pad.toolbar.import_export.title": "Import\/Eksport dari\/ke format-format fail berbeza", "pad.toolbar.import_export.title": "Import\/Eksport dari\/ke format-format fail berbeza",
"pad.toolbar.timeslider.title": "Gelangsar masa", "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.settings.title": "Tetapan",
"pad.toolbar.embed.title": "Benamkan pad ini", "pad.toolbar.embed.title": "Benamkan pad ini",
"pad.toolbar.showusers.title": "Tunjukkan pengguna pada pad ini", "pad.toolbar.showusers.title": "Tunjukkan pengguna pada pad ini",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Sentiasa bersembang pada skrin", "pad.settings.stickychat": "Sentiasa bersembang pada skrin",
"pad.settings.colorcheck": "Warna pengarang", "pad.settings.colorcheck": "Warna pengarang",
"pad.settings.linenocheck": "Nombor baris", "pad.settings.linenocheck": "Nombor baris",
"pad.settings.rtlcheck": "Membaca dari kanan ke kiri?",
"pad.settings.fontType": "Jenis fon:", "pad.settings.fontType": "Jenis fon:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Monospace", "pad.settings.fontType.monospaced": "Monospace",

View file

@ -34,6 +34,7 @@
"pad.settings.stickychat": "Chat altijd zichtbaar", "pad.settings.stickychat": "Chat altijd zichtbaar",
"pad.settings.colorcheck": "Kleuren auteurs", "pad.settings.colorcheck": "Kleuren auteurs",
"pad.settings.linenocheck": "Regelnummers", "pad.settings.linenocheck": "Regelnummers",
"pad.settings.rtlcheck": "Inhoud van rechts naar links lezen?",
"pad.settings.fontType": "Lettertype:", "pad.settings.fontType": "Lettertype:",
"pad.settings.fontType.normal": "Normaal", "pad.settings.fontType.normal": "Normaal",
"pad.settings.fontType.monospaced": "Monospace", "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.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.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.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.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.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", "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.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.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.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": "\u0422\u0438\u043f \u0448\u0440\u0438\u0444\u0442\u0430:",
"pad.settings.fontType.normal": "\u041e\u0431\u044b\u0447\u043d\u044b\u0439", "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", "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.clearAuthorship.title": "Po\u010disti barvo avtorstva",
"pad.toolbar.import_export.title": "Izvozi\/Uvozi razli\u010dne oblike zapisov", "pad.toolbar.import_export.title": "Izvozi\/Uvozi razli\u010dne oblike zapisov",
"pad.toolbar.timeslider.title": "Drsnik zgodovine", "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.settings.title": "Nastavitve",
"pad.toolbar.embed.title": "Vstavi dokument", "pad.toolbar.embed.title": "Vstavi dokument",
"pad.toolbar.showusers.title": "Poka\u017ei uporabnike dokumenta", "pad.toolbar.showusers.title": "Poka\u017ei uporabnike dokumenta",
@ -34,6 +34,7 @@
"pad.settings.stickychat": "Vsebina klepeta je vedno na zaslonu.", "pad.settings.stickychat": "Vsebina klepeta je vedno na zaslonu.",
"pad.settings.colorcheck": "Barve avtorstva", "pad.settings.colorcheck": "Barve avtorstva",
"pad.settings.linenocheck": "\u0160tevilke vrstic", "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": "Vrsta pisave:",
"pad.settings.fontType.normal": "Obi\u010dajno", "pad.settings.fontType.normal": "Obi\u010dajno",
"pad.settings.fontType.monospaced": "Monospace", "pad.settings.fontType.monospaced": "Monospace",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Rensa f\u00f6rfattarf\u00e4rger", "pad.toolbar.clearAuthorship.title": "Rensa f\u00f6rfattarf\u00e4rger",
"pad.toolbar.import_export.title": "Importera\/exportera fr\u00e5n\/till olika filformat", "pad.toolbar.import_export.title": "Importera\/exportera fr\u00e5n\/till olika filformat",
"pad.toolbar.timeslider.title": "Tidsreglage", "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.settings.title": "Inst\u00e4llningar",
"pad.toolbar.embed.title": "B\u00e4dda in detta block", "pad.toolbar.embed.title": "B\u00e4dda in detta block",
"pad.toolbar.showusers.title": "Visa anv\u00e4ndarna p\u00e5 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.stickychat": "Chatten alltid p\u00e5 sk\u00e4rmen",
"pad.settings.colorcheck": "F\u00f6rfattarskapsf\u00e4rger", "pad.settings.colorcheck": "F\u00f6rfattarskapsf\u00e4rger",
"pad.settings.linenocheck": "Radnummer", "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": "Typsnitt:",
"pad.settings.fontType.normal": "Normal", "pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "Fast breddsteg", "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.save": "\u0c2d\u0c26\u0c4d\u0c30\u0c2a\u0c30\u0c1a\u0c41",
"pad.colorpicker.cancel": "\u0c30\u0c26\u0c4d\u0c26\u0c41\u0c1a\u0c47\u0c2f\u0c3f", "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.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.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.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", "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.stickychat": "\u6c38\u9060\u5728\u5c4f\u5e55\u4e0a\u986f\u793a\u804a\u5929",
"pad.settings.colorcheck": "\u4f5c\u8005\u984f\u8272", "pad.settings.colorcheck": "\u4f5c\u8005\u984f\u8272",
"pad.settings.linenocheck": "\u884c\u865f", "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": "\u5b57\u9ad4\u985e\u578b\uff1a",
"pad.settings.fontType.normal": "\u6b63\u5e38", "pad.settings.fontType.normal": "\u6b63\u5e38",
"pad.settings.fontType.monospaced": "\u7b49\u5bec", "pad.settings.fontType.monospaced": "\u7b49\u5bec",

View file

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

View file

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

View file

@ -60,7 +60,7 @@ exports.doImport = function(req, res, padId)
form.parse(req, function(err, fields, files) { form.parse(req, function(err, fields, files) {
//the upload failed, stop at this point //the upload failed, stop at this point
if(err || files.file === undefined) { if(err || files.file === undefined) {
console.warn("Uploading Error: " + err.stack); if(err) console.warn("Uploading Error: " + err.stack);
callback("uploadFailed"); callback("uploadFailed");
} }
//everything ok, continue //everything ok, continue
@ -176,7 +176,7 @@ exports.doImport = function(req, res, padId)
ERR(err); ERR(err);
//close the connection //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 handleMessageHook = function(callback){
var dropMessage = false; var dropMessage = false;
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages // 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 // handleMessage will be called, even if the client is not authorized
hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) { hooks.aCallAll("handleMessage", { client: client, message: message }, function ( err, messages ) {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
_.each(messages, function(newMessage){ _.each(messages, function(newMessage){
if ( newMessage === null ) { if ( newMessage === null ) {
dropMessage = true; dropMessage = true;
@ -205,17 +203,29 @@ exports.handleMessage = function(client, message)
//check permissions //check permissions
function(callback) function(callback)
{ {
// client tried to auth for the first time (first msg from the client)
// If the message has a padId we assume the client is already known to the server and needs no re-authorization if(message.type == "CLIENT_READY") {
if(!message.padId) // Remember this information since we won't
return callback(); // 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 // Note: message.sessionID is an entirely different kind of
// session from the sessions we use here! Beware! FIXME: Call // session from the sessions we use here! Beware!
// our "sessions" "connections". // FIXME: Call our "sessions" "connections".
// FIXME: Use a hook instead // FIXME: Use a hook instead
// FIXME: Allow to override readwrite access with readonly // 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; 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) * Handles a custom message (sent via HTTP API request)
* *
@ -1478,3 +1507,5 @@ exports.padUsers = function (padID, callback) {
callback(null, {padUsers: result}); 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) { io.on('connection', function (socket) {
if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return; 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 // send currently installed plugins
socket.emit("installed-results", {results: plugins.plugins}); var installed = Object.keys(plugins.plugins).map(function(plugin) {
socket.emit("progress", {progress:1}); return plugins.plugins[plugin].package
})
socket.emit("results:installed", {installed: installed});
}); });
socket.on("checkUpdates", function() { socket.on("checkUpdates", function() {
socket.emit("progress", {progress:0, message:'Checking for plugin updates...'});
// Check plugins for updates // Check plugins for updates
installer.search({offset: 0, pattern: '', limit: 500}, /*useCache:*/true, function(data) { // hacky installer.getAvailablePlugins(/*maxCacheAge:*/60*10, function(er, results) {
if (!data.results) return; if(er) {
console.warn(er);
socket.emit("results:updatable", {updatable: {}});
return;
}
var updatable = _(plugins.plugins).keys().filter(function(plugin) { var updatable = _(plugins.plugins).keys().filter(function(plugin) {
if(!data.results[plugin]) return false; if(!results[plugin]) return false;
var latestVersion = data.results[plugin]['dist-tags'].latest var latestVersion = results[plugin].version
var currentVersion = plugins.plugins[plugin].package.version var currentVersion = plugins.plugins[plugin].package.version
return semver.gt(latestVersion, currentVersion) return semver.gt(latestVersion, currentVersion)
}); });
socket.emit("updatable", {updatable: updatable}); socket.emit("results:updatable", {updatable: updatable});
socket.emit("progress", {progress:1});
}); });
}) })
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.on("search", function (query) {
socket.emit("progress", {progress:0, message:'Fetching results...'}); installer.search(query.searchTerm, /*maxCacheAge:*/60*10, function (er, results) {
installer.search(query, true, function (progress) { if(er) {
if (progress.results) console.error(er)
socket.emit("search-result", progress); results = {}
socket.emit("progress", progress); }
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.on("install", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."}); installer.install(plugin_name, function (er) {
installer.install(plugin_name, function (progress) { if(er) console.warn(er)
socket.emit("progress", progress); socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null});
}); });
}); });
socket.on("uninstall", function (plugin_name) { socket.on("uninstall", function (plugin_name) {
socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."}); installer.uninstall(plugin_name, function (er) {
installer.uninstall(plugin_name, function (progress) { if(er) console.warn(er)
socket.emit("progress", progress); 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); }, 3000);
} }
process.on('uncaughtException', exports.gracefulShutdown);
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app; exports.app = args.app;
@ -47,6 +48,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//https://github.com/joyent/node/issues/1553 //https://github.com/joyent/node/issues/1553
process.on('SIGINT', exports.gracefulShutdown); 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 // Let's put this under /rest for now
var subpath = express(); var subpath = express();
args.app.use(express.bodyParser());
args.app.use(basePath, subpath); args.app.use(basePath, subpath);
swagger.setAppHandler(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. // 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. // 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")) 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 /* 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 * 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(); callback();
}); });
} };
exports.convertFile = function(srcFile, destFile, type, callback) exports.convertFile = function(srcFile, destFile, type, callback)
{ {
@ -121,7 +121,7 @@ else
firstPrompt = false; firstPrompt = false;
} }
}); });
} };
spawnAbiword(); spawnAbiword();
doConvertTask = function(task, callback) doConvertTask = function(task, callback)
@ -135,7 +135,7 @@ else
console.log("queue continue"); console.log("queue continue");
task.callback(err); task.callback(err);
}; };
} };
//Queue with the converts we have to do //Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1); var queue = async.queue(doConvertTask, 1);

View file

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

View file

@ -45,7 +45,7 @@ exports.getPadPlainText = function(pad, revNum){
} }
return pieces.join(''); return pieces.join('');
} };
exports._analyzeLine = function(text, aline, apool){ exports._analyzeLine = function(text, aline, apool){
@ -77,11 +77,11 @@ exports._analyzeLine = function(text, aline, apool){
line.aline = aline; line.aline = aline;
} }
return line; return line;
} };
exports._encodeWhitespace = function(s){ exports._encodeWhitespace = function(s){
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ 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 ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security'); var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); 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 _analyzeLine = require('./ExportHelper')._analyzeLine;
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
@ -515,7 +515,7 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
callback(null, head + html + foot); callback(null, head + html + foot);
}); });
}); });
} };
// copied from ACE // copied from ACE
@ -595,4 +595,3 @@ function _processSpaces(s){
} }
return parts.join(''); return parts.join('');
} }

View file

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

View file

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

View file

@ -137,12 +137,12 @@ exports.abiwordAvailable = function()
{ {
return "no"; return "no";
} }
} };
exports.reloadSettings = function reloadSettings() { exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives // Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json"; var settingsFilename = argv.settings || "settings.json";
settingsFilename = path.resolve(path.join(root, settingsFilename)); settingsFilename = path.resolve(path.join(exports.root, settingsFilename));
var settingsStr; var settingsStr;
try{ try{
@ -157,7 +157,7 @@ exports.reloadSettings = function reloadSettings() {
try { try {
if(settingsStr) { if(settingsStr) {
settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json"); 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){ }catch(e){
console.error('There was an error processing your settings.json file: '+e.message); 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"){ 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 // initially load settings
exports.reloadSettings(); exports.reloadSettings();

View file

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

View file

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

View file

@ -16,7 +16,7 @@
"require-kernel" : "1.0.5", "require-kernel" : "1.0.5",
"resolve" : "0.2.x", "resolve" : "0.2.x",
"socket.io" : "0.9.x", "socket.io" : "0.9.x",
"ueberDB" : "0.1.94", "ueberDB" : "0.2.x",
"async" : "0.1.x", "async" : "0.1.x",
"express" : "3.x", "express" : "3.x",
"connect" : "2.4.x", "connect" : "2.4.x",
@ -24,10 +24,10 @@
"uglify-js" : "1.2.5", "uglify-js" : "1.2.5",
"formidable" : "1.0.9", "formidable" : "1.0.9",
"log4js" : "0.5.x", "log4js" : "0.5.x",
"nodemailer" : "0.3.x",
"jsdom-nocontextifiy" : "0.2.10", "jsdom-nocontextifiy" : "0.2.10",
"async-stacktrace" : "0.0.2", "async-stacktrace" : "0.0.2",
"npm" : "1.1.x", "npm" : "1.2.x",
"npm-registry-client" : "0.2.10",
"ejs" : "0.6.1", "ejs" : "0.6.1",
"graceful-fs" : "1.1.5", "graceful-fs" : "1.1.5",
"slide" : "1.1.3", "slide" : "1.1.3",
@ -46,5 +46,5 @@
"engines" : { "node" : ">=0.6.3", "engines" : { "node" : ">=0.6.3",
"npm" : ">=1.0" "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); box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
margin: auto; margin: auto;
max-width: 1150px; max-width: 1150px;
min-height: 100%; min-height: 101%;/*always display a scrollbar*/
} }
h1 { h1 {
@ -102,12 +102,26 @@ input[type="text"] {
max-width: 500px; max-width: 500px;
} }
.sort {
cursor: pointer;
}
.sort:after {
content: '▲▼'
}
.sort.up:after {
content:'▲'
}
.sort.down:after {
content:'▼'
}
table { table {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 3px; border-radius: 3px;
border-spacing: 0; border-spacing: 0;
width: 100%; width: 100%;
margin: 20px 0; margin: 20px 0;
position:relative; /* Allows us to position the loading indicator relative to the table */
} }
table thead tr { table thead tr {
@ -122,13 +136,40 @@ td, th {
display: none; display: none;
} }
#progress { #installed-plugins td>div {
position: absolute; position: relative;/* Allows us to position the loading indicator relative to this row */
bottom: 50px; display: inline-block; /*make this fill the whole cell*/
width:100%;
} }
#progress img { .messages td>* {
vertical-align: top; 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 { .settings {
@ -147,7 +188,25 @@ a:link, a:visited, a:hover, a:focus {
} }
a:focus, a:hover { 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 { pre {

View file

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

View file

@ -559,6 +559,15 @@ table#otheruserstable {
margin: 4px 0 0 4px; margin: 4px 0 0 4px;
position: absolute; position: absolute;
} }
#titlesticky{
font-size: 10px;
padding-top:2px;
float: right;
text-align: right;
text-decoration: none;
cursor: pointer;
color: #555;
}
#titlecross { #titlecross {
font-size: 25px; font-size: 25px;
float: right; float: right;
@ -828,7 +837,44 @@ input[type=checkbox] {
padding: 4px 1px 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 { #editorcontainer {
top: 68px; top: 68px;
} }

View file

@ -1013,6 +1013,11 @@ function Ace2Inner(){
return caughtErrors.slice(); return caughtErrors.slice();
}; };
editorInfo.ace_getDocument = function()
{
return doc;
};
editorInfo.ace_getDebugProperty = function(prop) editorInfo.ace_getDebugProperty = function(prop)
{ {
if (prop == "debugger") 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 */ if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey)) /* Do a saved revision on ctrl S */
{ {
evt.preventDefault(); 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 */ 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; specialHandled = true;
} }
@ -3712,6 +3722,9 @@ function Ace2Inner(){
specialHandled = true; specialHandled = true;
} }
if((evt.which == 33 || evt.which == 34) && type == 'keydown'){ 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 oldVisibleLineRange = getVisibleLineRange();
var topOffset = rep.selStart[0] - oldVisibleLineRange[0]; var topOffset = rep.selStart[0] - oldVisibleLineRange[0];
if(topOffset < 0 ){ if(topOffset < 0 ){
@ -3722,29 +3735,38 @@ function Ace2Inner(){
var isPageUp = evt.which === 33; var isPageUp = evt.which === 33;
scheduler.setTimeout(function(){ scheduler.setTimeout(function(){
var newVisibleLineRange = getVisibleLineRange(); var newVisibleLineRange = getVisibleLineRange(); // the visible lines IE 1,10
var linesCount = rep.lines.length(); 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){ 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){ if(isPageDown){ // if we hit page down
newCaretRow = newVisibleLineRange[0] + topOffset; 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 //ensure min and max
if(newCaretRow < 0){ if(rep.selEnd[0] < 0){
newCaretRow = 0; rep.selEnd[0] = 0;
} }
if(newCaretRow >= linesCount){ if(rep.selStart[0] < 0){
newCaretRow = linesCount-1; rep.selStart[0] = 0;
}
if(rep.selEnd[0] >= linesCount){
rep.selEnd[0] = linesCount-1;
} }
rep.selStart[0] = newCaretRow;
rep.selEnd[0] = newCaretRow;
updateBrowserSelectionFromRep(); 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); }, 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 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 */ presses and holds the arrow key */
if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){ if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){
var viewport = getViewPortTopBottom();
var newVisibleLineRange = getVisibleLineRange(); // get the current visible range -- This works great.
var lineHeight = textLineHeight(); // what Is the height of each line?
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 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 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 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 var caretIsNotVisible = (caretOffsetTop <= viewport.top || caretOffsetTopBottom >= viewport.bottom); // Is the Caret Visible to the user?
newVisibleLineRange[1] = newVisibleLineRange[1]-1; if(caretIsNotVisible){ // is the cursor no longer visible to the user?
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?
// Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum. // 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 arrow
if(evt.which == 37 || evt.which == 38){ // If left or up var newY = caretOffsetTop; // That was easy!
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
}
setScrollY(newY); // set the scroll height of the browser
} }
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
}
}
} }
} }
} }
if (type == "keydown") if (type == "keydown")

View file

@ -12,176 +12,248 @@ $(document).ready(function () {
//connect //connect
socket = io.connect(url, {resource : resource}).of("/pluginfw/installer"); socket = io.connect(url, {resource : resource}).of("/pluginfw/installer");
$('.search-results').data('query', { function search(searchTerm, limit) {
pattern: '', if(search.searchTerm != searchTerm) {
offset: 0, search.offset = 0
limit: 12, search.results = []
}); search.end = false
var doUpdate = false;
var search = function () {
socket.emit("search", $('.search-results').data('query'));
tasks++;
} }
limit = limit? limit : search.limit
function updateHandlers() { search.searchTerm = searchTerm;
$("form").submit(function(){ socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir});
var query = $('.search-results').data('query'); search.offset += limit;
query.pattern = $("#search-query").val(); $('#search-progress').show()
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;
} }
search(); search.offset = 0;
}); search.limit = 12;
$(".do-next-page").unbind('click').click(function (e) { search.results = [];
var query = $('.search-results').data('query'); search.sortBy = 'name';
var total = $('.search-results').data('total'); search.sortDir = /*DESC?*/true;
if (query.offset + query.limit < total) { search.end = true;// have we received all results already?
query.offset += query.limit; search.messages = {
} show: function(msg) {
search(); $('.search-results .messages').show()
}); $('.search-results .messages .'+msg+'').show()
} },
hide: function(msg) {
updateHandlers(); $('.search-results .messages').hide()
$('.search-results .messages .'+msg+'').hide()
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++;
} }
} }
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('')
} }
}); },
messages: {
socket.on('search-result', function (data) { show: function(msg) {
var widget=$(".search-results"); $('.installed-results .messages').show()
$('.installed-results .messages .'+msg+'').show()
widget.data('query', data.query); },
widget.data('total', data.total); hide: function(msg) {
$('.installed-results .messages').hide()
widget.find('.offset').html(data.query.offset); $('.installed-results .messages .'+msg+'').hide()
if (data.query.offset + data.query.limit > data.total){ }
widget.find('.limit').html(data.total); },
}else{ list: []
widget.find('.limit').html(data.query.offset + data.query.limit);
} }
widget.find('.total').html(data.total);
widget.find(".results *").remove(); function displayPluginList(plugins, container, template) {
for (plugin_name in data.results) { plugins.forEach(function(plugin) {
var plugin = data.results[plugin_name]; var row = template.clone();
var row = widget.find(".template tr").clone();
for (attr in plugin) { for (attr in plugin) {
if(attr == "name"){ // Hack to rewrite URLS into name 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{ }else{
row.find("." + attr).html(plugin[attr]); row.find("." + attr).html(plugin[attr]);
} }
} }
row.find(".version").html( data.results[plugin_name]['dist-tags'].latest ); row.find(".version").html( plugin.version );
row.addClass(plugin.name)
widget.find(".results").append(row); row.data('plugin', plugin.name)
container.append(row);
})
updateHandlers();
} }
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());
}); });
socket.on('installed-results', function (data) { // update & install
$("#installed-plugins *").remove(); $(".do-install, .do-update").unbind('click').click(function (e) {
var $row = $(e.target).closest("tr")
for (plugin_name in data.results) { , plugin = $row.data('plugin');
if (plugin_name == "ep_etherpad-lite") continue; // Hack... if($(this).hasClass('do-install')) {
var plugin = data.results[plugin_name]; $row.remove().appendTo('#installed-plugins')
var row = $("#installed-plugin-template").clone(); installed.progress.show(plugin, 'Installing')
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{ }else{
row.find("." + attr).html(plugin.package[attr]); installed.progress.show(plugin, 'Updating')
} }
} socket.emit("install", plugin);
$("#installed-plugins").append(row); installed.messages.hide("nothing-installed")
}
updateHandlers();
socket.emit('checkUpdates');
tasks++;
}); });
socket.on('updatable', function(data) { // uninstall
$('#installed-plugins>tr').each(function(i,tr) { $(".do-uninstall").unbind('click').click(function (e) {
var pluginName = $(tr).find('.name').text() 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
})
});
if (data.updatable.indexOf(pluginName) >= 0) { // Sort
var actions = $(tr).find('.actions') $('.sort.up').unbind('click').click(function() {
actions.append('<input class="do-update" type="button" value="Update" />') search.sortBy = $(this).text().toLowerCase();
actions.css('width', 200) 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('results:installed', function (data) {
installed.messages.hide("fetching")
installed.messages.hide("nothing-installed")
installed.list = data.installed
sortPluginList(installed.list, 'name', /*ASC?*/true);
// 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")
}
});
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(); updateHandlers();
}) })
socket.emit("load"); socket.on('finished:install', function(data) {
tasks++; if(data.error) {
alert('An error occured while installing '+data.plugin+' \n'+data.error)
$('#installed-plugins .'+data.plugin).remove()
}
search(); 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 padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie; var padcookie = require('./pad_cookie').padcookie;
var Tinycon = require('tinycon/tinycon'); var Tinycon = require('tinycon/tinycon');
var hooks = require('./pluginfw/hooks');
var chat = (function() var chat = (function()
{ {
@ -99,19 +100,39 @@ var chat = (function()
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank"); 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;
}
/* End of new action */
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); 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>"; // the hook args
var ctx = {
"authorName" : authorName,
"author" : msg.userId,
"text" : text,
"sticky" : false,
"timestamp" : msg.time,
"timeStr" : timeStr
}
// 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");
// 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) if(isHistoryAdd)
$(html).insertAfter('#chatloadmessagesbutton'); $(html).insertAfter('#chatloadmessagesbutton');
else else
@ -120,52 +141,26 @@ var chat = (function()
//should we increment the counter?? //should we increment the counter??
if(increment && !isHistoryAdd) if(increment && !isHistoryAdd)
{ {
// Update the counter of unread messages
var count = Number($("#chatcounter").text()); var count = Number($("#chatcounter").text());
count++; 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");
$("#chatcounter").text(count); $("#chatcounter").text(count);
// chat throb stuff -- Just make it throw for twice as long
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) if(!chatOpen) {
{ // If the user was mentioned show for twice as long and flash the browser window
$.gritter.add({ $.gritter.add({
// (string | mandatory) the heading of the notification // (string | mandatory) the heading of the notification
title: authorName, title: ctx.authorName,
// (string | mandatory) the text inside the notification // (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 // (bool | optional) if you want it to fade out on its own or just sit there
sticky: true, sticky: ctx.sticky,
// (int | optional) the time you want it to be alive for before fading out
time: '2000'
});
chatMentions++;
Tinycon.setBubble(chatMentions);
}
else
{
if(!chatOpen){
$.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: false,
// (int | optional) the time you want it to be alive for before fading out // (int | optional) the time you want it to be alive for before fading out
time: '4000' 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(){ $('#chatinput').click(function(){
chatMentions = 0; chatMentions = 0;

View file

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

View file

@ -21,8 +21,6 @@
$.gritter.options = { $.gritter.options = {
position: '', position: '',
class_name: '', // could be set to 'gritter-light' to use white notifications 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... time: 6000 // hang on the screen for...
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,118 +1,77 @@
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm"); var npm = require("npm");
var RegClient = require("npm-registry-client")
var registry = new RegClient( var npmIsLoaded = false;
{ registry: "http://registry.npmjs.org" var withNpm = function (npmfn) {
, cache: npm.cache } if(npmIsLoaded) return npmfn();
);
var withNpm = function (npmfn, final, cb) {
npm.load({}, function (er) { npm.load({}, function (er) {
if (er) return cb({progress:1, error:er}); if (er) return npmfn(er);
npmIsLoaded = true;
npm.on("log", function (message) { npm.on("log", function (message) {
cb({progress: 0.5, message:message.msg + ": " + message.pref}); console.log('npm: ',message)
});
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();
}); });
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) { exports.uninstall = function(plugin_name, cb) {
withNpm( withNpm(function (er) {
function (cb) { if (er) return cb && cb(er);
npm.commands.uninstall([plugin_name], function (er) { npm.commands.uninstall([plugin_name], function (er) {
if (er) return cb(er); if (er) return cb && cb(er);
hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er); if (er) return cb(er);
plugins.update(cb); plugins.update(cb);
});
});
},
function () {
hooks.aCallAll("restartServer", {}, function () {}); hooks.aCallAll("restartServer", {}, function () {});
}, });
cb });
); });
}; };
exports.install = function(plugin_name, cb) { exports.install = function(plugin_name, cb) {
withNpm( withNpm(function (er) {
function (cb) { if (er) return cb && cb(er);
npm.commands.install([plugin_name], function (er) { npm.commands.install([plugin_name], function (er) {
if (er) return cb(er); if (er) return cb && cb(er);
hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) {
if (er) return cb(er); if (er) return cb(er);
plugins.update(cb); plugins.update(cb);
});
});
},
function () {
hooks.aCallAll("restartServer", {}, function () {}); hooks.aCallAll("restartServer", {}, function () {});
}, });
cb });
); });
}; };
exports.searchCache = null; exports.availablePlugins = null;
var cacheTimestamp = 0;
exports.search = function(query, cache, cb) { exports.getAvailablePlugins = function(maxCacheAge, cb) {
withNpm( withNpm(function (er) {
function (cb) { if (er) return cb && cb(er);
var getData = function (cb) { if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) {
if (cache && exports.searchCache) { return cb && cb(null, exports.availablePlugins)
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);
} }
); npm.commands.search(['ep_'], /*silent?*/true, function(er, results) {
} if(er) return cb && cb(er);
} exports.availablePlugins = results;
getData( cacheTimestamp = Math.round(+new Date/1000);
function (er, data) { cb && cb(null, results)
if (er) return cb(er); })
});
};
exports.search = function(searchTerm, maxCacheAge, cb) {
exports.getAvailablePlugins(maxCacheAge, function(er, results) {
if(er) return cb && cb(er);
var res = {}; var res = {};
var i = 0; searchTerm = searchTerm.toLowerCase();
var pattern = query.pattern.toLowerCase(); for (var pluginName in results) { // for every available plugin
for (key in data) { // for every plugin in the data from npm if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
if ( key.indexOf(plugins.prefix) == 0 if(pluginName.indexOf(searchTerm) < 0 && results[pluginName].description.indexOf(searchTerm) < 0) continue;
&& key.indexOf(pattern) != -1 res[pluginName] = results[pluginName];
|| 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 && cb(null, res)
} })
cb(null, {results:res, query: query, total:i});
}
);
},
function () { },
cb
);
}; };

View file

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

View file

@ -28,12 +28,11 @@
<li><a href="plugins/info">Troubleshooting information</a> </li> <li><a href="plugins/info">Troubleshooting information</a> </li>
<% e.end_block(); %> <% e.end_block(); %>
</ul> </ul>
<div id="progress"><img src="../static/img/loading.gif">&nbsp;&nbsp;<span class="message"></span></div>
</div> </div>
<div class="innerwrapper"> <div class="innerwrapper">
<h2>Installed plugins</h2> <h2>Installed plugins</h2>
<table> <table class="installed-results">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
@ -47,13 +46,22 @@
<td class="name" data-label="Name"></td> <td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td> <td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td> <td class="version" data-label="Version"></td>
<td class="actions"> <td>
<div class="actions">
<input type="button" value="Uninstall" class="do-uninstall"> <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> </td>
</tr> </tr>
</tbody> </tbody>
<tbody id="installed-plugins"> <tbody id="installed-plugins">
</tbody> </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> </table>
<div class="paged listing search-results"> <div class="paged listing search-results">
@ -61,15 +69,15 @@
<h2>Available plugins</h2> <h2>Available plugins</h2>
<form> <form>
<input type="text" name="search" placeholder="Search for plugins to install" id="search-query"> <input type="text" name="search" disabled placeholder="Search for plugins to install" id="search-query">
</form> </form>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th class="sort up" data-label="name">Name</th>
<th>Description</th> <th class="sort none" data-label="description">Description</th>
<th>Version</th> <th class="sort none" data-label="version">Version</th>
<td></td> <td></td>
</tr> </tr>
</thead> </thead>
@ -78,17 +86,24 @@
<td class="name" data-label="Name"></td> <td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td> <td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td> <td class="version" data-label="Version"></td>
<td class="actions"> <td>
<div class="actions">
<input type="button" value="Install" class="do-install"> <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> </td>
</tr> </tr>
</tbody> </tbody>
<tbody class="results"> <tbody class="results">
</tbody> </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> </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>
</div> </div>

View file

@ -195,7 +195,9 @@
<div id="wrongPassword"> <div id="wrongPassword">
<p data-l10n-id="pad.wrongPassword">Your password was wrong</p> <p data-l10n-id="pad.wrongPassword">Your password was wrong</p>
</div> </div>
<% e.begin_block("loading"); %>
<p data-l10n-id="pad.loading" id="loading">Loading...</p> <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> <noscript><strong>Sorry, you have to enable Javascript in order to use this.</strong></noscript>
</div> </div>
</div> </div>
@ -376,7 +378,10 @@
</div> </div>
<div id="chatbox"> <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"> <div id="chattext" class="authorColors">
<img alt="loading.." id="chatloadmessagesball" class="chatloadmessages" src="../static/img/loading.gif" align="top"> <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> <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;
}