diff options
237 files changed, 6368 insertions, 1909 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 052a819c..926e3d3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,84 @@ +# 1.5.4 + * SECURITY: Also don't allow read files on directory traversal on frontend tests path + +# 1.5.3 + * NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts + * NEW: API endpoint for Append Chat Message and Chat Backend Tests + * NEW: Error messages displayed on load are included in Default Pad Text (can be supressed) + * NEW: Content Collector can handle key values + * NEW: getAttributesOnPosition Method + * FIX: Firefox keeps attributes (bold etc) on cut/copy -> paste + * Fix: showControls=false now works + * Fix: Cut and Paste works... + * SECURITY: Don't allow read files on directory traversal + +# 1.5.2 + * NEW: Support for node version 0.12.x + * NEW: API endpoint saveRevision, getSavedRevisionCount and listSavedRevisions + * NEW: setting to allow load testing + * Fix: Rare scroll issue + * Fix: Handling of custom pad path + * Fix: Better error handling of imports and exports of type "etherpad" + * Fix: Walking caret in chrome + * Fix: Better handling for changeset problems + * SECURITY Fix: Information leak for etherpad exports (CVE-2015-2298) + +# 1.5.1 + * NEW: High resolution Icon + * NEW: Use HTTPS for plugins.json download + * NEW: Add 'last update' column + * NEW: Show users and chat at the same time + * NEW: Support io.js + * Fix: removeAttributeOnLine now works properly + * Fix: Plugin search and list + * Fix: Issue where unauthed request could cause error + * Fix: Privacy issue with .etherpad export + * Fix: Freeze deps to improve bisectability + * Fix: IE, everything. IE is so broken. + * Fix: Timeslider proxy + * Fix: All backend tests pass + * Fix: Better support for Export into HTML + * Fix: Timeslider stars + * Fix: Translation update + * Fix: Check filesystem if Abiword exists + * Fix: Docs formatting + * Fix: Move Save Revision notification to a gritter message + * Fix: UeberDB MySQL Timeout issue + * Fix: Indented +9 list items + * Fix: Don't paste on middle click of link + * SECURITY Fix: Issue where a malformed URL could cause EP to disclose installation location + +# 1.5.0 + * NEW: Lots of performance improvements for page load times + * NEW: Hook for adding CSS to Exports + * NEW: Allow shardable socket io + * NEW: Allow UI to show when attr/prop is applied (CSS) + * NEW: Various scripts + * NEW: Export full fidelity pads (including authors etc.) + * NEW: Various front end tests + * NEW: Backend tests + * NEW: switchPad hook to instantly switch between pads + * NEW: Various translations + * NEW: Icon sets instead of images to provide quality high DPI experience + * Fix: HTML Import blocking / hanging server + * Fix: Export Bullet / Numbered lists HTML + * Fix: Swagger deprecated warning + * Fix: Bad session from crashing server + * Fix: Allow relative settings path + * Fix: Stop attributes being improperly assigned between 2 lines + * Fix: Copy / Move Pad API race condition + * Fix: Save all user preferences + * Fix: Upgrade majority of dependency inc upgrade to SocketIO1+ + * Fix: Provide UI button to restore maximized chat window + * Fix: Timeslider UI Fix + * Fix: Remove Dokuwiki + * Fix: Remove long paths from windows build (stops error during extract) + * Fix: Various globals remvoed + * Fix: Move all scripts into bin/ + * Fix: Various CSS bugfixes for Mobile devices + * Fix: Overflow Toolbar + * Fix: Line Attribute management + # 1.4.1 * NEW: Translations * NEW: userLeave Hook diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0110151c..b6e1c247 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,31 @@ # Developer Guidelines (Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad-lite#get-in-touch)) +## How to write a bug report + +* Please be polite, we all are humans and problems can occur. +* Please add as much information as possible, for example + * client os(s) and version(s) + * browser(s) and version(s), is the problem reproduceable on different clients + * special environments like firewalls or antivirus + * host os and version + * npm and nodejs version + * Logfiles if available + * steps to reproduce + * what you expected to happen + * what actually happened +* Please format logfiles and code examples with markdown see github Markdown help below the issue textarea for more information. + +If you send logfiles, please set the loglevel switch DEBUG in your settings.json file: + +``` +/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */ + "loglevel": "DEBUG", +``` + +The logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad. + + ## Important note for pull requests **Pull requests should be issued against the develop branch**. We never pull directly into master. @@ -14,7 +14,7 @@ out/doc/assets/%: doc/assets/% out/doc/%.html: doc/%.md mkdir -p $(@D) - node tools/doc/generate.js --format=html --template=doc/template.html $< > $@ + node bin/doc/generate.js --format=html --template=doc/template.html $< > $@ ifeq ($(UNAME),Darwin) sed -i '' 's/__VERSION__/${VERSION}/' $@ else @@ -11,7 +11,7 @@ that allows your web application to manage pads, users and groups. It is recomme There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. -There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparce and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control. +There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparse and because Etherpad takes a lot of it's inspiration from Wordpress plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control. Finally, Etherpad comes with translations into most languages! Users are automatically delivered the correct language for their local settings. @@ -46,15 +46,21 @@ Now, run `start.bat` and open <http://localhost:9001> in your browser. Update to the latest version with `git pull origin`, then run `bin\installOnWindows.bat`, again. +If cloning to a subdirectory within another project, you may need to do the following: + +1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js]`) +2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`) +3. Add auto-generated files to the main project `.gitignore` + [Next steps](#next-steps). ## GNU/Linux and other UNIX-like systems You'll need gzip, git, curl, libssl develop libraries, python and gcc. -- *For Debian/Ubuntu*: `apt-get install gzip git-core curl python libssl-dev pkg-config build-essential` -- *For Fedora/CentOS*: `yum install gzip git-core curl python openssl-devel && yum groupinstall "Development Tools"` +- *For Debian/Ubuntu*: `apt-get install gzip git curl python libssl-dev pkg-config build-essential` +- *For Fedora/CentOS*: `yum install gzip git curl python openssl-devel && yum groupinstall "Development Tools"` - *For FreeBSD*: `portinstall node, npm, 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, we recommend installing/compiling nodejs from source (avoiding apt). **As any user (we recommend creating a separate user called etherpad):** @@ -74,6 +80,10 @@ You can initially modify the settings in `settings.json`. (If you need to handle 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. +## Plugins and themes + +Etherpad is very customizable through plugins. Instructions for installing themes and plugins can be found in [the plugin wiki article](https://github.com/ether/etherpad-lite/wiki/Available-Plugins). + ## 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. diff --git a/bin/backendTests.sh b/bin/backendTests.sh new file mode 100755 index 00000000..ec12775b --- /dev/null +++ b/bin/backendTests.sh @@ -0,0 +1 @@ +src/node_modules/mocha/bin/mocha --timeout 5000 --reporter nyan tests/backend/specs/api diff --git a/bin/buildForWindows.sh b/bin/buildForWindows.sh index a1e2c757..78441ba0 100755 --- a/bin/buildForWindows.sh +++ b/bin/buildForWindows.sh @@ -58,6 +58,8 @@ rm -rf .git/objects echo "remove windows jsdom-nocontextify/test folder" rm -rf /tmp/etherpad-lite-win/node_modules/ep_etherpad-lite/node_modules/jsdom-nocontextifiy/test/ rm -rf /tmp/etherpad-lite-win/src/node_modules/jsdom-nocontextifiy/test/ +rm -rf /tmp/etherpad-lite-win/src/node_modules/wd/node_modules/request/node_modules/form-data/node_modules/combined-stream/test +rm -rf /tmp/etherpad-lite-win/src/node_modules/nodemailer/node_modules/mailcomposer/node_modules/mimelib/node_modules/encoding/node_modules/iconv-lite/encodings/tables echo "create the zip..." cd /tmp diff --git a/bin/dirty-db-cleaner.py b/bin/dirty-db-cleaner.py new file mode 100755 index 00000000..8ed9c506 --- /dev/null +++ b/bin/dirty-db-cleaner.py @@ -0,0 +1,45 @@ +#!/usr/bin/python -u +# +# Created by Bjarni R. Einarsson, placed in the public domain. Go wild! +# +import json +import os +import sys + +try: + dirtydb_input = sys.argv[1] + dirtydb_output = '%s.new' % dirtydb_input + assert(os.path.exists(dirtydb_input)) + assert(not os.path.exists(dirtydb_output)) +except: + print + print 'Usage: %s /path/to/dirty.db' % sys.argv[0] + print + print 'Note: Will create a file named dirty.db.new in the same folder,' + print ' please make sure permissions are OK and a file by that' + print ' name does not exist already. This script works by omitting' + print ' duplicate lines from the dirty.db file, keeping only the' + print ' last (latest) instance. No revision data should be lost,' + print ' but be careful, make backups. If it breaks you get to keep' + print ' both pieces!' + print + sys.exit(1) + +dirtydb = {} +lines = 0 +with open(dirtydb_input, 'r') as fd: + print 'Reading %s' % dirtydb_input + for line in fd: + lines += 1 + data = json.loads(line) + dirtydb[data['key']] = line + if lines % 10000 == 0: + sys.stderr.write('.') +print +print 'OK, found %d unique keys in %d lines' % (len(dirtydb), lines) + +with open(dirtydb_output, 'w') as fd: + for data in dirtydb.values(): + fd.write(data) + +print 'Wrote data to %s. All done!' % dirtydb_output diff --git a/tools/doc/LICENSE b/bin/doc/LICENSE index e3d4e695..e3d4e695 100644 --- a/tools/doc/LICENSE +++ b/bin/doc/LICENSE diff --git a/tools/doc/README.md b/bin/doc/README.md index 3646e46e..3646e46e 100644 --- a/tools/doc/README.md +++ b/bin/doc/README.md diff --git a/tools/doc/generate.js b/bin/doc/generate.js index 8d52e101..8d52e101 100644 --- a/tools/doc/generate.js +++ b/bin/doc/generate.js diff --git a/tools/doc/html.js b/bin/doc/html.js index 700ab18c..700ab18c 100644 --- a/tools/doc/html.js +++ b/bin/doc/html.js diff --git a/tools/doc/json.js b/bin/doc/json.js index 2459bc26..2459bc26 100644 --- a/tools/doc/json.js +++ b/bin/doc/json.js diff --git a/tools/doc/node_modules/.bin/marked b/bin/doc/node_modules/.bin/marked index a8d872e9..a8d872e9 100644 --- a/tools/doc/node_modules/.bin/marked +++ b/bin/doc/node_modules/.bin/marked diff --git a/tools/doc/node_modules/marked/.npmignore b/bin/doc/node_modules/marked/.npmignore index 3fb773c0..3fb773c0 100644 --- a/tools/doc/node_modules/marked/.npmignore +++ b/bin/doc/node_modules/marked/.npmignore diff --git a/tools/doc/node_modules/marked/LICENSE b/bin/doc/node_modules/marked/LICENSE index 40597477..40597477 100644 --- a/tools/doc/node_modules/marked/LICENSE +++ b/bin/doc/node_modules/marked/LICENSE diff --git a/tools/doc/node_modules/marked/Makefile b/bin/doc/node_modules/marked/Makefile index 76904000..76904000 100644 --- a/tools/doc/node_modules/marked/Makefile +++ b/bin/doc/node_modules/marked/Makefile diff --git a/tools/doc/node_modules/marked/README.md b/bin/doc/node_modules/marked/README.md index 1a0747c0..1a0747c0 100644 --- a/tools/doc/node_modules/marked/README.md +++ b/bin/doc/node_modules/marked/README.md diff --git a/tools/doc/node_modules/marked/bin/marked b/bin/doc/node_modules/marked/bin/marked index 7d00504e..7d00504e 100644 --- a/tools/doc/node_modules/marked/bin/marked +++ b/bin/doc/node_modules/marked/bin/marked diff --git a/tools/doc/node_modules/marked/index.js b/bin/doc/node_modules/marked/index.js index a12f9056..a12f9056 100644 --- a/tools/doc/node_modules/marked/index.js +++ b/bin/doc/node_modules/marked/index.js diff --git a/tools/doc/node_modules/marked/lib/marked.js b/bin/doc/node_modules/marked/lib/marked.js index c6f3d0ac..c6f3d0ac 100644 --- a/tools/doc/node_modules/marked/lib/marked.js +++ b/bin/doc/node_modules/marked/lib/marked.js diff --git a/tools/doc/node_modules/marked/man/marked.1 b/bin/doc/node_modules/marked/man/marked.1 index 21453339..21453339 100644 --- a/tools/doc/node_modules/marked/man/marked.1 +++ b/bin/doc/node_modules/marked/man/marked.1 diff --git a/tools/doc/node_modules/marked/package.json b/bin/doc/node_modules/marked/package.json index f47a9e12..f47a9e12 100644 --- a/tools/doc/node_modules/marked/package.json +++ b/bin/doc/node_modules/marked/package.json diff --git a/tools/doc/package.json b/bin/doc/package.json index d87c9345..d87c9345 100644 --- a/tools/doc/package.json +++ b/bin/doc/package.json diff --git a/bin/installDeps.sh b/bin/installDeps.sh index a8bc88a8..a5e4d5ab 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -10,28 +10,29 @@ fi #Is gnu-grep (ggrep) installed on SunOS (Solaris) if [ $(uname) = "SunOS" ]; then - hash ggrep > /dev/null 2>&1 || { + hash ggrep > /dev/null 2>&1 || { echo "Please install ggrep (pkg install gnu-grep)" >&2 - exit 1 + exit 1 } fi #Is curl installed? -hash curl > /dev/null 2>&1 || { +hash curl > /dev/null 2>&1 || { echo "Please install curl" >&2 - exit 1 + exit 1 } #Is node installed? -hash node > /dev/null 2>&1 || { +#not checking io.js, default installation creates a symbolic link to node +hash node > /dev/null 2>&1 || { echo "Please install node.js ( http://nodejs.org )" >&2 - exit 1 + exit 1 } #Is npm installed? -hash npm > /dev/null 2>&1 || { +hash npm > /dev/null 2>&1 || { echo "Please install npm ( http://npmjs.org )" >&2 - exit 1 + exit 1 } #check npm version @@ -39,15 +40,21 @@ NPM_VERSION=$(npm --version) NPM_MAIN_VERSION=$(echo $NPM_VERSION | cut -d "." -f 1) if [ $(echo $NPM_MAIN_VERSION) = "0" ]; then echo "You're running a wrong version of npm, you're using $NPM_VERSION, we need 1.x or higher" >&2 - exit 1 + exit 1 fi #check node version NODE_VERSION=$(node --version) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) -if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ]; then - echo "You're running a wrong version of node, you're using $NODE_VERSION, we need v0.8.x, v0.10.x or v0.11.x" >&2 - exit 1 +#iojs version checking added +if hash iojs 2>/dev/null; then + IOJS_VERSION=$(iojs --version) +fi +if [ ! $NODE_V_MINOR = "v0.8" ] && [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ]; then + if [ ! $IOJS_VERSION ]; then + echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.8.x, v0.10.x, v0.11.x or v0.12.x" >&2 + exit 1 + fi fi #Get the name of the settings file @@ -71,9 +78,9 @@ echo "Ensure that all dependencies are up to date... If this is the first time [ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite cd ep_etherpad-lite npm install --loglevel warn -) || { +) || { rm -rf node_modules - exit 1 + exit 1 } echo "Ensure jQuery is downloaded and up to date..." @@ -81,9 +88,9 @@ DOWNLOAD_JQUERY="true" NEEDED_VERSION="1.9.1" if [ -f "src/static/js/jquery.js" ]; then if [ $(uname) = "SunOS" ]; then - VERSION=$(cat src/static/js/jquery.js | head -n 3 | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?"); + VERSION=$(head -n 3 src/static/js/jquery.js | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?") else - VERSION=$(cat src/static/js/jquery.js | head -n 3 | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?"); + VERSION=$(head -n 3 src/static/js/jquery.js | grep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?") fi if [ ${VERSION#v} = $NEEDED_VERSION ]; then @@ -96,17 +103,17 @@ if [ $DOWNLOAD_JQUERY = "true" ]; then fi #Remove all minified data to force node creating it new -echo "Clear minfified cache..." +echo "Clearing minified cache..." rm -f var/minified* -echo "ensure custom css/js files are created..." +echo "Ensure custom css/js files are created..." for f in "index" "pad" "timeslider" do if [ ! -f "src/static/custom/$f.js" ]; then cp "src/static/custom/js.template" "src/static/custom/$f.js" || exit 1 fi - + if [ ! -f "src/static/custom/$f.css" ]; then cp "src/static/custom/css.template" "src/static/custom/$f.css" || exit 1 fi diff --git a/bin/jshint.sh b/bin/jshint.sh deleted file mode 100755 index 4dea7396..00000000 --- a/bin/jshint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -if [ -d "../bin" ]; then - cd "../" -fi - -JSHINT=./node_modules/jshint/bin/hint - -$JSHINT ./node/ diff --git a/bin/loadTesting/README b/bin/loadTesting/README deleted file mode 100644 index c8ecd71e..00000000 --- a/bin/loadTesting/README +++ /dev/null @@ -1,79 +0,0 @@ -This is the new load testing file: https://bitbucket.org/rbraakman/etherpad-stresstest - -BELOW is the original load testing file. - -This load tester is extremely useful for testing how many dormant clients can connect to etherpad. - -TODO: -Emulate characters being typed into a pad - -HOW TO USE (from @mjd75) proper formatting at: https://github.com/ether/etherpad-lite/issues/360 - -Server 1: -Installed Node.js (etc), EtherPad and MySQL - -Server 2: -Installed Xvfb and PhantomJS - -I installed Xvfb following (roughly) this guide: http://blog.martin-lyness.com/archives/installing-xvfb-on-ubuntu-9-10-karmic-koala - - #sudo apt-get install xvfb - #sudo apt-get install xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic - -Launched two instances of Xvfb directly from the terminal: - - #Xvfb :0 -ac - #Xvfb :1 -ac - -I installed PhantomJS following this guide: http://code.google.com/p/phantomjs/wiki/Installation - - #sudo add-apt-repository ppa:jerome-etienne/neoip - #sudo apt-get update - #sudo apt-get install phantomjs - -I created a small JavaScript file for PhatomJS to use to control the browser instances: - -### BEGIN JAVASCRIPT ### - -var page = new WebPage(), - t, address; - -if (phantom.args.length === 0) { - console.log('Usage: loader.js <some URL>'); - phantom.exit(); -} else { - t = Date.now(); - address = phantom.args[0]; - - var page = new WebPage(); - page.onResourceRequested = function (request) { - console.log('Request ' + JSON.stringify(request, undefined, 4)); - }; - page.onResourceReceived = function (response) { - console.log('Receive ' + JSON.stringify(response, undefined, 4)); - }; - page.open(address); - -} - -### END JAVASCRIPT ### - -And finally a launcher script that uses screen to run 400 instances of PhantomJS with the above script: - -### BEGIN SHELL SCRIPT ### - -#!/bin/bash - -# connect 200 instances to display :0 -for i in {1..200} -do - DISPLAY=:0 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2 -done - -# connect 200 instances to display :1 -for i in {1..200} -do - DISPLAY=:1 screen -d -m phantomjs loader.js http://ec2-50-17-168-xx.compute-1.amazonaws.com:9001/p/pad2 && sleep 2 -done - -### END SHELL SCRIPT ### diff --git a/bin/loadTesting/launcher.sh b/bin/loadTesting/launcher.sh deleted file mode 100755 index e940f8e0..00000000 --- a/bin/loadTesting/launcher.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# connect 500 instances to display :0 -for i in {1..500} -do - echo $i - echo "Displaying Some shit" - DISPLAY=:0 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2 -done - -# connect 500 instances to display :1 -for i in {1..500} -do - echo $i - DISPLAY=:1 screen -d -m /home/phantomjs/bin/phantomjs loader.js http://10.0.0.55:9001/p/pad2 && sleep 2 -done diff --git a/bin/loadTesting/loader.js b/bin/loadTesting/loader.js deleted file mode 100644 index ddcd0572..00000000 --- a/bin/loadTesting/loader.js +++ /dev/null @@ -1,20 +0,0 @@ -var page = new WebPage(), - t, address; - -if (phantom.args.length === 0) { - console.log('Usage: loader.js <some URL>'); - phantom.exit(); -} else { - t = Date.now(); - address = phantom.args[0]; - - var page = new WebPage(); - page.onResourceRequested = function (request) { - console.log('Request ' + JSON.stringify(request, undefined, 4)); - }; - page.onResourceReceived = function (response) { - console.log('Receive ' + JSON.stringify(response, undefined, 4)); - }; - page.open(address); - -} diff --git a/bin/rebuildPad.js b/bin/rebuildPad.js new file mode 100644 index 00000000..c8383342 --- /dev/null +++ b/bin/rebuildPad.js @@ -0,0 +1,120 @@ +/* + This is a repair tool. It rebuilds an old pad at a new pad location up to a + known "good" revision. +*/ + +if(process.argv.length != 4 && process.argv.length != 5) { + console.error("Use: node bin/repairPad.js $PADID $REV [$NEWPADID]"); + process.exit(1); +} + +var npm = require("../src/node_modules/npm"); +var async = require("../src/node_modules/async"); +var ueberDB = require("../src/node_modules/ueberDB"); + +var padId = process.argv[2]; +var newRevHead = process.argv[3]; +var newPadId = process.argv[4] || padId + "-rebuilt"; + +var db, oldPad, newPad, settings; +var AuthorManager, ChangeSet, Pad, PadManager; + +async.series([ + function(callback) { + npm.load({}, function(err) { + if(err) { + console.error("Could not load NPM: " + err) + process.exit(1); + } else { + callback(); + } + }) + }, + function(callback) { + // Get a handle into the database + db = require('../src/node/db/DB'); + db.init(callback); + }, function(callback) { + PadManager = require('../src/node/db/PadManager'); + Pad = require('../src/node/db/Pad').Pad; + // Get references to the original pad and to a newly created pad + // HACK: This is a standalone script, so we want to write everything + // out to the database immediately. The only problem with this is + // that a driver (like the mysql driver) can hardcode these values. + db.db.db.settings = {cache: 0, writeInterval: 0, json: true}; + // Validate the newPadId if specified and that a pad with that ID does + // not already exist to avoid overwriting it. + if (!PadManager.isValidPadId(newPadId)) { + console.error("Cannot create a pad with that id as it is invalid"); + process.exit(1); + } + PadManager.doesPadExists(newPadId, function(err, exists) { + if (exists) { + console.error("Cannot create a pad with that id as it already exists"); + process.exit(1); + } + }); + PadManager.getPad(padId, function(err, pad) { + oldPad = pad; + newPad = new Pad(newPadId); + callback(); + }); + }, function(callback) { + // Clone all Chat revisions + var chatHead = oldPad.chatHead; + for(var i = 0, curHeadNum = 0; i <= chatHead; i++) { + db.db.get("pad:" + padId + ":chat:" + i, function (err, chat) { + db.db.set("pad:" + newPadId + ":chat:" + curHeadNum++, chat); + console.log("Created: Chat Revision: pad:" + newPadId + ":chat:" + curHeadNum); + }); + } + callback(); + }, function(callback) { + // Rebuild Pad from revisions up to and including the new revision head + AuthorManager = require("../src/node/db/AuthorManager"); + Changeset = require("ep_etherpad-lite/static/js/Changeset"); + // Author attributes are derived from changesets, but there can also be + // non-author attributes with specific mappings that changesets depend on + // and, AFAICT, cannot be recreated any other way + newPad.pool.numToAttrib = oldPad.pool.numToAttrib; + for(var curRevNum = 0; curRevNum <= newRevHead; curRevNum++) { + db.db.get("pad:" + padId + ":revs:" + curRevNum, function(err, rev) { + var newRevNum = ++newPad.head; + var newRevId = "pad:" + newPad.id + ":revs:" + newRevNum; + db.db.set(newRevId, rev); + AuthorManager.addPad(rev.meta.author, newPad.id); + newPad.atext = Changeset.applyToAText(rev.changeset, newPad.atext, newPad.pool); + console.log("Created: Revision: pad:" + newPad.id + ":revs:" + newRevNum); + if (newRevNum == newRevHead) { + callback(); + } + }); + } + }, function(callback) { + // Add saved revisions up to the new revision head + console.log(newPad.head); + var newSavedRevisions = []; + for(var i in oldPad.savedRevisions) { + savedRev = oldPad.savedRevisions[i] + if (savedRev.revNum <= newRevHead) { + newSavedRevisions.push(savedRev); + console.log("Added: Saved Revision: " + savedRev.revNum); + } + } + newPad.savedRevisions = newSavedRevisions; + callback(); + }, function(callback) { + // Save the source pad + db.db.set("pad:"+newPadId, newPad, function(err) { + console.log("Created: Source Pad: pad:" + newPadId); + newPad.saveToDatabase(); + callback(); + }); + } +], function (err) { + if(err) throw err; + else { + console.info("finished"); + process.exit(0); + } +}); @@ -32,7 +32,7 @@ fi bin/installDeps.sh $* || exit 1 #Move to the node folder and start -echo "start..." +echo "Started Etherpad..." SCRIPTPATH=`pwd -P` node $SCRIPTPATH/node_modules/ep_etherpad-lite/node/server.js $* diff --git a/bin/safeRun.sh b/bin/safeRun.sh index 4b3485ba..519a0b6e 100755 --- a/bin/safeRun.sh +++ b/bin/safeRun.sh @@ -55,7 +55,7 @@ do TIME_SINCE_LAST_SEND=$(($TIME_NOW - $LAST_EMAIL_SEND)) if [ $TIME_SINCE_LAST_SEND -gt $TIME_BETWEEN_EMAILS ]; then - printf "Server was restared at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS + printf "Server was restarted at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS LAST_EMAIL_SEND=$TIME_NOW fi diff --git a/bin/updatePlugins.sh b/bin/updatePlugins.sh new file mode 100755 index 00000000..d696eca7 --- /dev/null +++ b/bin/updatePlugins.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +#Move to the folder where ep-lite is installed +cd `dirname $0` + +#Was this script started in the bin folder? if yes move out +if [ -d "../bin" ]; then + cd "../" +fi + +npm outdated --depth=0 | grep -v "^Package" | awk '{print $1}' | xargs npm install $1 --save-dev + diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index b8a58b31..f9ad9147 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -80,6 +80,22 @@ This hook is called during the attribute processing procedure, and should be use The return value for this function should be a list of classes, which will then be parsed into a valid class string. +## aceAttribClasses +Called from: src/static/js/linestylefilter.js + +Things in context: +1. Attributes - Object of Attributes + +This hook is called when attributes are investigated on a line. It is useful if you want to add another attribute type or property type to a pad. + +Example: +``` +exports.aceAttribClasses = function(hook_name, attr, cb){ + attr.sub = 'tag:sub'; + cb(attr); +} +``` + ## aceGetFilterStack Called from: src/static/js/linestylefilter.js @@ -187,6 +203,36 @@ Things in context: This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. +E.g. if you need to apply an attribute to newly inserted characters, +call cc.doAttrib(state, "attributeName") which results in an attribute attributeName=true. + +If you want to specify also a value, call cc.doAttrib(state, "attributeName:value") +which results in an attribute attributeName=value. + + +## collectContentImage +Called from: src/static/js/contentcollector.js + +Things in context: + +1. cc - the contentcollector object +2. state - the current state of the change being made +3. tname - the tag name of this node currently being processed +4. style - the style applied to the node (probably CSS) +5. cls - the HTML class string of the node +6. node - the node being modified + +This hook is called before the content of an image node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. + +Example: + +``` +exports.collectContentImage = function(name, context){ + context.state.lineAttributes.img = context.node.outerHTML; +} + +``` + ## collectContentPost Called from: src/static/js/contentcollector.js diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 435872ea..c7e7a43a 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -247,6 +247,64 @@ Things in context: This hook will allow a plug-in developer to re-write each line when exporting to HTML. +Example: +``` +var Changeset = require("ep_etherpad-lite/static/js/Changeset"); + +exports.getLineHTMLForExport = function (hook, context) { + var header = _analyzeLine(context.attribLine, context.apool); + if (header) { + return "<" + header + ">" + context.lineContents + "</" + header + ">"; + } +} + +function _analyzeLine(alineAttrs, apool) { + var header = null; + if (alineAttrs) { + var opIter = Changeset.opIterator(alineAttrs); + if (opIter.hasNext()) { + var op = opIter.next(); + header = Changeset.opAttributeValue(op, 'heading', apool); + } + } + return header; +} +``` + +## stylesForExport +Called from: src/node/utils/ExportHtml.js + +Things in context: + +1. padId - The Pad Id + +This hook will allow a plug-in developer to append Styles to the Exported HTML. + +Example: + +``` +exports.stylesForExport = function(hook, padId, cb){ + cb("body{font-size:13.37em !important}"); +} +``` + +## aceAttribClasses +Called from: src/static/js/linestylefilter.js + +Things in context: +1. Attributes - Object of Attributes + +This hook is called when attributes are investigated on a line. It is useful if you want to add another attribute type or property type to a pad. + +Example: + +``` +exports.aceAttribClasses = function(hook_name, attr, cb){ + attr.sub = 'tag:sub'; + cb(attr); +} +``` + ## exportFileName Called from src/node/handler/ExportHandler.js @@ -264,6 +322,24 @@ exports.exportFileName = function(hook, padId, callback){ } ``` +## exportHtmlAdditionalTags +Called from src/node/utils/ExportHtml.js + +Things in context: + +1. Pad object + +This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. + +Example: +``` +// Add the props to be supported in export +exports.exportHtmlAdditionalTags = function(hook, pad, cb){ + var padId = pad.id; + cb(["massive","jugs"]); +}; +``` + ## userLeave Called from src/node/handler/PadMessageHandler.js diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 6cbe6e6b..2ae674d8 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -61,7 +61,7 @@ Portal submits content into new blog post ## Usage ### API version -The latest version is `1.2.9` +The latest version is `1.2.11` The current version can be queried via /api. @@ -402,6 +402,33 @@ returns the number of revisions of this pad * `{code: 0, message:"ok", data: {revisions: 56}}` * `{code: 1, message:"padID does not exist", data: null}` +#### getSavedRevisionsCount(padID) + * API >= 1.2.11 + +returns the number of saved revisions of this pad + +*Example returns:* + * `{code: 0, message:"ok", data: {savedRevisions: 42}}` + * `{code: 1, message:"padID does not exist", data: null}` + +#### listSavedRevisions(padID) + * API >= 1.2.11 + +returns the list of saved revisions of this pad + +*Example returns:* + * `{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}` + * `{code: 1, message:"padID does not exist", data: null}` + +#### saveRevision(padID [, rev]) + * API >= 1.2.11 + +saves a revision + +*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + #### padUsersCount(padID) * API >= 1 diff --git a/settings.json.template b/settings.json.template index 5868af6a..ea7ed1e1 100644 --- a/settings.json.template +++ b/settings.json.template @@ -54,6 +54,9 @@ //the default text of a pad "defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n", + /* Shoud we suppress errors from being visible in the default Pad Text? */ + "suppressErrorsInPadText" : false, + /* Users must have a session to access pads. This effectively allows only group pads to be accessed. */ "requireSession" : false, @@ -74,19 +77,22 @@ /* This is the path to the Abiword executable. Setting it to null, disables abiword. Abiword is needed to advanced import/export features of pads*/ "abiword" : null, + + /* Allow import of file types other than the supported types: txt, doc, docx, rtf, odt, html & htm */ + "allowUnknownFileEnds" : true, /* This setting is used if you require authentication of all users. Note: /admin always requires authentication. */ - "requireAuthentication": false, + "requireAuthentication" : false, /* Require authorization by a module, or a user with is_admin set, see below. */ - "requireAuthorization": false, + "requireAuthorization" : false, /*when you use NginX or another proxy/ load-balancer set this to true*/ - "trustProxy": false, + "trustProxy" : false, /* Privacy: disable IP logging */ - "disableIPlogging": false, + "disableIPlogging" : false, /* Users for basic authentication. is_admin = true gives access to /admin. If you do not uncomment this, /admin will not be available! */ @@ -105,6 +111,9 @@ // restrict socket.io transport methods "socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"], + + // Allow Load Testing tools to hit the Etherpad Instance. Warning this will disable security on the instance. + "loadTest": false, /* The toolbar buttons configuration. "toolbar": { diff --git a/src/etherpad_icon.svg b/src/etherpad_icon.svg new file mode 100644 index 00000000..ebdcde99 --- /dev/null +++ b/src/etherpad_icon.svg @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.1" + width="16" + height="16" + id="svg2987"> + <defs + id="defs2989" /> + <metadata + id="metadata2992"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + transform="translate(0,-1036.3618)" + id="layer1"> + <path + d="m 5.0621446,1039.5621 c 5.6960494,0 6.2053964,0 6.2053964,0 l -0.0238,-0.8981 -3.131226,-0.018 -3.1312269,-0.018 z" + id="path3806" + style="fill:#464646;fill-opacity:1;stroke:#464646;stroke-width:0.68021488;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 12.4892,1052.3561 0,-2.1733 2.173211,0 0,2.1733 z" + id="path3770-7-0-9" + style="fill:#b1b1b1;fill-opacity:1;stroke:none" /> + <path + d="m 1.6231475,1052.3561 0,-2.1733 2.1732107,0 0,2.1733 z" + id="path3770-7-0" + style="fill:#b1b1b1;fill-opacity:1;stroke:none" /> + <path + d="m 2.7097528,1041.49 2.1732106,-1.0866 0,-2.1251" + id="path3775" + style="fill:none;stroke:#aeaeae;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 13.575806,1041.49 -2.17321,-1.0866 0.0065,-2.0833" + id="path3777" + style="fill:none;stroke:#aeaeae;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 1.6231475,1041.49 c 0.014561,3.8432 -0.019342,6.178 0,7.6063 0.030207,2.23 0,2.1732 0,2.1732 1.1853886,0 1.1853886,0 1.1853886,1.0866 l 10.6684869,0 c 0,-1.0866 0,-1.0866 1.185388,-1.0866 0,-3.26 0,-6.5198 0,-9.7795 z" + id="path3019" + style="fill:#464646;fill-opacity:1;stroke:none" /> + <path + d="m 3.7963582,1050.1828 c 6.3865768,0 8.6928418,0 8.6928418,0" + id="path3023" + style="fill:none;stroke:#09c900;stroke-width:1.07546031;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 3.7963582,1048.0095 8.6928418,0" + id="path3025" + style="fill:none;stroke:#fcc200;stroke-width:1.1616298;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 3.7963582,1045.8365 8.6928418,0" + id="path3027" + style="fill:none;stroke:#00a3fb;stroke-width:1.1616298;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 3.7963582,1043.6632 8.6928418,0" + id="path3029" + style="fill:none;stroke:#ff4091;stroke-width:1.1616298;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + <path + d="m 1.6231475,1042.5766 0,-4.3464 1.0866053,0 0,4.3464 z" + id="path3770" + style="fill:#464646;fill-opacity:1;stroke:none" /> + <path + d="m 13.575806,1042.5766 0,-4.3464 1.086605,0 0,4.3464 z" + id="path3770-1" + style="fill:#464646;fill-opacity:1;stroke:none" /> + <path + d="m 1.6231475,1038.2302 0,-1.0867 1.0866053,0 0,1.0867 z" + id="path3770-7" + style="fill:#b1b1b1;fill-opacity:1;stroke:none" /> + <path + d="m 13.575806,1038.2302 0,-1.0867 1.086605,0 0,1.0867 z" + id="path3770-4" + style="fill:#b1b1b1;fill-opacity:1;stroke:none" /> + <path + d="m 5.3413804,1038.5358 c 2.6160566,-2.5761 3.1649988,-2.3796 5.6028086,0.018" + id="path3767" + style="fill:none;stroke:#464646;stroke-width:0.54330266;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5999999;stroke-opacity:1;stroke-dasharray:none" /> + </g> +</svg> diff --git a/src/locales/af.json b/src/locales/af.json index 9bf302e0..eb04e479 100644 --- a/src/locales/af.json +++ b/src/locales/af.json @@ -25,7 +25,6 @@ "pad.settings.fontType.monospaced": "Monospasie", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportpdf": "PDF", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.userdup.advice": "Maak weer 'n verbinding as u die venster wil gebruik.", "pad.modals.unauth": "Nie toegestaan", "pad.modals.deleted": "Geskrap.", diff --git a/src/locales/ar.json b/src/locales/ar.json index 0fa8c02b..9d29b521 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -3,7 +3,9 @@ "authors": [ "Ali1", "Tux-tn", - "Alami" + "Alami", + "Meno25", + "Test Create account" ] }, "index.newPad": "باد جديد", @@ -11,14 +13,14 @@ "pad.toolbar.bold.title": "سميك (Ctrl-B)", "pad.toolbar.italic.title": "مائل (Ctrl-I)", "pad.toolbar.underline.title": "تسطير (Ctrl-U)", - "pad.toolbar.strikethrough.title": "شطب", - "pad.toolbar.ol.title": "قائمة مرتبة", - "pad.toolbar.ul.title": "قائمة غير مرتبة", + "pad.toolbar.strikethrough.title": "شطب (Ctrl+5)", + "pad.toolbar.ol.title": "قائمة مرتبة (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "قائمة غير مرتبة (Ctrl+Shift+L)", "pad.toolbar.indent.title": "إزاحة", "pad.toolbar.unindent.title": "حذف الإزاحة", "pad.toolbar.undo.title": "فك (Ctrl-Z)", "pad.toolbar.redo.title": "تكرار (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "مسح ألوان التأليف", + "pad.toolbar.clearAuthorship.title": "مسح ألوان التأليف (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "استيراد/تصدير من/إلى تنسيقات ملفات مختلفة", "pad.toolbar.timeslider.title": "متصفح التاريخ", "pad.toolbar.savedRevision.title": "حفظ المراجعة", @@ -28,6 +30,7 @@ "pad.colorpicker.save": "تسجيل", "pad.colorpicker.cancel": "إلغاء", "pad.loading": "جاري التحميل...", + "pad.noCookie": "الكوكيز غير متاحة. الرجاء السماح بتحميل الكوكيز على متصفحك!", "pad.passwordRequired": "تحتاج إلى كلمة مرور للوصول إلى هذا الباد", "pad.permissionDenied": "ليس لديك إذن لدخول هذا الباد", "pad.wrongPassword": "كانت كلمة المرور خاطئة", @@ -46,12 +49,12 @@ "pad.importExport.import": "تحميل أي ملف نصي أو وثيقة", "pad.importExport.importSuccessful": "ناجح!", "pad.importExport.export": "تصدير الباد الحالي بصفة:", + "pad.importExport.exportetherpad": "إيثرباد", "pad.importExport.exporthtml": "إتش تي إم إل", "pad.importExport.exportplain": "نص عادي", "pad.importExport.exportword": "مايكروسوفت وورد", "pad.importExport.exportpdf": "صيغة المستندات المحمولة", "pad.importExport.exportopen": "ODF (نسق المستند المفتوح)", - "pad.importExport.exportdokuwiki": "دوکوويكي", "pad.importExport.abiword.innerHTML": "لايمكنك الاستيراد إلا من نص عادي أو من تنسيقات إتش تي إم إل. للحصول على المزيد من ميزات الاستيراد المتقدمة، يرجى تثبيت أبيورد <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\"></a>.", "pad.modals.connected": "متصل.", "pad.modals.reconnecting": "إعادة الاتصال ببادك", @@ -117,6 +120,7 @@ "pad.impexp.importing": "الاستيراد...", "pad.impexp.confirmimport": "استيراد ملف سيؤدي للكتابة فوق النص الحالي بالباد. هل أنت متأكد من أنك تريد المتابعة؟", "pad.impexp.convertFailed": "لم نتمكن من استيراد هذا الملف. يرجى استخدام تنسيق مستند مختلف، أو النسخ واللصق يدوياً", + "pad.impexp.padHasData": "لا يمكننا استيراد هذا الملف لأن هذه اللوحة تم بالفعل تغييره, الرجاء استيراد لوحة جديد", "pad.impexp.uploadFailed": "فشل التحميل، الرجاء المحاولة مرة أخرى", "pad.impexp.importfailed": "فشل الاستيراد", "pad.impexp.copypaste": "الرجاء نسخ/لصق", diff --git a/src/locales/ast.json b/src/locales/ast.json index 45ee9391..8eda58c8 100644 --- a/src/locales/ast.json +++ b/src/locales/ast.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Sólo se pue importar dende los formatos de testu planu o html. Pa carauterístiques d'importación más avanzaes <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\">instala abiword</a>.", "pad.modals.connected": "Coneutáu.", "pad.modals.reconnecting": "Reconeutando col to bloc...", diff --git a/src/locales/awa.json b/src/locales/awa.json new file mode 100644 index 00000000..f8c0d501 --- /dev/null +++ b/src/locales/awa.json @@ -0,0 +1,69 @@ +{ + "@metadata": { + "authors": [ + "1AnuraagPandey" + ] + }, + "index.newPad": "नयाँ प्याड", + "pad.toolbar.bold.title": "मोट (Ctrl-B)", + "pad.toolbar.italic.title": "तिरछा (Ctrl+I)", + "pad.toolbar.underline.title": "निम्न रेखाङ्कन (Ctrl-U)", + "pad.toolbar.indent.title": "इन्डेन्ट (TAB)", + "pad.toolbar.unindent.title": "आउटडेन्ट (Shift+TAB)", + "pad.toolbar.undo.title": "रद्द (Ctrl-Z)", + "pad.toolbar.redo.title": "पुन:लागु (Ctrl-Y)", + "pad.toolbar.timeslider.title": "टाइमस्लाइडर", + "pad.toolbar.savedRevision.title": "पुनरावलोकन संग्रह किहा जाय", + "pad.toolbar.settings.title": "सेटिङ्ग", + "pad.colorpicker.save": "सहेजा जाय", + "pad.colorpicker.cancel": "रद्द करा जाय", + "pad.loading": "लोड होत है...", + "pad.wrongPassword": "आप कय पासवर्ड गलत रहा", + "pad.settings.padSettings": "प्याड सेटिङ्ग", + "pad.settings.myView": "हमार दृष्य", + "pad.settings.colorcheck": "लेखकीय रङ्ग", + "pad.settings.linenocheck": "हरफ संख्या", + "pad.settings.fontType": "फन्ट प्रकार:", + "pad.settings.fontType.normal": "साधारण", + "pad.settings.fontType.monospaced": "मोनोस्पेस", + "pad.settings.globalView": "विश्वव्यापी दृष्य", + "pad.settings.language": "भाषा", + "pad.importExport.import_export": "आयात/निर्यात", + "pad.importExport.importSuccessful": "सफल!", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "सामान्य पाठ", + "pad.importExport.exportword": "माइक्रोसफ्ट वर्ड", + "pad.importExport.exportpdf": "पिडिएफ", + "pad.importExport.exportopen": "ओडिएफ(खुल्ला कागजात ढाँचा)", + "pad.modals.unauth": "अनाधिकृत", + "pad.modals.initsocketfail": "सर्भरमा पहुँच से बहरे है ।", + "pad.share.readonly": "पढय वाला खाली", + "pad.share.link": "लिङ्क", + "pad.share.emebdcode": "URL जोडा जाय", + "pad.chat": "बातचीत", + "timeslider.pageTitle": "{{appTitle}} समय रेखा", + "timeslider.toolbar.authors": "लेखक:", + "timeslider.toolbar.exportlink.title": "निर्यात", + "timeslider.version": "संस्करण {{version}}", + "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.month.january": "जनवरी", + "timeslider.month.february": "फेब्रुअरी", + "timeslider.month.march": "मार्च", + "timeslider.month.april": "अप्रैल", + "timeslider.month.may": "मई", + "timeslider.month.june": "जून", + "timeslider.month.july": "जुलाई", + "timeslider.month.august": "अगस्त", + "timeslider.month.september": "सेप्टेम्बर", + "timeslider.month.october": "अक्टूबर", + "timeslider.month.november": "नोभेम्बर", + "timeslider.month.december": "डिसेम्बर", + "timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}", + "pad.userlist.unnamed": "बेनामी", + "pad.userlist.guest": "पहुना", + "pad.userlist.deny": "अस्वीकार", + "pad.userlist.approve": "स्वीकृत", + "pad.impexp.importing": "आयात होत है...", + "pad.impexp.importfailed": "आयात असफल रहा", + "pad.impexp.copypaste": "कृपया कपी पेस्ट कीन जाय" +} diff --git a/src/locales/az.json b/src/locales/az.json index f4504323..f5968ee4 100644 --- a/src/locales/az.json +++ b/src/locales/az.json @@ -3,35 +3,36 @@ "authors": [ "AZISS", "Khan27", - "Mushviq Abdulla" + "Mushviq Abdulla", + "Wertuose" ] }, - "index.newPad": "Yeni Pad", - "index.createOpenPad": "və ya Pad-ı adı ilə yarat/aç:", + "index.newPad": "Yeni lövhə", + "index.createOpenPad": "və ya lövhəni bu adla yarat/aç:", "pad.toolbar.bold.title": "Qalın (Ctrl-B)", "pad.toolbar.italic.title": "Kursiv (Ctrl-I)", "pad.toolbar.underline.title": "Altından xətt çəkmə (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Pozulma", - "pad.toolbar.ol.title": "Qaydaya salınmış siyahı", - "pad.toolbar.ul.title": "Qaydaya salınmamış siyahı", - "pad.toolbar.indent.title": "Abzas", - "pad.toolbar.unindent.title": "Çıxıntı", - "pad.toolbar.undo.title": "Geri Al (Ctrl-Z)", - "pad.toolbar.redo.title": "Qaytarmaq (Ctrl-Y)", + "pad.toolbar.strikethrough.title": "Üstdən xətləmək (Ctrl+5)", + "pad.toolbar.ol.title": "Sıralanmış siyahı (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Sırasız siyahı (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Abzas (TAB)", + "pad.toolbar.unindent.title": "Çıxıntı (Shift+TAB)", + "pad.toolbar.undo.title": "Geri qaytar (Ctrl+Z)", + "pad.toolbar.redo.title": "Qaytar (Ctrl+Y)", "pad.toolbar.clearAuthorship.title": "Müəlliflik Rənglərini Təmizlə", "pad.toolbar.import_export.title": "Müxtəlif fayl formatların(a/dan) idxal/ixrac", "pad.toolbar.timeslider.title": "Vaxt cədvəli", "pad.toolbar.savedRevision.title": "Saxlanılan Düzəlişlər", "pad.toolbar.settings.title": "Tənzimləmələr", - "pad.toolbar.embed.title": "Bu pad-ı yayımla", - "pad.toolbar.showusers.title": "Pad-da istifadəçiləri göstər", + "pad.toolbar.embed.title": "Bu lövhəni paylaş və qur", + "pad.toolbar.showusers.title": "Lövhədəki istifadəçiləri göstər", "pad.colorpicker.save": "Saxla", "pad.colorpicker.cancel": "İmtina", "pad.loading": "Yüklənir...", - "pad.passwordRequired": "Bu pad-a daxil olmaq üçün parol lazımdır", - "pad.permissionDenied": "Bu pad-a daxil olmaq üçün icazəniz yoxdur", + "pad.passwordRequired": "Bu lövhəyə daxil olmaq üçün parol lazımdır", + "pad.permissionDenied": "Bu lövhəyə daxil olmaq üçün icazəniz yoxdur", "pad.wrongPassword": "Sizin parolunuz səhvdir", - "pad.settings.padSettings": "Pad Tənzimləmələri", + "pad.settings.padSettings": "Lövhə nizamlamaları", "pad.settings.myView": "Mənim Görüntüm", "pad.settings.stickychat": "Söhbət həmişə ekranda", "pad.settings.colorcheck": "Müəlliflik rəngləri", @@ -45,20 +46,20 @@ "pad.importExport.import_export": "İdxal/İxrac", "pad.importExport.import": "Hər hansı bir mətn faylı və ya sənəd yüklə", "pad.importExport.importSuccessful": "Uğurlu!", - "pad.importExport.export": "Hazırki pad-ı ixrac etmək kimi:", + "pad.importExport.export": "Hazırkı lövhəni bu şəkildə ixrac et:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Adi mətn", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", - "pad.importExport.exportopen": "ODF (Açıq Sənəd Formatı)", - "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.importExport.exportopen": "ODF (açıq sənəd formatı)", "pad.importExport.abiword.innerHTML": "Siz yalnız adi mətndən və ya HTML-dən idxal edə bilərsiniz. İdxalın daha mürəkkəb funksiyaları üçün, zəhmət olmasa, <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\"> AbiWord-i quraşdırın</a>.", "pad.modals.connected": "Bağlandı.", - "pad.modals.reconnecting": "Sizin pad yenidən qoşulur..", + "pad.modals.reconnecting": "Sizin lövhə yenidən qoşulur..", "pad.modals.forcereconnect": "Məcbur təkrarən bağlan", "pad.modals.userdup": "Başqa pəncərədə artıq açıqdır", - "pad.modals.userdup.explanation": "Sənəd, ola bilsin ki, bu kompyuterdə, brauzerin bir neçə pəncərəsində açılmışdır.", - "pad.modals.userdup.advice": "Bu pəncərədən istifadəylə yenidən qoşulun.", + "pad.modals.userdup.explanation": "Bu lövhə, ola bilsin ki, bu kompüterdəki brauzerin bir neçə pəncərəsində açılmışdır.", + "pad.modals.userdup.advice": "Bu pəncərəni istifadə etmək üçün yenidən qoşul.", "pad.modals.unauth": "İcazəli deyil", "pad.modals.unauth.explanation": "Bu səhifəyə baxdığınız vaxt sizin icazəniz dəyişilib. Bərpa etmək üşün yenidən cəhd edin.", "pad.modals.looping.explanation": "Sinxronlaşdırma serveri ilə kommunikasiya xətası var.", @@ -70,22 +71,22 @@ "pad.modals.slowcommit.cause": "Bu şəbəkə bağlantısında problemlər yarana bilər.", "pad.modals.badChangeset.explanation": "Etdiyiniz bir redaktə sinxronizasiya serveri tərəfindən qeyri-leqal/qanundan kənar olaraq təsbit edildi.", "pad.modals.badChangeset.cause": "Bu, yanlış server tərtibatı ya da başqa bir gözlənilməyən davranışlar nəticəsində ola bilər. Bu sizə bir xəta imiş kimi görünürsə lütfən servis nəzarətçisi ilə əlaqə yaradın. Redaktəyə davam etmək üçün yenidən qoşulmanı yoxlayın.", - "pad.modals.corruptPad.explanation": "Əldə etməyə çalışdığınız sənəd zədəlidir.", + "pad.modals.corruptPad.explanation": "Daxil olmağa çalışdığınız lövhə zədəlidir.", "pad.modals.corruptPad.cause": "Bu, yanlış server tərtibatı ya da başqa bir gözlənilməyən davranışlardan əmələ gələ bilər. Lütfən servis nəzarətçisi ilə əlaqə yaradın.", "pad.modals.deleted": "Silindi.", - "pad.modals.deleted.explanation": "Bu pad silindi.", + "pad.modals.deleted.explanation": "Bu lövhə silindi.", "pad.modals.disconnected": "Əlaqə kəsilib.", "pad.modals.disconnected.explanation": "Serverə qoşulma itirilib", "pad.modals.disconnected.cause": "Server istifadə olunmur. Əgər problem təkrarlanacaqsa, bizə bildirin.", - "pad.share": "Bu pad-ı yayımla", + "pad.share": "Bu lövhəni paylaş", "pad.share.readonly": "Yalnız oxuyun", "pad.share.link": "Keçid", "pad.share.emebdcode": "URL-ni yayımla", "pad.chat": "Söhbət", - "pad.chat.title": "Bu pad üçün chat açın.", + "pad.chat.title": "Bu lövhə üçün çat açın.", "pad.chat.loadmessages": "Daha çox mesaj yüklə", "timeslider.pageTitle": "{{appTitle}} Vaxt cədvəli", - "timeslider.toolbar.returnbutton": "Pad-a qayıt", + "timeslider.toolbar.returnbutton": "Lövhəyə qayıt", "timeslider.toolbar.authors": "Müəlliflər:", "timeslider.toolbar.authorsList": "Müəllif yoxdur", "timeslider.toolbar.exportlink.title": "İxrac", @@ -105,18 +106,19 @@ "timeslider.month.october": "Oktyabr", "timeslider.month.november": "Noyabr", "timeslider.month.december": "Dekabr", - "timeslider.unnamedauthors": "{{num}} adsız müəlliflər", + "timeslider.unnamedauthors": "{{num}} adsız {[plural(num) one: müəllif, other: müəllif]}", "pad.savedrevs.marked": "Bu versiya indi yaddaşa saxlanmış kimi nişanlandı", - "pad.userlist.entername": "Adınızı daxil et", + "pad.userlist.entername": "Adınızı daxil edin", "pad.userlist.unnamed": "adsız", "pad.userlist.guest": "Qonaq", "pad.userlist.deny": "İnkar etmək", "pad.userlist.approve": "Təsdiqləmək", - "pad.editbar.clearcolors": "Bütün sənədlərdə müəlliflik rənglərini təmizlə?", - "pad.impexp.importbutton": "İndi idxal edin", + "pad.editbar.clearcolors": "Bütün sənədlərdə müəllif rəngləri təmizlənsin?", + "pad.impexp.importbutton": "İndi idxal et", "pad.impexp.importing": "İdxal...", - "pad.impexp.confirmimport": "Faylın idxalı cari mətni yeniləyəcək. Siz əminsinizmi ki, davam etmək istəyirsiniz?", + "pad.impexp.confirmimport": "Faylın idxalı lövhədəki cari mətni yeniləyəcək. Davam etmək istədiyinizə əminsinizmi?", "pad.impexp.convertFailed": "Biz bu fayl idxal etmək mümkün deyil idi. Xahiş olunur müxtəlif sənəddən istifadə edin və ya kopyalayıb yapışdırmaq yolundan istifadə edin", + "pad.impexp.padHasData": "Biz bu faylı idxal edə bilmədik, çünki bu lövhədə düzəlişlər edilib, lütfən yeni lövhə idxal edin", "pad.impexp.uploadFailed": "Yükləmədə səhv, xahiş olunur yenə cəhd edin", "pad.impexp.importfailed": "İdxal zamanı səhv", "pad.impexp.copypaste": "Xahiş edirik kopyalayıb yapışdırın", diff --git a/src/locales/azb.json b/src/locales/azb.json index b3b76f80..799c5b6b 100644 --- a/src/locales/azb.json +++ b/src/locales/azb.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "Amir a57", - "Mousa" + "Mousa", + "Koroğlu" ] }, "index.newPad": "یئنی یادداشت دفترچه سی", @@ -45,7 +46,6 @@ "pad.importExport.exportword": "مایکروسافت وورد", "pad.importExport.exportpdf": "پی دی اف", "pad.importExport.exportopen": "او دی اف", - "pad.importExport.exportdokuwiki": "دوکو ویکی", "pad.modals.connected": "متصل اولدی", "pad.modals.reconnecting": "سیزین یادداشت دفترچه سینه یئنی دن متصیل اولدی", "pad.modals.forcereconnect": "یئنی اتصال اوچون زورلاما", @@ -68,5 +68,21 @@ "timeslider.pageTitle": "{{appTitle}}زمان اسلایدری", "timeslider.toolbar.returnbutton": "یادداشت دفترچه سینه قاییت", "timeslider.toolbar.authors": "یازیچیلار", - "timeslider.toolbar.authorsList": "یازیچی سیز" + "timeslider.toolbar.authorsList": "یازیچی سیز", + "timeslider.month.january": "ژانویه", + "timeslider.month.february": "فوریه", + "timeslider.month.march": "مارس", + "timeslider.month.april": "آپریل", + "timeslider.month.may": "مای", + "timeslider.month.june": "ژوئن", + "timeslider.month.july": "جولای", + "timeslider.month.august": "آقوست", + "timeslider.month.september": "سپتامبر", + "timeslider.month.october": "اوْکتوبر", + "timeslider.month.november": "نوْوامبر", + "timeslider.month.december": "دسامبر", + "pad.userlist.unnamed": "آدسیز", + "pad.userlist.guest": "قوْناق", + "pad.userlist.deny": "دانماق", + "pad.userlist.approve": "اوْنایلا" } diff --git a/src/locales/bcc.json b/src/locales/bcc.json index 10349ae5..7ae42005 100644 --- a/src/locales/bcc.json +++ b/src/locales/bcc.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (قالب سند باز)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "شما تنها میتوانید از قالب متن ساده یا اچتیامال درونریزی کنید. برای بیشتر شدن ویژگیهای درونریزی پیشرفته <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\">AbiWord</a> را نصب کنید.", "pad.modals.connected": "متصل شد.", "pad.modals.reconnecting": "در حال اتصال دوباره به دفترچه یادداشت شما..", diff --git a/src/locales/be-tarask.json b/src/locales/be-tarask.json index ae29c396..3c789858 100644 --- a/src/locales/be-tarask.json +++ b/src/locales/be-tarask.json @@ -11,14 +11,14 @@ "pad.toolbar.bold.title": "Тоўсты (Ctrl-B)", "pad.toolbar.italic.title": "Курсіў (Ctrl-I)", "pad.toolbar.underline.title": "Падкрэсьліваньне (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Закрэсьліваньне", - "pad.toolbar.ol.title": "Упарадкаваны сьпіс", - "pad.toolbar.ul.title": "Неўпарадкаваны сьпіс", + "pad.toolbar.strikethrough.title": "Закрэсьліваньне (Ctrl+5)", + "pad.toolbar.ol.title": "Упарадкаваны сьпіс (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Неўпарадкаваны сьпіс (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Водступ (TAB)", "pad.toolbar.unindent.title": "Выступ (Shift+TAB)", "pad.toolbar.undo.title": "Скасаваць(Ctrl-Z)", "pad.toolbar.redo.title": "Вярнуць (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Прыбраць колер дакумэнту", + "pad.toolbar.clearAuthorship.title": "Прыбраць колер дакумэнту (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Імпарт/Экспарт з выкарыстаньне розных фарматаў файлаў", "pad.toolbar.timeslider.title": "Шкала часу", "pad.toolbar.savedRevision.title": "Захаваць вэрсію", @@ -28,12 +28,14 @@ "pad.colorpicker.save": "Захаваць", "pad.colorpicker.cancel": "Скасаваць", "pad.loading": "Загрузка...", + "pad.noCookie": "Кукі ня знойдзеныя. Калі ласка, дазвольце кукі ў вашым браўзэры!", "pad.passwordRequired": "Для доступу да гэтага дакумэнта патрэбны пароль", "pad.permissionDenied": "Вы ня маеце дазволу на доступ да гэтага дакумэнта", "pad.wrongPassword": "Вы ўвялі няслушны пароль", "pad.settings.padSettings": "Налады дакумэнта", "pad.settings.myView": "Мой выгляд", "pad.settings.stickychat": "Заўсёды паказваць чат", + "pad.settings.chatandusers": "Паказаць чат і ўдзельнікаў", "pad.settings.colorcheck": "Колеры аўтарства", "pad.settings.linenocheck": "Нумары радкоў", "pad.settings.rtlcheck": "Тэкст справа-налева", @@ -46,12 +48,12 @@ "pad.importExport.import": "Загрузіжайце любыя тэкставыя файлы або дакумэнты", "pad.importExport.importSuccessful": "Пасьпяхова!", "pad.importExport.export": "Экспартаваць бягучы дакумэнт як:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Просты тэкст", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Вы можаце імпартаваць толькі з звычайнага тэксту або HTML. Дзеля больш пашыраных магчымасьцяў імпарту, калі ласка, <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\">усталюйце abiword</a>.", "pad.modals.connected": "Падлучыліся.", "pad.modals.reconnecting": "Перападлучэньне да вашага дакумэнта...", @@ -107,6 +109,7 @@ "timeslider.month.december": "сьнежань", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: безыменны аўтар, few: безыменныя аўтары, many: безыменных аўтараў, other: безыменных аўтараў ]}", "pad.savedrevs.marked": "Гэтая вэрсія цяпер пазначаная як захаваная", + "pad.savedrevs.timeslider": "Вы можаце пабачыць захаваныя вэрсіі з дапамогай шкалы часу", "pad.userlist.entername": "Увядзіце вашае імя", "pad.userlist.unnamed": "безыменны", "pad.userlist.guest": "Госьць", @@ -117,6 +120,7 @@ "pad.impexp.importing": "Імпартаваньне…", "pad.impexp.confirmimport": "Імпарт файла перазапіша цяперашні тэкст дакумэнту. Вы ўпэўненыя, што хочаце працягваць?", "pad.impexp.convertFailed": "Не атрымалася імпартаваць гэты файл. Калі ласка, выкарыстайце іншы фармат дакумэнту або скапіюйце ўручную.", + "pad.impexp.padHasData": "Мы не змаглі імпартаваць гэты файл, бо дакумэнт ужо мае зьмены, калі ласка, імпартуйце ў новы дакумэнт", "pad.impexp.uploadFailed": "Загрузка не атрымалася, калі ласка, паспрабуйце яшчэ раз", "pad.impexp.importfailed": "Памылка імпарту", "pad.impexp.copypaste": "Калі ласка, скапіюйце і ўстаўце", diff --git a/src/locales/bgn.json b/src/locales/bgn.json new file mode 100644 index 00000000..00efbf3f --- /dev/null +++ b/src/locales/bgn.json @@ -0,0 +1,75 @@ +{ + "@metadata": { + "authors": [ + "Baloch Afghanistan" + ] + }, + "index.newPad": "یاداشتی نوکین کتابچه", + "index.createOpenPad": "یا جوڑ\t کورتین/پاچ کورتین یک کتابچه ئی یاداشتی بی نام:", + "pad.toolbar.bold.title": "پررنگ (Ctrl-B)", + "pad.toolbar.italic.title": "چوّٹ (Ctrl-I)", + "pad.toolbar.underline.title": "جهلگ خط (Ctrl-U)", + "pad.toolbar.strikethrough.title": "خط وارته (Ctrl+5)", + "pad.toolbar.ol.title": "ترتیب بوتگین لر لیست (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "ترتیب نه بوتگین لر لیست (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "بیئتئ بوتگین (TAB)", + "pad.toolbar.unindent.title": "در آتگی (Shift+TAB)", + "pad.toolbar.undo.title": "باطلکورتین (Ctrl-Z)", + "pad.toolbar.redo.title": "شه نوک (Ctrl-Y)", + "pad.toolbar.clearAuthorship.title": "نویسوکئ رنگانی پاک کورتین (Ctrl+Shift+C)", + "pad.toolbar.import_export.title": "بی تئ کورتین/دَر کورتین شه/بی رکم رکمین قالیبان", + "pad.toolbar.timeslider.title": "وختئ لَگوشوک", + "pad.toolbar.savedRevision.title": "نسخه ئی ذخیره کورتین", + "pad.toolbar.settings.title": "تنظیمات", + "pad.colorpicker.save": "ذخیره", + "pad.colorpicker.cancel": "کنسیل", + "pad.loading": "لودینگ...", + "pad.wrongPassword": "شمی پاسورد جووان نه اینت", + "pad.settings.padSettings": "یاداشتئ دفترچه ئی تنظیمات", + "pad.settings.myView": "نئ دیست", + "pad.settings.stickychat": "هبر موچین وختا بی دیستئ تاکدیمئ سرا بیئت", + "pad.settings.colorcheck": "نویسوکی رنگ ئان", + "pad.settings.linenocheck": "خط ئانی نمبر", + "pad.settings.rtlcheck": "محتوایی وانتین شه راست بی چپا؟", + "pad.settings.fontType": "قلم رکم:", + "pad.settings.fontType.normal": "ساددگ", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "سراسرین دیست یا نما", + "pad.settings.language": "زبان:", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "ساده گین متن", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (پاچین سندئ قالب)", + "pad.importExport.abiword.innerHTML": "شما تا توانیت که شه ساده گین متنی ئین قالب یا اچتیامال بی تئ کنیت . په گیشتیرین کارا ئییان پیشرفته ئین بی تئ کورتینا <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\">AbiWord</a> نصب کنیت.", + "pad.modals.connected": "وصل بوت.", + "pad.modals.userdup": "نوکین دروازه گئ پاچ کورتین", + "pad.modals.unauth": "مجاز نه اینت", + "pad.modals.deleted.explanation": "ای یاداشتی دفترچه پاک بوته.", + "pad.share.readonly": "فقط وانتین", + "pad.share.link": "لینک", + "pad.chat": "چت وهبر", + "timeslider.toolbar.exportlink.title": "دَر کورتین", + "timeslider.month.january": "جنوری", + "timeslider.month.february": "فیبروری", + "timeslider.month.march": "مارچ", + "timeslider.month.april": "اپریل", + "timeslider.month.may": "می", + "timeslider.month.june": "جون", + "timeslider.month.july": "جولای", + "timeslider.month.august": "اگوست", + "timeslider.month.september": "سیپٹمبر", + "timeslider.month.october": "اکتوبر", + "timeslider.month.november": "نوامبر", + "timeslider.month.december": "ڈ\tسمبر", + "timeslider.unnamedauthors": "{{num}} بی نامین نویسوک", + "pad.userlist.entername": "وتئ ناما نیویشته بکنیت", + "pad.userlist.unnamed": "بی نام", + "pad.userlist.guest": "مهمان", + "pad.userlist.deny": "رد کورتین", + "pad.userlist.approve": "قبول کورتین", + "pad.impexp.importbutton": "انون بی تئ کن", + "pad.impexp.importing": "بی بی تئ کورتینی حالا...", + "pad.impexp.uploadFailed": "آپلود انجام نه بوت، پدا کوشش کن", + "pad.impexp.copypaste": "کپی پیست کَنیت" +} diff --git a/src/locales/bn.json b/src/locales/bn.json index 6c3d6cfc..10a804b4 100644 --- a/src/locales/bn.json +++ b/src/locales/bn.json @@ -4,7 +4,8 @@ "Bellayet", "Nasir8891", "Sankarshan", - "Aftab1995" + "Aftab1995", + "Aftabuzzaman" ] }, "index.newPad": "নতুন প্যাড", @@ -18,7 +19,7 @@ "pad.toolbar.unindent.title": "আউটডেন্ট (Shift+TAB)", "pad.toolbar.undo.title": "বাতিল করুন (Ctrl-Z)", "pad.toolbar.redo.title": "পুনরায় করুন (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "কৃতি রং পরিষ্কার করুন", + "pad.toolbar.clearAuthorship.title": "কৃতি রং পরিষ্কার করুন (Ctrl+Shift+C)", "pad.toolbar.timeslider.title": "টাইমস্লাইডার", "pad.toolbar.savedRevision.title": "সংস্করণ সংরক্ষণ করুন", "pad.toolbar.settings.title": "সেটিং", @@ -49,7 +50,6 @@ "pad.importExport.exportword": "মাইক্রোসফট ওয়ার্ড", "pad.importExport.exportpdf": "পিডিএফ", "pad.importExport.exportopen": "ওডিএফ (ওপেন ডকুমেন্ট ফরম্যাট)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "যোগাযোগ সফল", "pad.modals.reconnecting": "আপনার প্যাডের সাথে সংযোগস্থাপন করা হচ্ছে..", "pad.modals.forcereconnect": "পুনরায় সংযোগস্থাপনের চেষ্টা", diff --git a/src/locales/br.json b/src/locales/br.json index e22802c1..6bbd56d2 100644 --- a/src/locales/br.json +++ b/src/locales/br.json @@ -12,14 +12,14 @@ "pad.toolbar.bold.title": "Tev (Ctrl-B)", "pad.toolbar.italic.title": "Italek (Ctrl-I)", "pad.toolbar.underline.title": "Islinennañ (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Barrennet", - "pad.toolbar.ol.title": "Roll urzhiet", - "pad.toolbar.ul.title": "Roll en dizurzh", + "pad.toolbar.strikethrough.title": "Barrennet(Ktrl+5)", + "pad.toolbar.ol.title": "Listenn urzhiet (Ktrl+Pennlizherenn+N)", + "pad.toolbar.ul.title": "Listenn en dizurzh (Ktrl+Pennlizherenn+L)", "pad.toolbar.indent.title": "Endantañ (TAB)", "pad.toolbar.unindent.title": "Diendantañ (Shift+TAB)", "pad.toolbar.undo.title": "Dizober (Ktrl-Z)", "pad.toolbar.redo.title": "Adober (Ktrl-Y)", - "pad.toolbar.clearAuthorship.title": "Diverkañ al livioù oc'h anaout an aozerien", + "pad.toolbar.clearAuthorship.title": "Diverkañ al livioù oc'h anaout an aozerien (Ktrl+Pennlizherenn+C)", "pad.toolbar.import_export.title": "Enporzhiañ/Ezporzhiañ eus/war-zu ur furmad restr disheñvel", "pad.toolbar.timeslider.title": "Istor dinamek", "pad.toolbar.savedRevision.title": "Doareoù enrollet", @@ -29,6 +29,7 @@ "pad.colorpicker.save": "Enrollañ", "pad.colorpicker.cancel": "Nullañ", "pad.loading": "O kargañ...", + "pad.noCookie": "N'eus ket gallet kavout an toupin. Aotreit an toupinoù en ho merdeer, mar plij !", "pad.passwordRequired": "Ezhomm ho peus ur ger-tremen evit mont d'ar Pad-se", "pad.permissionDenied": "\nN'oc'h ket aotreet da vont d'ar pad-mañ", "pad.wrongPassword": "Fazius e oa ho ker-tremen", @@ -47,12 +48,12 @@ "pad.importExport.import": "Enkargañ un destenn pe ur restr", "pad.importExport.importSuccessful": "Deuet eo ganeoc'h !", "pad.importExport.export": "Ezporzhiañ ar pad bremañ evel :", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Testenn blaen", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Ne c'hallit ket emporzjiañ furmadoù testennoù kriz pe html. Evit arc'hwelioù enporzhiañ emdroetoc'h, staliit <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\">abiword</a> mar plij.", "pad.modals.connected": "Kevreet.", "pad.modals.reconnecting": "Adkevreañ war-zu ho pad...", @@ -118,6 +119,7 @@ "pad.impexp.importing": "Oc'h enporzhiañ...", "pad.impexp.confirmimport": "Ma vez enporzhiet ur restr e vo diverket ar pezh zo en teul a-vremañ. Ha sur oc'h e fell deoc'h mont betek penn ?", "pad.impexp.convertFailed": "N'eus ket bet gallet enporzhiañ ar restr. Ober gant ur furmad teul all pe eilañ/pegañ gant an dorn.", + "pad.impexp.padHasData": "N'hon eus ket gallet enporzhiañ ar restr-mañdre ma'z eus bet degaset kemmoù er bloc'h-se ; enporzhiit anezhi war-zu ur bloc'h nevez, mar plij.", "pad.impexp.uploadFailed": "C'hwitet eo bet an enporzhiañ. Klaskit en-dro.", "pad.impexp.importfailed": "C'hwitet eo an enporzhiadenn", "pad.impexp.copypaste": "Eilit/pegit, mar plij", diff --git a/src/locales/ca.json b/src/locales/ca.json index f6870db8..b7edc65b 100644 --- a/src/locales/ca.json +++ b/src/locales/ca.json @@ -4,7 +4,8 @@ "Alvaro Vidal-Abarca", "Pginer", "Pitort", - "Toniher" + "Toniher", + "Macofe" ] }, "index.newPad": "Nou pad", @@ -12,14 +13,14 @@ "pad.toolbar.bold.title": "Negreta (Ctrl-B)", "pad.toolbar.italic.title": "Cursiva (Ctrl-I)", "pad.toolbar.underline.title": "Subratllat (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Ratllat", - "pad.toolbar.ol.title": "Llista ordenada", - "pad.toolbar.ul.title": "Llista sense ordenar", + "pad.toolbar.strikethrough.title": "Ratllat (Ctrl+5)", + "pad.toolbar.ol.title": "Llista ordenada (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Llista sense ordenar (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Sagnat (TAB)", "pad.toolbar.unindent.title": "Sagnat invers (Majúsc+TAB)", "pad.toolbar.undo.title": "Desfés (Ctrl-Z)", "pad.toolbar.redo.title": "Refés (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Neteja els colors d'autoria", + "pad.toolbar.clearAuthorship.title": "Neteja els colors d'autoria (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importa/exporta a partir de diferents formats de fitxer", "pad.toolbar.timeslider.title": "Línia temporal", "pad.toolbar.savedRevision.title": "Desa la revisió", @@ -29,6 +30,7 @@ "pad.colorpicker.save": "Desa", "pad.colorpicker.cancel": "Cancel·la", "pad.loading": "S'està carregant...", + "pad.noCookie": "No s'ha trobat la galeta. Permeteu les galetes en el navegador!", "pad.passwordRequired": "Us cal una contrasenya per a accedir a aquest pad", "pad.permissionDenied": "No teniu permisos per a accedir a aquest pad", "pad.wrongPassword": "La contrasenya és incorrecta", @@ -47,12 +49,12 @@ "pad.importExport.import": "Puja qualsevol fitxer de text o document", "pad.importExport.importSuccessful": "Hi ha hagut èxit!", "pad.importExport.export": "Exporta el pad actual com a:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Text net", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Només podeu importar de text net o html. Per a opcions d'importació més avançades <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\">instal·leu l'Abiword</a>.", "pad.modals.connected": "Connectat.", "pad.modals.reconnecting": "S'està tornant a connectar al vostre pad…", @@ -118,6 +120,7 @@ "pad.impexp.importing": "Important...", "pad.impexp.confirmimport": "En importar un fitxer se sobreescriurà el text actual del pad. Esteu segur que voleu continuar?", "pad.impexp.convertFailed": "No és possible d'importar aquest fitxer. Si us plau, podeu provar d'utilitzar un format diferent o copiar i enganxar manualment.", + "pad.impexp.padHasData": "No vam poder importar el fitxer perquè el pad ja tenia canvis. Importeu-lo a un nou pad", "pad.impexp.uploadFailed": "Ha fallat la càrrega. Torneu-ho a provar", "pad.impexp.importfailed": "Ha fallat la importació", "pad.impexp.copypaste": "Si us plau, copieu i enganxeu", diff --git a/src/locales/cs.json b/src/locales/cs.json index 62a2302c..19552ffd 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -13,11 +13,11 @@ "pad.toolbar.bold.title": "Tučný text (Ctrl-B)", "pad.toolbar.italic.title": "Kurzíva (Ctrl-I)", "pad.toolbar.underline.title": "Podtržené písmo (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Přeskrtnuté písmo", + "pad.toolbar.strikethrough.title": "Přeškrtnuto (Ctrl+5)", "pad.toolbar.ol.title": "Číslovaný seznam", "pad.toolbar.ul.title": "Nečíslovaný seznam", - "pad.toolbar.indent.title": "Odsazení", - "pad.toolbar.unindent.title": "Předsazení", + "pad.toolbar.indent.title": "Odsazení (TAB)", + "pad.toolbar.unindent.title": "Předsazení (Shift+TAB)", "pad.toolbar.undo.title": "Zpět (Ctrl-Z)", "pad.toolbar.redo.title": "Opakovat (Ctrl-Y)", "pad.toolbar.clearAuthorship.title": "Vymazat barvy autorů", @@ -30,12 +30,14 @@ "pad.colorpicker.save": "Uložit", "pad.colorpicker.cancel": "Zrušit", "pad.loading": "Načítání...", + "pad.noCookie": "Nelze nalézt cookie. Povolte prosím cookie ve Vašem prohlížeči.", "pad.passwordRequired": "Pro přístup k tomuto Padu je třeba znát heslo", "pad.permissionDenied": "Nemáte oprávnění pro přístup k tomuto Padu", "pad.wrongPassword": "Nesprávné heslo", "pad.settings.padSettings": "Nastavení Padu", "pad.settings.myView": "Vlastní pohled", "pad.settings.stickychat": "Chat vždy na obrazovce", + "pad.settings.chatandusers": "Ukázat Chat a Uživatele", "pad.settings.colorcheck": "Barvy autorů", "pad.settings.linenocheck": "Čísla řádků", "pad.settings.rtlcheck": "Číst obsah zprava doleva?", @@ -48,12 +50,12 @@ "pad.importExport.import": "Nahrát libovolný textový soubor nebo dokument", "pad.importExport.importSuccessful": "Úspěšně!", "pad.importExport.export": "Exportovat stávající Pad jako:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Prostý text", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Importovat můžeš pouze prostý text nebo HTML formátování. Pro pokročilejší funkce importu, prosím, nainstaluj „<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\">abiword</a>“.", "pad.modals.connected": "Připojeno.", "pad.modals.reconnecting": "Znovupřipojování k Padu…", @@ -109,6 +111,7 @@ "timeslider.month.december": "prosinec", "timeslider.unnamedauthors": "{{num}} {[ plural(num) one: nejmenovaný Autor, few: nejmenovaní Autoři, other: nejmenovaných Autorů ]}", "pad.savedrevs.marked": "Tato revize je nyní označena jako uložená", + "pad.savedrevs.timeslider": "Návštěvou časové osy zobrazíte uložené revize", "pad.userlist.entername": "Zadejte své jméno", "pad.userlist.unnamed": "nejmenovaný", "pad.userlist.guest": "Host", @@ -119,6 +122,7 @@ "pad.impexp.importing": "Importování…", "pad.impexp.confirmimport": "Import souboru přepíše aktuální text v padu. Opravdu chcete tuto akci provést?", "pad.impexp.convertFailed": "Tento soubor nelze importovat. Použijte prosím jiný formát dokumentu nebo nakopírujte text ručně", + "pad.impexp.padHasData": "Tento soubor jsme nebyly schopni importovat, protože tento Pad již obsahoval změny. Importujte ho prosím do nového padu", "pad.impexp.uploadFailed": "Nahrávání selhalo, zkuste to znovu", "pad.impexp.importfailed": "Import selhal", "pad.impexp.copypaste": "Vložte prosím kopii", diff --git a/src/locales/da.json b/src/locales/da.json index a6a07d14..662e9afb 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -11,14 +11,14 @@ "pad.toolbar.bold.title": "Fed (Ctrl-B)", "pad.toolbar.italic.title": "Kursiv (Ctrl-I)", "pad.toolbar.underline.title": "Understregning (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Gennemstregning", - "pad.toolbar.ol.title": "Sorteret liste", - "pad.toolbar.ul.title": "Usorteret liste", + "pad.toolbar.strikethrough.title": "Gennemstregning (Ctrl+5)", + "pad.toolbar.ol.title": "Sorteret liste (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Usorteret liste (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Indrykning (TAB)", "pad.toolbar.unindent.title": "Ryk ud (Shift+TAB)", "pad.toolbar.undo.title": "Fortryd (Ctrl-Z)", "pad.toolbar.redo.title": "Annuller Fortryd (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Fjern farver for forfatterskab", + "pad.toolbar.clearAuthorship.title": "Fjern farver for forfatterskab (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Import/eksport fra/til forskellige filformater", "pad.toolbar.timeslider.title": "Timeslider", "pad.toolbar.savedRevision.title": "Gem Revision", @@ -51,7 +51,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Du kan kun importere fra almindelig tekst eller HTML-formater. For mere avancerede importfunktioner, <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\">installer venligst abiword</a>.", "pad.modals.connected": "Forbundet.", "pad.modals.reconnecting": "Genopretter forbindelsen til din pad...", diff --git a/src/locales/de.json b/src/locales/de.json index 05a9af79..4888b8e8 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -12,14 +12,14 @@ "pad.toolbar.bold.title": "Fett (Strg-B)", "pad.toolbar.italic.title": "Kursiv (Strg-I)", "pad.toolbar.underline.title": "Unterstrichen (Strg-U)", - "pad.toolbar.strikethrough.title": "Durchgestrichen", - "pad.toolbar.ol.title": "Nummerierte Liste", - "pad.toolbar.ul.title": "Ungeordnete Liste", + "pad.toolbar.strikethrough.title": "Durchgestrichen (Strg+5)", + "pad.toolbar.ol.title": "Nummerierte Liste (Strg+Shift+N)", + "pad.toolbar.ul.title": "Ungeordnete Liste (Strg+Shift+L)", "pad.toolbar.indent.title": "Einrücken (TAB)", "pad.toolbar.unindent.title": "Ausrücken (Shift+TAB)", "pad.toolbar.undo.title": "Rückgängig (Strg-Z)", "pad.toolbar.redo.title": "Wiederholen (Strg-Y)", - "pad.toolbar.clearAuthorship.title": "Autorenfarben zurücksetzen", + "pad.toolbar.clearAuthorship.title": "Autorenfarben zurücksetzen (Strg+Shift+C)", "pad.toolbar.import_export.title": "Import/Export von/zu verschiedenen Dateiformaten", "pad.toolbar.timeslider.title": "Pad-Versionsgeschichte anzeigen", "pad.toolbar.savedRevision.title": "Version markieren", @@ -29,12 +29,14 @@ "pad.colorpicker.save": "Speichern", "pad.colorpicker.cancel": "Abbrechen", "pad.loading": "Laden …", + "pad.noCookie": "Das Cookie konnte nicht gefunden werden. Bitte erlaube Cookies in deinem Browser!", "pad.passwordRequired": "Sie benötigen ein Passwort, um auf dieses Pad zuzugreifen", "pad.permissionDenied": "Sie haben keine Berechtigung, um auf dieses Pad zuzugreifen", "pad.wrongPassword": "Ihr Passwort war falsch", "pad.settings.padSettings": "Pad Einstellungen", "pad.settings.myView": "Eigene Ansicht", "pad.settings.stickychat": "Chat immer anzeigen", + "pad.settings.chatandusers": "Chat und Benutzer anzeigen", "pad.settings.colorcheck": "Autorenfarben anzeigen", "pad.settings.linenocheck": "Zeilennummern", "pad.settings.rtlcheck": "Inhalt von rechts nach links lesen?", @@ -47,12 +49,12 @@ "pad.importExport.import": "Text-Datei oder Dokument hochladen", "pad.importExport.importSuccessful": "Erfolgreich!", "pad.importExport.export": "Aktuelles Pad exportieren als:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Textdatei", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Sie können nur aus Klartext oder HTML-Formaten importieren. Für mehr erweiterte Importfunktionen <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\">installieren Sie bitte abiword</a>.", "pad.modals.connected": "Verbunden.", "pad.modals.reconnecting": "Wiederherstellen der Verbindung …", @@ -108,6 +110,7 @@ "timeslider.month.december": "Dezember", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: unbenannter Autor, other: unbenannte Autoren ]}", "pad.savedrevs.marked": "Diese Version wurde jetzt als gespeicherte Version gekennzeichnet", + "pad.savedrevs.timeslider": "Du kannst gespeicherte Versionen durch das Besuchen der Pad-Versionsgeschichte ansehen", "pad.userlist.entername": "Geben Sie Ihren Namen ein", "pad.userlist.unnamed": "unbenannt", "pad.userlist.guest": "Gast", @@ -118,6 +121,7 @@ "pad.impexp.importing": "Importiere …", "pad.impexp.confirmimport": "Das Importieren einer Datei überschreibt den aktuellen Text des Pads. Wollen Sie wirklich fortfahren?", "pad.impexp.convertFailed": "Wir können diese Datei nicht importieren. Bitte verwenden Sie ein anderes Dokumentformat oder übertragen Sie den Text manuell.", + "pad.impexp.padHasData": "Wir konnten diese Datei nicht importieren, da dieses Pad bereits Änderungen enthält. Bitte importiere sie in ein neues Pad.", "pad.impexp.uploadFailed": "Der Upload ist fehlgeschlagen. Bitte versuchen Sie es erneut.", "pad.impexp.importfailed": "Import fehlgeschlagen", "pad.impexp.copypaste": "Bitte kopieren und einfügen", diff --git a/src/locales/diq.json b/src/locales/diq.json index a98b3c14..81a55477 100644 --- a/src/locales/diq.json +++ b/src/locales/diq.json @@ -10,14 +10,14 @@ "pad.toolbar.bold.title": "Qalın (Ctrl-B)", "pad.toolbar.italic.title": "Namıte (Ctrl-I)", "pad.toolbar.underline.title": "Bınxetın (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Serxetın", - "pad.toolbar.ol.title": "Lista rêzkerdiye", - "pad.toolbar.ul.title": "Lista rêznêkerdiye", + "pad.toolbar.strikethrough.title": "Serxetın (Ctrl+5)", + "pad.toolbar.ol.title": "Lista rêzkerdiye (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista rêznêkerdiye (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Serrêze (TAB)", "pad.toolbar.unindent.title": "Teberdayış (Shift+TAB)", "pad.toolbar.undo.title": "Meke (Ctrl-Z)", "pad.toolbar.redo.title": "Fına bıke (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Rengê Nuştoğiê Arıstey", + "pad.toolbar.clearAuthorship.title": "Rengê Nuştoğiê Arıstey (Ctrl+Shift+C)", "pad.toolbar.timeslider.title": "Ğızagê zemani", "pad.toolbar.savedRevision.title": "Çımraviyarnayışi qeyd ke", "pad.toolbar.settings.title": "Sazkerdışi", @@ -43,12 +43,12 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "Gırediya.", "pad.modals.forcereconnect": "Mecbur anciya gırê de", "pad.modals.userdup": "Zewbina pençere de bi a", "pad.modals.unauth": "Selahiyetdar niyo", "pad.modals.initsocketfail": "Nêresneyêno ciyageyroği.", + "pad.modals.slowcommit.explanation": "Server cewab nêdano.", "pad.modals.deleted": "Esteriya.", "pad.modals.deleted.explanation": "Ena ped wedariye", "pad.share": "Na ped vıla ke", @@ -80,7 +80,7 @@ "timeslider.month.november": "Tışrino Peyên", "timeslider.month.december": "Kanun", "timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) zu: nuştoğ, zewbi: nustoği ]}", - "pad.userlist.entername": "Namey ğo cı kewe", + "pad.userlist.entername": "Nameyê xo cıkewe", "pad.userlist.unnamed": "Name nébıyo", "pad.userlist.guest": "Meyman", "pad.userlist.deny": "Red ke", diff --git a/src/locales/dsb.json b/src/locales/dsb.json index a24536d4..0600be90 100644 --- a/src/locales/dsb.json +++ b/src/locales/dsb.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Móžoš jano z fprmatow lutnego teksta abo z HTML-formata importěrowaś. Za wěcej rozšyrjone importěrowańske funkcije <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\">instalěruj pšosym Abiword</a>.", "pad.modals.connected": "Zwězany.", "pad.modals.reconnecting": "Zwězujo se znowego z twójim zapisnikom...", diff --git a/src/locales/el.json b/src/locales/el.json index 0c334c1c..f18c71e4 100644 --- a/src/locales/el.json +++ b/src/locales/el.json @@ -13,14 +13,14 @@ "pad.toolbar.bold.title": "Έντονη (Ctrl-B)", "pad.toolbar.italic.title": "Πλάγια (Ctrl-I)", "pad.toolbar.underline.title": "Υπογράμμιση (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Διακριτή διαγραφή", - "pad.toolbar.ol.title": "Ταξινομημένη λίστα", - "pad.toolbar.ul.title": "Λίστα χωρίς σειρά", + "pad.toolbar.strikethrough.title": "Διακριτή διαγραφή (Ctrl+5)", + "pad.toolbar.ol.title": "Ταξινομημένη λίστα (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Λίστα χωρίς σειρά (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Αριστερά εσοχή (TAB)", "pad.toolbar.unindent.title": "Δεξιά εσοχή (Shift+TAB)", "pad.toolbar.undo.title": "Αναίρεση (Ctrl-Z)", "pad.toolbar.redo.title": "Επανάληψη (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Εκκαθάριση των χρωμάτων των συντακτών", + "pad.toolbar.clearAuthorship.title": "Εκκαθάριση των χρωμάτων των συντακτών (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Εισαγωγή/Εξαγωγή από/σε διαφορετικούς τύπους αρχείων", "pad.toolbar.timeslider.title": "Χρονοδιάγραμμα", "pad.toolbar.savedRevision.title": "Αποθήκευση Αναθεώρησης", @@ -30,12 +30,14 @@ "pad.colorpicker.save": "Αποθήκευση", "pad.colorpicker.cancel": "Άκυρο", "pad.loading": "Φόρτωση...", + "pad.noCookie": "Το cookie δεν βρέθηκε. Παρακαλώ επιτρέψτε τα cookies στον περιηγητή σας!", "pad.passwordRequired": "Χρειάζεστε κωδικό πρόσβασης για πρόσβαση σε αυτό το pad", "pad.permissionDenied": "Δεν έχετε δικαίωμα πρόσβασης σε αυτό το pad", "pad.wrongPassword": "Ο κωδικός σας ήταν λανθασμένος", "pad.settings.padSettings": "Ρυθμίσεις Pad", "pad.settings.myView": "Η προβολή μου", "pad.settings.stickychat": "Να είναι πάντα ορατή η συνομιλία", + "pad.settings.chatandusers": "Εμφάνιση Συνομιλίας και Χρηστών", "pad.settings.colorcheck": "Χρώματα συντάκτη", "pad.settings.linenocheck": "Αριθμοί γραμμών", "pad.settings.rtlcheck": "Διαβάζεται το περιεχόμενο από δεξιά προς τα αριστερά;", @@ -48,19 +50,19 @@ "pad.importExport.import": "Αποστολή οποιουδήποτε αρχείου κειμένου ή εγγράφου", "pad.importExport.importSuccessful": "Επιτυχής!", "pad.importExport.export": "Εξαγωγή τρέχοντος pad ως:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Απλό κείμενο", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Μορφή Open Document)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Μπορείτε να κάνετε εισαγωγή απλού κειμένου ή μορφής html. Για πιο προηγμένες δυνατότητες εισαγωγής παρακαλώ <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\">εγκαταστήστε το abiword</a>.", "pad.modals.connected": "Συνδεμένοι.", "pad.modals.reconnecting": "Επανασύνδεση στο pad σας...", "pad.modals.forcereconnect": "Επιβολή επανασύνδεσης", "pad.modals.userdup": "Ανοιγμένο σε άλλο παράθυρο", "pad.modals.userdup.explanation": "Αυτό το pad φαίνεται να είναι ανοιχτό σε περισσότερα από ένα παράθυρο του προγράμματος περιήγησης σε αυτόν τον υπολογιστή.", - "pad.modals.userdup.advice": "Επανασύνδεση για να χρησιμοποιήσετε αυτό το παράθυρο.", + "pad.modals.userdup.advice": "Επανασυνδεθείτε για να χρησιμοποιήσετε αυτό το παράθυρο.", "pad.modals.unauth": "Δεν επιτρέπεται", "pad.modals.unauth.explanation": "Τα δικαιώματά σας άλλαξαν όσο βλέπατε αυτήν τη σελίδα. Δοκιμάστε να επανασυνδεθείτε.", "pad.modals.looping.explanation": "Υπάρχουν προβλήματα επικοινωνίας με τον διακομιστή συγχρονισμού.", @@ -76,7 +78,7 @@ "pad.modals.corruptPad.cause": "Αυτό μπορεί να οφείλεται σε ένα λάθος στη ρύθμιση του διακομιστή ή κάποια άλλη απρόβλεπτη συμπεριφορά. Παρακαλώ επικοινωνήστε με τον διαχειριστή της υπηρεσίας.", "pad.modals.deleted": "Διεγράφη.", "pad.modals.deleted.explanation": "Αυτό το pad έχει καταργηθεί.", - "pad.modals.disconnected": "Έχετε αποσυνδεθεί.", + "pad.modals.disconnected": "Είστε αποσυνδεδεμένοι.", "pad.modals.disconnected.explanation": "Χάθηκε η σύνδεση με τον διακομιστή", "pad.modals.disconnected.cause": "Ο διακομιστής μπορεί να μην είναι διαθέσιμος. Παρακαλούμε ειδοποιήστε τον διαχειριστή της υπηρεσίας εάν εξακολουθεί να συμβαίνει αυτό.", "pad.share": "Μοιραστείτε αυτό το pad", @@ -109,6 +111,7 @@ "timeslider.month.december": "Δεκεμβρίου", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: ανώνυμος συντάκτης, other: ανώνυμοι συντάκτες]}", "pad.savedrevs.marked": "Αυτή η έκδοση επισημάνθηκε ως αποθηκευμένη έκδοση", + "pad.savedrevs.timeslider": "Μπορείτε να δείτε αποθηκευμένες αναθεωρήσεις στο χρονοδιάγραμμα", "pad.userlist.entername": "Εισάγετε το όνομά σας", "pad.userlist.unnamed": "ανώνυμος", "pad.userlist.guest": "Επισκέπτης", @@ -119,6 +122,7 @@ "pad.impexp.importing": "Εισάγεται...", "pad.impexp.confirmimport": "Η εισαγωγή ενός αρχείου θα αντικαταστήσει το κείμενο του pad. Είστε βέβαιοι ότι θέλετε να συνεχίσετε;", "pad.impexp.convertFailed": "Δεν καταφέραμε να εισάγουμε αυτό το αρχείο. Παρακαλώ χρησιμοποιήστε διαφορετικό τύπο αρχείου ή αντιγράψτε και επικολλήστε χειροκίνητα", + "pad.impexp.padHasData": "Δεν μπορέσαμε να εισάγουμε το αρχείο επειδή το Pad είχε ήδη αλλαγές. Παρακαλούμε εισαγάγετε το αρχείο σε νέο pad", "pad.impexp.uploadFailed": "Η αποστολή απέτυχε, παρακαλώ προσπαθήστε ξανά", "pad.impexp.importfailed": "Η εισαγωγή απέτυχε", "pad.impexp.copypaste": "Παρακαλώ αντιγράψτε και επικολλήστε", diff --git a/src/locales/en-gb.json b/src/locales/en-gb.json new file mode 100644 index 00000000..258b4331 --- /dev/null +++ b/src/locales/en-gb.json @@ -0,0 +1,127 @@ +{ + "@metadata": { + "authors": [ + "Chase me ladies, I'm the Cavalry", + "Shirayuki" + ] + }, + "index.newPad": "New Pad", + "index.createOpenPad": "or create/open a Pad with the name:", + "pad.toolbar.bold.title": "Bold (Ctrl+B)", + "pad.toolbar.italic.title": "Italic (Ctrl+I)", + "pad.toolbar.underline.title": "Underline (Ctrl+U)", + "pad.toolbar.strikethrough.title": "Strikethrough (Ctrl+5)", + "pad.toolbar.ol.title": "Ordered list (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Unordered List (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Indent (Tab)", + "pad.toolbar.unindent.title": "Outdent (Shift+Tab)", + "pad.toolbar.undo.title": "Undo (Ctrl+Z)", + "pad.toolbar.redo.title": "Redo (Ctrl+Y)", + "pad.toolbar.clearAuthorship.title": "Clear Authorship Colours (Ctrl+Shift+C)", + "pad.toolbar.import_export.title": "Import/Export from/to different file formats", + "pad.toolbar.timeslider.title": "Timeslider", + "pad.toolbar.savedRevision.title": "Save Revision", + "pad.toolbar.settings.title": "Settings", + "pad.toolbar.embed.title": "Share and Embed this pad", + "pad.toolbar.showusers.title": "Show the users on this pad", + "pad.colorpicker.save": "Save", + "pad.colorpicker.cancel": "Cancel", + "pad.loading": "Loading...", + "pad.noCookie": "Cookie could not be found. Please allow cookies in your browser!", + "pad.passwordRequired": "You need a password to access this pad", + "pad.permissionDenied": "You do not have permission to access this pad", + "pad.wrongPassword": "Your password was wrong", + "pad.settings.padSettings": "Pad Settings", + "pad.settings.myView": "My View", + "pad.settings.stickychat": "Chat always on screen", + "pad.settings.chatandusers": "Show Chat and Users", + "pad.settings.colorcheck": "Authorship colours", + "pad.settings.linenocheck": "Line numbers", + "pad.settings.rtlcheck": "Read content from right to left?", + "pad.settings.fontType": "Font type:", + "pad.settings.fontType.normal": "Normal", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "Global View", + "pad.settings.language": "Language:", + "pad.importExport.import_export": "Import/Export", + "pad.importExport.import": "Upload any text file or document", + "pad.importExport.importSuccessful": "Successful!", + "pad.importExport.export": "Export current pad as:", + "pad.importExport.exportetherpad": "Etherpad", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "Plain text", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.abiword.innerHTML": "You only can import from plain text or HTML formats. For more advanced import features please <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\">install abiword</a>.", + "pad.modals.connected": "Connected.", + "pad.modals.reconnecting": "Reconnecting to your pad..", + "pad.modals.forcereconnect": "Force reconnect", + "pad.modals.userdup": "Opened in another window", + "pad.modals.userdup.explanation": "This pad seems to be opened in more than one browser window on this computer.", + "pad.modals.userdup.advice": "Reconnect to use this window instead.", + "pad.modals.unauth": "Not authorised", + "pad.modals.unauth.explanation": "Your permissions have changed while viewing this page. Try to reconnect.", + "pad.modals.looping.explanation": "There are communication problems with the synchronisation server.", + "pad.modals.looping.cause": "Perhaps you connected through an incompatible firewall or proxy.", + "pad.modals.initsocketfail": "Server is unreachable.", + "pad.modals.initsocketfail.explanation": "Couldn't connect to the synchronisation server.", + "pad.modals.initsocketfail.cause": "This is probably due to a problem with your browser or your internet connection.", + "pad.modals.slowcommit.explanation": "The server is not responding.", + "pad.modals.slowcommit.cause": "This could be due to problems with network connectivity.", + "pad.modals.badChangeset.explanation": "An edit you have made was classified illegal by the synchronisation server.", + "pad.modals.badChangeset.cause": "This could be due to a wrong server configuration or some other unexpected behaviour. Please contact the service administrator, if you feel this is an error. Try to reconnect in order to continue editing.", + "pad.modals.corruptPad.explanation": "The pad you are trying to access is corrupt.", + "pad.modals.corruptPad.cause": "This may be due to a wrong server configuration or some other unexpected behaviour. Please contact the service administrator.", + "pad.modals.deleted": "Deleted.", + "pad.modals.deleted.explanation": "This pad has been removed.", + "pad.modals.disconnected": "You have been disconnected.", + "pad.modals.disconnected.explanation": "The connection to the server was lost", + "pad.modals.disconnected.cause": "The server may be unavailable. Please notify the service administrator if this continues to happen.", + "pad.share": "Share this pad", + "pad.share.readonly": "Read only", + "pad.share.link": "Link", + "pad.share.emebdcode": "Embed URL", + "pad.chat": "Chat", + "pad.chat.title": "Open the chat for this pad.", + "pad.chat.loadmessages": "Load more messages", + "timeslider.pageTitle": "{{appTitle}} Timeslider", + "timeslider.toolbar.returnbutton": "Return to pad", + "timeslider.toolbar.authors": "Authors:", + "timeslider.toolbar.authorsList": "No Authors", + "timeslider.toolbar.exportlink.title": "Export", + "timeslider.exportCurrent": "Export current version as:", + "timeslider.version": "Version {{version}}", + "timeslider.saved": "Saved {{month}} {{day}}, {{year}}", + "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.month.january": "January", + "timeslider.month.february": "February", + "timeslider.month.march": "March", + "timeslider.month.april": "April", + "timeslider.month.may": "May", + "timeslider.month.june": "June", + "timeslider.month.july": "July", + "timeslider.month.august": "August", + "timeslider.month.september": "September", + "timeslider.month.october": "October", + "timeslider.month.november": "November", + "timeslider.month.december": "December", + "timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}", + "pad.savedrevs.marked": "This revision is now marked as a saved revision", + "pad.savedrevs.timeslider": "You can see saved revisions by visiting the timeslider", + "pad.userlist.entername": "Enter your name", + "pad.userlist.unnamed": "unnamed", + "pad.userlist.guest": "Guest", + "pad.userlist.deny": "Deny", + "pad.userlist.approve": "Approve", + "pad.editbar.clearcolors": "Clear authorship colours on entire document?", + "pad.impexp.importbutton": "Import Now", + "pad.impexp.importing": "Importing...", + "pad.impexp.confirmimport": "Importing a file will overwrite the current text of the pad. Are you sure you want to proceed?", + "pad.impexp.convertFailed": "We were not able to import this file. Please use a different document format or copy & paste manually", + "pad.impexp.padHasData": "We were not able to import this file because this Pad has already had changes, please import to a new pad", + "pad.impexp.uploadFailed": "The upload failed, please try again", + "pad.impexp.importfailed": "Import failed", + "pad.impexp.copypaste": "Please copy & paste", + "pad.impexp.exportdisabled": "Exporting as {{type}} format is disabled. Please contact your system administrator for details." +} diff --git a/src/locales/en.json b/src/locales/en.json index d9b98389..3e16c5de 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -12,7 +12,7 @@ "pad.toolbar.unindent.title": "Outdent (Shift+TAB)", "pad.toolbar.undo.title": "Undo (Ctrl+Z)", "pad.toolbar.redo.title": "Redo (Ctrl+Y)", - "pad.toolbar.clearAuthorship.title": "Clear Authorship Colors", + "pad.toolbar.clearAuthorship.title": "Clear Authorship Colors (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Import/Export from/to different file formats", "pad.toolbar.timeslider.title": "Timeslider", "pad.toolbar.savedRevision.title": "Save Revision", @@ -24,6 +24,7 @@ "pad.colorpicker.cancel": "Cancel", "pad.loading": "Loading...", + "pad.noCookie": "Cookie could not be found. Please allow cookies in your browser!", "pad.passwordRequired": "You need a password to access this pad", "pad.permissionDenied": "You do not have permission to access this pad", "pad.wrongPassword": "Your password was wrong", @@ -31,12 +32,30 @@ "pad.settings.padSettings": "Pad Settings", "pad.settings.myView": "My View", "pad.settings.stickychat": "Chat always on screen", + "pad.settings.chatandusers": "Show Chat and Users", "pad.settings.colorcheck": "Authorship colors", "pad.settings.linenocheck": "Line numbers", "pad.settings.rtlcheck": "Read content from right to left?", "pad.settings.fontType": "Font type:", "pad.settings.fontType.normal": "Normal", + "pad.settings.fontType.opendyslexic": "Open Dyslexic", "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.fontType.comicsans": "Comic Sans", + "pad.settings.fontType.couriernew": "Courier New", + "pad.settings.fontType.georgia": "Georgia", + "pad.settings.fontType.impact": "Impact", + "pad.settings.fontType.lucida": "Lucida", + "pad.settings.fontType.lucidasans": "Lucida Sans", + "pad.settings.fontType.palatino": "Palatino", + "pad.settings.fontType.tahoma": "Tahoma", + "pad.settings.fontType.timesnewroman": "Times New Roman", + "pad.settings.fontType.trebuchet": "Trebuchet", + "pad.settings.fontType.verdana": "Verdana", + "pad.settings.fontType.symbol": "Symbol", + "pad.settings.fontType.webdings": "Webdings", + "pad.settings.fontType.wingdings": "Wingdings", + "pad.settings.fontType.sansserif": "Sans Serif", + "pad.settings.fontType.serif": "Serif", "pad.settings.globalView": "Global View", "pad.settings.language": "Language:", @@ -44,12 +63,12 @@ "pad.importExport.import": "Upload any text file or document", "pad.importExport.importSuccessful": "Successful!", "pad.importExport.export": "Export current pad as:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Plain text", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "You only can import from plain text or HTML formats. For more advanced import features please <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\">install abiword</a>.", "pad.modals.connected": "Connected.", @@ -103,6 +122,10 @@ "timeslider.version": "Version {{version}}", "timeslider.saved": "Saved {{month}} {{day}}, {{year}}", + "timeslider.playPause": "Playback / Pause Pad Contents", + "timeslider.backRevision":"Go back a revision in this Pad", + "timeslider.forwardRevision":"Go forward a revision in this Pad", + "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "January", "timeslider.month.february": "February", @@ -119,6 +142,7 @@ "timeslider.unnamedauthors": "{{num}} unnamed {[plural(num) one: author, other: authors ]}", "pad.savedrevs.marked": "This revision is now marked as a saved revision", + "pad.savedrevs.timeslider": "You can see saved revisions by visiting the timeslider", "pad.userlist.entername": "Enter your name", "pad.userlist.unnamed": "unnamed", "pad.userlist.guest": "Guest", @@ -130,6 +154,7 @@ "pad.impexp.importing": "Importing...", "pad.impexp.confirmimport": "Importing a file will overwrite the current text of the pad. Are you sure you want to proceed?", "pad.impexp.convertFailed": "We were not able to import this file. Please use a different document format or copy paste manually", + "pad.impexp.padHasData": "We were not able to import this file because this Pad has already had changes, please import to a new pad", "pad.impexp.uploadFailed": "The upload failed, please try again", "pad.impexp.importfailed": "Import failed", "pad.impexp.copypaste": "Please copy paste", diff --git a/src/locales/es.json b/src/locales/es.json index 62e255ea..21eb60a7 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -10,7 +10,8 @@ "VegaDark", "Vivaelcelta", "Xuacu", - "Macofe" + "Macofe", + "Fitoschido" ] }, "index.newPad": "Nuevo pad", @@ -25,7 +26,7 @@ "pad.toolbar.unindent.title": "Eliminar sangría (Shift+TAB)", "pad.toolbar.undo.title": "Deshacer (Ctrl-Z)", "pad.toolbar.redo.title": "Rehacer (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Eliminar los colores de autoría", + "pad.toolbar.clearAuthorship.title": "Eliminar los colores de autoría (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/Exportar a diferentes formatos de archivos", "pad.toolbar.timeslider.title": "Línea de tiempo", "pad.toolbar.savedRevision.title": "Guardar revisión", @@ -35,12 +36,14 @@ "pad.colorpicker.save": "Guardar", "pad.colorpicker.cancel": "Cancelar", "pad.loading": "Cargando...", + "pad.noCookie": "La cookie no se pudo encontrar. ¡Por favor, habilita las cookies en tu navegador!", "pad.passwordRequired": "Necesitas una contraseña para acceder a este pad", "pad.permissionDenied": "No tienes permiso para acceder a este pad", "pad.wrongPassword": "La contraseña era incorrecta", "pad.settings.padSettings": "Configuración del pad", "pad.settings.myView": "Preferencias personales", "pad.settings.stickychat": "Chat siempre en pantalla", + "pad.settings.chatandusers": "Mostrar el chat y los usuarios", "pad.settings.colorcheck": "Colores de autoría", "pad.settings.linenocheck": "Números de línea", "pad.settings.rtlcheck": "¿Leer contenido de derecha a izquierda?", @@ -53,13 +56,13 @@ "pad.importExport.import": "Subir cualquier texto o documento", "pad.importExport.importSuccessful": "¡Éxito!", "pad.importExport.export": "Exporta el pad actual como:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", - "pad.importExport.exportplain": "Texto plano", + "pad.importExport.exportplain": "Texto sin formato", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", - "pad.importExport.abiword.innerHTML": "Sólo puedes importar formatos de texto plano o html. Para funciones más avanzadas instala <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\">abiword</a>.", + "pad.importExport.abiword.innerHTML": "Solo es posible importar texto sin formato o en HTML. Para obtener funciones de importación más avanzadas es necesario <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\">instalar AbiWord</a>.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando a tu pad..", "pad.modals.forcereconnect": "Forzar reconexión", @@ -100,20 +103,21 @@ "timeslider.version": "Versión {{version}}", "timeslider.saved": "Guardado el {{day}} de {{month}} de {{year}}", "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", - "timeslider.month.january": "Enero", - "timeslider.month.february": "Febrero", - "timeslider.month.march": "Marzo", - "timeslider.month.april": "Abril", - "timeslider.month.may": "Mayo", - "timeslider.month.june": "Junio", - "timeslider.month.july": "Julio", - "timeslider.month.august": "Agosto", - "timeslider.month.september": "Septiembre", - "timeslider.month.october": "Octubre", - "timeslider.month.november": "Noviembre", - "timeslider.month.december": "Diciembre", + "timeslider.month.january": "enero", + "timeslider.month.february": "febrero", + "timeslider.month.march": "marzo", + "timeslider.month.april": "abril", + "timeslider.month.may": "mayo", + "timeslider.month.june": "junio", + "timeslider.month.july": "julio", + "timeslider.month.august": "agosto", + "timeslider.month.september": "septiembre", + "timeslider.month.october": "octubre", + "timeslider.month.november": "noviembre", + "timeslider.month.december": "diciembre", "timeslider.unnamedauthors": "{{num}} {[ plural(num) one: autor desconocido, other: autores desconocidos]}", "pad.savedrevs.marked": "Revisión guardada", + "pad.savedrevs.timeslider": "Puedes ver revisiones guardadas visitando la línea de tiempo", "pad.userlist.entername": "Escribe tu nombre", "pad.userlist.unnamed": "anónimo", "pad.userlist.guest": "Invitado", @@ -124,6 +128,7 @@ "pad.impexp.importing": "Importando...", "pad.impexp.confirmimport": "Al importar un archivo se borrará el contenido actual del pad. ¿Estás seguro de que quieres continuar?", "pad.impexp.convertFailed": "No pudimos importar este archivo. Inténtalo con un formato diferente o copia y pega manualmente.", + "pad.impexp.padHasData": "No hemos podido importar este archivo porque esta almohadilla ya ha tenido cambios, por favor, importa a una nueva almohadilla", "pad.impexp.uploadFailed": "El envío falló. Intentalo de nuevo.", "pad.impexp.importfailed": "Fallo al importar", "pad.impexp.copypaste": "Intenta copiar y pegar", diff --git a/src/locales/et.json b/src/locales/et.json index 29cd92b9..3ea8b3e6 100644 --- a/src/locales/et.json +++ b/src/locales/et.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Paraku on ainult lihttekstis voi HTML-vormingus dokumentide importimine võimaldatud. Rohkem võimaluste jaoks peab <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\">paigaldama abiword</a>.", "pad.modals.connected": "Ühendatud.", "pad.modals.reconnecting": "Proovitakse luua ühendus klade juurde...", diff --git a/src/locales/eu.json b/src/locales/eu.json index 5aa3ec4e..9cd06076 100644 --- a/src/locales/eu.json +++ b/src/locales/eu.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Theklan" + "Theklan", + "Subi" ] }, "index.newPad": "Pad berria", @@ -44,12 +45,12 @@ "pad.importExport.import": "Igo edozein testu fitxategi edo dokumentu", "pad.importExport.importSuccessful": "Arrakastatsua!", "pad.importExport.export": "Oraingo pad hau honela esportatu:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Testu laua", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DocuWiki", "pad.importExport.abiword.innerHTML": "Testu laua edo html formatudun testuak bakarrik inporta ditzakezu. Aurreratuagoak diren inportazio aukerak izateko <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\">abiword instala ezazu</a>.", "pad.modals.connected": "Konektatuta.", "pad.modals.reconnecting": "Zure pad-era birkonektatu...", diff --git a/src/locales/fa.json b/src/locales/fa.json index 3b421972..53fb55b2 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -14,14 +14,14 @@ "pad.toolbar.bold.title": "پررنگ (Ctrl-B)", "pad.toolbar.italic.title": "کج (Ctrl-I)", "pad.toolbar.underline.title": "زیرخط (Ctrl-U)", - "pad.toolbar.strikethrough.title": "خط خورده", - "pad.toolbar.ol.title": "فهرست مرتب شده", - "pad.toolbar.ul.title": "فهرست مرتب نشده", + "pad.toolbar.strikethrough.title": "خط خورده (Ctrl+5)", + "pad.toolbar.ol.title": "فهرست مرتب شده (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "فهرست مرتب نشده (Ctrl+Shift+L)", "pad.toolbar.indent.title": "تورفتگی (TAB)", "pad.toolbar.unindent.title": "بیرون رفتگی (Shift+TAB)", "pad.toolbar.undo.title": "باطلکردن (Ctrl-Z)", "pad.toolbar.redo.title": "از نو (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "پاککردن رنگهای نویسندگی", + "pad.toolbar.clearAuthorship.title": "پاککردن رنگهای نویسندگی (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "درونریزی/برونریزی از/به قالبهای مختلف", "pad.toolbar.timeslider.title": "لغزندهٔ زمان", "pad.toolbar.savedRevision.title": "ذخیرهسازی نسخه", @@ -31,6 +31,7 @@ "pad.colorpicker.save": "ذخیره", "pad.colorpicker.cancel": "لغو", "pad.loading": "در حال بارگذاری...", + "pad.noCookie": "کوکی یافت نشد. لطفاً اجازهٔ اجرای کوکی در مروگرتان را بدهید!", "pad.passwordRequired": "برای دسترسی به این دفترچه یادداشت نیاز به یک گذرواژه دارید", "pad.permissionDenied": "شما اجازهی دسترسی به این دفترچه یادداشت را ندارید", "pad.wrongPassword": "گذرواژهی شما درست نیست", @@ -49,12 +50,12 @@ "pad.importExport.import": "بارگذاری پروندهی متنی یا سند", "pad.importExport.importSuccessful": "موفقیت آمیز بود!", "pad.importExport.export": "برونریزی این دفترچه یادداشت با قالب:", + "pad.importExport.exportetherpad": "اترپد", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "متن ساده", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (قالب سند باز)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "شما تنها میتوانید از قالب متن ساده یا اچتیامال درونریزی کنید. برای بیشتر شدن ویژگیهای درونریزی پیشرفته <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\">AbiWord</a> را نصب کنید.", "pad.modals.connected": "متصل شد.", "pad.modals.reconnecting": "در حال اتصال دوباره به دفترچه یادداشت شما..", @@ -120,6 +121,7 @@ "pad.impexp.importing": "در حال درونریزی...", "pad.impexp.confirmimport": "با درونریزی یک پرونده نوشتهٔ کنونی دفترچه پاک میشود. آیا میخواهید ادامه دهید؟", "pad.impexp.convertFailed": "ما نمیتوانیم این پرونده را درونریزی کنیم. خواهشمندیم قالب دیگری برای سندتان انتخاب کرده یا بصورت دستی آنرا کپی کنید", + "pad.impexp.padHasData": "امکان درونریز این پرونده نیست زیرا این پد تغییر کردهاست. لطفاً در پد جدید درونریزی کنید.", "pad.impexp.uploadFailed": "آپلود انجام نشد، دوباره تلاش کنید", "pad.impexp.importfailed": "درونریزی انجام نشد", "pad.impexp.copypaste": "کپی پیست کنید", diff --git a/src/locales/fi.json b/src/locales/fi.json index 656f0a21..25e4d084 100644 --- a/src/locales/fi.json +++ b/src/locales/fi.json @@ -9,7 +9,9 @@ "Stryn", "Tomi Toivio", "Veikk0.ma", - "VezonThunder" + "VezonThunder", + "Macofe", + "MrTapsa" ] }, "index.newPad": "Uusi muistio", @@ -17,14 +19,14 @@ "pad.toolbar.bold.title": "Lihavointi (Ctrl-B)", "pad.toolbar.italic.title": "Kursivointi (Ctrl-I)", "pad.toolbar.underline.title": "Alleviivaus (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Yliviivaus", - "pad.toolbar.ol.title": "Numeroitu lista", - "pad.toolbar.ul.title": "Numeroimaton lista", + "pad.toolbar.strikethrough.title": "Yliviivaus (Ctrl+5)", + "pad.toolbar.ol.title": "Numeroitu lista (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Numeroimaton lista (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Sisennä (TAB)", "pad.toolbar.unindent.title": "Ulonna (Shift+TAB)", "pad.toolbar.undo.title": "Kumoa (Ctrl-Z)", "pad.toolbar.redo.title": "Tee uudelleen (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Poista kirjoittajavärit", + "pad.toolbar.clearAuthorship.title": "Poista kirjoittajavärit (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Tuo tai vie eri tiedostomuodoista tai -muotoihin", "pad.toolbar.timeslider.title": "Aikajana", "pad.toolbar.savedRevision.title": "Tallenna muutos", @@ -34,6 +36,7 @@ "pad.colorpicker.save": "Tallenna", "pad.colorpicker.cancel": "Peru", "pad.loading": "Ladataan…", + "pad.noCookie": "Evästettä ei löytynyt. Ole hyvä, ja salli evästeet selaimessasi!", "pad.passwordRequired": "Tämä muistio on suojattu salasanalla.", "pad.permissionDenied": "Käyttöoikeutesi eivät riitä tämän muistion käyttämiseen.", "pad.wrongPassword": "Väärä salasana", @@ -57,7 +60,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Tuonti on tuettu vain HTML- ja raakatekstitiedostoista. Lisätietoja tuonnin lisäasetuksista on sivulla <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\">install abiword</a>.", "pad.modals.connected": "Yhdistetty.", "pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen...", diff --git a/src/locales/fo.json b/src/locales/fo.json index cd72dec7..43e9d9f1 100644 --- a/src/locales/fo.json +++ b/src/locales/fo.json @@ -43,7 +43,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Opið Dokument Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Tú kanst bert innflyta frá einføldum teksti ella html formatum. Fyri funksjónir til innflytan fyri víðarikomin vinarliga <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\">installera abiword</a>.", "pad.modals.connected": "Tú hevur samband.", "pad.modals.reconnecting": "Roynir aftur at fáa samband við tín pad..", diff --git a/src/locales/fr.json b/src/locales/fr.json index b6b1a720..ba289e3b 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -17,7 +17,8 @@ "Stephane Cottin", "Tux-tn", "Maxim21", - "Boniface" + "Boniface", + "Macofe" ] }, "index.newPad": "Nouveau pad", @@ -32,7 +33,7 @@ "pad.toolbar.unindent.title": "Désindenter (Maj+TAB)", "pad.toolbar.undo.title": "Annuler (Ctrl-Z)", "pad.toolbar.redo.title": "Rétablir (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Effacer les couleurs identifiant les auteurs", + "pad.toolbar.clearAuthorship.title": "Effacer les couleurs identifiant les auteurs (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importer/Exporter de/vers un format de fichier différent", "pad.toolbar.timeslider.title": "Historique dynamique", "pad.toolbar.savedRevision.title": "Enregistrer la révision", @@ -42,12 +43,14 @@ "pad.colorpicker.save": "Enregistrer", "pad.colorpicker.cancel": "Annuler", "pad.loading": "Chargement…", + "pad.noCookie": "Le cookie n’a pas pu être trouvé. Veuillez autoriser les cookies dans votre navigateur !", "pad.passwordRequired": "Vous avez besoin d'un mot de passe pour accéder à ce pad", "pad.permissionDenied": "Il ne vous est pas permis d’accéder à ce pad", "pad.wrongPassword": "Votre mot de passe est incorrect", "pad.settings.padSettings": "Paramètres du pad", "pad.settings.myView": "Ma vue", "pad.settings.stickychat": "Toujours afficher le chat", + "pad.settings.chatandusers": "Afficher la discussion et les utilisateurs", "pad.settings.colorcheck": "Couleurs d’identification", "pad.settings.linenocheck": "Numéros de lignes", "pad.settings.rtlcheck": "Le contenu doit-il être lu de droite à gauche ?", @@ -60,12 +63,12 @@ "pad.importExport.import": "Charger un texte ou un document", "pad.importExport.importSuccessful": "Réussi !", "pad.importExport.export": "Exporter le pad actuel comme :", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Texte brut", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Vous ne pouvez importer que des formats texte brut ou html. Pour des fonctionnalités d'importation plus évoluées, veuillez <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\">installer abiword</a>.", "pad.modals.connected": "Connecté.", "pad.modals.reconnecting": "Reconnexion vers votre pad...", @@ -121,6 +124,7 @@ "timeslider.month.december": "décembre", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: auteur anonyme, other: auteurs anonymes ]}", "pad.savedrevs.marked": "Cette révision est maintenant marquée comme révision enregistrée", + "pad.savedrevs.timeslider": "Vous pouvez voir les révisions enregistrées en visitant l’ascenseur temporel", "pad.userlist.entername": "Entrez votre nom", "pad.userlist.unnamed": "anonyme", "pad.userlist.guest": "Invité", @@ -131,6 +135,7 @@ "pad.impexp.importing": "Import en cours...", "pad.impexp.confirmimport": "Importer un fichier écrasera le texte actuel du pad. Êtes-vous sûr de vouloir le faire ?", "pad.impexp.convertFailed": "Nous ne pouvons pas importer ce fichier. Veuillez utiliser un autre format de document ou faire un copier/coller manuel", + "pad.impexp.padHasData": "Nous n’avons pas pu importer ce fichier parce que ce bloc a déjà eu des modifications ; veuillez importer vers un nouveau bloc", "pad.impexp.uploadFailed": "Le téléchargement a échoué, veuillez réessayer", "pad.impexp.importfailed": "Échec de l'importation", "pad.impexp.copypaste": "Veuillez copier/coller", diff --git a/src/locales/gl.json b/src/locales/gl.json index 93855a8b..381296aa 100644 --- a/src/locales/gl.json +++ b/src/locales/gl.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Toliño" + "Toliño", + "Elisardojm" ] }, "index.newPad": "Novo documento", @@ -9,14 +10,14 @@ "pad.toolbar.bold.title": "Negra (Ctrl-B)", "pad.toolbar.italic.title": "Cursiva (Ctrl-I)", "pad.toolbar.underline.title": "Subliñar (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Riscar", - "pad.toolbar.ol.title": "Lista ordenada", - "pad.toolbar.ul.title": "Lista sen ordenar", + "pad.toolbar.strikethrough.title": "Riscar (Ctrl+5)", + "pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista sen ordenar (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Sangría (TAB)", "pad.toolbar.unindent.title": "Sen sangría (Maiús.+TAB)", "pad.toolbar.undo.title": "Desfacer (Ctrl-Z)", "pad.toolbar.redo.title": "Refacer (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Limpar as cores de identificación dos autores", + "pad.toolbar.clearAuthorship.title": "Limpar as cores de identificación dos autores (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/Exportar desde/a diferentes formatos de ficheiro", "pad.toolbar.timeslider.title": "Liña do tempo", "pad.toolbar.savedRevision.title": "Gardar a revisión", @@ -26,12 +27,14 @@ "pad.colorpicker.save": "Gardar", "pad.colorpicker.cancel": "Cancelar", "pad.loading": "Cargando...", + "pad.noCookie": "Non se puido atopar a cookie. Por favor, habilite as cookies no seu navegador!", "pad.passwordRequired": "Cómpre un contrasinal para acceder a este documento", "pad.permissionDenied": "Non ten permiso para acceder a este documento", "pad.wrongPassword": "O contrasinal era incorrecto", "pad.settings.padSettings": "Configuracións do documento", "pad.settings.myView": "A miña vista", "pad.settings.stickychat": "Chat sempre visible", + "pad.settings.chatandusers": "Mostrar o chat e os usuarios", "pad.settings.colorcheck": "Cores de identificación", "pad.settings.linenocheck": "Números de liña", "pad.settings.rtlcheck": "Quere ler o contido da dereita á esquerda?", @@ -44,12 +47,12 @@ "pad.importExport.import": "Cargar un ficheiro de texto ou documento", "pad.importExport.importSuccessful": "Correcto!", "pad.importExport.export": "Exportar o documento actual en formato:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Texto simple", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Só pode importar texto simple ou formatos HTML. Para obter máis información sobre as características de importación avanzadas <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\">instale abiword</a>.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando co seu documento...", @@ -105,6 +108,7 @@ "timeslider.month.december": "decembro", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anónimo, other: autores anónimos ]}", "pad.savedrevs.marked": "Esta revisión está agora marcada como revisión gardada", + "pad.savedrevs.timeslider": "Pode consultar as revisións gardadas visitando a liña do tempo", "pad.userlist.entername": "Insira o seu nome", "pad.userlist.unnamed": "anónimo", "pad.userlist.guest": "Convidado", @@ -115,6 +119,7 @@ "pad.impexp.importing": "Importando...", "pad.impexp.confirmimport": "A importación dun ficheiro ha sobrescribir o texto actual do documento. Está seguro de querer continuar?", "pad.impexp.convertFailed": "Non somos capaces de importar o ficheiro. Utilice un formato de documento diferente ou copie e pegue manualmente", + "pad.impexp.padHasData": "Non puidemos importar este ficheiro porque este documento xa sufriu cambios; importe a un novo documento.", "pad.impexp.uploadFailed": "Houbo un erro ao cargar o ficheiro; inténteo de novo", "pad.impexp.importfailed": "Fallou a importación", "pad.impexp.copypaste": "Copie e pegue", diff --git a/src/locales/he.json b/src/locales/he.json index 7d7f2b78..4222e153 100644 --- a/src/locales/he.json +++ b/src/locales/he.json @@ -19,7 +19,7 @@ "pad.toolbar.unindent.title": "צמצום הזחה (שיפט–טאב)", "pad.toolbar.undo.title": "ביטול (Ctrl-Z)", "pad.toolbar.redo.title": "ביצוע מחדש", - "pad.toolbar.clearAuthorship.title": "ניקוי צבעים", + "pad.toolbar.clearAuthorship.title": "ניקוי צבעי כותבים (Ctrl-Shift-C)", "pad.toolbar.import_export.title": "ייבוא/ייצוא בתסדירי קבצים שונים", "pad.toolbar.timeslider.title": "גולל זמן", "pad.toolbar.savedRevision.title": "שמירת גרסה", @@ -29,12 +29,14 @@ "pad.colorpicker.save": "שמירה", "pad.colorpicker.cancel": "ביטול", "pad.loading": "טעינה...", + "pad.noCookie": "העוגייה לא נמצאה. נא לאפשר עוגיות בדפדפן שלך!", "pad.passwordRequired": "דרושה ססמה כדי לגשת לפנקס הזה", "pad.permissionDenied": "אין לך הרשאה לגשת לפנקס הזה", "pad.wrongPassword": "ססמתך הייתה שגויה", "pad.settings.padSettings": "הגדרות פנקס", "pad.settings.myView": "התצוגה שלי", "pad.settings.stickychat": "השיחה תמיד על המסך", + "pad.settings.chatandusers": "הצגת צ'אט ומשתמשים", "pad.settings.colorcheck": "צביעה לפי מחבר", "pad.settings.linenocheck": "מספרי שורות", "pad.settings.rtlcheck": "לקרוא את התוכן מימין לשמאל?", @@ -47,12 +49,12 @@ "pad.importExport.import": "העלאת כל קובץ טקסט או מסמך", "pad.importExport.importSuccessful": "זה עבד!", "pad.importExport.export": "ייצוא הפנקס הנוכחי בתור:", + "pad.importExport.exportetherpad": "את'רפד", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "טקסט רגיל", "pad.importExport.exportword": "מיקרוסופט וורד", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "באפשרותך לייבא מטקסט פשוט או מ־HTML. לאפשרויות ייבוא מתקדמות יותר יש <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\">להתקין את abiword</a>.", "pad.modals.connected": "מחובר.", "pad.modals.reconnecting": "מתבצע חיבור מחדש...", @@ -108,6 +110,7 @@ "timeslider.month.december": "דצמבר", "timeslider.unnamedauthors": "{[plural(num) one: יוצר אחד, other: {{num}} יוצרים ]} ללא שם", "pad.savedrevs.marked": "גרסה זו מסומנת כגרסה שמורה", + "pad.savedrevs.timeslider": "אפשר להציג גרסאות שמורות באמצעות ביקור בגולל הזמן", "pad.userlist.entername": "נא להזין את שמך", "pad.userlist.unnamed": "ללא שם", "pad.userlist.guest": "אורח", @@ -118,6 +121,7 @@ "pad.impexp.importing": "ייבוא...", "pad.impexp.confirmimport": "ייבוא של קובץ יבטל את הטקסט הנוכחי בפנקס. האם ברצונך להמשיך?", "pad.impexp.convertFailed": "לא הצלחנו לייבא את הקובץ הזה. נא להשתמש בתסדיר מסמך שונה או להעתיק ולהדביק ידנית", + "pad.impexp.padHasData": "לא הצלחנו לייבא את הקובץ הזה, כי בפנקס הזה כבר יש שינויים. נא לייבא לפנקס חדש.", "pad.impexp.uploadFailed": "ההעלאה נכשלה, נא לנסות שוב", "pad.impexp.importfailed": "הייבוא נכשל", "pad.impexp.copypaste": "נא להעתיק ולהדביק", diff --git a/src/locales/hrx.json b/src/locales/hrx.json index 808a1297..74630efa 100644 --- a/src/locales/hrx.json +++ b/src/locales/hrx.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Sie können nur aus Klartext oder HTML-Formaten importieren. Für mehr erweiterte Importfunktionen <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\">installieren Sie bitte abiword</a>.", "pad.modals.connected": "Verbünd (konnektiert).", "pad.modals.reconnecting": "Wiederherstelle von der Verbinnung …", diff --git a/src/locales/hsb.json b/src/locales/hsb.json index c86f6078..ddf3cc4a 100644 --- a/src/locales/hsb.json +++ b/src/locales/hsb.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Móžeš jenož z formatow luteho teksta abo z HTML-formata importować. Za bóle rozšěrjene importowanske funkcije <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\">instaluj prošu Abiword</a>.", "pad.modals.connected": "Zwjazany.", "pad.modals.reconnecting": "Zwjazuje so znowa z twojim zapisnikom...", diff --git a/src/locales/hu.json b/src/locales/hu.json index 2cbf5749..287e7954 100644 --- a/src/locales/hu.json +++ b/src/locales/hu.json @@ -13,14 +13,14 @@ "pad.toolbar.bold.title": "Félkövér (Ctrl-B)", "pad.toolbar.italic.title": "Dőlt (Ctrl-I)", "pad.toolbar.underline.title": "Aláhúzás (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Áthúzás", - "pad.toolbar.ol.title": "Számozott lista", - "pad.toolbar.ul.title": "Számozatlan lista", + "pad.toolbar.strikethrough.title": "Áthúzás (Ctrl+5)", + "pad.toolbar.ol.title": "Számozott lista (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Számozatlan lista (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Behúzás növelése (TAB)", "pad.toolbar.unindent.title": "Behúzás csökkentése (Shift+TAB)", "pad.toolbar.undo.title": "Visszavonás (Ctrl-Z)", "pad.toolbar.redo.title": "Újra (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Szerzők színezésének kikapcsolása", + "pad.toolbar.clearAuthorship.title": "Szerzők színezésének kikapcsolása (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importálás/exportálás különböző fájlformátumokból/ba", "pad.toolbar.timeslider.title": "Időcsúszka", "pad.toolbar.savedRevision.title": "Revízió mentése", @@ -30,6 +30,7 @@ "pad.colorpicker.save": "Mentés", "pad.colorpicker.cancel": "Mégsem", "pad.loading": "Betöltés…", + "pad.noCookie": "Nem található a süti. Engedélyezd a böngésződben a sütik használatát!", "pad.passwordRequired": "Jelszóra van szükséged ezen notesz eléréséhez", "pad.permissionDenied": "Nincs engedélyed ezen notesz eléréséhez", "pad.wrongPassword": "A jelszó rossz volt", @@ -48,12 +49,12 @@ "pad.importExport.import": "Tetszőleges szövegfájl vagy dokumentum feltöltése", "pad.importExport.importSuccessful": "Siker!", "pad.importExport.export": "Jelenlegi notesz exportálása így:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Sima szöveg", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document formátum)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Csak szöveges, vagy HTML formátumokból importálhatsz. A speciális importálási funkciókért kérjük <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\">telepítsd az abiword-öt</a>.", "pad.modals.connected": "Kapcsolódva.", "pad.modals.reconnecting": "Újrakapcsolódás a noteszhez...", @@ -119,6 +120,7 @@ "pad.impexp.importing": "Importálás…", "pad.impexp.confirmimport": "Egy fájl importálása felülírja a jelenlegi szöveget a noteszben. Biztos hogy folytatod?", "pad.impexp.convertFailed": "Nem tudtuk importálni ezt a fájlt. Kérjük, használj másik dokumentum formátumot, vagy kézzel másold és illeszd be a tartalmat", + "pad.impexp.padHasData": "Nem tudjuk importálni ezt a fájlt, mert ez a Pad már megváltozott, kérjük, importálj egy új padra", "pad.impexp.uploadFailed": "A feltöltés sikertelen, próbáld meg újra", "pad.impexp.importfailed": "Az importálás nem sikerült", "pad.impexp.copypaste": "Kérjük másold be", diff --git a/src/locales/ia.json b/src/locales/ia.json index 971f2919..e7f5cc2b 100644 --- a/src/locales/ia.json +++ b/src/locales/ia.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Grasse (Ctrl-B)", "pad.toolbar.italic.title": "Italic (Ctrl-I)", "pad.toolbar.underline.title": "Sublinear (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Cancellar", - "pad.toolbar.ol.title": "Lista ordinate", - "pad.toolbar.ul.title": "Lista non ordinate", + "pad.toolbar.strikethrough.title": "Cancellar (Ctrl+5)", + "pad.toolbar.ol.title": "Lista ordinate (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista non ordinate (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Indentar (TAB)", "pad.toolbar.unindent.title": "Disindentar (Shift+TAB)", "pad.toolbar.undo.title": "Disfacer (Ctrl-Z)", "pad.toolbar.redo.title": "Refacer (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Rader colores de autor", + "pad.toolbar.clearAuthorship.title": "Rader colores de autor (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/exportar in differente formatos de file", "pad.toolbar.timeslider.title": "Glissa-tempore", "pad.toolbar.savedRevision.title": "Version salveguardate", @@ -26,6 +26,7 @@ "pad.colorpicker.save": "Salveguardar", "pad.colorpicker.cancel": "Cancellar", "pad.loading": "Cargamento…", + "pad.noCookie": "Le cookie non pote esser trovate. Per favor permitte le cookies in tu navigator!", "pad.passwordRequired": "Un contrasigno es necessari pro acceder a iste pad", "pad.permissionDenied": "Tu non ha le permission de acceder a iste pad", "pad.wrongPassword": "Le contrasigno es incorrecte", @@ -44,12 +45,12 @@ "pad.importExport.import": "Incargar qualcunque file de texto o documento", "pad.importExport.importSuccessful": "Succedite!", "pad.importExport.export": "Exportar le pad actual como:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Texto simple", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Tu pote solmente importar files in formato de texto simple o HTML. Pro functionalitate de importation plus extense, <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\">installa AbiWord</a>.", "pad.modals.connected": "Connectite.", "pad.modals.reconnecting": "Reconnecte a tu pad…", @@ -115,6 +116,7 @@ "pad.impexp.importing": "Importation in curso…", "pad.impexp.confirmimport": "Le importation de un file superscribera le texto actual del pad. Es tu secur de voler continuar?", "pad.impexp.convertFailed": "Nos non ha potite importar iste file. Per favor usa un altere formato de documento o copia e colla le texto manualmente.", + "pad.impexp.padHasData": "Nos non ha potite importar iste file perque iste Pad ha jam habite cambiamentos. Per favor importa lo a un nove pad.", "pad.impexp.uploadFailed": "Le incargamento ha fallite. Per favor reproba.", "pad.impexp.importfailed": "Importation fallite", "pad.impexp.copypaste": "Per favor copia e colla", diff --git a/src/locales/it.json b/src/locales/it.json index 8292c4f2..a6c30d96 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -4,7 +4,9 @@ "Beta16", "Gianfranco", "Muxator", - "Vituzzu" + "Vituzzu", + "Macofe", + "Nivit" ] }, "index.newPad": "Nuovo Pad", @@ -12,14 +14,14 @@ "pad.toolbar.bold.title": "Grassetto (Ctrl-B)", "pad.toolbar.italic.title": "Corsivo (Ctrl-I)", "pad.toolbar.underline.title": "Sottolineato (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Barrato", - "pad.toolbar.ol.title": "Elenco numerato", - "pad.toolbar.ul.title": "Elenco puntato", + "pad.toolbar.strikethrough.title": "Barrato (Ctrl+5)", + "pad.toolbar.ol.title": "Elenco numerato (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Elenco puntato (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Rientro (TAB)", "pad.toolbar.unindent.title": "Riduci rientro (Shift+TAB)", "pad.toolbar.undo.title": "Annulla (Ctrl-Z)", "pad.toolbar.redo.title": "Ripeti (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Elimina i colori che indicano gli autori", + "pad.toolbar.clearAuthorship.title": "Elimina i colori che indicano gli autori (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importa/esporta da/a diversi formati di file", "pad.toolbar.timeslider.title": "Presentazione cronologia", "pad.toolbar.savedRevision.title": "Versione salvata", @@ -35,6 +37,7 @@ "pad.settings.padSettings": "Impostazioni del Pad", "pad.settings.myView": "Mia visualizzazione", "pad.settings.stickychat": "Chat sempre sullo schermo", + "pad.settings.chatandusers": "Mostra chat e utenti", "pad.settings.colorcheck": "Colori che indicano gli autori", "pad.settings.linenocheck": "Numeri di riga", "pad.settings.rtlcheck": "Leggere il contenuto da destra a sinistra?", @@ -47,12 +50,12 @@ "pad.importExport.import": "Carica un file di testo o un documento", "pad.importExport.importSuccessful": "Riuscito!", "pad.importExport.export": "Esportare il Pad corrente come:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Solo testo", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "È possibile importare solo i formati di testo semplice o HTML. Per metodi più avanzati di importazione <a href=https://github.com/broadcast/etherpad-lite/wiki/How-to-enable-importing and exporting-different file formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord>installare Abiword</a>.", "pad.modals.connected": "Connesso.", "pad.modals.reconnecting": "Riconnessione al pad in corso...", diff --git a/src/locales/ja.json b/src/locales/ja.json index 011c935a..62e6dc62 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "プレーンテキストまたは HTML ファイルからのみインポートできます。より高度なインポート機能を使用するには、<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\">abiword をインストール</a>してください。", "pad.modals.connected": "接続されました。", "pad.modals.reconnecting": "パッドに再接続中...", diff --git a/src/locales/km.json b/src/locales/km.json index a90c0e97..4dea037a 100644 --- a/src/locales/km.json +++ b/src/locales/km.json @@ -47,7 +47,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "បានតភ្ជាប់។", "pad.modals.reconnecting": "កំពុងភ្ជាប់ទៅផេតរបស់អ្នកម្ដងទៀត..", "pad.modals.forcereconnect": "បង្ខំឲ្យភ្ជាប់ឡើងវិញ", diff --git a/src/locales/ko.json b/src/locales/ko.json index 1fed980f..e18c5bbd 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -11,14 +11,14 @@ "pad.toolbar.bold.title": "굵게 (Ctrl-B)", "pad.toolbar.italic.title": "기울임 (Ctrl-I)", "pad.toolbar.underline.title": "밑줄 (Ctrl-U)", - "pad.toolbar.strikethrough.title": "취소선", - "pad.toolbar.ol.title": "순서 있는 목록", - "pad.toolbar.ul.title": "순서 없는 목록", + "pad.toolbar.strikethrough.title": "취소선 (Ctrl+5)", + "pad.toolbar.ol.title": "순서 있는 목록 (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "순서 없는 목록 (Ctrl+Shift+L)", "pad.toolbar.indent.title": "들여쓰기 (TAB)", "pad.toolbar.unindent.title": "내어쓰기 (Shift+TAB)", "pad.toolbar.undo.title": "실행 취소 (Ctrl-Z)", "pad.toolbar.redo.title": "다시 실행 (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "저자의 색 지우기", + "pad.toolbar.clearAuthorship.title": "저자의 색 지우기 (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "다른 파일 형식으로 가져오기/내보내기", "pad.toolbar.timeslider.title": "시간슬라이더", "pad.toolbar.savedRevision.title": "판 저장", @@ -51,7 +51,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "일반 텍스트나 html 형식으로만 가져올 수 있습니다. 고급 가져오기 기능에 대해서는 <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\">abiword를 설치</a>하세요.", "pad.modals.connected": "연결했습니다.", "pad.modals.reconnecting": "패드에 다시 연결 중..", diff --git a/src/locales/ksh.json b/src/locales/ksh.json index 1de3c450..f851bbf1 100644 --- a/src/locales/ksh.json +++ b/src/locales/ksh.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Fättschreff (Strg-B)", "pad.toolbar.italic.title": "Scheive Schreff (Strg-I)", "pad.toolbar.underline.title": "Ongerstresche (Strg-U)", - "pad.toolbar.strikethrough.title": "Dorschjeschtresche", - "pad.toolbar.ol.title": "Leß met Nommere", - "pad.toolbar.ul.title": "Leß met Pongkte", + "pad.toolbar.strikethrough.title": "Dorschjeschtresche (Strg+5)", + "pad.toolbar.ol.title": "Leß met Nommere (Strg+Jruhß+N)", + "pad.toolbar.ul.title": "Leß met Pongkte (Strg+Jruhß+L)", "pad.toolbar.indent.title": "Enjerök (TAB)", "pad.toolbar.unindent.title": "Ußjerök (Jruhßschreff+TAB)", "pad.toolbar.undo.title": "Retuur nämme (Strg-Z)", "pad.toolbar.redo.title": "Norrens (Strg-Y)", - "pad.toolbar.clearAuthorship.title": "Donn dä Schriiver ier Färve fottnämme", + "pad.toolbar.clearAuthorship.title": "Donn dä Schriiver ier Färve fottnämme (Strg+Jruhß+C)", "pad.toolbar.import_export.title": "Ongerscheidlijje Dattei_Fommaate empotteere udder äxpotteere", "pad.toolbar.timeslider.title": "Verjangeheid afschpelle", "pad.toolbar.savedRevision.title": "Di Väsjohn faßhallde", @@ -26,12 +26,14 @@ "pad.colorpicker.save": "Faßhallde", "pad.colorpicker.cancel": "Ophüüre", "pad.loading": "Ben aam Laade …", + "pad.noCookie": "Dat Pläzje wood nit jevonge. Don dat en Dingem Brauser zohlohße!", "pad.passwordRequired": "Do bruchs e Paßwoot för heh dat Pädd.", "pad.permissionDenied": "Do häs nit dat Rääsch, op heh dat Pädd zohzejriife.", "pad.wrongPassword": "Ding Paßwoot wohr verkeht.", "pad.settings.padSettings": "Däm Pädd sing Enschtällonge", "pad.settings.myView": "Aanseesch", "pad.settings.stickychat": "Donn der Klaaf emmer aanzeije", + "pad.settings.chatandusers": "Dunn de Metmaacher un der Klaaf aanzeije", "pad.settings.colorcheck": "Färve för de Schriiver", "pad.settings.linenocheck": "Nommere för de Reije", "pad.settings.rtlcheck": "Schreff vun Rääschß noh Lenks?", @@ -44,12 +46,12 @@ "pad.importExport.import": "Donn jeede Täx udder jeede Zoot Dokemänt huhlaade", "pad.importExport.importSuccessful": "Jeschaff!", "pad.importExport.export": "Don dat Pädd äxpoteere alß:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Eijfach Täx", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF (Poteerbaa Dokemänte Fommaat)", "pad.importExport.exportopen": "ODF (Offe Dokemänte-Fommaat)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Mer künne bloß eijfaache Täxte udder HTML_Fommaate empoteere. Opwändejere Müjjeleschkeite fö der Empoot jon och, doför bruch mer en <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\">Enschtallazjuhn met <i lang=\"en\" xml:lang=\"en\">Abiword</i></a>.", "pad.modals.connected": "Verbonge.", "pad.modals.reconnecting": "Ben wider aam Verbenge …", @@ -105,6 +107,7 @@ "timeslider.month.december": "Dezämber", "timeslider.unnamedauthors": "{[plural(num) zero: keine, one: eine, other: {{num}} ]} nahmeloose Schriiver", "pad.savedrevs.marked": "Heh di Väsjohn es jäz faßjehallde.", + "pad.savedrevs.timeslider": "Mer kann de faßjehallde Väsjohne belohre beim Verjangeheid afschpelle", "pad.userlist.entername": "Jif Dinge Nahme en", "pad.userlist.unnamed": "nahmeloßß", "pad.userlist.guest": "Jaßß", @@ -115,6 +118,7 @@ "pad.impexp.importing": "Ben aam Empotteere …", "pad.impexp.confirmimport": "En Dattei ze empotteere määt der janze Täx em Pädd fott. Wells De dat verfaftesch hann?", "pad.impexp.convertFailed": "Mer kunnte di Dattei nit empoteere. Nemm en ander Dattei-Fommaat udder donn dä Täx vun Hand kopeere un ennföhje.", + "pad.impexp.padHasData": "Mer kunnte di Dattei nit empottehre weil et Pädd alt Veränderonge metjemaht hät. Donn se en e neu Pädd empottehre.", "pad.impexp.uploadFailed": "Dat Huhlaade es donävve jejange. Bes esu johd un probeer et norr_ens.", "pad.impexp.importfailed": "Et Empoteere es donävve jejange.", "pad.impexp.copypaste": "Bes esu johd un donn et koppeere un enfööje", diff --git a/src/locales/lb.json b/src/locales/lb.json index 7f936906..68bdb418 100644 --- a/src/locales/lb.json +++ b/src/locales/lb.json @@ -6,8 +6,8 @@ ] }, "index.newPad": "Neie Pad", - "pad.toolbar.ol.title": "Numeréiert Lëscht", - "pad.toolbar.ul.title": "Net-numeréiert Lëscht", + "pad.toolbar.ol.title": "Numeréiert Lëscht (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Net-numeréiert Lëscht (Ctrl+Shift+L)", "pad.toolbar.undo.title": "Réckgängeg (Ctrl-Z)", "pad.toolbar.redo.title": "Widderhuelen (Ctrl-Y)", "pad.toolbar.savedRevision.title": "Versioun späicheren", @@ -15,6 +15,7 @@ "pad.colorpicker.save": "Späicheren", "pad.colorpicker.cancel": "Ofbriechen", "pad.loading": "Lueden...", + "pad.noCookie": "Cookie gouf net fonnt. Erlaabt w.e.g. Cookien an Ärem Browser!", "pad.wrongPassword": "Äert Passwuert ass falsch", "pad.settings.fontType.normal": "Normal", "pad.settings.language": "Sprooch:", diff --git a/src/locales/lrc.json b/src/locales/lrc.json index 322b6f23..910f8f1e 100644 --- a/src/locales/lrc.json +++ b/src/locales/lrc.json @@ -36,7 +36,6 @@ "pad.importExport.exportword": "واجه پالایشتگر مایکروسافت", "pad.importExport.exportpdf": "پی دی اف", "pad.importExport.exportopen": "او دی اف(قالو سند وا بیه)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "وصل بیه", "pad.modals.forcereconnect": "سی وصل بین مژبور کو", "pad.modals.userdup": "د نیمدری هنی واز بیه", diff --git a/src/locales/lt.json b/src/locales/lt.json index 503b6589..7b4e1481 100644 --- a/src/locales/lt.json +++ b/src/locales/lt.json @@ -28,7 +28,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Atvirasis dokumento formatas)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "Prisijungta.", "pad.modals.unauth": "Neleidžiama", "pad.modals.initsocketfail": "Serveris yra nepasiekiamas.", diff --git a/src/locales/lv.json b/src/locales/lv.json index e57c4259..ee402d33 100644 --- a/src/locales/lv.json +++ b/src/locales/lv.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Treknrakstā (CTRL + B)", "pad.toolbar.italic.title": "Slīpraksta (Ctrl-es)", "pad.toolbar.underline.title": "Pasvītrojuma (CTRL + U)", - "pad.toolbar.strikethrough.title": "Pārsvītrojums", - "pad.toolbar.ol.title": "Sakārtots saraksts", - "pad.toolbar.ul.title": "Nesakārtots saraksts", - "pad.toolbar.indent.title": "Atkāpe", - "pad.toolbar.unindent.title": "Izkāpe", + "pad.toolbar.strikethrough.title": "Pārsvītrojums (Ctrl+5)", + "pad.toolbar.ol.title": "Sakārtots saraksts (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Nesakārtots saraksts (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Atkāpe (TAB)", + "pad.toolbar.unindent.title": "Izkāpe (Shift+TAB)", "pad.toolbar.undo.title": "Atsaukt (CTRL + Z)", "pad.toolbar.redo.title": "Atcelt atsaukšanu (CTRL + Y)", - "pad.toolbar.clearAuthorship.title": "Notīrit autoru krāsas", + "pad.toolbar.clearAuthorship.title": "Notīrit autoru krāsas (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importēšanas/eksportēšanas no un uz citu failu formātiem", "pad.toolbar.savedRevision.title": "Saglabāt pārskatīšanu", "pad.toolbar.settings.title": "Iestatījumi", @@ -46,7 +46,7 @@ "pad.importExport.exportword": "Programma Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open dokumenta formāts)", - "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.modals.connected": "Pievienojies.", "pad.modals.userdup": "Atvērts citā logā", "pad.modals.unauth": "Nav atļauts", "pad.modals.looping.explanation": "Pastāv sakaru problēmas ar sinhronizācijas servera.", @@ -56,7 +56,7 @@ "pad.modals.deleted": "Dzēsts", "pad.modals.disconnected": "Jūs esat atvienots.", "pad.modals.disconnected.explanation": "Tika zaudēts savienojums ar serveri", - "pad.modals.disconnected.cause": "Iespējams, ka serveris nav pieejams. Lūgums paziņot mums, ja tas turpina notikt.", + "pad.modals.disconnected.cause": "Iespējams, ka serveris nav pieejams. Lūgums paziņot pakalpojuma administratoram, ja tas turpina notikt.", "pad.share": "Koplietot šo pad", "pad.share.readonly": "Tikai lasāms", "pad.share.link": "Saite", diff --git a/src/locales/map-bms.json b/src/locales/map-bms.json index 883b1d8f..1b87bac8 100644 --- a/src/locales/map-bms.json +++ b/src/locales/map-bms.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Rika mung teyeng impor sekang format plain text utawa HTML. Kanggo fitur impor sing lewih maju monggo <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\">masang abiword</a>.", "pad.modals.connected": "Nyambung.", "pad.modals.reconnecting": "Mbaleli nyambung ming pad Rika...", diff --git a/src/locales/mk.json b/src/locales/mk.json index 8aaf9917..a924875a 100644 --- a/src/locales/mk.json +++ b/src/locales/mk.json @@ -10,14 +10,14 @@ "pad.toolbar.bold.title": "Задебелено (Ctrl-B)", "pad.toolbar.italic.title": "Косо (Ctrl-I)", "pad.toolbar.underline.title": "Подвлечено (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Прецртано", - "pad.toolbar.ol.title": "Подреден список", - "pad.toolbar.ul.title": "Неподреден список", + "pad.toolbar.strikethrough.title": "Прецртано (Ctrl+5)", + "pad.toolbar.ol.title": "Подреден список (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Неподреден список (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Вовлекување (TAB)", "pad.toolbar.unindent.title": "Отстап (Shift+TAB)", "pad.toolbar.undo.title": "Врати (Ctrl-Z)", "pad.toolbar.redo.title": "Повтори (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Поништи ги авторските бои", + "pad.toolbar.clearAuthorship.title": "Тргни ги авторските бои (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Увоз/Извоз од/во разни податотечни формати", "pad.toolbar.timeslider.title": "Историски преглед", "pad.toolbar.savedRevision.title": "Зачувај преработка", @@ -27,12 +27,14 @@ "pad.colorpicker.save": "Зачувај", "pad.colorpicker.cancel": "Откажи", "pad.loading": "Вчитувам...", + "pad.noCookie": "Не можев да го најдам колачето. Овозможете колачиња во вашиот прелистувач!", "pad.passwordRequired": "Потребна е лозинка за пристап", "pad.permissionDenied": "За овде не е потребна дозвола за пристап", "pad.wrongPassword": "Погрешна лозинка", "pad.settings.padSettings": "Поставки на тетратката", "pad.settings.myView": "Мој поглед", "pad.settings.stickychat": "Разговорите секогаш на екранот", + "pad.settings.chatandusers": "Прикажи разговор и корисници", "pad.settings.colorcheck": "Авторски бои", "pad.settings.linenocheck": "Броеви на редовите", "pad.settings.rtlcheck": "Содржините да се читаат од десно на лево?", @@ -45,12 +47,12 @@ "pad.importExport.import": "Подигање на било каква текстуална податотека или документ", "pad.importExport.importSuccessful": "Успешно!", "pad.importExport.export": "Извези ја тековната тетратка како", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Прост текст", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Можете да увезувате само од прост текст и HTML-формат. Понапредни можности за увоз ќе добиете ако <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\">воспоставите AbiWord</a>.", "pad.modals.connected": "Поврзано.", "pad.modals.reconnecting": "Ве преповрзувам со тетратката...", @@ -106,6 +108,7 @@ "timeslider.month.december": "декември", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: неименуван автор, other: неименувани автори ]}", "pad.savedrevs.marked": "Оваа преработка сега е означена како зачувана", + "pad.savedrevs.timeslider": "Можете да ги погледате зачуваните преработки посетувајќи го времеследниот лизгач", "pad.userlist.entername": "Внесете го вашето име", "pad.userlist.unnamed": "без име", "pad.userlist.guest": "Гостин", @@ -116,6 +119,7 @@ "pad.impexp.importing": "Увезувам...", "pad.impexp.confirmimport": "Увезувајќи ја податотеката ќе го замените целиот досегашен текст во тетратката. Дали сте сигурни дека сакате да продолжите?", "pad.impexp.convertFailed": "Не можев да ја увезам податотеката. Послужете се со поинаков формат или прекопирајте го текстот рачно.", + "pad.impexp.padHasData": "Не можевме да ја увеземе оваа податотека бидејќи оваа тетратка веќе има промени. Увезете ја во нова тетратка.", "pad.impexp.uploadFailed": "Подигањето не успеа. Обидете се повторно.", "pad.impexp.importfailed": "Увозот не успеа", "pad.impexp.copypaste": "Прекопирајте", diff --git a/src/locales/ml.json b/src/locales/ml.json index ff7a38f3..680df0c2 100644 --- a/src/locales/ml.json +++ b/src/locales/ml.json @@ -13,14 +13,14 @@ "pad.toolbar.bold.title": "കടുപ്പത്തിലെഴുതുക (Ctrl-B)", "pad.toolbar.italic.title": "ചെരിച്ചെഴുതുക (Ctrl-I)", "pad.toolbar.underline.title": "അടിവരയിടുക (Ctrl-U)", - "pad.toolbar.strikethrough.title": "വെട്ടുക", - "pad.toolbar.ol.title": "ക്രമത്തിലുള്ള പട്ടിക", - "pad.toolbar.ul.title": "ക്രമരഹിത പട്ടിക", + "pad.toolbar.strikethrough.title": "വെട്ടുക (Ctrl+5)", + "pad.toolbar.ol.title": "ക്രമത്തിലുള്ള പട്ടിക (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "ക്രമരഹിത പട്ടിക (Ctrl+Shift+L)", "pad.toolbar.indent.title": "വലത്തേക്ക് തള്ളുക (ടാബ്)", "pad.toolbar.unindent.title": "ഇടത്തേക്ക് തള്ളുക (ഷിഫ്റ്റ്+ടാബ്)", "pad.toolbar.undo.title": "തിരസ്കരിക്കുക (Ctrl-Z)", "pad.toolbar.redo.title": "വീണ്ടും ചെയ്യുക (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "രചയിതാക്കൾക്കുള്ള നിറം കളയുക", + "pad.toolbar.clearAuthorship.title": "രചയിതാക്കൾക്കുള്ള നിറം കളയുക (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "വ്യത്യസ്ത ഫയൽ തരങ്ങളിലേക്ക്/തരങ്ങളിൽ നിന്ന് ഇറക്കുമതി/കയറ്റുമതി ചെയ്യുക", "pad.toolbar.timeslider.title": "സമയരേഖ", "pad.toolbar.savedRevision.title": "നാൾപ്പതിപ്പ് സേവ് ചെയ്യുക", @@ -53,7 +53,6 @@ "pad.importExport.exportword": "മൈക്രോസോഫ്റ്റ് വേഡ്", "pad.importExport.exportpdf": "പി.ഡി.എഫ്.", "pad.importExport.exportopen": "ഒ.ഡി.എഫ്. (ഓപ്പൺ ഡോക്യുമെന്റ് ഫോർമാറ്റ്)", - "pad.importExport.exportdokuwiki": "ഡോകുവിക്കി", "pad.importExport.abiword.innerHTML": "പ്ലെയിൻ ടെക്സ്റ്റോ എച്ച്.റ്റി.എം.എൽ. തരമോ മാത്രമേ താങ്കൾക്ക് ഇറക്കുമതി ചെയ്യാനാവൂ. കൂടുതൽ വിപുലീകൃത ഇറക്കുമതി സൗകര്യങ്ങൾക്കായി ദയവായി <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\">അബിവേഡ് ഇൻസ്റ്റോൾ ചെയ്യുക</a>.", "pad.modals.connected": "ബന്ധിപ്പിച്ചിരിക്കുന്നു.", "pad.modals.reconnecting": "താങ്കളുടെ പാഡിലേയ്ക്ക് വീണ്ടും ബന്ധിപ്പിക്കുന്നു...", diff --git a/src/locales/mr.json b/src/locales/mr.json index 31de89aa..536b578c 100644 --- a/src/locales/mr.json +++ b/src/locales/mr.json @@ -26,7 +26,6 @@ "pad.importExport.exportword": "मायक्रोसॉफ्ट वर्ड", "pad.importExport.exportpdf": "पीडीएफ", "pad.importExport.exportopen": "ओडीएफ(ओपन डॉक्यूमेंट फॉरमॅट)", - "pad.importExport.exportdokuwiki": "डुकुविकि", "pad.modals.connected": "अनुबंधित", "pad.modals.initsocketfail": "विदागारास पोच नाही.", "pad.modals.deleted": "वगळले.", diff --git a/src/locales/ms.json b/src/locales/ms.json index 033de1bc..d099de06 100644 --- a/src/locales/ms.json +++ b/src/locales/ms.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Tebal (Ctrl-B)", "pad.toolbar.italic.title": "Miring (Ctrl-I)", "pad.toolbar.underline.title": "Garis bawah (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Garis lorek", - "pad.toolbar.ol.title": "Senarai tertib", - "pad.toolbar.ul.title": "Senarai tak tertib", + "pad.toolbar.strikethrough.title": "Garis lorek (Ctrl+5)", + "pad.toolbar.ol.title": "Senarai tertib (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Senarai tak tertib (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Engsot ke dalam (TAB)", "pad.toolbar.unindent.title": "Engsot ke luar (Shift + TAB)", "pad.toolbar.undo.title": "Buat asal (Ctrl-Z)", "pad.toolbar.redo.title": "Buat semula (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Padamkan Warna Pengarang", + "pad.toolbar.clearAuthorship.title": "Padamkan Warna Pengarang (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Import/Eksport dari/ke format-format fail berbeza", "pad.toolbar.timeslider.title": "Gelangsar masa", "pad.toolbar.savedRevision.title": "Simpan Semakan", @@ -26,6 +26,7 @@ "pad.colorpicker.save": "Simpan", "pad.colorpicker.cancel": "Batalkan", "pad.loading": "Sedang dimuatkan...", + "pad.noCookie": "Cookie tidak dapat dijumpai. Tolong benarkan cookie dalam pelayar anda!", "pad.passwordRequired": "Anda memerlukan kata laluan untuk mengakses pad ini", "pad.permissionDenied": "Anda tiada kebenaran untuk mengakses pad ini", "pad.wrongPassword": "Kata laluan anda salah", @@ -44,12 +45,12 @@ "pad.importExport.import": "Muat naik sebarang fail teks atau dokumen", "pad.importExport.importSuccessful": "Berjaya!", "pad.importExport.export": "Eksport pad semasa sebagai:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Teks biasa", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Anda hanya boleh mengimport dari format teks biasa atau html. Untuk ciri-ciri import yang lebih maju, sila <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\">memasang abiword</a>.", "pad.modals.connected": "Bersambung.", "pad.modals.reconnecting": "Bersambung semula dengan pad anda...", @@ -115,6 +116,7 @@ "pad.impexp.importing": "Sedang mengimport...", "pad.impexp.confirmimport": "Mengimport fail akan menulis ganti teks semasa pada pad ini. Adakah anda benar-benar ingin teruskan?", "pad.impexp.convertFailed": "Fail tidak dapat diimport. Sila gunakan format dokumen yang lain atau salin tampal secara manual", + "pad.impexp.padHasData": "Kami tidak dapat mengimport fail ini kerana Pad ini sudah mengalami perubahan. Sila import ke pad yang baru", "pad.impexp.uploadFailed": "Muat naik gagal, sila cuba lagi", "pad.impexp.importfailed": "Import gagal", "pad.impexp.copypaste": "Sila salin tampal", diff --git a/src/locales/nap.json b/src/locales/nap.json index 6ba696fc..6cd1651b 100644 --- a/src/locales/nap.json +++ b/src/locales/nap.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Chelin" + "Chelin", + "C.R." ] }, "index.newPad": "Novo Pad", @@ -9,36 +10,70 @@ "pad.toolbar.bold.title": "Grassetto (Ctrl-B)", "pad.toolbar.italic.title": "Cursivo (Ctrl-I)", "pad.toolbar.underline.title": "Sottolineato (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Barrato", - "pad.toolbar.ol.title": "Ennece nummerato", - "pad.toolbar.ul.title": "Ennece puntato", + "pad.toolbar.strikethrough.title": "Barrato (Ctrl+5)", + "pad.toolbar.ol.title": "Ennece nummerato (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Ennece puntato (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Rientro (TAB)", "pad.toolbar.unindent.title": "Riduce rientro (Shift+TAB)", "pad.toolbar.undo.title": "Annulla (Ctrl-Z)", "pad.toolbar.redo.title": "Ripete (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Elimina 'e culure ca 'ndicanno 'e auture", + "pad.toolbar.clearAuthorship.title": "Elimina 'e culure ca 'ndicanno 'e auture (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "'Mporta/esporta 'e/a diverse furmate 'e file", "pad.toolbar.timeslider.title": "Presentazzione cronologgia", "pad.toolbar.savedRevision.title": "Sarva revisione", "pad.toolbar.settings.title": "Mpustaziune", + "pad.toolbar.embed.title": "Sparte e nzerta stu Pad", + "pad.toolbar.showusers.title": "Mmusta ll'utente ncopp'a stu Pad", "pad.colorpicker.save": "Sarva", "pad.colorpicker.cancel": "Canciella", "pad.loading": "Carecamiento 'n curso…", + "pad.noCookie": "Cookie nun truvata. Pe' piacere premmettete 'e cookies dint' 'o navigatóre vuosto!", "pad.passwordRequired": "Pe' accede a chisto Pad è necessaria 'na password", "pad.permissionDenied": "Nun se dispunne d\"e permisse necessare pe' accede a chisto Pad", "pad.wrongPassword": "'A password è sbagliata", "pad.settings.padSettings": "Mpostazzione d\"o pad", + "pad.settings.myView": "Mia Veruta", + "pad.settings.stickychat": "Chat sempe ncopp' 'o schermo", + "pad.settings.colorcheck": "Auturevolezza pe' culure", + "pad.settings.linenocheck": "Nummere 'e riga", + "pad.settings.rtlcheck": "Lieggere 'e cuntenute 'a destra a smerza?", + "pad.settings.fontType": "Tipo 'e funte:", "pad.settings.fontType.normal": "Nurmale", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "Visualizzazione globbale", + "pad.settings.language": "Llengua:", + "pad.importExport.import_export": "Mpurtaziune/sportaziune", + "pad.importExport.import": "Carreca coccherunto testo o documento", + "pad.importExport.importSuccessful": "Ngarrata!", + "pad.importExport.export": "Sportà stu Pad comme:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "Testo nurmale", + "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.importExport.abiword.innerHTML": "Putite surtanto mpurtà testo chiano o furmatte HTML. Pe n'avé sisteme cchiù annanze 'e mpurtazione pe' piacere <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\">installate Abiword</a>.", "pad.modals.connected": "Cunnesso.", "pad.modals.reconnecting": "Ricunnessione ô pad 'n curso...", "pad.modals.forcereconnect": "Forza 'a ricunnessione", "pad.modals.userdup": "Aprito 'n n'ata fenesta", + "pad.modals.userdup.explanation": "Stu Pad pare fosse araputo dint'a cchiù 'e na fenesta 'e navigatore dint'a stu computer.", + "pad.modals.userdup.advice": "Riconnettateve pe' putè ausà mmece sta fenesta.", "pad.modals.unauth": "Nun autorizzato", + "pad.modals.unauth.explanation": "'E premmesse vuoste so' cagnate pe' tramente ca se vereva sta paggena. Tentate 'e ve riconnettà.", + "pad.modals.looping.explanation": "Ce stanno probbleme 'e comunicazione c' 'o server 'e sincronizzaziona.", + "pad.modals.looping.cause": "Può darse ca ve site cullegato pe' mmiez' 'e nu firewall incompatibbele o proxy.", + "pad.modals.initsocketfail": "Nun se può arrevà 'o server.", + "pad.modals.initsocketfail.explanation": "Nun se può cunnettà 'o server e sincronizzaziona.", + "pad.modals.initsocketfail.cause": "Stu fatto è succiesso, probabbilmente pe' bbìa 'e nu probblema c' 'o navigatóre 'o ll'internet.", + "pad.modals.slowcommit.explanation": "'O server nun risponne.", + "pad.modals.slowcommit.cause": "Stu fatto può darse ca è causato pe' bbìa 'e prubbleme 'e connettività 'e rezza.", + "pad.modals.badChangeset.explanation": "Nu cagnamento ca stavate facenno è stato classeficato comme illegale p' 'o server 'e sincronizzaziona.", + "pad.modals.badChangeset.cause": "Chistu fatto può darse ca è causato pe' bbìa 'e na mpustazione errata d' 'o server o cocch'atu comportamento nun preveduto. Pe' piacere cuntattate l'ammenistratore d' 'o servizio, si se pienza ca chist'è n'errore. Tentate a ve riconnettà pe' cuntinuà 'a edità.", + "pad.modals.corruptPad.explanation": "'O pad addò vulevate trasì è scassato.", "pad.modals.deleted": "Canciellato.", + "pad.share.link": "Jonta", + "pad.chat": "Chiàcchiera", "timeslider.pageTitle": "Cronologgia {{appTitle}}", "timeslider.toolbar.returnbutton": "Ritorna ô Pad", "timeslider.toolbar.authors": "Auture:", diff --git a/src/locales/nb.json b/src/locales/nb.json index 0620d2b4..a1b3a5ca 100644 --- a/src/locales/nb.json +++ b/src/locales/nb.json @@ -10,14 +10,14 @@ "pad.toolbar.bold.title": "Fet (Ctrl-B)", "pad.toolbar.italic.title": "Kursiv (Ctrl-I)", "pad.toolbar.underline.title": "Understreking (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Gjennomstreking", - "pad.toolbar.ol.title": "Nummerert liste", - "pad.toolbar.ul.title": "Punktliste", + "pad.toolbar.strikethrough.title": "Gjennomstreking (Ctrl+5)", + "pad.toolbar.ol.title": "Nummerert liste (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Punktliste (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Innrykk (TAB)", "pad.toolbar.unindent.title": "Rykk ut (Shift+TAB)", "pad.toolbar.undo.title": "Angre (Ctrl-Z)", "pad.toolbar.redo.title": "Gjør omigjen (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Fjern forfatterfarger", + "pad.toolbar.clearAuthorship.title": "Fjern forfatterfarger (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importer/eksporter fra/til forskjellige filformater", "pad.toolbar.timeslider.title": "Tidslinje", "pad.toolbar.savedRevision.title": "Lagre revisjoner", @@ -50,7 +50,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Du kan bare importere fra ren tekst eller HTML-formater. For mer avanserte importfunksjoner, <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\">installer abiword</a>.", "pad.modals.connected": "Tilkoblet.", "pad.modals.reconnecting": "Kobler til din pad på nytt...", diff --git a/src/locales/nds.json b/src/locales/nds.json index 451a0211..dfb92278 100644 --- a/src/locales/nds.json +++ b/src/locales/nds.json @@ -50,7 +50,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Se köönt blots wat vun Kloortext oder HTML-Stücken röverhalen. Mit <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\"> köönt Se ok anner Saken röverhalen. Dorför mööt Se bidde abiword inrichten</a>.", "pad.modals.connected": "Verbindung steiht.", "pad.modals.reconnecting": "En Verbindung wedder opboen ...", diff --git a/src/locales/ne.json b/src/locales/ne.json index 37194884..b380526e 100644 --- a/src/locales/ne.json +++ b/src/locales/ne.json @@ -48,7 +48,6 @@ "pad.importExport.exportword": "माइक्रोसफ्ट वर्ड", "pad.importExport.exportpdf": "पिडिएफ", "pad.importExport.exportopen": "ओडिएफ(खुल्ला कागजात ढाँचा)", - "pad.importExport.exportdokuwiki": "डकुविकि", "pad.modals.connected": "जोडीएको।", "pad.modals.reconnecting": "तपाईँको प्याडमा पुन: जडान गर्दै", "pad.modals.forcereconnect": "जडानको लागि जोडगर्ने", diff --git a/src/locales/nl.json b/src/locales/nl.json index 5a9d9fba..183d0fa9 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1,7 +1,9 @@ { "@metadata": { "authors": [ - "Siebrand" + "Siebrand", + "Macofe", + "Robin0van0der0vliet" ] }, "index.newPad": "Nieuw pad", @@ -9,14 +11,14 @@ "pad.toolbar.bold.title": "Vet (Ctrl-B)", "pad.toolbar.italic.title": "Cursief (Ctrl-I)", "pad.toolbar.underline.title": "Onderstrepen (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Doorhalen", - "pad.toolbar.ol.title": "Geordende lijst", - "pad.toolbar.ul.title": "Ongeordende lijst", + "pad.toolbar.strikethrough.title": "Doorhalen (Ctrl+5)", + "pad.toolbar.ol.title": "Geordende lijst (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Ongeordende lijst (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Inspringen (Tab)", "pad.toolbar.unindent.title": "Inspringing verkleinen (Shift+Tab)", "pad.toolbar.undo.title": "Ongedaan maken (Ctrl-Z)", "pad.toolbar.redo.title": "Opnieuw uitvoeren (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Kleuren auteurs wissen", + "pad.toolbar.clearAuthorship.title": "Kleuren auteurs wissen (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Naar/van andere opmaak exporteren/importeren", "pad.toolbar.timeslider.title": "Tijdlijn", "pad.toolbar.savedRevision.title": "Versie opslaan", @@ -26,12 +28,14 @@ "pad.colorpicker.save": "Opslaan", "pad.colorpicker.cancel": "Annuleren", "pad.loading": "Bezig met laden…", + "pad.noCookie": "Er kon geen cookie gevonden worden. Zorg ervoor dat uw browser cookies accepteert.", "pad.passwordRequired": "U hebt een wachtwoord nodig om toegang te krijgen tot deze pad", "pad.permissionDenied": "U hebt geen rechten om deze pad te bekijken", "pad.wrongPassword": "U hebt een onjuist wachtwoord ingevoerd", "pad.settings.padSettings": "Padinstellingen", "pad.settings.myView": "Mijn overzicht", "pad.settings.stickychat": "Chat altijd zichtbaar", + "pad.settings.chatandusers": "Chat en gebruikers weergeven", "pad.settings.colorcheck": "Kleuren auteurs", "pad.settings.linenocheck": "Regelnummers", "pad.settings.rtlcheck": "Inhoud van rechts naar links lezen?", @@ -44,12 +48,12 @@ "pad.importExport.import": "Upload een tekstbestand of document", "pad.importExport.importSuccessful": "Afgerond", "pad.importExport.export": "Huidige pad exporteren als", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Tekst zonder opmaak", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "Pdf", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "U kunt alleen importeren vanuit platte tekst of een HTML-opmaak. <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\">Installeer abiword</a> om meer geavanceerde importmogelijkheden te krijgen.", "pad.modals.connected": "Verbonden.", "pad.modals.reconnecting": "Opnieuw verbinding maken met uw pad...", @@ -105,6 +109,7 @@ "timeslider.month.december": "december", "timeslider.unnamedauthors": "{{num}} onbekende {[plural(num) one: auteur, other: auteurs ]}", "pad.savedrevs.marked": "Deze versie is nu gemarkeerd als opgeslagen versie", + "pad.savedrevs.timeslider": "U kunt opgeslagen versies bekijken via de tijdschuiver.", "pad.userlist.entername": "Geef uw naam op", "pad.userlist.unnamed": "zonder naam", "pad.userlist.guest": "Gast", @@ -115,6 +120,7 @@ "pad.impexp.importing": "Bezig met importeren…", "pad.impexp.confirmimport": "Door een bestand te importeren overschrijft u de huidige tekst van de pad. Wilt u echt doorgaan?", "pad.impexp.convertFailed": "Het was niet mogelijk dit bestand te importeren. Gebruik een andere documentopmaak of kopieer en plak de inhoud handmatig", + "pad.impexp.padHasData": "Het was niet mogelijk dit bestand te importeren omdat er al wijzigingen aan de etherpad zijn gemaakt. Importeer naar een nieuwe etherpad.", "pad.impexp.uploadFailed": "Het uploaden is mislukt. Probeer het opnieuw", "pad.impexp.importfailed": "Importeren is mislukt", "pad.impexp.copypaste": "Gebruik kopiëren en plakken", diff --git a/src/locales/nn.json b/src/locales/nn.json index 104a17dc..dc6368ed 100644 --- a/src/locales/nn.json +++ b/src/locales/nn.json @@ -48,7 +48,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Du kan berre importera frå rein tekst- eller HTML-format. Ver venleg og <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\">installer Abiword</a> om du treng meir avanserte importfunksjonar.", "pad.modals.connected": "Tilkopla.", "pad.modals.reconnecting": "Gjenopprettar tilkoplinga til blokka di …", diff --git a/src/locales/oc.json b/src/locales/oc.json index 5921f4ff..e62d387a 100644 --- a/src/locales/oc.json +++ b/src/locales/oc.json @@ -9,14 +9,14 @@ "pad.toolbar.bold.title": "Gras (Ctrl-B)", "pad.toolbar.italic.title": "Italica (Ctrl-I)", "pad.toolbar.underline.title": "Soslinhat (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Raiat", - "pad.toolbar.ol.title": "Lista ordenada", - "pad.toolbar.ul.title": "Lista amb de piuses", + "pad.toolbar.strikethrough.title": "Raiat (Ctrl+5)", + "pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista pas ordenada (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Indentar (TAB)", "pad.toolbar.unindent.title": "Desindentar (Maj+TAB)", "pad.toolbar.undo.title": "Anullar (Ctrl-Z)", "pad.toolbar.redo.title": "Restablir (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Escafar las colors qu'identifican los autors", + "pad.toolbar.clearAuthorship.title": "Escafar las colors qu'identifican los autors (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/Exportar de/cap a un format de fichièr diferent", "pad.toolbar.timeslider.title": "Istoric dinamic", "pad.toolbar.savedRevision.title": "Enregistrar la revision", @@ -26,6 +26,7 @@ "pad.colorpicker.save": "Enregistrar", "pad.colorpicker.cancel": "Anullar", "pad.loading": "Cargament...", + "pad.noCookie": "Lo cookie a pas pogut èsser trobat. Autorizatz los cookies dins vòstre navigador !", "pad.passwordRequired": "Avètz besonh d'un senhal per accedir a aqueste Pad", "pad.permissionDenied": "Vos es pas permés d’accedir a aqueste Pad.", "pad.wrongPassword": "Senhal incorrècte", @@ -44,12 +45,12 @@ "pad.importExport.import": "Cargar un tèxte o un document", "pad.importExport.importSuccessful": "Capitat !", "pad.importExport.export": "Exportar lo Pad actual coma :", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Tèxte brut", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Podètz pas importar que de formats tèxte brut o html. Per de foncionalitats d'importacion mai evoluadas, <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\">installatz abiword</a>.", "pad.modals.connected": "Connectat.", "pad.modals.reconnecting": "Reconnexion cap a vòstre Pad...", @@ -115,6 +116,7 @@ "pad.impexp.importing": "Impòrt en cors...", "pad.impexp.confirmimport": "Importar un fichièr espotirà lo tèxte actual del blòt. Sètz segur que lo volètz far ?", "pad.impexp.convertFailed": "Podèm pas importar aqueste fichièr. Utilizatz un autre format de document o fasètz un copiar/pegar manual", + "pad.impexp.padHasData": "Avèm pas pogut importar aqueste fichièr perque aqueste blòt a ja agut de modificacions ; importatz cap a un blòt novèl", "pad.impexp.uploadFailed": "Lo telecargament a fracassat, reensajatz", "pad.impexp.importfailed": "Fracàs de l'importacion", "pad.impexp.copypaste": "Copiatz/pegatz", diff --git a/src/locales/os.json b/src/locales/os.json index 109dada7..941fe2b8 100644 --- a/src/locales/os.json +++ b/src/locales/os.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Дӕ бон у импорт кӕнын ӕрмӕст хуымӕтӕг текст кӕнӕ html форматӕй. Лӕмбынӕг импорты миниуджытӕн, дӕ хорзӕхӕй, <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\">сӕвӕр abiword</a>.", "pad.modals.connected": "Иугонд.", "pad.modals.reconnecting": "Дӕ документмӕ ногӕй иугонд цӕуы..", diff --git a/src/locales/pa.json b/src/locales/pa.json index 55ce5e6c..9e154e36 100644 --- a/src/locales/pa.json +++ b/src/locales/pa.json @@ -50,7 +50,6 @@ "pad.importExport.exportword": "ਮਾਈਕਰੋਸਾਫਟ ਵਰਡ", "pad.importExport.exportpdf": "ਪੀਡੀਐਫ", "pad.importExport.exportopen": "ODF (ਓਪਨ ਡੌਕੂਮੈਂਟ ਫਾਰਮੈਟ)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "ਤੁਸੀਂ ਸਿਰਫ਼ ਸਾਦੀਆਂ ਲਿਖਤੀ ਜਾਂ ਐੱਚ.ਟੀ.ਐੱਮ.ਐੱਲ. ਰੂਪ-ਰੇਖਾਵਾਂ ਤੋਂ ਦਰਾਮਦ ਕਰ ਸਕਦੇ ਹੋ। ਹੋਰ ਉੱਨਤ ਦਰਾਮਦੀ ਗੁਣਾਂ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ <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\">ਐਬੀਵਰਡ ਥਾਪੋ</a>।", "pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।", "pad.modals.reconnecting": "..ਤੁਹਾਡੇ ਪੈਡ ਨਾਲ ਮੁੜ-ਕੁਨੈਕਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ", diff --git a/src/locales/pl.json b/src/locales/pl.json index 9e7be2fb..bcd7a0ef 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -4,7 +4,9 @@ "Rezonansowy", "Ty221", "WTM", - "Woytecr" + "Woytecr", + "Macofe", + "Pan Cube" ] }, "index.newPad": "Nowy dokument", @@ -19,7 +21,7 @@ "pad.toolbar.unindent.title": "Wcięcie (Shift + TAB)", "pad.toolbar.undo.title": "Cofnij (Ctrl-Z)", "pad.toolbar.redo.title": "Ponów (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Usuń kolory autorów", + "pad.toolbar.clearAuthorship.title": "Usuń kolory autorów (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Import/eksport z/do różnych formatów plików", "pad.toolbar.timeslider.title": "Oś czasu", "pad.toolbar.savedRevision.title": "Zapisz wersję", @@ -47,12 +49,12 @@ "pad.importExport.import": "Prześlij dowolny plik tekstowy lub dokument", "pad.importExport.importSuccessful": "Sukces!", "pad.importExport.export": "Eksportuj bieżący dokument jako:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Zwykły tekst", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Możesz importować pliki tylko w formacie zwykłego tekstu lub html. Aby umożliwić bardziej zaawansowane funkcje importu, <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\">zainstaluj abiword</a>.", "pad.modals.connected": "Połączony.", "pad.modals.reconnecting": "Ponowne łączenie z dokumentem...", diff --git a/src/locales/ps.json b/src/locales/ps.json index 76004412..1a57e76a 100644 --- a/src/locales/ps.json +++ b/src/locales/ps.json @@ -32,7 +32,6 @@ "pad.importExport.exportword": "مايکروسافټ ورډ", "pad.importExport.exportpdf": "پي ډي اېف", "pad.importExport.exportopen": "ODF (اوپن ډاکومنټ فارمټ)", - "pad.importExport.exportdokuwiki": "ډوکوويکي", "pad.modals.connected": "اړيکمن شو.", "pad.modals.slowcommit.explanation": "پالنگر ځواب نه وايي.", "pad.modals.slowcommit.cause": "دا کېدای شي د جال د اړيکتيايي ستونزو په سبب وي.", diff --git a/src/locales/pt-br.json b/src/locales/pt-br.json index 333d59fb..1fd145bc 100644 --- a/src/locales/pt-br.json +++ b/src/locales/pt-br.json @@ -8,7 +8,11 @@ "Titoncio", "Tuliouel", "Rafaelff", - "Dianakc" + "Dianakc", + "Macofe", + "Rodrigo codignoli", + "Webysther", + "Fasouzafreitas" ] }, "index.newPad": "Nova Nota", @@ -16,14 +20,14 @@ "pad.toolbar.bold.title": "Negrito (Ctrl-B)", "pad.toolbar.italic.title": "Itálico (Ctrl-I)", "pad.toolbar.underline.title": "Sublinhar (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Tachado", - "pad.toolbar.ol.title": "Lista ordenada", - "pad.toolbar.ul.title": "Lista não ordenada", + "pad.toolbar.strikethrough.title": "Tachado (Ctrl+5)", + "pad.toolbar.ol.title": "Lista ordenada (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Lista não ordenada (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Aumentar Recuo (TAB)", "pad.toolbar.unindent.title": "Diminuir Recuo (Shift+TAB)", "pad.toolbar.undo.title": "Desfazer (Ctrl-Z)", "pad.toolbar.redo.title": "Refazer (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Limpar as cores de identificação de autoria", + "pad.toolbar.clearAuthorship.title": "Limpar as cores de identificação de autoria (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/Exportar de/para diferentes formatos de arquivo", "pad.toolbar.timeslider.title": "Linha do tempo", "pad.toolbar.savedRevision.title": "Salvar revisão", @@ -33,12 +37,14 @@ "pad.colorpicker.save": "Salvar", "pad.colorpicker.cancel": "Cancelar", "pad.loading": "Carregando...", + "pad.noCookie": "Cookie não foi encontrado. Por favor, habilite cookies no seu navegador!", "pad.passwordRequired": "Você precisa de uma senha para acessar esta Nota", "pad.permissionDenied": "Você não tem permissão para acessar esta Nota", "pad.wrongPassword": "Senha incorreta", "pad.settings.padSettings": "Configurações da Nota", "pad.settings.myView": "Minha Visão", "pad.settings.stickychat": "Conversa sempre visível", + "pad.settings.chatandusers": "Mostrar o chat e os usuários", "pad.settings.colorcheck": "Cores de autoria", "pad.settings.linenocheck": "Números de linha", "pad.settings.rtlcheck": "Ler conteúdo da direita para esquerda?", @@ -51,12 +57,12 @@ "pad.importExport.import": "Enviar um arquivo texto ou documento", "pad.importExport.importSuccessful": "Completo!", "pad.importExport.export": "Exportar a presente nota como:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Texto puro", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Você só pode importar de formatos de texto puro ou html. Para recursos de importação mais avançados <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\">instale o abiword</a>.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando à sua nota...", @@ -112,6 +118,7 @@ "timeslider.month.december": "Dezembro", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: autor anônimo, other: autores anônimos ]}", "pad.savedrevs.marked": "Esta revisão foi marcada como salva", + "pad.savedrevs.timeslider": "Pode consultar as revisões salvas visitando a linha do tempo", "pad.userlist.entername": "Insira o seu nome", "pad.userlist.unnamed": "Sem título", "pad.userlist.guest": "Convidado", @@ -122,6 +129,7 @@ "pad.impexp.importing": "Importando...", "pad.impexp.confirmimport": "Importar um arquivo sobrescreverá o atual texto da nota. Tem certeza de que deseja prosseguir?", "pad.impexp.convertFailed": "Não foi possível importar este arquivo. Use outro formato ou copie e cole manualmente", + "pad.impexp.padHasData": "Não foi possível importar este arquivo porque este bloco de notas já tinha alterações, consulte como importar para um novo bloco de notas", "pad.impexp.uploadFailed": "O envio falhou. Tente outra vez", "pad.impexp.importfailed": "A importação falhou", "pad.impexp.copypaste": "Copie e cole", diff --git a/src/locales/pt.json b/src/locales/pt.json index 69a4dca1..473980fd 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -6,7 +6,8 @@ "Tuliouel", "Waldir", "Imperadeiro98", - "Macofe" + "Macofe", + "Ti4goc" ] }, "index.newPad": "Nova Nota", @@ -21,7 +22,7 @@ "pad.toolbar.unindent.title": "Recuar (Shift+TAB)", "pad.toolbar.undo.title": "Desfazer (Ctrl-Z)", "pad.toolbar.redo.title": "Refazer (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Limpar cores de autoria", + "pad.toolbar.clearAuthorship.title": "Limpar cores de autoria (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importar/exportar de/para diferentes formatos de ficheiro", "pad.toolbar.timeslider.title": "Linha de tempo", "pad.toolbar.savedRevision.title": "Salvar revisão", @@ -54,7 +55,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.modals.connected": "Ligado.", "pad.modals.reconnecting": "Reconectando-se ao seu bloco…", "pad.modals.forcereconnect": "Forçar reconexão", @@ -106,6 +106,7 @@ "pad.impexp.importbutton": "Importar agora", "pad.impexp.importing": "Importando...", "pad.impexp.confirmimport": "A importação de um ficheiro irá substituir o texto atual do pad. Tem certeza que deseja continuar?", + "pad.impexp.padHasData": "Não fomos capazes de importar este ficheiro porque esta Almofada já tinha alterações, consulte importar para um novo bloco", "pad.impexp.uploadFailed": "O upload falhou. Por favor, tente novamente", "pad.impexp.importfailed": "A importação falhou", "pad.impexp.copypaste": "Por favor, copie e cole" diff --git a/src/locales/ru.json b/src/locales/ru.json index cd030bad..bd2143a1 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -13,14 +13,14 @@ "pad.toolbar.bold.title": "полужирный (Ctrl-B)", "pad.toolbar.italic.title": "курсив (Ctrl-I)", "pad.toolbar.underline.title": "подчёркивание (Ctrl-U)", - "pad.toolbar.strikethrough.title": "зачёркивание", - "pad.toolbar.ol.title": "Упорядоченный список", - "pad.toolbar.ul.title": "Неупорядоченный список", + "pad.toolbar.strikethrough.title": "Зачёркивание (Ctrl+5)", + "pad.toolbar.ol.title": "Упорядоченный список (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Неупорядоченный список (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Отступ (TAB)", "pad.toolbar.unindent.title": "Выступ (Shift+TAB)", "pad.toolbar.undo.title": "Отменить (Ctrl-Z)", "pad.toolbar.redo.title": "Вернуть (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Очистить цвета документа", + "pad.toolbar.clearAuthorship.title": "Очистить цвета документа (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Импорт/экспорт с использованием различных форматов файлов", "pad.toolbar.timeslider.title": "Шкала времени", "pad.toolbar.savedRevision.title": "Сохранить версию", @@ -48,12 +48,12 @@ "pad.importExport.import": "Загрузить любой текстовый файл или документ", "pad.importExport.importSuccessful": "Успешно!", "pad.importExport.export": "Экспортировать текущий документ как:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Обычный текст", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (документ OpenOffice)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Вы можете импортировать только из обычного текста или HTML. Для более продвинутых функций импорта, пожалуйста, <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\">установите AbiWord</a>.", "pad.modals.connected": "Подключен.", "pad.modals.reconnecting": "Повторное подключение к вашему документу", diff --git a/src/locales/sco.json b/src/locales/sco.json index efcb187a..f7b01824 100644 --- a/src/locales/sco.json +++ b/src/locales/sco.json @@ -49,7 +49,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Ye can yinly import fae plain tex or HTML formats. Fer mair advanced import features please <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\">install abiword</a>.", "pad.modals.connected": "Connected.", "pad.modals.reconnecting": "Reconnectin til yer pad..", diff --git a/src/locales/sk.json b/src/locales/sk.json index 8f376926..02c5c978 100644 --- a/src/locales/sk.json +++ b/src/locales/sk.json @@ -51,7 +51,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Importovať môžete len čistý text alebo HTML. Pre pokročilejšie funkcie importu prosím nainštalujte „<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\">Abiword</a>“.", "pad.modals.connected": "Pripojené.", "pad.modals.reconnecting": "Opätovné pripájanie k vášmu Padu...", diff --git a/src/locales/sl.json b/src/locales/sl.json index 8e57cffa..41a6ce76 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -10,14 +10,14 @@ "pad.toolbar.bold.title": "Krepko (Ctrl-B)", "pad.toolbar.italic.title": "Ležeče (Ctrl-I)", "pad.toolbar.underline.title": "Podčrtano (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Prečrtano", - "pad.toolbar.ol.title": "Oštevilčen seznam", - "pad.toolbar.ul.title": "Vrstični seznam", + "pad.toolbar.strikethrough.title": "Prečrtano (Ctrl+5)", + "pad.toolbar.ol.title": "Oštevilčen seznam (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Neurejen seznam (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Zamik desno (TAB)", "pad.toolbar.unindent.title": "Zamik levo (Shift+TAB)", "pad.toolbar.undo.title": "Razveljavi (Ctrl-Z)", "pad.toolbar.redo.title": "Ponovno uveljavi (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Počisti barvo avtorstva", + "pad.toolbar.clearAuthorship.title": "Počisti barvo avtorstva (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Izvozi/Uvozi različne oblike zapisov", "pad.toolbar.timeslider.title": "Drsnik zgodovine", "pad.toolbar.savedRevision.title": "Shrani predelavo", @@ -50,7 +50,6 @@ "pad.importExport.exportword": "DOC (zapis Microsoft Word)", "pad.importExport.exportpdf": "PDF (zapis Acrobat PDF)", "pad.importExport.exportopen": "ODF (zapis Open Document)", - "pad.importExport.exportdokuwiki": "DokuWiki (zapis DokuWiki)", "pad.importExport.abiword.innerHTML": "Uvoziti je mogoče le običajno neoblikovano besedilo in zapise HTML. Za naprednejše zmožnosti namestite <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\\\">program Abiword</a>.", "pad.modals.connected": "Povezano.", "pad.modals.reconnecting": "Poteka povezovanje z dokumentom ...", diff --git a/src/locales/sq.json b/src/locales/sq.json index 54e76067..6374ea62 100644 --- a/src/locales/sq.json +++ b/src/locales/sq.json @@ -9,17 +9,17 @@ "pad.toolbar.bold.title": "Të trasha (Ctrl-B)", "pad.toolbar.italic.title": "Të pjerrëta (Ctrl-I)", "pad.toolbar.underline.title": "Të nënvizuara (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Hequrvije", - "pad.toolbar.ol.title": "Listë e renditur", - "pad.toolbar.ul.title": "Listë e parenditur", - "pad.toolbar.indent.title": "Brendazi", - "pad.toolbar.unindent.title": "Jashtazi", + "pad.toolbar.strikethrough.title": "Hequrvije (Ctrl+5)", + "pad.toolbar.ol.title": "Listë e renditur (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Listë e parenditur (Ctrl+Shift+L)", + "pad.toolbar.indent.title": "Brendazi (TAB)", + "pad.toolbar.unindent.title": "Jashtazi (Shift+TAB)", "pad.toolbar.undo.title": "Zhbëje (Ctrl-Z)", "pad.toolbar.redo.title": "Ribëje (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Hiq Ngjyra Autorësish", + "pad.toolbar.clearAuthorship.title": "Hiqju Ngjyra Autorësish (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importoni/Eksportoni nga/në formate të tjera kartelash", "pad.toolbar.timeslider.title": "Rrjedha kohore", - "pad.toolbar.savedRevision.title": "Ruaje Rishikin", + "pad.toolbar.savedRevision.title": "Ruaje Rishikimin", "pad.toolbar.settings.title": "Rregullime", "pad.toolbar.embed.title": "Ndajeni me të tjerët dhe Trupëzojeni këtë bllok", "pad.toolbar.showusers.title": "Shfaq përdoruesit në këtë bllok", @@ -49,14 +49,13 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Mund të importoni vetëm prej formati tekst i thjeshtë ose html. Për veçori më të përparuara importimi, ju lutemi, <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\">instaloni Abiword-in</a>.", "pad.modals.connected": "I lidhur.", "pad.modals.reconnecting": "Po rilidheni te blloku juaj..", "pad.modals.forcereconnect": "Rilidhje e detyruar", "pad.modals.userdup": "Hapur në një tjetër dritare", "pad.modals.userdup.explanation": "Ky bllok duket se gjendet i hapur në më shumë se një dritare shfletuesi në këtë kompjuter.", - "pad.modals.userdup.advice": "Rilidhu që të përdoret kjo dritare, më mirë.", + "pad.modals.userdup.advice": "Rilidhuni që të përdoret kjo dritare.", "pad.modals.unauth": "I paautorizuar", "pad.modals.unauth.explanation": "Ndërkohë që shihnit këtë dritare, lejet tuaja kanë ndryshuar. Provoni të rilidheni.", "pad.modals.looping.explanation": "Ka probleme komunikimi me shërbyesin e njëkohësimit.", @@ -66,14 +65,14 @@ "pad.modals.initsocketfail.cause": "Ka gjasa që kjo vjen për shkak të një problemi me shfletuesin tuaj ose lidhjen tuaj në internet.", "pad.modals.slowcommit.explanation": "Shërbyesi nuk po përgjigjet.", "pad.modals.slowcommit.cause": "Kjo mund të vijë për shkak problemesh lidhjeje me rrjetin.", - "pad.modals.badChangeset.explanation": "Një përpunim që keni bërë u shpall i paligjshëm nga shërbyesu i njëkohësimit.", + "pad.modals.badChangeset.explanation": "Një përpunim që keni bërë u vlerësua si i paligjshëm nga shërbyesi i njëkohësimit.", "pad.modals.badChangeset.cause": "Kjo mund të jetë për shkak të një formësimi të gabuar të shërbyesit ose ndonjë tjetër sjelljeje të papritur. Ju lutemi, lidhuni me përgjegjësin e shërbimit, nëse mendoni që ky është një gabim. Provoni të rilidheni që të vazhdoni përpunimin.", "pad.modals.corruptPad.explanation": "Blloku te i cili po përpiqeni të hyni është i dëmtuar.", "pad.modals.corruptPad.cause": "Kjo mund të vijë nga një formësim i gabuar shërbyesi ose ndonjë tjetër sjellje e papritur. Ju lutemi, lidhuni me përgjegjësin e shërbimit.", "pad.modals.deleted": "I fshirë.", "pad.modals.deleted.explanation": "Ky bllok është hequr.", "pad.modals.disconnected": "Jeni shkëputur.", - "pad.modals.disconnected.explanation": "U pre lidhja me shërbyesin", + "pad.modals.disconnected.explanation": "U ndërpre lidhja me shërbyesin", "pad.modals.disconnected.cause": "Shërbyesi mund të mos jetë në punë. Ju lutemi, njoftoni përgjegjësin e shërbimit, nëse kjo vazhdon të ndodhë.", "pad.share": "Ndajeni këtë bllok me të tjerët", "pad.share.readonly": "Vetëm për lexim", @@ -103,11 +102,12 @@ "timeslider.month.october": "Tetor", "timeslider.month.november": "Nëntor", "timeslider.month.december": "Dhjetor", + "timeslider.unnamedauthors": "{{num}} i paemërt {[plural(num) një: autor, tjetër: autorë ]}", "pad.savedrevs.marked": "Ky rishikim tani është shënuar si rishikim i ruajtur", "pad.userlist.entername": "Jepni emrin tuaj", "pad.userlist.unnamed": "pa emër", "pad.userlist.guest": "Vizitor", - "pad.userlist.deny": "Mohoje", + "pad.userlist.deny": "Hidheni Tej", "pad.userlist.approve": "Miratoje", "pad.editbar.clearcolors": "Të hiqen ngjyra autorësish në krejt dokumentin?", "pad.impexp.importbutton": "Importoje Tani", diff --git a/src/locales/sr-ec.json b/src/locales/sr-ec.json new file mode 100644 index 00000000..9fd20b18 --- /dev/null +++ b/src/locales/sr-ec.json @@ -0,0 +1,59 @@ +{ + "@metadata": { + "authors": [ + "Aktron", + "Milicevic01", + "Милан Јелисавчић" + ] + }, + "index.newPad": "Нови Пад", + "pad.toolbar.bold.title": "Подебљано (Ctrl-B)", + "pad.toolbar.italic.title": "Искошено (Ctrl-I)", + "pad.toolbar.underline.title": "Подвучено (Ctrl-U)", + "pad.toolbar.strikethrough.title": "Прецртано", + "pad.toolbar.ol.title": "Уређен списак", + "pad.toolbar.ul.title": "Неуређен списак", + "pad.toolbar.indent.title": "Увлачење (TAB)", + "pad.toolbar.undo.title": "Опозови (Ctrl+Z)", + "pad.toolbar.settings.title": "Подешавања", + "pad.colorpicker.save": "Сачувај", + "pad.colorpicker.cancel": "Откажи", + "pad.loading": "Учитавање...", + "pad.wrongPassword": "Ваша лозинка није исправна", + "pad.settings.myView": "Мој приказ", + "pad.settings.fontType": "Врста фонта:", + "pad.settings.fontType.normal": "Нормално", + "pad.settings.fontType.monospaced": "Monospace", + "pad.settings.globalView": "Глобални приказ", + "pad.settings.language": "Језик:", + "pad.importExport.import_export": "Увоз/извоз", + "pad.importExport.import": "Отпремите било коју текстуалну датотеку или документ", + "pad.importExport.importSuccessful": "Успело!", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "чист текст", + "pad.importExport.exportpdf": "PDF", + "pad.modals.connected": "Повезано.", + "pad.modals.slowcommit.explanation": "Сервер не одговара.", + "pad.modals.deleted": "Обрисано.", + "pad.share": "Дели овај пад", + "pad.share.readonly": "Само за читање", + "pad.share.link": "Веза", + "pad.chat": "Ћаскање", + "pad.chat.title": "Отворите ћаскање за овај пад.", + "pad.chat.loadmessages": "Учитајте више порука.", + "timeslider.month.january": "јануар", + "timeslider.month.february": "фебруар", + "timeslider.month.march": "март", + "timeslider.month.april": "април", + "timeslider.month.may": "мај", + "timeslider.month.june": "јун", + "timeslider.month.july": "јул", + "timeslider.month.august": "август", + "timeslider.month.september": "септембар", + "timeslider.month.october": "октобар", + "timeslider.month.november": "новембар", + "timeslider.month.december": "децембар", + "pad.userlist.approve": "одобрено", + "pad.impexp.importbutton": "Увези одмах", + "pad.impexp.importing": "Увожење..." +} diff --git a/src/locales/sv.json b/src/locales/sv.json index 623def9a..ae9b1f9a 100644 --- a/src/locales/sv.json +++ b/src/locales/sv.json @@ -11,14 +11,14 @@ "pad.toolbar.bold.title": "Fet (Ctrl-B)", "pad.toolbar.italic.title": "Kursiv (Ctrl-I)", "pad.toolbar.underline.title": "Understruken (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Genomstruken", - "pad.toolbar.ol.title": "Numrerad lista", - "pad.toolbar.ul.title": "Osorterad lista", + "pad.toolbar.strikethrough.title": "Genomstruken (Ctrl+5)", + "pad.toolbar.ol.title": "Numrerad lista (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Onumrerad lista (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Öka indrag (TABB)", "pad.toolbar.unindent.title": "Minska indrag (Shift+TABB)", "pad.toolbar.undo.title": "Ångra (Ctrl-Z)", "pad.toolbar.redo.title": "Gör om (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "Rensa författarfärger", + "pad.toolbar.clearAuthorship.title": "Rensa författarfärger (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Importera/exportera från/till olika filformat", "pad.toolbar.timeslider.title": "Tidsreglage", "pad.toolbar.savedRevision.title": "Spara version", @@ -28,12 +28,14 @@ "pad.colorpicker.save": "Spara", "pad.colorpicker.cancel": "Avbryt", "pad.loading": "Läser in...", + "pad.noCookie": "Kunde inte hitta några kakor. Var god tillåt kakor i din webbläsare!", "pad.passwordRequired": "Du behöver ett lösenord för att få tillgång till detta block", "pad.permissionDenied": "Du har inte åtkomstbehörighet för detta block", "pad.wrongPassword": "Ditt lösenord var fel", "pad.settings.padSettings": "Blockinställningar", "pad.settings.myView": "Min vy", "pad.settings.stickychat": "Chatten alltid på skärmen", + "pad.settings.chatandusers": "Visa chatt och användare", "pad.settings.colorcheck": "Författarskapsfärger", "pad.settings.linenocheck": "Radnummer", "pad.settings.rtlcheck": "Vill du läsa innehållet från höger till vänster?", @@ -46,13 +48,13 @@ "pad.importExport.import": "Ladda upp textfiler eller dokument", "pad.importExport.importSuccessful": "Åtgärden slutfördes!", "pad.importExport.export": "Export aktuellt block som:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Oformaterad text", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.exportdokuwiki": "DokuWiki", - "pad.importExport.abiword.innerHTML": "Du kan endast importera från oformaterad text eller html-format. För mer avancerade importeringsfunktioner, var god <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\">installera abiword</a>.", + "pad.importExport.abiword.innerHTML": "Du kan endast importera från oformaterad text eller HTML-format. För mer avancerade importeringsfunktioner, var god <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\">installera abiword</a>.", "pad.modals.connected": "Ansluten.", "pad.modals.reconnecting": "Återansluter till ditt block...", "pad.modals.forcereconnect": "Tvinga återanslutning", @@ -60,7 +62,7 @@ "pad.modals.userdup.explanation": "Detta block verkar vara öppet i mer än ett fönster på denna dator.", "pad.modals.userdup.advice": "Återanslut för att använda detta fönster istället.", "pad.modals.unauth": "Inte godkänd", - "pad.modals.unauth.explanation": "Din behörighet ändrades medan du visar denna sida. Försök att återansluta.", + "pad.modals.unauth.explanation": "Din behörighet ändrades medan du visade denna sida. Försök att återansluta.", "pad.modals.looping.explanation": "Kommunikationsproblem med synkroniseringsservern har uppstått.", "pad.modals.looping.cause": "Kanske du är ansluten via en inkompatibel brandvägg eller proxy.", "pad.modals.initsocketfail": "Servern kan inte nås.", @@ -69,7 +71,7 @@ "pad.modals.slowcommit.explanation": "Servern svarar inte.", "pad.modals.slowcommit.cause": "Detta kan bero på problem med nätverksanslutningen.", "pad.modals.badChangeset.explanation": "En redigering som du gjort klassificerades som otillåten av synkroniseringsservern.", - "pad.modals.badChangeset.cause": "Detta kan bero på en felaktig konfiguration av servern eller något annat oväntad beteende. Var god kontakta tjänstadministratören om du anser att detta är ett fel. Försök ansluta igen för att fortsätta redigera.", + "pad.modals.badChangeset.cause": "Detta kan bero på en felaktig konfiguration av servern eller något annat oväntad beteende. Var god kontakta tjänsteadministratören om du upplever att detta är ett fel. Försök att ansluta igen för att fortsätta redigera.", "pad.modals.corruptPad.explanation": "Blocket du försöker komma åt är skadat.", "pad.modals.corruptPad.cause": "Detta kan bero på en felaktig konfiguration av servern eller något annat oväntad beteende. Var god kontakta tjänstadministratören.", "pad.modals.deleted": "Raderad.", @@ -84,7 +86,7 @@ "pad.chat": "Chatt", "pad.chat.title": "Öppna chatten för detta block.", "pad.chat.loadmessages": "Läs in fler meddelanden", - "timeslider.pageTitle": "Tidsreglage för {{appTitle}}", + "timeslider.pageTitle": "{{appTitle}} tidsreglage", "timeslider.toolbar.returnbutton": "Återvänd till blocket", "timeslider.toolbar.authors": "Författare:", "timeslider.toolbar.authorsList": "Inga författare", @@ -107,6 +109,7 @@ "timeslider.month.december": "december", "timeslider.unnamedauthors": "{{num}} {[plural(num) one: namnlös författare, other: namnlösa författare]}", "pad.savedrevs.marked": "Denna version är nu markerad som en sparad version", + "pad.savedrevs.timeslider": "Du kan se sparade versioner med tidsreglaget", "pad.userlist.entername": "Ange ditt namn", "pad.userlist.unnamed": "namnlös", "pad.userlist.guest": "Gäst", @@ -117,6 +120,7 @@ "pad.impexp.importing": "Importerar...", "pad.impexp.confirmimport": "Att importera en fil kommer att skriva över den aktuella texten i blocket. Är du säker på att du vill fortsätta?", "pad.impexp.convertFailed": "Vi kunde inte importera denna fil. Var god använd ett annat dokumentformat eller kopiera och klistra in den manuellt", + "pad.impexp.padHasData": "Vi kunde inte importera denna fil eftersom detta block redan har redigerats. Importera den till ett nytt block.", "pad.impexp.uploadFailed": "Uppladdningen misslyckades, var god försök igen", "pad.impexp.importfailed": "Importering misslyckades", "pad.impexp.copypaste": "Var god kopiera och klistra in", diff --git a/src/locales/te.json b/src/locales/te.json index 41341d1e..19878206 100644 --- a/src/locales/te.json +++ b/src/locales/te.json @@ -50,7 +50,6 @@ "pad.importExport.exportword": "మైక్రోసాఫ్ట్ వర్డ్", "pad.importExport.exportpdf": "పీ డి ఎఫ్", "pad.importExport.exportopen": "ఓ డి ఎఫ్ (ఓపెన్ డాక్యుమెంట్ ఫార్మాట్)", - "pad.importExport.exportdokuwiki": "డాక్యువికి", "pad.modals.connected": "సంబంధం కుదిరింది.", "pad.modals.reconnecting": "మీ పలకకు మరల సంబంధం కలుపుతుంది...", "pad.modals.forcereconnect": "బలవంతంగానైనా సంబంధం కుదిరించు", diff --git a/src/locales/tr.json b/src/locales/tr.json index a83c7313..b0b6dbee 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -20,7 +20,7 @@ "pad.toolbar.unindent.title": "Girintiyi azalt (Shift+TAB)", "pad.toolbar.undo.title": "Geri Al (Ctrl-Z)", "pad.toolbar.redo.title": "Yinele (Ctrl+Y)", - "pad.toolbar.clearAuthorship.title": "Yazarlık Renklerini Temizle", + "pad.toolbar.clearAuthorship.title": "Yazarlık Renklerini Temizle (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Farklı dosya biçimlerini içeri/dışarı aktar", "pad.toolbar.timeslider.title": "Zaman Çizelgesi", "pad.toolbar.savedRevision.title": "Düzeltmeyi Kaydet", @@ -30,6 +30,7 @@ "pad.colorpicker.save": "Kaydet", "pad.colorpicker.cancel": "İptal", "pad.loading": "Yükleniyor...", + "pad.noCookie": "Çerez bulunamadı. Lütfen tarayıcınızda çerezlere izin veriniz!", "pad.passwordRequired": "Bu bloknota erişebilmeniz için parolaya ihtiyacınız var", "pad.permissionDenied": "Bu bloknota erişmeye izniniz yok", "pad.wrongPassword": "Parolanız yanlış", @@ -48,12 +49,12 @@ "pad.importExport.import": "Herhangi bir metin dosyası ya da belgesi yükle", "pad.importExport.importSuccessful": "Başarılı!", "pad.importExport.export": "Mevcut bloknotu şu olarak dışa aktar:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Düz metin", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Açık Doküman Biçimi)", - "pad.importExport.exportdokuwiki": "VikiBelge", "pad.importExport.abiword.innerHTML": "Yalnızca düz metin ya da HTML biçimlerini içe aktarabilirsiniz. Daha fazla gelişmiş içe aktarım özellikleri için <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\">AbiWord'ü yükleyin</a>.", "pad.modals.connected": "Bağlandı.", "pad.modals.reconnecting": "Bloknotunuza tekrar bağlanılıyor...", diff --git a/src/locales/uk.json b/src/locales/uk.json index 41686c5e..79a39a33 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -53,7 +53,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (документ OpenOffice)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Ви можете імпортувати лище формати простого тексту або html. Для більш просунутих способів імпорту <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\">встановіть abiword</a>.", "pad.modals.connected": "З'єднано.", "pad.modals.reconnecting": "Перепідлючення до Вашого документу..", diff --git a/src/locales/vi.json b/src/locales/vi.json index 608e9645..da0baf50 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -3,7 +3,8 @@ "authors": [ "Baonguyen21022003", "Minh Nguyen", - "Tuankiet65" + "Tuankiet65", + "Max20091" ] }, "index.newPad": "Tạo một Pad mới", @@ -11,9 +12,9 @@ "pad.toolbar.bold.title": "In đậm (Ctrl-B)", "pad.toolbar.italic.title": "In nghiêng (Ctrl-I)", "pad.toolbar.underline.title": "Gạch chân (Ctrl-U)", - "pad.toolbar.strikethrough.title": "Gạch ngang", - "pad.toolbar.ol.title": "Danh sách Có Đánh số", - "pad.toolbar.ul.title": "Danh sách Không Đánh số", + "pad.toolbar.strikethrough.title": "Gạch ngang (Ctrl+5)", + "pad.toolbar.ol.title": "Danh sách Có Đánh số (Ctrl+Shift+N)", + "pad.toolbar.ul.title": "Danh sách Không Đánh số (Ctrl+Shift+L)", "pad.toolbar.indent.title": "Tăng lề (TAB)", "pad.toolbar.unindent.title": "Giảm lề (Shift+TAB)", "pad.toolbar.undo.title": "Hoàn tác (Ctrl-Z)", @@ -51,7 +52,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "Bạn chỉ có thể nhập vào từ văn bản thuần túy hay định dạng HTML. Nếu muốn có nhiều chức năng nhập hơn xin hãy <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\">cài đặt abiword</a>.", "pad.modals.connected": "Đã kết nối lại.", "pad.modals.reconnecting": "Kết nối lại tới pad của bạn", diff --git a/src/locales/zh-hans.json b/src/locales/zh-hans.json index 61aa99fc..4af3cb48 100644 --- a/src/locales/zh-hans.json +++ b/src/locales/zh-hans.json @@ -24,8 +24,8 @@ "pad.toolbar.indent.title": "增加缩进(TAB)", "pad.toolbar.unindent.title": "减少缩进(Shift+TAB)", "pad.toolbar.undo.title": "撤消 (Ctrl-Z)", - "pad.toolbar.redo.title": "重做 (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "清除作者颜色", + "pad.toolbar.redo.title": "重做(Ctrl+Y)", + "pad.toolbar.clearAuthorship.title": "清除作者颜色(Ctrl+Shift+C)", "pad.toolbar.import_export.title": "从不同的文件格式导入/导出", "pad.toolbar.timeslider.title": "时间轴", "pad.toolbar.savedRevision.title": "保存修订", @@ -35,12 +35,14 @@ "pad.colorpicker.save": "保存", "pad.colorpicker.cancel": "取消", "pad.loading": "载入中……", + "pad.noCookie": "无法找到Cookie。请在您的浏览器中允许Cookie!", "pad.passwordRequired": "您需要密码才能访问这个记事本", "pad.permissionDenied": "您没有访问这个记事本的权限", "pad.wrongPassword": "您的密码错了", "pad.settings.padSettings": "记事本设置", "pad.settings.myView": "我的视窗", "pad.settings.stickychat": "总是显示聊天屏幕", + "pad.settings.chatandusers": "显示聊天和用户", "pad.settings.colorcheck": "作者颜色", "pad.settings.linenocheck": "行号", "pad.settings.rtlcheck": "从右到左阅读内容吗?", @@ -53,12 +55,12 @@ "pad.importExport.import": "上载任何文本文件或档案", "pad.importExport.importSuccessful": "成功!", "pad.importExport.export": "当前记事本导出为:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "纯文本", "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF(开放文档格式)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "您只能导入纯文本或HTML格式。<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\">安裝abiword</a>取得更多高级的导入功能。", "pad.modals.connected": "已连接。", "pad.modals.reconnecting": "重新连接到您的记事本...", @@ -99,9 +101,9 @@ "timeslider.exportCurrent": "当前版本导出为:", "timeslider.version": "版本 {{version}}", "timeslider.saved": "在{{year}}年{{month}}{{day}}日保存", - "timeslider.dateformat": "{{year}}年{{month}}{{day}}日 {{hours}}时:{{minutes}}分:{{seconds}}秒", - "timeslider.month.january": "一月", - "timeslider.month.february": "二月", + "timeslider.dateformat": "{{year}}年{{month}}月{{day}}日 {{hours}}时:{{minutes}}分:{{seconds}}秒", + "timeslider.month.january": "1月", + "timeslider.month.february": "2月", "timeslider.month.march": "三月", "timeslider.month.april": "四月", "timeslider.month.may": "五月", @@ -114,6 +116,7 @@ "timeslider.month.december": "十二月", "timeslider.unnamedauthors": "{{num}}个匿名作者", "pad.savedrevs.marked": "这一修订现在被标记为已保存的修订版本", + "pad.savedrevs.timeslider": "您可以使用时间滑块查阅已保存的版本", "pad.userlist.entername": "输入您的姓名", "pad.userlist.unnamed": "匿名", "pad.userlist.guest": "访客", @@ -124,6 +127,7 @@ "pad.impexp.importing": "正在导入...", "pad.impexp.confirmimport": "导入的文件将覆盖记事本的当前文本。你确定要继续吗?", "pad.impexp.convertFailed": "我们无法导入此文档。请使用他文档格式或手动复制贴上。", + "pad.impexp.padHasData": "我们无法导入此文件,因为此记事本已经变更,请导入到一个新的记事本", "pad.impexp.uploadFailed": "上载失败,请重试", "pad.impexp.importfailed": "导入失败", "pad.impexp.copypaste": "请复制粘贴", diff --git a/src/locales/zh-hant.json b/src/locales/zh-hant.json index 0707648a..a692d0ca 100644 --- a/src/locales/zh-hant.json +++ b/src/locales/zh-hant.json @@ -14,9 +14,9 @@ "pad.toolbar.bold.title": "粗體(Ctrl-B)", "pad.toolbar.italic.title": "斜體(Ctrl-I)", "pad.toolbar.underline.title": "底線(Ctrl-U)", - "pad.toolbar.strikethrough.title": "刪除線", - "pad.toolbar.ol.title": "有序清單", - "pad.toolbar.ul.title": "無序清單", + "pad.toolbar.strikethrough.title": "刪除線(Ctrl+5)", + "pad.toolbar.ol.title": "有序清單(Ctrl+Shift+N)", + "pad.toolbar.ul.title": "無序清單(Ctrl+Shift+L)", "pad.toolbar.indent.title": "縮排(TAB)", "pad.toolbar.unindent.title": "凸排(Shift+TAB)", "pad.toolbar.undo.title": "撤銷(Ctrl-Z)", @@ -54,7 +54,6 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF(開放文件格式)", - "pad.importExport.exportdokuwiki": "DokuWiki", "pad.importExport.abiword.innerHTML": "您只可以純文字或html格式檔匯入。<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\">安裝abiword</a>取得更多進階的匯入功能。", "pad.modals.connected": "已連線。", "pad.modals.reconnecting": "重新連接到您的pad...", @@ -73,7 +72,7 @@ "pad.modals.slowcommit.cause": "這可能是因為網路連線問題所造成。", "pad.modals.badChangeset.explanation": "您的一個編輯被同步伺服器類為非法。", "pad.modals.badChangeset.cause": "這可能由於伺服器的配置錯誤或遇到意外問題。若您認為這是錯誤,請聯繫伺服器管理員。如要繼續編輯,請嘗試重新連接。", - "pad.modals.corruptPad.explanation": "您試圖訪問的平板已損壞。", + "pad.modals.corruptPad.explanation": "您試圖存取的平板已損壞。", "pad.modals.corruptPad.cause": "這可能由於伺服器的配置錯誤或遇到意外問題。請聯繫伺服器管理員。", "pad.modals.deleted": "已刪除。", "pad.modals.deleted.explanation": "此pad已被移除。", @@ -94,16 +93,16 @@ "timeslider.toolbar.exportlink.title": "匯出", "timeslider.exportCurrent": "匯出當前版本為:", "timeslider.version": "版本{{version}}", - "timeslider.saved": "{{year}}年{{month}}月{{day}}日儲存", + "timeslider.saved": "{{year}}年{{month}}{{day}}日儲存", "timeslider.dateformat": "{{year}}年{{month}}月{{day}}日 {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "1月", - "timeslider.month.february": "2月", + "timeslider.month.february": "二月", "timeslider.month.march": "3月", "timeslider.month.april": "4月", "timeslider.month.may": "5月", "timeslider.month.june": "6月", "timeslider.month.july": "7月", - "timeslider.month.august": "8月", + "timeslider.month.august": "八月", "timeslider.month.september": "9月", "timeslider.month.october": "10月", "timeslider.month.november": "11月", diff --git a/src/node/db/API.js b/src/node/db/API.js index 79f5fbeb..97d5162d 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -263,7 +263,7 @@ exports.getText = function(padID, rev, callback) { if(ERR(err, callback)) return; - data = {text: atext.text}; + var data = {text: atext.text}; callback(null, data); }) @@ -368,7 +368,7 @@ exports.getHTML = function(padID, rev, callback) if(ERR(err, callback)) return; html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head html += "</body></html>"; - data = {html: html}; + var data = {html: html}; callback(null, data); }); } @@ -380,7 +380,7 @@ exports.getHTML = function(padID, rev, callback) if(ERR(err, callback)) return; html = "<!DOCTYPE HTML><html><body>" +html; // adds HTML head html += "</body></html>"; - data = {html: html}; + var data = {html: html}; callback(null, data); }); } @@ -410,11 +410,16 @@ exports.setHTML = function(padID, html, callback) if(ERR(err, callback)) return; // add a new changeset with the new html to the pad - importHtml.setPadHTML(pad, cleanText(html), callback); - - //update the clients on the pad - padMessageHandler.updatePadClients(pad, callback); - + importHtml.setPadHTML(pad, cleanText(html), function(e){ + if(e){ + callback(new customError("HTML is malformed","apierror")); + return; + }else{ + //update the clients on the pad + padMessageHandler.updatePadClients(pad, callback); + return; + } + }); }); } @@ -427,8 +432,8 @@ getChatHistory(padId, start, end), returns a part of or the whole chat-history o Example returns: -{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"}, - {"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}} +{"code":0,"message":"ok","data":{"messages":[{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"}, + {"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}]}} {code: 1, message:"start is higher or equal to the current chatHead", data: null} @@ -489,6 +494,33 @@ exports.getChatHistory = function(padID, start, end, callback) }); } +/** +appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp + +Example returns: + +{code: 0, message:"ok", data: null +{code: 1, message:"padID does not exist", data: null} +*/ +exports.appendChatMessage = function(padID, text, authorID, time, callback) +{ + //text is required + if(typeof text != "string") + { + callback(new customError("text is no string","apierror")); + return; + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + pad.appendChatMessage(text, authorID, parseInt(time)); + callback(); + }); +} + /*****************/ /**PAD FUNCTIONS */ /*****************/ @@ -513,6 +545,117 @@ exports.getRevisionsCount = function(padID, callback) } /** +getSavedRevisionsCount(padID) returns the number of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: {savedRevisions: 42}} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.getSavedRevisionsCount = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {savedRevisions: pad.getSavedRevisionsNumber()}); + }); +} + +/** +listSavedRevisions(padID) returns the list of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.listSavedRevisions = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {savedRevisions: pad.getSavedRevisionsList()}); + }); +} + +/** +saveRevision(padID) returns the list of saved revisions of this pad + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"padID does not exist", data: null} +*/ +exports.saveRevision = function(padID, rev, callback) +{ + //check if rev is set + if(typeof rev == "function") + { + callback = rev; + rev = undefined; + } + + //check if rev is a number + if(rev !== undefined && typeof rev != "number") + { + //try to parse the number + if(!isNaN(parseInt(rev))) + { + rev = parseInt(rev); + } + else + { + callback(new customError("rev is not a number", "apierror")); + return; + } + } + + //ensure this is not a negativ number + if(rev !== undefined && rev < 0) + { + callback(new customError("rev is a negativ number","apierror")); + return; + } + + //ensure this is not a float value + if(rev !== undefined && !is_int(rev)) + { + callback(new customError("rev is a float value","apierror")); + return; + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + //the client asked for a special revision + if(rev !== undefined) + { + //check if this is a valid revision + if(rev > pad.getHeadRevisionNumber()) + { + callback(new customError("rev is higher than the head revision of the pad","apierror")); + return; + } + } else { + rev = pad.getHeadRevisionNumber(); + } + + authorManager.createAuthor('API', function(err, author) { + if(ERR(err, callback)) return; + + pad.addSavedRevision(rev, author.authorID, 'Saved through API call'); + callback(); + }); + }); +} + +/** getLastEdited(padID) returns the timestamp of the last revision of the pad Example returns: @@ -584,6 +727,117 @@ exports.deletePad = function(padID, callback) pad.remove(callback); }); } +/** + restoreRevision(padID, [rev]) Restores revision from past as new changeset + + Example returns: + + {code:0, message:"ok", data:null} + {code: 1, message:"padID does not exist", data: null} + */ +exports.restoreRevision = function (padID, rev, callback) +{ + var Changeset = require("ep_etherpad-lite/static/js/Changeset"); + var padMessage = require("ep_etherpad-lite/node/handler/PadMessageHandler.js"); + + //check if rev is a number + if (rev !== undefined && typeof rev != "number") + { + //try to parse the number + if (!isNaN(parseInt(rev))) + { + rev = parseInt(rev); + } + else + { + callback(new customError("rev is not a number", "apierror")); + return; + } + } + + //ensure this is not a negativ number + if (rev !== undefined && rev < 0) + { + callback(new customError("rev is a negativ number", "apierror")); + return; + } + + //ensure this is not a float value + if (rev !== undefined && !is_int(rev)) + { + callback(new customError("rev is a float value", "apierror")); + return; + } + + //get the pad + getPadSafe(padID, true, function (err, pad) + { + if (ERR(err, callback)) return; + + + //check if this is a valid revision + if (rev > pad.getHeadRevisionNumber()) + { + callback(new customError("rev is higher than the head revision of the pad", "apierror")); + return; + } + + pad.getInternalRevisionAText(rev, function (err, atext) + { + if (ERR(err, callback)) return; + + var oldText = pad.text(); + atext.text += "\n"; + function eachAttribRun(attribs, func) + { + var attribsIter = Changeset.opIterator(attribs); + var textIndex = 0; + var newTextStart = 0; + var newTextEnd = atext.text.length; + while (attribsIter.hasNext()) + { + var op = attribsIter.next(); + var nextIndex = textIndex + op.chars; + if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) + { + func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); + } + textIndex = nextIndex; + } + } + + // create a new changeset with a helper builder object + var builder = Changeset.builder(oldText.length); + + // assemble each line into the builder + eachAttribRun(atext.attribs, function (start, end, attribs) + { + builder.insert(atext.text.substring(start, end), attribs); + }); + + var lastNewlinePos = oldText.lastIndexOf('\n'); + if (lastNewlinePos < 0) + { + builder.remove(oldText.length - 1, 0); + } else + { + builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1); + builder.remove(oldText.length - lastNewlinePos - 1, 0); + } + + var changeset = builder.toString(); + + //append the changeset + pad.appendRevision(changeset); + // + padMessage.updatePadClients(pad, function () + { + }); + callback(null, null); + }); + + }); +}; /** copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true, diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index 5ba608e9..e0f569ef 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -21,7 +21,6 @@ var ERR = require("async-stacktrace"); var db = require("./DB").db; -var async = require("async"); var customError = require("../utils/customError"); var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 4670696a..53847600 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -54,6 +54,21 @@ Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() { return this.head; }; +Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() { + return this.savedRevisions.length; +}; + +Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { + var savedRev = new Array(); + for(var rev in this.savedRevisions){ + savedRev.push(this.savedRevisions[rev].revNum); + } + savedRev.sort(function(a, b) { + return a - b; + }); + return savedRev; +}; + Pad.prototype.getPublicStatus = function getPublicStatus() { return this.publicStatus; }; @@ -135,7 +150,7 @@ Pad.prototype.getRevisionDate = function getRevisionDate(revNum, callback) { Pad.prototype.getAllAuthors = function getAllAuthors() { var authors = []; - for(key in this.pool.numToAttrib) + for(var key in this.pool.numToAttrib) { if(this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { @@ -461,7 +476,6 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { // if the pad exists, we should abort, unless forced. function(callback) { - console.log("destinationID", destinationID, force); padManager.doesPadExists(destinationID, function (err, exists) { if(ERR(err, callback)) return; @@ -470,9 +484,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { { if (!force) { - console.log("erroring out without force"); + console.error("erroring out without force"); callback(new customError("destinationID already exists","apierror")); - console.log("erroring out without force - after"); + console.error("erroring out without force - after"); return; } else // exists and forcing @@ -521,12 +535,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { function(callback) { var revHead = _this.head; - //console.log(revHead); for(var i=0;i<=revHead;i++) { db.get("pad:"+sourceID+":revs:"+i, function (err, rev) { - //console.log("HERE"); - if (ERR(err, callback)) return; db.set("pad:"+destinationID+":revs:"+i, rev); }); @@ -538,10 +549,8 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { function(callback) { var authorIDs = _this.getAllAuthors(); - authorIDs.forEach(function (authorID) { - console.log("authors"); authorManager.addPad(authorID, destinationID); }); @@ -555,7 +564,9 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { if(destGroupID) db.setSub("group:" + destGroupID, ["pads", destinationID], 1); // Initialize the new pad (will update the listAllPads cache) - padManager.getPad(destinationID, null, callback) + setTimeout(function(){ + padManager.getPad(destinationID, null, callback) // this runs too early. + },10); } // series ], function(err) @@ -690,7 +701,7 @@ Pad.prototype.isPasswordProtected = function isPasswordProtected() { Pad.prototype.addSavedRevision = function addSavedRevision(revNum, savedById, label) { //if this revision is already saved, return silently for(var i in this.savedRevisions){ - if(this.savedRevisions.revNum === revNum){ + if(this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum){ return; } } diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index df3c3826..6fae57ff 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -20,7 +20,6 @@ var ERR = require("async-stacktrace"); -var db = require("./DB").db; var async = require("async"); var authorManager = require("./AuthorManager"); var padManager = require("./PadManager"); diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js index 71315adc..f8000e47 100644 --- a/src/node/db/SessionManager.js +++ b/src/node/db/SessionManager.js @@ -351,7 +351,15 @@ function listSessionsWithDBKey (dbkey, callback) { exports.getSessionInfo(sessionID, function(err, sessionInfo) { - if(ERR(err, callback)) return; + if (err == "apierror: sessionID does not exist") + { + console.warn("Found bad session " + sessionID + " in " + dbkey + "."); + } + else if(ERR(err, callback)) + { + return; + } + sessions[sessionID] = sessionInfo; callback(); }); diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js index 52a504f1..5c45ddb3 100644 --- a/src/node/db/SessionStore.js +++ b/src/node/db/SessionStore.js @@ -5,8 +5,6 @@ */ var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'), - utils = require('ep_etherpad-lite/node_modules/connect/lib/utils'), - Session = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/session'), db = require('ep_etherpad-lite/node/db/DB').db, log4js = require('ep_etherpad-lite/node_modules/log4js'), messageLogger = log4js.getLogger("SessionStore"); diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index 48185d80..30f5a442 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -71,7 +71,7 @@ exports.begin_define_block = function (name) { } exports.end_define_block = function () { - content = exports.end_capture(); + var content = exports.end_capture(); return content; } diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 273a58a6..b4d24201 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -345,10 +345,109 @@ var version = , "getChatHistory" : ["padID", "start", "end"] , "getChatHead" : ["padID"] } +, "1.2.11": + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "listAllPads" : [] + , "createDiffHTML" : ["padID", "startRev", "endRev"] + , "createPad" : ["padID", "text"] + , "createGroupPad" : ["groupID", "padName", "text"] + , "createAuthor" : ["name"] + , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] + , "listPadsOfAuthor" : ["authorID"] + , "createSession" : ["groupID", "authorID", "validUntil"] + , "deleteSession" : ["sessionID"] + , "getSessionInfo" : ["sessionID"] + , "listSessionsOfGroup" : ["groupID"] + , "listSessionsOfAuthor" : ["authorID"] + , "getText" : ["padID", "rev"] + , "setText" : ["padID", "text"] + , "getHTML" : ["padID", "rev"] + , "setHTML" : ["padID", "html"] + , "getAttributePool" : ["padID"] + , "getRevisionsCount" : ["padID"] + , "getSavedRevisionsCount" : ["padID"] + , "listSavedRevisions" : ["padID"] + , "saveRevision" : ["padID", "rev"] + , "getRevisionChangeset" : ["padID", "rev"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "copyPad" : ["sourceID", "destinationID", "force"] + , "movePad" : ["sourceID", "destinationID", "force"] + , "getReadOnlyID" : ["padID"] + , "getPadID" : ["roID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + , "getAuthorName" : ["authorID"] + , "padUsers" : ["padID"] + , "sendClientsMessage" : ["padID", "msg"] + , "listAllGroups" : [] + , "checkToken" : [] + , "getChatHistory" : ["padID"] + , "getChatHistory" : ["padID", "start", "end"] + , "getChatHead" : ["padID"] + , "restoreRevision" : ["padID", "rev"] + } +, "1.2.12": + { "createGroup" : [] + , "createGroupIfNotExistsFor" : ["groupMapper"] + , "deleteGroup" : ["groupID"] + , "listPads" : ["groupID"] + , "listAllPads" : [] + , "createDiffHTML" : ["padID", "startRev", "endRev"] + , "createPad" : ["padID", "text"] + , "createGroupPad" : ["groupID", "padName", "text"] + , "createAuthor" : ["name"] + , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] + , "listPadsOfAuthor" : ["authorID"] + , "createSession" : ["groupID", "authorID", "validUntil"] + , "deleteSession" : ["sessionID"] + , "getSessionInfo" : ["sessionID"] + , "listSessionsOfGroup" : ["groupID"] + , "listSessionsOfAuthor" : ["authorID"] + , "getText" : ["padID", "rev"] + , "setText" : ["padID", "text"] + , "getHTML" : ["padID", "rev"] + , "setHTML" : ["padID", "html"] + , "getAttributePool" : ["padID"] + , "getRevisionsCount" : ["padID"] + , "getSavedRevisionsCount" : ["padID"] + , "listSavedRevisions" : ["padID"] + , "saveRevision" : ["padID", "rev"] + , "getRevisionChangeset" : ["padID", "rev"] + , "getLastEdited" : ["padID"] + , "deletePad" : ["padID"] + , "copyPad" : ["sourceID", "destinationID", "force"] + , "movePad" : ["sourceID", "destinationID", "force"] + , "getReadOnlyID" : ["padID"] + , "getPadID" : ["roID"] + , "setPublicStatus" : ["padID", "publicStatus"] + , "getPublicStatus" : ["padID"] + , "setPassword" : ["padID", "password"] + , "isPasswordProtected" : ["padID"] + , "listAuthorsOfPad" : ["padID"] + , "padUsersCount" : ["padID"] + , "getAuthorName" : ["authorID"] + , "padUsers" : ["padID"] + , "sendClientsMessage" : ["padID", "msg"] + , "listAllGroups" : [] + , "checkToken" : [] + , "appendChatMessage" : ["padID", "text", "authorID", "time"] + , "getChatHistory" : ["padID"] + , "getChatHistory" : ["padID", "start", "end"] + , "getChatHead" : ["padID"] + , "restoreRevision" : ["padID", "rev"] + } }; // set the latest available API version here -exports.latestApiVersion = '1.2.10'; +exports.latestApiVersion = '1.2.12'; // exports the versions so it can be used by the new Swagger endpoint exports.version = version; @@ -404,6 +503,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res) if(fields["apikey"] != apikey.trim()) { + res.statusCode = 401; res.send({code: 4, message: "no or wrong API Key", data: null}); return; } diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 5bedcce2..0654deb4 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -4,6 +4,7 @@ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) + * 2014 John McLear (Etherpad Foundation / McLear Ltd) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +22,7 @@ var ERR = require("async-stacktrace"); var exporthtml = require("../utils/ExportHtml"); var exporttxt = require("../utils/ExportTxt"); -var exportdokuwiki = require("../utils/ExportDokuWiki"); -var padManager = require("../db/PadManager"); +var exportEtherpad = require("../utils/ExportEtherpad"); var async = require("async"); var fs = require("fs"); var settings = require('../utils/Settings'); @@ -54,14 +54,20 @@ exports.doExport = function(req, res, padId, type) // if fileName is set then set it to the padId, note that fileName is returned as an array. if(hookFileName.length) fileName = hookFileName; - //tell the browser that this is a downloadable file res.attachment(fileName + "." + type); //if this is a plain text export, we can do this directly // We have to over engineer this because tabs are stored as attributes and not plain text - - if(type == "txt") + if(type == "etherpad"){ + exportEtherpad.getPadRaw(padId, function(err, pad){ + if(!err){ + res.send(pad); + // return; + } + }); + } + else if(type == "txt") { var txt; var randNum; @@ -129,26 +135,6 @@ exports.doExport = function(req, res, padId, type) if(err && err != "stop") ERR(err); }) } - else if(type == 'dokuwiki') - { - var randNum; - var srcFile, destFile; - - async.series([ - //render the dokuwiki document - function(callback) - { - exportdokuwiki.getPadDokuWikiDocument(padId, req.params.rev, function(err, dokuwiki) - { - res.send(dokuwiki); - callback("stop"); - }); - }, - ], function(err) - { - if(err && err != "stop") throw err; - }); - } else { var html; @@ -172,8 +158,12 @@ exports.doExport = function(req, res, padId, type) //if this is a html export, we can send this from here directly if(type == "html") { - res.send(html); - callback("stop"); + // do any final changes the plugin might want to make cake + hooks.aCallFirst("exportHTMLSend", html, function(err, newHTML){ + if(newHTML.length) html = newHTML; + res.send(html); + callback("stop"); + }); } else //write the html export to a file { diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 60fa5ffb..2dad8b3d 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -5,6 +5,7 @@ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * 2012 Iván Eixarch + * 2014 John McLear (Etherpad Foundation / McLear Ltd) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +30,7 @@ var ERR = require("async-stacktrace") , formidable = require('formidable') , os = require("os") , importHtml = require("../utils/ImportHtml") + , importEtherpad = require("../utils/ImportEtherpad") , log4js = require("log4js") , hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); @@ -53,7 +55,8 @@ exports.doImport = function(req, res, padId) var srcFile, destFile , pad , text - , importHandledByPlugin; + , importHandledByPlugin + , directDatabaseAccess; var randNum = Math.floor(Math.random()*0xFFFFFFFF); @@ -83,7 +86,7 @@ exports.doImport = function(req, res, padId) //this allows us to accept source code files like .c or .java function(callback) { var fileEnding = path.extname(srcFile).toLowerCase() - , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm"] + , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad"] , fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1); //if the file ending is known, continue as normal @@ -92,9 +95,14 @@ exports.doImport = function(req, res, padId) } //we need to rename this file with a .txt ending else { - var oldSrcFile = srcFile; - srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt"); - fs.rename(oldSrcFile, srcFile, callback); + if(settings.allowUnknownFileEnds === true){ + var oldSrcFile = srcFile; + srcFile = path.join(path.dirname(srcFile),path.basename(srcFile, fileEnding)+".txt"); + fs.rename(oldSrcFile, srcFile, callback); + }else{ + console.warn("Not allowing unknown file type to be imported", fileEnding); + callback("uploadFailed"); + } } }, function(callback){ @@ -111,11 +119,38 @@ exports.doImport = function(req, res, padId) } }); }, + function(callback) { + var fileEnding = path.extname(srcFile).toLowerCase() + var fileIsEtherpad = (fileEnding === ".etherpad"); + + if(fileIsEtherpad){ + // we do this here so we can see if the pad has quit ea few edits + padManager.getPad(padId, function(err, _pad){ + var headCount = _pad.head; + if(headCount >= 10){ + apiLogger.warn("Direct database Import attempt of a pad that already has content, we wont be doing this") + return callback("padHasData"); + }else{ + fs.readFile(srcFile, "utf8", function(err, _text){ + directDatabaseAccess = true; + importEtherpad.setPadRaw(padId, _text, function(err){ + callback(); + }); + }); + } + }); + }else{ + callback(); + } + }, //convert file to html function(callback) { - if(!importHandledByPlugin){ + if(!importHandledByPlugin || !directDatabaseAccess){ var fileEnding = path.extname(srcFile).toLowerCase(); var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); + var fileIsTXT = (fileEnding === ".txt"); + if (fileIsTXT) abiword = false; // Don't use abiword for text files + // See https://github.com/ether/etherpad-lite/issues/2572 if (abiword && !fileIsHTML) { abiword.convertFile(srcFile, destFile, "htm", function(err) { //catch convert errors @@ -136,24 +171,28 @@ exports.doImport = function(req, res, padId) }, function(callback) { - if (!abiword) { - // Read the file with no encoding for raw buffer access. - fs.readFile(destFile, function(err, buf) { - if (err) throw err; - var isAscii = true; - // Check if there are only ascii chars in the uploaded file - for (var i=0, len=buf.length; i<len; i++) { - if (buf[i] > 240) { - isAscii=false; - break; + if (!abiword){ + if(!directDatabaseAccess) { + // Read the file with no encoding for raw buffer access. + fs.readFile(destFile, function(err, buf) { + if (err) throw err; + var isAscii = true; + // Check if there are only ascii chars in the uploaded file + for (var i=0, len=buf.length; i<len; i++) { + if (buf[i] > 240) { + isAscii=false; + break; + } } - } - if (isAscii) { - callback(); - } else { - callback("uploadFailed"); - } - }); + if (isAscii) { + callback(); + } else { + callback("uploadFailed"); + } + }); + }else{ + callback(); + } } else { callback(); } @@ -170,66 +209,101 @@ exports.doImport = function(req, res, padId) //read the text function(callback) { - fs.readFile(destFile, "utf8", function(err, _text){ - if(ERR(err, callback)) return; - text = _text; - // Title needs to be stripped out else it appends it to the pad.. - text = text.replace("<title>", "<!-- <title>"); - text = text.replace("</title>","</title>-->"); + if(!directDatabaseAccess){ + fs.readFile(destFile, "utf8", function(err, _text){ + if(ERR(err, callback)) return; + text = _text; + // Title needs to be stripped out else it appends it to the pad.. + text = text.replace("<title>", "<!-- <title>"); + text = text.replace("</title>","</title>-->"); - //node on windows has a delay on releasing of the file lock. - //We add a 100ms delay to work around this - if(os.type().indexOf("Windows") > -1){ - setTimeout(function() {callback();}, 100); - } else { - callback(); - } - }); + //node on windows has a delay on releasing of the file lock. + //We add a 100ms delay to work around this + if(os.type().indexOf("Windows") > -1){ + setTimeout(function() {callback();}, 100); + } else { + callback(); + } + }); + }else{ + callback(); + } }, //change text of the pad and broadcast the changeset function(callback) { - var fileEnding = path.extname(srcFile).toLowerCase(); - if (abiword || fileEnding == ".htm" || fileEnding == ".html") { - try{ - importHtml.setPadHTML(pad, text); - }catch(e){ - apiLogger.warn("Error importing, possibly caused by malformed HTML"); + if(!directDatabaseAccess){ + var fileEnding = path.extname(srcFile).toLowerCase(); + if (abiword || fileEnding == ".htm" || fileEnding == ".html") { + importHtml.setPadHTML(pad, text, function(e){ + if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML"); + }); + } else { + pad.setText(text); } - } else { - pad.setText(text); } - padMessageHandler.updatePadClients(pad, callback); + + // Load the Pad into memory then brodcast updates to all clients + padManager.unloadPad(padId); + padManager.getPad(padId, function(err, _pad){ + var pad = _pad; + padManager.unloadPad(padId); + // direct Database Access means a pad user should perform a switchToPad + // and not attempt to recieve updated pad data.. + if(!directDatabaseAccess){ + padMessageHandler.updatePadClients(pad, function(){ + callback(); + }); + }else{ + callback(); + } + }); + }, //clean up temporary files function(callback) { - //for node < 0.7 compatible - var fileExists = fs.exists || path.exists; - async.parallel([ - function(callback){ - fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); }); - }, - function(callback){ - fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); }); - } - ], callback); + if(!directDatabaseAccess){ + //for node < 0.7 compatible + var fileExists = fs.exists || path.exists; + async.parallel([ + function(callback){ + fileExists (srcFile, function(exist) { (exist)? fs.unlink(srcFile, callback): callback(); }); + }, + function(callback){ + fileExists (destFile, function(exist) { (exist)? fs.unlink(destFile, callback): callback(); }); + } + ], callback); + }else{ + callback(); + } } ], function(err) { - var status = "ok"; //check for known errors and replace the status - if(err == "uploadFailed" || err == "convertFailed") + if(err == "uploadFailed" || err == "convertFailed" || err == "padHasData") { status = err; err = null; } ERR(err); - + //close the connection - 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); + res.send( + "<head> \ + <script type='text/javascript' src='../../static/js/jquery.js'></script> \ + </head> \ + <script> \ + $(window).load(function(){ \ + if(navigator.userAgent.indexOf('MSIE') === -1){ \ + document.domain = document.domain; \ + } \ + var impexp = window.parent.padimpexp.handleFrameCall('" + directDatabaseAccess +"', '" + status + "'); \ + }) \ + </script>" + , 200); }); } diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index e1ac994e..c210ab2b 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -37,6 +37,7 @@ var _ = require('underscore'); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); var channels = require("channels"); var stats = require('../stats'); +var remoteAddress = require("../utils/RemoteAddress").remoteAddress; /** * A associative array that saves informations about a session @@ -93,8 +94,18 @@ exports.handleConnect = function(client) */ exports.kickSessionsFromPad = function(padID) { + if(typeof socketio.sockets['clients'] !== 'function') + return; + //skip if there is nobody on this pad - if(socketio.sockets.clients(padID).length == 0) + var roomClients = [], room = socketio.sockets.adapter.rooms[padID]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + + if(roomClients.length == 0) return; //disconnect everyone from this pad @@ -115,14 +126,16 @@ exports.handleDisconnect = function(client) //if this connection was already etablished with a handshake, send a disconnect message to the others if(session && session.author) { - client.get('remoteAddress', function(er, ip) { - //Anonymize the IP address if IP logging is disabled - if(settings.disableIPlogging) { - ip = 'ANONYMOUS'; - } - accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad') - }) + // Get the IP address from our persistant object + var ip = remoteAddress[client.id]; + + // Anonymize the IP address if IP logging is disabled + if(settings.disableIPlogging) { + ip = 'ANONYMOUS'; + } + + accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad') //get the author color out of the db authorManager.getAuthorColorId(session.author, function(err, color) @@ -220,6 +233,8 @@ exports.handleMessage = function(client, message) } else { messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type); } + } else if(message.type == "SWITCH_TO_PAD") { + handleSwitchToPad(client, message); } else { messageLogger.warn("Dropped message, unknown Message Type " + message.type); } @@ -233,18 +248,7 @@ exports.handleMessage = function(client, message) { // client tried to auth for the first time (first msg from the client) if(message.type == "CLIENT_READY") { - // Remember this information since we won't - // have the cookie in further socket.io messages. - // This information will be used to check if - // the sessionId of this connection is still valid - // since it could have been deleted by the API. - sessioninfos[client.id].auth = - { - sessionID: message.sessionID, - padID: message.padId, - token : message.token, - password: message.password - }; + createSessionInfo(client, message); } // Note: message.sessionID is an entirely different kind of @@ -253,11 +257,10 @@ exports.handleMessage = function(client, message) // FIXME: Use a hook instead // FIXME: Allow to override readwrite access with readonly - // FIXME: A message might arrive but wont have an auth object, this is obviously bad so we should deny it // Simulate using the load testing tool if(!sessioninfos[client.id].auth){ console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.") - callback(); + return; }else{ var auth = sessioninfos[client.id].auth; var checkAccessCallback = function(err, statusObject) @@ -493,14 +496,19 @@ function handleSuggestUserName(client, message) return; } - var padId = sessioninfos[client.id].padId, - clients = socketio.sockets.clients(padId); + var padId = sessioninfos[client.id].padId; + var roomClients = [], room = socketio.sockets.adapter.rooms[padId]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } //search the author and send him this message - for(var i = 0; i < clients.length; i++) { - var session = sessioninfos[clients[i].id]; + for(var i = 0; i < roomClients.length; i++) { + var session = sessioninfos[roomClients[i].id]; if(session && session.author == message.data.payload.unnamedId) { - clients[i].json.send(message); + roomClients[i].json.send(message); break; } } @@ -648,12 +656,17 @@ function handleUserChanges(data, cb) , op while(iterator.hasNext()) { op = iterator.next() - if(op.opcode != '+') continue; + + //+ can add text with attribs + //= can change or add attribs + //- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool + op.attribs.split('*').forEach(function(attr) { if(!attr) return attr = wireApool.getAttrib(attr) if(!attr) return - if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset); + //the empty author is used in the clearAuthorship functionality so this should be the only exception + if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset); }) } @@ -694,6 +707,14 @@ function handleUserChanges(data, cb) // and can be applied after "c". try { + // a changeset can be based on an old revision with the same changes in it + // prevent eplite from accepting it TODO: better send the client a NEW_CHANGES + // of that revision + if(baseRev+1 == r && c == changeset) { + client.json.send({disconnect:"badChangeset"}); + stats.meter('failedChangesets').mark(); + return callback(new Error("Won't apply USER_CHANGES, because it contains an already accepted changeset")); + } changeset = Changeset.follow(c, changeset, false, apool); }catch(e){ client.json.send({disconnect:"badChangeset"}); @@ -724,7 +745,16 @@ function handleUserChanges(data, cb) return callback(new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length)); } - pad.appendRevision(changeset, thisSession.author); + try + { + pad.appendRevision(changeset, thisSession.author); + } + catch(e) + { + client.json.send({disconnect:"badChangeset"}); + stats.meter('failedChangesets').mark(); + return callback(e) + } var correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) { @@ -753,7 +783,13 @@ function handleUserChanges(data, cb) exports.updatePadClients = function(pad, callback) { //skip this step if noone is on this pad - var roomClients = socketio.sockets.clients(pad.id); + var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + if(roomClients.length==0) return callback(); @@ -766,10 +802,8 @@ exports.updatePadClients = function(pad, callback) var revCache = {}; //go trough all sessions on this pad - async.forEach(roomClients, function(client, callback) - { + async.forEach(roomClients, function(client, callback){ var sid = client.id; - //https://github.com/caolan/async#whilst //send them all new changesets async.whilst( @@ -816,10 +850,10 @@ exports.updatePadClients = function(pad, callback) client.json.send(wireMsg); } - - sessioninfos[sid].time = currentTime; - sessioninfos[sid].rev = r; - + if(sessioninfos[sid]){ + sessioninfos[sid].time = currentTime; + sessioninfos[sid].rev = r; + } callback(null); } ], callback); @@ -875,6 +909,48 @@ function _correctMarkersInPad(atext, apool) { return builder.toString(); } +function handleSwitchToPad(client, message) +{ + // clear the session and leave the room + var currentSession = sessioninfos[client.id]; + var padId = currentSession.padId; + var roomClients = [], room = socketio.sockets.adapter.rooms[padId]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + + for(var i = 0; i < roomClients.length; i++) { + var sinfo = sessioninfos[roomClients[i].id]; + if(sinfo && sinfo.author == currentSession.author) { + // fix user's counter, works on page refresh or if user closes browser window and then rejoins + sessioninfos[roomClients[i].id] = {}; + roomClients[i].leave(padId); + } + } + + // start up the new pad + createSessionInfo(client, message); + handleClientReady(client, message); +} + +function createSessionInfo(client, message) +{ + // Remember this information since we won't + // have the cookie in further socket.io messages. + // This information will be used to check if + // the sessionId of this connection is still valid + // since it could have been deleted by the API. + sessioninfos[client.id].auth = + { + sessionID: message.sessionID, + padID: message.padId, + token : message.token, + password: message.password + }; +} + /** * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token * and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad @@ -998,6 +1074,11 @@ function handleClientReady(client, message) { authorManager.getAuthor(authorId, function(err, author) { + if(!author && !err) + { + messageLogger.error("There is no author for authorId:", authorId); + return callback(); + } if(ERR(err, callback)) return; historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients) callback(); @@ -1015,7 +1096,13 @@ function handleClientReady(client, message) return callback(); //Check if this author is already on the pad, if yes, kick the other sessions! - var roomClients = socketio.sockets.clients(padIds.padId); + var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + for(var i = 0; i < roomClients.length; i++) { var sinfo = sessioninfos[roomClients[i].id]; if(sinfo && sinfo.author == author) { @@ -1032,19 +1119,19 @@ function handleClientReady(client, message) sessioninfos[client.id].readonly = padIds.readonly; //Log creation/(re-)entering of a pad - client.get('remoteAddress', function(er, ip) { - //Anonymize the IP address if IP logging is disabled - if(settings.disableIPlogging) { - ip = 'ANONYMOUS'; - } + var ip = remoteAddress[client.id]; - if(pad.head > 0) { - accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad'); - } - else if(pad.head == 0) { - accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad'); - } - }) + //Anonymize the IP address if IP logging is disabled + if(settings.disableIPlogging) { + ip = 'ANONYMOUS'; + } + + if(pad.head > 0) { + accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad'); + } + else if(pad.head == 0) { + accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad'); + } //If this is a reconnect, we don't have to send the client the ClientVars again if(message.reconnect == true) @@ -1165,7 +1252,14 @@ function handleClientReady(client, message) client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers); //Run trough all sessions of this pad - async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback) + var roomClients = [], room = socketio.sockets.adapter.rooms[pad.id]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + + async.forEach(roomClients, function(roomClient, callback) { var author; @@ -1540,10 +1634,15 @@ function composePadChangesets(padId, startNum, endNum, callback) changeset = changesets[startNum]; var pool = pad.apool(); - for(var r=startNum+1;r<endNum;r++) - { - var cs = changesets[r]; - changeset = Changeset.compose(changeset, cs, pool); + try { + for(var r=startNum+1;r<endNum;r++) { + var cs = changesets[r]; + changeset = Changeset.compose(changeset, cs, pool); + } + } catch(e){ + // r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3 + console.warn("failed to compose cs in pad:",padId," startrev:",startNum," current rev:",r); + return callback(e); } callback(null); @@ -1561,8 +1660,16 @@ function composePadChangesets(padId, startNum, endNum, callback) * Get the number of users in a pad */ exports.padUsersCount = function (padID, callback) { + + var roomClients = [], room = socketio.sockets.adapter.rooms[padID]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + callback(null, { - padUsersCount: socketio.sockets.clients(padID).length + padUsersCount: roomClients.length }); } @@ -1572,7 +1679,14 @@ exports.padUsersCount = function (padID, callback) { exports.padUsers = function (padID, callback) { var result = []; - async.forEach(socketio.sockets.clients(padID), function(roomClient, callback) { + var roomClients = [], room = socketio.sockets.adapter.rooms[padID]; + if (room) { + for (var id in room) { + roomClients.push(socketio.sockets.adapter.nsp.connected[id]); + } + } + + async.forEach(roomClients, function(roomClient, callback) { var s = sessioninfos[roomClient.id]; if(s) { authorManager.getAuthor(s.author, function(err, author) { diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.js index b3e046d2..0a7361f4 100644 --- a/src/node/handler/SocketIORouter.js +++ b/src/node/handler/SocketIORouter.js @@ -24,6 +24,7 @@ var log4js = require('log4js'); var messageLogger = log4js.getLogger("message"); var securityManager = require("../db/SecurityManager"); var readOnlyManager = require("../db/ReadOnlyManager"); +var remoteAddress = require("../utils/RemoteAddress").remoteAddress; var settings = require('../utils/Settings'); /** @@ -56,11 +57,15 @@ exports.setSocketIO = function(_socket) { socket.sockets.on('connection', function(client) { + + // Broken: See http://stackoverflow.com/questions/4647348/send-message-to-specific-client-with-socket-io-and-node-js + // Fixed by having a persistant object, ideally this would actually be in the database layer + // TODO move to database layer if(settings.trustProxy && client.handshake.headers['x-forwarded-for'] !== undefined){ - client.set('remoteAddress', client.handshake.headers['x-forwarded-for']); + remoteAddress[client.id] = client.handshake.headers['x-forwarded-for']; } else{ - client.set('remoteAddress', client.handshake.address.address); + remoteAddress[client.id] = client.handshake.address; } var clientAuthorized = false; diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index c6573c80..bf849419 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -10,24 +10,11 @@ var server; var serverName; exports.createServer = function () { - //try to get the git version - var version = ""; - try - { - var rootPath = path.resolve(npm.dir, '..'); - var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); - var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); - version = fs.readFileSync(refPath, "utf-8"); - version = version.substring(0, 7); - console.log("Your Etherpad git version is " + version); - } - catch(e) - { - console.warn("Can't get git version for server header\n" + e.message) - } console.log("Report bugs at https://github.com/ether/etherpad-lite/issues") - serverName = "Etherpad " + version + " (http://etherpad.org)"; + serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)"; + + console.log("Your Etherpad version is " + settings.getEpVersion() + " (" + settings.getGitCommit() + ")"); exports.restartServer(); @@ -38,7 +25,6 @@ exports.createServer = function () { else{ console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json"); } - } exports.restartServer = function () { @@ -56,7 +42,7 @@ exports.restartServer = function () { console.log( "SSL -- server key file: " + settings.ssl.key ); console.log( "SSL -- Certificate Authority's certificate file: " + settings.ssl.cert ); - options = { + var options = { key: fs.readFileSync( settings.ssl.key ), cert: fs.readFileSync( settings.ssl.cert ) }; diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index d8f19bba..1ae8d7b5 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -1,10 +1,9 @@ -var path = require('path'); var eejs = require('ep_etherpad-lite/node/eejs'); +var settings = require('ep_etherpad-lite/node/utils/Settings'); var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); var _ = require('underscore'); var semver = require('semver'); -var async = require('async'); exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/admin/plugins', function(req, res) { @@ -14,18 +13,25 @@ exports.expressCreateServer = function (hook_name, args, cb) { search_results: {}, errors: [], }; - res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins.html", render_args) ); }); args.app.get('/admin/plugins/info', function(req, res) { - res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {}) ); + var gitCommit = settings.getGitCommit(); + var epVersion = settings.getEpVersion(); + res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", + { + gitCommit: gitCommit, + epVersion: epVersion + }) + ); }); } exports.socketio = function (hook_name, args, cb) { var io = args.io.of("/pluginfw/installer"); io.on('connection', function (socket) { - if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return; + + if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; socket.on("getInstalled", function (query) { // send currently installed plugins @@ -85,7 +91,7 @@ exports.socketio = function (hook_name, args, cb) { socket.on("install", function (plugin_name) { installer.install(plugin_name, function (er) { if(er) console.warn(er) - socket.emit("finished:install", {plugin: plugin_name, error: er? er.message : null}); + socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null}); }); }); @@ -107,4 +113,4 @@ function sortPluginList(plugins, property, /*ASC?*/dir) { // a must be equal to b return 0; }) -}
\ No newline at end of file +} diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js index 2a48d289..4986f093 100644 --- a/src/node/hooks/express/adminsettings.js +++ b/src/node/hooks/express/adminsettings.js @@ -1,7 +1,5 @@ -var path = require('path'); var eejs = require('ep_etherpad-lite/node/eejs'); var settings = require('ep_etherpad-lite/node/utils/Settings'); -var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var fs = require('fs'); @@ -22,7 +20,8 @@ exports.expressCreateServer = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) { var io = args.io.of("/settings"); io.on('connection', function (socket) { - if (!socket.handshake.session.user || !socket.handshake.session.user.is_admin) return; + + if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; socket.on("load", function (query) { fs.readFile('settings.json', 'utf8', function (err,data) { diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index f5a3e5a1..f3f05163 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -5,7 +5,7 @@ var importHandler = require('../../handler/ImportHandler'); exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/p/:pad/:rev?/export/:type', function(req, res, next) { - var types = ["pdf", "doc", "txt", "html", "odt", "dokuwiki"]; + var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"]; //send a 404 if we don't support this filetype if (types.indexOf(req.params.type) == -1) { next(); diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js index 9a0a52bf..d60d3863 100644 --- a/src/node/hooks/express/padreadonly.js +++ b/src/node/hooks/express/padreadonly.js @@ -10,7 +10,6 @@ exports.expressCreateServer = function (hook_name, args, cb) { { var html; var padId; - var pad; async.series([ //translate the read only pad to a padId diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index 524bab3d..35d6d074 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -1,6 +1,5 @@ -var log4js = require('log4js'); -var socketio = require('socket.io'); var settings = require('../../utils/Settings'); +var socketio = require('socket.io'); var socketIORouter = require("../../handler/SocketIORouter"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var webaccess = require("ep_etherpad-lite/node/hooks/express/webaccess"); @@ -11,14 +10,25 @@ var connect = require('connect'); exports.expressCreateServer = function (hook_name, args, cb) { //init socket.io and redirect all requests to the MessageHandler - var io = socketio.listen(args.server); + // there shouldn't be a browser that isn't compatible to all + // transports in this list at once + // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling + var io = socketio({ + transports: settings.socketTransportProtocols + }).listen(args.server); /* Require an express session cookie to be present, and load the * session. See http://www.danielbaulig.de/socket-ioexpress for more * info */ - io.set('authorization', function (data, accept) { - if (!data.headers.cookie) return accept('No session cookie transmitted.', false); + io.use(function(socket, accept) { + var data = socket.request; + // Use a setting if we want to allow load Testing + if(!data.headers.cookie && settings.loadTest){ + accept(null, true); + }else{ + if (!data.headers.cookie) return accept('No session cookie transmitted.', false); + } // Use connect's cookie parser, because it knows how to parse signed cookies connect.cookieParser(webaccess.secret)(data, {}, function(err){ if(err) { @@ -36,35 +46,17 @@ exports.expressCreateServer = function (hook_name, args, cb) { }); }); - // there shouldn't be a browser that isn't compatible to all - // transports in this list at once - // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling - io.set('transports', settings.socketTransportProtocols ); - - var socketIOLogger = log4js.getLogger("socket.io"); - io.set('logger', { - debug: function (str) - { - socketIOLogger.debug.apply(socketIOLogger, arguments); - }, - info: function (str) - { - socketIOLogger.info.apply(socketIOLogger, arguments); - }, - warn: function (str) - { - socketIOLogger.warn.apply(socketIOLogger, arguments); - }, - error: function (str) - { - socketIOLogger.error.apply(socketIOLogger, arguments); - }, - }); + // var socketIOLogger = log4js.getLogger("socket.io"); + // Debug logging now has to be set at an environment level, this is stupid. + // https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0 + // This debug logging environment is set in Settings.js //minify socket.io javascript - if(settings.minify) - io.enable('browser client minification'); - + // Due to a shitty decision by the SocketIO team minification is + // no longer available, details available at: + // http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0 + // if(settings.minify) io.enable('browser client minification'); + //Initalize the Socket.IO Router socketIORouter.setSocketIO(io); socketIORouter.addComponent("pad", padMessageHandler); diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js index 7d654c1b..e5a2bff0 100644 --- a/src/node/hooks/express/static.js +++ b/src/node/hooks/express/static.js @@ -1,11 +1,8 @@ -var path = require('path'); var minify = require('../../utils/Minify'); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var CachingMiddleware = require('../../utils/caching_middleware'); var settings = require("../../utils/Settings"); -var Yajsml = require('yajsml'); -var fs = require("fs"); -var ERR = require("async-stacktrace"); +var Yajsml = require('etherpad-yajsml'); var _ = require("underscore"); exports.expressCreateServer = function (hook_name, args, cb) { diff --git a/src/node/hooks/express/swagger.js b/src/node/hooks/express/swagger.js index e8daa61c..f606eb88 100644 --- a/src/node/hooks/express/swagger.js +++ b/src/node/hooks/express/swagger.js @@ -1,4 +1,3 @@ -var log4js = require('log4js'); var express = require('express'); var apiHandler = require('../../handler/APIHandler'); var apiCaller = require('./apicalls').apiCaller; @@ -285,6 +284,10 @@ var API = { } }, "response": {"chatHead":{"type":"Message"}} + }, + "appendChatMessage": { + "func": "appendChatMessage", + "description": "appends a chat message" } } }; @@ -356,7 +359,17 @@ exports.expressCreateServer = function (hook_name, args, cb) { args.app.use(basePath, subpath); - swagger.setAppHandler(subpath); + //hack! + var swagger_temp = swagger + swagger = swagger.createNew(subpath); + swagger.params = swagger_temp.params + swagger.queryParam = swagger_temp.queryParam + swagger.pathParam = swagger_temp.pathParam + swagger.bodyParam = swagger_temp.bodyParam + swagger.formParam = swagger_temp.formParam + swagger.headerParam = swagger_temp.headerParam + swagger.error = swagger_temp.error + //swagger.setAppHandler(subpath); swagger.addModels(swaggerModels); diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 3157d68e..151c99fa 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -23,6 +23,10 @@ exports.expressCreateServer = function (hook_name, args, cb) { }); + + // path.join seems to normalize by default, but we'll just be explicit + var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/")); + var url2FilePath = function(url){ var subPath = url.substr("/tests/frontend".length); if (subPath == ""){ @@ -30,8 +34,11 @@ exports.expressCreateServer = function (hook_name, args, cb) { } subPath = subPath.split("?")[0]; - var filePath = path.normalize(npm.root + "/../tests/frontend/") - filePath += subPath.replace("..", ""); + var filePath = path.normalize(path.join(rootTestFolder, subPath)); + // make sure we jail the paths to the test folder, otherwise serve index + if (filePath.indexOf(rootTestFolder) !== 0) { + filePath = path.join(rootTestFolder, "index.html"); + } return filePath; } diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index 6998853f..b798f2c7 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -2,7 +2,6 @@ var express = require('express'); var log4js = require('log4js'); var httpLogger = log4js.getLogger("http"); var settings = require('../../utils/Settings'); -var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var ueberStore = require('../../db/SessionStore'); var stats = require('ep_etherpad-lite/node/stats') diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js index 62631b93..67815659 100644 --- a/src/node/hooks/i18n.js +++ b/src/node/hooks/i18n.js @@ -1,7 +1,6 @@ var languages = require('languages4translatewiki') , fs = require('fs') , path = require('path') - , express = require('express') , _ = require('underscore') , npm = require('npm') , plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins.js').plugins diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js index 5f12bd97..1d9ac5d3 100644 --- a/src/node/utils/Abiword.js +++ b/src/node/utils/Abiword.js @@ -18,7 +18,6 @@ * limitations under the License. */ -var util = require('util'); var spawn = require('child_process').spawn; var async = require("async"); var settings = require("./Settings"); diff --git a/src/node/utils/ExportDokuWiki.js b/src/node/utils/ExportDokuWiki.js deleted file mode 100644 index f5d2d177..00000000 --- a/src/node/utils/ExportDokuWiki.js +++ /dev/null @@ -1,350 +0,0 @@ -/** - * Copyright 2011 Adrian Lang - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var async = require("async"); - -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var padManager = require("../db/PadManager"); - -function getPadDokuWiki(pad, revNum, callback) -{ - var atext = pad.atext; - var dokuwiki; - async.waterfall([ - // fetch revision atext - - - function (callback) - { - if (revNum != undefined) - { - pad.getInternalRevisionAText(revNum, function (err, revisionAtext) - { - atext = revisionAtext; - callback(err); - }); - } - else - { - callback(null); - } - }, - - // convert atext to dokuwiki text - - function (callback) - { - dokuwiki = getDokuWikiFromAtext(pad, atext); - callback(null); - }], - // run final callback - - - function (err) - { - callback(err, dokuwiki); - }); -} - -function getDokuWikiFromAtext(pad, atext) -{ - var apool = pad.apool(); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - - var tags = ['======', '=====', '**', '//', '__', 'del>']; - var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; - var anumMap = {}; - - props.forEach(function (propName, i) - { - var propTrueNum = apool.putAttrib([propName, true], true); - if (propTrueNum >= 0) - { - anumMap[propTrueNum] = i; - } - }); - - function getLineDokuWiki(text, attribs) - { - var propVals = [false, false, false]; - var ENTER = 1; - var STAY = 2; - var LEAVE = 0; - - // Use order of tags (b/i/u) as order of nesting, for simplicity - // and decent nesting. For example, - // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> - // becomes - // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> - var taker = Changeset.stringIterator(text); - var assem = Changeset.stringAssembler(); - - function emitOpenTag(i) - { - if (tags[i].indexOf('>') !== -1) { - assem.append('<'); - } - assem.append(tags[i]); - } - - function emitCloseTag(i) - { - if (tags[i].indexOf('>') !== -1) { - assem.append('</'); - } - assem.append(tags[i]); - } - - var urls = _findURLs(text); - - var idx = 0; - - function processNextChars(numChars) - { - if (numChars <= 0) - { - return; - } - - var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); - idx += numChars; - - while (iter.hasNext()) - { - var o = iter.next(); - var propChanged = false; - Changeset.eachAttribNumber(o.attribs, function (a) - { - if (a in anumMap) - { - var i = anumMap[a]; // i = 0 => bold, etc. - if (!propVals[i]) - { - propVals[i] = ENTER; - propChanged = true; - } - else - { - propVals[i] = STAY; - } - } - }); - for (var i = 0; i < propVals.length; i++) - { - if (propVals[i] === true) - { - propVals[i] = LEAVE; - propChanged = true; - } - else if (propVals[i] === STAY) - { - propVals[i] = true; // set it back - } - } - // now each member of propVal is in {false,LEAVE,ENTER,true} - // according to what happens at start of span - if (propChanged) - { - // leaving bold (e.g.) also leaves italics, etc. - var left = false; - for (var i = 0; i < propVals.length; i++) - { - var v = propVals[i]; - if (!left) - { - if (v === LEAVE) - { - left = true; - } - } - else - { - if (v === true) - { - propVals[i] = STAY; // tag will be closed and re-opened - } - } - } - - for (var i = propVals.length - 1; i >= 0; i--) - { - if (propVals[i] === LEAVE) - { - emitCloseTag(i); - propVals[i] = false; - } - else if (propVals[i] === STAY) - { - emitCloseTag(i); - } - } - for (var i = 0; i < propVals.length; i++) - { - if (propVals[i] === ENTER || propVals[i] === STAY) - { - emitOpenTag(i); - propVals[i] = true; - } - } - // propVals is now all {true,false} again - } // end if (propChanged) - var chars = o.chars; - if (o.lines) - { - chars--; // exclude newline at end of line, if present - } - var s = taker.take(chars); - - assem.append(_escapeDokuWiki(s)); - } // end iteration over spans in line - for (var i = propVals.length - 1; i >= 0; i--) - { - if (propVals[i]) - { - emitCloseTag(i); - propVals[i] = false; - } - } - } // end processNextChars - if (urls) - { - urls.forEach(function (urlData) - { - var startIndex = urlData[0]; - var url = urlData[1]; - var urlLength = url.length; - processNextChars(startIndex - idx); - assem.append('[['); - - // Do not use processNextChars since a link does not contain syntax and - // needs no escaping - var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + urlLength)); - idx += urlLength; - assem.append(taker.take(iter.next().chars)); - - assem.append(']]'); - }); - } - processNextChars(text.length - idx); - - return assem.toString() + "\n"; - } // end getLineDokuWiki - var pieces = []; - - for (var i = 0; i < textLines.length; i++) - { - var line = _analyzeLine(textLines[i], attribLines[i], apool); - var lineContent = getLineDokuWiki(line.text, line.aline); - - if (line.listLevel && lineContent) - { - if (line.listTypeName == "number") - { - pieces.push(new Array(line.listLevel + 1).join(' ') + ' - '); - } else { - pieces.push(new Array(line.listLevel + 1).join(' ') + '* '); - } - } - pieces.push(lineContent); - } - - return pieces.join(''); -} - -function _analyzeLine(text, aline, apool) -{ - var line = {}; - - // identify list - var lineMarker = 0; - line.listLevel = 0; - if (aline) - { - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()) - { - var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); - if (listType) - { - lineMarker = 1; - listType = /([a-z]+)([12345678])/.exec(listType); - if (listType) - { - line.listTypeName = listType[1]; - line.listLevel = Number(listType[2]); - } - } - } - } - if (lineMarker) - { - line.text = text.substring(1); - line.aline = Changeset.subattribution(aline, 1); - } - else - { - line.text = text; - line.aline = aline; - } - - return line; -} - -exports.getPadDokuWikiDocument = function (padId, revNum, callback) -{ - padManager.getPad(padId, function (err, pad) - { - if (err) - { - callback(err); - return; - } - - getPadDokuWiki(pad, revNum, callback); - }); -}; - -function _escapeDokuWiki(s) -{ - s = s.replace(/(\/\/|\*\*|__)/g, '%%$1%%'); - return s; -} - -// copied from ACE -var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; -var _REGEX_SPACE = /\s/; -var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); -var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); - -// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] - - -function _findURLs(text) -{ - _REGEX_URL.lastIndex = 0; - var urls = null; - var execResult; - while ((execResult = _REGEX_URL.exec(text))) - { - urls = (urls || []); - var startIndex = execResult.index; - var url = execResult[0]; - urls.push([startIndex, url]); - } - - return urls; -} diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js new file mode 100644 index 00000000..46ae0d7a --- /dev/null +++ b/src/node/utils/ExportEtherpad.js @@ -0,0 +1,79 @@ +/** + * 2014 John McLear (Etherpad Foundation / McLear Ltd) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +var async = require("async"); +var db = require("../db/DB").db; +var ERR = require("async-stacktrace"); + +exports.getPadRaw = function(padId, callback){ + async.waterfall([ + function(cb){ + + // Get the Pad + db.findKeys("pad:"+padId, null, function(err,padcontent){ + if(!err){ + cb(err, padcontent); + } + }) + }, + function(padcontent,cb){ + + // Get the Pad available content keys + db.findKeys("pad:"+padId+":*", null, function(err,records){ + if(!err){ + for (var key in padcontent) { records.push(padcontent[key]);} + cb(err, records); + } + }) + }, + function(records, cb){ + var data = {}; + + async.forEachSeries(Object.keys(records), function(key, r){ + + // For each piece of info about a pad. + db.get(records[key], function(err, entry){ + data[records[key]] = entry; + + // Get the Pad Authors + if(entry.pool && entry.pool.numToAttrib){ + var authors = entry.pool.numToAttrib; + async.forEachSeries(Object.keys(authors), function(k, c){ + if(authors[k][0] === "author"){ + var authorId = authors[k][1]; + + // Get the author info + db.get("globalAuthor:"+authorId, function(e, authorEntry){ + if(authorEntry && authorEntry.padIDs) authorEntry.padIDs = padId; + if(!e) data["globalAuthor:"+authorId] = authorEntry; + }); + + } + // console.log("authorsK", authors[k]); + c(null); + }); + } + r(null); // callback; + }); + }, function(err){ + cb(err, data); + }) + } + ], function(err, data){ + callback(null, data); + }); +} diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index 136896f0..297c2d7a 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -18,12 +18,7 @@ * limitations under the License. */ -var async = require("async"); var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var padManager = require("../db/PadManager"); -var ERR = require("async-stacktrace"); -var Security = require('ep_etherpad-lite/static/js/security'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); exports.getPadPlainText = function(pad, revNum){ var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); @@ -60,7 +55,7 @@ exports._analyzeLine = function(text, aline, apool){ var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); if (listType){ lineMarker = 1; - listType = /([a-z]+)([12345678])/.exec(listType); + listType = /([a-z]+)([0-9]+)/.exec(listType); if (listType){ line.listTypeName = listType[1]; line.listLevel = Number(listType[2]); diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 01920da7..9e1ba124 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -30,8 +30,6 @@ function getPadHTML(pad, revNum, callback) var html; async.waterfall([ // fetch revision atext - - function (callback) { if (revNum != undefined) @@ -78,6 +76,14 @@ function getHTMLFromAtext(pad, atext, authorColors) var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; + + hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){ + newProps.forEach(function (propName, i){ + tags.push(propName); + props.push(propName); + }); + }); + // holds a map of used styling attributes (*1, *2, etc) in the apool // and maps them to an index in props // *3:2 -> the attribute *3 means strong @@ -297,10 +303,12 @@ function getHTMLFromAtext(pad, atext, authorColors) // want to deal gracefully with blank lines. // => keeps track of the parents level of indentation var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...] + var listLevels = [] for (var i = 0; i < textLines.length; i++) { var line = _analyzeLine(textLines[i], attribLines[i], apool); var lineContent = getLineHTML(line.text, line.aline); + listLevels.push(line.listLevel) if (line.listLevel)//If we are inside a list { @@ -320,13 +328,27 @@ function getHTMLFromAtext(pad, atext, authorColors) if (whichList >= lists.length)//means we are on a deeper level of indentation than the previous line { + if(lists.length > 0){ + pieces.push('</li>') + } lists.push([line.listLevel, line.listTypeName]); + + // if there is a previous list we need to open x tags, where x is the difference of the levels + // if there is no previous list we need to open x tags, where x is the wanted level + var toOpen = lists.length > 1 ? line.listLevel - lists[lists.length - 2][0] - 1 : line.listLevel - 1 + if(line.listTypeName == "number") { + if(toOpen > 0){ + pieces.push(new Array(toOpen + 1).join('<ol>')) + } pieces.push('<ol class="'+line.listTypeName+'"><li>', lineContent || '<br>'); } else { + if(toOpen > 0){ + pieces.push(new Array(toOpen + 1).join('<ul>')) + } pieces.push('<ul class="'+line.listTypeName+'"><li>', lineContent || '<br>'); } } @@ -355,44 +377,50 @@ function getHTMLFromAtext(pad, atext, authorColors) pieces.push('<br><br>'); } }*/ - else//means we are getting closer to the lowest level of indentation + else//means we are getting closer to the lowest level of indentation or are at the same level { - while (whichList < lists.length - 1) - { + var toClose = lists.length > 0 ? listLevels[listLevels.length - 2] - line.listLevel : 0 + if( toClose > 0){ + pieces.push('</li>') if(lists[lists.length - 1][1] == "number") { - pieces.push('</li></ol>'); + pieces.push(new Array(toClose+1).join('</ol>')) + pieces.push('<li>', lineContent || '<br>'); } else { - pieces.push('</li></ul>'); + pieces.push(new Array(toClose+1).join('</ul>')) + pieces.push('<li>', lineContent || '<br>'); } - lists.length--; + lists = lists.slice(0,whichList+1) + } else { + pieces.push('</li><li>', lineContent || '<br>'); } - pieces.push('</li><li>', lineContent || '<br>'); } } - else//outside any list + else//outside any list, need to close line.listLevel of lists { - while (lists.length > 0)//if was in a list: close it before - { - if(lists[lists.length - 1][1] == "number") - { + if(lists.length > 0){ + if(lists[lists.length - 1][1] == "number"){ pieces.push('</li></ol>'); - } - else - { + pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ol>')) + } else { pieces.push('</li></ul>'); + pieces.push(new Array(listLevels[listLevels.length - 2]).join('</ul>')) } - lists.length--; } - var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", - { + lists = [] + + var context = { line: line, + lineContent: lineContent, apool: apool, attribLine: attribLines[i], text: textLines[i] - }, " ", " ", ""); + } + + var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", context, " ", " ", ""); + if (lineContentFromHook) { pieces.push(lineContentFromHook, ''); @@ -425,37 +453,120 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) { if(ERR(err, callback)) return; - var head = - (noDocType ? '' : '<!doctype html>\n') + - '<html lang="en">\n' + (noDocType ? '' : '<head>\n' + - '<title>' + Security.escapeHTML(padId) + '</title>\n' + - '<meta charset="utf-8">\n' + - '<style> * { font-family: arial, sans-serif;\n' + - 'font-size: 13px;\n' + - 'line-height: 17px; }' + - 'ul.indent { list-style-type: none; }' + - 'ol { list-style-type: decimal; }' + - 'ol ol { list-style-type: lower-latin; }' + - 'ol ol ol { list-style-type: lower-roman; }' + - 'ol ol ol ol { list-style-type: decimal; }' + - 'ol ol ol ol ol { list-style-type: lower-latin; }' + - 'ol ol ol ol ol ol{ list-style-type: lower-roman; }' + - 'ol ol ol ol ol ol ol { list-style-type: decimal; }' + - 'ol ol ol ol ol ol ol ol{ list-style-type: lower-latin; }' + - '</style>\n' + '</head>\n') + - '<body>'; - - var foot = '</body>\n</html>\n'; - - getPadHTML(pad, revNum, function (err, html) - { - if(ERR(err, callback)) return; - callback(null, head + html + foot); + var stylesForExportCSS = ""; + // Include some Styles into the Head for Export + hooks.aCallAll("stylesForExport", padId, function(err, stylesForExport){ + stylesForExport.forEach(function(css){ + stylesForExportCSS += css; + }); + // Core inclusion of head etc. + var head = + (noDocType ? '' : '<!doctype html>\n') + + '<html lang="en">\n' + (noDocType ? '' : '<head>\n' + + '<title>' + Security.escapeHTML(padId) + '</title>\n' + + '<meta charset="utf-8">\n' + + '<style> * { font-family: arial, sans-serif;\n' + + 'font-size: 13px;\n' + + 'line-height: 17px; }' + + 'ul.indent { list-style-type: none; }' + + + 'ol { list-style-type: none; padding-left:0;}' + + 'body > ol { counter-reset: first second third fourth fifth sixth seventh eigth ninth tenth eleventh twelth thirteenth fourteenth fifteenth sixteenth; }' + + 'ol > li:before {' + + 'content: counter(first) ". " ;'+ + 'counter-increment: first;}' + + + 'ol > ol > li:before {' + + 'content: counter(first) "." counter(second) ". " ;'+ + 'counter-increment: second;}' + + + 'ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) ". ";'+ + 'counter-increment: third;}' + + + 'ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) ". ";'+ + 'counter-increment: fourth;}' + + + 'ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) ". ";'+ + 'counter-increment: fifth;}' + + + 'ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) ". ";'+ + 'counter-increment: sixth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) ". ";'+ + 'counter-increment: seventh;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) ". ";'+ + 'counter-increment: eigth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) ". ";'+ + 'counter-increment: ninth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) ". ";'+ + 'counter-increment: tenth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". ";'+ + 'counter-increment: eleventh;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) ". ";'+ + 'counter-increment: twelth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) ". ";'+ + 'counter-increment: thirteenth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) ". ";'+ + 'counter-increment: fourteenth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". ";'+ + 'counter-increment: fifteenth;}' + + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {' + + 'content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixthteenth) ". ";'+ + 'counter-increment: sixthteenth;}' + + + 'ol{ text-indent: 0px; }' + + 'ol > ol{ text-indent: 10px; }' + + 'ol > ol > ol{ text-indent: 20px; }' + + 'ol > ol > ol > ol{ text-indent: 30px; }' + + 'ol > ol > ol > ol > ol{ text-indent: 40px; }' + + 'ol > ol > ol > ol > ol > ol{ text-indent: 50px; }' + + 'ol > ol > ol > ol > ol > ol > ol{ text-indent: 60px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 70px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 80px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 90px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 100px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 110px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol { text-indent: 120px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 130px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 140px; }' + + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 150px; }' + + + stylesForExportCSS + + '</style>\n' + '</head>\n') + + '<body>'; + var foot = '</body>\n</html>\n'; + + getPadHTML(pad, revNum, function (err, html) + { + if(ERR(err, callback)) return; + callback(null, head + html + foot); + }); }); }); }; - // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; var _REGEX_SPACE = /\s/; diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index f0b62743..a6bec4a5 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -22,9 +22,6 @@ var async = require("async"); var Changeset = require("ep_etherpad-lite/static/js/Changeset"); var padManager = require("../db/PadManager"); var ERR = require("async-stacktrace"); -var Security = require('ep_etherpad-lite/static/js/security'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var getPadPlainText = require('./ExportHelper').getPadPlainText; var _analyzeLine = require('./ExportHelper')._analyzeLine; // This is slightly different than the HTML method as it passes the output to getTXTFromAText @@ -82,7 +79,6 @@ function getTXTFromAtext(pad, atext, authorColors) var textLines = atext.text.slice(0, -1).split('\n'); var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; var anumMap = {}; var css = ""; @@ -110,7 +106,6 @@ function getTXTFromAtext(pad, atext, authorColors) // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> var taker = Changeset.stringIterator(text); var assem = Changeset.stringAssembler(); - var openTags = []; var idx = 0; @@ -250,7 +245,6 @@ function getTXTFromAtext(pad, atext, authorColors) // so we want to do something reasonable there. We also // want to deal gracefully with blank lines. // => keeps track of the parents level of indentation - var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...] for (var i = 0; i < textLines.length; i++) { var line = _analyzeLine(textLines[i], attribLines[i], apool); diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js new file mode 100644 index 00000000..37863bff --- /dev/null +++ b/src/node/utils/ImportEtherpad.js @@ -0,0 +1,83 @@ +/** + * 2014 John McLear (Etherpad Foundation / McLear Ltd) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var log4js = require('log4js'); +var async = require("async"); +var db = require("../db/DB").db; + +exports.setPadRaw = function(padId, records, callback){ + records = JSON.parse(records); + + // !! HACK !! + // If you have a really large pad it will cause a Maximum Range Stack crash + // This is a temporary patch for that so things are kept stable. + var recordCount = Object.keys(records).length; + if(recordCount >= 50000){ + console.warn("Etherpad file is too large to import.. We need to fix this. See https://github.com/ether/etherpad-lite/issues/2524"); + return callback("tooLarge", false); + } + + async.eachSeries(Object.keys(records), function(key, cb){ + var value = records[key] + + if(!value){ + cb(); // null values are bad. + } + + // Author data + if(value.padIDs){ + // rewrite author pad ids + value.padIDs[padId] = 1; + var newKey = key; + + // Does this author already exist? + db.get(key, function(err, author){ + if(author){ + // Yes, add the padID to the author.. + if( Object.prototype.toString.call(author) === '[object Array]'){ + author.padIDs.push(padId); + } + value = author; + }else{ + // No, create a new array with the author info in + value.padIDs = [padId]; + } + }); + + // Not author data, probably pad data + }else{ + // we can split it to look to see if its pad data + var oldPadId = key.split(":"); + + // we know its pad data.. + if(oldPadId[0] === "pad"){ + + // so set the new pad id for the author + oldPadId[1] = padId; + + // and create the value + var newKey = oldPadId.join(":"); // create the new key + } + + } + // Write the value to the server + db.set(newKey, value); + + cb(); + }, function(){ + callback(null, true); + }); +} diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.js index 48188dfd..33fd91c6 100644 --- a/src/node/utils/ImportHtml.js +++ b/src/node/utils/ImportHtml.js @@ -14,23 +14,22 @@ * limitations under the License. */ -var jsdom = require('jsdom-nocontextifiy').jsdom; var log4js = require('log4js'); var Changeset = require("ep_etherpad-lite/static/js/Changeset"); var contentcollector = require("ep_etherpad-lite/static/js/contentcollector"); +var cheerio = require("cheerio"); function setPadHTML(pad, html, callback) { var apiLogger = log4js.getLogger("ImportHtml"); - // Parse the incoming HTML with jsdom - try{ - var doc = jsdom(html.replace(/>\n+</g, '><')); - }catch(e){ - apiLogger.warn("Error importing, possibly caused by malformed HTML"); - var doc = jsdom("<html><body><div>Error during import, possibly malformed HTML</div></body></html>"); - } + var $ = cheerio.load(html); + + // Appends a line break, used by Etherpad to ensure a caret is available + // below the last line of an import + $('body').append("<p></p>"); + var doc = $('html')[0]; apiLogger.debug('html:'); apiLogger.debug(html); @@ -38,10 +37,10 @@ function setPadHTML(pad, html, callback) // using the content collector object var cc = contentcollector.makeContentCollector(true, null, pad.pool); try{ // we use a try here because if the HTML is bad it will blow up - cc.collectContent(doc.childNodes[0]); + cc.collectContent(doc); }catch(e){ apiLogger.warn("HTML was not properly formed", e); - return; // We don't process the HTML because it was bad.. + return callback(e); // We don't process the HTML because it was bad.. } var result = cc.finish(); @@ -92,6 +91,7 @@ function setPadHTML(pad, html, callback) apiLogger.debug('The changeset: ' + theChangeset); pad.setText(""); pad.appendRevision(theChangeset); + callback(null); } exports.setPadHTML = setPadHTML; diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 58d08b30..ba45ab75 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -23,12 +23,12 @@ var ERR = require("async-stacktrace"); var settings = require('./Settings'); var async = require('async'); var fs = require('fs'); -var cleanCSS = require('clean-css'); +var CleanCSS = require('clean-css'); var jsp = require("uglify-js").parser; var pro = require("uglify-js").uglify; var path = require('path'); var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); -var RequireKernel = require('require-kernel'); +var RequireKernel = require('etherpad-require-kernel'); var urlutil = require('url'); var ROOT_DIR = path.normalize(__dirname + "/../../static/"); @@ -145,7 +145,6 @@ function minify(req, res, next) filename = path.normalize(path.join(ROOT_DIR, filename)); if (filename.indexOf(ROOT_DIR) == 0) { filename = filename.slice(ROOT_DIR.length); - filename = filename.replace(/\\/g, '/'); // Windows (safe generally?) } else { res.writeHead(404, {}); res.end(); @@ -261,7 +260,6 @@ function getAceFile(callback) { // them into the file. async.forEach(founds, function (item, callback) { var filename = item.match(/"([^"]*)"/)[1]; - var request = require('request'); var baseURI = 'http://localhost:' + settings.port; var resourceURI = baseURI + path.normalize(path.join('/static/', filename)); @@ -411,7 +409,8 @@ function compressJS(values) function compressCSS(values) { var complete = values.join("\n"); - return cleanCSS.process(complete); + var minimized = new CleanCSS().minify(complete).styles; + return minimized; } exports.minify = minify; diff --git a/src/node/utils/RemoteAddress.js b/src/node/utils/RemoteAddress.js new file mode 100644 index 00000000..86a4a5b2 --- /dev/null +++ b/src/node/utils/RemoteAddress.js @@ -0,0 +1 @@ +exports.remoteAddress = {}; diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index c455617b..7e0e6c5a 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -27,7 +27,7 @@ var npm = require("npm/lib/npm.js"); var jsonminify = require("jsonminify"); var log4js = require("log4js"); var randomString = require("./randomstring"); - +var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n"; /* Root path of the installation */ exports.root = path.normalize(path.join(npm.dir, "..")); @@ -55,6 +55,11 @@ exports.ip = "0.0.0.0"; exports.port = process.env.PORT || 9001; /** + * Should we suppress Error messages from being in Pad Contents + */ +exports.suppressErrorsInPadText = false; + +/** * The SSL signed server key and the Certificate Authority's own certificate * default case: ep-lite does *not* use SSL. A signed server key is not required in this case. */ @@ -95,7 +100,7 @@ exports.toolbar = { ["showusers"] ], timeslider: [ - ["timeslider_export", "timeslider_returnToPad"] + ["timeslider_export", "timeslider_settings", "timeslider_returnToPad"] ] } @@ -130,6 +135,11 @@ exports.minify = true; exports.abiword = null; /** + * Should we support none natively supported file types on import? + */ +exports.allowUnknownFileEnds = true; + +/** * The log level of log4js */ exports.loglevel = "INFO"; @@ -139,6 +149,11 @@ exports.loglevel = "INFO"; */ exports.disableIPlogging = false; +/** + * Disable Load Testing + */ +exports.loadTest = false; + /* * log4js appender configuration */ @@ -174,6 +189,29 @@ exports.abiwordAvailable = function() } }; +// Provide git version if available +exports.getGitCommit = function() { + var version = ""; + try + { + var rootPath = path.resolve(npm.dir, '..'); + var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); + var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); + version = fs.readFileSync(refPath, "utf-8"); + version = version.substring(0, 7); + } + catch(e) + { + console.warn("Can't get git version for server header\n" + e.message) + } + return version; +} + +// Return etherpad version from package.json +exports.getEpVersion = function() { + return require('ep_etherpad-lite/package.json').version; +} + exports.reloadSettings = function reloadSettings() { // Discover where the settings file lives var settingsFilename = argv.settings || "settings.json"; @@ -228,17 +266,45 @@ exports.reloadSettings = function reloadSettings() { log4js.configure(exports.logconfig);//Configure the logging appenders log4js.setGlobalLogLevel(exports.loglevel);//set loglevel + process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug log4js.replaceConsole(); + if(exports.abiword){ + // Check abiword actually exists + if(exports.abiword != null) + { + fs.exists(exports.abiword, function(exists) { + if (!exists) { + var abiwordError = "Abiword does not exist at this path, check your settings file"; + if(!exports.suppressErrorsInPadText){ + exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg; + } + console.error(abiwordError); + exports.abiword = null; + } + }); + } + } + if(!exports.sessionKey){ // If the secretKey isn't set we also create yet another unique value here exports.sessionKey = randomString(32); - console.warn("You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts"); + var sessionWarning = "You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts"; + if(!exports.suppressErrorsInPadText){ + exports.defaultPadText = exports.defaultPadText + "\nWarning: " + sessionWarning + suppressDisableMsg; + } + console.warn(sessionWarning); } if(exports.dbType === "dirty"){ - console.warn("DirtyDB is used. This is fine for testing but not recommended for production."); + var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production."; + if(!exports.suppressErrorsInPadText){ + exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg; + } + console.warn(dirtyWarning); } }; // initially load settings exports.reloadSettings(); + + diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index d30dc398..97134356 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -19,7 +19,6 @@ var Buffer = require('buffer').Buffer; var fs = require('fs'); var path = require('path'); var zlib = require('zlib'); -var util = require('util'); var settings = require('./Settings'); var semver = require('semver'); diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 88fa5cba..24d5bb0c 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -101,8 +101,12 @@ PadDiff.prototype._createClearStartAtext = function(rev, callback){ return callback(err); } + try { //apply the clearAuthorship changeset var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); + } catch(err) { + return callback(err) + } callback(null, newAText); }); @@ -209,10 +213,14 @@ PadDiff.prototype._createDiffAtext = function(callback) { if(superChangeset){ var deletionChangeset = self._createDeletionChangeset(superChangeset,atext,self._pad.pool); - //apply the superChangeset, which includes all addings - atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool); - //apply the deletionChangeset, which adds a deletions - atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool); + try { + //apply the superChangeset, which includes all addings + atext = Changeset.applyToAText(superChangeset,atext,self._pad.pool); + //apply the deletionChangeset, which adds a deletions + atext = Changeset.applyToAText(deletionChangeset,atext,self._pad.pool); + } catch(err) { + return callback(err) + } } callback(err, atext); diff --git a/src/node/utils/tar.json b/src/node/utils/tar.json index 70001f8f..05d764a7 100644 --- a/src/node/utils/tar.json +++ b/src/node/utils/tar.json @@ -2,6 +2,7 @@ "pad.js": [ "pad.js" , "pad_utils.js" + , "browser.js" , "pad_cookie.js" , "pad_editor.js" , "pad_editbar.js" @@ -24,6 +25,7 @@ , "colorutils.js" , "draggable.js" , "pad_utils.js" + , "browser.js" , "pad_cookie.js" , "pad_editor.js" , "pad_editbar.js" @@ -42,6 +44,7 @@ ] , "ace2_inner.js": [ "ace2_inner.js" + , "browser.js" , "AttributePool.js" , "Changeset.js" , "ChangesetUtils.js" @@ -58,6 +61,7 @@ ] , "ace2_common.js": [ "ace2_common.js" + , "browser.js" , "jquery.js" , "rjquery.js" , "$async.js" diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.js index e8d02dd6..07b86496 100644 --- a/src/node/utils/toolbar.js +++ b/src/node/utils/toolbar.js @@ -4,7 +4,6 @@ var _ = require("underscore") , tagAttributes , tag - , defaultButtons , Button , ButtonsGroup , Separator @@ -100,12 +99,14 @@ _.extend(Button.prototype, { }; return tag("li", liAttributes, tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId }, - tag("span", { "class": " "+ this.attributes.class }) + tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId }) ) ); } }); + + SelectButton = function (attributes) { this.attributes = attributes; this.options = []; @@ -122,8 +123,7 @@ _.extend(SelectButton.prototype, Button.prototype, { }, select: function (attributes) { - var self = this - , options = []; + var options = []; _.each(this.options, function (opt) { var a = _.extend({ @@ -210,6 +210,12 @@ module.exports = { class: "buttonicon buttonicon-import_export" }, + timeslider_settings: { + command: "settings", + localizationId: "pad.toolbar.settings.title", + class: "buttonicon buttonicon-settings" + }, + timeslider_returnToPad: { command: "timeslider_returnToPad", localizationId: "timeslider.toolbar.returnbutton", diff --git a/src/package.json b/src/package.json index 7cabf6e6..ed9ba957 100644 --- a/src/package.json +++ b/src/package.json @@ -1,50 +1,52 @@ { "name" : "ep_etherpad-lite", "description" : "A Etherpad based on node.js", - "homepage" : "https://github.com/ether/etherpad-lite", + "homepage" : "http://etherpad.org", "keywords" : ["etherpad", "realtime", "collaborative", "editor"], - "author" : "Peter 'Pita' Martischka <petermartischka@googlemail.com> - Primary Technology Ltd", + "author" : "Etherpad Foundation", "contributors" : [ { "name": "John McLear" }, { "name": "Hans Pinckaers" }, { "name": "Robin Buse" }, - { "name": "Marcel Klehr" } + { "name": "Marcel Klehr" }, + { "name": "Peter Martischka" } ], "dependencies" : { - "yajsml" : "1.1.6", - "request" : "2.9.100", - "require-kernel" : "1.0.5", - "resolve" : ">=1.0.0", - "socket.io" : "0.9.x", - "ueberDB" : ">=0.2.9", - "express" : "3.1.0", - "async" : "0.1.x", - "connect" : "2.7.x", - "clean-css" : "0.3.2", - "uglify-js" : "1.2.5", - "formidable" : "1.0.9", - "log4js" : "0.6.6", - "nodemailer" : "0.3.x", - "jsdom-nocontextifiy" : "0.2.10", + "etherpad-yajsml" : "0.0.2", + "request" : "2.55.0", + "etherpad-require-kernel" : "1.0.8", + "resolve" : "1.1.6", + "socket.io" : "1.3.5", + "ueberDB" : "0.2.15", + "express" : "3.8.1", + "async" : "0.9.0", + "connect" : "2.7.11", + "clean-css" : "3.1.9", + "uglify-js" : "2.4.19", + "formidable" : "1.0.17", + "log4js" : "0.6.22", + "cheerio" : "0.19.0", "async-stacktrace" : "0.0.2", - "npm" : "1.4.x", - "ejs" : "0.6.1", - "graceful-fs" : "1.1.5", - "slide" : "1.1.3", - "semver" : "1.0.13", + "npm" : "2.7.5", + "ejs" : "1.0.0", + "graceful-fs" : "3.0.6", + "slide" : "1.1.6", + "semver" : "4.3.3", "security" : "1.0.0", "tinycon" : "0.0.1", "underscore" : "1.5.1", - "unorm" : "1.0.0", + "unorm" : "1.3.3", "languages4translatewiki" : "0.1.3", - "swagger-node-express" : "1.2.3", - "channels" : "0.0.x", - "jsonminify" : "0.2.2", - "measured" : "0.1.3" + "swagger-node-express" : "2.1.3", + "channels" : "0.0.4", + "jsonminify" : "0.2.3", + "measured" : "1.0.0", + "mocha" : "2.2.1", + "supertest" : "0.15.0" }, "bin": { "etherpad-lite": "./node/server.js" }, "devDependencies": { - "wd" : "0.0.31" + "wd" : "0.3.11" }, "engines" : { "node" : ">=0.6.3", "npm" : ">=1.0" @@ -52,5 +54,5 @@ "repository" : { "type" : "git", "url" : "http://github.com/ether/etherpad-lite.git" }, - "version" : "1.4.1" + "version" : "1.5.4" } diff --git a/src/static/css/fontawesome-etherpad.css b/src/static/css/fontawesome-etherpad.css deleted file mode 100644 index 13f28f2b..00000000 --- a/src/static/css/fontawesome-etherpad.css +++ /dev/null @@ -1,76 +0,0 @@ -@font-face { - font-family: 'fontawesome-etherpad'; - src: url('../font/fontawesome-etherpad.eot?81419457'); - src: url('../font/fontawesome-etherpad.eot?81419457#iefix') format('embedded-opentype'), - url('../font/fontawesome-etherpad.woff?81419457') format('woff'), - url('../font/fontawesome-etherpad.ttf?81419457') format('truetype'), - url('../font/fontawesome-etherpad.svg?81419457#fontawesome-etherpad') format('svg'); - font-weight: normal; - font-style: normal; -} -/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ -/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ -/* -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'fontawesome-etherpad'; - src: url('../font/fontawesome-etherpad.svg?81419457#fontawesome-etherpad') format('svg'); - } -} -*/ - - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontawesome-etherpad"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} - -.icon-users:before { content: '\e800'; } /* '' */ -.icon-star:before { content: '\e801'; } /* '' */ -.icon-cog:before { content: '\e802'; } /* '' */ -.icon-bold:before { content: '\e803'; } /* '' */ -.icon-italic:before { content: '\e804'; } /* '' */ -.icon-indent-left:before { content: '\e805'; } /* '' */ -.icon-indent-right:before { content: '\e806'; } /* '' */ -.icon-list-bullet:before { content: '\e807'; } /* '' */ -.icon-list-numbered:before { content: '\e808'; } /* '' */ -.icon-strike:before { content: '\e809'; } /* '' */ -.icon-underline:before { content: '\e80a'; } /* '' */ -.icon-cw:before { content: '\e80b'; } /* '' */ -.icon-ccw:before { content: '\e80c'; } /* '' */ -.icon-clock:before { content: '\e80d'; } /* '' */ -.icon-eye-off:before { content: '\e80e'; } /* '' */ -.icon-eye:before { content: '\e80f'; } /* '' */ -.icon-play:before { content: '\e810'; } /* '' */ -.icon-fast-bw:before { content: '\e811'; } /* '' */ -.icon-fast-fw:before { content: '\e812'; } /* '' */ -.icon-pause:before { content: '\e813'; } /* '' */ -.icon-glass:before { content: '\e814'; } /* '' */ -.icon-code:before { content: '\e815'; } /* '' */ -.icon-exchange:before { content: '\e816'; } /* '' */ -.icon-chat:before { content: '\e817'; } /* '' */
\ No newline at end of file diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index b88db7a1..b708e2f4 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -11,7 +11,10 @@ span { cursor: auto; } background: #acf; } -a { cursor: pointer !important; } +a { + cursor: pointer !important; + white-space:pre-wrap; +} ul, ol, li { padding: 0; @@ -28,6 +31,14 @@ ul.list-bullet5 { margin-left: 7.5em; } ul.list-bullet6 { margin-left: 9em; } ul.list-bullet7 { margin-left: 10.5em; } ul.list-bullet8 { margin-left: 12em; } +ul.list-bullet9 { margin-left: 13.5em; } +ul.list-bullet10 { margin-left: 15em; } +ul.list-bullet11 { margin-left: 16.5em; } +ul.list-bullet12 { margin-left: 18em; } +ul.list-bullet13 { margin-left: 19.5em; } +ul.list-bullet14 { margin-left: 21em; } +ul.list-bullet15 { margin-left: 22.5em; } +ul.list-bullet16 { margin-left: 24em; } ul { list-style-type: disc; } ul.list-bullet1 { list-style-type: disc; } @@ -38,6 +49,14 @@ ul.list-bullet5 { list-style-type: circle; } ul.list-bullet6 { list-style-type: square; } ul.list-bullet7 { list-style-type: disc; } ul.list-bullet8 { list-style-type: circle; } +ul.list-bullet9 { list-style-type: disc; } +ul.list-bullet10 { list-style-type: circle; } +ul.list-bullet11 { list-style-type: square; } +ul.list-bullet12 { list-style-type: disc; } +ul.list-bullet13 { list-style-type: circle; } +ul.list-bullet14 { list-style-type: square; } +ul.list-bullet15 { list-style-type: disc; } +ul.list-bullet16 { list-style-type: circle; } ul.list-indent1 { margin-left: 1.5em; } ul.list-indent2 { margin-left: 3em; } @@ -47,15 +66,19 @@ ul.list-indent5 { margin-left: 7.5em; } ul.list-indent6 { margin-left: 9em; } ul.list-indent7 { margin-left: 10.5em; } ul.list-indent8 { margin-left: 12em; } - -ul.list-indent1 { list-style-type: none; } -ul.list-indent2 { list-style-type: none; } -ul.list-indent3 { list-style-type: none; } -ul.list-indent4 { list-style-type: none; } -ul.list-indent5 { list-style-type: none; } -ul.list-indent6 { list-style-type: none; } -ul.list-indent7 { list-style-type: none; } -ul.list-indent8 { list-style-type: none; } +ul.list-indent9 { margin-left: 13.5em; } +ul.list-indent10 { margin-left: 15em; } +ul.list-indent11 { margin-left: 16.5em; } +ul.list-indent12 { margin-left: 18em; } +ul.list-indent13 { margin-left: 19.5em; } +ul.list-indent14 { margin-left: 21em; } +ul.list-indent15 { margin-left: 22.5em; } +ul.list-indent16 { margin-left: 24em; } + +ul.list-indent1, ul.list-indent2, ul.list-indent3, ul.list-indent4, ul.list-indent5, +ul.list-indent6, ul.list-indent7, ul.list-indent8, ul.list-indent9, ul.list-indent10, +ul.list-indent11, ul.list-indent12, ul.list-indent13, +ul.list-indent14, ul.list-indent15, ul.list-indent16 { list-style-type: none; } body { margin: 0; @@ -75,10 +98,26 @@ body.grayedout { background-color: #eee !important } } body.doesWrap { - white-space: pre-wrap; /*Must be pre-wrap to keep trailing spaces. Otherwise you get a zombie caret, walking around your screen (see #1766), WARNING: Enabling this causes Paste as plain text in Chrome to remove line breaks, this is probably undesirable */ + /* white-space: pre-wrap; */ + + /* + Must be pre-wrap to keep trailing spaces. Otherwise you get a zombie caret, + walking around your screen (see #1766). + WARNING: Enabling this causes Paste as plain text in Chrome to remove line breaks + this is probably undesirable + WARNING: This causes copy & paste events to lose bold etc. attributes + NOTE: The walking-zombie caret issue seems to have been fixed in FF upstream + so let's try diabling pre-wrap and see how we get on now. + For more details see: https://github.com/ether/etherpad-lite/issues/2574 + */ word-wrap: break-word; /* fix for issue #1648 - firefox not wrapping long lines (without spaces) correctly */ } +body.doesWrap > div{ + /* Related to #1766 */ + white-space: pre-wrap; +} + #innerdocbody { padding-top: 1px; /* important for some reason? */ padding-right: 10px; @@ -184,6 +223,14 @@ ol.list-number5{ text-indent: 40px; } ol.list-number6{ text-indent: 50px; } ol.list-number7{ text-indent: 60px; } ol.list-number8{ text-indent: 70px; } +ol.list-number9{ text-indent: 80px; } +ol.list-number10{ text-indent: 90px; } +ol.list-number11{ text-indent: 100px; } +ol.list-number12{ text-indent: 110px; } +ol.list-number13{ text-indent: 120px; } +ol.list-number14{ text-indent: 130px; } +ol.list-number15{ text-indent: 140px; } +ol.list-number16{ text-indent: 150px; } /* Add styling to the first item in a list */ @@ -195,6 +242,14 @@ ol.list-number8{ text-indent: 70px; } .list-start-number6 { counter-reset: sixth; } .list-start-number7 { counter-reset: seventh; } .list-start-number8 { counter-reset: eighth; } +.list-start-number9 { counter-reset: ninth; } +.list-start-number10 { counter-reset: tenth; } +.list-start-number11 { counter-reset: eleventh; } +.list-start-number12 { counter-reset: twelth; } +.list-start-number13 { counter-reset: thirteenth; } +.list-start-number14 { counter-reset: fourteenth; } +.list-start-number15 { counter-reset: fifteenth; } +.list-start-number16 { counter-reset: sixteenth; } /* The behavior for incrementing and the prefix */ .list-number1 li:before { @@ -233,6 +288,47 @@ ol.list-number8{ text-indent: 70px; } } .list-number8 li:before { - content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(eighth) ". " ; + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) ". " ; counter-increment: eighth 1; } + +.list-number9 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) ". "; + counter-increment: ninth 1; +} + +.list-number10 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) ". "; + counter-increment: tenth 1; +} + +.list-number11 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". "; + counter-increment: eleventh 1; +} + +.list-number12 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) ". "; + counter-increment: twelth 1; +} + +.list-number13 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) ". "; + counter-increment: thirteenth 1; +} + +.list-number14 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) ". "; + counter-increment: fourteenth 1; +} + +.list-number15 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". "; + counter-increment: fifteenth 1; +} + +.list-number16 li:before { + content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixteenth) ". "; + counter-increment: fixteenth 1; +} + diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 599b9fd4..ff8ab5ab 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -59,7 +59,7 @@ a img { height: 32px; } .toolbar ul { - position: relative; + position: absolute; list-style: none; padding-right: 3px; padding-left: 1px; @@ -67,16 +67,10 @@ a img { overflow: hidden; float: left } -.toolbar ul.menu_right { - float: right -} .toolbar ul li { float: left; margin-left: 2px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + height:32px; } .toolbar ul li.separator { border: inherit; @@ -84,6 +78,7 @@ a img { visibility: hidden; width: 0px; padding: 5px; + height:22px; } .toolbar ul li a:hover { text-decoration: none; @@ -107,6 +102,17 @@ a img { -moz-box-shadow: 0 0 8px rgba(0,0,0,.1) inset; box-shadow: 0 0 8px rgba(0,0,0,.1) inset; } +.toolbar ul li .activeButton { + background: #eee; + background: -webkit-linear-gradient(#ddd, #fff); + background: -moz-linear-gradient(#ddd, #fff); + background: -o-linear-gradient(#ddd, #fff); + background: -ms-linear-gradient(#ddd, #fff); + background: linear-gradient(#ddd, #fff); + -webkit-box-shadow: 0 0 8px rgba(0,0,0,.1) inset; + -moz-box-shadow: 0 0 8px rgba(0,0,0,.1) inset; + box-shadow: 0 0 8px rgba(0,0,0,.1) inset; +} .toolbar ul li a { background: #fff; background: -webkit-linear-gradient(#fff, #f0f0f0); @@ -131,9 +137,24 @@ a img { top: 1px; } .toolbar ul li a .buttontext { - color: #222; + color: #666; font-size: 14px; + border:none; + background:none; + margin-top:1px; + color:#666; } + +.buttontext::-moz-focus-inner { + padding: 0; + border: 0; +} + +.buttontext:focus{ + /* Not sure why important is required here but it is */ + border: 1px solid #666 !important; +} + .toolbar ul li a.grouped-left { border-radius: 3px 0 0 3px; } @@ -164,6 +185,15 @@ a img { border: 1px solid #ccc; outline: none; } +.toolbar ul.menu_left { + left:0px; + right:250px; +} + +.toolbar ul.menu_right { + right:0px; +} + li[data-key=showusers] > a { min-width: 30px; text-align: left; @@ -175,6 +205,10 @@ li[data-key=showusers] > a #online_count { top: 2px; padding-left: 2px; } +#editbar{ + display:none; +} + #editorcontainer { position: absolute; top: 37px; /* + 1px border */ @@ -214,6 +248,41 @@ li[data-key=showusers] > a #online_count { padding:10px; } +.loadingAnimation{ + -webkit-animation: loadingAnimation 2s infinite linear; + animation: loadingAnimation 2s infinite linear; + font-family: "fontawesome-etherpad"; + font-size:24px; + z-index:150; + width:25px; + height:25px; +} + +.loadingAnimation:before{ + content: "\e80e"; +} + +@-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + #editorcontainerbox { position: absolute; bottom: 0; @@ -316,7 +385,7 @@ li[data-key=showusers] > a #online_count { border-radius: 5px; } #myusernameform { - margin-left: 35px + margin-left: 30px } #myusernameedit { font-size: 1.3em; @@ -325,7 +394,7 @@ li[data-key=showusers] > a #online_count { height: 18px; margin: 0; border: 0; - width: 117px; + width: 122px; background: transparent; } #myusernameform input.editable { @@ -391,6 +460,12 @@ table#otheruserstable { height: 13px; overflow: hidden; margin: 0 4px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .usertdswatch { width: 1% @@ -601,38 +676,49 @@ table#otheruserstable { margin-top: 4px; background-repeat: no-repeat; padding-left: 25px; - background-image: url("../../static/img/etherpad_lite_icons.png"); color: #333; text-decoration: none; padding-bottom:2px; + display:inline; + padding-left:5px; + font-family: "Arial"; +} +.exportlink{ + font-family: "fontawesome-etherpad"; + display:block; + margin:5px; + color:#666; } -#exporthtml { - background-position: 0px -299px +#exporthtmla:before { + content: "\e826"; } -#exportplain { - background-position: 0px -395px +#exportplaina:before { + content: "\e802"; } -#exportword { - background-position: 0px -275px +#exportworda:before { + content: "\e804"; } -#exportpdf { - background-position: 0px -371px +#exportpdfa:before { + content: "\e803"; } -#exportopen { - background-position: 0px -347px +#exportetherpada:before { + content: "\e806"; } -#exportdokuwiki { - background-position: 0px -459px +#exportopena:before { + content: "\e805"; } /* hidden element */ #importstatusball, -#importarrow, #importmessagesuccess, #importmessageabiword { display: none; } +.throbbold{ + font-weight:bold; +} + #importmessageabiword { color: #900; font-size: small; @@ -642,9 +728,6 @@ table#otheruserstable { margin-top: 12px; padding:2px 4px 2px 4px; } -#importstatusball { - height: 50px -} #chatthrob { display: none; position: absolute; @@ -671,66 +754,78 @@ table#otheruserstable { height: 16px; display: inline-block; vertical-align: middle; - + border: none; + padding: 0; + background: none; font-family: "fontawesome-etherpad"; font-size: 15px; font-style: normal; font-weight: normal; color: #666; + cursor: pointer; +} + +.buttonicon::-moz-focus-inner { + padding: 0; + border: 0 +} + +.buttonicon:focus{ + border: 1px solid #666; } .buttonicon-bold:before { - content: "\e803"; + content: "\e81c"; } .buttonicon-italic:before { - content: "\e804"; + content: "\e81d"; } .buttonicon-underline:before { - content: "\e80a"; + content: "\e817"; } .buttonicon-strikethrough:before { - content: "\e809"; + content: "\e818"; } .buttonicon-insertorderedlist:before { - content: "\e808"; + content: "\e816"; } .buttonicon-insertunorderedlist:before { - content: "\e807"; + content: "\e815"; } .buttonicon-indent:before { - content: "\e806"; + content: "\e814"; } .buttonicon-outdent:before { - content: "\e805"; + content: "\e813"; } .buttonicon-undo:before { - content: "\e80c"; + content: "\e823"; } .buttonicon-redo:before { - content: "\e80b"; + content: "\e824"; } .buttonicon-clearauthorship:before { - content: "\e80e"; + content: "\e80d"; } .buttonicon-settings:before { - content: "\e802"; + content: "\e833"; } .buttonicon-import_export:before { - content: "\e816"; + content: "\e834"; } .buttonicon-embed:before { - content: "\e815"; + content: "\e827"; } .buttonicon-history:before { - content: "\e80d"; + content: "\e837"; } .buttonicon-chat:before { - content: "\e817"; + content: "\e829"; } .buttonicon-showusers:before { - content: "\e800"; + content: "\e808"; } .buttonicon-savedRevision:before { - content: "\e801"; + content: "\e835"; } #focusprotector { z-index: 100; @@ -818,6 +913,7 @@ input[type=checkbox] { } .column { float: left; + width:50%; } #settings, #import_export, @@ -825,7 +921,7 @@ input[type=checkbox] { #connectivity, #users { position: absolute; - top: 36px; + top: 38px; right: 20px; display: none; z-index: 500; @@ -842,13 +938,47 @@ input[type=checkbox] { border-left: 1px solid #ccc !important; width: 185px !important; } +.chatAndUsers{ + display:block !important; + right:0px !important; + border-radius:0px !important; + width:182px !important; +/* Below makes UI look weird when X makes editbar flow onto two lines */ +/* margin:2px 0 0 0 !important;*/ + border: none !important; + border-bottom: 1px solid #ccc !important; + height:155px !important; + border-left: 1px solid #ccc !important; +} +.chatAndUsers > #otherusers{ + max-height: 100px; + overflow-y: auto; +} +.chatAndUsersChat > div > #titlecross{ + display:none; +} +.chatAndUsersChat{ + bottom:0px !important; + padding:0 !important; + margin: 165px 0px 0px 0px; + right:0 !important; + width:182px !important; + border: none !important; + padding:5px !important; + border-left: 1px solid #ccc !important; +} + @media screen and (max-width: 600px) { - .toolbar ul li.separator { - display: none; - } - .toolbar ul li a { - padding: 4px 1px - } + .toolbar ul li.separator { + display: none; + } + .toolbar ul li a { + padding: 4px 1px + } + .toolbar ul.menu_left { + left:0px; + right:150px; + } } @media all and (max-width: 400px){ #gritter-notice-wrapper{ @@ -894,9 +1024,13 @@ input[type=checkbox] { #editbar { height: 62px; } + .toolbar ul.menu_left { + left:0px; + right:100px; + } + .toolbar ul.menu_right { - float: left; - margin-top:2px; + right:0px; } .popup { width:100%; @@ -904,19 +1038,29 @@ input[type=checkbox] { top: 72px !important; } } + +/* Mobile devices */ @media only screen and (min-device-width: 320px) and (max-device-width: 720px) { #users { - top: 36px; - bottom: 40px; - border-radius: none; + top: auto; + right:0px !important; + bottom: 33px; + border-radius: 0px !important; + height: 55px !important; + overflow: auto; } #mycolorpicker { left: -73px; + top:auto !important; + bottom:33px !important; /* #mycolorpicker: width -#users: width */; } #editorcontainer { margin-bottom: 33px } + .toolbar ul.menu_left { + right:0px; + } .toolbar ul.menu_right { background: #f7f7f7; background: -webkit-linear-gradient(#f7f7f7, #f1f1f1 80%); @@ -925,6 +1069,7 @@ input[type=checkbox] { background: -ms-linear-gradient(#f7f7f7, #f1f1f1 80%); background: linear-gradient(#f7f7f7, #f1f1f1 80%); width: 100%; + right:0px !important; overflow: hidden; height: 32px; position: fixed; @@ -944,8 +1089,16 @@ input[type=checkbox] { .toolbar ul li a.selected { background: none !important } - #chaticon, #timesliderlink { - display: none !important + li[data-key="showusers"] > a { + + margin-top:-10px; + padding-top:2px !important; + line-height:20px; + vertical-align:top !important; + } + #chaticon { + position:absolute; + right:48px; } .popup { -webkit-border-radius: 0; @@ -958,11 +1111,11 @@ input[type=checkbox] { width: 100%; } #settings, - #importexport, + #import_export, #connectivity, #embed { + top:auto; left: 0; - top: 0; bottom: 33px; right: 0; } @@ -972,6 +1125,15 @@ input[type=checkbox] { #online_count { line-height: 24px } + #chatbox{ + position:absolute; + bottom:33px !important; + margin: 65px 0 0 0; + } + #gritter-notice-wrapper{ + bottom:43px !important; + right:10px !important; + } } #passwordRequired{ @@ -986,6 +1148,10 @@ input[type=checkbox] { display:none; } +#noCookie{ + display:none; +} + /* gritter stuff */ #gritter-notice-wrapper { position:fixed; @@ -993,6 +1159,7 @@ input[type=checkbox] { right:20px; width:301px; z-index:9999; + background-color:#666; } #gritter-notice-wrapper.bottom-right { top: auto; @@ -1006,14 +1173,12 @@ input[type=checkbox] { } .gritter-top { - background:url(../../static/img/gritter.png) no-repeat left -30px; height:10px; } .hover .gritter-top { background-position:right -30px; } .gritter-bottom { - background:url(../../static/img/gritter.png) no-repeat left bottom; height:8px; margin:0; } @@ -1022,7 +1187,6 @@ input[type=checkbox] { } .gritter-item { display:block; - background:url(../../static/img/gritter.png) no-repeat left -40px; color:#eee; padding:2px 11px 8px 11px; font-size: 11px; @@ -1040,7 +1204,6 @@ input[type=checkbox] { position:absolute; top:5px; left:3px; - background:url('../../static/img/gritter.png') no-repeat left top; cursor:pointer; width:30px; height:30px; @@ -1075,5 +1238,56 @@ input[type=checkbox] { .gritter-light .gritter-title { text-shadow: none; } +/* End of gritter stuff */ + +@font-face { + font-family: opendyslexic; + src: url("../../static/font/opendyslexic.otf") format("opentype"); +} + +@font-face { + font-family: "fontawesome-etherpad"; + src:url("../font/fontawesome-etherpad.eot"); + src:url("../font/fontawesome-etherpad.eot?#iefix") format("embedded-opentype"), + url("../font/fontawesome-etherpad.woff") format("woff"), + url("../font/fontawesome-etherpad.ttf") format("truetype"), + url("../font/fontawesome-etherpad.svg#fontawesome-etherpad") format("svg"); + font-weight: normal; + font-style: normal; + +} + +[data-icon]:before { + font-family: "fontawesome-etherpad" !important; + content: attr(data-icon); + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +[class^="icon-"]:before, +[class*=" icon-"]:before { + font-family: "fontawesome-etherpad" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.hideControlsEditor{ + top:0px !important; +} +.hideControlsEditbar{ + display:none !important; +} + -/* End of gritter stuff */
\ No newline at end of file diff --git a/src/static/css/timeslider.css b/src/static/css/timeslider.css index f478c895..9f4e4683 100644 --- a/src/static/css/timeslider.css +++ b/src/static/css/timeslider.css @@ -14,14 +14,14 @@ top: 0; } #timeslider-left { - background-image: url(../../static/img/timeslider_left.png); + background-color:#fff; height: 63px; left: 0; position: absolute; width: 134px; } #timeslider-right { - background-image: url(../../static/img/timeslider_right.png); + background-color:#fff; height: 63px; position: absolute; right: 0; @@ -29,7 +29,6 @@ width: 155px; } #timeslider { - background-image: url(../../static/img/timeslider_background.png); height: 63px; margin: 0 9px; -webkit-touch-callout: none; @@ -38,6 +37,8 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; + background-color:#fff; + /* bgcolor is reuqired so you can't see pad content behind it */ } #timeslider #timeslider-slider { height: 61px; @@ -50,7 +51,7 @@ -webkit-user-select: none; -moz-user-select: none; user-select: none; - background-image: url(../../static/img/crushed_current_location.png); + background-color: #666; cursor: pointer; height: 61px; left: 0; @@ -63,74 +64,114 @@ -moz-user-select: none; user-select: none; cursor: pointer; - height: 35px; + height: 60px; margin-left: 5px; margin-right: 150px; position: relative; - top: 20px; + top: 0px; } + #playpause_button, #playpause_button_icon { - height: 47px; + height: 44px; position: absolute; - width: 47px; + width: 44px; + text-align:center; + vertical-align:middle; + background:none; } #playpause_button { - background-image: url(../../static/img/crushed_button_undepressed.png); right: 77px; top: 9px; -} + height:50px; + height:50px; + background: background-linear-gradient( #F7F7F7, #F1F1F1 80%) repeat scroll 0 0 transparent; + border-radius:24px; + cursor:hand; +} +#playpause_button_icon:before { + line-height:44px; + padding-left:2px; + font-family: fontawesome-etherpad; + content: "\e82c"; + font-size:24px; + color:#666; +} + #playpause_button_icon { - background-image: url(../../static/img/play.png); left: 0; top: 0; -} -.pause#playpause_button_icon { - background-image: url(../../static/img/pause.png) + border-radius:48px; + border: solid 1px #666; +} +.pause:before { + line-height:44px; + padding-left:2px; + font-family: fontawesome-etherpad; + content: "\e82e" !important; + font-size:24px; + color:#666; + padding-left:0 !important; } #leftstar, #rightstar, #leftstep, #rightstep { - background: url(../../static/img/stepper_buttons.png) 0 0 no-repeat; - height: 21px; + background-color: white; overflow: hidden; position: absolute; } -#leftstar { - background-position: 0 -44px; - right: 34px; - top: 8px; - width: 30px; -} -#rightstar { - background-position: -29px -44px; - right: 5px; - top: 8px; - width: 29px; -} + +.stepper{ + font-family: fontawesome-etherpad; + border-radius:2px; + border: #666 solid 1px; +/* line-height:18px; */ + text-align:center; + height:22px; + color:#666; +} + +stepper:active{ + color:#000; +} + #leftstep { - background-position: 0 -22px; - right: 34px; + right: 38px; top: 20px; - width: 30px; + width: 25px; +} +#leftstep:before{ + content: '\e821'; + vertical-align:middle; } +#rightstep:before{ + content: "\e822"; + vertical-align:middle; +} + #rightstep { - background-position: -29px -22px; - right: 5px; + right: 12px; top: 20px; - width: 30px; + width: 25px; +} +.star:before{ + font-family: fontawesome-etherpad; + content: "\e835"; + vertical-align:middle; + font-size:16px; } #timeslider .star { - background-image: url(../../static/img/star.png); cursor: pointer; height: 16px; position: absolute; - top: 40px; + top: 25px; width: 15px; } #timeslider #timer { - color: #fff; + background: linear-gradient(#F7F7F7, #F1F1F1 80%) repeat scroll 0% 0% transparent; + padding:2px; + border-radius:2px; font-family: Arial, sans-serif; font-size: 11px; left: 7px; @@ -154,15 +195,19 @@ -ms-user-select: none; user-select: none; } -#editbarright { - float: right +.editbarright { + float: right; + text-align: right; + height: 30px !important; } -#settings, -#import_export, -#embed, -#connectivity, -#users { - top: 62px; +.toolbar ul{ + position:relative; + float:right; + height:30px; +} +#import_export, #settings{ + top: 115px; + position: fixed; } #import_export .popup { width: 183px; @@ -171,7 +216,6 @@ border-radius: 0 0 0 6px; } #import_export { - top: 115px; width: 185px; } .timeslider-bar { @@ -188,8 +232,7 @@ .timeslider-bar #editbar { border-bottom: none; float: right; - width: 170px; - width: initial; + width: 180px; } .timeslider-bar h1 { margin: 5px @@ -290,16 +333,19 @@ OL { .list-number6 { list-style-type: lower-roman } -/* IE 6/7 fixes */ -* HTML #ui-slider-handle { - background-image: url(../../static/img/current_location.gif) -} -* HTML #timeslider .star { - background-image: url(../../static/img/star.gif) + +button{ + margin:0; + padding:0; + cursor:pointer; } -* HTML #playpause_button_icon { - background-image: url(../../static/img/play.gif) + +button::-moz-focus-inner { + padding: 0; + border: 0 } -* HTML .pause#playpause_button_icon { - background-image: url(../../static/img/pause.gif) + +button:focus{ + border: 1px solid #666; } + diff --git a/src/static/favicon.ico b/src/static/favicon.ico Binary files differindex df7b6289..938e9550 100644 --- a/src/static/favicon.ico +++ b/src/static/favicon.ico diff --git a/src/static/font/config.json b/src/static/font/config.json new file mode 100644 index 00000000..34231cca --- /dev/null +++ b/src/static/font/config.json @@ -0,0 +1,346 @@ +{ + "name": "fontawesome-etherpad", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 850, + "glyphs": [ + { + "uid": "bf882b30900da12fca090d9796bc3030", + "css": "mail", + "code": 59402, + "src": "fontawesome" + }, + { + "uid": "474656633f79ea2f1dad59ff63f6bf07", + "css": "star", + "code": 59446, + "src": "fontawesome" + }, + { + "uid": "d17030afaecc1e1c22349b99f3c4992a", + "css": "star-empty", + "code": 59445, + "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59401, + "src": "fontawesome" + }, + { + "uid": "31972e4e9d080eaa796290349ae6c1fd", + "css": "users", + "code": 59400, + "src": "fontawesome" + }, + { + "uid": "0f99ab40ab0b4d64a74f2d0deeb03e42", + "css": "videocam", + "code": 59403, + "src": "fontawesome" + }, + { + "uid": "381da2c2f7fd51f8de877c044d7f439d", + "css": "picture", + "code": 59404, + "src": "fontawesome" + }, + { + "uid": "7fd683b2c518ceb9e5fa6757f2276faa", + "css": "eye-off", + "code": 59405, + "src": "fontawesome" + }, + { + "uid": "7034e4d22866af82bef811f52fb1ba46", + "css": "code", + "code": 59431, + "src": "fontawesome" + }, + { + "uid": "7277ded7695b2a307a5f9d50097bb64c", + "css": "print", + "code": 59393, + "src": "fontawesome" + }, + { + "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3", + "css": "chat", + "code": 59432, + "src": "fontawesome" + }, + { + "uid": "9c1376672bb4f1ed616fdd78a23667e9", + "css": "comment-empty", + "code": 59433, + "src": "fontawesome" + }, + { + "uid": "f48ae54adfb27d8ada53d0fd9e34ee10", + "css": "trash-empty", + "code": 59434, + "src": "fontawesome" + }, + { + "uid": "1b5a5d7b7e3c71437f5a26befdd045ed", + "css": "doc", + "code": 59394, + "src": "fontawesome" + }, + { + "uid": "9daa1fdf0838118518a7e22715e83abc", + "css": "file-pdf", + "code": 59395, + "src": "fontawesome" + }, + { + "uid": "310ffd629da85142bc8669f010556f2d", + "css": "file-word", + "code": 59396, + "src": "fontawesome" + }, + { + "uid": "f761c3bbe16ba2d332914ecb28e7a042", + "css": "file-excel", + "code": 59397, + "src": "fontawesome" + }, + { + "uid": "edcd4022de8d8df266ef7c42d2658ca5", + "css": "file-powerpoint", + "code": 59398, + "src": "fontawesome" + }, + { + "uid": "3c961c1a8d874815856fc6637dc5a13c", + "css": "file-image", + "code": 59399, + "src": "fontawesome" + }, + { + "uid": "26613a2e6bc41593c54bead46f8c8ee3", + "css": "file-code", + "code": 59430, + "src": "fontawesome" + }, + { + "uid": "e99461abfef3923546da8d745372c995", + "css": "cog", + "code": 59443, + "src": "fontawesome" + }, + { + "uid": "19c50c52858a81de58f9db488aba77bc", + "css": "mic", + "code": 59435, + "src": "fontawesome" + }, + { + "uid": "598a5f2bcf3521d1615de8e1881ccd17", + "css": "clock", + "code": 59447, + "src": "fontawesome" + }, + { + "uid": "bc71f4c6e53394d5ba46b063040014f1", + "css": "cw", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "f9c3205df26e7778abac86183aefdc99", + "css": "ccw", + "code": 59427, + "src": "fontawesome" + }, + { + "uid": "a73c5deb486c8d66249811642e5d719a", + "css": "arrows-cw", + "code": 59429, + "src": "fontawesome" + }, + { + "uid": "6020aff067fc3c119cdd75daa5249220", + "css": "exchange", + "code": 59444, + "src": "fontawesome" + }, + { + "uid": "ce06b5805120d0c2f8d60cd3f1a4fdb5", + "css": "play", + "code": 59436, + "src": "fontawesome" + }, + { + "uid": "b624a1e512819d410ddbee84e6918b9d", + "css": "stop", + "code": 59437, + "src": "fontawesome" + }, + { + "uid": "0b28050bac9d3facf2f0226db643ece0", + "css": "pause", + "code": 59438, + "src": "fontawesome" + }, + { + "uid": "c47efa0e3e74f6ba4c2562c1258fff1f", + "css": "to-end", + "code": 59426, + "src": "fontawesome" + }, + { + "uid": "12052b30d23a1a70d6b32962d5464cae", + "css": "to-start", + "code": 59425, + "src": "fontawesome" + }, + { + "uid": "f9cbf7508cd04145ade2800169959eef", + "css": "font", + "code": 59419, + "src": "fontawesome" + }, + { + "uid": "02cca871bb69da75e8ee286b7055832c", + "css": "bold", + "code": 59420, + "src": "fontawesome" + }, + { + "uid": "a8cb1c217f02b073db3670c061cc54d2", + "css": "italic", + "code": 59421, + "src": "fontawesome" + }, + { + "uid": "0c708edd8fae2376b3370aa56d40cf9e", + "css": "header", + "code": 59422, + "src": "fontawesome" + }, + { + "uid": "c009d417f87d6a27bb5a1cefd30b6cbd", + "css": "text-height", + "code": 59423, + "src": "fontawesome" + }, + { + "uid": "13a971bcccd2dda26d4d4eccd8593f8a", + "css": "text-width", + "code": 59424, + "src": "fontawesome" + }, + { + "uid": "f4f0e849b805be1f6d76b65581cb3b8b", + "css": "align-left", + "code": 59392, + "src": "fontawesome" + }, + { + "uid": "ae6336c46d73af999fe7460c089abb4d", + "css": "align-center", + "code": 59407, + "src": "fontawesome" + }, + { + "uid": "e1e7306b47c3c5e6faecce9d32571381", + "css": "align-right", + "code": 59408, + "src": "fontawesome" + }, + { + "uid": "25a81737628d1e654a30ad412d7d6dd7", + "css": "align-justify", + "code": 59409, + "src": "fontawesome" + }, + { + "uid": "48b87105bd38c20315f1b705b8c7b38c", + "css": "list", + "code": 59410, + "src": "fontawesome" + }, + { + "uid": "594e9271c08ff732c04b3bf3117b9040", + "css": "indent-left", + "code": 59411, + "src": "fontawesome" + }, + { + "uid": "4d2dfc45d8176b1f26aed973fa84a91e", + "css": "indent-right", + "code": 59412, + "src": "fontawesome" + }, + { + "uid": "a2a74f5e7b7d9ba054897d8c795a326a", + "css": "list-bullet", + "code": 59413, + "src": "fontawesome" + }, + { + "uid": "f6766a8b042c2453a4e153af03294383", + "css": "list-numbered", + "code": 59414, + "src": "fontawesome" + }, + { + "uid": "61c242c9e2134d5864d7fdd57b3c9289", + "css": "strike", + "code": 59416, + "src": "fontawesome" + }, + { + "uid": "d4a4a38a40b728f46dad1de4ac950231", + "css": "underline", + "code": 59415, + "src": "fontawesome" + }, + { + "uid": "4e88371fb8857dacc1f66afe6314e426", + "css": "superscript", + "code": 59417, + "src": "fontawesome" + }, + { + "uid": "3d1c929dbc966992185ce749548c1b2c", + "css": "subscript", + "code": 59418, + "src": "fontawesome" + }, + { + "uid": "9396b2d8849e0213a0f11c5fd7fcc522", + "css": "tasks", + "code": 59442, + "src": "fontawesome" + }, + { + "uid": "0bda4bc779d4c32623dec2e43bd67ee8", + "css": "gauge", + "code": 59439, + "src": "fontawesome" + }, + { + "uid": "cda0cdcfd38f5f1d9255e722dad42012", + "css": "spinner", + "code": 59406, + "src": "fontawesome" + }, + { + "uid": "fa9a0b7e788c2d78e24cef1de6b70e80", + "css": "brush", + "code": 59440, + "src": "fontawesome" + }, + { + "uid": "be13b8c668eb18839d5d53107725f1de", + "css": "slideshare", + "code": 59441, + "src": "fontawesome" + } + ] +}
\ No newline at end of file diff --git a/src/static/font/fontawesome-etherpad.eot b/src/static/font/fontawesome-etherpad.eot Binary files differindex 9a24fc67..fb651686 100644 --- a/src/static/font/fontawesome-etherpad.eot +++ b/src/static/font/fontawesome-etherpad.eot diff --git a/src/static/font/fontawesome-etherpad.svg b/src/static/font/fontawesome-etherpad.svg index ba49c31c..4033c4d1 100644 --- a/src/static/font/fontawesome-etherpad.svg +++ b/src/static/font/fontawesome-etherpad.svg @@ -6,30 +6,62 @@ <font id="fontawesome-etherpad" horiz-adv-x="1000" > <font-face font-family="fontawesome-etherpad" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" /> <missing-glyph horiz-adv-x="1000" /> -<glyph glyph-name="users" unicode="" d="m0 367q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143-90-3-148-72h-75q-45 0-77 23t-31 66z m71 340q0 59 42 101t101 42 101-42 42-101-42-101-101-42-101 42-42 101z m72-713q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105z m178 499q0 89 63 151t152 63 151-63 63-151-63-152-151-63-152 63-63 152z m393 214q0 59 42 101t101 42 101-42 42-101-42-101-101-42-101 42-42 101z m27-357q45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197 0-43-31-66t-77-22h-75q-57 68-147 71z" horiz-adv-x="1071.4" /> -<glyph glyph-name="star" unicode="" d="m0 489q0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26 0-12-15-27l-203-197 48-279q1-4 1-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-13-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27z" horiz-adv-x="928.6" /> -<glyph glyph-name="cog" unicode="" d="m0 289v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 15 20 15h124q7 0 13-4t7-12l15-103q28-9 50-21l80 60q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-13 0-6-4-12-9-12-29-38t-30-39q14-28 23-55l102-15q7-1 12-7t4-13v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-28 59-77 6-6 6-14t-5-12q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 20l-79-59q-6-5-14-5-8 0-14 6-70 63-92 94-4 5-4 12 0 7 5 13 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13z m286 61q0-59 42-101t101-42 101 42 41 101-41 101-101 42-101-42-42-101z" horiz-adv-x="857.1" /> -<glyph glyph-name="bold" unicode="" d="m0-79l1 53q9 2 48 9t59 15q4 7 7 15t4 19 4 18 1 21 0 19v36q0 548-12 572-2 5-12 8t-25 6-28 4-27 3-17 2l-2 46q55 1 190 6t208 5q13 0 38 0t38 0q39 0 76-7t72-24 60-39 41-59 16-76q0-29-9-54t-22-40-36-32-41-25-47-22q86-20 144-75t57-139q0-55-20-100t-52-73-77-47-91-27-98-8q-25 0-74 2t-74 1q-59 0-171-6t-129-7z m297 793q0-28 3-84t2-85q0-15 0-45t-1-44q0-26 1-38 23-4 61-4 46 0 80 7t61 25 41 50 15 79q0 39-16 68t-45 46-60 24-69 8q-28 0-73-7z m4-629q0-21 2-47t7-37q41-18 78-18 210 0 210 187 0 64-23 101-15 24-35 41t-37 26-45 14-47 6-53 1q-40 0-56-6 0-29 0-88t-1-89q0-4 0-37t0-54z" horiz-adv-x="785.7" /> -<glyph glyph-name="italic" unicode="" d="m0-78l10 48q3 1 45 12t62 21q16 19 23 56 1 4 35 162t63 303 29 165v14q-13 7-30 11t-39 4-32 3l10 58q19-2 67-4t84-4 67-1q27 0 55 1t67 4 55 4q-2-22-10-50-17-6-57-16t-60-19q-5-10-8-23t-5-23-4-25-4-24q-15-82-49-234t-43-198q-1-5-7-32t-11-51-9-46-4-32l1-10q9-3 103-18-2-24-9-55-6 0-18-1t-18-1q-16 0-49 6t-48 6q-77 1-115 1-28 0-79-5t-68-6z" horiz-adv-x="571.4" /> -<glyph glyph-name="indent-left" unicode="" d="m0 11v107q0 7 5 12t13 6h964q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13z m0 643v107q0 7 5 12t13 6h964q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13z m18-268q0 8 5 13l161 160q5 5 12 5 8 0 13-5t5-13v-321q0-7-5-13t-13-5q-7 0-12 5l-161 161q-5 5-5 13z m339-161v107q0 7 5 13t13 5h607q7 0 13-5t5-13v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13z m0 214v107q0 8 5 13t13 5h607q7 0 13-5t5-13v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12z" horiz-adv-x="1000" /> -<glyph glyph-name="indent-right" unicode="" d="m0 11v107q0 7 5 12t13 6h964q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13z m0 214v321q0 8 5 13t13 5q8 0 13-5l160-160q5-5 5-13t-5-13l-160-161q-5-5-13-5-7 0-13 5t-5 13z m0 429v107q0 7 5 12t13 6h964q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13z m357-429v107q0 7 5 13t13 5h607q7 0 13-5t5-13v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13z m0 214v107q0 8 5 13t13 5h607q7 0 13-5t5-13v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12z" horiz-adv-x="1000" /> -<glyph glyph-name="list-bullet" unicode="" d="m0 64q0 45 31 76t76 31 76-31 31-76-31-76-76-31-76 31-31 76z m0 286q0 45 31 76t76 31 76-31 31-76-31-76-76-31-76 31-31 76z m0 286q0 44 31 76t76 31 76-31 31-76-31-76-76-31-76 31-31 76z m286-625v107q0 7 5 12t13 6h678q7 0 13-6t5-12v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13z m0 285v108q0 7 5 12t13 5h678q7 0 13-5t5-12v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12z m0 286v107q0 8 5 13t13 5h678q7 0 13-5t5-13v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12z" horiz-adv-x="1000" /> -<glyph glyph-name="list-numbered" unicode="" d="m8 237q0 29 14 52t31 38 37 27 31 24 14 25q0 14-9 22t-22 7q-25 0-45-32l-47 33q13 28 40 44t59 16q40 0 68-23t28-63q0-28-19-51t-42-36-42-28-20-30h71v34h59v-89h-202q-4 20-4 30z m3-350l31 49q28-25 60-25 16 0 28 8t12 24q0 35-59 31l-14 31q4 6 18 24t24 31 20 21v1q-9 0-27-1t-27 0v-30h-59v85h186v-49l-53-65q28-6 45-27t17-49q0-45-31-70t-75-26q-60 0-96 37z m8 887l76 71h59v-225h60v-56h-187v56h60q0 22 0 68t1 67v7h-1q-5-10-28-30z m267-763v107q0 8 5 13t13 5h678q7 0 13-6t5-12v-107q0-8-5-13t-13-5h-678q-8 0-13 5t-5 13z m0 285v108q0 7 5 12t13 5h678q7 0 13-5t5-12v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12z m0 286v107q0 8 5 13t13 5h678q7 0 13-5t5-13v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12z" horiz-adv-x="1000" /> -<glyph glyph-name="strike" unicode="" d="m0 296v36q0 8 5 13t13 5h964q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12z m214 239q0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-79t156-72q38-11 96-36 33-16 53-29h-414q-16 19-29 44-27 54-27 105z m17-456q0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-77 40h230q4-22 4-51 0-62-23-119-13-30-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 9-40 15-5 5-5 13v7q0 60-1 87z" horiz-adv-x="1000" /> -<glyph glyph-name="underline" unicode="" d="m0-25v-36q0-8 5-13t13-5h821q8 0 13 5t5 13v36q0 8-5 13t-13 5h-821q-8 0-13-5t-5-13z m0 802q7 0 22 0 34 0 63-2 74-4 92-4 48 0 94 2 65 2 82 3 31 0 48 1l-1-8 1-36v-5q-33-5-69-5-33 0-44-14-7-7-7-73 0-8 0-18t0-15l1-128 8-156q3-69 28-112 20-33 54-52 49-26 98-26 58 0 107 16 31 10 55 28 27 20 37 36 20 31 29 63 12 41 12 128 0 44-2 72t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48h47l114-6q43-2 110 6l10-2q3-21 3-28 0-4-2-17-25-7-47-8-41-6-44-9-8-9-8-23 0-4 0-15t1-17q5-11 13-221 3-109-9-170-8-42-23-68-21-36-62-69-42-32-102-49-61-19-142-19-93 0-159 26-66 26-99 68-34 42-47 109-9 44-9 132v186q0 105-9 119-14 20-82 21-21 2-25 3z" horiz-adv-x="857.1" /> -<glyph glyph-name="cw" unicode="" d="m0 350q0 87 34 166t92 137 136 92 167 34q82 0 158-31t137-88l72 72q16 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 22 7 38l77 77q-82 77-194 77-58 0-111-23t-91-61-62-91-22-111 22-111 62-91 91-61 111-23q66 0 125 29t100 82q4 6 13 7 8 0 14-5l76-77q5-4 6-11t-5-13q-60-74-147-114t-182-41q-87 0-167 34t-136 92-92 137-34 166z" horiz-adv-x="857.1" /> -<glyph glyph-name="ccw" unicode="" d="m0 457v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166-34-166-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 12t5 12l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25z" horiz-adv-x="857.1" /> -<glyph glyph-name="clock" unicode="" d="m0 350q0 117 58 215t155 156 216 58 215-58 156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215z m125 0q0-83 41-152t110-111 153-41 152 41 110 111 41 152-41 152-110 111-152 41-153-41-110-111-41-152z m161-54v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12z" horiz-adv-x="857.1" /> -<glyph glyph-name="eye-off" unicode="" d="m0 314q0 22 11 39 86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15 0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 48-80 37-147 97t-117 137q-11 17-11 38z m71 0q94-144 239-209l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197z m259 72q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19-8 19-19 7q-70 0-120-50t-50-119z m170-393l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-86t81-102q11-19 11-39t-11-38q-22-36-61-81-84-96-194-149t-234-53z m89 159l157 281q4-26 4-47 0-78-44-142t-117-92z" horiz-adv-x="1000" /> -<glyph glyph-name="eye" unicode="" d="m0 314q0 19 11 39 78 128 210 205t279 78 279-78 210-205q11-20 11-39t-11-38q-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38z m71 0q75-114 187-182t242-68 242 68 187 182q-85 132-213 197 34-58 34-125 0-104-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197z m259 72q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19-8 19-19 7q-70 0-120-50t-50-119z" horiz-adv-x="1000" /> -<glyph glyph-name="play" unicode="" d="m0-61v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17l-741-412q-13-7-22-2t-9 20z" horiz-adv-x="785.7" /> -<glyph glyph-name="fast-bw" unicode="" d="m68 350q0 15 11 25l396 396q11 11 18 8t7-18v-396q3 6 7 10l396 396q11 11 18 8t8-18v-822q0-14-8-18t-18 8l-396 396q-4 5-7 11v-397q0-14-7-18t-18 8l-396 396q-11 11-11 25z" horiz-adv-x="928.6" /> -<glyph glyph-name="fast-fw" unicode="" d="m0-61v822q0 14 7 18t18-8l396-396q5-4 8-11v397q0 14 7 18t18-8l396-396q10-11 10-25t-10-25l-396-396q-11-11-18-8t-7 18v396q-3-5-8-10l-396-396q-10-11-18-8t-7 18z" horiz-adv-x="928.6" /> -<glyph glyph-name="pause" unicode="" d="m0-43v786q0 14 11 25t25 11h285q15 0 26-11t10-25v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25z m500 0v786q0 14 11 25t25 11h285q15 0 26-11t10-25v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25z" horiz-adv-x="857.1" /> -<glyph glyph-name="glass" unicode="" d="m52 746q0 13 10 21t21 9 24 3h786q13 0 24-3t21-9 10-21q0-19-24-43l-353-353v-429h179q15 0 25-10t11-25-11-25-25-11h-500q-14 0-25 11t-11 25 11 25 25 10h179v429l-353 353q-24 24-24 43z" horiz-adv-x="1000" /> -<glyph glyph-name="code" unicode="" d="m25 314q0 8 6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 260q-6 6-6 13z m372-350l208 720q3 8 9 11t13 2l35-10q7-2 11-9t1-13l-208-721q-2-7-9-11t-13-1l-34 9q-8 3-11 9t-2 14z m325 118q0 7 5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13t-5-13l-260-260q-6-5-13-5t-13 5l-28 28q-5 6-5 13z" horiz-adv-x="1071.4" /> -<glyph glyph-name="exchange" unicode="" d="m0 136q0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13z m0 303v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13t-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12z" horiz-adv-x="1000" /> -<glyph glyph-name="chat" unicode="" d="m0 421q0 78 53 144t143 104 197 38 197-38 143-104 53-144-53-143-143-104-197-38q-48 0-98 9-70-50-155-72-21-5-48-9h-2q-6 0-12 5t-6 11q-1 2-1 4t1 4 1 3l1 3t2 3 2 3 3 2 2 3q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125z m344-354q32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-13-4q-27 4-48 9-85 23-155 72-50-9-98-9-151 0-263 74z" horiz-adv-x="1000" /> +<glyph glyph-name="align-left" unicode="" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" /> +<glyph glyph-name="print" unicode="" d="m214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" /> +<glyph glyph-name="doc" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-pdf" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-287 331q18-14 47-31 33 4 65 4 82 0 99-27 9-13 1-29 0-1-1-1l-1-2v0q-3-21-39-21-27 0-65 11t-72 29q-123-13-219-46-85-146-135-146-8 0-15 4l-14 6q0 1-3 3-6 6-4 20 5 23 32 51t73 54q8 5 13-3 1-1 1-2 29 47 60 110 38 76 58 146-13 46-17 89t4 71q6 22 23 22h12q13 0 20-8 10-12 5-38-1-3-2-4 0-2 0-5v-17q-1-68-8-107 31-91 82-133z m-322-229q30 13 77 88-29-22-49-47t-28-41z m223 513q-9-23-2-73 1 4 4 24 0 2 4 24 1 3 3 5-1 0-1 1t0 1-1 1q0 12-7 20 0-1 0-1v-2z m-70-368q76 30 159 45-1 0-7 5t-9 8q-43 37-71 98-15-48-47-110-16-31-25-46z m361 8q-14 14-78 14 42-16 69-16 8 0 10 1 0 0-1 1z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-word" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-656 500v-59h39l92-369h88l72 271q4 11 5 25 1 9 1 14h3l1-14q1-1 2-11t3-14l72-271h89l91 369h39v59h-167v-59h50l-55-245q-3-11-4-25l-1-12h-3l-1 12q-1 2-2 11t-3 14l-81 304h-63l-81-304q-1-5-2-13t-2-12l-2-12h-2l-2 12q-1 14-4 25l-55 245h50v59h-167z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-excel" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-547 131v-59h157v59h-42l58 90q3 4 5 9t5 8 2 2h1q0-2 2-6 2-2 3-4t3-4 4-5l60-90h-43v-59h163v59h-38l-107 152 108 158h38v59h-156v-59h41l-57-89q-2-4-6-9t-5-8l-1-1h-1q-1 2-3 5-3 6-9 13l-59 89h42v59h-162v-59h38l106-152-109-158h-38z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-powerpoint" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-554 131v-59h183v59h-52v93h76q43 0 66 9 37 12 59 48t23 82q0 45-21 78t-56 49q-27 10-72 10h-206v-59h52v-310h-52z m197 156h-66v150h67q29 0 46-10 31-19 31-64 0-50-34-67-18-9-44-9z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-image" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-72 250v-178h-571v107l107 107 71-71 215 214z m-464 108q-45 0-76 31t-31 76 31 76 76 31 76-31 31-76-31-76-76-31z" horiz-adv-x="857.1" /> +<glyph glyph-name="users" unicode="" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" /> +<glyph glyph-name="user" unicode="" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" /> +<glyph glyph-name="mail" unicode="" d="m929 11v428q-18-20-39-37-149-114-238-188-28-24-46-38t-48-27-57-13h-2q-26 0-57 13t-48 27-46 38q-88 74-238 188-21 17-39 37v-428q0-8 6-13t12-5h822q7 0 12 5t6 13z m0 586v14t-1 7-1 7-3 5-5 4-8 2h-822q-7 0-12-6t-6-12q0-94 82-159 108-85 224-177 4-2 20-16t25-21 25-18 28-15 24-5h2q11 0 24 5t28 15 25 18 25 21 20 16q116 92 224 177 30 24 56 65t26 73z m71 21v-607q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v607q0 37 26 63t63 26h822q37 0 63-26t26-63z" horiz-adv-x="1000" /> +<glyph glyph-name="videocam" unicode="" d="m1000 654v-608q0-23-22-32-7-3-14-3-15 0-25 10l-225 225v-92q0-67-47-114t-113-47h-393q-67 0-114 47t-47 114v392q0 67 47 114t114 47h393q66 0 113-47t47-114v-92l225 225q10 10 25 10 7 0 14-3 22-9 22-32z" horiz-adv-x="1000" /> +<glyph glyph-name="picture" unicode="" d="m357 529q0-45-31-76t-76-32-76 32-31 76 31 75 76 32 76-32 31-75z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-8 6-13t12-5h893q7 0 13 5t5 13v678q0 7-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" /> +<glyph glyph-name="eye-off" unicode="" d="m310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-92l157 281q4-26 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-86t81-102q11-19 11-39z" horiz-adv-x="1000" /> +<glyph glyph-name="spinner" unicode="" d="m277 100q0-33-24-57t-57-23q-33 0-56 23t-24 57 24 57 56 23q33 0 57-23t24-57z m241-107q0-30-21-51t-51-21-50 21-21 51 21 50 50 21 51-21 21-50z m-339 357q0-37-27-63t-63-26-63 26-26 63 26 63 63 26 63-26 27-63z m580-250q0-26-18-44t-45-18-44 18-18 44 18 44 44 19 45-19 18-44z m-464 500q0-41-29-69t-70-29-69 29-29 69 29 69 69 29 70-29 29-69z m259 107q0-45-32-76t-76-31-75 31-32 76 32 76 75 31 76-31 32-76z m303-357q0-22-15-38t-38-16-38 16-16 38 16 38 38 16 38-16 15-38z m-116 250q0-18-13-32t-32-13-31 13-13 32 13 31 31 14 32-14 13-31z" horiz-adv-x="875" /> +<glyph glyph-name="align-center" unicode="" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-500q-14 0-25 11t-11 25v71q0 15 11 25t25 11h500q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v72q0 14 11 25t25 10h786q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-358q-14 0-25 10t-10 25v72q0 14 10 25t25 11h358q14 0 25-11t10-25z" horiz-adv-x="1000" /> +<glyph glyph-name="align-right" unicode="" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-714q-14 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-857q-14 0-25 11t-11 25v72q0 14 11 25t25 10h857q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-643q-14 0-25 10t-10 25v72q0 14 10 25t25 11h643q15 0 25-11t11-25z" horiz-adv-x="1000" /> +<glyph glyph-name="align-justify" unicode="" d="m1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v72q0 14 11 25t25 10h928q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v72q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" /> +<glyph glyph-name="list" unicode="" d="m143 118v-107q0-7-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 12t13 6h107q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 13t13 5h107q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-107q-7 0-13 6t-5 12v107q0 8 5 13t13 5h107q7 0 13-5t5-13z m857-428v-107q0-7-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 12t12 6h750q7 0 13-6t5-12z m-857 643v-107q0-8-5-13t-13-5h-107q-7 0-13 5t-5 13v107q0 7 5 12t13 6h107q7 0 13-6t5-12z m857-429v-107q0-7-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 13t12 5h750q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-750q-7 0-12 6t-6 12v107q0 8 6 13t12 5h750q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-750q-7 0-12 5t-6 13v107q0 7 6 12t12 6h750q7 0 13-6t5-12z" horiz-adv-x="1000" /> +<glyph glyph-name="indent-left" unicode="" d="m214 546v-321q0-7-5-13t-13-5q-7 0-12 5l-161 161q-5 5-5 13t5 13l161 160q5 5 12 5 8 0 13-5t5-13z m786-428v-107q0-7-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13v107q0 7 5 13t13 5h607q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12v107q0 8 5 13t13 5h607q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z" horiz-adv-x="1000" /> +<glyph glyph-name="indent-right" unicode="" d="m196 386q0-8-5-13l-160-161q-5-5-13-5-7 0-13 5t-5 13v321q0 8 5 13t13 5q8 0 13-5l160-160q5-5 5-13z m804-268v-107q0-7-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z m0 214v-107q0-7-5-13t-13-5h-607q-7 0-13 5t-5 13v107q0 7 5 13t13 5h607q7 0 13-5t5-13z m0 214v-107q0-7-5-12t-13-6h-607q-7 0-13 6t-5 12v107q0 8 5 13t13 5h607q7 0 13-5t5-13z m0 215v-107q0-8-5-13t-13-5h-964q-7 0-13 5t-5 13v107q0 7 5 12t13 6h964q7 0 13-6t5-12z" horiz-adv-x="1000" /> +<glyph glyph-name="list-bullet" unicode="" d="m214 64q0-44-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m0 286q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 7 5 12t13 6h678q7 0 13-6t5-12z m-786 518q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" /> +<glyph glyph-name="list-numbered" unicode="" d="m213-54q0-45-31-70t-75-26q-60 0-96 37l31 49q28-25 60-25 16 0 28 8t12 24q0 35-59 31l-14 31q4 6 18 24t24 31 20 21v1q-9 0-27-1t-27 0v-30h-59v85h186v-49l-53-65q28-6 45-27t17-49z m1 350v-89h-202q-4 20-4 30 0 29 14 52t31 38 37 27 31 24 14 25q0 14-9 22t-22 7q-25 0-45-32l-47 33q13 28 40 44t59 16q40 0 68-23t28-63q0-28-19-51t-42-36-42-28-20-30h71v34h59z m786-178v-107q0-8-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 8 5 13t13 5h678q7 0 13-6t5-12z m-786 502v-56h-187v56h60q0 22 0 68t1 67v7h-1q-5-10-28-30l-40 42 76 71h59v-225h60z m786-216v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" /> +<glyph glyph-name="underline" unicode="" d="m27 726q-21 1-25 2l-2 49q7 0 22 0 34 0 63-2 74-4 92-4 48 0 94 2 65 2 82 3 31 0 48 1l-1-8 1-36v-5q-33-5-69-5-33 0-44-14-7-7-7-73 0-8 0-18t0-15l1-128 8-156q3-69 28-112 20-33 54-52 49-26 98-26 58 0 107 16 31 10 55 28 27 20 37 36 20 31 29 63 12 41 12 128 0 44-2 72t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48h47l114-6q43-2 110 6l10-2q3-21 3-28 0-4-2-17-25-7-47-8-41-6-44-9-8-9-8-23 0-4 0-15t1-17q5-11 13-221 3-109-9-170-8-42-23-68-21-36-62-69-42-32-102-49-61-19-142-19-93 0-159 26-66 26-99 68-34 42-47 109-9 44-9 132v186q0 105-9 119-14 20-82 21z m830-787v36q0 8-5 13t-13 5h-821q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h821q8 0 13 5t5 13z" horiz-adv-x="857.1" /> +<glyph glyph-name="strike" unicode="" d="m982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 54-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-79t156-72q38-11 96-36 33-16 53-29h-414z m282-143h230q4-22 4-51 0-62-23-119-13-30-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-77 40z" horiz-adv-x="1000" /> +<glyph glyph-name="superscript" unicode="" d="m501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2l-5-12q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m355 379v-115h-287l-1 15q-3 16-3 26 0 35 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-29 0-54-21-8-7-20-22l-59 52q15 20 35 37 46 36 105 36 61 0 99-33t38-89q0-31-13-57t-35-43-45-33-46-28-37-29-17-35h130v45h70z" horiz-adv-x="857.1" /> +<glyph glyph-name="subscript" unicode="" d="m501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2l-5-12q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m356-121v-115h-287l-2 15q-2 25-2 26 0 35 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-29 0-54-21-8-6-20-22l-59 52q15 20 35 37 45 36 105 36 62 0 99-33t38-89q0-37-19-66t-47-48-55-35-49-35-23-41h130v45h70z" horiz-adv-x="857.1" /> +<glyph glyph-name="font" unicode="" d="m405 538l-95-251q18 0 76-1t89-1q11 0 32 1-48 141-102 252z m-405-617l1 44q13 4 31 7t32 6 28 8 25 17 17 28l132 344 156 404h72q4-8 6-12l114-268q19-44 60-144t63-153q9-19 33-81t40-94q11-25 19-31 11-9 49-17t47-11q4-22 4-32 0-2-1-7t0-8q-35 0-106 5t-107 4q-42 0-120-4t-99-4q0 24 2 43l73 16q1 0 7 1t9 2 8 3 9 4 6 4 5 6 1 8q0 9-17 54t-40 99-24 56l-251 1q-14-33-43-109t-28-91q0-12 8-21t24-14 27-7 32-5 23-2q1-11 1-32 0-5-2-16-32 0-97 6t-97 6q-5 0-15-3t-12-2q-45-8-105-8z" horiz-adv-x="928.6" /> +<glyph glyph-name="bold" unicode="" d="m310 1q41-18 78-18 210 0 210 187 0 64-23 101-15 24-35 41t-37 26-45 14-47 6-53 1q-40 0-56-6 0-29 0-88t-1-89q0-4 0-37t0-54 2-47 7-37z m-8 417q23-4 61-4 46 0 80 7t61 25 41 50 15 79q0 39-16 68t-45 46-60 24-69 8q-28 0-73-7 0-28 3-84t2-85q0-15 0-45t-1-44q0-26 1-38z m-302-497l1 53q9 2 48 9t59 15q4 7 7 15t4 19 4 18 1 21 0 19v36q0 548-12 572-2 5-12 8t-25 6-28 4-27 3-17 2l-2 46q55 1 190 6t208 5q13 0 38 0t38 0q39 0 76-7t72-24 60-39 41-59 16-76q0-29-9-54t-22-40-36-32-41-25-47-22q86-20 144-75t57-139q0-55-20-100t-52-73-77-47-91-27-98-8q-25 0-74 2t-74 1q-59 0-171-6t-129-7z" horiz-adv-x="785.7" /> +<glyph glyph-name="italic" unicode="" d="m0-78l10 48q3 1 45 12t62 21q16 19 23 56 1 4 35 162t63 303 29 165v14q-13 7-30 11t-39 4-32 3l10 58q19-2 67-4t84-4 67-1q27 0 55 1t67 4 55 4q-2-22-10-50-17-6-57-16t-60-19q-5-10-8-23t-5-23-4-25-4-24q-15-82-49-234t-43-198q-1-5-7-32t-11-51-9-46-4-32l1-10q9-3 103-18-2-24-9-55-6 0-18-1t-18-1q-16 0-49 6t-48 6q-77 1-115 1-28 0-79-5t-68-6z" horiz-adv-x="571.4" /> +<glyph glyph-name="header" unicode="" d="m939-79q-25 0-74 2t-75 2q-24 0-73-2t-74-2q-14 0-21 12t-7 25q0 17 9 26t22 9 29 4 25 9q18 11 18 78l0 218q0 12-1 17-7 3-28 3h-376q-22 0-29-3 0-5 0-17l-1-207q0-79 21-92 9-5 26-7t32-2 25-8 11-26q0-14-7-26t-20-13q-26 0-78 2t-77 2q-24 0-71-2t-71-2q-13 0-20 12t-7 25q0 17 9 25t20 10 26 4 24 9q18 13 18 80l-1 31v454q0 2 1 14t0 21-1 21-2 24-4 20-6 18-9 10q-8 5-25 6t-29 2-23 7-10 26q0 14 6 26t20 13q26 0 78-2t77-2q23 0 71 2t70 2q14 0 21-13t7-26q0-17-9-25t-22-8-28-2-24-7q-19-12-19-90l1-178q0-12 0-18 7-2 22-2h390q14 0 21 2 1 6 1 18l0 178q0 78-19 90-10 6-33 7t-37 7-14 28q0 14 7 26t21 13q24 0 74-2t73-2q24 0 72 2t72 2q14 0 21-13t7-26q0-17-10-25t-22-8-29-2-24-7q-20-13-20-90l1-526q0-66 19-78 9-6 25-7t30-3 23-9 10-24q0-15-6-27t-20-13z" horiz-adv-x="1000" /> +<glyph glyph-name="text-height" unicode="" d="m973 64q19 0 24-10t-6-25l-71-90q-11-15-27-15t-27 15l-71 90q-11 15-6 25t23 10h45v572h-45q-18 0-23 10t6 25l71 90q11 15 27 15t27-15l71-90q11-15 6-25t-24-10h-44v-572h44z m-928 714l30-15q7-3 118-3 25 0 74 1t73 1q21 0 60 0t60 0h164q3 0 12 0t11 0 9 1 10 5 8 10l24 1q2 0 7-1t8 0q1-62 1-187 0-45-2-61-22-8-38-10-14 24-31 71-1 5-6 27t-8 41-4 20q-3 4-7 7t-8 3-8 1-10 1-9-1q-9 0-37 1t-42 0-35-1-40-4q-5-45-4-75 0-53 1-217t1-254q0-9-1-40t0-51 7-38q22-12 69-24t67-21q2-22 2-28 0-8-1-16l-19-1q-43-1-122 5t-115 5q-28 0-85-5t-84-5q-2 29-2 29v5q9 15 34 24t55 17 44 15q10 23 10 213 0 57-1 169t-2 170v65q0 1 0 8t1 14-1 15-2 13-3 8q-6 7-90 7-18 0-52-7t-44-15q-11-7-19-40t-18-62-24-30q-23 15-31 25v213z" horiz-adv-x="1000" /> +<glyph glyph-name="text-width" unicode="" d="m45 778l30-15q7-3 118-3 25 0 74 1t73 1q39 0 138 1t170 0 138-2q18-1 31 17l23 0q3 0 8 0t8 0q1-63 1-188 0-44-3-60-21-8-38-10-14 24-30 71-1 5-6 26t-8 41-4 21q-6 7-15 10-3 1-37 1-17 0-52 1t-57 1-53-2-53-4q-5-45-5-75l1-85v29q0-31 0-86t1-101 0-85q0-9-1-40t0-51 7-38q22-12 69-24t67-21q3-22 3-28 0-8-2-16l-19-1q-42-1-121 5t-116 5q-28 0-84-5t-85-5q-2 29-2 29v5q10 15 35 24t55 16 43 16q4 8 7 41t3 81 1 86-1 86 0 50q0 4-1 12t-2 12q0 4 1 25t0 41 0 42-1 38-4 18q-6 7-90 7-23 0-91-8t-77-14q-11-6-19-40t-18-62-24-30q-23 15-31 25v213z m686-715q7 0 23-11t33-23 33-28 20-17q14-11 14-27t-14-27q-2-2-20-17t-33-27-33-23-23-11q-7 0-11 5t-6 16-1 19 0 18 1 11h-571q0-1 1-11t1-18-2-19-5-16-12-5q-7 0-23 11t-32 23-34 27-20 17q-14 11-14 27t14 27q3 2 20 17t34 28 32 23 23 11q7 0 12-6t5-16 2-19-1-18-1-11h571q0 1-1 11t0 18 1 19 6 16 11 6z" horiz-adv-x="857.1" /> +<glyph glyph-name="to-start" unicode="" d="m546 771q11 11 18 8t7-18v-822q0-14-7-18t-18 8l-396 396q-5 5-7 11v-379q0-14-11-25t-25-11h-71q-15 0-25 11t-11 25v786q0 14 11 25t25 11h71q15 0 25-11t11-25v-379q2 7 7 11z" horiz-adv-x="571.4" /> +<glyph glyph-name="to-end" unicode="" d="m25-71q-10-11-18-8t-7 18v822q0 14 7 18t18-8l396-396q5-4 8-11v379q0 14 10 25t25 11h72q14 0 25-11t10-25v-786q0-14-10-25t-25-11h-72q-14 0-25 11t-10 25v379q-3-6-8-11z" horiz-adv-x="571.4" /> +<glyph glyph-name="ccw" unicode="" d="m857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z" horiz-adv-x="857.1" /> +<glyph glyph-name="cw" unicode="" d="m857 707v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 22 7 38l77 77q-82 77-194 77-58 0-111-23t-91-61-62-91-22-111 22-111 62-91 91-61 111-23q66 0 125 29t100 82q4 6 13 7 8 0 14-5l76-77q5-4 6-11t-5-13q-60-74-147-114t-182-41q-87 0-167 34t-136 92-92 137-34 166 34 166 92 137 136 92 167 34q82 0 158-31t137-88l72 72q16 18 39 8 22-9 22-33z" horiz-adv-x="857.1" /> +<glyph glyph-name="arrows-cw" unicode="" d="m843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-37 90-57t105-20q74 0 139 37t104 99q6 10 29 66 5 13 17 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" /> +<glyph glyph-name="file-code" unicode="" d="m819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 16-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 15t-16 38v233h-429v-858h715z m-518 500q4 7 12 7t13-3l28-21q7-5 7-12t-3-14l-102-135 102-136q4-6 3-13t-7-12l-28-21q-6-4-13-4t-12 7l-126 168q-8 11 0 21z m447-168q8-10 0-21l-126-168q-4-6-11-7t-14 4l-28 21q-6 5-7 12t3 13l102 136-102 135q-4 7-3 14t7 12l28 21q6 4 14 3t11-7z m-346-257q-7 1-11 7t-3 14l77 464q1 7 7 11t14 3l35-5q7-2 11-8t3-13l-77-464q-1-7-7-11t-13-3z" horiz-adv-x="857.1" /> +<glyph glyph-name="code" unicode="" d="m344 69l-28-28q-5-5-12-5t-13 5l-260 260q-6 6-6 13t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13t-6-13z m330 596l-208-721q-2-7-9-11t-13-1l-34 9q-8 3-11 9t-2 14l208 720q3 8 9 11t13 2l35-10q7-2 11-9t1-13z m367-364l-260-260q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13t-5-13z" horiz-adv-x="1071.4" /> +<glyph glyph-name="chat" unicode="" d="m786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-13-4q-27 4-48 9-85 23-155 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" /> +<glyph glyph-name="comment-empty" unicode="" d="m500 636q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-54q-13-50-39-95 85 35 154 95l24 21 31-3q39-5 73-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-7-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 7t1 5 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" /> +<glyph glyph-name="trash-empty" unicode="" d="m286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q22 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" /> +<glyph glyph-name="mic" unicode="" d="m643 457v-71q0-124-82-215t-204-104v-74h143q15 0 25-11t11-25-11-25-25-11h-357q-15 0-25 11t-11 25 11 25 25 11h143v74q-121 13-204 104t-82 215v71q0 15 11 25t25 11 25-11 10-25v-71q0-104 74-177t176-73 177 73 73 177v71q0 15 11 25t25 11 25-11 11-25z m-143 214v-285q0-74-52-126t-127-53-126 53-52 126v285q0 74 52 127t126 52 127-52 52-127z" horiz-adv-x="642.9" /> +<glyph glyph-name="play" unicode="" d="m772 333l-741-412q-13-7-22-2t-9 20v822q0 14 9 20t22-2l741-412q13-7 13-17t-13-17z" horiz-adv-x="785.7" /> +<glyph glyph-name="stop" unicode="" d="m857 743v-786q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v786q0 14 11 25t25 11h785q15 0 26-11t10-25z" horiz-adv-x="857.1" /> +<glyph glyph-name="pause" unicode="" d="m857 743v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25v786q0 14 11 25t25 11h285q15 0 26-11t10-25z m-500 0v-786q0-14-10-25t-26-11h-285q-15 0-25 11t-11 25v786q0 14 11 25t25 11h285q15 0 26-11t10-25z" horiz-adv-x="857.1" /> +<glyph glyph-name="gauge" unicode="" d="m214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" /> +<glyph glyph-name="brush" unicode="" d="m901 850q39 0 69-26t29-65q0-35-25-84-185-351-260-420-54-51-121-51-71 0-121 52t-51 122q0 72 52 119l356 323q33 30 72 30z m-507-577q22-42 59-73t84-42l1-40q2-119-72-193t-195-75q-68 0-121 26t-86 71-48 102-16 123q4-3 23-17t35-25 32-20 26-9q23 0 31 20 14 37 32 63t39 42 49 27 57 14 70 6z" horiz-adv-x="1000" /> +<glyph glyph-name="slideshare" unicode="" d="m487 437q0-46-35-79t-85-34-85 34-36 79q0 47 36 80t85 33 85-33 35-80z m280 0q0-46-35-79t-85-34q-50 0-85 34t-36 79q0 47 36 80t85 33q50 0 85-33t35-80z m126-100v372q0 48-18 69t-62 20h-620q-47 0-63-19t-17-70v-376q24-13 50-22t45-16 45-10 40-6 39-2 32-1 32 1 25 1q38 1 53-15 3-3 5-5 15-14 34-28 4 51 66 48 3 0 21 0t24-1 25-1 30 1 30 2 34 5 35 7 37 11 38 15 40 20z m91 2q-68-83-208-140 47-159-13-260-36-63-102-82-58-18-101 8-48 28-46 92l-1 182v0q-4 1-13 3t-13 3l-1-188q2-64-46-92-44-26-102-8-66 20-102 83-59 101-12 259-140 57-208 140-14 21-2 36t33-1q2-1 7-4t6-4v387q0 40 26 69t64 28h701q37 0 64-28t26-69v-387l12 8q21 15 33 0t-2-35z" horiz-adv-x="1000" /> +<glyph glyph-name="tasks" unicode="" d="m571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" /> +<glyph glyph-name="cog" unicode="" d="m571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 15 20 15h124q7 0 13-4t7-12l15-103q28-9 50-21l80 60q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-13 0-6-4-12-9-12-29-38t-30-39q14-28 23-55l102-15q7-1 12-7t4-13z" horiz-adv-x="857.1" /> +<glyph glyph-name="exchange" unicode="" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13 0 8 5 13l179 178q5 5 12 5 8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" /> +<glyph glyph-name="star-empty" unicode="" d="m634 290l171 165-235 35-106 213-105-213-236-35 171-165-41-235 211 111 211-111z m295 199q0-12-15-27l-203-197 48-279q1-4 1-12 0-28-23-28-10 0-22 7l-251 132-250-132q-13-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> +<glyph glyph-name="star" unicode="" d="m929 489q0-12-15-27l-203-197 48-279q1-4 1-12 0-11-6-19t-17-9q-10 0-22 7l-251 132-250-132q-13-7-23-7-11 0-17 9t-6 19q0 4 1 12l48 279-203 197q-14 15-14 27 0 21 31 26l280 40 126 254q11 23 27 23t28-23l125-254 280-40q32-5 32-26z" horiz-adv-x="928.6" /> +<glyph glyph-name="clock" unicode="" d="m500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" /> </font> </defs> </svg>
\ No newline at end of file diff --git a/src/static/font/fontawesome-etherpad.ttf b/src/static/font/fontawesome-etherpad.ttf Binary files differindex a179bd19..f2fe398e 100644 --- a/src/static/font/fontawesome-etherpad.ttf +++ b/src/static/font/fontawesome-etherpad.ttf diff --git a/src/static/font/fontawesome-etherpad.woff b/src/static/font/fontawesome-etherpad.woff Binary files differindex 1015d8e2..98de9c3e 100644 --- a/src/static/font/fontawesome-etherpad.woff +++ b/src/static/font/fontawesome-etherpad.woff diff --git a/src/static/font/opendyslexic.otf b/src/static/font/opendyslexic.otf Binary files differnew file mode 100644 index 00000000..1a7c9d41 --- /dev/null +++ b/src/static/font/opendyslexic.otf diff --git a/src/static/img/backgrad.gif b/src/static/img/backgrad.gif Binary files differdeleted file mode 100644 index 8fee1a5b..00000000 --- a/src/static/img/backgrad.gif +++ /dev/null diff --git a/src/static/img/connectingbar.gif b/src/static/img/connectingbar.gif Binary files differdeleted file mode 100644 index 5e54d694..00000000 --- a/src/static/img/connectingbar.gif +++ /dev/null diff --git a/src/static/img/crushed_button_depressed.png b/src/static/img/crushed_button_depressed.png Binary files differdeleted file mode 100644 index d75dcce2..00000000 --- a/src/static/img/crushed_button_depressed.png +++ /dev/null diff --git a/src/static/img/crushed_button_undepressed.png b/src/static/img/crushed_button_undepressed.png Binary files differdeleted file mode 100644 index d86e3f39..00000000 --- a/src/static/img/crushed_button_undepressed.png +++ /dev/null diff --git a/src/static/img/crushed_current_location.png b/src/static/img/crushed_current_location.png Binary files differdeleted file mode 100644 index 76e08359..00000000 --- a/src/static/img/crushed_current_location.png +++ /dev/null diff --git a/src/static/img/etherpad_lite_icons.png b/src/static/img/etherpad_lite_icons.png Binary files differdeleted file mode 100644 index 27867d42..00000000 --- a/src/static/img/etherpad_lite_icons.png +++ /dev/null diff --git a/src/static/img/fileicons.gif b/src/static/img/fileicons.gif Binary files differdeleted file mode 100644 index c03b6031..00000000 --- a/src/static/img/fileicons.gif +++ /dev/null diff --git a/src/static/img/gritter.png b/src/static/img/gritter.png Binary files differdeleted file mode 100644 index 0ca3bc0a..00000000 --- a/src/static/img/gritter.png +++ /dev/null diff --git a/src/static/img/leftarrow.png b/src/static/img/leftarrow.png Binary files differdeleted file mode 100644 index 1bec1288..00000000 --- a/src/static/img/leftarrow.png +++ /dev/null diff --git a/src/static/img/loading.gif b/src/static/img/loading.gif Binary files differdeleted file mode 100644 index bb42be59..00000000 --- a/src/static/img/loading.gif +++ /dev/null diff --git a/src/static/img/pause.png b/src/static/img/pause.png Binary files differdeleted file mode 100644 index 657782c0..00000000 --- a/src/static/img/pause.png +++ /dev/null diff --git a/src/static/img/play.png b/src/static/img/play.png Binary files differdeleted file mode 100644 index 19afe034..00000000 --- a/src/static/img/play.png +++ /dev/null diff --git a/src/static/img/roundcorner_left.gif b/src/static/img/roundcorner_left.gif Binary files differdeleted file mode 100644 index 000de752..00000000 --- a/src/static/img/roundcorner_left.gif +++ /dev/null diff --git a/src/static/img/roundcorner_right.gif b/src/static/img/roundcorner_right.gif Binary files differdeleted file mode 100644 index 97acfbf2..00000000 --- a/src/static/img/roundcorner_right.gif +++ /dev/null diff --git a/src/static/img/star.png b/src/static/img/star.png Binary files differdeleted file mode 100644 index e0c7099e..00000000 --- a/src/static/img/star.png +++ /dev/null diff --git a/src/static/img/stepper_buttons.png b/src/static/img/stepper_buttons.png Binary files differdeleted file mode 100644 index e011a451..00000000 --- a/src/static/img/stepper_buttons.png +++ /dev/null diff --git a/src/static/img/timeslider_background.png b/src/static/img/timeslider_background.png Binary files differdeleted file mode 100644 index 851af4e8..00000000 --- a/src/static/img/timeslider_background.png +++ /dev/null diff --git a/src/static/img/timeslider_left.png b/src/static/img/timeslider_left.png Binary files differdeleted file mode 100644 index 48a9b0e1..00000000 --- a/src/static/img/timeslider_left.png +++ /dev/null diff --git a/src/static/img/timeslider_right.png b/src/static/img/timeslider_right.png Binary files differdeleted file mode 100644 index 1a1b2685..00000000 --- a/src/static/img/timeslider_right.png +++ /dev/null diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 2d523f6a..3f464908 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -97,6 +97,85 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ }, /* + Gets all attributes on a line + @param lineNum: the number of the line to get the attribute for + */ + getAttributesOnLine: function(lineNum){ + // get attributes of first char of line + var aline = this.rep.alines[lineNum]; + var attributes = [] + if (aline) + { + var opIter = Changeset.opIterator(aline) + , op + if (opIter.hasNext()) + { + op = opIter.next() + if(!op.attribs) return [] + + Changeset.eachAttribNumber(op.attribs, function(n) { + attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]) + }.bind(this)) + return attributes; + } + } + return []; + }, + + /* + Gets all attributes at a position containing line number and column + @param lineNumber starting with zero + @param column starting with zero + returns a list of attributes in the format + [ ["key","value"], ["key","value"], ... ] + */ + getAttributesOnPosition: function(lineNumber, column){ + // get all attributes of the line + var aline = this.rep.alines[lineNumber]; + + if (!aline) { + return []; + } + // iterate through all operations of a line + var opIter = Changeset.opIterator(aline); + + // we need to sum up how much characters each operations take until the wanted position + var currentPointer = 0; + var attributes = []; + var currentOperation; + + while (opIter.hasNext()) { + currentOperation = opIter.next(); + currentPointer = currentPointer + currentOperation.chars; + + if (currentPointer > column) { + // we got the operation of the wanted position, now collect all its attributes + Changeset.eachAttribNumber(currentOperation.attribs, function (n) { + attributes.push([ + this.rep.apool.getAttribKey(n), + this.rep.apool.getAttribValue(n) + ]); + }.bind(this)); + + // skip the loop + return attributes; + } + } + return attributes; + + }, + + /* + Gets all attributes at caret position + if the user selected a range, the start of the selection is taken + returns a list of attributes in the format + [ ["key","value"], ["key","value"], ... ] + */ + getAttributesOnCaret: function(){ + return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]); + }, + + /* Sets a specified attribute on a line @param lineNum: the number of the line to set the attribute for @param attributeKey: the name of the attribute to set, e.g. list @@ -127,38 +206,58 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ return this.applyChangeset(builder); }, - /* - Removes a specified attribute on a line - @param lineNum: the number of the affected line - @param attributeKey: the name of the attribute to remove, e.g. list - + /** + * Removes a specified attribute on a line + * @param lineNum the number of the affected line + * @param attributeName the name of the attribute to remove, e.g. list + * @param attributeValue if given only attributes with equal value will be removed */ - removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ - - var loc = [0,0]; - var builder = Changeset.builder(this.rep.lines.totalWidth()); - var hasMarker = this.lineHasMarker(lineNum); - - if(hasMarker){ - ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0])); - ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1])); + removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ + var builder = Changeset.builder(this.rep.lines.totalWidth()); + var hasMarker = this.lineHasMarker(lineNum); + var found = false; + + var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { + if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ + found = true; + return [attributeName, '']; } - - return this.applyChangeset(builder); - }, + return attrib; + }); + + if (!found) { + return; + } + + ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); + + var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];}) + .map(function(a){return a[0];}).difference(['author', 'lmkr', 'insertorder', 'start']).size().value(); + + //if we have marker and any of attributes don't need to have marker. we need delete it + if(hasMarker && !countAttribsWithMarker){ + ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); + }else{ + ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); + } + + return this.applyChangeset(builder); + }, /* - Sets a specified attribute on a line - @param lineNum: the number of the line to set the attribute for - @param attributeKey: the name of the attribute to set, e.g. list - @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level) + Toggles a line attribute for the specified line number + If a line attribute with the specified name exists with any value it will be removed + Otherwise it will be set to the given value + @param lineNum: the number of the line to toggle the attribute for + @param attributeKey: the name of the attribute to toggle, e.g. list + @param attributeValue: the value to pass to the attribute (e.g. indention level) */ toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) { - return this.getAttributeOnLine(attributeName) ? + return this.getAttributeOnLine(lineNum, attributeName) ? this.removeAttributeOnLine(lineNum, attributeName) : this.setAttributeOnLine(lineNum, attributeName, attributeValue); } }); -module.exports = AttributeManager;
\ No newline at end of file +module.exports = AttributeManager; diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 355bef4a..df180f9c 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -507,6 +507,11 @@ exports.opAssembler = function () { */ exports.stringIterator = function (str) { var curIndex = 0; + // newLines is the number of \n between curIndex and str.length + var newLines = str.split("\n").length - 1 + function getnewLines(){ + return newLines + } function assertRemaining(n) { exports.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")"); @@ -515,6 +520,7 @@ exports.stringIterator = function (str) { function take(n) { assertRemaining(n); var s = str.substr(curIndex, n); + newLines -= s.split("\n").length - 1 curIndex += n; return s; } @@ -537,7 +543,8 @@ exports.stringIterator = function (str) { take: take, skip: skip, remaining: remaining, - peek: peek + peek: peek, + newlines: getnewLines }; }; @@ -913,12 +920,27 @@ exports.applyToText = function (cs, str) { var op = csIter.next(); switch (op.opcode) { case '+': + //op is + and op.lines 0: no newlines must be in op.chars + //op is + and op.lines >0: op.chars must include op.lines newlines + if(op.lines != bankIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op +; cs:"+cs+" and text:"+str); + } assem.append(bankIter.take(op.chars)); break; case '-': + //op is - and op.lines 0: no newlines must be in the deleted string + //op is - and op.lines >0: op.lines newlines must be in the deleted string + if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op -; cs:"+cs+" and text:"+str); + } strIter.skip(op.chars); break; case '=': + //op is = and op.lines 0: no newlines must be in the copied string + //op is = and op.lines >0: op.lines newlines must be in the copied string + if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ + throw new Error("newline count is wrong in op =; cs:"+cs+" and text:"+str); + } assem.append(strIter.take(op.chars)); break; } @@ -1216,7 +1238,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { } } - exports.assert(!lineAssem, "line assembler not finished"); + exports.assert(!lineAssem, "line assembler not finished:"+cs); mut.close(); //dmesg("-> "+lines.toSource()); diff --git a/src/static/js/ace.js b/src/static/js/ace.js index addc412f..c446939a 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -265,7 +265,7 @@ plugins.ensure(function () {\n\ iframeHTML: iframeHTML }); - iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false"> </body></html>'); + iframeHTML.push('</head><body id="innerdocbody" role="application" class="syntax" spellcheck="false"> </body></html>'); // Expose myself to global for my child frame. var thisFunctionsName = "ChildAccessibleAce2Editor"; @@ -279,6 +279,7 @@ window.onload = function () {\n\ setTimeout(function () {\n\ var iframe = document.createElement("IFRAME");\n\ iframe.name = "ace_inner";\n\ + iframe.title = "pad";\n\ iframe.scrolling = "no";\n\ var outerdocbody = document.getElementById("outerdocbody");\n\ iframe.frameBorder = 0;\n\ @@ -319,6 +320,7 @@ window.onload = function () {\n\ var outerFrame = document.createElement("IFRAME"); outerFrame.name = "ace_outer"; outerFrame.frameBorder = 0; // for IE + outerFrame.title = "Ether"; info.frame = outerFrame; document.getElementById(containerId).appendChild(outerFrame); diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index ad5dd905..cf062d26 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -21,6 +21,17 @@ */ var _, $, jQuery, plugins, Ace2Common; +var browser = require('./browser').browser; +if(browser.msie){ + // Honestly fuck IE royally. + // Basically every hack we have since V11 causes a problem + if(parseInt(browser.version) >= 11){ + delete browser.msie; + browser.chrome = true; + browser.modernIE = true; + } +} + Ace2Common = require('./ace2_common'); plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); @@ -28,15 +39,13 @@ $ = jQuery = require('./rjquery').$; _ = require("./underscore"); var isNodeText = Ace2Common.isNodeText, - browser = $.browser, getAssoc = Ace2Common.getAssoc, setAssoc = Ace2Common.setAssoc, isTextNode = Ace2Common.isTextNode, binarySearchInfinite = Ace2Common.binarySearchInfinite, htmlPrettyEscape = Ace2Common.htmlPrettyEscape, noop = Ace2Common.noop; - var hooks = require('./pluginfw/hooks'); - +var hooks = require('./pluginfw/hooks'); function Ace2Inner(){ @@ -58,7 +67,7 @@ function Ace2Inner(){ var isSetUp = false; var THE_TAB = ' '; //4 - var MAX_LIST_LEVEL = 8; + var MAX_LIST_LEVEL = 16; var LINE_NUMBER_PADDING_RIGHT = 4; var LINE_NUMBER_PADDING_LEFT = 4; @@ -152,7 +161,6 @@ function Ace2Inner(){ var dmesg = noop; window.dmesg = noop; - var scheduler = parent; // hack for opera required var textFace = 'monospace'; @@ -597,6 +605,16 @@ function Ace2Inner(){ fixView(); }); }, 0); + + // Chrome can't handle the truth.. If CSS rule white-space:pre-wrap + // is true then any paste event will insert two lines.. + // Sadly this will mean you get a walking Caret in Chrome when clicking on a URL + // So this has to be set to pre-wrap ;( + // We need to file a bug w/ the Chromium team. + if(browser.chrome){ + $("#innerdocbody").css({"white-space":"pre-wrap"}); + } + } function setStyled(newVal) @@ -1348,7 +1366,7 @@ function Ace2Inner(){ // (from how it looks in our representation) and record them in a way // that can be used to "normalize" the document (apply the changes to our // representation, and put the DOM in a canonical form). - //top.console.log("observeChangesAroundNode(%o)", node); + // top.console.log("observeChangesAroundNode(%o)", node); var cleanNode; var hasAdjacentDirtyness; if (!isNodeDirty(node)) @@ -1600,7 +1618,7 @@ function Ace2Inner(){ if (linesWrapped > 0) { - if(!browser.ie){ + if(!browser.msie){ // chrome decides in it's infinite wisdom that its okay to put the browsers visisble window in the middle of the span // an outcome of this is that the first chars of the string are no longer visible to the user.. Yay chrome.. // Move the browsers visible area to the left hand side of the span @@ -1905,6 +1923,7 @@ function Ace2Inner(){ if (charsLeft === 0) { var index = 0; + browser.msie = false; // Temp fix to resolve enter and backspace issues.. if (browser.msie && line == (rep.lines.length() - 1) && lineNode.childNodes.length === 0) { // best to stay at end of last empty div in IE @@ -2306,74 +2325,72 @@ function Ace2Inner(){ } editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection; + function getAttributeOnSelection(attributeName){ - if (!(rep.selStart && rep.selEnd)) return; - var selectionAllHasIt = true; + if (!(rep.selStart && rep.selEnd)) return + var withIt = Changeset.makeAttribsString('+', [ [attributeName, 'true'] ], rep.apool); var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); - function hasIt(attribs) { return withItRegex.test(attribs); } - var selStartLine = rep.selStart[0]; - var selEndLine = rep.selEnd[0]; - for (var n = selStartLine; n <= selEndLine; n++) - { - var opIter = Changeset.opIterator(rep.alines[n]); - var indexIntoLine = 0; - var selectionStartInLine = 0; - var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline - if(rep.lines.atIndex(n).text.length == 0){ - return false; // If the line length is 0 we basically treat it as having no formatting - } - if(rep.selStart[1] == rep.selEnd[1] && rep.selStart[1] == rep.lines.atIndex(n).text.length){ - return false; // If we're at the end of a line we treat it as having no formatting - } - if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){ - rep.selEnd[1] == 1; - } - if(rep.selEnd[1] == -1){ - rep.selEnd[1] = 1; // sometimes rep.selEnd is -1, not sure why.. When it is we should look at the first char - } - if (n == selStartLine) - { - selectionStartInLine = rep.selStart[1]; - } - if (n == selEndLine) - { - selectionEndInLine = rep.selEnd[1]; - } - while (opIter.hasNext()) - { + return rangeHasAttrib(rep.selStart, rep.selEnd) + + function rangeHasAttrib(selStart, selEnd) { + // if range is collapsed -> no attribs in range + if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false + + if(selStart[0] != selEnd[0]) { // -> More than one line selected + var hasAttrib = true + + // from selStart to the end of the first line + hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]) + + // for all lines in between + for(var n=selStart[0]+1; n < selEnd[0]; n++) { + hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]) + } + + // for the last, potentially partial, line + hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]) + + return hasAttrib + } + + // Logic tells us we now have a range on a single line + + var lineNum = selStart[0] + , start = selStart[1] + , end = selEnd[1] + , hasAttrib = true + + // Iterate over attribs on this line + + var opIter = Changeset.opIterator(rep.alines[lineNum]) + , indexIntoLine = 0 + + while (opIter.hasNext()) { var op = opIter.next(); var opStartInLine = indexIntoLine; var opEndInLine = opStartInLine + op.chars; - if (!hasIt(op.attribs)) - { + if (!hasIt(op.attribs)) { // does op overlap selection? - if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) - { - selectionAllHasIt = false; + if (!(opEndInLine <= start || opStartInLine >= end)) { + hasAttrib = false; // since it's overlapping but hasn't got the attrib -> range hasn't got it break; } } indexIntoLine = opEndInLine; } - if (!selectionAllHasIt) - { - break; - } - } - if(selectionAllHasIt){ - return true; - }else{ - return false; + + return hasAttrib } } + editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection; function toggleAttributeOnSelection(attributeName) @@ -2935,7 +2952,6 @@ function Ace2Inner(){ { return ""; }; - return result; } else @@ -3279,6 +3295,15 @@ function Ace2Inner(){ return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])]; } + function handleCut(evt) + { + inCallStackIfNecessary("handleCut", function() + { + doDeleteKey(evt); + }); + return true; + } + function handleClick(evt) { inCallStackIfNecessary("handleClick", function() @@ -3333,7 +3358,7 @@ function Ace2Inner(){ if (listType) { var text = rep.lines.atIndex(lineNum).text; - listType = /([a-z]+)([12345678])/.exec(listType); + listType = /([a-z]+)([0-9]+)/.exec(listType); var type = listType[1]; var level = Number(listType[2]); @@ -3385,7 +3410,7 @@ function Ace2Inner(){ var level = 0; if (listType) { - listType = /([a-z]+)([12345678])/.exec(listType); + listType = /([a-z]+)([0-9]+)/.exec(listType); if (listType) { t = listType[1]; @@ -3559,7 +3584,7 @@ function Ace2Inner(){ // On Mac and Linux, move right moves to end of word and move left moves to start; // on Windows, always move to start of word. // On Windows, Firefox and IE disagree on whether to stop for punctuation (FF says no). - if (browser.windows && forwardNotBack) + if (browser.msie && forwardNotBack) { while ((!isDone()) && isWordChar(nextChar())) { @@ -3593,6 +3618,8 @@ function Ace2Inner(){ var charCode = evt.charCode; var keyCode = evt.keyCode; var which = evt.which; + var altKey = evt.altKey; + var shiftKey = evt.shiftKey; // prevent ESC key if (keyCode == 27) @@ -3600,6 +3627,25 @@ function Ace2Inner(){ evt.preventDefault(); return; } + // Is caret potentially hidden by the chat button? + var myselection = document.getSelection(); // get the current caret selection + var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 + + if(myselection.focusNode.wholeText){ // Is there any content? If not lineHeight will report wrong.. + var lineHeight = myselection.focusNode.parentNode.offsetHeight; // line height of populated links + }else{ + var lineHeight = myselection.focusNode.offsetHeight; // line height of blank lines + } + var heightOfChatIcon = parent.parent.$('#chaticon').height(); // height of the chat icon button + lineHeight = (lineHeight *2) + heightOfChatIcon; + var viewport = getViewPortTopBottom(); + var viewportHeight = viewport.bottom - viewport.top - lineHeight; + var relCaretOffsetTop = caretOffsetTop - viewport.top; // relative Caret Offset Top to viewport + if (viewportHeight < relCaretOffsetTop){ + parent.parent.$("#chaticon").css("opacity",".3"); // make chaticon opacity low when user types near it + }else{ + parent.parent.$("#chaticon").css("opacity","1"); // make chaticon opacity back to full (so fully visible) + } //dmesg("keyevent type: "+type+", which: "+which); // Don't take action based on modifier keys going up and down. @@ -3614,11 +3660,9 @@ function Ace2Inner(){ if (keyCode == 13 && browser.opera && (type == "keypress")){ return; // This stops double enters in Opera but double Tabs still show on single tab keypress, adding keyCode == 9 to this doesn't help as the event is fired twice } - var specialHandled = false; var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress")); var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress")); - var stopped = false; inCallStackIfNecessary("handleKeyEvent", function() @@ -3646,6 +3690,101 @@ function Ace2Inner(){ evt:evt }); specialHandled = (specialHandledInHook&&specialHandledInHook.length>0)?specialHandledInHook[0]:specialHandled; + if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120){ + // Alt F9 focuses on the File Menu and/or editbar. + // Note that while most editors use Alt F10 this is not desirable + // As ubuntu cannot use Alt F10.... + // Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it) + var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); + $(this).blur(); + firstEditbarElement.focus(); + evt.preventDefault(); + } + if ((!specialHandled) && altKey && keyCode == 67){ + // Alt c focuses on the Chat window + $(this).blur(); + parent.parent.chat.show(); + parent.parent.chat.focus(); + evt.preventDefault(); + } + if ((!specialHandled) && evt.ctrlKey && shiftKey && keyCode == 50 && type === "keydown"){ + // Control-Shift-2 shows a gritter popup showing a line author + var lineNumber = rep.selEnd[0]; + var alineAttrs = rep.alines[lineNumber]; + var apool = rep.apool; + + // TODO: support selection ranges + // TODO: Still work when authorship colors have been cleared + // TODO: i18n + // TODO: There appears to be a race condition or so. + + var author = null; + if (alineAttrs) { + var authors = []; + var authorNames = []; + var opIter = Changeset.opIterator(alineAttrs); + + while (opIter.hasNext()){ + var op = opIter.next(); + authorId = Changeset.opAttributeValue(op, 'author', apool); + + // Only push unique authors and ones with values + if(authors.indexOf(authorId) === -1 && authorId !== ""){ + authors.push(authorId); + } + + } + + } + + // No author information is available IE on a new pad. + if(authors.length === 0){ + var authorString = "No author information is available"; + } + else{ + // Known authors info, both current and historical + var padAuthors = parent.parent.pad.userList(); + var authorObj = {}; + authors.forEach(function(authorId){ + padAuthors.forEach(function(padAuthor){ + // If the person doing the lookup is the author.. + if(padAuthor.userId === authorId){ + if(parent.parent.clientVars.userId === authorId){ + authorObj = { + name: "Me" + } + }else{ + authorObj = padAuthor; + } + } + }); + if(!authorObj){ + author = "Unknown"; + return; + } + author = authorObj.name; + if(!author) author = "Unknown"; + authorNames.push(author); + }) + } + if(authors.length === 1){ + var authorString = "The author of this line is " + authorNames; + } + if(authors.length > 1){ + var authorString = "The authors of this line are " + authorNames.join(" & "); + } + + parent.parent.$.gritter.add({ + // (string | mandatory) the heading of the notification + title: 'Line Authors', + // (string | mandatory) the text inside the notification + text: authorString, + // (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 + time: '4000' + }); + } if ((!specialHandled) && isTypeForSpecialKey && keyCode == 8) { // "delete" key; in mozilla, if we're at the beginning of a line, normalize now, @@ -3674,7 +3813,7 @@ function Ace2Inner(){ }, 0); specialHandled = true; } - 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) && !evt.altKey) /* Do a saved revision on ctrl S */ { evt.preventDefault(); var originalBackground = parent.parent.$('#revisionlink').css("background") @@ -3823,7 +3962,10 @@ function Ace2Inner(){ 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); + + // sometimes the first selection is -1 which causes problems (Especially with ep_page_view) + // so use focusNode.offsetTop value. + if(caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop; setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document }, 200); @@ -3831,7 +3973,7 @@ function Ace2Inner(){ /* Attempt to apply some sanity to cursor handling in Chrome after a copy / paste event 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 .. Sorry if this is ugly, blame Chrome's weird handling of viewports after new content is added*/ - 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 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 @@ -3865,10 +4007,10 @@ function Ace2Inner(){ // 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 - top.console.log(rep.selStart[0], visibleLineRange[1], rep.selStart[0], visibleLineRange[0]); + // 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 + // top.console.log(rep.selStart[0], visibleLineRange[1], rep.selStart[0], visibleLineRange[0]); var newY = viewport.top + lineHeight; } if(newY){ @@ -3902,7 +4044,7 @@ function Ace2Inner(){ } // Is part of multi-keystroke international character on Firefox Mac - var isFirefoxHalfCharacter = (browser.mozilla && evt.altKey && charCode === 0 && keyCode === 0); + var isFirefoxHalfCharacter = (browser.firefox && evt.altKey && charCode === 0 && keyCode === 0); // Is part of multi-keystroke international character on Safari Mac var isSafariHalfCharacter = (browser.safari && evt.altKey && keyCode == 229); @@ -4217,12 +4359,6 @@ function Ace2Inner(){ end.collapse(false); selection.startPoint = pointFromCollapsedRange(start); selection.endPoint = pointFromCollapsedRange(end); -/*if ((!selection.startPoint.node.isText) && (!selection.endPoint.node.isText)) { - console.log(selection.startPoint.node.uniqueId()+","+ - selection.startPoint.index+" / "+ - selection.endPoint.node.uniqueId()+","+ - selection.endPoint.index); -}*/ } return selection; } @@ -4634,7 +4770,7 @@ function Ace2Inner(){ setIfNecessary(iframe.style, "width", newWidth + "px"); setIfNecessary(sideDiv.style, "height", newHeight + "px"); } - if (browser.mozilla) + if (browser.firefox) { if (!doesWrap) { @@ -4805,14 +4941,27 @@ function Ace2Inner(){ $(document).on("keypress", handleKeyEvent); $(document).on("keyup", handleKeyEvent); $(document).on("click", handleClick); + + // Disabled: https://github.com/ether/etherpad-lite/issues/2546 + // Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533 + // $(document).on("cut", handleCut); + $(root).on("blur", handleBlur); if (browser.msie) { $(document).on("click", handleIEOuterClick); } if (browser.msie) $(root).on("paste", handleIEPaste); + + // Don't paste on middle click of links + $(root).on("paste", function(e){ + if(e.target.a){ + e.preventDefault(); + } + }) + // CompositionEvent is not implemented below IE version 8 - if ( !(browser.msie && browser.version < 9) && document.documentElement) + if ( !(browser.msie && parseInt(browser.version <= 9)) && document.documentElement) { $(document.documentElement).on("compositionstart", handleCompositionEvent); $(document.documentElement).on("compositionend", handleCompositionEvent); @@ -5029,6 +5178,7 @@ function Ace2Inner(){ { if(listType == ''){ documentAttributeManager.removeAttributeOnLine(lineNum, listAttributeName); + documentAttributeManager.removeAttributeOnLine(lineNum, 'start'); }else{ documentAttributeManager.setAttributeOnLine(lineNum, listAttributeName, listType); } @@ -5050,7 +5200,7 @@ function Ace2Inner(){ { return null; } - type = /([a-z]+)[12345678]/.exec(type); + type = /([a-z]+)[0-9]+/.exec(type); if(type[1] == "indent") { return null; @@ -5059,7 +5209,7 @@ function Ace2Inner(){ //2-find the first line of the list while(lineNum-1 >= 0 && (type=getLineListType(lineNum-1))) { - type = /([a-z]+)[12345678]/.exec(type); + type = /([a-z]+)[0-9]+/.exec(type); if(type[1] == "indent") break; lineNum--; @@ -5068,7 +5218,7 @@ function Ace2Inner(){ //3-renumber every list item of the same level from the beginning, level 1 //IMPORTANT: never skip a level because there imbrication may be arbitrary var builder = Changeset.builder(rep.lines.totalWidth()); - loc = [0,0]; + var loc = [0,0]; function applyNumberList(line, level) { //init @@ -5079,7 +5229,7 @@ function Ace2Inner(){ while(listType = getLineListType(line)) { //apply new num - listType = /([a-z]+)([12345678])/.exec(listType); + listType = /([a-z]+)([0-9]+)/.exec(listType); curLevel = Number(listType[2]); if(isNaN(curLevel) || listType[0] == "indent") { @@ -5147,7 +5297,7 @@ function Ace2Inner(){ { var t = ''; var level = 0; - var listType = /([a-z]+)([12345678])/.exec(getLineListType(n)); + var listType = /([a-z]+)([0-9]+)/.exec(getLineListType(n)); if (listType) { t = listType[1]; @@ -5274,20 +5424,9 @@ function Ace2Inner(){ { var body = doc.getElementById("innerdocbody"); root = body; // defined as a var in scope outside - if (browser.mozilla) $(root).addClass("mozilla"); + if (browser.firefox) $(root).addClass("mozilla"); if (browser.safari) $(root).addClass("safari"); if (browser.msie) $(root).addClass("msie"); - if (browser.msie) - { - // cache CSS background images - try - { - doc.execCommand("BackgroundImageCache", false, true); - } - catch (e) - { /* throws an error in some IE 6 but not others! */ - } - } setClassPresence(root, "authorColors", true); setClassPresence(root, "doesWrap", doesWrap); diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index e6c7a122..d337da03 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -10,7 +10,8 @@ $(document).ready(function () { resource = baseURL.substring(1) + "socket.io"; //connect - socket = io.connect(url, {resource : resource}).of("/pluginfw/installer"); + var room = url + "pluginfw/installer"; + socket = io.connect(room, {resource : resource}); function search(searchTerm, limit) { if(search.searchTerm != searchTerm) { @@ -25,12 +26,11 @@ $(document).ready(function () { $('#search-progress').show() search.messages.show('fetching') - storeScrollPosition() search.searching = true } search.searching = false; search.offset = 0; - search.limit = 25; + search.limit = 999; search.results = []; search.sortBy = 'name'; search.sortDir = /*DESC?*/true; @@ -42,7 +42,7 @@ $(document).ready(function () { $('.search-results .messages .'+msg+' *').show() }, hide: function(msg) { - //$('.search-results .messages').hide() + $('.search-results .messages').hide() $('.search-results .messages .'+msg+'').hide() $('.search-results .messages .'+msg+' *').hide() } @@ -103,33 +103,16 @@ $(document).ready(function () { }) } - // Infinite scroll - var scrollPosition - function storeScrollPosition() { - scrollPosition = $(window).scrollTop() - } - function restoreScrollPosition() { - setTimeout(function() { - $(window).scrollTop(scrollPosition) - }, 0) - } - - $(window).scroll(checkInfiniteScroll) - function checkInfiniteScroll() { - if(search.end || search.searching) return;// don't keep requesting if there are no more results - setTimeout(function() { - try{ - var top = $('.results>tr:last').offset().top - if($(window).scrollTop()+$(window).height() > top) search(search.searchTerm) - }catch(e){} - }, 1) - } - function updateHandlers() { // Search $("#search-query").unbind('keyup').keyup(function () { search($("#search-query").val()); }); + + // Prevent form submit + $('#search-query').parent().bind('submit', function() { + return false; + }); // update & install $(".do-install, .do-update").unbind('click').click(function (e) { @@ -158,14 +141,14 @@ $(document).ready(function () { // Sort $('.sort.up').unbind('click').click(function() { - search.sortBy = $(this).text().toLowerCase(); + search.sortBy = $(this).attr('data-label').toLowerCase(); search.sortDir = false; search.offset = 0; search(search.searchTerm, search.results.length); search.results = []; }) $('.sort.down, .sort.none').unbind('click').click(function() { - search.sortBy = $(this).text().toLowerCase(); + search.sortBy = $(this).attr('data-label').toLowerCase(); search.sortDir = true; search.offset = 0; search(search.searchTerm, search.results.length); @@ -175,6 +158,7 @@ $(document).ready(function () { socket.on('results:search', function (data) { if(!data.results.length) search.end = true; + if(data.query.offset == 0) search.results = []; search.messages.hide('nothing-found') search.messages.hide('fetching') $("#search-query").removeAttr('disabled') @@ -202,8 +186,6 @@ $(document).ready(function () { } search.messages.hide('fetching') $('#search-progress').hide() - restoreScrollPosition() - checkInfiniteScroll() search.searching = false }); @@ -243,6 +225,9 @@ $(document).ready(function () { socket.on('finished:install', function(data) { if(data.error) { + if(data.code === "EPEERINVALID"){ + alert("This plugin requires that you update Etherpad so it can operate in it's true glory"); + } alert('An error occured while installing '+data.plugin+' \n'+data.error) $('#installed-plugins .'+data.plugin).remove() } diff --git a/src/static/js/admin/settings.js b/src/static/js/admin/settings.js index fb3f2e97..8a4473d6 100644 --- a/src/static/js/admin/settings.js +++ b/src/static/js/admin/settings.js @@ -9,7 +9,8 @@ $(document).ready(function () { resource = baseURL.substring(1) + "socket.io"; //connect - socket = io.connect(url, {resource : resource}).of("/settings"); + var room = url + "settings"; + socket = io.connect(room, {resource : resource}); socket.on('settings', function (settings) { diff --git a/src/static/js/broadcast.js b/src/static/js/broadcast.js index 9ac8ca3d..817155b5 100644 --- a/src/static/js/broadcast.js +++ b/src/static/js/broadcast.js @@ -27,6 +27,7 @@ var Changeset = require('./Changeset'); var linestylefilter = require('./linestylefilter').linestylefilter; var colorutils = require('./colorutils').colorutils; var _ = require('./underscore'); +var hooks = require('./pluginfw/hooks'); // These parameters were global, now they are injected. A reference to the // Timeslider controller would probably be more appropriate. @@ -65,18 +66,6 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro } } - // for IE - if ($.browser.msie) - { - try - { - document.execCommand("BackgroundImageCache", false, true); - } - catch (e) - {} - } - - //var socket; var channelState = "DISCONNECTED"; @@ -534,6 +523,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro var savedRev = obj.savedRev; BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev); } + hooks.callAll('handleClientTimesliderMessage_' + obj.type, {payload: obj}); } else if(obj.type == "CHANGESET_REQ") { diff --git a/src/static/js/broadcast_slider.js b/src/static/js/broadcast_slider.js index 0b0147dd..eff20b52 100644 --- a/src/static/js/broadcast_slider.js +++ b/src/static/js/broadcast_slider.js @@ -29,6 +29,9 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { var BroadcastSlider; + // Hack to ensure timeslider i18n values are in + $("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get("timeslider.toolbar.returnbutton")); + (function() { // wrap this code in its own namespace var sliderLength = 1000; @@ -177,23 +180,26 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) var colorsAnonymous = []; _.each(authors, function(author) { - var authorColor = clientVars.colorPalette[author.colorId] || author.colorId; - if (author.name) - { - if (numNamed !== 0) authorsList.append(', '); - - $('<span />') - .text(author.name || "unnamed") - .css('background-color', authorColor) - .addClass('author') - .appendTo(authorsList); - - numNamed++; - } - else + if(author) { - numAnonymous++; - if(authorColor) colorsAnonymous.push(authorColor); + var authorColor = clientVars.colorPalette[author.colorId] || author.colorId; + if (author.name) + { + if (numNamed !== 0) authorsList.append(', '); + + $('<span />') + .text(author.name || "unnamed") + .css('background-color', authorColor) + .addClass('author') + .appendTo(authorsList); + + numNamed++; + } + else + { + numAnonymous++; + if(authorColor) colorsAnonymous.push(authorColor); + } } }); if (numAnonymous > 0) @@ -284,6 +290,11 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) $(document).keyup(function(e) { + // If focus is on editbar, don't do anything + var target = $(':focus'); + if($(target).parents(".toolbar").length === 1){ + return; + } var code = -1; if (!e) var e = window.event; if (e.keyCode) code = e.keyCode; @@ -324,7 +335,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) } } else if (code == 32) playpause(); - }); $(window).resize(function() @@ -379,16 +389,16 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { var self = this; - $(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)'); + // $(self).css('background-image', 'url(/static/img/crushed_button_depressed.png)'); $(self).mouseup(function(evt2) { - $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); + // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); $(self).unbind('mouseup'); BroadcastSlider.playpause(); }); $(document).mouseup(function(evt2) { - $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); + // $(self).css('background-image', 'url(/static/img/crushed_button_undepressed.png)'); $(document).unbind('mouseup'); }); }); diff --git a/src/static/js/browser.js b/src/static/js/browser.js new file mode 100644 index 00000000..f763963e --- /dev/null +++ b/src/static/js/browser.js @@ -0,0 +1,240 @@ +/*! + * Bowser - a browser detector + * https://github.com/ded/bowser + * MIT License | (c) Dustin Diaz 2014 + */ + +!function (name, definition) { + if (typeof module != 'undefined' && module.exports) module.exports['browser'] = definition() + else if (typeof define == 'function' && define.amd) define(definition) + else this[name] = definition() +}('bowser', function () { + /** + * See useragents.js for examples of navigator.userAgent + */ + + var t = true + + function detect(ua) { + + function getFirstMatch(regex) { + var match = ua.match(regex); + return (match && match.length > 1 && match[1]) || ''; + } + + var iosdevice = getFirstMatch(/(ipod|iphone|ipad)/i).toLowerCase() + , likeAndroid = /like android/i.test(ua) + , android = !likeAndroid && /android/i.test(ua) + , versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i) + , tablet = /tablet/i.test(ua) + , mobile = !tablet && /[^-]mobi/i.test(ua) + , result + + if (/opera|opr/i.test(ua)) { + result = { + name: 'Opera' + , opera: t + , version: versionIdentifier || getFirstMatch(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i) + } + } + else if (/windows phone/i.test(ua)) { + result = { + name: 'Windows Phone' + , windowsphone: t + , msie: t + , version: getFirstMatch(/iemobile\/(\d+(\.\d+)?)/i) + } + } + else if (/msie|trident/i.test(ua)) { + result = { + name: 'Internet Explorer' + , msie: t + , version: getFirstMatch(/(?:msie |rv:)(\d+(\.\d+)?)/i) + } + } + else if (/chrome|crios|crmo/i.test(ua)) { + result = { + name: 'Chrome' + , chrome: t + , version: getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i) + } + } + else if (iosdevice) { + result = { + name : iosdevice == 'iphone' ? 'iPhone' : iosdevice == 'ipad' ? 'iPad' : 'iPod' + } + // WTF: version is not part of user agent in web apps + if (versionIdentifier) { + result.version = versionIdentifier + } + } + else if (/sailfish/i.test(ua)) { + result = { + name: 'Sailfish' + , sailfish: t + , version: getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i) + } + } + else if (/seamonkey\//i.test(ua)) { + result = { + name: 'SeaMonkey' + , seamonkey: t + , version: getFirstMatch(/seamonkey\/(\d+(\.\d+)?)/i) + } + } + else if (/firefox|iceweasel/i.test(ua)) { + result = { + name: 'Firefox' + , firefox: t + , version: getFirstMatch(/(?:firefox|iceweasel)[ \/](\d+(\.\d+)?)/i) + } + if (/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(ua)) { + result.firefoxos = t + } + } + else if (/silk/i.test(ua)) { + result = { + name: 'Amazon Silk' + , silk: t + , version : getFirstMatch(/silk\/(\d+(\.\d+)?)/i) + } + } + else if (android) { + result = { + name: 'Android' + , version: versionIdentifier + } + } + else if (/phantom/i.test(ua)) { + result = { + name: 'PhantomJS' + , phantom: t + , version: getFirstMatch(/phantomjs\/(\d+(\.\d+)?)/i) + } + } + else if (/blackberry|\bbb\d+/i.test(ua) || /rim\stablet/i.test(ua)) { + result = { + name: 'BlackBerry' + , blackberry: t + , version: versionIdentifier || getFirstMatch(/blackberry[\d]+\/(\d+(\.\d+)?)/i) + } + } + else if (/(web|hpw)os/i.test(ua)) { + result = { + name: 'WebOS' + , webos: t + , version: versionIdentifier || getFirstMatch(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i) + }; + /touchpad\//i.test(ua) && (result.touchpad = t) + } + else if (/bada/i.test(ua)) { + result = { + name: 'Bada' + , bada: t + , version: getFirstMatch(/dolfin\/(\d+(\.\d+)?)/i) + }; + } + else if (/tizen/i.test(ua)) { + result = { + name: 'Tizen' + , tizen: t + , version: getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i) || versionIdentifier + }; + } + else if (/safari/i.test(ua)) { + result = { + name: 'Safari' + , safari: t + , version: versionIdentifier + } + } + else result = {} + + // set webkit or gecko flag for browsers based on these engines + if (/(apple)?webkit/i.test(ua)) { + result.name = result.name || "Webkit" + result.webkit = t + if (!result.version && versionIdentifier) { + result.version = versionIdentifier + } + } else if (!result.opera && /gecko\//i.test(ua)) { + result.name = result.name || "Gecko" + result.gecko = t + result.version = result.version || getFirstMatch(/gecko\/(\d+(\.\d+)?)/i) + } + + // set OS flags for platforms that have multiple browsers + if (android || result.silk) { + result.android = t + } else if (iosdevice) { + result[iosdevice] = t + result.ios = t + } + + // OS version extraction + var osVersion = ''; + if (iosdevice) { + osVersion = getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i); + osVersion = osVersion.replace(/[_\s]/g, '.'); + } else if (android) { + osVersion = getFirstMatch(/android[ \/-](\d+(\.\d+)*)/i); + } else if (result.windowsphone) { + osVersion = getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i); + } else if (result.webos) { + osVersion = getFirstMatch(/(?:web|hpw)os\/(\d+(\.\d+)*)/i); + } else if (result.blackberry) { + osVersion = getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i); + } else if (result.bada) { + osVersion = getFirstMatch(/bada\/(\d+(\.\d+)*)/i); + } else if (result.tizen) { + osVersion = getFirstMatch(/tizen[\/\s](\d+(\.\d+)*)/i); + } + if (osVersion) { + result.osversion = osVersion; + } + + // device type extraction + var osMajorVersion = osVersion.split('.')[0]; + if (tablet || iosdevice == 'ipad' || (android && (osMajorVersion == 3 || (osMajorVersion == 4 && !mobile))) || result.silk) { + result.tablet = t + } else if (mobile || iosdevice == 'iphone' || iosdevice == 'ipod' || android || result.blackberry || result.webos || result.bada) { + result.mobile = t + } + + // Graded Browser Support + // http://developer.yahoo.com/yui/articles/gbs + if ((result.msie && result.version >= 10) || + (result.chrome && result.version >= 20) || + (result.firefox && result.version >= 20.0) || + (result.safari && result.version >= 6) || + (result.opera && result.version >= 10.0) || + (result.ios && result.osversion && result.osversion.split(".")[0] >= 6) || + (result.blackberry && result.version >= 10.1) + ) { + result.a = t; + } + else if ((result.msie && result.version < 10) || + (result.chrome && result.version < 20) || + (result.firefox && result.version < 20.0) || + (result.safari && result.version < 6) || + (result.opera && result.version < 10.0) || + (result.ios && result.osversion && result.osversion.split(".")[0] < 6) + ) { + result.c = t + } else result.x = t + + return result + } + + var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '') + + + /* + * Set our detect method to the main bowser object so we can + * reuse it to test other user agents. + * This is needed to implement future tests. + */ + bowser._detect = detect; + + return bowser +}); diff --git a/src/static/js/changesettracker.js b/src/static/js/changesettracker.js index f3efc407..fe362c4b 100644 --- a/src/static/js/changesettracker.js +++ b/src/static/js/changesettracker.js @@ -179,7 +179,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) // We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes if(apool.numToAttrib){ for (var attr in apool.numToAttrib){ - if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) authorAttr = Number(attr).toString(36) + if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36) } // Replace all added 'author' attribs with the value of the current user diff --git a/src/static/js/chat.js b/src/static/js/chat.js index 65fc8dd9..42cd50f4 100644 --- a/src/static/js/chat.js +++ b/src/static/js/chat.js @@ -18,10 +18,12 @@ var padutils = require('./pad_utils').padutils; var padcookie = require('./pad_cookie').padcookie; var Tinycon = require('tinycon/tinycon'); var hooks = require('./pluginfw/hooks'); +var padeditor = require('./pad_editor').padeditor; var chat = (function() { var isStuck = false; + var userAndChat = false; var gotInitialMessages = false; var historyPointer = 0; var chatMentions = 0; @@ -35,13 +37,21 @@ var chat = (function() chatMentions = 0; Tinycon.setBubble(0); }, + focus: function () + { + // I'm not sure why we need a setTimeout here but without it we don't get focus... + // Animation maybe? + setTimeout(function(){ + $("#chatinput").focus(); + },100); + }, stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen { chat.show(); if(!isStuck || fromInitialCall) { // Stick it to padcookie.setPref("chatAlwaysVisible", true); $('#chatbox').addClass("stickyChat"); - $('#chattext').css({"top":"0px"}); + $('#titlesticky').hide(); $('#editorcontainer').css({"right":"192px"}); $('.stickyChat').css("top",$('#editorcontainer').offset().top+"px"); isStuck = true; @@ -49,24 +59,54 @@ var chat = (function() padcookie.setPref("chatAlwaysVisible", false); $('.stickyChat').css("top", "auto"); $('#chatbox').removeClass("stickyChat"); - $('#chattext').css({"top":"25px"}); + $('#titlesticky').show(); $('#editorcontainer').css({"right":"0px"}); isStuck = false; } }, + chatAndUsers: function(fromInitialCall) + { + var toEnable = $('#options-chatandusers').is(":checked"); + if(toEnable || !userAndChat || fromInitialCall){ + padcookie.setPref("chatAndUsers", true); + chat.stickToScreen(true); + $('#options-stickychat').prop('checked', true) + $('#options-chatandusers').prop('checked', true) + $('#options-stickychat').prop("disabled", "disabled"); + $('#users').addClass("chatAndUsers"); + $("#chatbox").addClass("chatAndUsersChat"); + // redraw + userAndChat = true; + padeditbar.redrawHeight() + }else{ + padcookie.setPref("chatAndUsers", false); + $('#options-stickychat').prop("disabled", false); + $('#users').removeClass("chatAndUsers"); + $("#chatbox").removeClass("chatAndUsersChat"); + } + }, hide: function () { - $("#chatcounter").text("0"); - $("#chaticon").show(); - $("#chatbox").hide(); - $.gritter.removeAll(); - $("#gritter-notice-wrapper").show(); + // decide on hide logic based on chat window being maximized or not + if ($('#options-stickychat').prop('checked')) { + chat.stickToScreen(); + $('#options-stickychat').prop('checked', false); + } + else { + $("#chatcounter").text("0"); + $("#chaticon").show(); + $("#chatbox").hide(); + $.gritter.removeAll(); + $("#gritter-notice-wrapper").show(); + } }, scrollDown: function() { if($('#chatbox').css("display") != "none"){ if(!self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < $('#chattext').height()) { - $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, "slow"); + // if we use a slow animate here we can have a race condition when a users focus can not be moved away + // from the last message recieved. + $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false }); self.lastMessage = $('#chattext > p').eq(-1); } } @@ -174,8 +214,28 @@ var chat = (function() init: function(pad) { this._pad = pad; - $("#chatinput").keypress(function(evt) + $("#chatinput").keyup(function(evt) { + // If the event is Alt C or Escape & we're already in the chat menu + // Send the users focus back to the pad + if((evt.altKey == true && evt.which === 67) || evt.which === 27){ + // If we're in chat already.. + $(':focus').blur(); // required to do not try to remove! + padeditor.ace.focus(); // Sends focus back to pad + } + }); + + $('body:not(#chatinput)').on("keydown", function(evt){ + if (evt.altKey && evt.which == 67){ + // Alt c focuses on the Chat window + $(this).blur(); + parent.parent.chat.show(); + parent.parent.chat.focus(); + evt.preventDefault(); + } + }); + + $("#chatinput").keypress(function(evt){ //if the user typed enter, fire the send if(evt.which == 13 || evt.which == 10) { diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index 146ec51b..e5c0ec8b 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -81,8 +81,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) onServerMessage: function() {} }; - - if ($.browser.mozilla) + if (browser.firefox) { // Prevent "escape" from taking effect and canceling a comet connection; // doesn't work if focus is on an iframe. @@ -152,6 +151,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) // apply msgQueue changeset. if (msgQueue.length != 0) { + var msg; while (msg = msgQueue.shift()) { var newRev = msg.newRev; rev=newRev; @@ -274,7 +274,7 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) // When inInternationalComposition, msg pushed msgQueue. if (msgQueue.length > 0 || editor.getInInternationalComposition()) { - if (msgQueue.length > 0) oldRev = msgQueue[msgQueue.length - 1].newRev; + if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev; else oldRev = rev; if (newRev != (oldRev + 1)) diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index 5e393670..857e171f 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -23,7 +23,7 @@ * limitations under the License. */ -var _MAX_LIST_LEVEL = 8; +var _MAX_LIST_LEVEL = 16; var UNorm = require('unorm'); var Changeset = require('./Changeset'); @@ -32,12 +32,13 @@ var _ = require('./underscore'); function sanitizeUnicode(s) { - return UNorm.nfc(s).replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?'); + return UNorm.nfc(s); } -function makeContentCollector(collectStyles, browser, apool, domInterface, className2Author) +function makeContentCollector(collectStyles, abrowser, apool, domInterface, className2Author) { - browser = browser || {}; + abrowser = abrowser || {}; + // I don't like the above. var dom = domInterface || { isNodeText: function(n) @@ -54,10 +55,14 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class }, nodeNumChildren: function(n) { + if(n.childNodes == null) return 0; return n.childNodes.length; }, nodeChild: function(n, i) { + if(n.childNodes.item == null){ + return n.childNodes[i]; + } return n.childNodes.item(i); }, nodeProp: function(n, p) @@ -66,6 +71,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class }, nodeAttr: function(n, a) { + if(n.getAttribute == null) return null; return n.getAttribute(a); }, optNodeInnerHTML: function(n) @@ -81,6 +87,10 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class "li": 1 }; + _.each(hooks.callAll('ccRegisterBlockElements'), function(element){ + _blockElems[element] = 1; + }); + function isBlockElement(n) { return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()]; @@ -287,7 +297,23 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class { if (state.attribs[a]) { - lst.push([a, 'true']); + // The following splitting of the attribute name is a workaround + // to enable the content collector to store key-value attributes + // see https://github.com/ether/etherpad-lite/issues/2567 for more information + // in long term the contentcollector should be refactored to get rid of this workaround + var ATTRIBUTE_SPLIT_STRING = "::"; + + // see if attributeString is splittable + var attributeSplits = a.split(ATTRIBUTE_SPLIT_STRING); + if (attributeSplits.length > 1) { + // the attribute name follows the convention key::value + // so save it as a key value attribute + lst.push([attributeSplits[0], attributeSplits[1]]); + } else { + // the "normal" case, the attribute is just a switch + // so set it true + lst.push([a, 'true']); + } } } if (state.authorLevel > 0) @@ -314,7 +340,6 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class return [key, value]; }) ); - lines.appendText('*', Changeset.makeAttribsString('+', attributes , apool)); } cc.startNewLine = function(state) @@ -452,8 +477,23 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class else { var tname = (dom.nodeTagName(node) || "").toLowerCase(); + + if (tname == "img"){ + var collectContentImage = hooks.callAll('collectContentImage', { + cc: cc, + state: state, + tname: tname, + styl: styl, + cls: cls, + node: node + }); + }else{ + // THIS SEEMS VERY HACKY! -- Please submit a better fix! + delete state.lineAttributes.img + } + if (tname == "br") - { + { this.breakLine = true; var tvalue = dom.nodeAttr(node, 'value'); var induceLineBreak = hooks.callAll('collectContentLineBreak', { @@ -477,9 +517,8 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class { var styl = dom.nodeAttr(node, "style"); var cls = dom.nodeProp(node, "className"); - var isPre = (tname == "pre"); - if ((!isPre) && browser.safari) + if ((!isPre) && abrowser.safari) { isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl)); } @@ -513,14 +552,44 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class } if (tname == "ul" || tname == "ol") { - var type; - var rr = cls && /(?:^| )list-([a-z]+[12345678])\b/.exec(cls); - type = rr && rr[1] || (tname == "ul" ? "bullet" : "number") + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1)); + if(node.attribs){ + var type = node.attribs.class; + }else{ + var type = null; + } + var rr = cls && /(?:^| )list-([a-z]+[0-9]+)\b/.exec(cls); + // lists do not need to have a type, so before we make a wrong guess, check if we find a better hint within the node's children + if(!rr && !type){ + for (var i in node.children){ + if(node.children[i] && node.children[i].name=='ul'){ + type = node.children[i].attribs.class + if(type){ + break + } + } + } + } + if(rr && rr[1]){ + type = rr[1] + } else { + if(tname == "ul"){ + if((type && type.match("indent")) || (node.attribs && node.attribs.class && node.attribs.class.match("indent"))){ + type = "indent" + } else { + type = "bullet" + } + } else { + type = "number" + } + type = type + String(Math.min(_MAX_LIST_LEVEL, (state.listNesting || 0) + 1)); + } oldListTypeOrNull = (_enterList(state, type) || 'none'); } else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/)) { - oldListTypeOrNull = (_enterList(state, type) || 'none'); + // This has undesirable behavior in Chrome but is right in other browsers. + // See https://github.com/ether/etherpad-lite/issues/2412 for reasoning + if(!abrowser.chrome) oldListTypeOrNull = (_enterList(state, type) || 'none'); } if (className2Author && cls) { @@ -577,7 +646,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class } } } - if (!browser.msie) + if (!abrowser.msie) { _reachBlockPoint(node, 1, state); } @@ -592,13 +661,11 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class _ensureColumnZero(state); } } - - if (browser.msie) + if (abrowser.msie) { // in IE, a point immediately after a DIV appears on the next line _reachBlockPoint(node, 1, state); } - state.localAttribs = localAttribs; }; // can pass a falsy value for end of doc @@ -663,7 +730,7 @@ function makeContentCollector(collectStyles, browser, apool, domInterface, class { //var semiloc = oldString.lastIndexOf(';', lineLimit-1); //var lengthToTake = (semiloc >= 0 ? (semiloc+1) : lineLimit); - lengthToTake = lineLimit; + var lengthToTake = lineLimit; newStrings.push(oldString.substring(0, lengthToTake)); oldString = oldString.substring(lengthToTake); newAttribStrings.push(Changeset.subattribution(oldAttribString, 0, lengthToTake)); diff --git a/src/static/js/domline.js b/src/static/js/domline.js index b1927b16..03f1b9c8 100644 --- a/src/static/js/domline.js +++ b/src/static/js/domline.js @@ -65,7 +65,6 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) lineMarker: 0 }; - var browser = (optBrowser || {}); var document = optDocument; if (document) @@ -93,8 +92,10 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) var perTextNodeProcess = (doesWrap ? _.identity : processSpaces); var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity); var lineClass = 'ace-line'; + result.appendSpan = function(txt, cls) { + var processedMarker = false; // Handle lineAttributeMarker, if present if (cls.indexOf(lineAttributeMarker) >= 0) @@ -225,7 +226,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) { newHTML += ' '; } - else if (!browser.msie) + else if (!optBrowser.msie) { newHTML += '<br/>'; } diff --git a/src/static/js/farbtastic.js b/src/static/js/farbtastic.js index 114c4d72..8a61627d 100644 --- a/src/static/js/farbtastic.js +++ b/src/static/js/farbtastic.js @@ -170,7 +170,7 @@ $._farbtastic = function (container, options) { // New color color2 = fb.pack(fb.HSLToRGB([d2, 1, 0.5])); if (i > 0) { - if ($.browser.msie) { + if (browser.msie) { // IE's gradient calculations mess up the colors. Correct along the diagonals. var corr = (1 + Math.min(Math.abs(Math.tan(angle1)), Math.abs(Math.tan(Math.PI / 2 - angle1)))) / n; color1 = fb.pack(fb.HSLToRGB([d1 - 0.15 * corr, 1, 0.5])); @@ -254,7 +254,7 @@ $._farbtastic = function (container, options) { fb.ctxMask.drawImage(buffer, 0, 0, sz + 1, sz + 1, -sq, -sq, sq * 2, sq * 2); } // Method #2: drawing commands (old Canvas). - else if (!$.browser.msie) { + else if (!browser.msie) { // Render directly at half-resolution var sz = Math.floor(size / 2); calculateMask(sz, sz, function (x, y, c, a) { diff --git a/src/static/js/gritter.js b/src/static/js/gritter.js index 9778707e..7f8c5b6e 100644 --- a/src/static/js/gritter.js +++ b/src/static/js/gritter.js @@ -78,7 +78,7 @@ _tpl_close: '<div class="gritter-close"></div>', _tpl_title: '<span class="gritter-title">[[title]]</span>', _tpl_item: '<div id="gritter-item-[[number]]" class="gritter-item-wrapper [[item_class]]" style="display:none"><div class="gritter-top"></div><div class="gritter-item">[[close]][[image]]<div class="[[class_name]]">[[title]]<p>[[text]]</p></div><div style="clear:both"></div></div><div class="gritter-bottom"></div></div>', - _tpl_wrap: '<div id="gritter-notice-wrapper"></div>', + _tpl_wrap: '<div id="gritter-notice-wrapper" aria-live="polite" aria-atomic="false" aria-relevant="additions" role="log"></div>', /** * Add a gritter notification to the screen diff --git a/src/static/js/html10n.js b/src/static/js/html10n.js index 49a0a80d..19cf9de1 100644 --- a/src/static/js/html10n.js +++ b/src/static/js/html10n.js @@ -23,28 +23,16 @@ window.html10n = (function(window, document, undefined) { // fix console - var console = window.console - function interceptConsole(method){ - if (!console) return function() {} - - var original = console[method] - - // do sneaky stuff - if (original.bind){ - // Do this for normal browsers - return original.bind(console) - }else{ - return function() { - // Do this for IE - var message = Array.prototype.slice.apply(arguments).join(' ') - original(message) - } + (function() { + var noop = function() {}; + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + var console = (window.console = window.console || {}); + for (var i = 0; i < names.length; ++i) { + if (!console[names[i]]) { + console[names[i]] = noop; } - } - var consoleLog = interceptConsole('log') - , consoleWarn = interceptConsole('warn') - , consoleError = interceptConsole('warn') - + } + }()); // fix Array#forEach in IE // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach @@ -148,7 +136,7 @@ window.html10n = (function(window, document, undefined) { for (var i=0, n=this.resources.length; i < n; i++) { this.fetch(this.resources[i], lang, function(e) { reqs++; - if(e) consoleWarn(e) + if(e) console.warn(e) if (reqs < n) return;// Call back once all reqs are completed cb && cb() @@ -647,7 +635,7 @@ window.html10n = (function(window, document, undefined) { // return a function that gives the plural form name for a given integer var index = locales2rules[lang.replace(/-.*$/, '')]; if (!(index in pluralRules)) { - consoleWarn('plural form unknown for [' + lang + ']'); + console.warn('plural form unknown for [' + lang + ']'); return function() { return 'other'; }; } return pluralRules[index]; @@ -727,7 +715,7 @@ window.html10n = (function(window, document, undefined) { var i = 0 , n = list.length iterator(list[i], i, function each(err) { - if(err) consoleLog(err) + if(err) console.error(err) i++ if (i < n) return iterator(list[i],i, each); cb() @@ -750,8 +738,8 @@ window.html10n = (function(window, document, undefined) { html10n.get = function(id, args) { var translations = html10n.translations - if(!translations) return consoleWarn('No translations available (yet)') - if(!translations[id]) return consoleWarn('Could not find string '+id) + if(!translations) return console.warn('No translations available (yet)') + if(!translations[id]) return console.warn('Could not find string '+id) // apply macros var str = translations[id] @@ -781,7 +769,7 @@ window.html10n = (function(window, document, undefined) { } else if (arg in translations) { sub = translations[arg] } else { - consoleWarn('Could not find argument {{' + arg + '}}') + console.warn('Could not find argument {{' + arg + '}}') return str } @@ -840,7 +828,7 @@ window.html10n = (function(window, document, undefined) { str.id = node.getAttribute('data-l10n-id') if (!str.id) return - if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id) + if(!translations[str.id]) return console.warn('Couldn\'t find translation key '+str.id) // get args if(window.JSON) { @@ -849,7 +837,7 @@ window.html10n = (function(window, document, undefined) { try{ str.args = eval(node.getAttribute('data-l10n-args')) }catch(e) { - consoleWarn('Couldn\'t parse args for '+str.id) + console.warn('Couldn\'t parse args for '+str.id) } } @@ -887,7 +875,7 @@ window.html10n = (function(window, document, undefined) { } } if (!found) { - consoleWarn('Unexpected error: could not translate element content for key '+str.id, node) + console.warn('Unexpected error: could not translate element content for key '+str.id, node) } } } diff --git a/src/static/js/jquery_browser.js b/src/static/js/jquery_browser.js deleted file mode 100644 index 0d61e0dd..00000000 --- a/src/static/js/jquery_browser.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - Copied from jQuery 1.8, the last jquery version with browser recognition support -*/ - -(function(){ - // Use of jQuery.browser is frowned upon. - // More details: http://api.jquery.com/jQuery.browser - // jQuery.uaMatch maintained for back-compat - var uaMatch = function( ua ) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || - /(webkit)[ \/]([\w.]+)/.exec( ua ) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || - /(msie) ([\w.]+)/.exec( ua ) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - - var userAgent = navigator.userAgent; - var matched = uaMatch(userAgent); - var browser = {}; - - if ( matched.browser ) { - browser[ matched.browser ] = true; - browser.version = matched.version; - } - - // Chrome is Webkit, but Webkit is also Safari. - if ( browser.chrome ) { - browser.webkit = true; - } else if ( browser.webkit ) { - browser.safari = true; - } - - //custom extensions, the original jquery didn't have these - browser.windows = /windows/i.test(userAgent); - browser.mobile = /mobile/i.test(userAgent) || /android/i.test(userAgent); - - if(typeof exports !== 'undefined'){ - exports.browser = browser; - } else{ - $.browser = browser; - } -})();
\ No newline at end of file diff --git a/src/static/js/linestylefilter.js b/src/static/js/linestylefilter.js index cb1ee1d5..17ab993b 100644 --- a/src/static/js/linestylefilter.js +++ b/src/static/js/linestylefilter.js @@ -34,7 +34,6 @@ var linestylefilter = {}; var _ = require('./underscore'); var AttributeManager = require('./AttributeManager'); - linestylefilter.ATTRIB_CLASSES = { 'bold': 'tag:b', 'italic': 'tag:i', @@ -59,6 +58,13 @@ linestylefilter.getAuthorClassName = function(author) linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool) { + // Plugin Hook to add more Attrib Classes + hooks.aCallAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES, function(err, ATTRIB_CLASSES){ + if(ATTRIB_CLASSES.length >= 1){ + linestylefilter.ATTRIB_CLASSES = ATTRIB_CLASSES[0]; + } + }); + if (lineLength == 0) return textAndClassFunc; var nextAfterAuthorColors = textAndClassFunc; @@ -312,20 +318,20 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) return spanHandler; }; -linestylefilter.getFilterStack = function(lineText, textAndClassFunc, browser) +linestylefilter.getFilterStack = function(lineText, textAndClassFunc, abrowser) { var func = linestylefilter.getURLFilter(lineText, textAndClassFunc); var hookFilters = hooks.callAll("aceGetFilterStack", { linestylefilter: linestylefilter, - browser: browser + browser: abrowser }); _.map(hookFilters ,function(hookFilter) { func = hookFilter(lineText, func); }); - if (browser !== undefined && browser.msie) + if (abrowser !== undefined && abrowser.msie) { // IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com. // We then normalize it back to text with no angle brackets. It's weird. So always diff --git a/src/static/js/pad.js b/src/static/js/pad.js index 73fcd3d6..f1de80f0 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -43,7 +43,6 @@ var padsavedrevs = require('./pad_savedrevs'); var paduserlist = require('./pad_userlist').paduserlist; var padutils = require('./pad_utils').padutils; var colorutils = require('./colorutils').colorutils; - var createCookie = require('./pad_utils').createCookie; var readCookie = require('./pad_utils').readCookie; var randomString = require('./pad_utils').randomString; @@ -51,6 +50,8 @@ var gritter = require('./gritter').gritter; var hooks = require('./pluginfw/hooks'); +var receivedClientVars = false; + function createCookie(name, value, days, path){ /* Warning Internet Explorer doesn't use this it uses the one from pad_utils.js */ if (days) { @@ -67,7 +68,7 @@ function createCookie(name, value, days, path){ /* Warning Internet Explorer doe } //Check if the browser is IE and if so make sure the full path is set in the cookie - if(navigator.appName=='Microsoft Internet Explorer'){ + if((navigator.appName == 'Microsoft Internet Explorer') || ((navigator.appName == 'Netscape') && (new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null))){ document.cookie = name + "=" + value + expires + "; path="+document.location; } else{ @@ -109,7 +110,7 @@ function randomString() // callback: the function to call when all above succeeds, `val` is the value supplied by the user var getParameters = [ { name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } }, - { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').hide(); $('#editorcontainer').css({"top":"0px"}); } }, + { name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').addClass('hideControlsEditbar'); $('#editorcontainer').addClass('hideControlsEditor'); } }, { name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } }, { name: "showLineNumbers", checkVal: "false", callback: function(val) { settings.LineNumbersDisabled = true; } }, { name: "useMonospaceFont", checkVal: "true", callback: function(val) { settings.useMonospaceFontGlobal = true; } }, @@ -119,6 +120,7 @@ var getParameters = [ { name: "userColor", checkVal: null, callback: function(val) { settings.globalUserColor = decodeURIComponent(val); } }, { name: "rtl", checkVal: "true", callback: function(val) { settings.rtlIsTrue = true } }, { name: "alwaysShowChat", checkVal: "true", callback: function(val) { chat.stickToScreen(); } }, + { name: "chatAndUsers", checkVal: "true", callback: function(val) { chat.chatAndUsers(); } }, { name: "lang", checkVal: null, callback: function(val) { window.html10n.localize([val, 'en']); } } ]; @@ -160,6 +162,49 @@ function savePassword() return false; } +function sendClientReady(isReconnect, messageType) +{ + messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; + var padId = document.location.pathname.substring(document.location.pathname.lastIndexOf("/") + 1); + padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces + + if(!isReconnect) + { + var titleArray = document.title.split('|'); + var title = titleArray[titleArray.length - 1]; + document.title = padId.replace(/_+/g, ' ') + " | " + title; + } + + var token = readCookie("token"); + if (token == null) + { + token = "t." + randomString(); + createCookie("token", token, 60); + } + + var sessionID = decodeURIComponent(readCookie("sessionID")); + var password = readCookie("password"); + + var msg = { + "component": "pad", + "type": messageType, + "padId": padId, + "sessionID": sessionID, + "password": password, + "token": token, + "protocolVersion": 2 + }; + + //this is a reconnect, lets tell the server our revisionnumber + if(isReconnect == true) + { + msg.client_rev=pad.collabClient.getCurrentRevisionNumber(); + msg.reconnect=true; + } + + socket.json.send(msg); +} + function handshake() { var loc = document.location; @@ -171,49 +216,13 @@ function handshake() var resource = exports.baseURL.substring(1) + "socket.io"; //connect socket = pad.socket = io.connect(url, { - resource: resource, + // Allow deployers to host Etherpad on a non-root path + 'path': exports.baseURL + "socket.io", + 'resource': resource, 'max reconnection attempts': 3, 'sync disconnect on unload' : false }); - function sendClientReady(isReconnect) - { - var padId = document.location.pathname.substring(document.location.pathname.lastIndexOf("/") + 1); - padId = decodeURIComponent(padId); // unescape neccesary due to Safari and Opera interpretation of spaces - - if(!isReconnect) - document.title = padId.replace(/_+/g, ' ') + " | " + document.title; - - var token = readCookie("token"); - if (token == null) - { - token = "t." + randomString(); - createCookie("token", token, 60); - } - - var sessionID = decodeURIComponent(readCookie("sessionID")); - var password = readCookie("password"); - - var msg = { - "component": "pad", - "type": "CLIENT_READY", - "padId": padId, - "sessionID": sessionID, - "password": password, - "token": token, - "protocolVersion": 2 - }; - - //this is a reconnect, lets tell the server our revisionnumber - if(isReconnect == true) - { - msg.client_rev=pad.collabClient.getCurrentRevisionNumber(); - msg.reconnect=true; - } - - socket.json.send(msg); - }; - var disconnectTimeout; socket.once('connect', function () { @@ -228,7 +237,7 @@ function handshake() } pad.collabClient.setChannelState("CONNECTED"); - sendClientReady(true); + pad.sendClientReady(true); }); socket.on('disconnect', function (reason) { @@ -246,7 +255,6 @@ function handshake() } }); - var receivedClientVars = false; var initalized = false; socket.on('message', function(obj) @@ -286,7 +294,7 @@ function handshake() } //if we haven't recieved the clientVars yet, then this message should it be - else if (!receivedClientVars) + else if (!receivedClientVars && obj.type == "CLIENT_VARS") { //log the message if (window.console) console.log(obj); @@ -386,7 +394,6 @@ var pad = { diagnosticInfo: {}, initTime: 0, clientTimeOffset: null, - preloadedImages: false, padOptions: {}, // these don't require init; clientVars should all go through here @@ -426,6 +433,34 @@ var pad = { { return pad.myUserInfo.name; }, + userList: function() + { + return paduserlist.users(); + }, + sendClientReady: function(isReconnect, messageType) + { + messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY'; + sendClientReady(isReconnect, messageType); + }, + switchToPad: function(padId) + { + var options = document.location.href.split('?')[1]; + var newHref = "/p/" + padId; + if (options != null) + newHref = newHref + '?' + options; + + if(window.history && window.history.pushState) + { + $('#chattext p').remove(); //clear the chat messages + window.history.pushState("", "", newHref); + receivedClientVars = false; + sendClientReady(false, 'SWITCH_TO_PAD'); + } + else // fallback + { + window.location.href = newHref; + } + }, sendClientMessage: function(msg) { pad.collabClient.sendClientMessage(msg); @@ -442,6 +477,16 @@ var pad = { if (typeof customStart == "function") customStart(); getParams(); handshake(); + + // To use etherpad you have to allow cookies. + // This will check if the creation of a test-cookie has success. + // Otherwise it shows up a message to the user. + createCookie("test", "test"); + if (!readCookie("test")) + { + $('#loading').hide(); + $('#noCookie').show(); + } }); }, _afterHandshake: function() @@ -454,13 +499,13 @@ var pad = { pad.initTime = +(new Date()); pad.padOptions = clientVars.initialOptions; - if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) + if ((!browser.msie) && (!(browser.firefox && browser.version.indexOf("1.8.") == 0))) { document.domain = document.domain; // for comet } // for IE - if ($.browser.msie) + if (browser.msie) { try { @@ -522,9 +567,31 @@ var pad = { chat.stickToScreen(true); // stick it to the screen $('#options-stickychat').prop("checked", true); // set the checkbox to on } + if(padcookie.getPref("chatAndUsers")){ // if we have a cookie for always showing chat then show it + chat.chatAndUsers(true); // stick it to the screen + $('#options-chatandusers').prop("checked", true); // set the checkbox to on + } if(padcookie.getPref("showAuthorshipColors") == false){ pad.changeViewOption('showAuthorColors', false); } + if(padcookie.getPref("showLineNumbers") == false){ + pad.changeViewOption('showLineNumbers', false); + } + if(padcookie.getPref("rtlIsTrue") == true){ + pad.changeViewOption('rtlIsTrue', true); + } + + var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', + 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', + 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', + 'useSerifFont']; + + $.each(fonts, function(i, font){ + if(padcookie.getPref(font) == true){ + pad.changeViewOption(font, true); + } + }) + hooks.aCallAll("postAceInit", {ace: padeditor.ace, pad: pad}); } }, @@ -575,6 +642,7 @@ var pad = { for (var k in opts.view) { pad.padOptions.view[k] = opts.view[k]; + padcookie.setPref(k, opts.view[k]); } padeditor.setViewOptions(pad.padOptions.view); } @@ -728,23 +796,9 @@ var pad = { }, handleIsFullyConnected: function(isConnected, isInitialConnect) { - // load all images referenced from CSS, one at a time, - // starting one second after connection is first established. - if (isConnected && !pad.preloadedImages) - { - window.setTimeout(function() - { - if (!pad.preloadedImages) - { - pad.preloadImages(); - pad.preloadedImages = true; - } - }, 1000); - } - pad.determineChatVisibility(isConnected && !isInitialConnect); + pad.determineChatAndUsersVisibility(isConnected && !isInitialConnect); pad.determineAuthorshipColorsVisibility(); - }, determineChatVisibility: function(asNowConnectedFeedback){ var chatVisCookie = padcookie.getPref('chatAlwaysVisible'); @@ -756,6 +810,16 @@ var pad = { $('#options-stickychat').prop("checked", false); // set the checkbox for off } }, + determineChatAndUsersVisibility: function(asNowConnectedFeedback){ + var chatAUVisCookie = padcookie.getPref('chatAndUsersVisible'); + if(chatAUVisCookie){ // if the cookie is set for chat always visible + chat.chatAndUsers(true); // stick it to the screen + $('#options-chatandusers').prop("checked", true); // set the checkbox to on + } + else{ + $('#options-chatandusers').prop("checked", false); // set the checkbox for off + } + }, determineAuthorshipColorsVisibility: function(){ var authColCookie = padcookie.getPref('showAuthorshipColors'); if (authColCookie){ @@ -837,34 +901,6 @@ var pad = { { pad.collabClient.addHistoricalAuthors(data); } - }, - preloadImages: function() - { - var images = ["../static/img/connectingbar.gif"]; - - function loadNextImage() - { - if (images.length == 0) - { - return; - } - var img = new Image(); - img.src = images.shift(); - if (img.complete) - { - scheduleLoadNextImage(); - } - else - { - $(img).bind('error load onreadystatechange', scheduleLoadNextImage); - } - } - - function scheduleLoadNextImage() - { - window.setTimeout(loadNextImage, 0); - } - scheduleLoadNextImage(); } }; @@ -937,4 +973,3 @@ exports.handshake = handshake; exports.pad = pad; exports.init = init; exports.alertBar = alertBar; - diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index 6352b129..e418969e 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -63,6 +63,7 @@ ToolbarItem.prototype.bind = function (callback) { if (self.isButton()) { self.$el.click(function (event) { + $(':focus').blur(); callback(self.getCommand(), self); event.preventDefault(); }); @@ -140,15 +141,29 @@ var padeditbar = (function() init: function() { var self = this; self.dropdowns = []; - + // Listen for resize events (sucks but needed as iFrame ace_inner has to be position absolute + // A CSS fix for this would be nice but I'm not sure how we'd do it. + $(window).resize(function(){ + self.redrawHeight(); + }); + $("#editbar .editbarbutton").attr("unselectable", "on"); // for IE $("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar"); $("#editbar [data-key]").each(function () { + $(this).unbind("click"); (new ToolbarItem($(this))).bind(function (command, item) { self.triggerCommand(command, item); }); }); + $('body:not(#editorcontainerbox)').on("keydown", function(evt){ + bodyKeyEvent(evt); + }); + + $('#editbar').show(); + + this.redrawHeight(); + registerDefaultCommands(self); hooks.callAll("postToolbarInit", { @@ -170,6 +185,33 @@ var padeditbar = (function() this.commands[cmd] = callback; return this; }, + redrawHeight: function(){ + var editbarHeight = $('.menu_left').height() + 1 + "px"; + var containerTop = $('.menu_left').height() + 6 + "px"; + $('#editbar').css("height", editbarHeight); + + $('#editorcontainer').css("top", containerTop); + + // make sure pop ups are in the right place + if($('#editorcontainer').offset()){ + $('.popup').css("top", $('#editorcontainer').offset().top + "px"); + } + + // If sticky chat is enabled.. + if($('#options-stickychat').is(":checked")){ + if($('#editorcontainer').offset()){ + $('#chatbox').css("top", $('#editorcontainer').offset().top + "px"); + } + }; + + // If chat and Users is enabled.. + if($('#options-chatandusers').is(":checked")){ + if($('#editorcontainer').offset()){ + $('#users').css("top", $('#editorcontainer').offset().top + "px"); + } + } + + }, registerDropdownCommand: function (cmd, dropdown) { dropdown = dropdown || cmd; self.dropdowns.push(dropdown) @@ -263,6 +305,72 @@ var padeditbar = (function() } }; + var editbarPosition = 0; + + function bodyKeyEvent(evt){ + + // If the event is Alt F9 or Escape & we're already in the editbar menu + // Send the users focus back to the pad + if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){ + if($(':focus').parents(".toolbar").length === 1){ + // If we're in the editbar already.. + // Close any dropdowns we have open.. + padeditbar.toggleDropDown("none"); + // Check we're on a pad and not on the timeslider + // Or some other window I haven't thought about! + if(typeof pad === 'undefined'){ + // Timeslider probably.. + // Shift focus away from any drop downs + $(':focus').blur(); // required to do not try to remove! + $('#padmain').focus(); // Focus back onto the pad + }else{ + // Shift focus away from any drop downs + $(':focus').blur(); // required to do not try to remove! + padeditor.ace.focus(); // Sends focus back to pad + // The above focus doesn't always work in FF, you have to hit enter afterwards + evt.preventDefault(); + } + }else{ + // Focus on the editbar :) + var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); + $(this).blur(); + firstEditbarElement.focus(); + evt.preventDefault(); + } + } + // Are we in the toolbar?? + if($(':focus').parents(".toolbar").length === 1){ + // On arrow keys go to next/previous button item in editbar + if(evt.keyCode !== 39 && evt.keyCode !== 37) return; + + // Get all the focusable items in the editbar + var focusItems = $('#editbar').find('button, select'); + + // On left arrow move to next button in editbar + if(evt.keyCode === 37){ + // If a dropdown is visible or we're in an input don't move to the next button + if($('.popup').is(":visible") || evt.target.localName === "input") return; + + editbarPosition--; + // Allow focus to shift back to end of row and start of row + if(editbarPosition === -1) editbarPosition = focusItems.length -1; + $(focusItems[editbarPosition]).focus() + } + + // On right arrow move to next button in editbar + if(evt.keyCode === 39){ + // If a dropdown is visible or we're in an input don't move to the next button + if($('.popup').is(":visible") || evt.target.localName === "input") return; + + editbarPosition++; + // Allow focus to shift back to end of row and start of row + if(editbarPosition >= focusItems.length) editbarPosition = 0; + $(focusItems[editbarPosition]).focus(); + } + } + + } + function aceAttributeCommand(cmd, ace) { ace.ace_toggleAttributeOnSelection(cmd); } @@ -274,10 +382,36 @@ var padeditbar = (function() toolbar.registerDropdownCommand("import_export"); toolbar.registerDropdownCommand("embed"); + toolbar.registerCommand("settings", function () { + toolbar.toggleDropDown("settings", function(){ + $('#options-stickychat').focus(); + }); + }); + + toolbar.registerCommand("import_export", function () { + toolbar.toggleDropDown("import_export", function(){ + // If Import file input exists then focus on it.. + if($('#importfileinput').length !== 0){ + setTimeout(function(){ + $('#importfileinput').focus(); + }, 100); + }else{ + $('.exportlink').first().focus(); + } + }); + }); + + toolbar.registerCommand("showusers", function () { + toolbar.toggleDropDown("users", function(){ + $('#myusernameedit').focus(); + }); + }); + toolbar.registerCommand("embed", function () { toolbar.setEmbedLinks(); - $('#linkinput').focus().select(); - toolbar.toggleDropDown("embed"); + toolbar.toggleDropDown("embed", function(){ + $('#linkinput').focus().select(); + }); }); toolbar.registerCommand("savedRevision", function () { diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index b73409ff..b1ea09f7 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -28,6 +28,13 @@ var padeditor = (function() var Ace2Editor = undefined; var pad = undefined; var settings = undefined; + + // Array of available fonts + var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont', + 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont', + 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont', + 'useSerifFont']; + var self = { ace: null, // this is accessed directly from other files @@ -85,10 +92,15 @@ var padeditor = (function() padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection())); }) - // font face + // font family change $("#viewfontmenu").change(function() { - pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace'); + $.each(fonts, function(i, font){ + var sfont = font.replace("use",""); + sfont = sfont.replace("Font",""); + sfont = sfont.toLowerCase(); + pad.changeViewOption(font, $("#viewfontmenu").val() == sfont); + }); }); // Language @@ -98,12 +110,12 @@ var padeditor = (function() // this does not interfere with html10n's normal value-setting because html10n just ingores <input>s // also, a value which has been set by the user will be not overwritten since a user-edited <input> // does *not* have the editempty-class - $('input[data-l10n-id]').each(function(key, input) - { - input = $(input); - if(input.hasClass("editempty")) - input.val(html10n.get(input.attr("data-l10n-id"))); - }); + $('input[data-l10n-id]').each(function(key, input){ + input = $(input); + if(input.hasClass("editempty")){ + input.val(html10n.get(input.attr("data-l10n-id"))); + } + }); }) $("#languagemenu").val(html10n.getLanguage()); $("#languagemenu").change(function() { @@ -136,13 +148,49 @@ var padeditor = (function() v = getOption('showAuthorColors', true); self.ace.setProperty("showsauthorcolors", v); padutils.setCheckbox($("#options-colorscheck"), v); + // Override from parameters if true - if (settings.noColors !== false) + if (settings.noColors !== false){ self.ace.setProperty("showsauthorcolors", !settings.noColors); + } + + var normalFont = true; + // Go through each font and see if the option is set.. + $.each(fonts, function(i, font){ + var isEnabled = getOption(font, false); + if(isEnabled){ + font = font.replace("use",""); + font = font.replace("Font",""); + font = font.toLowerCase(); + if(font === "monospace") self.ace.setProperty("textface", "Courier new"); + if(font === "opendyslexic") self.ace.setProperty("textface", "OpenDyslexic"); + if(font === "comicsans") self.ace.setProperty("textface", "Comic Sans MS"); + if(font === "georgia") self.ace.setProperty("textface", "Georgia"); + if(font === "impact") self.ace.setProperty("textface", "Impact"); + if(font === "lucida") self.ace.setProperty("textface", "Lucida"); + if(font === "lucidasans") self.ace.setProperty("textface", "Lucida Sans Unicode"); + if(font === "palatino") self.ace.setProperty("textface", "Palatino Linotype"); + if(font === "tahoma") self.ace.setProperty("textface", "Tahoma"); + if(font === "timesnewroman") self.ace.setProperty("textface", "Times New Roman"); + if(font === "trebuchet") self.ace.setProperty("textface", "Trebuchet MS"); + if(font === "verdana") self.ace.setProperty("textface", "Verdana"); + if(font === "symbol") self.ace.setProperty("textface", "Symbol"); + if(font === "webdings") self.ace.setProperty("textface", "Webdings"); + if(font === "wingdings") self.ace.setProperty("textface", "Wingdings"); + if(font === "sansserif") self.ace.setProperty("textface", "MS Sans Serif"); + if(font === "serif") self.ace.setProperty("textface", "MS Serif"); + + // $("#viewfontmenu").val(font); + normalFont = false; + } + }); + + // No font has been previously selected so use the Normal font + if(normalFont){ + self.ace.setProperty("textface", "Arial, sans-serif"); + // $("#viewfontmenu").val("normal"); + } - v = getOption('useMonospaceFont', false); - self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif")); - $("#viewfontmenu").val(v ? "monospace" : "normal"); }, dispose: function() { diff --git a/src/static/js/pad_impexp.js b/src/static/js/pad_impexp.js index aa48ad77..96761570 100644 --- a/src/static/js/pad_impexp.js +++ b/src/static/js/pad_impexp.js @@ -35,32 +35,10 @@ var padimpexp = (function() function fileInputUpdated() { + $('#importsubmitinput').addClass('throbbold'); $('#importformfilediv').addClass('importformenabled'); $('#importsubmitinput').removeAttr('disabled'); - $('#importmessagefail').fadeOut("fast"); - $('#importarrow').show(); - $('#importarrow').animate( - { - paddingLeft: "0px" - }, 500).animate( - { - paddingLeft: "10px" - }, 150, 'swing').animate( - { - paddingLeft: "0px" - }, 150, 'swing').animate( - { - paddingLeft: "10px" - }, 150, 'swing').animate( - { - paddingLeft: "0px" - }, 150, 'swing').animate( - { - paddingLeft: "10px" - }, 150, 'swing').animate( - { - paddingLeft: "0px" - }, 150, 'swing'); + $('#importmessagefail').fadeOut('fast'); } function fileInputSubmit() @@ -131,6 +109,8 @@ var padimpexp = (function() msg = html10n.get("pad.impexp.convertFailed"); } else if(status === "uploadFailed"){ msg = html10n.get("pad.impexp.uploadFailed"); + } else if(status === "padHasData"){ + msg = html10n.get("pad.impexp.padHasData"); } function showError(fade) @@ -208,7 +188,8 @@ var padimpexp = (function() pad = _pad; //get /p/padname - var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname); + // if /p/ isn't available due to a rewrite we use the clientVars padId + var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId; //get http://example.com/p/padname without Params var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname; @@ -220,8 +201,8 @@ var padimpexp = (function() // build the export links $("#exporthtmla").attr("href", pad_root_path + "/export/html"); + $("#exportetherpada").attr("href", pad_root_path + "/export/etherpad"); $("#exportplaina").attr("href", pad_root_path + "/export/txt"); - $("#exportdokuwikia").attr("href", pad_root_path + "/export/dokuwiki"); // activate action to import in the form $("#importform").attr('action', pad_root_url + "/import"); @@ -257,13 +238,13 @@ var padimpexp = (function() $('#importform').submit(fileInputSubmit); $('.disabledexport').click(cantExport); }, - handleFrameCall: function(status) + handleFrameCall: function(directDatabaseAccess, status) { if (status !== "ok") { importFailed(status); } - + if(directDatabaseAccess) pad.switchToPad(clientVars.padId); importDone(); }, disable: function() diff --git a/src/static/js/pad_savedrevs.js b/src/static/js/pad_savedrevs.js index e1552c27..34323b22 100644 --- a/src/static/js/pad_savedrevs.js +++ b/src/static/js/pad_savedrevs.js @@ -18,7 +18,16 @@ var pad; exports.saveNow = function(){ pad.collabClient.sendMessage({"type": "SAVE_REVISION"}); - alert(_("pad.savedrevs.marked")); + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: _("pad.savedrevs.marked"), + // (string | mandatory) the text inside the notification + text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider", + // (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 + time: '2000' + }); } exports.init = function(_pad){ diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index 93c8ff70..22dab40a 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -468,7 +468,7 @@ var paduserlist = (function() self.setMyUserInfo(myInitialUserInfo); - $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>'); + if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>'); $("#otheruserstable tr").remove(); @@ -508,6 +508,30 @@ var paduserlist = (function() }); // }, + users: function(){ + // Returns an object of users who have been on this pad + // Firstly we have to get live data.. + var userList = otherUsersInfo; + // Now we need to add ourselves.. + userList.push(myUserInfo); + // Now we add historical authors + var historical = clientVars.collab_client_vars.historicalAuthorData; + for (var key in historical){ + var userId = historical[key].userId; + // Check we don't already have this author in our array + var exists = false; + + userList.forEach(function(user){ + if(user.userId === userId) exists = true; + }); + + if(exists === false){ + userList.push(historical[key]); + } + + } + return userList; + }, setMyUserInfo: function(info) { //translate the colorId @@ -730,7 +754,7 @@ var paduserlist = (function() $("#myswatch").css({'background-color': myUserInfo.colorId}); - if ($.browser.msie && parseInt($.browser.version) <= 8) { + if (browser.msie && parseInt(browser.version) <= 8) { $("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId}); } else diff --git a/src/static/js/pad_utils.js b/src/static/js/pad_utils.js index 343e5fce..ff60ca7c 100644 --- a/src/static/js/pad_utils.js +++ b/src/static/js/pad_utils.js @@ -55,7 +55,7 @@ function createCookie(name, value, days, path){ /* Used by IE */ } //Check if the browser is IE and if so make sure the full path is set in the cookie - if(navigator.appName=='Microsoft Internet Explorer'){ + if((navigator.appName == 'Microsoft Internet Explorer') || ((navigator.appName == 'Netscape') && (new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})").exec(navigator.userAgent) != null))){ document.cookie = name + "=" + value + expires + "; path=/"; /* Note this bodge fix for IE is temporary until auth is rewritten */ } else{ diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js index a8ac413f..cf5fcc4e 100644 --- a/src/static/js/pluginfw/hooks.js +++ b/src/static/js/pluginfw/hooks.js @@ -41,7 +41,7 @@ exports.syncMapFirst = function (lst, fn) { exports.mapFirst = function (lst, fn, cb) { var i = 0; - next = function () { + var next = function () { if (i >= lst.length) return cb(undefined); fn(lst[i++], function (err, result) { if (err) return cb(err); diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 9f7ac939..cd2ed330 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -1,6 +1,7 @@ var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var npm = require("npm"); +var request = require("request"); var npmIsLoaded = false; var withNpm = function (npmfn) { @@ -60,17 +61,20 @@ exports.availablePlugins = null; var cacheTimestamp = 0; exports.getAvailablePlugins = function(maxCacheAge, cb) { - withNpm(function (er) { + request("https://static.etherpad.org/plugins.json", function(er, response, plugins){ if (er) return cb && cb(er); if(exports.availablePlugins && maxCacheAge && Math.round(+new Date/1000)-cacheTimestamp <= maxCacheAge) { return cb && cb(null, exports.availablePlugins) } - npm.commands.search(['ep_'], /*silent?*/true, function(er, results) { - if(er) return cb && cb(er); - exports.availablePlugins = results; - cacheTimestamp = Math.round(+new Date/1000); - cb && cb(null, results) - }) + try { + plugins = JSON.parse(plugins); + } catch (err) { + console.error('error parsing plugins.json:', err); + plugins = []; + } + exports.availablePlugins = plugins; + cacheTimestamp = Math.round(+new Date/1000); + cb && cb(null, plugins) }); }; diff --git a/src/static/js/rjquery.js b/src/static/js/rjquery.js index d9d1ed16..1c0d98e6 100644 --- a/src/static/js/rjquery.js +++ b/src/static/js/rjquery.js @@ -1,10 +1,5 @@ // Proviedes a require'able version of jQuery without leaking $ and jQuery; - require('./jquery'); var jq = window.$.noConflict(true); - -//added the old browser recognition -jq.browser = require('./jquery_browser').browser; - -exports.jQuery = exports.$ = jq;
\ No newline at end of file +exports.jQuery = exports.$ = jq; diff --git a/src/static/js/timeslider.js b/src/static/js/timeslider.js index b3c10b8a..75c20022 100644 --- a/src/static/js/timeslider.js +++ b/src/static/js/timeslider.js @@ -62,8 +62,8 @@ function init() { var resource = exports.baseURL.substring(1) + 'socket.io'; //build up the socket io connection - socket = io.connect(url, {resource: resource}); - + socket = io.connect(url, {path: exports.baseURL + 'socket.io', resource: resource}); + //send the ready message once we're connected socket.on('connect', function() { @@ -157,6 +157,38 @@ function handleClientVars(message) fireWhenAllScriptsAreLoaded[i](); } $("#ui-slider-handle").css('left', $("#ui-slider-bar").width() - 2); + + // Translate some strings where we only want to set the title not the actual values + $('#playpause_button_icon').attr("title", html10n.get("timeslider.playPause")); + $('#leftstep').attr("title", html10n.get("timeslider.backRevision")); + $('#rightstep').attr("title", html10n.get("timeslider.forwardRevision")); + + // font family change + $("#viewfontmenu").change(function(){ + var font = $("#viewfontmenu").val(); + if(font === "monospace") setFont("Courier new"); + if(font === "opendyslexic") setFont("OpenDyslexic"); + if(font === "comicsans") setFont("Comic Sans MS"); + if(font === "georgia") setFont("Georgia"); + if(font === "impact") setFont("Impact"); + if(font === "lucida") setFont("Lucida"); + if(font === "lucidasans") setFont("Lucida Sans Unicode"); + if(font === "palatino") setFont("Palatino Linotype"); + if(font === "tahoma") setFont("Tahoma"); + if(font === "timesnewroman") setFont("Times New Roman"); + if(font === "trebuchet") setFont("Trebuchet MS"); + if(font === "verdana") setFont("Verdana"); + if(font === "symbol") setFont("Symbol"); + if(font === "webdings") setFont("Webdings"); + if(font === "wingdings") setFont("Wingdings"); + if(font === "sansserif") setFont("MS Sans Serif"); + if(font === "serif") setFont("MS Serif"); + }); + +} + +function setFont(font){ + $('#padcontent').css("font-family", font); } exports.baseURL = ''; diff --git a/src/templates/admin/index.html b/src/templates/admin/index.html index 750a4b52..f6e9e29e 100644 --- a/src/templates/admin/index.html +++ b/src/templates/admin/index.html @@ -10,7 +10,7 @@ <body> <div id="wrapper"> <div class="menu"> - <h1><a href="../../">Etherpad</a></h1> + <h1><a href="../">Etherpad</a></h1> <ul> <% e.begin_block("adminMenu"); %> <li><a href="plugins">Plugin manager</a> </li> diff --git a/src/templates/admin/plugins-info.html b/src/templates/admin/plugins-info.html index ca6fbc82..5d39c388 100644 --- a/src/templates/admin/plugins-info.html +++ b/src/templates/admin/plugins-info.html @@ -11,7 +11,7 @@ <body> <div id="wrapper"> <div class="menu"> - <h1><a href="../../../">Etherpad</a></h1> + <h1><a href="../../">Etherpad</a></h1> <ul> <% e.begin_block("adminMenu"); %> <li><a href="../plugins">Plugin manager</a> </li> @@ -22,6 +22,9 @@ </div> <div class="innerwrapper"> + <h2>Etherpad version</h2> + <p>Version number: <%= epVersion %></p> + <p>Git sha: <a href='https://github.com/ether/etherpad-lite/commit/<%= gitCommit %>'><%= gitCommit %></a></p> <h2>Installed plugins</h2> <pre><%- plugins.formatPlugins().replace(", ","\n") %></pre> diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 75c47ad4..71c4dbcc 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -20,7 +20,7 @@ <% } %> <div class="menu"> - <h1><a href="../../">Etherpad</a></h1> + <h1><a href="../">Etherpad</a></h1> <ul> <% e.begin_block("adminMenu"); %> <li><a href="plugins">Plugin manager</a> </li> @@ -49,7 +49,7 @@ <td> <div class="actions"> <input type="button" value="Uninstall" class="do-uninstall"> - <div class="progress"><p><img src="../static/img/loading.gif"/></p><p><span class="message"></span></p></div> + <div class="progress"><p class="loadingAnimation"></p><p><span class="message"></span></p></div> </div> </td> </tr> @@ -59,7 +59,7 @@ <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> + <p class="fetching"><p class="loadingAnimation"></p><br/>Fetching installed plugins...</p> </td><td></td></tr> </tbody> </table> @@ -78,6 +78,7 @@ <th class="sort up" data-label="name">Name</th> <th class="sort none" data-label="description">Description</th> <th class="sort none" data-label="version">Version</th> + <th class="sort none" data-label="time">Last update</th> <td></td> </tr> </thead> @@ -86,10 +87,11 @@ <td class="name" data-label="Name"></td> <td class="description" data-label="Description"></td> <td class="version" data-label="Version"></td> + <td class="time" data-label="Time"></td> <td> <div class="actions"> <input type="button" value="Install" class="do-install"> - <div class="progress"><p><img src="../static/img/loading.gif"/></p><p><span class="message"></span></p></div> + <div class="progress"><p><p class="loadingAnimation"></p></p><p><span class="message"></span></p></div> </div> </td> </tr> @@ -101,7 +103,7 @@ <div class="messages"> <div id="search-progress" class="progress"><p> </p></div> <p class="nothing-found">No plugins found.</p> - <p class="fetching"><img src="../static/img/loading.gif"/><br/>Fetching...</p> + <p class="fetching"><p class="loadingAnimation"></p><br/>Fetching...</p> </div> </td><td></td></tr> </tbody> diff --git a/src/templates/admin/settings.html b/src/templates/admin/settings.html index f6b8087c..3b8615fc 100644 --- a/src/templates/admin/settings.html +++ b/src/templates/admin/settings.html @@ -24,7 +24,7 @@ <div class="menu"> - <h1><a href="../../">Etherpad</a></h1> + <h1><a href="../">Etherpad</a></h1> <ul> <% e.begin_block("adminMenu"); %> <li><a href="plugins">Plugin manager</a> </li> diff --git a/src/templates/index.html b/src/templates/index.html index 02ecf67a..626630e3 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -70,9 +70,10 @@ } #button { margin: 0 auto; - border-radius: 3px; text-align: center; font: 36px verdana,arial,sans-serif; + width:300px; + border:none; color: white; text-shadow: 0 -1px 0 rgba(0,0,0,.8); height: 70px; @@ -100,6 +101,7 @@ text-align: left; text-shadow: 0 1px 1px #fff; margin: 16px auto 0; + display:block; } #padname{ height:38px; @@ -158,8 +160,8 @@ <div id="wrapper"> <% e.begin_block("indexWrapper"); %> <div id="inner"> - <div id="button" onclick="go2Random()" data-l10n-id="index.newPad"></div> - <div id="label" data-l10n-id="index.createOpenPad"></div> + <buttOn id="button" onclick="go2Random()" data-l10n-id="index.newPad"></button> + <label id="label" for="padname" data-l10n-id="index.createOpenPad"></label> <form action="#" onsubmit="go2Name();return false;"> <input type="text" id="padname" autofocus x-webkit-speech> <button type="submit">OK</button> diff --git a/src/templates/pad.html b/src/templates/pad.html index 15fd45e2..dd260414 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -40,9 +40,12 @@ <link rel="shortcut icon" href="<%=settings.faviconPad%>"> <% e.begin_block("styles"); %> - <link href="../static/css/fontawesome-etherpad.css" rel="stylesheet"> <link href="../static/css/pad.css" rel="stylesheet"> + + <% e.begin_block("customStyles"); %> <link href="../static/custom/pad.css" rel="stylesheet"> + <% e.end_block(); %> + <style title="dynamicsyntax"></style> <% e.end_block(); %> @@ -53,17 +56,17 @@ <!-- head and body had been removed intentionally --> <% e.begin_block("body"); %> - <div id="editbar" class="toolbar"> + <div id="editbar" class="toolbar" title="Toolbar (Alt F9)"> <div id="overlay"> <div id="overlay-inner"></div> </div> - <ul class="menu_left"> + <ul class="menu_left" role="toolbar"> <% e.begin_block("editbarMenuLeft"); %> <%- toolbar.menu(settings.toolbar.left) %> <% e.end_block(); %> </ul> - <ul class="menu_right"> + <ul class="menu_right" role="toolbar"> <% e.begin_block("editbarMenuRight"); %> <%- toolbar.menu(settings.toolbar.right) %> <% e.end_block(); %> @@ -85,7 +88,7 @@ <div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled" data-l10n-id="pad.userlist.entername"></div> <div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div> </div> - <div id="otherusers"> + <div id="otherusers" aria-role="document"> <div id="guestprompts"></div> <table id="otheruserstable" cellspacing="0" cellpadding="0" border="0"> <tr><td></td></tr> @@ -111,6 +114,9 @@ <div id="wrongPassword"> <p data-l10n-id="pad.wrongPassword">Your password was wrong</p> </div> + <div id="noCookie"> + <p data-l10n-id="pad.noCookie">Cookie could not be found. Please allow cookies in your browser!</p> + </div> <% e.begin_block("loading"); %> <p data-l10n-id="pad.loading" id="loading">Loading...</p> <% e.end_block(); %> @@ -128,6 +134,10 @@ <label for="options-stickychat" data-l10n-id="pad.settings.stickychat"></label> </p> <p> + <input type="checkbox" id="options-chatandusers" onClick="chat.chatAndUsers();"> + <label for="options-chatandusers" data-l10n-id="pad.settings.chatandusers"></label> + </p> + <p> <input type="checkbox" id="options-colorscheck"> <label for="options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label> </p> @@ -150,6 +160,22 @@ <select id="viewfontmenu"> <option value="normal" data-l10n-id="pad.settings.fontType.normal"></option> <option value="monospace" data-l10n-id="pad.settings.fontType.monospaced"></option> + <option value="opendyslexic" data-l10n-id="pad.settings.fontType.opendyslexic"></option> + <option value="comicsans" data-l10n-id="pad.settings.fontType.comicsans"></option> + <option value="georgia" data-l10n-id="pad.settings.fontType.georgia"></option> + <option value="impact" data-l10n-id="pad.settings.fontType.impact"></option> + <option value="lucida" data-l10n-id="pad.settings.fontType.lucida"></option> + <option value="lucidasans" data-l10n-id="pad.settings.fontType.lucidasans"></option> + <option value="palatino" data-l10n-id="pad.settings.fontType.palatino"></option> + <option value="tahoma" data-l10n-id="pad.settings.fontType.tahoma"></option> + <option value="timesnewroman" data-l10n-id="pad.settings.fontType.timesnewroman"></option> + <option value="trebuchet" data-l10n-id="pad.settings.fontType.trebuchet"></option> + <option value="verdana" data-l10n-id="pad.settings.fontType.verdana"></option> + <option value="symbol" data-l10n-id="pad.settings.fontType.symbol"></option> + <option value="webdings" data-l10n-id="pad.settings.fontType.webdings"></option> + <option value="wingdings" data-l10n-id="pad.settings.fontType.wingdings"></option> + <option value="sansserif" data-l10n-id="pad.settings.fontType.sansserif"></option> + <option value="serif" data-l10n-id="pad.settings.fontType.serif"></option> </select> </td> </tr> @@ -191,8 +217,7 @@ <div class="importformdiv" id="importformsubmitdiv"> <span class="nowrap"> <input type="submit" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput"> - <img alt="" id="importstatusball" src="../static/img/loading.gif" align="top"> - <img alt="" id="importarrow" src="../static/img/leftarrow.png" align="top"> + <div alt="" id="importstatusball" class="loadingAnimation" align="top"></div> </span> </div> </form> @@ -201,12 +226,12 @@ <div class="column" id="exportColumn"> <h2 data-l10n-id="pad.importExport.export"></h2> <% e.begin_block("exportColumn"); %> + <a id="exportetherpada" target="_blank" class="exportlink"><div class="exporttype" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad"></div></a> <a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></div></a> <a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain" data-l10n-id="pad.importExport.exportplain"></div></a> <a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword" data-l10n-id="pad.importExport.exportword"></div></a> <a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></div></a> <a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen" data-l10n-id="pad.importExport.exportopen"></div></a> - <a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki" data-l10n-id="pad.importExport.exportdokuwiki"></div></a> <% e.end_block(); %> </div> </div> @@ -218,7 +243,7 @@ </div> <div class="reconnecting"> <h1 data-l10n-id="pad.modals.reconnecting"></h1> - <p><img alt="" border="0" src="../static/img/connectingbar.gif" /></p> + <p class='loadingAnimation'></p> </div> <div class="userdup"> <h1 data-l10n-id="pad.modals.userdup"></h1> @@ -297,7 +322,7 @@ <% e.end_block(); %> </div> - <div id="chaticon" onclick="chat.show();return false;"> + <div id="chaticon" onclick="chat.show();return false;" title="Chat (Alt C)"> <span id="chatlabel" data-l10n-id="pad.chat"></span> <span class="buttonicon buttonicon-chat"></span> <span id="chatcounter">0</span> @@ -308,8 +333,8 @@ <a id="titlecross" onClick="chat.hide();return false;">- </a> <a id="titlesticky" onClick="chat.stickToScreen(true);$('#options-stickychat').prop('checked', true);return false;" title="Stick chat to screen">█ </a> </div> - <div id="chattext" class="authorColors"> - <img alt="loading.." id="chatloadmessagesball" class="chatloadmessages" src="../static/img/loading.gif" align="top"> + <div id="chattext" class="authorColors" aria-live="polite" aria-relevant="additions removals text" role="log" aria-atomic="false"> + <div alt="loading.." id="chatloadmessagesball" class="chatloadmessages loadingAnimation" align="top"></div> <button id="chatloadmessagesbutton" class="chatloadmessages" data-l10n-id="pad.chat.loadmessages"></button> </div> <div id="chatinputbox"> @@ -347,7 +372,9 @@ <script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/pad.js?callback=require.define"></script> <script type="text/javascript" src="../javascripts/lib/ep_etherpad-lite/static/js/ace2_common.js?callback=require.define"></script> + <% e.begin_block("customScripts"); %> <script type="text/javascript" src="../static/custom/pad.js"></script> + <% e.end_block(); %> <!-- Bootstrap page --> <script type="text/javascript"> @@ -363,8 +390,8 @@ require.setGlobalKeyPath("require"); $ = jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; // Expose jQuery #HACK - - if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { + browser = require('ep_etherpad-lite/static/js/browser').browser; + if ((!browser.msie) && (!(browser.mozilla && browser.version.indexOf("1.8.") == 0))) { document.domain = document.domain; // for comet } diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index 19f8dde0..6ec27c05 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -61,11 +61,11 @@ <div id="ui-slider-bar"></div> </div> <div id="playpause_button"> - <div id="playpause_button_icon" class=""></div> + <button id="playpause_button_icon" class=""></button> </div> <div id="steppers"> - <div class="stepper" id="leftstep"></div> - <div class="stepper" id="rightstep"></div> + <button class="stepper" id="leftstep"></button> + <button class="stepper" id="rightstep"></button> </div> </div> @@ -116,7 +116,7 @@ </div> <div class="reconnecting"> <h1 data-l10n-id="pad.modals.reconnecting"></h1> - <p><img alt="" border="0" src="../../static/img/connectingbar.gif" /></p> + <p class="loadingAnimation"></p> </div> <div class="userdup"> <h1 data-l10n-id="pad.modals.userdup"></h1> @@ -176,17 +176,46 @@ <% e.end_block(); %> </div> +<div class="popup" id="settings"> + <tr> + <td> + <label for="viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label> + </td> + <td> + <select id="viewfontmenu"> + <option value="normal" data-l10n-id="pad.settings.fontType.normal"></option> + <option value="monospace" data-l10n-id="pad.settings.fontType.monospaced"></option> + <option value="opendyslexic" data-l10n-id="pad.settings.fontType.opendyslexic"></option> + <option value="comicsans" data-l10n-id="pad.settings.fontType.comicsans"></option> + <option value="georgia" data-l10n-id="pad.settings.fontType.georgia"></option> + <option value="impact" data-l10n-id="pad.settings.fontType.impact"></option> + <option value="lucida" data-l10n-id="pad.settings.fontType.lucida"></option> + <option value="lucidasans" data-l10n-id="pad.settings.fontType.lucidasans"></option> + <option value="palatino" data-l10n-id="pad.settings.fontType.palatino"></option> + <option value="tahoma" data-l10n-id="pad.settings.fontType.tahoma"></option> + <option value="timesnewroman" data-l10n-id="pad.settings.fontType.timesnewroman"></option> + <option value="trebuchet" data-l10n-id="pad.settings.fontType.trebuchet"></option> + <option value="verdana" data-l10n-id="pad.settings.fontType.verdana"></option> + <option value="symbol" data-l10n-id="pad.settings.fontType.symbol"></option> + <option value="webdings" data-l10n-id="pad.settings.fontType.webdings"></option> + <option value="wingdings" data-l10n-id="pad.settings.fontType.wingdings"></option> + <option value="sansserif" data-l10n-id="pad.settings.fontType.sansserif"></option> + <option value="serif" data-l10n-id="pad.settings.fontType.serif"></option> + </select> + </td> + </tr> +</div> + <!-- export code --> <div id="import_export"> - <div id="export" class="popup"> <p data-l10n-id="timeslider.exportCurrent"></p> + <a id="exportetherpada" target="_blank" class="exportlink"><div class="exporttype" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad"></div></a> <a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></div></a> <a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain" data-l10n-id="pad.importExport.exportplain"></div></a> <a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword" data-l10n-id="pad.importExport.exportword"></div></a> <a id="exportpdfa" target="_blank" class="exportlink"><div class="exporttype" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></div></a> <a id="exportopena" target="_blank" class="exportlink"><div class="exporttype" id="exportopen" data-l10n-id="pad.importExport.exportopen"></div></a> - <a id="exportdokuwikia" target="_blank" class="exportlink"><div class="exporttype" id="exportdokuwiki" data-l10n-id="pad.importExport.exportdokuwiki"></div></a> </div> </div> @@ -215,8 +244,9 @@ require.setGlobalKeyPath("require"); $ = jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; // Expose jQuery #HACK + browser = require('ep_etherpad-lite/static/js/browser').browser; - if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { + if ((!browser.msie) && (!(browser.mozilla && browser.version.indexOf("1.8.") == 0))) { document.domain = document.domain; // for comet } diff --git a/tests/README.md b/tests/README.md index 1851305f..201ee4c8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -3,3 +3,7 @@ ## Frontend To run the tests, point your browser to `<yourdomainhere>/tests/frontend` + +## Backend + +To run the tests, run ``bin/backendTests.sh`` diff --git a/tests/backend/specs/api/chat.js b/tests/backend/specs/api/chat.js new file mode 100644 index 00000000..59b7edc0 --- /dev/null +++ b/tests/backend/specs/api/chat.js @@ -0,0 +1,113 @@ +var assert = require('assert') + supertest = require(__dirname+'/../../../../src/node_modules/supertest'), + fs = require('fs'), + api = supertest('http://localhost:9001'); + path = require('path'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var authorID = ""; +var padID = makeid(); +var timestamp = Date.now(); + +describe('API Versioning', function(){ + it('errors if can not connect', function(done) { + api.get('/api/') + .expect(function(res){ + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +// BEGIN GROUP AND AUTHOR TESTS +///////////////////////////////////// +///////////////////////////////////// + +/* Tests performed +-> createPad(padID) + -> createAuthor([name]) -- should return an authorID + -> appendChatMessage(padID, text, authorID, time) + -> getChatHead(padID) + -> getChatHistory(padID) +*/ + +describe('createPad', function(){ + it('creates a new Pad', function(done) { + api.get(endPoint('createPad')+"&padID="+padID) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create new Pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createAuthor', function(){ + it('Creates an author with a name set', function(done) { + api.get(endPoint('createAuthor')) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create author"); + authorID = res.body.data.authorID; // we will be this author for the rest of the tests + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('appendChatMessage', function(){ + it('Adds a chat message to the pad', function(done) { + api.get(endPoint('appendChatMessage')+"&padID="+padID+"&text=blalblalbha&authorID="+authorID+"&time="+timestamp) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create chat message"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + + +describe('getChatHead', function(){ + it('Gets the head of chat', function(done) { + api.get(endPoint('getChatHead')+"&padID="+padID) + .expect(function(res){ + if(res.body.data.chatHead !== 0) throw new Error("Chat Head Length is wrong"); + + if(res.body.code !== 0) throw new Error("Unable to get chat head"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getChatHistory', function(){ + it('Gets Chat History of a Pad', function(done) { + api.get(endPoint('getChatHistory')+"&padID="+padID) + .expect(function(res){ + if(res.body.data.messages.length !== 1) throw new Error("Chat History Length is wrong"); + if(res.body.code !== 0) throw new Error("Unable to get chat history"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +var endPoint = function(point){ + return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js new file mode 100644 index 00000000..52849c2e --- /dev/null +++ b/tests/backend/specs/api/pad.js @@ -0,0 +1,548 @@ +var assert = require('assert') + supertest = require(__dirname+'/../../../../src/node_modules/supertest'), + fs = require('fs'), + api = supertest('http://localhost:9001'); + path = require('path'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = makeid(); +var lastEdited = ""; +var text = generateLongText(); +var ULhtml = '<!DOCTYPE html><html><body><ul class="bullet"><li>one</li><li>2</li></ul><br><ul><ul class="bullet"><li>UL2</li></ul></ul></body></html>'; + +describe('Connectivity', function(){ + it('errors if can not connect', function(done) { + api.get('/api/') + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('API Versioning', function(){ + it('errors if can not connect', function(done) { + api.get('/api/') + .expect(function(res){ + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +describe('Permission', function(){ + it('errors if can connect without correct APIKey', function(done) { + // This is broken because Etherpad doesn't handle HTTP codes properly see #2343 + // If your APIKey is password you deserve to fail all tests anyway + var permErrorURL = '/api/'+apiVersion+'/createPad?apikey=password&padID=test'; + api.get(permErrorURL) + .expect(401, done) + }); +}) + +/* Pad Tests Order of execution +-> deletePad -- This gives us a guaranteed clear environment + -> createPad + -> getRevisions -- Should be 0 + -> getSavedRevisionsCount(padID) -- Should be 0 + -> listSavedRevisions(padID) -- Should be an empty array + -> getHTML -- Should be the default pad text in HTML format + -> deletePad -- Should just delete a pad + -> getHTML -- Should return an error + -> createPad(withText) + -> getText -- Should have the text specified above as the pad text + -> setText + -> getText -- Should be the text set before + -> getRevisions -- Should be 0 still? + -> saveRevision + -> getSavedRevisionsCount(padID) -- Should be 0 still? + -> listSavedRevisions(padID) -- Should be an empty array still ? + -> padUsersCount -- Should be 0 + -> getReadOnlyId -- Should be a value + -> listAuthorsOfPad(padID) -- should be empty array? + -> getLastEdited(padID) -- Should be when pad was made + -> setText(padId) + -> getLastEdited(padID) -- Should be when setText was performed + -> padUsers(padID) -- Should be when setText was performed + + -> setText(padId, "hello world") + -> getLastEdited(padID) -- Should be when pad was made + -> getText(padId) -- Should be "hello world" + -> movePad(padID, newPadId) -- Should provide consistant pad data + -> getText(newPadId) -- Should be "hello world" + -> movePad(newPadID, originalPadId) -- Should provide consistant pad data + -> getText(originalPadId) -- Should be "hello world" + -> getLastEdited(padID) -- Should not be 0 + -> setHTML(padID) -- Should fail on invalid HTML + -> setHTML(padID) *3 -- Should fail on invalid HTML + -> getHTML(padID) -- Should return HTML close to posted HTML + +*/ + +describe('deletePad', function(){ + it('deletes a Pad', function(done) { + api.get(endPoint('deletePad')+"&padID="+testPadId) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createPad', function(){ + it('creates a new Pad', function(done) { + api.get(endPoint('createPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create new Pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getRevisionsCount', function(){ + it('gets revision count of Pad', function(done) { + api.get(endPoint('getRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Revision Count"); + if(res.body.data.revisions !== 0) throw new Error("Incorrect Revision Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 0) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getHTML', function(){ + it('get the HTML of Pad', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.html.length <= 1) throw new Error("Unable to get the HTML"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('deletePad', function(){ + it('deletes a Pad', function(done) { + api.get(endPoint('deletePad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Deletion failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getHTML', function(){ + it('get the HTML of a Pad -- Should return a failure', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 1) throw new Error("Pad deletion failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createPad', function(){ + it('creates a new Pad with text', function(done) { + api.get(endPoint('createPad')+"&padID="+testPadId+"&text=testText") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Creation failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getText', function(){ + it('gets the Pad text and expect it to be testText with \n which is a line break', function(done) { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.text !== "testText\n") throw new Error("Pad Creation with text") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setText', function(){ + it('creates a new Pad with text', function(done) { + api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad setting text failed"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getText', function(){ + it('gets the Pad text', function(done) { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.text !== "testTextTwo\n") throw new Error("Setting Text") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getRevisionsCount', function(){ + it('gets Revision Count of a Pad', function(done) { + api.get(endPoint('getRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.revisions !== 1) throw new Error("Unable to get text revision count") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('saveRevision', function(){ + it('saves Revision', function(done) { + api.get(endPoint('saveRevision')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to save Revision"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 1) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([1])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) +describe('padUsersCount', function(){ + it('gets User Count of a Pad', function(done) { + api.get(endPoint('padUsersCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.padUsersCount !== 0) throw new Error("Incorrect Pad User count") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getReadOnlyID', function(){ + it('Gets the Read Only ID of a Pad', function(done) { + api.get(endPoint('getReadOnlyID')+"&padID="+testPadId) + .expect(function(res){ + if(!res.body.data.readOnlyID) throw new Error("No Read Only ID for Pad") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listAuthorsOfPad', function(){ + it('Get Authors of the Pad', function(done) { + api.get(endPoint('listAuthorsOfPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.authorIDs.length !== 0) throw new Error("# of Authors of pad is not 0") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getLastEdited', function(){ + it('Get When Pad was left Edited', function(done) { + api.get(endPoint('getLastEdited')+"&padID="+testPadId) + .expect(function(res){ + if(!res.body.data.lastEdited){ + throw new Error("# of Authors of pad is not 0") + }else{ + lastEdited = res.body.data.lastEdited; + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setText', function(){ + it('creates a new Pad with text', function(done) { + api.get(endPoint('setText')+"&padID="+testPadId+"&text=testTextTwo") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad setting text failed"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getLastEdited', function(){ + it('Get When Pad was left Edited', function(done) { + api.get(endPoint('getLastEdited')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.lastEdited <= lastEdited){ + throw new Error("Editing A Pad is not updating when it was last edited") + } + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('padUsers', function(){ + it('gets User Count of a Pad', function(done) { + api.get(endPoint('padUsers')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.padUsers.length !== 0) throw new Error("Incorrect Pad Users") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('deletePad', function(){ + it('deletes a Pad', function(done) { + api.get(endPoint('deletePad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Deletion failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +var originalPadId = testPadId; +var newPadId = makeid(); + +describe('createPad', function(){ + it('creates a new Pad with text', function(done) { + api.get(endPoint('createPad')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Creation failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setText', function(){ + it('Sets text on a pad Id', function(done) { + api.get(endPoint('setText')+"&padID="+testPadId+"&text="+text) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Set Text failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getText', function(){ + it('Gets text on a pad Id', function(done) { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Pad Get Text failed") + if(res.body.data.text !== text+"\n") throw new Error("Pad Text not set properly"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getLastEdited', function(){ + it('Gets when pad was last edited', function(done) { + api.get(endPoint('getLastEdited')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.lastEdited === 0) throw new Error("Get Last Edited Failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('movePad', function(){ + it('Move a Pad to a different Pad ID', function(done) { + api.get(endPoint('movePad')+"&sourceID="+testPadId+"&destinationID="+newPadId+"&force=true") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Moving Pad Failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getText', function(){ + it('Gets text on a pad Id', function(done) { + api.get(endPoint('getText')+"&padID="+newPadId) + .expect(function(res){ + if(res.body.data.text !== text+"\n") throw new Error("Pad Get Text failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('movePad', function(){ + it('Move a Pad to a different Pad ID', function(done) { + api.get(endPoint('movePad')+"&sourceID="+newPadId+"&destinationID="+testPadId+"&force=false") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Moving Pad Failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getText', function(){ + it('Gets text on a pad Id', function(done) { + api.get(endPoint('getText')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.data.text !== text+"\n") throw new Error("Pad Get Text failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getLastEdited', function(){ + it('Gets when pad was last edited', function(done) { + api.get(endPoint('getLastEdited')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.lastEdited === 0) throw new Error("Get Last Edited Failed") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setHTML', function(){ + it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) { + var html = "<div><b>Hello HTML</title></head></div>"; + api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+html) + .expect(function(res){ +console.log(res.body.code); + if(res.body.code !== 1) throw new Error("Allowing crappy HTML to be imported") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setHTML', function(){ + it('Sets the HTML of a Pad with a bunch of weird unordered lists inserted', function(done) { + api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ULhtml) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("List HTML cant be imported") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getHTML', function(){ + it('Gets the HTML of a Pad with a bunch of weird unordered lists inserted', function(done) { + api.get(endPoint('getHTML')+"&padID="+testPadId) + .expect(function(res){ + var ehtml = res.body.data.html.replace("<br></body>", "</body>").toLowerCase(); + var uhtml = ULhtml.toLowerCase(); + if(ehtml !== uhtml) throw new Error("Imported HTML does not match served HTML") + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + + +/* + -> movePadForce Test + +*/ + +var endPoint = function(point){ + return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +function generateLongText(){ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 80000; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +// Need this to compare arrays (listSavedRevisions test) +Array.prototype.equals = function (array) { + // if the other array is a falsy value, return + if (!array) + return false; + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; +} diff --git a/tests/backend/specs/api/sessionsAndGroups.js b/tests/backend/specs/api/sessionsAndGroups.js new file mode 100644 index 00000000..4742852f --- /dev/null +++ b/tests/backend/specs/api/sessionsAndGroups.js @@ -0,0 +1,364 @@ +var assert = require('assert') + supertest = require(__dirname+'/../../../../src/node_modules/supertest'), + fs = require('fs'), + api = supertest('http://localhost:9001'); + path = require('path'); + +var filePath = path.join(__dirname, '../../../../APIKEY.txt'); + +var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); +apiKey = apiKey.replace(/\n$/, ""); +var apiVersion = 1; +var testPadId = makeid(); +var groupID = ""; +var authorID = ""; +var sessionID = ""; +var padID = makeid(); + +describe('API Versioning', function(){ + it('errors if can not connect', function(done) { + api.get('/api/') + .expect(function(res){ + apiVersion = res.body.currentVersion; + if (!res.body.currentVersion) throw new Error("No version set in API"); + return; + }) + .expect(200, done) + }); +}) + +// BEGIN GROUP AND AUTHOR TESTS +///////////////////////////////////// +///////////////////////////////////// + +/* Tests performed +-> createGroup() -- should return a groupID + -> listSessionsOfGroup(groupID) -- should be 0 + -> deleteGroup(groupID) + -> createGroupIfNotExistsFor(groupMapper) -- should return a groupID + + -> createAuthor([name]) -- should return an authorID + -> createAuthorIfNotExistsFor(authorMapper [, name]) -- should return an authorID + -> getAuthorName(authorID) -- should return a name IE "john" + +-> createSession(groupID, authorID, validUntil) + -> getSessionInfo(sessionID) + -> listSessionsOfGroup(groupID) -- should be 1 + -> deleteSession(sessionID) + -> getSessionInfo(sessionID) -- should have author id etc in + +-> listPads(groupID) -- should be empty array + -> createGroupPad(groupID, padName [, text]) + -> listPads(groupID) -- should be empty array + -> getPublicStatus(padId) + -> setPublicStatus(padId, status) + -> getPublicStatus(padId) + -> isPasswordProtected(padID) -- should be false + -> setPassword(padID, password) + -> isPasswordProtected(padID) -- should be true + +-> listPadsOfAuthor(authorID) +*/ + +describe('createGroup', function(){ + it('creates a new group', function(done) { + api.get(endPoint('createGroup')) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.groupID) throw new Error("Unable to create new Pad"); + groupID = res.body.data.groupID; + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSessionsOfGroup', function(){ + it('Lists the session of a group', function(done) { + api.get(endPoint('listSessionsOfGroup')+"&groupID="+groupID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data !== null) throw new Error("Sessions show as existing for this group"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('deleteGroup', function(){ + it('Deletes a group', function(done) { + api.get(endPoint('deleteGroup')+"&groupID="+groupID) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Group failed to be deleted"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createGroupIfNotExistsFor', function(){ + it('Creates a group if one doesnt exist for mapper 0', function(done) { + api.get(endPoint('createGroupIfNotExistsFor')+"&groupMapper=management") + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.groupID) throw new Error("Sessions show as existing for this group"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createGroup', function(){ + it('creates a new group', function(done) { + api.get(endPoint('createGroup')) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.groupID) throw new Error("Unable to create new Pad"); + groupID = res.body.data.groupID; + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createAuthor', function(){ + it('Creates an author with a name set', function(done) { + api.get(endPoint('createAuthor')) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create author"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createAuthor', function(){ + it('Creates an author with a name set', function(done) { + api.get(endPoint('createAuthor')+"&name=john") + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create user with name set"); + authorID = res.body.data.authorID; // we will be this author for the rest of the tests + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createAuthorIfNotExistsFor', function(){ + it('Creates an author if it doesnt exist already and provides mapping', function(done) { + api.get(endPoint('createAuthorIfNotExistsFor')+"&authorMapper=chris") + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create author with mapper"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getAuthorName', function(){ + it('Gets the author name', function(done) { + api.get(endPoint('getAuthorName')+"&authorID="+authorID) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data === "john") throw new Error("Unable to get Author Name from Author ID"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +// BEGIN SESSION TESTS +/////////////////////////////////////// +/////////////////////////////////////// + +describe('createSession', function(){ + it('Creates a session for an Author', function(done) { + api.get(endPoint('createSession')+"&authorID="+authorID+"&groupID="+groupID+"&validUntil=999999999999") + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.sessionID) throw new Error("Unable to create Session"); + sessionID = res.body.data.sessionID; + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSessionInfo', function(){ + it('Gets session inf', function(done) { + api.get(endPoint('getSessionInfo')+"&sessionID="+sessionID) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.groupID || !res.body.data.authorID || !res.body.data.validUntil) throw new Error("Unable to get Session info"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSessionsOfGroup', function(){ + it('Gets sessions of a group', function(done) { + api.get(endPoint('listSessionsOfGroup')+"&groupID="+groupID) + .expect(function(res){ + if(res.body.code !== 0 || typeof res.body.data !== "object") throw new Error("Unable to get sessions of a group"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('deleteSession', function(){ + it('Deletes a session', function(done) { + api.get(endPoint('deleteSession')+"&sessionID="+sessionID) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to delete a session"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSessionInfo', function(){ + it('Gets session info', function(done) { + api.get(endPoint('getSessionInfo')+"&sessionID="+sessionID) + .expect(function(res){ + if(res.body.code !== 1) throw new Error("Session was not properly deleted"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +// GROUP PAD MANAGEMENT +/////////////////////////////////////// +/////////////////////////////////////// + +describe('listPads', function(){ + it('Lists Pads of a Group', function(done) { + api.get(endPoint('listPads')+"&groupID="+groupID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data.padIDs.length !== 0) throw new Error("Group already had pads for some reason"+res.body.data.padIDs); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('createGroupPad', function(){ + it('Creates a Group Pad', function(done) { + api.get(endPoint('createGroupPad')+"&groupID="+groupID+"&padName="+padID) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to create group pad"); + padID = res.body.data.padID; + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listPads', function(){ + it('Lists Pads of a Group', function(done) { + api.get(endPoint('listPads')+"&groupID="+groupID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data.padIDs.length !== 1) throw new Error("Group isnt listing this pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +// PAD SECURITY /-_-\ +/////////////////////////////////////// +/////////////////////////////////////// + +describe('getPublicStatus', function(){ + it('Gets the public status of a pad', function(done) { + api.get(endPoint('getPublicStatus')+"&padID="+padID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data.publicstatus) throw new Error("Unable to get public status of this pad"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setPublicStatus', function(){ + it('Sets the public status of a pad', function(done) { + api.get(endPoint('setPublicStatus')+"&padID="+padID+"&publicStatus=true") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Setting status did not work"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getPublicStatus', function(){ + it('Gets the public status of a pad', function(done) { + api.get(endPoint('getPublicStatus')+"&padID="+padID) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.publicStatus) throw new Error("Setting public status of this pad did not work"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('isPasswordProtected', function(){ + it('Gets the public status of a pad', function(done) { + api.get(endPoint('isPasswordProtected')+"&padID="+padID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data.isPasswordProtected) throw new Error("Pad is password protected by default"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('setPassword', function(){ + it('Gets the public status of a pad', function(done) { + api.get(endPoint('setPassword')+"&padID="+padID+"&password=test") + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unabe to set password"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('isPasswordProtected', function(){ + it('Gets the public status of a pad', function(done) { + api.get(endPoint('isPasswordProtected')+"&padID="+padID) + .expect(function(res){ + if(res.body.code !== 0 || !res.body.data.isPasswordProtected) throw new Error("Pad password protection has not applied"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + + +// NOT SURE HOW TO POPULAT THIS /-_-\ +/////////////////////////////////////// +/////////////////////////////////////// + +describe('listPadsOfAuthor', function(){ + it('Gets the Pads of an Author', function(done) { + api.get(endPoint('listPadsOfAuthor')+"&authorID="+authorID) + .expect(function(res){ + if(res.body.code !== 0 || res.body.data.padIDs.length !== 0) throw new Error("Pad password protection has not applied"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + + + +var endPoint = function(point){ + return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey; +} + +function makeid() +{ + var text = ""; + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for( var i=0; i < 5; i++ ){ + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/tests/frontend/index.html b/tests/frontend/index.html index ac85a136..1bf10495 100644 --- a/tests/frontend/index.html +++ b/tests/frontend/index.html @@ -10,7 +10,7 @@ <div id="iframe-container"></div> <script src="/static/js/jquery.js"></script> - <script src="/static/js/jquery_browser.js"></script> + <script src="/static/js/browser.js"></script> <script src="lib/underscore.js"></script> <script src="lib/mocha.js"></script> @@ -22,6 +22,5 @@ <script src="helper.js"></script> <script src="specs_list.js"></script> - <script src="runner.js"></script> </html> diff --git a/tests/frontend/lib/jquery.iframe.js b/tests/frontend/lib/jquery.iframe.js index 3c3b7b05..604ae1bc 100644 --- a/tests/frontend/lib/jquery.iframe.js +++ b/tests/frontend/lib/jquery.iframe.js @@ -2,8 +2,9 @@ (function($) { $.fn.purgeFrame = function() { var deferred; + var browser = bowser; - if ($.browser.msie && parseFloat($.browser.version, 10) < 9) { + if (browser.msie && parseFloat(browser.version, 10) < 9) { deferred = purge(this); } else { this.remove(); @@ -36,4 +37,4 @@ return deferred.promise(); } -})(jQuery);
\ No newline at end of file +})(jQuery); diff --git a/tests/frontend/runner.js b/tests/frontend/runner.js index 8f722125..4801b95c 100644 --- a/tests/frontend/runner.js +++ b/tests/frontend/runner.js @@ -162,7 +162,8 @@ $(function(){ } //allow cross iframe access - if ((!$.browser.msie) && (!($.browser.mozilla && $.browser.version.indexOf("1.8.") == 0))) { + var browser = bowser; + if ((!browser.msie) && (!(browser.mozilla && browser.version.indexOf("1.8.") == 0))) { document.domain = document.domain; // for comet } diff --git a/tests/frontend/specs/bold.js b/tests/frontend/specs/bold.js index 95da7331..b54466e4 100644 --- a/tests/frontend/specs/bold.js +++ b/tests/frontend/specs/bold.js @@ -43,7 +43,8 @@ describe("bold button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$.browser.mozilla){ // if it's a mozilla browser + + if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/caret.js b/tests/frontend/specs/caret.js index b33f5168..14ff8d6a 100644 --- a/tests/frontend/specs/caret.js +++ b/tests/frontend/specs/caret.js @@ -1,11 +1,19 @@ describe("As the caret is moved is the UI properly updated?", function(){ var padName; var numberOfRows = 50; +/* + + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); - it("creates a pad", function(done) { + xit("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 @@ -20,10 +28,12 @@ describe("As the caret is moved is the UI properly updated?", function(){ * 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! +/* + // THIS DOESNT WORK IN CHROME AS IT DOESNT MOVE THE CURSOR! it("down arrow", function(done){ var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var $newFirstTextElement = inner$("div").first(); $newFirstTextElement.focus(); keyEvent(inner$, 37, false, false); // arrow down @@ -31,9 +41,10 @@ describe("As the caret is moved is the UI properly updated?", function(){ done(); }); -/* + it("Creates N lines", function(done){ var inner$ = helper.padInner$; +console.log(inner$); var chrome$ = helper.padChrome$; var $newFirstTextElement = inner$("div").first(); @@ -224,7 +235,6 @@ describe("As the caret is moved is the UI properly updated?", function(){ }); var i = 0; while(i < numberOfRows){ // press down arrow -console.log("dwn"); keyEvent(inner$, 40, false, false); i++; } @@ -287,7 +297,7 @@ function prepareDocument(n, target){ // generates a random document with random } function keyEvent(target, charCode, ctrl, shift){ // sends a charCode to the window - if(target.browser.mozilla){ // if it's a mozilla browser + if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/chat.js b/tests/frontend/specs/chat.js index ccb8b569..7ebb76fb 100644 --- a/tests/frontend/specs/chat.js +++ b/tests/frontend/specs/chat.js @@ -96,4 +96,35 @@ describe("Chat messages and UI", function(){ done(); }); + + it("makes chat stick to right side of the screen then makes it one step smaller", function(done) { + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + + //click on the settings button to make settings visible + var $settingsButton = chrome$(".buttonicon-settings"); + $settingsButton.click(); + + //get the chat selector + var $stickychatCheckbox = chrome$("#options-stickychat"); + + //select chat always on screen and fire change event + $stickychatCheckbox.attr('selected','selected'); + $stickychatCheckbox.change(); + $stickychatCheckbox.click(); + + //check if chat changed to get the stickychat Class + var $chatbox = chrome$("#chatbox"); + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(true); + + //select chat always on screen and fire change event + chrome$('#titlecross').click(); + + //check if chat changed to remove the stickychat Class + var hasStickyChatClass = $chatbox.hasClass("stickyChat"); + expect(hasStickyChatClass).to.be(false); + + done(); + }); }); diff --git a/tests/frontend/specs/clear_authorship_colors.js b/tests/frontend/specs/clear_authorship_colors.js index 5db35612..1417f63c 100644 --- a/tests/frontend/specs/clear_authorship_colors.js +++ b/tests/frontend/specs/clear_authorship_colors.js @@ -47,6 +47,11 @@ describe("clear authorship colors button", function(){ var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; expect(hasAuthorClass).to.be(false); + setTimeout(function(){ + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + expect(disconnectVisible).to.be(true); + },1000); + done(); }); diff --git a/tests/frontend/specs/font_type.js b/tests/frontend/specs/font_type.js index 25d9df05..41b1de34 100644 --- a/tests/frontend/specs/font_type.js +++ b/tests/frontend/specs/font_type.js @@ -24,7 +24,7 @@ describe("font select", function(){ //check if font changed to monospace var fontFamily = inner$("body").css("font-family").toLowerCase(); - expect(fontFamily).to.be("monospace"); + expect(fontFamily).to.be("courier new"); done(); }); diff --git a/tests/frontend/specs/importexport.js b/tests/frontend/specs/importexport.js new file mode 100644 index 00000000..59607dba --- /dev/null +++ b/tests/frontend/specs/importexport.js @@ -0,0 +1,237 @@ +describe("import functionality", function(){ + beforeEach(function(cb){ + helper.newPad(cb); // creates a new pad + this.timeout(60000); + }); + + function getinnertext(){ + var inner = helper.padInner$ + if(!inner){ + return "" + } + var newtext = "" + inner("div").each(function(line,el){ + newtext += el.innerHTML+"\n" + }) + return newtext + } + function importrequest(data,importurl,type){ + var success; + var error; + var result = $.ajax({ + url: importurl, + type: "post", + processData: false, + async: false, + contentType: 'multipart/form-data; boundary=boundary', + accepts: { + text: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + data: 'Content-Type: multipart/form-data; boundary=--boundary\r\n\r\n--boundary\r\nContent-Disposition: form-data; name="file"; filename="import.'+type+'"\r\nContent-Type: text/plain\r\n\r\n' + data + '\r\n\r\n--boundary', + error: function(res){ + error = res + } + }) + expect(error).to.be(undefined) + return result + } + function exportfunc(link){ + var exportresults = [] + $.ajaxSetup({ + async:false + }) + $.get(link+"/export/html",function(data){ + var start = data.indexOf("<body>") + var end = data.indexOf("</body>") + var html = data.substr(start+6,end-start-6) + exportresults.push(["html",html]) + }) + $.get(link+"/export/txt",function(data){ + exportresults.push(["txt",data]) + }) + return exportresults + } + + it("import a pad with newlines from txt", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var textWithNewLines = 'imported text\nnewline' + importrequest(textWithNewLines,importurl,"txt") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('<span class="">imported text</span>\n<span class="">newline</span>\n<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be("imported text<br>newline<br><br>") + expect(results[1][1]).to.be("imported text\nnewline\n\n") + done() + }) + it("import a pad with newlines from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithNewLines = '<html><body>htmltext<br/>newline</body></html>' + importrequest(htmlWithNewLines,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('<span class="">htmltext</span>\n<span class="">newline</span>\n<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be("htmltext<br>newline<br><br>") + expect(results[1][1]).to.be("htmltext\nnewline\n\n") + done() + }) + it("import a pad with attributes from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithNewLines = '<html><body>htmltext<br/><span class="b s i u"><b><i><s><u>newline</u></s></i></b></body></html>' + importrequest(htmlWithNewLines,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('<span class="">htmltext</span>\n<span class="b i s u"><b><i><s><u>newline</u></s></i></b></span>\n<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('htmltext<br><strong><em><s><u>newline</u></s></em></strong><br><br>') + expect(results[1][1]).to.be('htmltext\nnewline\n\n') + done() + }) + it("import a pad with bullets from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li><li>bullet2 line 2</li></ul></ul></body></html>' + importrequest(htmlWithBullets,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\ +<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><li>bullet2 line 2</li></ul></ul><br>') + expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t* bullet2 line 2\n\n') + done() + }) + it("import a pad with bullets and newlines from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><li>bullet2 line 2</li></ul></ul></body></html>' + importrequest(htmlWithBullets,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\ +<br>\n\ +<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n\ +<br>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 2</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul class="bullet"><li>bullet2 line 2</li></ul></ul><br>') + expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t* bullet2 line 2\n\n') + done() + }) + it("import a pad with bullets and newlines and attributes from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li><span class="b s i u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>bullet4 line 2 bs</s></b></span></li><li><span class="u"><u>bullet4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li></ul></ul></ul></ul></body></html>' + importrequest(htmlWithBullets,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\<br>\n\ +<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n\ +<ul class="list-bullet4"><li><span class="b i s u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li></ul>\n\ +<ul class="list-bullet4"><li><span class="b s"><b><s>bullet4 line 2 bs</s></b></span></li></ul>\n\ +<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li></ul></ul></ul></ul><br>') + expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\n') + done() + }) + it("import a pad with nested bullets from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul></body></html>' + importrequest(htmlWithBullets,importurl,"html") + var oldtext=getinnertext() + helper.waitFor(function(){ + return oldtext != getinnertext() +// return expect(getinnertext()).to.be('\ +//<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\ +//<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\ +//<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n\ +//<ul class="list-bullet4"><li><span class="">bullet4 line 2</span></li></ul>\n\ +//<ul class="list-bullet4"><li><span class="">bullet4 line 2</span></li></ul>\n\ +//<ul class="list-bullet4"><li><span class="">bullet4 line 2</span></li></ul>\n\ +//<br>\n') + }) + + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li><ul><ul class="bullet"><li>bullet4 line 2</li><li>bullet4 line 2</li><li>bullet4 line 2</li></ul><li>bullet3 line 1</li></ul></ul><li>bullet2 line 1</li></ul><br>') + expect(results[1][1]).to.be('\t* bullet line 1\n\t* bullet line 2\n\t\t* bullet2 line 1\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t\t* bullet4 line 2\n\t\t\t* bullet3 line 1\n\t* bullet2 line 1\n\n') + done() + }) + it("import a pad with 8 levels of bullets and newlines and attributes from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ul class="list-bullet1"><li>bullet line 1</li></ul><br/><ul class="list-bullet1"><li>bullet line 2</li><ul class="list-bullet2"><li>bullet2 line 1</li></ul></ul><br/><ul class="list-bullet1"><ul class="list-bullet2"><ul class="list-bullet3"><ul class="list-bullet4"><li><span class="b s i u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>bullet4 line 2 bs</s></b></span></li><li><span class="u"><u>bullet4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li><ul class="list-bullet5"><ul class="list-bullet6"><ul class="list-bullet7"><ul class="list-bullet8"><li><span class="">foo</span></li><li><span class="b s"><b><s>foobar bs</b></s></span></li></ul></ul></ul></ul><ul class="list-bullet5"><li>foobar</li></ul></ul></ul></ul></body></html>' + importrequest(htmlWithBullets,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-bullet1"><li><span class="">bullet line 1</span></li></ul>\n\<br>\n\ +<ul class="list-bullet1"><li><span class="">bullet line 2</span></li></ul>\n\ +<ul class="list-bullet2"><li><span class="">bullet2 line 1</span></li></ul>\n<br>\n\ +<ul class="list-bullet4"><li><span class="b i s u"><b><i><s><u>bullet4 line 2 bisu</u></s></i></b></span></li></ul>\n\ +<ul class="list-bullet4"><li><span class="b s"><b><s>bullet4 line 2 bs</s></b></span></li></ul>\n\ +<ul class="list-bullet4"><li><span class="u"><u>bullet4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\ +<ul class="list-bullet8"><li><span class="">foo</span></li></ul>\n\ +<ul class="list-bullet8"><li><span class="b s"><b><s>foobar bs</s></b></span></li></ul>\n\ +<ul class="list-bullet5"><li><span class="">foobar</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="bullet"><li>bullet line 1</li></ul><br><ul class="bullet"><li>bullet line 2</li><ul class="bullet"><li>bullet2 line 1</li></ul></ul><br><ul><ul><ul><ul class="bullet"><li><strong><em><s><u>bullet4 line 2 bisu</u></s></em></strong></li><li><strong><s>bullet4 line 2 bs</s></strong></li><li><u>bullet4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="bullet"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>') + expect(results[1][1]).to.be('\t* bullet line 1\n\n\t* bullet line 2\n\t\t* bullet2 line 1\n\n\t\t\t\t* bullet4 line 2 bisu\n\t\t\t\t* bullet4 line 2 bs\n\t\t\t\t* bullet4 line 2 uuis\n\t\t\t\t\t\t\t\t* foo\n\t\t\t\t\t\t\t\t* foobar bs\n\t\t\t\t\t* foobar\n\n') + done() + }) + + xit("import a pad with ordered lists from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol></body></html>' + importrequest(htmlWithBullets,importurl,"html") + -console.error(getinnertext()) + expect(getinnertext()).to.be('\ +<ol class="list-number1" start="1"><li><span class="">number 1 line 1</span></li></ol>\n\ +<ol class="list-number1" start="2"><li><span class="">number 2 line 2</span></li></ol>\n\ +<br>\n') + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ol class="list-number1" start="1"><li>number 1 line 1</li></ol><ol class="list-number1" start="2"><li>number 2 line 2</li></ol>') + expect(results[1][1]).to.be('') + done() + }) + xit("import a pad with ordered lists and newlines from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li>number 9 line 1</li></ol><br/><ol class="list-number1" start="2"><li>number 10 line 2</li><ol class="list-number2"><li>number 2 times line 1</li></ol></ol><br/><ol class="list-bullet1"><ol class="list-number2"><li>number 2 times line 2</li></ol></ol></body></html>' + importrequest(htmlWithBullets,importurl,"html") + expect(getinnertext()).to.be('\ +<ol class="list-number1" start="1"><li><span class="">number 9 line 1</span></li></ol>\n\ +<br>\n\ +<ol class="list-number1" start="2"><li><span class="">number 10 line 2</span></li></ol>\n\ +<ol class="list-number2"><li><span class="">number 2 times line 1</span></li></ol>\n\ +<br>\n\ +<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n\ +<br>\n') + var results = exportfunc(helper.padChrome$.window.location.href) + console.error(results) + done() + }) + xit("import a pad with nested ordered lists and attributes and newlines from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithBullets = '<html><body><ol class="list-number1" start="1"><li><span class="b s i u"><b><i><s><u>bold strikethrough italics underline</u></s><i/></b></span> line <span class="b"><b>1bold</b></span></li></ol><br/><span class="i"><i><ol class="list-number1" start="2"><li>number 10 line 2</li><ol class="list-number2"><li>number 2 times line 1</li></ol></ol></i></span><br/><ol class="list-bullet1"><ol class="list-number2"><li>number 2 times line 2</li></ol></ol></body></html>' + importrequest(htmlWithBullets,importurl,"html") + expect(getinnertext()).to.be('\ +<ol class="list-number1"><li><span class="b i s u"><b><i><s><u>bold strikethrough italics underline</u></s></i></b></span><span class=""> line </span><span class="b"><b>1bold</b></span></li></ol>\n\ +<br>\n\ +<ol class="list-number1"><li><span class="i"><i>number 10 line 2</i></span></li></ol>\n\ +<ol class="list-number2"><li><span class="i"><i>number 2 times line 1</i></span></li></ol>\n\ +<br>\n\ +<ol class="list-number2"><li><span class="">number 2 times line 2</span></li></ol>\n\ +<br>\n') + var results = exportfunc(helper.padChrome$.window.location.href) + console.error(results) + done() + }) +}) diff --git a/tests/frontend/specs/importindents.js b/tests/frontend/specs/importindents.js new file mode 100644 index 00000000..db2b33b0 --- /dev/null +++ b/tests/frontend/specs/importindents.js @@ -0,0 +1,111 @@ +describe("import indents functionality", function(){ + beforeEach(function(cb){ + helper.newPad(cb); // creates a new pad + this.timeout(60000); + }); + + function getinnertext(){ + var inner = helper.padInner$ + var newtext = "" + inner("div").each(function(line,el){ + newtext += el.innerHTML+"\n" + }) + return newtext + } + function importrequest(data,importurl,type){ + var success; + var error; + var result = $.ajax({ + url: importurl, + type: "post", + processData: false, + async: false, + contentType: 'multipart/form-data; boundary=boundary', + accepts: { + text: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + }, + data: 'Content-Type: multipart/form-data; boundary=--boundary\r\n\r\n--boundary\r\nContent-Disposition: form-data; name="file"; filename="import.'+type+'"\r\nContent-Type: text/plain\r\n\r\n' + data + '\r\n\r\n--boundary', + error: function(res){ + error = res + } + }) + expect(error).to.be(undefined) + return result + } + function exportfunc(link){ + var exportresults = [] + $.ajaxSetup({ + async:false + }) + $.get(link+"/export/html",function(data){ + var start = data.indexOf("<body>") + var end = data.indexOf("</body>") + var html = data.substr(start+6,end-start-6) + exportresults.push(["html",html]) + }) + $.get(link+"/export/txt",function(data){ + exportresults.push(["txt",data]) + }) + return exportresults + } + + it("import a pad with indents from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li><li>indent line 2</li><ul class="list-indent2"><li>indent2 line 1</li><li>indent2 line 2</li></ul></ul></body></html>' + importrequest(htmlWithIndents,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\ +<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n\ +<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n\ +<ul class="list-indent2"><li><span class="">indent2 line 2</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li><li>indent2 line 2</li></ul></ul><br>') + expect(results[1][1]).to.be('\tindent line 1\n\tindent line 2\n\t\tindent2 line 1\n\t\tindent2 line 2\n\n') + done() + }) + + it("import a pad with indented lists and newlines from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent 1 line 2</li><ul class="list-indent2"><li>indent 2 times line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><li>indent 2 times line 2</li></ul></ul></body></html>' + importrequest(htmlWithIndents,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\ +<br>\n\ +<ul class="list-indent1"><li><span class="">indent 1 line 2</span></li></ul>\n\ +<ul class="list-indent2"><li><span class="">indent 2 times line 1</span></li></ul>\n\ +<br>\n\ +<ul class="list-indent2"><li><span class="">indent 2 times line 2</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent 1 line 2</li><ul class="indent"><li>indent 2 times line 1</li></ul></ul><br><ul><ul class="indent"><li>indent 2 times line 2</li></ul></ul><br>') + expect(results[1][1]).to.be('\tindent line 1\n\n\tindent 1 line 2\n\t\tindent 2 times line 1\n\n\t\tindent 2 times line 2\n\n') + done() + }) + it("import a pad with 8 levels of indents and newlines and attributes from html", function(done){ + var importurl = helper.padChrome$.window.location.href+'/import' + var htmlWithIndents = '<html><body><ul class="list-indent1"><li>indent line 1</li></ul><br/><ul class="list-indent1"><li>indent line 2</li><ul class="list-indent2"><li>indent2 line 1</li></ul></ul><br/><ul class="list-indent1"><ul class="list-indent2"><ul class="list-indent3"><ul class="list-indent4"><li><span class="b s i u"><b><i><s><u>indent4 line 2 bisu</u></s></i></b></span></li><li><span class="b s "><b><s>indent4 line 2 bs</s></b></span></li><li><span class="u"><u>indent4 line 2 u</u></span><span class="u i s"><i><s><u>uis</u></s></i></span></li><ul class="list-indent5"><ul class="list-indent6"><ul class="list-indent7"><ul class="list-indent8"><li><span class="">foo</span></li><li><span class="b s"><b><s>foobar bs</b></s></span></li></ul></ul></ul></ul><ul class="list-indent5"><li>foobar</li></ul></ul></ul></ul></body></html>' + importrequest(htmlWithIndents,importurl,"html") + helper.waitFor(function(){ + return expect(getinnertext()).to.be('\ +<ul class="list-indent1"><li><span class="">indent line 1</span></li></ul>\n\<br>\n\ +<ul class="list-indent1"><li><span class="">indent line 2</span></li></ul>\n\ +<ul class="list-indent2"><li><span class="">indent2 line 1</span></li></ul>\n<br>\n\ +<ul class="list-indent4"><li><span class="b i s u"><b><i><s><u>indent4 line 2 bisu</u></s></i></b></span></li></ul>\n\ +<ul class="list-indent4"><li><span class="b s"><b><s>indent4 line 2 bs</s></b></span></li></ul>\n\ +<ul class="list-indent4"><li><span class="u"><u>indent4 line 2 u</u></span><span class="i s u"><i><s><u>uis</u></s></i></span></li></ul>\n\ +<ul class="list-indent8"><li><span class="">foo</span></li></ul>\n\ +<ul class="list-indent8"><li><span class="b s"><b><s>foobar bs</s></b></span></li></ul>\n\ +<ul class="list-indent5"><li><span class="">foobar</span></li></ul>\n\ +<br>\n') + }) + var results = exportfunc(helper.padChrome$.window.location.href) + expect(results[0][1]).to.be('<ul class="indent"><li>indent line 1</li></ul><br><ul class="indent"><li>indent line 2</li><ul class="indent"><li>indent2 line 1</li></ul></ul><br><ul><ul><ul><ul class="indent"><li><strong><em><s><u>indent4 line 2 bisu</u></s></em></strong></li><li><strong><s>indent4 line 2 bs</s></strong></li><li><u>indent4 line 2 u<em><s>uis</s></em></u></li><ul><ul><ul><ul class="indent"><li>foo</li><li><strong><s>foobar bs</s></strong></li></ul></ul></ul><li>foobar</li></ul></ul></ul></ul></ul><br>') + expect(results[1][1]).to.be('\tindent line 1\n\n\tindent line 2\n\t\tindent2 line 1\n\n\t\t\t\tindent4 line 2 bisu\n\t\t\t\tindent4 line 2 bs\n\t\t\t\tindent4 line 2 uuis\n\t\t\t\t\t\t\t\tfoo\n\t\t\t\t\t\t\t\tfoobar bs\n\t\t\t\t\tfoobar\n\n') + done() + }) +}) diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 3d14a7a8..8e851d87 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,7 +15,7 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$.browser.mozilla){ // if it's a mozilla browser + if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/italic.js b/tests/frontend/specs/italic.js index 29dbae59..bf7f2bc6 100644 --- a/tests/frontend/specs/italic.js +++ b/tests/frontend/specs/italic.js @@ -44,7 +44,7 @@ describe("italic some text", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$.browser.mozilla){ // if it's a mozilla browser + if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/redo.js b/tests/frontend/specs/redo.js index c2f8a95a..caa32fee 100644 --- a/tests/frontend/specs/redo.js +++ b/tests/frontend/specs/redo.js @@ -47,7 +47,7 @@ describe("undo button then redo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(inner$.browser.mozilla){ // if it's a mozilla browser + if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/responsiveness.js b/tests/frontend/specs/responsiveness.js index 44bdd611..ff7dace1 100644 --- a/tests/frontend/specs/responsiveness.js +++ b/tests/frontend/specs/responsiveness.js @@ -19,7 +19,7 @@ describe('Responsiveness of Editor', function() { helper.newPad(cb); this.timeout(6000); }); - it('Fast response to keypress in pad with large amount of contents', function(done) { + xit('Fast response to keypress in pad with large amount of contents', function(done) { var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; var chars = '0000000000'; // row of placeholder chars diff --git a/tests/frontend/specs/timeslider_revisions.js b/tests/frontend/specs/timeslider_revisions.js index 67938134..2afd2e9d 100644 --- a/tests/frontend/specs/timeslider_revisions.js +++ b/tests/frontend/specs/timeslider_revisions.js @@ -4,7 +4,8 @@ describe("timeslider", function(){ helper.newPad(cb); this.timeout(6000); }); - it("loads adds a hundred revisions", function(done) { + + it("loads adds a hundred revisions", function(done) { // passes var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -18,6 +19,7 @@ describe("timeslider", function(){ inner$("div").first().sendkeys('a'); }, timePerRev*i); } + chrome$('.buttonicon-savedRevision').click(); setTimeout(function() { // go to timeslider @@ -50,13 +52,18 @@ describe("timeslider", function(){ setTimeout(function() { //make sure the text has changed expect( timeslider$('#padcontent').text() ).not.to.eql( latestContents ); + var starIsVisible = timeslider$('.star').is(":visible"); + expect( starIsVisible ).to.eql( true ); done(); }, 1000); }, 6000); }, revs*timePerRev); }); - it("changes the url when clicking on the timeslider", function(done) { + + + // Disabled as jquery trigger no longer works properly + xit("changes the url when clicking on the timeslider", function(done) { var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; @@ -80,7 +87,6 @@ describe("timeslider", function(){ var $sliderBar = timeslider$('#ui-slider-bar'); var latestContents = timeslider$('#padcontent').text(); - var oldUrl = $('#iframe-container iframe')[0].contentWindow.location.hash; // Click somewhere on the timeslider @@ -111,6 +117,7 @@ describe("timeslider", function(){ var oldLength = inner$('body').text().length + newLines / 2; expect( oldLength ).to.not.eql( 0 ); inner$("div").first().sendkeys('a'); + var timeslider$; // wait for our additional revision to be added helper.waitFor(function(){ @@ -140,6 +147,7 @@ describe("timeslider", function(){ }); }); }); + it("checks the export url", function(done) { var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; diff --git a/tests/frontend/specs/undo.js b/tests/frontend/specs/undo.js index 0c58c9b8..b8b7c785 100644 --- a/tests/frontend/specs/undo.js +++ b/tests/frontend/specs/undo.js @@ -44,7 +44,7 @@ describe("undo button", function(){ var modifiedValue = $firstTextElement.text(); // get the modified value expect(modifiedValue).not.to.be(originalValue); // expect the value to change - if(inner$.browser.mozilla){ // if it's a mozilla browser + if(inner$(window)[0].bowser.firefox){ // if it's a mozilla browser var evtType = "keypress"; }else{ var evtType = "keydown"; diff --git a/tests/frontend/specs/unordered_list.js b/tests/frontend/specs/unordered_list.js new file mode 100644 index 00000000..4ea77b8a --- /dev/null +++ b/tests/frontend/specs/unordered_list.js @@ -0,0 +1,35 @@ +describe("assign unordered list", function(){ + //create a new pad before each test run + beforeEach(function(cb){ + helper.newPad(cb); + this.timeout(60000); + }); + + it("insert unordered list text then removes by outdent", function(done){ + var inner$ = helper.padInner$; + var chrome$ = helper.padChrome$; + var originalText = inner$("div").first().text(); + + var $insertunorderedlistButton = chrome$(".buttonicon-insertunorderedlist"); + $insertunorderedlistButton.click(); + + helper.waitFor(function(){ + var newText = inner$("div").first().text(); + if(newText === originalText){ + return inner$("div").first().find("ul li").length === 1; + } + }).done(function(){ + + // remove indentation by bullet and ensure text string remains the same + chrome$(".buttonicon-outdent").click(); + helper.waitFor(function(){ + var newText = inner$("div").first().text(); + return (newText === originalText); + }).done(function(){ + done(); + }); + + }); + }); + +}); |