diff --git a/.travis.yml b/.travis.yml index 908aca419..72ae8027b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "0.10" + - "lts/*" install: - "bin/installDeps.sh" - "export GIT_HASH=$(git rev-parse --verify --short HEAD)" diff --git a/CHANGELOG.md b/CHANGELOG.md index fc6688dc5..5f95accf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 1.7.5 +* FEATURE: introduced support for multiple skins. See http://etherpad.org/doc/v1.7.5/#index_skins +* FEATURE: added a new, optional skin. It can be activated choosing `skinName: "colibris"` in `settings.json` +* FEATURE: allow file import using LibreOffice +* SECURITY: updated many dependencies. No known high or moderate risk dependencies remain. +* SECURITY: generate better random pad names +* FIX: don't nuke all installed plugins if `npm install` fails +* FIX: improved LibreOffice export +* FIX: allow debug mode on node versions >= 6.3 +* MINOR: started making Etherpad less dependent on current working directory when running +* MINOR: started simplifying the code structure, flattening complex conditions +* MINOR: simplified a bit the startup scripts + +*UPGRADE NOTES*: if you have custom files in `src/static/custom`, save them +somewhere else, revert the directory contents, update to Etherpad 1.7.5, and +finally put them back in their new location, uder `src/static/skins/no-skin`. + # 1.7.0 * FIX: `getLineHTMLForExport()` no longer produces multiple copies of a line. **WARNING**: this could potentially break some plugins * FIX: authorship of bullet points no longer changes when a second author edits them diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a734643b7..11cf2f818 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,7 +119,7 @@ Back-end tests can be run from the `src` directory, via `npm test`. ## Things you can help with Etherpad is much more than software. So if you aren't a developer then worry not, there is still a LOT you can do! A big part of what we do is community engagement. You can help in the following ways - * Triage bugs (applying labels) and confirming their existance + * Triage bugs (applying labels) and confirming their existence * Testing fixes (simply applying them and seeing if it fixes your issue or not) - Some git experience required * Notifying large site admins of new releases * Writing Changelogs for releases diff --git a/README.md b/README.md index f8c4f1227..41ac95848 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ![Demo Etherpad Animated Jif](https://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad") # About -Etherpad is a really-real time collaborative editor scalable to thousands of simultanious real time users. Unlike all other collaborative tools Etherpad provides full fidelity data export and portability making it fully GDPR compliant. +Etherpad is a really-real time collaborative editor scalable to thousands of simultaneous real time users. Unlike all other collaborative tools Etherpad provides full fidelity data export and portability making it fully GDPR compliant. -**[Try it out](http://beta.etherpad.org)** +**[Try it out](https://beta.etherpad.org)** # Installation @@ -15,26 +15,19 @@ Etherpad is a really-real time collaborative editor scalable to thousands of sim ``` curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash - sudo apt-get install -y nodejs -git clone https://github.com/ether/etherpad-lite.git && cd etherpad-lite && bin/run.sh +git clone --branch master https://github.com/ether/etherpad-lite.git && cd etherpad-lite && bin/run.sh ``` ## GNU/Linux and other UNIX-like systems -You'll need gzip, git, curl, libssl develop libraries, python and gcc. -- *For Debian/Ubuntu*: `apt 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, curl, git (optional)` - -Additionally, you'll need [node.js](https://nodejs.org) installed (minimum required Node version: **6.9.0**). -Ideally, the latest stable version is preferred. Please note that the packages offered on some operating systems are outdated. In those cases, we recommend installing nodejs from official archives or compiling it from source (avoiding yum/apt). +You'll need git and [node.js](https://nodejs.org) installed (minimum required Node version: **6.9.0**, preferred: >= **8.9**). **As any user (we recommend creating a separate user called etherpad):** -1. Move to a folder where you want to install Etherpad. Clone the git repository: `git clone git://github.com/ether/etherpad-lite.git` +1. Move to a folder where you want to install Etherpad. Clone the git repository: `git clone --branch master git://github.com/ether/etherpad-lite.git` 2. Change into the new directory containing the cloned source code: `cd etherpad-lite` +3. run `bin/run.sh` and open in your browser. -Now, run `bin/run.sh` and open in your browser. - -Update to the latest version with `git pull origin`. The next start with `bin/run.sh` will update the dependencies. +To update to the latest released version, execute `git pull origin`. The next start with `bin/run.sh` will update the dependencies. [Next steps](#next-steps). @@ -48,12 +41,12 @@ This package works out of the box on any windows machine, but it's not very usef Now, run `start.bat` and open in your browser. You like it? [Next steps](#next-steps). -### Fancy install +### Manually install on Windows You'll need [node.js](https://nodejs.org) and (optionally, though recommended) git. 1. Grab the source, either - download - - or `git clone https://github.com/ether/etherpad-lite.git` (for this you need git, obviously) + - or `git clone --branch master https://github.com/ether/etherpad-lite.git` (for this you need git, obviously) 2. start `bin\installOnWindows.bat` Now, run `start.bat` and open in your browser. @@ -114,9 +107,9 @@ Etherpad is written in JavaScript on both the server and client so it's easy for # HTTP API Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) -that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. +that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. -# jQuery plugin +# jQuery plugin There is a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. # Plugin Framework diff --git a/bin/checkAllPads.js b/bin/checkAllPads.js index 90cb15276..a94c38d23 100644 --- a/bin/checkAllPads.js +++ b/bin/checkAllPads.js @@ -8,7 +8,7 @@ if(process.argv.length != 2) process.exit(1); } -//initalize the variables +//initialize the variables var db, settings, padManager; var npm = require("../src/node_modules/npm"); var async = require("../src/node_modules/async"); @@ -25,7 +25,7 @@ async.series([ settings = require('../src/node/utils/Settings'); db = require('../src/node/db/DB'); - //initalize the database + //initialize the database db.init(callback); }, //load pads diff --git a/bin/cleanRun.sh b/bin/cleanRun.sh index 57325dd23..288f5a040 100755 --- a/bin/cleanRun.sh +++ b/bin/cleanRun.sh @@ -38,4 +38,4 @@ bin/installDeps.sh $* || exit 1 echo "Started Etherpad..." SCRIPTPATH=`pwd -P` -node "${$SCRIPTPATH}/node_modules/ep_etherpad-lite/node/server.js" $* +node "${SCRIPTPATH}/node_modules/ep_etherpad-lite/node/server.js" $* diff --git a/bin/convert.js b/bin/convert.js index 88af1ad58..757602c29 100644 --- a/bin/convert.js +++ b/bin/convert.js @@ -422,7 +422,7 @@ function convertPad(padId, callback) /** * This parses a Page like Etherpad uses them in the databases - * The offsets descripes the length of a unit in the page, the data are + * The offsets describes the length of a unit in the page, the data are * all values behind each other */ function parsePage(array, pageStart, offsets, data, json) diff --git a/bin/debugRun.sh b/bin/debugRun.sh index b42112f7f..e187a0f0c 100755 --- a/bin/debugRun.sh +++ b/bin/debugRun.sh @@ -11,18 +11,10 @@ fi #Prepare the environment bin/installDeps.sh || exit 1 -hash node-inspector > /dev/null 2>&1 || { - echo "You need to install node-inspector to run the tests!" >&2 - echo "You can install it with npm" >&2 - echo "Run: npm install -g node-inspector" >&2 - exit 1 -} +echo "If you are new to debugging Node.js with Chrome DevTools, take a look at this page:" +echo "https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27" +echo "Open 'chrome://inspect' on Chrome to start debugging." -node-inspector & - -echo "If you are new to node-inspector, take a look at this video: https://youtu.be/AOnK3NVnxL8" - -node --debug node_modules/ep_etherpad-lite/node/server.js $* - -#Kill node-inspector before ending -kill $! +#Use 0.0.0.0 to allow external connections to the debugger +#(ex: running Etherpad on a docker container). Use default port # (9229) +node --inspect=0.0.0.0:9229 node_modules/ep_etherpad-lite/node/server.js $* diff --git a/bin/dirty-db-cleaner.py b/bin/dirty-db-cleaner.py index d3e49a0d2..2c7128588 100755 --- a/bin/dirty-db-cleaner.py +++ b/bin/dirty-db-cleaner.py @@ -1,4 +1,4 @@ -#!/usr/bin/env PYTHONUNBUFFERED=1 python2 +#!/usr/bin/env PYTHONUNBUFFERED=1 python # # Created by Bjarni R. Einarsson, placed in the public domain. Go wild! # @@ -12,34 +12,37 @@ try: 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 + 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 + print('Reading %s' % dirtydb_input) for line in fd: lines += 1 - data = json.loads(line) - dirtydb[data['key']] = line + try: + data = json.loads(line) + dirtydb[data['key']] = line + except: + print("Skipping invalid JSON!") if lines % 10000 == 0: sys.stderr.write('.') -print -print 'OK, found %d unique keys in %d lines' % (len(dirtydb), lines) +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(): + for data in list(dirtydb.values()): fd.write(data) -print 'Wrote data to %s. All done!' % dirtydb_output +print('Wrote data to %s. All done!' % dirtydb_output) diff --git a/bin/installDeps.sh b/bin/installDeps.sh index 47373a5f6..a56031217 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -56,20 +56,6 @@ if [ -d "../bin" ]; then cd "../" fi -#Is gnu-grep (ggrep) installed on SunOS (Solaris) -if [ $(uname) = "SunOS" ]; then - hash ggrep > /dev/null 2>&1 || { - echo "Please install ggrep (pkg install gnu-grep)" >&2 - exit 1 - } -fi - -#Is curl installed? -hash curl > /dev/null 2>&1 || { - echo "Please install curl" >&2 - exit 1 -} - #Is node installed? #Not checking io.js, default installation creates a symbolic link to node hash node > /dev/null 2>&1 || { @@ -116,44 +102,12 @@ echo "Ensure that all dependencies are up to date... If this is the first time cd ep_etherpad-lite npm install --no-save --loglevel warn ) || { - rm -rf node_modules + rm -rf src/node_modules exit 1 } -echo "Ensure jQuery is downloaded and up to date..." -DOWNLOAD_JQUERY="true" -NEEDED_VERSION="1.9.1" -if [ -f "src/static/js/jquery.js" ]; then - if [ $(uname) = "SunOS" ]; then - VERSION=$(head -n 3 src/static/js/jquery.js | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?") - else - 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 - DOWNLOAD_JQUERY="false" - fi -fi - -if [ $DOWNLOAD_JQUERY = "true" ]; then - curl -lo src/static/js/jquery.js https://code.jquery.com/jquery-$NEEDED_VERSION.js || exit 1 -fi - #Remove all minified data to force node creating it new echo "Clearing minified cache..." rm -f var/minified* -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 -done - exit 0 diff --git a/bin/installOnWindows.bat b/bin/installOnWindows.bat index 5ba057365..75982aaff 100644 --- a/bin/installOnWindows.bat +++ b/bin/installOnWindows.bat @@ -18,14 +18,6 @@ cmd /C npm install --loglevel warn || exit /B 1 cd /D "%~dp0\.." -echo _ -echo Copying custom templates... -set custom_dir=node_modules\ep_etherpad-lite\static\custom -FOR %%f IN (index pad timeslider) DO ( - if NOT EXIST "%custom_dir%\%%f.js" copy "%custom_dir%\js.template" "%custom_dir%\%%f.js" - if NOT EXIST "%custom_dir%\%%f.css" copy "%custom_dir%\css.template" "%custom_dir%\%%f.css" -) - echo _ echo Clearing cache... del /S var\minified* @@ -39,4 +31,4 @@ IF NOT EXIST settings.json ( ) echo _ -echo Installed Etherpad! To run Etherpad type start.bat \ No newline at end of file +echo Installed Etherpad! To run Etherpad type start.bat diff --git a/bin/migrateDirtyDBtoRealDB.js b/bin/migrateDirtyDBtoRealDB.js index c616714a0..086d92b7b 100644 --- a/bin/migrateDirtyDBtoRealDB.js +++ b/bin/migrateDirtyDBtoRealDB.js @@ -6,12 +6,17 @@ require("ep_etherpad-lite/node_modules/npm").load({}, function(er,npm) { // to work with a real database. Please make a backup of your dirty.db // file before using this script, just to be safe. + // It might be necessary to run the script using more memory: + // `node --max-old-space-size=4096 bin/migrateDirtyDBtoRealDB.js` + + var settings = require("ep_etherpad-lite/node/utils/Settings"); var dirty = require("../src/node_modules/dirty")('var/dirty.db'); var ueberDB = require("../src/node_modules/ueberdb2"); var log4js = require("../src/node_modules/log4js"); var dbWrapperSettings = { "cache": "0", // The cache slows things down when you're mostly writing. + "writeInterval": 0 // Write directly to the database, don't buffer }; var db = new ueberDB.database(settings.dbType, settings.dbSettings, dbWrapperSettings, log4js.getLogger("ueberDB")); diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 9af035698..f30578d7e 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -356,7 +356,7 @@ Called from: src/static/js/ace2_inner.js Things in context: -1. dynamicCSS - css manger for inner ace +1. dynamicCSS - css manager for inner ace 2. outerDynamicCSS - css manager for outer ace 3. parentDynamicCSS - css manager for parent document 4. info - author style info diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 0baae7277..59008743a 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -379,7 +379,7 @@ Restores revision from past as new changeset returns * a part of the chat history, when `start` and `end` are given -* the whole chat histroy, when no extra parameters are given +* the whole chat history, when no extra parameters are given *Example returns:* diff --git a/doc/custom_static.md b/doc/custom_static.md deleted file mode 100644 index 7bb290094..000000000 --- a/doc/custom_static.md +++ /dev/null @@ -1,11 +0,0 @@ -# Custom static files -Etherpad allows you to include your own static files in the browser, by modifying the files in `static/custom`. - -* `index.js` Javascript that'll be run in `/` -* `index.css` Stylesheet affecting `/` -* `pad.js` Javascript that'll be run in `/p/:padid` -* `pad.css` Stylesheet affecting `/p/:padid` -* `timeslider.js` Javascript that'll be run in `/p/:padid/timeslider` -* `timeslider.css` Stylesheet affecting `/p/:padid/timeslider` -* `favicon.ico` Overrides the default favicon. -* `robots.txt` Overrides the default `robots.txt`. \ No newline at end of file diff --git a/doc/database.md b/doc/database.md index 9f6126d63..2455cdc22 100644 --- a/doc/database.md +++ b/doc/database.md @@ -13,7 +13,7 @@ Contains all information about pads * **head** - the number of the latest revision * **chatHead** - the number of the latest chat entry * **public** - flag that disables security for this pad -* **passwordHash** - string that contains a bcrypt hashed password for this pad +* **passwordHash** - string that contains a salted sha512 sum of this pad's password ### pad:$PADID:revs:$REVNUM Saves a revision $REVNUM of pad $PADID diff --git a/doc/easysync/easysync-full-description.pdf b/doc/easysync/easysync-full-description.pdf index 170b6ccd4..c55216604 100644 Binary files a/doc/easysync/easysync-full-description.pdf and b/doc/easysync/easysync-full-description.pdf differ diff --git a/doc/index.md b/doc/index.md index 09553f686..5b93bac68 100644 --- a/doc/index.md +++ b/doc/index.md @@ -1,7 +1,7 @@ @include documentation @include stats @include localization -@include custom_static +@include skins @include api/api @include plugins @include database diff --git a/doc/plugins.md b/doc/plugins.md index 0dccbe848..5cb8d0ebf 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -47,6 +47,22 @@ You can omit the `FUNCTIONNAME` part, if the exported function has got the same ### Client hooks and server hooks There are server hooks, which will be executed on the server (e.g. `expressCreateServer`), and there are client hooks, which are executed on the client (e.g. `acePopulateDomLine`). Be sure to not make assumptions about the environment your code is running in, e.g. don't try to access `process`, if you know your code will be run on the client, and likewise, don't try to access `window` on the server... +### Styling +When you install a client-side plugin (e.g. one that implements at least one client-side hook), the plugin name is added to the `class` attribute of the div `#editorcontainerbox` in the main window. +This gives you the opportunity of tuning the appearance of the main UI in your plugin. + +For example, this is the markup with no plugins installed: +```html +
+``` + +and this is the contents after installing `someplugin`: +```html +
+``` + +This feature was introduced in Etherpad **1.8**. + ### Parts As your plugins become more and more complex, you will find yourself in the need to manage dependencies between plugins. E.g. you want the hooks of a certain plugin to be executed before (or after) yours. You can also manage these dependencies in your plugin definition file `ep.json`: diff --git a/doc/skins.md b/doc/skins.md new file mode 100644 index 000000000..90a786f84 --- /dev/null +++ b/doc/skins.md @@ -0,0 +1,19 @@ +# Skins +You can customize Etherpad appearance using skins. +A skin is a directory located under `static/skins/`, with the following contents: + +* `index.js`: javascript that will be run in `/` +* `index.css`: stylesheet affecting `/` +* `pad.js`: javascript that will be run in `/p/:padid` +* `pad.css`: stylesheet affecting `/p/:padid` +* `timeslider.js`: javascript that will be run in `/p/:padid/timeslider` +* `timeslider.css`: stylesheet affecting `/p/:padid/timeslider` +* `favicon.ico`: overrides the default favicon +* `robots.txt`: overrides the default `robots.txt` + +You can choose a skin changing the parameter `skinName` in `settings.json`. + +Since Etherpad **1.7.5**, two skins are included: + +* `no-skin`: an empty skin, leaving the default Etherpad appearance unchanged, that you can use as a guidance to develop your own. +* `colibris`: a new, experimental skin, that will become the default in Etherpad 2.0. diff --git a/settings.json.template b/settings.json.template index 684e9d598..a89cc4247 100644 --- a/settings.json.template +++ b/settings.json.template @@ -18,6 +18,19 @@ */ "favicon": "favicon.ico", + /* + * Skin name. + * + * Its value has to be an existing directory under src/static/skins. + * You can write your own, or use one of the included ones: + * + * - "no-skin": an empty skin (default). This yields the unmodified, + * traditional Etherpad theme. + * - "colibris": the new experimental skin (since Etherpad 1.8), candidate to + * become the default in Etherpad 2.0 + */ + "skinName": "no-skin", + /* * IP and port which etherpad should bind at */ @@ -56,18 +69,16 @@ * You shouldn't use "dirty" for for anything else than testing or * development. * - * For a complete list of the supported drivers, please consult: + * + * Database specific settings are dependent on dbType, and go in dbSettings. + * Remember that since Etherpad 1.6.0 you can also store these informations in + * credentials.json. + * + * For a complete list of the supported drivers, please refer to: * https://www.npmjs.com/package/ueberdb2 */ "dbType" : "dirty", - - /* - * Database specific settings (dependent on dbType). - * - * Remember that since Etherpad 1.6.0 you can also store these informations in - * credentials.json. - */ "dbSettings" : { "filename" : "var/dirty.db" }, diff --git a/src/locales/ar.json b/src/locales/ar.json index 39ec236ce..0237830f1 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -61,7 +61,7 @@ "pad.importExport.exportword": "مايكروسوفت وورد", "pad.importExport.exportpdf": "صيغة المستندات المحمولة", "pad.importExport.exportopen": "ODF (نسق المستند المفتوح)", - "pad.importExport.abiword.innerHTML": "لايمكنك الاستيراد إلا من نص عادي أو من تنسيقات إتش تي إم إل. للحصول على المزيد من ميزات الاستيراد المتقدمة، يرجى تثبيت أبيورد .", + "pad.importExport.abiword.innerHTML": "لا يمكنك الاستيراد إلا من نص عادي أو من تنسيقات HTML. للحصول على المزيد من ميزات الاستيراد المتقدمة، يرجى تثبيت AbiWord.", "pad.modals.connected": "متصل.", "pad.modals.reconnecting": "إعادة الاتصال ببادك", "pad.modals.forcereconnect": "فرض إعادة الاتصال", diff --git a/src/locales/bg.json b/src/locales/bg.json index 52e424c37..0b29b0e77 100644 --- a/src/locales/bg.json +++ b/src/locales/bg.json @@ -19,7 +19,7 @@ "pad.toolbar.redo.title": "Връщане (Ctrl+Y)", "pad.toolbar.settings.title": "Настройки", "pad.colorpicker.save": "Съхраняване", - "pad.colorpicker.cancel": "Отказване", + "pad.colorpicker.cancel": "Отказ", "pad.loading": "Зареждане...", "pad.wrongPassword": "Неправилна парола", "pad.settings.language": "Език:", @@ -29,7 +29,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.modals.cancel": "Отказване", + "pad.modals.cancel": "Отказ", "pad.modals.userdup": "Отворен в друг прозорец", "pad.modals.userdup.explanation": "Изглежда, че този пад е отворен на повече от един раздел в браузъра на компютъра.", "pad.modals.looping.explanation": "Има проблеми с комуникацията със сървъра за синхронизация.", diff --git a/src/locales/br.json b/src/locales/br.json index aa31f7fa0..ea3da7687 100644 --- a/src/locales/br.json +++ b/src/locales/br.json @@ -55,7 +55,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Ne c'hallit ket emporzjiañ furmadoù testennoù kriz pe html. Evit arc'hwelioù enporzhiañ emdroetoc'h, staliit abiword mar plij.", + "pad.importExport.abiword.innerHTML": "Ne c'hallit ket enporzhiañ furmadoù testennoù kriz pe HTML hepken. Evit arc'hwelioù enporzhiañ emdroetoc'h, staliit staliañ Abiword mar plij.", "pad.modals.connected": "Kevreet.", "pad.modals.reconnecting": "Adkevreañ war-zu ho pad...", "pad.modals.forcereconnect": "Adkevreañ dre heg", diff --git a/src/locales/da.json b/src/locales/da.json index 06280b8e7..612cd9815 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -4,7 +4,8 @@ "Christian List", "Peter Alberti", "Steenth", - "Joedalton" + "Joedalton", + "Saederup92" ] }, "index.newPad": "Ny Pad", @@ -43,6 +44,7 @@ "pad.settings.fontType": "Skrifttype:", "pad.settings.fontType.normal": "Normal", "pad.settings.fontType.monospaced": "Fastbredde", + "pad.settings.fontType.trebuchet": "Blide", "pad.settings.globalView": "Global visning", "pad.settings.language": "Sprog:", "pad.importExport.import_export": "Import/Eksport", diff --git a/src/locales/es.json b/src/locales/es.json index d271b61ea..747ec7bf5 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -13,7 +13,8 @@ "Macofe", "Fitoschido", "Dgstranz", - "Luzcaru" + "Luzcaru", + "Tiberius1701" ] }, "index.newPad": "Nuevo pad", @@ -83,14 +84,14 @@ "pad.modals.slowcommit.explanation": "El servidor no responde.", "pad.modals.slowcommit.cause": "Puede deberse a problemas con tu conexión de red.", "pad.modals.badChangeset.explanation": "Has hecho una edición clasificada como ilegal por el servidor de sincronización.", - "pad.modals.badChangeset.cause": "Esto podría deberse a una mala configuración del servidor o algún otro comportamiento inesperado. Contacta al administrador del servicio, si piensas que esto es un error. Intenta reconectarte con el fin de seguir editando.", + "pad.modals.badChangeset.cause": "Esto podría deberse a una mala configuración del servidor o algún otro comportamiento inesperado. Contacta con administrador del servicio, si piensas que esto es un error. Intenta volver a conectar para continuar editando.", "pad.modals.corruptPad.explanation": "El pad que intentas acceder está dañado.", - "pad.modals.corruptPad.cause": "Esto puede deberse a una mala configuración del servidor o algún otro comportamiento inesperado. Contacta al administrador del servicio.", + "pad.modals.corruptPad.cause": "Esto puede deberse a una mala configuración del servidor o algún otro comportamiento inesperado. Contacta con el administrador del servicio.", "pad.modals.deleted": "Borrado.", "pad.modals.deleted.explanation": "Este pad ha sido borrado.", "pad.modals.disconnected": "Te has desconectado.", "pad.modals.disconnected.explanation": "Se perdió la conexión con el servidor", - "pad.modals.disconnected.cause": "El servidor podría no estar disponible. Contacta al administrador del servicio si esto continúa sucediendo.", + "pad.modals.disconnected.cause": "El servidor podría no estar disponible. Contacta con el administrador del servicio si esto continúa sucediendo.", "pad.share": "Compatir este pad", "pad.share.readonly": "Solo lectura", "pad.share.link": "Enlace", @@ -139,5 +140,5 @@ "pad.impexp.uploadFailed": "El envío falló. Inténtalo de nuevo.", "pad.impexp.importfailed": "Fallo al importar", "pad.impexp.copypaste": "Intenta copiar y pegar", - "pad.impexp.exportdisabled": "La exportación al formato {{type}} está desactivada. Contacta a tu administrador de sistemas." + "pad.impexp.exportdisabled": "La exportación al formato {{type}} está desactivada. Contacta con tu administrador del sistema." } diff --git a/src/locales/gl.json b/src/locales/gl.json index 67491763d..a8173379b 100644 --- a/src/locales/gl.json +++ b/src/locales/gl.json @@ -53,7 +53,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "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 instale abiword.", + "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 instale AbiWord.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando co seu documento...", "pad.modals.forcereconnect": "Forzar a reconexión", diff --git a/src/locales/is.json b/src/locales/is.json index 3d51bd72a..65226445d 100644 --- a/src/locales/is.json +++ b/src/locales/is.json @@ -53,7 +53,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Þú getur aðeins flutt inn úr hreinum texta eða HTML sniðum. Til að geta nýtt \nfleiri þróaðri innflutningssnið settu þá upp abiword forritið.", + "pad.importExport.abiword.innerHTML": "Þú getur aðeins flutt inn úr hreinum texta eða HTML sniðum. Til að geta nýtt \nfleiri þróaðri innflutningssnið settu þá upp AbiWord forritið.", "pad.modals.connected": "Tengt.", "pad.modals.reconnecting": "Endurtengist skrifblokkinni þinni...", "pad.modals.forcereconnect": "Þvinga endurtengingu", diff --git a/src/locales/lt.json b/src/locales/lt.json index f0f02d1ea..0dca1db38 100644 --- a/src/locales/lt.json +++ b/src/locales/lt.json @@ -4,7 +4,8 @@ "Eitvys200", "Mantak111", "I-svetaines", - "Zygimantus" + "Zygimantus", + "Vogone" ] }, "index.newPad": "Naujas bloknotas", @@ -54,7 +55,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Atvirasis dokumento formatas)", - "pad.importExport.abiword.innerHTML": "Galite importuoti tik iš paprasto teksto ar HTML formato. Dėl išplėstinių importavimo funkcijų prašome įdiegti abiword.", + "pad.importExport.abiword.innerHTML": "Galite importuoti tik iš paprasto teksto ar HTML formato. Dėl išplėstinių importavimo funkcijų prašome įdiegti AbiWord.", "pad.modals.connected": "Prisijungta.", "pad.modals.reconnecting": "Iš naujo prisijungiama prie Jūsų bloknoto", "pad.modals.forcereconnect": "Priversti prisijungti iš naujo", diff --git a/src/locales/mk.json b/src/locales/mk.json index 2ca04171e..fda3bf4ff 100644 --- a/src/locales/mk.json +++ b/src/locales/mk.json @@ -53,7 +53,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Можете да увезувате само од прост текст и HTML-формат. Понапредни можности за увоз ќе добиете ако воспоставите AbiWord.", + "pad.importExport.abiword.innerHTML": "Можете да увезувате само од прост текст и HTML-формат. Понапредни можности за увоз ќе добиете ако воспоставите AbiWord.", "pad.modals.connected": "Поврзано.", "pad.modals.reconnecting": "Ве преповрзувам со тетратката...", "pad.modals.forcereconnect": "Наметни преповрзување", diff --git a/src/locales/ml.json b/src/locales/ml.json index 7d6dd66b9..e6968036e 100644 --- a/src/locales/ml.json +++ b/src/locales/ml.json @@ -7,14 +7,15 @@ "Praveenp", "Santhosh.thottingal", "Nesi", - "Jinoytommanjaly" + "Jinoytommanjaly", + "Ambadyanands" ] }, "index.newPad": "പുതിയ പാഡ്", "index.createOpenPad": "അല്ലെങ്കിൽ പേരുപയോഗിച്ച് പാഡ് സൃഷ്ടിക്കുക/തുറക്കുക:", - "pad.toolbar.bold.title": "കടുപ്പത്തിലെഴുതുക (Ctrl-B)", - "pad.toolbar.italic.title": "ചെരിച്ചെഴുതുക (Ctrl-I)", - "pad.toolbar.underline.title": "അടിവരയിടുക (Ctrl-U)", + "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)", @@ -22,7 +23,7 @@ "pad.toolbar.unindent.title": "ഇടത്തേക്ക് തള്ളുക (ഷിഫ്റ്റ്+ടാബ്)", "pad.toolbar.undo.title": "തിരസ്കരിക്കുക (Ctrl-Z)", "pad.toolbar.redo.title": "വീണ്ടും ചെയ്യുക (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "രചയിതാക്കൾക്കുള്ള നിറം കളയുക (Ctrl+Shift+C)", + "pad.toolbar.clearAuthorship.title": "രചയിതാക്കൾക്കുള്ള നിറങ്ങൾ കളയുക (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "വ്യത്യസ്ത ഫയൽ തരങ്ങളിലേക്ക്/തരങ്ങളിൽ നിന്ന് ഇറക്കുമതി/കയറ്റുമതി ചെയ്യുക", "pad.toolbar.timeslider.title": "സമയരേഖ", "pad.toolbar.savedRevision.title": "നാൾപ്പതിപ്പ് സേവ് ചെയ്യുക", @@ -58,10 +59,11 @@ "pad.importExport.exportword": "മൈക്രോസോഫ്റ്റ് വേഡ്", "pad.importExport.exportpdf": "പി.ഡി.എഫ്.", "pad.importExport.exportopen": "ഒ.ഡി.എഫ്. (ഓപ്പൺ ഡോക്യുമെന്റ് ഫോർമാറ്റ്)", - "pad.importExport.abiword.innerHTML": "പ്ലെയിൻ ടെക്സ്റ്റോ എച്ച്.റ്റി.എം.എൽ. തരമോ മാത്രമേ താങ്കൾക്ക് ഇറക്കുമതി ചെയ്യാനാവൂ. കൂടുതൽ വിപുലീകൃത ഇറക്കുമതി സൗകര്യങ്ങൾക്കായി ദയവായി അബിവേഡ് ഇൻസ്റ്റോൾ ചെയ്യുക.", + "pad.importExport.abiword.innerHTML": "പ്ലെയിൻ ടെക്സ്റ്റോ എച്ച്.റ്റി.എം.എൽ. തരമോ മാത്രമേ താങ്കൾക്ക് ഇറക്കുമതി ചെയ്യാനാവൂ. കൂടുതൽ വിപുലീകൃത ഇറക്കുമതി സൗകര്യങ്ങൾക്കായി ദയവായി അബിവേഡ് ഇൻസ്റ്റോൾ ചെയ്യുക.", "pad.modals.connected": "ബന്ധിപ്പിച്ചിരിക്കുന്നു.", "pad.modals.reconnecting": "താങ്കളുടെ പാഡിലേയ്ക്ക് വീണ്ടും ബന്ധിപ്പിക്കുന്നു...", "pad.modals.forcereconnect": "എന്തായാലും ബന്ധിപ്പിക്കുക", + "pad.modals.reconnecttimer": "വീണ്ടും ബന്ധപ്പെടുവാൻ ശ്രമിക്കുന്നു", "pad.modals.cancel": "റദ്ദാക്കുക", "pad.modals.userdup": "മറ്റൊരു ജാലകത്തിൽ തുറന്നിരിക്കുന്നു", "pad.modals.userdup.explanation": "ഈ കമ്പ്യൂട്ടറിൽ ഈ പാഡ് ഒന്നിലധികം ബ്രൗസർ ജാലകങ്ങളിൽ തുറന്നതായി കാണുന്നു.", @@ -99,6 +101,7 @@ "timeslider.exportCurrent": "ഈ പതിപ്പ് ഇങ്ങനെ എടുക്കുക:", "timeslider.version": "പതിപ്പ് {{version}}", "timeslider.saved": "സേവ് ചെയ്തത് {{month}} {{day}}, {{year}}", + "timeslider.playPause": "പാ‍ഡിലെ ഉള്ളടക്കങ്ങൾ പ്ലേ / പോസ് ചെയ്യുക", "timeslider.backRevision": "ഈ പാഡിലെ ഒരു നാൾപ്പതിപ്പിലേക്ക് മടങ്ങുക", "timeslider.forwardRevision": "ഈ പാഡിലെ അടുത്ത മാറ്റത്തിലേക്ക് പോവുക", "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", diff --git a/src/locales/mnw.json b/src/locales/mnw.json new file mode 100644 index 000000000..ae9f18c3c --- /dev/null +++ b/src/locales/mnw.json @@ -0,0 +1,62 @@ +{ + "@metadata": { + "authors": [ + "Aue Nai" + ] + }, + "index.newPad": "တၞးတၟိ", + "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.colorpicker.save": "ဂိုင်သိပ်", + "pad.colorpicker.cancel": "တးပဲါ", + "pad.loading": "ပတိုန်ဒၟံၚ်", + "pad.wrongPassword": "ကောန်ဍေၚ်မၞးဂှ် ဗၠေတ်မံၚ်", + "pad.settings.language": "အရေဝ်ဘာသာ", + "pad.importExport.import_export": "ပလုပ်/ပတိတ်", + "pad.importExport.importSuccessful": "ဍိုက်ပေၚ်စိုပ်ဒတုဲ", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "လိက်ပလး", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.modals.reconnecttimer": "ဂစာန်မံၚ်သွက်ကလေၚ်ဆက်လုပ်", + "pad.modals.cancel": "တးပဲါ", + "pad.modals.unauth": "အခေါၚ်အဝဵုဟွံမွဲ", + "pad.modals.deleted": "ပလီု", + "pad.modals.deleted.explanation": "တၞးဏအ်ဒးဒုၚ်တးပဲါထောံယျ။", + "pad.modals.disconnected": "မၞးဆက်စၠောံဟွံမွဲမံၚ်ယျ", + "pad.share": "ပါ်ပရအ်တၞးဏအ်ညိ", + "pad.share.readonly": "ဆ အယာံမာတ်ဗှ်ဟေၚ်", + "pad.share.link": "လေန်", + "pad.share.emebdcode": "Embed URL", + "timeslider.pageTitle": "{{appTitle}} Timeslider", + "timeslider.toolbar.returnbutton": "ကလၚ်တၞးတေံ", + "timeslider.toolbar.authors": "ကဝိ", + "timeslider.toolbar.authorsList": "ကဝိ ဟွံမွဲ", + "timeslider.toolbar.exportlink.title": "ပတိတ်", + "timeslider.version": "ဗာရှောန်{{version}}", + "timeslider.saved": "သီဂိုၚ်လဝ် {{month}} {{day}}, {{year}}", + "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": "\nသေပ်တေမ်ပါ", + "timeslider.month.october": "\nအံက်တဝ်ပါ", + "timeslider.month.november": "\nနဝ်ဝေမ်ပါ", + "timeslider.month.december": "ဒဳဇြေန်ပါ", + "timeslider.unnamedauthors": "{{num}} ဟွံကဵုယၟု {[ဂမၠိုၚ်(num) မွဲ: ကဝိ, တၞဟ်: ကဝိဂမၠိုၚ် ]}", + "pad.userlist.entername": "စုတ် ယၟုညးလွပ်", + "pad.userlist.unnamed": "ဟွံကဵုလဝ်ယၟု", + "pad.userlist.guest": "ကၟုဲ", + "pad.userlist.deny": "တးပဲါ", + "pad.userlist.approve": "သ္ပကဵုဒတန်", + "pad.impexp.importbutton": " မပၠောပ်စုတ် လၟုဟ်", + "pad.impexp.importing": "မပၠောပ်စုတ်ဒၟံၚ်...", + "pad.impexp.importfailed": "မပၠောပ်စုတ်တအ်လီုအာ" +} diff --git a/src/locales/nah.json b/src/locales/nah.json index 262766b33..b9e2ac5c6 100644 --- a/src/locales/nah.json +++ b/src/locales/nah.json @@ -15,7 +15,7 @@ "pad.toolbar.redo.title": "Occeppa (Ctrl+Y)", "pad.toolbar.settings.title": "Tlatlālīliztli", "pad.colorpicker.save": "Xicpiya", - "pad.colorpicker.cancel": "Xiccāhua", + "pad.colorpicker.cancel": "Moxitiniz", "pad.settings.padSettings": "Pad Ītlatlālīliz", "pad.settings.myView": "Notlachiyaliz", "pad.settings.language": "Tlahtōlli:", @@ -24,6 +24,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.modals.cancel": "Moxitiniz", "pad.modals.deleted": "Omopohpoloh.", "pad.modals.deleted.explanation": "Ōmopoloh inīn Pad.", "timeslider.version": "Inīc {{version}} Cuepaliztli", diff --git a/src/locales/nl.json b/src/locales/nl.json index 727e8abec..6f468d531 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -4,11 +4,14 @@ "Siebrand", "Macofe", "Robin0van0der0vliet", - "Robin van der Vliet" + "Robin van der Vliet", + "Mainframe98", + "KlaasZ4usV", + "Rickvl" ] }, "index.newPad": "Nieuw pad", - "index.createOpenPad": "Pad maken op openen met de naam:", + "index.createOpenPad": "of maak/open een pad met de naam:", "pad.toolbar.bold.title": "Vet (Ctrl-B)", "pad.toolbar.italic.title": "Cursief (Ctrl-I)", "pad.toolbar.underline.title": "Onderstrepen (Ctrl-U)", @@ -55,7 +58,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "Pdf", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "U kunt alleen importeren vanuit Tekst zonder opmaak of een HTML-opmaak. Installeer abiword om meer geavanceerde importmogelijkheden te krijgen.", + "pad.importExport.abiword.innerHTML": "U kunt alleen importeren vanuit Tekst zonder opmaak of een HTML-opmaak. Installeer AbiWord om meer geavanceerde importmogelijkheden te krijgen.", "pad.modals.connected": "Verbonden.", "pad.modals.reconnecting": "Opnieuw verbinding maken met uw pad...", "pad.modals.forcereconnect": "Opnieuw verbinden", diff --git a/src/locales/pt-br.json b/src/locales/pt-br.json index d72a71289..967af6a17 100644 --- a/src/locales/pt-br.json +++ b/src/locales/pt-br.json @@ -67,7 +67,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Você só pode importar de formatos de texto puro ou html. Para recursos de importação mais avançados instale o abiword.", + "pad.importExport.abiword.innerHTML": "Só é possível importar texto sem formatação ou HTML. Para obter funcionalidades de importação mais avançadas, por favor instale o AbiWord.", "pad.modals.connected": "Conectado.", "pad.modals.reconnecting": "Reconectando à sua nota...", "pad.modals.forcereconnect": "Forçar reconexão", diff --git a/src/locales/sd.json b/src/locales/sd.json index bf8a36e57..533c6c50c 100644 --- a/src/locales/sd.json +++ b/src/locales/sd.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": [ - "Mehtab ahmed" + "Mehtab ahmed", + "Tweety" ] }, "index.newPad": "نئين پٽي", @@ -32,18 +33,19 @@ "pad.settings.fontType": "اکرن جو قسم:", "pad.settings.globalView": "عالمي نظارو", "pad.settings.language": "ٻولي:", - "pad.importExport.import_export": "درآمد ڪريو\\برآمد ڪريو", + "pad.importExport.import_export": "برآمد/درآمد", "pad.importExport.import": "ڪو به متن وارو فائيل يا دستاويز چاڙهيو", "pad.importExport.importSuccessful": "ڪامياب!", "pad.importExport.export": "هاڻوڪي پٽي برآمد ڪريو جي طور:", "pad.importExport.exporthtml": "HTML", - "pad.importExport.exportplain": "سادو متن", + "pad.importExport.exportplain": "سدا اکر", "pad.importExport.exportword": "مائيڪرسافٽ ورڊ", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (کليل دستاويز فارميٽ)", "pad.modals.connected": "ڳنڍيل.", "pad.modals.reconnecting": "توهان جي پٽي سان ٻيهر ڳنڍي رهيو آهي...", "pad.modals.forcereconnect": "جبري طور ٻيهر ڳنڍيو", + "pad.modals.cancel": "رد", "pad.modals.userdup": "هڪ ٻي دري ۾ کليل", "pad.modals.unauth": "اختيار نه آهي", "pad.modals.initsocketfail": "سَروَرَ کي پڄي نٿو سگھجي.", diff --git a/src/locales/shn.json b/src/locales/shn.json index 434ef4957..c5c817519 100644 --- a/src/locales/shn.json +++ b/src/locales/shn.json @@ -1,7 +1,9 @@ { "@metadata": { "authors": [ - "Saosukham" + "Saosukham", + "Ninjastrikers", + "Saimawnkham" ] }, "index.newPad": "ၽႅတ်ႉမႂ်ႇ", @@ -50,10 +52,12 @@ "pad.importExport.exportword": "မၢႆႇၶရူဝ်ႇသွပ်ႉဝၢတ်ႉ", "pad.importExport.exportpdf": "ၽီႇတီႇဢႅပ်ႉၾ်", "pad.importExport.exportopen": "ဢူဝ်တီႇဢႅပ်ႉၾ် (Open Document Format)", - "pad.importExport.abiword.innerHTML": "ၸဝ်ႈၵဝ်ႇၸၢင်ႈလုၵ်ႉတီႈ လိၵ်ႈပဝ်ႇသေ သူင်ႇၶဝ်ႈၵႂႃႇ ဢမ်ႇၼၼ် ပိူင်လၢႆႈ HTML. ပုၼ်ႈတႃႇ ၸိူဝ်းပိူင်မႂ်ႇ ဢၼ်သူင်ႇၶဝ်ႈမႃးၼၼ်ႉ ၶွပ်ႈၸႂ်သေ install abiword.", + "pad.importExport.abiword.innerHTML": "ၸဝ်ႈၵဝ်ႇတေ ၸၢင်ႈလုၵ်ႉတီႈ တူဝ်လိၵ်ႈလွၼ်ႉလွၼ်ႉ ဢမ်ႇၼၼ် HTML သေ သူင်ႇၶဝ်ႈၵႂႃႇၵူၺ်း။ တွၼ်ႈတႃႇ လၢႆးၵၢၼ်သူင်ႇၶဝ်ႈၶိုၵ်ႉတွၼ်းတၢင်ႇၸိူဝ်းၼၼ်ႉ ၶႅၼ်းတေႃႈ ဢူၼ်းသႂ်ႇ AbiWord.", "pad.modals.connected": "ၵွင်ႉသၢၼ်ယဝ်ႉ", "pad.modals.reconnecting": "ၶိုၼ်းၵွင်ႉသၢၼ်ၸူး ၽႅတ်ႉၸဝ်ႈၵဝ်ႇယူႇ", "pad.modals.forcereconnect": "တဵၵ်းၸႂ်ႉ ၶိုၼ်းၵွင်ႉသၢၼ်", + "pad.modals.reconnecttimer": "ၶတ်းၸႂ်တူၺ်း တႃႇၶိုၼ်းၵွင်ႉသိုပ်ႇၸူး", + "pad.modals.cancel": "ဢမ်ႇႁဵတ်း", "pad.modals.userdup": "ပိုတ်ႇတမ်ႈတီႈ ၼႃႈတူမႂ်ႇ", "pad.modals.userdup.explanation": "တမ်ႈတီႈၼႂ်းၶွမ်းတၢင်ႇဢၼ်ၼၼ်ႉ ၽႅတ်ႉဢၼ်ၼႆႉ လႅပ်ႉပိုတ်ႇဝႆႉ တမ်ႈတီႈ ပရၢဝ်ႇသႃႇတၢင်ႇတီႈယူႇ", "pad.modals.userdup.advice": "ၶိုၼ်းၵွင်ႉသၢၼ်တၢင် တမ်ႈတီႈ ဝိၼ်းတူဝ်းၼႆႉ", @@ -67,6 +71,7 @@ "pad.modals.slowcommit.explanation": "သႃႇဝႃႇ ဢမ်ႇတွပ်ႇပၼ်", "pad.modals.slowcommit.cause": "ၼႆႉပူပ်ႉၺႃး ပၼ်ႁႃ ၵိုၵ်းလူၺ်ႈ သၢႆၼႅင်ႈၵွင်ႉသၢၼ်", "pad.modals.badChangeset.explanation": "ၶေႃႈထတ်း ဢၼ်ၸဝ်ႈၵဝ်ႇႁဵတ်းၼၼ်ႉ မၼ်းဢမ်ႇႁူမ်ႈၶဝ်ႈၶႂၢင်ႇ ၸွမ်းၼင်ႇ သႃႇဝႃႇဢၼ် ၸၼ်ထိင်းဝႆႉ", + "pad.modals.badChangeset.cause": "ၼႆႉမၼ်းၸၢင်ႈပဵၼ်ယွၼ်ႉပိူဝ်ႈ လွင်ႈၵုမ်းၵၢၼ်သႃႇပိူဝ်ႇ ၽိတ်းပိူင်ႈဝႆႉ ဢမ်ႇၼၼ် ပဵၼ်ယွၼ်ႉလွင်ႈဢၼ်ဢမ်ႇမုင်ႈမွင်းဝႆႉ။ သင်ၸိူဝ်ႉဝႃႈ ၸဝ်ႈၵဝ်ႇယိၼ်းဝႃႈပဵၼ်လွင်ႈၽိတ်းပိူင်ႈၼႆ ၶႅၼ်းတေႃႈၵပ်းသိုပ်ႇၸူးတင်း ၽူႈၵုမ်းၵၢၼ် ၵၢၼ်ၸွႆႈသၢင်ႈလႄႈ။ ၶိုၼ်းၶတ်းၸႂ်ၵွင်ႉသိုပ်ႇသေ တွၼ်ႈတႃႇ သိုပ်ႇႁဵတ်းလွင်ႈမႄးထတ်း။", "pad.modals.corruptPad.explanation": "ၽႅတ်ႉဢၼ်ၸဝ်ႈၵဝ်ႇပေႃႉၼၼ်ႉ ၶဝ်ႈၽိတ်းဝႆႉ", "pad.modals.corruptPad.cause": "ဢၼ်ၼႆႉ သႃႇဝႃႇဢၼ်ၸၼ်ထိင်းမၼ်း ၽိတ်းဝႆႉ ဢမ်ႇၼၼ် ဢမ်ႇမုင်ႈမွင်းသေ ၽိတ်းပိူင်ႈဝႆႉယဝ်ႉ။ ၶႅၼ်းတေႃႈ ၵပ်းသိုပ်ႇတမ်ႈတီႈ ၽူႈၵုမ်းၵၢၼ်.", "pad.modals.deleted": "ယႃႉ", @@ -103,7 +108,7 @@ "timeslider.month.august": "ဢေႃးၵၢတ်ႉ", "timeslider.month.september": "သႅပ်ႉထိမ်ႇပႃႇ", "timeslider.month.october": "ဢွၵ်ႇထူဝ်ႇပႃႇ", - "timeslider.month.november": "ၼူဝ်ႇ​ဝႅမ်ႇ​ပႃႇ", + "timeslider.month.november": "ၼူဝ်ႇဝႅမ်ႇပႃႇ", "timeslider.month.december": "တီႇသႅမ်ႇပႃႇ", "timeslider.unnamedauthors": "{{num}} ဢမ်ႇသႂ်ႇၸိုဝ်ႈ {[plural(num) ၼိုင်ႈ: ၽူႈတႅမ်ႈလိၵ်ႈ, တၢင်ႇၸိူဝ်း: ၽူႈတႅမ်ႈလိၵ်ႈၶဝ် ]}", "pad.savedrevs.marked": "ဢၼ်မႄးဝႆႉၼႆႉ ယၢမ်းလဵဝ် ၶိုၼ်းမႄး ၵဵပ်းသိမ်းဝႆႉ ၸိူင်ႉၼင်ႇဢၼ်ၼိုင်ႈ", diff --git a/src/locales/sq.json b/src/locales/sq.json index 28485b332..e498c202b 100644 --- a/src/locales/sq.json +++ b/src/locales/sq.json @@ -53,10 +53,12 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "Mund të importoni vetëm prej formati tekst i thjeshtë ose html. Për veçori më të thelluara importimi, ju lutemi, instaloni Abiword-in.", + "pad.importExport.abiword.innerHTML": "Mund të importoni vetëm prej formati tekst i thjeshtë ose HTML. Për veçori më të thelluara importimi, ju lutemi, instaloni Abiword-in.", "pad.modals.connected": "I lidhur.", "pad.modals.reconnecting": "Po rilidheni te blloku juaj…", "pad.modals.forcereconnect": "Rilidhje e detyruar", + "pad.modals.reconnecttimer": "Provë për rilidhje pas", + "pad.modals.cancel": "Anuloje", "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": "Rilidhuni që të përdoret kjo dritare.", diff --git a/src/locales/uk.json b/src/locales/uk.json index 9f89e5021..e6e6489bd 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -9,7 +9,8 @@ "Lxlalexlxl", "Григорій Пугач", "Bunyk", - "Piramidion" + "Piramidion", + "Movses" ] }, "index.newPad": "Створити", @@ -24,25 +25,25 @@ "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.clearAuthorship.title": "Очистити кольори авторства (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "Імпорт/Експорт з використанням різних форматів файлів", "pad.toolbar.timeslider.title": "Шкала часу", "pad.toolbar.savedRevision.title": "Зберегти версію", "pad.toolbar.settings.title": "Налаштування", "pad.toolbar.embed.title": "Поділитись та вбудувати цей документ", - "pad.toolbar.showusers.title": "Показати користувачів цього документу", + "pad.toolbar.showusers.title": "Показати користувачів цього документа", "pad.colorpicker.save": "Зберегти", "pad.colorpicker.cancel": "Скасувати", "pad.loading": "Завантаження…", "pad.noCookie": "Реп'яшки не знайдено. Будь-ласка, увімкніть реп'яшки у вашому браузері!", - "pad.passwordRequired": "Вам необхідний пароль для доступу до цього документу", - "pad.permissionDenied": "Ви не має дозволу для доступу до цього документу", + "pad.passwordRequired": "Вам необхідний пароль для доступу до цього документа", + "pad.permissionDenied": "У Вас немає дозволу для доступу до цього документа", "pad.wrongPassword": "Неправильний пароль", - "pad.settings.padSettings": "Налаштування документу", + "pad.settings.padSettings": "Налаштування документа", "pad.settings.myView": "Мій Вигляд", "pad.settings.stickychat": "Завжди відображувати чат", "pad.settings.chatandusers": "Показати чат і користувачів", - "pad.settings.colorcheck": "Кольори документу", + "pad.settings.colorcheck": "Кольори авторства", "pad.settings.linenocheck": "Номери рядків", "pad.settings.rtlcheck": "Читати вміст з права на ліво?", "pad.settings.fontType": "Тип шрифту:", @@ -60,9 +61,9 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (документ OpenOffice)", - "pad.importExport.abiword.innerHTML": "Ви можете імпортувати лище формати простого тексту або html. Для більш просунутих способів імпорту встановіть abiword.", + "pad.importExport.abiword.innerHTML": "Ви можете імпортувати лище формати простого тексту або HTML. Для більш просунутих способів імпорту встановіть AbiWord.", "pad.modals.connected": "З'єднано.", - "pad.modals.reconnecting": "Перепідлючення до Вашого документу..", + "pad.modals.reconnecting": "Перепідлючення до Вашого документа..", "pad.modals.forcereconnect": "Примусове перепідключення", "pad.modals.reconnecttimer": "Триває спроба відновлення з'єднання", "pad.modals.cancel": "Скасувати", @@ -92,10 +93,10 @@ "pad.share.link": "Посилання", "pad.share.emebdcode": "Вставити URL", "pad.chat": "Чат", - "pad.chat.title": "Відкрити чат для цього документу.", + "pad.chat.title": "Відкрити чат для цього документа.", "pad.chat.loadmessages": "Завантажити більше повідомлень", "timeslider.pageTitle": "Часова шкала {{appTitle}}", - "timeslider.toolbar.returnbutton": "Повернутись до документу", + "timeslider.toolbar.returnbutton": "Повернутись до документа", "timeslider.toolbar.authors": "Автори:", "timeslider.toolbar.authorsList": "Немає авторів", "timeslider.toolbar.exportlink.title": "Експорт", @@ -129,8 +130,8 @@ "pad.editbar.clearcolors": "Очистити кольори у всьому документі?", "pad.impexp.importbutton": "Імпортувати зараз", "pad.impexp.importing": "Імпорт...", - "pad.impexp.confirmimport": "Імпортування файлу перезапише поточний текст документу. Ви дійсно хочете продовжити?", - "pad.impexp.convertFailed": "Ми не можемо імпортувати цей файл. Будь ласка, використайте інший формат документу, або прямо скопіюйте та вставте", + "pad.impexp.confirmimport": "Імпортування файлу перезапише поточний текст документа. Ви дійсно хочете продовжити?", + "pad.impexp.convertFailed": "Ми не можемо імпортувати цей файл. Будь ласка, використайте інший формат документа, або прямо скопіюйте та вставте", "pad.impexp.padHasData": "Ми були не в стані імпортувати цей файл, тому що ця панель, вже відредактована, будь ласка, імпортуйте на нову панель", "pad.impexp.uploadFailed": "Завантаження не вдалось, будь ласка, спробуйте знову", "pad.impexp.importfailed": "Помилка при імпортуванні", diff --git a/src/node/README.md b/src/node/README.md index 4b443289e..56ff491f1 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -1,6 +1,6 @@ # About the folder structure -* **db** - all modules that are accesing the data structure and are communicating directly to the database +* **db** - all modules that are accessing the data structure and are communicating directly to the database * **handler** - all modules that responds directly to requests/messages of the browser * **utils** - helper modules diff --git a/src/node/db/API.js b/src/node/db/API.js index be3e73486..21c958809 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -137,15 +137,13 @@ exports.getRevisionChangeset = function(padID, rev, callback) if (rev !== undefined && typeof rev !== "number") { // try to parse the number - if (!isNaN(parseInt(rev))) - { - rev = parseInt(rev); - } - else + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } + + rev = parseInt(rev); } // ensure this is not a negative number @@ -184,17 +182,17 @@ exports.getRevisionChangeset = function(padID, rev, callback) callback(null, changeset); }) - } - //the client wants the latest changeset, lets return it to him - else - { - pad.getRevisionChangeset(pad.getHeadRevisionNumber(), function(err, changeset) - { - if(ERR(err, callback)) return; - callback(null, changeset); - }) + return; } + + //the client wants the latest changeset, lets return it to him + pad.getRevisionChangeset(pad.getHeadRevisionNumber(), function(err, changeset) + { + if(ERR(err, callback)) return; + + callback(null, changeset); + }) }); } @@ -219,15 +217,13 @@ exports.getText = function(padID, rev, callback) if(rev !== undefined && typeof rev != "number") { //try to parse the number - if(!isNaN(parseInt(rev))) - { - rev = parseInt(rev); - } - else + if(isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } + + rev = parseInt(rev); } //ensure this is not a negativ number @@ -268,13 +264,13 @@ exports.getText = function(padID, rev, callback) callback(null, data); }) + + return; } + //the client wants the latest text, lets return it to him - else - { - var padText = exportTxt.getTXTFromAtext(pad, pad.atext); - callback(null, {"text": padText}); - } + var padText = exportTxt.getTXTFromAtext(pad, pad.atext); + callback(null, {"text": padText}); }); } @@ -359,15 +355,13 @@ exports.getHTML = function(padID, rev, callback) if (rev !== undefined && typeof rev != "number") { - if (!isNaN(parseInt(rev))) - { - rev = parseInt(rev); - } - else + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number","apierror")); return; } + + rev = parseInt(rev); } if(rev !== undefined && rev < 0) @@ -405,19 +399,19 @@ exports.getHTML = function(padID, rev, callback) var data = {html: html}; callback(null, data); }); + + return; } + //the client wants the latest text, lets return it to him - else + exportHtml.getPadHTML(pad, undefined, function (err, html) { - exportHtml.getPadHTML(pad, undefined, function (err, html) - { - if(ERR(err, callback)) return; - html = "" +html; // adds HTML head - html += ""; - var data = {html: html}; - callback(null, data); - }); - } + if(ERR(err, callback)) return; + html = "" +html; // adds HTML head + html += ""; + var data = {html: html}; + callback(null, data); + }); }); } @@ -448,11 +442,10 @@ exports.setHTML = function(padID, html, callback) if(e){ callback(new customError("HTML is malformed","apierror")); return; - }else{ - //update the clients on the pad - padMessageHandler.updatePadClients(pad, callback); - return; } + + //update the clients on the pad + padMessageHandler.updatePadClients(pad, callback); }); }); } @@ -641,15 +634,13 @@ exports.saveRevision = function(padID, rev, callback) if(rev !== undefined && typeof rev != "number") { //try to parse the number - if(!isNaN(parseInt(rev))) - { - rev = parseInt(rev); - } - else + if(isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } + + rev = parseInt(rev); } //ensure this is not a negativ number @@ -732,8 +723,9 @@ exports.createPad = function(padID, text, callback) callback(new customError("createPad can't create group pads","apierror")); return; } + //check for url special characters - else if(padID.match(/(\/|\?|&|#)/)) + if(padID.match(/(\/|\?|&|#)/)) { callback(new customError("malformed padID: Remove special characters","apierror")); return; @@ -782,15 +774,13 @@ exports.restoreRevision = function (padID, rev, callback) if (rev !== undefined && typeof rev != "number") { //try to parse the number - if (!isNaN(parseInt(rev))) - { - rev = parseInt(rev); - } - else + if (isNaN(parseInt(rev))) { callback(new customError("rev is not a number", "apierror")); return; } + + rev = parseInt(rev); } //ensure this is not a negativ number @@ -959,11 +949,10 @@ exports.getPadID = function(roID, callback) if(retrievedPadID == null) { callback(new customError("padID does not exist","apierror")); + return; } - else - { - callback(null, {padID: retrievedPadID}); - } + + callback(null, {padID: retrievedPadID}); }); } @@ -1127,9 +1116,9 @@ exports.sendClientsMessage = function (padID, msg, callback) { getPadSafe(padID, true, function (err, pad) { if (ERR(err, callback)) { return; - } else { - padMessageHandler.handleCustomMessage(padID, msg, callback); } + + padMessageHandler.handleCustomMessage(padID, msg, callback); } ); } @@ -1177,30 +1166,26 @@ exports.createDiffHTML = function(padID, startRev, endRev, callback){ if(startRev !== undefined && typeof startRev != "number") { //try to parse the number - if(!isNaN(parseInt(startRev))) - { - startRev = parseInt(startRev, 10); - } - else + if(isNaN(parseInt(startRev))) { callback({stop: "startRev is not a number"}); return; } + + startRev = parseInt(startRev, 10); } //check if rev is a number if(endRev !== undefined && typeof endRev != "number") { //try to parse the number - if(!isNaN(parseInt(endRev))) - { - endRev = parseInt(endRev, 10); - } - else + if(isNaN(parseInt(endRev))) { callback({stop: "endRev is not a number"}); return; } + + endRev = parseInt(endRev, 10); } //get the pad diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index 1f2a736be..c7ebf47f4 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -104,16 +104,16 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback) //return the author callback(null, author); }); - } - //there is a author with this mapper - else - { - //update the timestamp of this author - db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); - //return the author - callback(null, {authorID: author}); + return; } + + //there is a author with this mapper + //update the timestamp of this author + db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); + + //return the author + callback(null, {authorID: author}); }); } @@ -209,20 +209,19 @@ exports.listPadsOfAuthor = function (authorID, callback) if(author == null) { callback(new customError("authorID does not exist","apierror")) + return; } + //everything is fine, return the pad IDs - else + var pads = []; + if(author.padIDs != null) { - var pads = []; - if(author.padIDs != null) + for (var padId in author.padIDs) { - for (var padId in author.padIDs) - { - pads.push(padId); - } + pads.push(padId); } - callback(null, {padIDs: pads}); } + callback(null, {padIDs: pads}); }); } diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index 82c14c39d..0c9be1221 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -62,13 +62,12 @@ exports.deleteGroup = function(groupID, callback) if(_group == null) { callback(new customError("groupID does not exist","apierror")); + return; } + //group exists, everything is fine - else - { - group = _group; - callback(); - } + group = _group; + callback(); }); }, //iterate trough all pads of this groups and delete them @@ -213,34 +212,35 @@ exports.createGroupIfNotExistsFor = function(groupMapper, callback) //try to get a group for this mapper db.get("mapper2group:"+groupMapper, function(err, groupID) { - if(ERR(err, callback)) return; + function createGroupForMapper(cb) { + exports.createGroup(function(err, responseObj) + { + if(ERR(err, cb)) return; + + //create the mapper entry for this group + db.set("mapper2group:"+groupMapper, responseObj.groupID); + + cb(null, responseObj); + }); + } + + if(ERR(err, callback)) return; - // there is a group for this mapper - if(groupID) { - exports.doesGroupExist(groupID, function(err, exists) { - if(ERR(err, callback)) return; - if(exists) return callback(null, {groupID: groupID}); - - // hah, the returned group doesn't exist, let's create one - createGroupForMapper(callback) - }) - } - //there is no group for this mapper, let's create a group - else { - createGroupForMapper(callback) - } - - function createGroupForMapper(cb) { - exports.createGroup(function(err, responseObj) - { - if(ERR(err, cb)) return; - - //create the mapper entry for this group - db.set("mapper2group:"+groupMapper, responseObj.groupID); - - cb(null, responseObj); - }); - } + // there is a group for this mapper + if(groupID) { + exports.doesGroupExist(groupID, function(err, exists) { + if(ERR(err, callback)) return; + if(exists) return callback(null, {groupID: groupID}); + + // hah, the returned group doesn't exist, let's create one + createGroupForMapper(callback) + }) + + return; + } + + //there is no group for this mapper, let's create a group + createGroupForMapper(callback) }); } @@ -261,12 +261,11 @@ exports.createGroupPad = function(groupID, padName, text, callback) if(exists == false) { callback(new customError("groupID does not exist","apierror")); + return; } + //group exists, everything is fine - else - { - callback(); - } + callback(); }); }, //ensure pad does not exists @@ -280,12 +279,11 @@ exports.createGroupPad = function(groupID, padName, text, callback) if(exists == true) { callback(new customError("padName does already exist","apierror")); + return; } + //pad does not exist, everything is fine - else - { - callback(); - } + callback(); }); }, //create the pad @@ -320,19 +318,18 @@ exports.listPads = function(groupID, callback) if(exists == false) { callback(new customError("groupID does not exist","apierror")); + return; } + //group exists, let's get the pads - else + db.getSub("group:" + groupID, ["pads"], function(err, result) { - db.getSub("group:" + groupID, ["pads"], function(err, result) - { - if(ERR(err, callback)) return; - var pads = []; - for ( var padId in result ) { - pads.push(padId); - } - callback(null, {padIDs: pads}); - }); - } + if(ERR(err, callback)) return; + var pads = []; + for ( var padId in result ) { + pads.push(padId); + } + callback(null, {padIDs: pads}); + }); }); } diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 0cb01cace..91ab7f792 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -476,28 +476,27 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { // if it's a group pad, let's make sure the group exists. function(callback) { - if (destinationID.indexOf("$") != -1) + if (destinationID.indexOf("$") === -1) { - destGroupID = destinationID.split("$")[0] - groupManager.doesGroupExist(destGroupID, function (err, exists) - { - if(ERR(err, callback)) return; - - //group does not exist - if(exists == false) - { - callback(new customError("groupID does not exist for destinationID","apierror")); - return; - } - //everything is fine, continue - else - { - callback(); - } - }); - } - else callback(); + return; + } + + destGroupID = destinationID.split("$")[0] + groupManager.doesGroupExist(destGroupID, function (err, exists) + { + if(ERR(err, callback)) return; + + //group does not exist + if(exists == false) + { + callback(new customError("groupID does not exist for destinationID","apierror")); + return; + } + + //everything is fine, continue + callback(); + }); }, // if the pad exists, we should abort, unless forced. function(callback) @@ -506,27 +505,29 @@ Pad.prototype.copy = function copy(destinationID, force, callback) { { if(ERR(err, callback)) return; - if(exists == true) - { - if (!force) - { - console.error("erroring out without force"); - callback(new customError("destinationID already exists","apierror")); - console.error("erroring out without force - after"); - return; - } - else // exists and forcing - { - padManager.getPad(destinationID, function(err, pad) { - if (ERR(err, callback)) return; - pad.remove(callback); - }); - } - } - else + /* + * this is the negation of a truthy comparison. Has been left in this + * wonky state to keep the old (possibly buggy) behaviour + */ + if (!(exists == true)) { callback(); + return; } + + if (!force) + { + console.error("erroring out without force"); + callback(new customError("destinationID already exists","apierror")); + console.error("erroring out without force - after"); + return; + } + + // exists and forcing + padManager.getPad(destinationID, function(err, pad) { + if (ERR(err, callback)) return; + pad.remove(callback); + }); }); }, // copy the 'pad' entry @@ -622,29 +623,28 @@ Pad.prototype.remove = function remove(callback) { //is it a group pad? -> delete the entry of this pad in the group function(callback) { - //is it a group pad? - if(padID.indexOf("$")!=-1) - { - var groupID = padID.substring(0,padID.indexOf("$")); - - db.get("group:" + groupID, function (err, group) - { - if(ERR(err, callback)) return; - - //remove the pad entry - delete group.pads[padID]; - - //set the new value - db.set("group:" + groupID, group); - - callback(); - }); - } - //its no group pad, nothing to do here - else + if(padID.indexOf("$") === -1) { + // it isn't a group pad, nothing to do here callback(); + return; } + + // it is a group pad + var groupID = padID.substring(0,padID.indexOf("$")); + + db.get("group:" + groupID, function (err, group) + { + if(ERR(err, callback)) return; + + //remove the pad entry + delete group.pads[padID]; + + //set the new value + db.set("group:" + groupID, group); + + callback(); + }); }, //remove the readonly entries function(callback) diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 2ecd6e274..035ef3e5e 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -159,21 +159,20 @@ exports.getPad = function(id, text, callback) if(pad != null) { callback(null, pad); + return; } - //try to load pad - else - { - pad = new Pad(id); - //initalize the pad - pad.init(text, function(err) - { - if(ERR(err, callback)) return; - globalPads.set(id, pad); - padList.addPad(id); - callback(null, pad); - }); - } + //try to load pad + pad = new Pad(id); + + //initalize the pad + pad.init(text, function(err) + { + if(ERR(err, callback)) return; + globalPads.set(id, pad); + padList.addPad(id); + callback(null, pad); + }); } exports.listAllPads = function(cb) @@ -206,30 +205,28 @@ exports.sanitizePadId = function(padId, callback) { if(transform_index >= padIdTransforms.length) { callback(padId); + return; } + //check if padId exists - else + exports.doesPadExists(padId, function(junk, exists) { - exports.doesPadExists(padId, function(junk, exists) + if(exists) { - if(exists) - { - callback(padId); - } - else - { - //get the next transformation *that's different* - var transformedPadId = padId; - while(transformedPadId == padId && transform_index < padIdTransforms.length) - { - transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]); - transform_index += 1; - } - //check the next transform - exports.sanitizePadId(transformedPadId, callback, transform_index); - } - }); - } + callback(padId); + return; + } + + //get the next transformation *that's different* + var transformedPadId = padId; + while(transformedPadId == padId && transform_index < padIdTransforms.length) + { + transformedPadId = padId.replace(padIdTransforms[transform_index][0], padIdTransforms[transform_index][1]); + transform_index += 1; + } + //check the next transform + exports.sanitizePadId(transformedPadId, callback, transform_index); + }); } exports.isValidPadId = function(padId) diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 98feafb3a..f930b9618 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -90,13 +90,13 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback) // grant or deny access, with author of token callback(null, statusObject); }); + + return; } + // user may create new pads - no need to check anything - else - { - // grant access, with author of token - callback(null, statusObject); - } + // grant access, with author of token + callback(null, statusObject); }); //don't continue diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js index f8000e47e..954203758 100644 --- a/src/node/db/SessionManager.js +++ b/src/node/db/SessionManager.js @@ -90,15 +90,13 @@ exports.createSession = function(groupID, authorID, validUntil, callback) if(typeof validUntil != "number") { //try to parse the number - if(!isNaN(parseInt(validUntil))) - { - validUntil = parseInt(validUntil); - } - else + if(isNaN(parseInt(validUntil))) { callback(new customError("validUntil is not a number","apierror")); return; } + + validUntil = parseInt(validUntil); } //ensure this is not a negativ number @@ -353,7 +351,7 @@ function listSessionsWithDBKey (dbkey, callback) { if (err == "apierror: sessionID does not exist") { - console.warn("Found bad session " + sessionID + " in " + dbkey + "."); + console.warn(`Found bad session ${sessionID} in ${dbkey}`); } else if(ERR(err, callback)) { diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 05e147058..6ec5907e2 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -18,30 +18,36 @@ * limitations under the License. */ - +var absolutePaths = require('../utils/AbsolutePaths'); var ERR = require("async-stacktrace"); var fs = require("fs"); var api = require("../db/API"); +var log4js = require('log4js'); var padManager = require("../db/PadManager"); var randomString = require("../utils/randomstring"); var argv = require('../utils/Cli').argv; +var apiHandlerLogger = log4js.getLogger('APIHandler'); + //ensure we have an apikey var apikey = null; -var apikeyFilename = argv.apikey || "./APIKEY.txt"; +var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt"); try { apikey = fs.readFileSync(apikeyFilename,"utf8"); + apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`); } catch(e) { + apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`); apikey = randomString(32); fs.writeFileSync(apikeyFilename,apikey,"utf8"); } //a list of all functions -var version = -{ "1": +var version = {}; + +version["1"] = Object.assign({}, { "createGroup" : [] , "createGroupIfNotExistsFor" : ["groupMapper"] , "deleteGroup" : ["groupID"] @@ -71,433 +77,67 @@ var version = , "listAuthorsOfPad" : ["padID"] , "padUsersCount" : ["padID"] } -, "1.1": - { "createGroup" : [] - , "createGroupIfNotExistsFor" : ["groupMapper"] - , "deleteGroup" : ["groupID"] - , "listPads" : ["groupID"] - , "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"] - , "getRevisionsCount" : ["padID"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "setPublicStatus" : ["padID", "publicStatus"] - , "getPublicStatus" : ["padID"] - , "setPassword" : ["padID", "password"] - , "isPasswordProtected" : ["padID"] - , "listAuthorsOfPad" : ["padID"] - , "padUsersCount" : ["padID"] - , "getAuthorName" : ["authorID"] +); + +version["1.1"] = Object.assign({}, version["1"], + { "getAuthorName" : ["authorID"] , "padUsers" : ["padID"] , "sendClientsMessage" : ["padID", "msg"] , "listAllGroups" : [] } -, "1.2": - { "createGroup" : [] - , "createGroupIfNotExistsFor" : ["groupMapper"] - , "deleteGroup" : ["groupID"] - , "listPads" : ["groupID"] - , "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"] - , "getRevisionsCount" : ["padID"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "setPublicStatus" : ["padID", "publicStatus"] - , "getPublicStatus" : ["padID"] - , "setPassword" : ["padID", "password"] - , "isPasswordProtected" : ["padID"] - , "listAuthorsOfPad" : ["padID"] - , "padUsersCount" : ["padID"] - , "getAuthorName" : ["authorID"] - , "padUsers" : ["padID"] - , "sendClientsMessage" : ["padID", "msg"] - , "listAllGroups" : [] - , "checkToken" : [] +); + +version["1.2"] = Object.assign({}, version["1.1"], + { "checkToken" : [] } -, "1.2.1": - { "createGroup" : [] - , "createGroupIfNotExistsFor" : ["groupMapper"] - , "deleteGroup" : ["groupID"] - , "listPads" : ["groupID"] - , "listAllPads" : [] - , "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"] - , "getRevisionsCount" : ["padID"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "setPublicStatus" : ["padID", "publicStatus"] - , "getPublicStatus" : ["padID"] - , "setPassword" : ["padID", "password"] - , "isPasswordProtected" : ["padID"] - , "listAuthorsOfPad" : ["padID"] - , "padUsersCount" : ["padID"] - , "getAuthorName" : ["authorID"] - , "padUsers" : ["padID"] - , "sendClientsMessage" : ["padID", "msg"] - , "listAllGroups" : [] - , "checkToken" : [] +); + +version["1.2.1"] = Object.assign({}, version["1.2"], + { "listAllPads" : [] } -, "1.2.7": - { "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"] - , "getRevisionsCount" : ["padID"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "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"] +); + +version["1.2.7"] = Object.assign({}, version["1.2.1"], + { "createDiffHTML" : ["padID", "startRev", "endRev"] , "getChatHistory" : ["padID", "start", "end"] , "getChatHead" : ["padID"] } -, "1.2.8": - { "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"] +); + +version["1.2.8"] = Object.assign({}, version["1.2.7"], + { "getAttributePool" : ["padID"] , "getRevisionChangeset" : ["padID", "rev"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "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"] } -, "1.2.9": - { "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"] - , "getRevisionChangeset" : ["padID", "rev"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "copyPad" : ["sourceID", "destinationID", "force"] +); + +version["1.2.9"] = Object.assign({}, version["1.2.8"], + { "copyPad" : ["sourceID", "destinationID", "force"] , "movePad" : ["sourceID", "destinationID", "force"] - , "getReadOnlyID" : ["padID"] - , "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"] } -, "1.2.10": - { "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"] - , "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"] +); + +version["1.2.10"] = Object.assign({}, version["1.2.9"], + { "getPadID" : ["roID"] } -, "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"] +); + +version["1.2.11"] = Object.assign({}, version["1.2.10"], + { "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"] +); + +version["1.2.12"] = Object.assign({}, version["1.2.11"], + { "appendChatMessage" : ["padID", "text", "authorID", "time"] } -, "1.2.13": - { "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"] - , "appendText" : ["padID", "text"] +); + +version["1.2.13"] = Object.assign({}, version["1.2.12"], + { "appendText" : ["padID", "text"] } -}; +); // set the latest available API version here exports.latestApiVersion = '1.2.13'; @@ -525,7 +165,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res) } } - //say goodbye if this is an unkown API version + //say goodbye if this is an unknown API version if(!isKnownApiVersion) { res.statusCode = 404; @@ -544,7 +184,7 @@ exports.handle = function(apiVersion, functionName, fields, req, res) } } - //say goodbye if this is a unkown function + //say goodbye if this is a unknown function if(!isKnownFunctionname) { res.send({code: 3, message: "no such function", data: null}); @@ -608,7 +248,7 @@ function callAPI(apiVersion, functionName, fields, req, res) { res.send({code: 1, message: err.message, data: null}); } - //an unkown error happend + //an unknown error happend else { res.send({code: 2, message: "internal error", data: null}); diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index 3e3dc195e..45c4c6ea8 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -34,9 +34,18 @@ var ERR = require("async-stacktrace") , log4js = require("log4js") , hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); -//load abiword only if its enabled -if(settings.abiword != null) - var abiword = require("../utils/Abiword"); +var convertor = null; +var exportExtension = "htm"; + +//load abiword only if its enabled and if soffice is disabled +if(settings.abiword != null && settings.soffice === null) + convertor = require("../utils/Abiword"); + +//load soffice only if its enabled +if(settings.soffice != null) { + convertor = require("../utils/LibreOffice"); + exportExtension = "html"; +} //for node 0.6 compatibily, os.tmpDir() only works from 0.8 var tmpDirectory = process.env.TEMP || process.env.TMPDIR || process.env.TMP || '/tmp'; @@ -49,7 +58,7 @@ exports.doImport = function(req, res, padId) var apiLogger = log4js.getLogger("ImportHandler"); //pipe to a file - //convert file to html via abiword + //convert file to html via abiword or soffice //set html in the pad var srcFile, destFile @@ -57,12 +66,12 @@ exports.doImport = function(req, res, padId) , text , importHandledByPlugin , directDatabaseAccess - , useAbiword; + , useConvertor; var randNum = Math.floor(Math.random()*0xFFFFFFFF); - // setting flag for whether to use abiword or not - useAbiword = (abiword != null); + // setting flag for whether to use convertor or not + useConvertor = (convertor != null); async.series([ //save the uploaded file to /tmp @@ -76,13 +85,14 @@ exports.doImport = function(req, res, padId) if(err || files.file === undefined) { if(err) console.warn("Uploading Error: " + err.stack); callback("uploadFailed"); + + return; } + //everything ok, continue - else { - //save the path of the uploaded file - srcFile = files.file.path; - callback(); - } + //save the path of the uploaded file + srcFile = files.file.path; + callback(); }); }, @@ -96,21 +106,22 @@ exports.doImport = function(req, res, padId) //if the file ending is known, continue as normal if(fileEndingKnown) { callback(); + + return; } + //we need to rename this file with a .txt ending - else { - 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"); - } + 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){ - destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + ".htm"); + destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension); // Logic for allowing external Import Plugins hooks.aCallAll("import", {srcFile: srcFile, destFile: destFile}, function(err, result){ @@ -123,77 +134,88 @@ exports.doImport = function(req, res, padId) }, function(callback) { var fileEnding = path.extname(srcFile).toLowerCase() - var fileIsEtherpad = (fileEnding === ".etherpad"); + var fileIsNotEtherpad = (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{ + if (fileIsNotEtherpad) { callback(); + + return; } + + // 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"); + } + + fs.readFile(srcFile, "utf8", function(err, _text){ + directDatabaseAccess = true; + importEtherpad.setPadRaw(padId, _text, function(err){ + callback(); + }); + }); + }); }, //convert file to html function(callback) { - if(!importHandledByPlugin && !directDatabaseAccess){ - var fileEnding = path.extname(srcFile).toLowerCase(); - var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); - var fileIsTXT = (fileEnding === ".txt"); - if (fileIsTXT) useAbiword = false; // Don't use abiword for text files - // See https://github.com/ether/etherpad-lite/issues/2572 - if (useAbiword && !fileIsHTML) { - abiword.convertFile(srcFile, destFile, "htm", function(err) { - //catch convert errors - if(err) { - console.warn("Converting Error:", err); - return callback("convertFailed"); - } else { - callback(); - } - }); - } else { - // if no abiword only rename - fs.rename(srcFile, destFile, callback); - } - }else{ + if (importHandledByPlugin || directDatabaseAccess) { callback(); + + return; } + + var fileEnding = path.extname(srcFile).toLowerCase(); + var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); + var fileIsTXT = (fileEnding === ".txt"); + if (fileIsTXT) useConvertor = false; // Don't use convertor for text files + // See https://github.com/ether/etherpad-lite/issues/2572 + if (fileIsHTML || (useConvertor === false)) { + // if no convertor only rename + fs.rename(srcFile, destFile, callback); + + return; + } + + convertor.convertFile(srcFile, destFile, exportExtension, function(err) { + //catch convert errors + if(err) { + console.warn("Converting Error:", err); + return callback("convertFailed"); + } + + callback(); + }); }, function(callback) { - if (!useAbiword && !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 240) { - isAscii=false; - break; - } - } - if (isAscii) { - callback(); - } else { - callback("uploadFailed"); - } - }); - } else { + if (useConvertor || directDatabaseAccess) { callback(); + + return; } + + // 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 240) { + isAscii=false; + break; + } + } + + if (!isAscii) { + callback("uploadFailed"); + + return; + } + + callback(); + }); }, //get the pad object @@ -207,32 +229,34 @@ exports.doImport = function(req, res, padId) //read the text function(callback) { - 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>"); - text = text.replace("","-->"); - - //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{ + if (directDatabaseAccess) { callback(); + + return; } + + 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>"); + text = text.replace("","-->"); + + //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(); + } + }); }, //change text of the pad and broadcast the changeset function(callback) { if(!directDatabaseAccess){ var fileEnding = path.extname(srcFile).toLowerCase(); - if (importHandledByPlugin || useAbiword || fileEnding == ".htm" || fileEnding == ".html") { + if (importHandledByPlugin || useConvertor || fileEnding == ".htm" || fileEnding == ".html") { importHtml.setPadHTML(pad, text, function(e){ if(e) apiLogger.warn("Error importing, possibly caused by malformed HTML"); }); @@ -248,33 +272,37 @@ exports.doImport = function(req, res, padId) 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{ + if (directDatabaseAccess) { callback(); + + return; } + + padMessageHandler.updatePadClients(pad, function(){ + callback(); + }); }); }, //clean up temporary files function(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{ + if (directDatabaseAccess) { callback(); + + return; } + + //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); } ], function(err) { var status = "ok"; diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index b575125a2..c52565de3 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -244,59 +244,71 @@ exports.handleMessage = function(client, message) } }; - if (message) { - async.series([ - handleMessageHook, - //check permissions - function(callback) - { - // client tried to auth for the first time (first msg from the client) - if(message.type == "CLIENT_READY") { - createSessionInfo(client, message); - } - - // Note: message.sessionID is an entirely different kind of - // session from the sessions we use here! Beware! - // FIXME: Call our "sessions" "connections". - // FIXME: Use a hook instead - // FIXME: Allow to override readwrite access with readonly - - // 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.") - return; - }else{ - var auth = sessioninfos[client.id].auth; - var checkAccessCallback = function(err, statusObject) - { - if(ERR(err, callback)) return; - - //access was granted - if(statusObject.accessStatus == "grant") - { - callback(); - } - //no access, send the client a message that tell him why - else - { - client.json.send({accessStatus: statusObject.accessStatus}) - } - }; - //check if pad is requested via readOnly - if (auth.padID.indexOf("r.") === 0) { - //Pad is readOnly, first get the real Pad ID - readOnlyManager.getPadId(auth.padID, function(err, value) { - ERR(err); - securityManager.checkAccess(value, auth.sessionID, auth.token, auth.password, checkAccessCallback); - }); - } else { - securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, checkAccessCallback); - } - } - }, - finalHandler - ]); + /* + * In a previous version of this code, an "if (message)" wrapped the + * following async.series(). + * This ugly "!Boolean(message)" is a lame way to exactly negate the truthy + * condition and replace it with an early return, while being sure to leave + * the original behaviour unchanged. + * + * A shallower code could maybe make more evident latent logic errors. + */ + if (!Boolean(message)) { + return; } + + async.series([ + handleMessageHook, + //check permissions + function(callback) + { + // client tried to auth for the first time (first msg from the client) + if(message.type == "CLIENT_READY") { + createSessionInfo(client, message); + } + + // Note: message.sessionID is an entirely different kind of + // session from the sessions we use here! Beware! + // FIXME: Call our "sessions" "connections". + // FIXME: Use a hook instead + // FIXME: Allow to override readwrite access with readonly + + // 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.") + return; + } + + var auth = sessioninfos[client.id].auth; + var checkAccessCallback = function(err, statusObject) + { + if(ERR(err, callback)) return; + + //access was granted + if(statusObject.accessStatus == "grant") + { + callback(); + } + //no access, send the client a message that tell him why + else + { + client.json.send({accessStatus: statusObject.accessStatus}) + } + }; + + //check if pad is requested via readOnly + if (auth.padID.indexOf("r.") === 0) { + //Pad is readOnly, first get the real Pad ID + readOnlyManager.getPadId(auth.padID, function(err, value) { + ERR(err); + securityManager.checkAccess(value, auth.sessionID, auth.token, auth.password, checkAccessCallback); + }); + } else { + securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, checkAccessCallback); + } + }, + finalHandler + ]); } @@ -1268,6 +1280,7 @@ function handleClientReady(client, message) // client is read only you would open a security hole 1 swedish // mile wide... var clientVars = { + "skinName": settings.skinName, "accountPrivs": { "maxRevisions": 100 }, diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index 48dcf56cb..e7b373805 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -12,15 +12,15 @@ var serverName; exports.createServer = function () { console.log("Report bugs at https://github.com/ether/etherpad-lite/issues") - serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)"; + serverName = `Etherpad ${settings.getGitCommit()} (http://etherpad.org)`; - console.log("Your Etherpad version is " + settings.getEpVersion() + " (" + settings.getGitCommit() + ")"); + console.log(`Your Etherpad version is ${settings.getEpVersion()} (${settings.getGitCommit()})`); exports.restartServer(); - console.log("You can access your Etherpad instance at http://" + settings.ip + ":" + settings.port + "/"); + console.log(`You can access your Etherpad instance at http://${settings.ip}:${settings.port}/`); if(!_.isEmpty(settings.users)){ - console.log("The plugin admin page is at http://" + settings.ip + ":" + settings.port + "/admin/plugins"); + console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`); } else{ console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json"); @@ -42,9 +42,9 @@ exports.restartServer = function () { if (settings.ssl) { - console.log( "SSL -- enabled"); - console.log( "SSL -- server key file: " + settings.ssl.key ); - console.log( "SSL -- Certificate Authority's certificate file: " + settings.ssl.cert ); + console.log("SSL -- enabled"); + console.log(`SSL -- server key file: ${settings.ssl.key}`); + console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`); var options = { key: fs.readFileSync( settings.ssl.key ), diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js index 73691837a..baa0bb70a 100644 --- a/src/node/hooks/express/adminsettings.js +++ b/src/node/hooks/express/adminsettings.js @@ -28,15 +28,13 @@ exports.socketio = function (hook_name, args, cb) { if (err) { return console.log(err); } - else - { - //if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result - if(settings.showSettingsInAdminPage === false) { - socket.emit("settings", {results:'NOT_ALLOWED'}); - } - else { - socket.emit("settings", {results: data}); - } + + //if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result + if(settings.showSettingsInAdminPage === false) { + socket.emit("settings", {results:'NOT_ALLOWED'}); + } + else { + socket.emit("settings", {results: data}); } }); }); diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index a9972220b..be3ffb1b4 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -8,26 +8,25 @@ exports.expressCreateServer = function (hook_name, args, cb) { if(!padManager.isValidPadId(padId) || /\/$/.test(req.url)) { res.status(404).send('Such a padname is forbidden'); + return; } - else - { - padManager.sanitizePadId(padId, function(sanitizedPadId) { - //the pad id was sanitized, so we redirect to the sanitized version - if(sanitizedPadId != padId) - { - var real_url = sanitizedPadId; - real_url = encodeURIComponent(real_url); - var query = url.parse(req.url).query; - if ( query ) real_url += '?' + query; - res.header('Location', real_url); - res.status(302).send('You should be redirected to ' + real_url + ''); - } - //the pad id was fine, so just render it - else - { - next(); - } - }); - } + + padManager.sanitizePadId(padId, function(sanitizedPadId) { + //the pad id was sanitized, so we redirect to the sanitized version + if(sanitizedPadId != padId) + { + var real_url = sanitizedPadId; + real_url = encodeURIComponent(real_url); + var query = url.parse(req.url).query; + if ( query ) real_url += '?' + query; + res.header('Location', real_url); + res.status(302).send('You should be redirected to ' + real_url + ''); + } + //the pad id was fine, so just render it + else + { + next(); + } + }); }); } diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index 2840f82ca..69d23ff6d 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -26,13 +26,13 @@ exports.expressCreateServer = function (hook_name, args, cb) { //serve robots.txt args.app.get('/robots.txt', function(req, res) { - var filePath = path.normalize(__dirname + "/../../../static/custom/robots.txt"); + var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt"); res.sendFile(filePath, function(err) { //there is no custom favicon, send the default robots.txt which dissallows all if(err) { - filePath = path.normalize(__dirname + "/../../../static/robots.txt"); + filePath = path.join(settings.root, "src", "static", "robots.txt"); res.sendFile(filePath); } }); @@ -79,13 +79,14 @@ exports.expressCreateServer = function (hook_name, args, cb) { //serve favicon.ico from all path levels except as a pad name args.app.get( /\/favicon.ico$/, function(req, res) { - var filePath = path.normalize(__dirname + "/../../../static/custom/favicon.ico"); + var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico"); + res.sendFile(filePath, function(err) { //there is no custom favicon, send the default favicon if(err) { - filePath = path.normalize(__dirname + "/../../../static/favicon.ico"); + filePath = path.join(settings.root, "src", "static", "favicon.ico"); res.sendFile(filePath); } }); diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js index 1d9ac5d30..2aae5a8ac 100644 --- a/src/node/utils/Abiword.js +++ b/src/node/utils/Abiword.js @@ -52,7 +52,7 @@ if(os.type().indexOf("Windows") > -1) abiword.on('exit', function (code) { if(code != 0) { - return callback("Abiword died with exit code " + code); + return callback(`Abiword died with exit code ${code}`); } if(stdoutBuffer != "") @@ -91,7 +91,7 @@ else abiword.on('exit', function (code) { spawnAbiword(); - stdoutCallback("Abiword died with exit code " + code); + stdoutCallback(`Abiword died with exit code ${code}`); }); //delegate the processing of stdout to a other function diff --git a/src/node/utils/AbsolutePaths.js b/src/node/utils/AbsolutePaths.js new file mode 100644 index 000000000..55dfc98e3 --- /dev/null +++ b/src/node/utils/AbsolutePaths.js @@ -0,0 +1,153 @@ +/** + * Library for deterministic relative filename expansion for Etherpad. + */ + +/* + * 2018 - muxator + * + * 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 path = require('path'); +var _ = require('underscore'); + +var absPathLogger = log4js.getLogger('AbsolutePaths'); + +/* + * findEtherpadRoot() computes its value only on first invocation. + * Subsequent invocations are served from this variable. + */ +var etherpadRoot = null; + +/** + * If stringArray's last elements are exactly equal to lastDesiredElements, + * returns a copy in which those last elements are popped, or false otherwise. + * + * @param {string[]} stringArray - The input array. + * @param {string[]} lastDesiredElements - The elements to remove from the end + * of the input array. + * @return {string[]|boolean} The shortened array, or false if there was no + * overlap. + */ +var popIfEndsWith = function(stringArray, lastDesiredElements) { + if (stringArray.length <= lastDesiredElements.length) { + absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1 } elements`); + + return false; + } + + const lastElementsFound = _.last(stringArray, lastDesiredElements.length); + + if (_.isEqual(lastElementsFound, lastDesiredElements)) { + return _.initial(stringArray, lastDesiredElements.length); + } + + absPathLogger.debug(`${stringArray.join(path.sep)} does not end with "${lastDesiredElements.join(path.sep)}"`); + return false; +}; + +/** + * Heuristically computes the directory in which Etherpad is installed. + * + * All the relative paths have to be interpreted against this absolute base + * path. Since the Unix and Windows install have a different layout on disk, + * they are treated as two special cases. + * + * The path is computed only on first invocation. Subsequent invocations return + * a cached value. + * + * The cached value is stored in AbsolutePaths.etherpadRoot via a side effect. + * + * @return {string} The identified absolute base path. If such path cannot be + * identified, prints a log and exits the application. + */ +exports.findEtherpadRoot = function() { + if (etherpadRoot !== null) { + return etherpadRoot; + } + + const findRoot = require('find-root'); + const foundRoot = findRoot(__dirname); + + var directoriesToStrip; + if (process.platform === 'win32') { + /* + * Given the structure of our Windows package, foundRoot's value + * will be the following on win32: + * + * \node_modules\ep_etherpad-lite + */ + directoriesToStrip = ['node_modules', 'ep_etherpad-lite']; + } else { + /* + * On Unix platforms, foundRoot's value will be: + * + * \src + */ + directoriesToStrip = ['src']; + } + + const maybeEtherpadRoot = popIfEndsWith(foundRoot.split(path.sep), directoriesToStrip); + if (maybeEtherpadRoot === false) { + absPathLogger.error(`Could not identity Etherpad base path in this ${process.platform} installation in "${foundRoot}"`); + process.exit(1); + } + + // SIDE EFFECT on this module-level variable + etherpadRoot = maybeEtherpadRoot.join(path.sep); + + if (path.isAbsolute(etherpadRoot)) { + return etherpadRoot; + } + + absPathLogger.error(`To run, Etherpad has to identify an absolute base path. This is not: "${etherpadRoot}"`); + process.exit(1); +}; + +/** + * Receives a filesystem path in input. If the path is absolute, returns it + * unchanged. If the path is relative, an absolute version of it is returned, + * built prepending exports.findEtherpadRoot() to it. + * + * @param {string} somePath - an absolute or relative path + * @return {string} An absolute path. If the input path was already absolute, + * it is returned unchanged. Otherwise it is interpreted + * relative to exports.root. + */ +exports.makeAbsolute = function(somePath) { + if (path.isAbsolute(somePath)) { + return somePath; + } + + const rewrittenPath = path.normalize(path.join(exports.findEtherpadRoot(), somePath)); + + absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`); + return rewrittenPath; +}; + +/** + * Returns whether arbitraryDir is a subdirectory of parent. + * + * @param {string} parent - a path to check arbitraryDir against + * @param {string} arbitraryDir - the function will check if this directory is + * a subdirectory of the base one + * @return {boolean} + */ +exports.isSubdir = function(parent, arbitraryDir) { + // modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825 + const relative = path.relative(parent, arbitraryDir); + const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); + + return isSubdir; +}; diff --git a/src/node/utils/LibreOffice.js b/src/node/utils/LibreOffice.js index 3cf63e1d6..d72b93249 100644 --- a/src/node/utils/LibreOffice.js +++ b/src/node/utils/LibreOffice.js @@ -18,6 +18,7 @@ var async = require("async"); var fs = require("fs"); +var log4js = require('log4js'); var os = require("os"); var path = require("path"); var settings = require("./Settings"); @@ -26,6 +27,8 @@ var spawn = require("child_process").spawn; // Conversion tasks will be queued up, so we don't overload the system var queue = async.queue(doConvertTask, 1); +var libreOfficeLogger = log4js.getLogger('LibreOffice'); + /** * Convert a file from one type to another * @@ -56,13 +59,18 @@ function doConvertTask(task, callback) { var tmpDir = os.tmpdir(); async.series([ - // Generate a PDF file with LibreOffice + /* + * use LibreOffice to convert task.srcFile to another format, given in + * task.type + */ function(callback) { + libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`); var soffice = spawn(settings.soffice, [ '--headless', '--invisible', '--nologo', '--nolockcheck', + '--writer', '--convert-to', task.type, task.srcFile, '--outdir', tmpDir @@ -80,22 +88,24 @@ function doConvertTask(task, callback) { stdoutBuffer += data.toString(); }); - // Throw an exception if libreoffice failed soffice.on('exit', function(code) { if (code != 0) { - return callback("LibreOffice died with exit code " + code + " and message: " + stdoutBuffer); + // Throw an exception if libreoffice failed + return callback(`LibreOffice died with exit code ${code} and message: ${stdoutBuffer}`); } + // if LibreOffice exited succesfully, go on with processing callback(); }) }, - // Move the PDF file to the correct place + // Move the converted file to the correct place function(callback) { var filename = path.basename(task.srcFile); - var pdfFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.type; - var pdfPath = path.join(tmpDir, pdfFilename); - fs.rename(pdfPath, task.destFile, callback); + var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.type; + var sourcePath = path.join(tmpDir, sourceFilename); + libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`); + fs.rename(sourcePath, task.destFile, callback); } ], function(err) { // Invoke the callback for the local queue diff --git a/src/node/utils/NodeVersion.js b/src/node/utils/NodeVersion.js index 49c1efe85..2909d5bc9 100644 --- a/src/node/utils/NodeVersion.js +++ b/src/node/utils/NodeVersion.js @@ -3,6 +3,8 @@ */ /* + * 2018 - muxator + * * 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 diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 08ace60ca..508e6148d 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -19,6 +19,7 @@ * limitations under the License. */ +var absolutePaths = require('./AbsolutePaths'); var fs = require("fs"); var os = require("os"); var path = require('path'); @@ -31,7 +32,8 @@ var suppressDisableMsg = " -- To suppress these warning messages change suppress var _ = require("underscore"); /* Root path of the installation */ -exports.root = path.normalize(path.join(npm.dir, "..")); +exports.root = absolutePaths.findEtherpadRoot(); +console.log(`All relative paths will be interpreted relative to the identified Etherpad base dir: ${exports.root}`); /** * The app title, visible e.g. in the browser window @@ -45,6 +47,14 @@ exports.favicon = "favicon.ico"; exports.faviconPad = "../" + exports.favicon; exports.faviconTimeslider = "../../" + exports.favicon; +/* + * Skin name. + * + * Initialized to null, so we can spot an old configuration file and invite the + * user to update it before falling back to the default. + */ +exports.skinName = null; + /** * The IP ep-lite should listen to */ @@ -78,7 +88,7 @@ exports.dbType = "dirty"; /** * This setting is passed with dbType to ueberDB to set up the database */ -exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") }; +exports.dbSettings = { "filename" : path.join(exports.root, "var/dirty.db") }; /** * The default Text of a new pad @@ -339,34 +349,27 @@ exports.getEpVersion = function() { exports.reloadSettings = function reloadSettings() { // Discover where the settings file lives - var settingsFilename = argv.settings || "settings.json"; - + var settingsFilename = absolutePaths.makeAbsolute(argv.settings || "settings.json"); + // Discover if a credential file exists - var credentialsFilename = argv.credentials || "credentials.json"; - - if (path.resolve(settingsFilename)===settingsFilename) { - settingsFilename = path.resolve(settingsFilename); - } else { - settingsFilename = path.resolve(path.join(exports.root, settingsFilename)); - } - - if (path.resolve(credentialsFilename)===credentialsFilename) { - credentialsFilename = path.resolve(credentialsFilename); - } + var credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || "credentials.json"); var settingsStr, credentialsStr; try{ //read the settings sync settingsStr = fs.readFileSync(settingsFilename).toString(); + console.info(`Settings loaded from: ${settingsFilename}`); } catch(e){ - console.warn('No settings file found. Continuing using defaults!'); + console.warn(`No settings file found in ${settingsFilename}. Continuing using defaults!`); } try{ //read the credentials sync credentialsStr = fs.readFileSync(credentialsFilename).toString(); + console.info(`Credentials file read from: ${credentialsFilename}`); } catch(e){ // Doesn't matter if no credentials file found.. + console.info(`No credentials file found in ${credentialsFilename}. Ignoring.`); } // try to parse the settings @@ -378,7 +381,7 @@ exports.reloadSettings = function reloadSettings() { settings = JSON.parse(settingsStr); } }catch(e){ - console.error('There was an error processing your settings.json file: '+e.message); + console.error(`There was an error processing your settings file from ${settingsFilename}:` + e.message); process.exit(1); } @@ -390,10 +393,10 @@ exports.reloadSettings = function reloadSettings() { //loop trough the settings for(var i in settings) { - //test if the setting start with a low character + //test if the setting start with a lowercase character if(i.charAt(0).search("[a-z]") !== 0) { - console.warn("Settings should start with a low character: '" + i + "'"); + console.warn(`Settings should start with a lowercase character: '${i}'`); } //we know this setting, so we overwrite it @@ -409,17 +412,17 @@ exports.reloadSettings = function reloadSettings() { //this setting is unkown, output a warning and throw it away else { - console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed"); + console.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`); } } //loop trough the settings for(var i in credentials) { - //test if the setting start with a low character + //test if the setting start with a lowercase character if(i.charAt(0).search("[a-z]") !== 0) { - console.warn("Settings should start with a low character: '" + i + "'"); + console.warn(`Settings should start with a lowercase character: '${i}'`); } //we know this setting, so we overwrite it @@ -435,7 +438,7 @@ exports.reloadSettings = function reloadSettings() { //this setting is unkown, output a warning and throw it away else { - console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed"); + console.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`); } } @@ -444,6 +447,42 @@ exports.reloadSettings = function reloadSettings() { process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug log4js.replaceConsole(); + if (!exports.skinName) { + console.warn(`No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "no-skin".`); + exports.skinName = "no-skin"; + } + + // checks if skinName has an acceptable value, otherwise falls back to "no-skin" + if (exports.skinName) { + const skinBasePath = path.join(exports.root, "src", "static", "skins"); + const countPieces = exports.skinName.split(path.sep).length; + + if (countPieces != 1) { + console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "no-skin".`); + + exports.skinName = "no-skin"; + } + + // informative variable, just for the log messages + var skinPath = path.normalize(path.join(skinBasePath, exports.skinName)); + + // what if someone sets skinName == ".." or "."? We catch him! + if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { + console.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. Falling back to the default "no-skin".`); + + exports.skinName = "no-skin"; + skinPath = path.join(skinBasePath, exports.skinName); + } + + if (fs.existsSync(skinPath) === false) { + console.error(`Skin path ${skinPath} does not exist. Falling back to the default "no-skin".`); + exports.skinName = "no-skin"; + skinPath = path.join(skinBasePath, exports.skinName); + } + + console.info(`Using skin "${exports.skinName}" in dir: ${skinPath}`); + } + if(exports.abiword){ // Check abiword actually exists if(exports.abiword != null) @@ -476,10 +515,12 @@ exports.reloadSettings = function reloadSettings() { } if (!exports.sessionKey) { - var sessionkeyFilename = argv.sessionkey || "./SESSIONKEY.txt"; + var sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || "./SESSIONKEY.txt"); try { exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8"); + console.info(`Session key loaded from: ${sessionkeyFilename}`); } catch(e) { + console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`); exports.sessionKey = randomString(32); fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8"); } @@ -492,7 +533,9 @@ exports.reloadSettings = function reloadSettings() { if(!exports.suppressErrorsInPadText){ exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg; } - console.warn(dirtyWarning); + + exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename); + console.warn(dirtyWarning + ` File location: ${exports.dbSettings.filename}`); } }; diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.js index eaa1d4217..67ce12593 100644 --- a/src/node/utils/toolbar.js +++ b/src/node/utils/toolbar.js @@ -116,7 +116,7 @@ _.extend(Button.prototype, { -SelectButton = function (attributes) { +var SelectButton = function (attributes) { this.attributes = attributes; this.options = []; }; diff --git a/src/package.json b/src/package.json index fcf5b97c0..1b9060942 100644 --- a/src/package.json +++ b/src/package.json @@ -13,6 +13,9 @@ { "name": "John McLear" }, + { + "name": "Antonio Muci" + }, { "name": "Hans Pinckaers" }, @@ -32,26 +35,27 @@ "channels": "0.0.4", "cheerio": "0.20.0", "clean-css": "3.4.19", - "cookie-parser": "1.3.4", - "ejs": "2.5.7", + "cookie-parser": "1.4.3", + "ejs": "2.6.1", "etherpad-require-kernel": "1.0.9", "etherpad-yajsml": "0.0.2", - "express": "4.16.3", + "express": "4.16.4", "express-session": "1.15.6", + "find-root": "1.1.0", "formidable": "1.2.1", - "graceful-fs": "4.1.3", + "graceful-fs": "4.1.11", "jsonminify": "0.4.1", "languages4translatewiki": "0.1.3", "log4js": "0.6.35", "measured-core": "1.11.2", - "npm": "6.4.0", + "npm": "6.4.1", "object.values": "^1.0.4", - "request": "2.83.0", + "request": "2.88.0", "resolve": "1.1.7", "security": "1.0.0", - "semver": "5.1.0", + "semver": "5.6.0", "slide": "1.1.6", - "socket.io": "1.7.3", + "socket.io": "2.1.1", "swagger-node-express": "2.1.3", "tinycon": "0.0.1", "ueberdb2": "0.4.0", @@ -66,7 +70,7 @@ "mocha": "5.2.0", "nyc": "^12.0.2", "supertest": "3.0.0", - "wd": "1.10.3" + "wd": "1.11.1" }, "engines": { "node": ">=6.9.0", @@ -74,11 +78,12 @@ }, "repository": { "type": "git", - "url": "http://github.com/ether/etherpad-lite.git" + "url": "https://github.com/ether/etherpad-lite.git" }, "scripts": { "test": "nyc mocha --timeout 5000 ../tests/backend/specs/api" }, - "version": "1.7.0", + "version": "1.7.5", "license": "Apache-2.0" } + diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 484e6f2ab..18b936478 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -791,6 +791,10 @@ table#otheruserstable { border: 0 } +.buttonicon:before { + font-family: "fontawesome-etherpad"; +} + .buttonicon:focus{ border: 1px solid #666; } @@ -1320,19 +1324,7 @@ input[type=checkbox] { } -[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; -} - +[data-icon]:before, [class^="icon-"]:before, [class*=" icon-"]:before { font-family: "fontawesome-etherpad" !important; diff --git a/src/static/custom/.gitignore b/src/static/custom/.gitignore deleted file mode 100644 index aae16bb24..000000000 --- a/src/static/custom/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!.gitignore -!*.template diff --git a/src/static/font/config.json b/src/static/font/config.json index f9c2c3d65..1c636281d 100644 --- a/src/static/font/config.json +++ b/src/static/font/config.json @@ -347,6 +347,24 @@ "css": "slideshare", "code": 59441, "src": "fontawesome" + }, + { + "uid": "d35a1d35efeb784d1dc9ac18b9b6c2b6", + "css": "pencil", + "code": 59449, + "src": "fontawesome" + }, + { + "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1", + "css": "link", + "code": 59450, + "src": "fontawesome" + }, + { + "uid": "8fb55fd696d9a0f58f3b27c1d8633750", + "css": "table", + "code": 61646, + "src": "fontawesome" } ] } \ No newline at end of file diff --git a/src/static/font/fontawesome-etherpad.eot b/src/static/font/fontawesome-etherpad.eot index 284149e2b..35a002de2 100644 Binary files a/src/static/font/fontawesome-etherpad.eot and b/src/static/font/fontawesome-etherpad.eot differ diff --git a/src/static/font/fontawesome-etherpad.svg b/src/static/font/fontawesome-etherpad.svg index a64ba864e..b69d0d974 100644 --- a/src/static/font/fontawesome-etherpad.svg +++ b/src/static/font/fontawesome-etherpad.svg @@ -1,68 +1,130 @@ -Copyright (C) 2015 by original authors @ fontello.com +Copyright (C) 2018 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/static/font/fontawesome-etherpad.ttf b/src/static/font/fontawesome-etherpad.ttf index f596b4fe6..280a3d460 100644 Binary files a/src/static/font/fontawesome-etherpad.ttf and b/src/static/font/fontawesome-etherpad.ttf differ diff --git a/src/static/font/fontawesome-etherpad.woff b/src/static/font/fontawesome-etherpad.woff index ebca7fffa..c66051390 100644 Binary files a/src/static/font/fontawesome-etherpad.woff and b/src/static/font/fontawesome-etherpad.woff differ diff --git a/src/static/js/ace.js b/src/static/js/ace.js index 9f219e6c2..038ead7c2 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -186,7 +186,7 @@ function Ace2Editor() } for (var i = 0, ii = remoteFiles.length; i < ii; i++) { var file = remoteFiles[i]; - buffer.push(''); + buffer.push(''); } } @@ -230,7 +230,6 @@ function Ace2Editor() // disableCustomScriptsAndStyles can be used to disable loading of custom scripts if(!clientVars.disableCustomScriptsAndStyles){ $$INCLUDE_CSS("../static/css/pad.css"); - $$INCLUDE_CSS("../static/custom/pad.css"); } var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ @@ -240,6 +239,7 @@ function Ace2Editor() return '../static/plugins/' + path; }); includedCSS = includedCSS.concat(additionalCSS); + $$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css"); pushStyleTagsFor(iframeHTML, includedCSS); @@ -314,7 +314,6 @@ window.onload = function () {\n\ var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)}; $$INCLUDE_CSS("../static/css/iframe_editor.css"); $$INCLUDE_CSS("../static/css/pad.css"); - $$INCLUDE_CSS("../static/custom/pad.css"); var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ @@ -324,12 +323,13 @@ window.onload = function () {\n\ return '../static/plugins/' + path } ); includedCSS = includedCSS.concat(additionalCSS); + $$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css"); pushStyleTagsFor(outerHTML, includedCSS); // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // (throbs busy while typing) - outerHTML.push('', '', scriptTag(outerScript), '
x
'); + outerHTML.push('', '', scriptTag(outerScript), '
x
'); var outerFrame = document.createElement("IFRAME"); outerFrame.name = "ace_outer"; diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 8b0e2c3e9..a34b94e59 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -975,9 +975,8 @@ function Ace2Inner(){ showsuserselections: setClassPresenceNamed(root, "userSelections"), showslinenumbers : function(value){ hasLineNumbers = !! value; - // disable line numbers on mobile devices - if (browser.mobile) hasLineNumbers = false; setClassPresence(sideDiv, "sidedivhidden", !hasLineNumbers); + setClassPresence(sideDiv.parentNode, "sidediv-hidden", !hasLineNumbers); fixView(); }, grayedout: setClassPresenceNamed(outerWin.document.body, "grayedout"), @@ -5404,7 +5403,7 @@ function Ace2Inner(){ // didn't do this special case, we would miss out on any top margin // included on the first line. The default stylesheet doesn't add // extra margins/padding, but plugins might. - h = b.nextSibling.offsetTop - window.getComputedStyle(doc.body).getPropertyValue("padding-top"); + h = b.nextSibling.offsetTop - parseInt(window.getComputedStyle(doc.body).getPropertyValue("padding-top").split('px')[0]); } else { h = b.nextSibling.offsetTop - b.offsetTop; } diff --git a/src/static/js/pad.js b/src/static/js/pad.js index de613910d..a72225979 100644 --- a/src/static/js/pad.js +++ b/src/static/js/pad.js @@ -314,7 +314,7 @@ function handshake() // If the Monospacefont value is set to true then change it to monospace. if (settings.useMonospaceFontGlobal == true) { - pad.changeViewOption('useMonospaceFont', true); + pad.changeViewOption('padFontFamily', 'monospace'); } // if the globalUserName value is set we need to tell the server and the client about the new authorname if (settings.globalUserName !== false) @@ -560,19 +560,7 @@ var pad = { if(padcookie.getPref("rtlIsTrue") == true){ pad.changeViewOption('rtlIsTrue', true); } - - - var fonts = ['useMonospaceFont', 'useMontserratFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', - 'useGeorgiaFont', 'useImpactFont', 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useRobotoMonoFont', - 'useTahomaFont', 'useTimesNewRomanFont', 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', - 'useWingDingsFont', 'useSansSerifFont', 'useSerifFont']; - - - $.each(fonts, function(i, font){ - if(padcookie.getPref(font) == true){ - pad.changeViewOption(font, true); - } - }) + pad.changeViewOption('padFontFamily', padcookie.getPref("padFontFamily")); hooks.aCallAll("postAceInit", {ace: padeditor.ace, pad: pad}); } diff --git a/src/static/js/pad_editor.js b/src/static/js/pad_editor.js index 0b282659d..ec2339562 100644 --- a/src/static/js/pad_editor.js +++ b/src/static/js/pad_editor.js @@ -29,14 +29,6 @@ var padeditor = (function() var pad = undefined; var settings = undefined; - // Array of available fonts - - var fonts = ['useMonospaceFont', 'useMontserratFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', - 'useGeorgiaFont', 'useImpactFont', 'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useRobotoMonoFont', - 'useTahomaFont', 'useTimesNewRomanFont', 'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', - 'useWingDingsFont', 'useSansSerifFont', 'useSerifFont']; - - var self = { ace: null, // this is accessed directly from other files @@ -97,12 +89,7 @@ var padeditor = (function() // font family change $("#viewfontmenu").change(function() { - $.each(fonts, function(i, font){ - var sfont = font.replace("use",""); - sfont = sfont.replace("Font",""); - sfont = sfont.toLowerCase(); - pad.changeViewOption(font, $("#viewfontmenu").val() == sfont); - }); + pad.changeViewOption('padFontFamily', $("#viewfontmenu").val()); }); // Language @@ -154,45 +141,29 @@ var padeditor = (function() 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", "monospace"); - if(font === "montserrat") self.ace.setProperty("textface", "Montserrat"); - if(font === "opendyslexic") self.ace.setProperty("textface", "OpenDyslexic"); - if(font === "comicsans") self.ace.setProperty("textface", "'Comic Sans MS','Comic Sans',cursive"); - if(font === "georgia") self.ace.setProperty("textface", "Georgia,'Bitstream Charter',serif"); - if(font === "impact") self.ace.setProperty("textface", "Impact,Haettenschweiler,'Arial Black',sans-serif"); - if(font === "lucida") self.ace.setProperty("textface", "Lucida,'Lucida Serif','Lucida Bright',serif"); - if(font === "lucidasans") self.ace.setProperty("textface", "'Lucida Sans','Lucida Grande','Lucida Sans Unicode','Luxi Sans',sans-serif"); - if(font === "palatino") self.ace.setProperty("textface", "Palatino,'Palatino Linotype','URW Palladio L',Georgia,serif"); - if(font === "robotomono") self.ace.setProperty("textface", "RobotoMono"); - if(font === "tahoma") self.ace.setProperty("textface", "Tahoma,sans-serif"); - if(font === "timesnewroman") self.ace.setProperty("textface", "'Times New Roman',Times,serif"); - if(font === "trebuchet") self.ace.setProperty("textface", "'Trebuchet MS',sans-serif"); - if(font === "verdana") self.ace.setProperty("textface", "Verdana,'DejaVu Sans',sans-serif"); - 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", "sans-serif"); - if(font === "serif") self.ace.setProperty("textface", "serif"); - - // $("#viewfontmenu").val(font); - normalFont = false; - } - }); - - // No font has been previously selected so use the Normal font - if(normalFont){ - self.ace.setProperty("textface", "'Helvetica Neue',Arial, sans-serif"); - // $("#viewfontmenu").val("normal"); + var fontFamily = newOptions['padFontFamily']; + switch (fontFamily) { + case "monospace": self.ace.setProperty("textface", "monospace"); break; + case "montserrat": self.ace.setProperty("textface", "Montserrat"); break; + case "opendyslexic": self.ace.setProperty("textface", "OpenDyslexic"); break; + case "comicsans": self.ace.setProperty("textface", "'Comic Sans MS','Comic Sans',cursive"); break; + case "georgia": self.ace.setProperty("textface", "Georgia,'Bitstream Charter',serif"); break; + case "impact": self.ace.setProperty("textface", "Impact,Haettenschweiler,'Arial Black',sans-serif"); break; + case "lucida": self.ace.setProperty("textface", "Lucida,'Lucida Serif','Lucida Bright',serif"); break; + case "lucidasans": self.ace.setProperty("textface", "'Lucida Sans','Lucida Grande','Lucida Sans Unicode','Luxi Sans',sans-serif"); break; + case "palatino": self.ace.setProperty("textface", "Palatino,'Palatino Linotype','URW Palladio L',Georgia,serif"); break; + case "robotomono": self.ace.setProperty("textface", "RobotoMono"); break; + case "tahoma": self.ace.setProperty("textface", "Tahoma,sans-serif"); break; + case "timesnewroman": self.ace.setProperty("textface", "'Times New Roman',Times,serif"); break; + case "trebuchet": self.ace.setProperty("textface", "'Trebuchet MS',sans-serif"); break; + case "verdana": self.ace.setProperty("textface", "Verdana,'DejaVu Sans',sans-serif"); break; + case "symbol": self.ace.setProperty("textface", "Symbol"); break; + case "webdings": self.ace.setProperty("textface", "Webdings"); break; + case "wingdings": self.ace.setProperty("textface", "Wingdings"); break; + case "sansserif": self.ace.setProperty("textface", "sans-serif"); break; + case "serif": self.ace.setProperty("textface", "serif"); break; + default: self.ace.setProperty("textface", ""); break; } - }, dispose: function() { diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js index cf5fcc4eb..3d19107d6 100644 --- a/src/static/js/pluginfw/hooks.js +++ b/src/static/js/pluginfw/hooks.js @@ -125,3 +125,29 @@ exports.callAllStr = function(hook_name, args, sep, pre, post) { } return newCallhooks.join(sep || ""); } + +/* + * Returns an array containing the names of the installed client-side plugins + * + * If no client-side plugins are installed, returns an empty array. + * Duplicate names are always discarded. + * + * A client-side plugin is a plugin that implements at least one client_hook + * + * EXAMPLE: + * No plugins: [] + * Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ] + */ +exports.clientPluginNames = function() { + if (!(exports.plugins)) { + return []; + } + + var client_plugin_names = _.uniq( + exports.plugins.parts + .filter(function(part) { return part.hasOwnProperty('client_hooks'); }) + .map(function(part) { return part['plugin']; }) + ); + + return client_plugin_names; +} diff --git a/src/static/js/timeslider.js b/src/static/js/timeslider.js index 08d6f68d6..7e744118f 100644 --- a/src/static/js/timeslider.js +++ b/src/static/js/timeslider.js @@ -166,25 +166,27 @@ function handleClientVars(message) // 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"); + switch (font) { + case "monospace": setFont("Courier new");break; + case "opendyslexic": setFont("OpenDyslexic");break; + case "comicsans": setFont("Comic Sans MS");break; + case "georgia": setFont("Georgia");break; + case "impact": setFont("Impact");break; + case "lucida": setFont("Lucida");break; + case "lucidasans": setFont("Lucida Sans Unicode");break; + case "palatino": setFont("Palatino Linotype");break; + case "tahoma": setFont("Tahoma");break; + case "timesnewroman": setFont("Times New Roman");break; + case "trebuchet": setFont("Trebuchet MS");break; + case "verdana": setFont("Verdana");break; + case "symbol": setFont("Symbol");break; + case "webdings": setFont("Webdings");break; + case "wingdings": setFont("Wingdings");break; + case "sansserif": setFont("MS Sans Serif");break; + case "serif": setFont("MS Serif");break; + default: setFont("");break; + } }); - } function setFont(font){ diff --git a/src/static/skins/colibris/images/fond.jpg b/src/static/skins/colibris/images/fond.jpg new file mode 100644 index 000000000..81357c7bb Binary files /dev/null and b/src/static/skins/colibris/images/fond.jpg differ diff --git a/src/static/skins/colibris/images/screenshot.png b/src/static/skins/colibris/images/screenshot.png new file mode 100644 index 000000000..d461192f6 Binary files /dev/null and b/src/static/skins/colibris/images/screenshot.png differ diff --git a/src/static/skins/colibris/index.css b/src/static/skins/colibris/index.css new file mode 100644 index 000000000..1526f8560 --- /dev/null +++ b/src/static/skins/colibris/index.css @@ -0,0 +1,82 @@ +#button, +body, +form { + border: none +} + +body { + background: url(images/fond.jpg) center center no-repeat fixed #fff; + font-family: Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; + font-size: 16px; + line-height: 1.42857143; + color: #333; + display: flex; + align-items: center; + justify-content: center; + background-size: cover; +} + +#wrapper { + border-top: none; + margin-top: 0; + padding: 0; + background: 0 0; + box-shadow: none +} + +#inner { + background: transparent; + padding-top: 0; + width: 350px; + max-width: 350px; + text-align: center; + color:#FFF; +} + +#label { + text-shadow: none; + color: #FFF; + font-weight: normal; + text-align: center; +} + +#button { + margin: 0 auto; + text-align: center; + width: 100%; + text-shadow: none; + font-size: 23px; + line-height: 1.8; + color: #64d29b; + background: #586a69; + border-radius: 3px; + box-shadow: none; + height: 53px; + border: none; + display: block; +} + + +button[type=submit] { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + left: 305px; + color: #64d29b; + background: #586a69; + border: none; +} + +#button:hover, +button[type=submit]:hover { + cursor: pointer; + background: #64d29b; + border: 2px solid #586a69; + color: #586a69; +} + +#padname { + height: 38px; + max-width: 350px; + padding: 0 12px; + position: relative; +} \ No newline at end of file diff --git a/src/static/custom/js.template b/src/static/skins/colibris/index.js similarity index 100% rename from src/static/custom/js.template rename to src/static/skins/colibris/index.js diff --git a/src/static/skins/colibris/pad.css b/src/static/skins/colibris/pad.css new file mode 100644 index 000000000..3164808f0 --- /dev/null +++ b/src/static/skins/colibris/pad.css @@ -0,0 +1,127 @@ +@import url("src/layout.css"); +@import url("src/pad-editor.css"); + +@import url("src/components/buttons.css"); +@import url("src/components/popup.css"); + +@import url("src/components/chat.css"); +@import url("src/components/sidediv.css"); +@import url("src/components/gritter.css"); +@import url("src/components/table-of-content.css"); +@import url("src/components/toolbar.css"); +@import url("src/components/users.css"); +@import url("src/components/form.css"); +@import url("src/components/import-export.css"); + +@import url("src/plugins/brightcolorpicker.css"); +@import url("src/plugins/comments_page.css"); +@import url("src/plugins/font_color.css"); +@import url("src/plugins/set_title_on_pad.css"); +@import url("src/plugins/tables2.css"); +@import url("src/plugins/embedded_hyperlinks.css"); +@import url("src/plugins/author_hover.css"); + +/* NEUTRAL COLOR */ +body, +#innerdocbody +#users, +#chattext, +#chatinput, +#chatlabel, +#toc, +#tocItems a, +.toolbar ul li a:hover .buttonicon, +.toolbar ul li a, +.toolbar ul li select, +#mycolorpickercancel, +.btn-default, +.color\:black, +[data-color=black], +#chattext.authorColors p, #chattext.authorColors span, +#chattext .time, +#tbl_context_menu ul .yuimenuitemlabel, +.yui-skin-sam .yui-split-button button em:not(.color-picker-button), +#yui-picker-panel .button-group .yui-button:first-child button, +#newComment .sidebar-comment input[type=reset], #newComment .sidebar-comment input[type=reset]:hover, +#newComment .sidebar-comment input[type=submit]:hover, +.suggestion, .comment-reply-input, .reply-suggestion p:not(.reply-comment-suggest-from-p), .comment-text, +.sidebar-comment textarea, .reply-comment-suggest label, .comment-suggest label, .comment-reply-input +#comments, #newComments, .reply-comment-suggest-from-p, +.comment-changeFrom-value, +.comment-changeTo-value, +.reply-suggestion .reply-comment-suggest-from, +.suggestion .comment-suggest-from, +.hyperlink-dialog>.hyperlink-url, +.timeslider #padcontent span , +.exporttype, .timeslider #export > p +{ color: #2E3338 !important; } + +/* MENUS ICONS */ +#edit_title:before, +#tbl-menu:before +{ color: #767676 !important; } +/* MENU BUTTONS */ +.timeslider #editbar .buttontext +{ background-color: #767676 !important; } + +/* PRIMARY BUTTONS */ +#mycolorpickersave, +.btn-primary, +#tbl_btn_close, +#save_title button, +#yui-picker-panel .button-group .yui-button:last-child button, +#newComment.sidebar-comment input[type=submit], +.comment-changeTo-approve input[type=submit], +.hyperlink-dialog>.hyperlink-save, +#importsubmitinput, #forcereconnect +{ + background-color: #64d29b; + color: white; +} + +/* PRIMARY COLOR */ +h1, +#titlelabel, +.yui-skin-sam .yui-panel .hd, +p[data-l10n-id="ep_comments_page.comment"], +.comment-reply-input-label span, +.stepper, #importmessageabiword, #importmessageabiword > a +{ color: #64d29b; } +#ui-slider-handle, #playpause_button_icon { + background-color: #64d29b; +} +.stepper { + border-color: #64d29b; +} + +/* BACKGROUND COLOR */ +#editorcontainer, #padmain { + background-color: #f9f9f9 !important; +} + + +/* NEUTRAL FONT */ +body, +#innerdocbody, +#chatinput, +.toolbar ul li select, +button, input, select, textarea, +td[name=tData], +#comments, #newComments, +#sidediv, +#comments .sidebar-comment, +#timeslider #timer, .exporttype +{ font-family: Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } + +/* PRIMARY FONT */ +h1, +#titlelabel, +#chatlabel, +.btn, +.yui-skin-sam .yui-panel .hd, +.reply-suggestion p:not(.reply-comment-suggest-from-p), +p[data-l10n-id="ep_comments_page.comment"], +#newComment.sidebar-comment input[type=submit], +.comment-changeTo-approve input[type=submit], +.hyperlink-dialog>.hyperlink-save +{ font-family: Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; } diff --git a/src/static/skins/colibris/pad.js b/src/static/skins/colibris/pad.js new file mode 100644 index 000000000..88ddce4c3 --- /dev/null +++ b/src/static/skins/colibris/pad.js @@ -0,0 +1,4 @@ +function customStart() +{ + $('#pad_title').show(); +} diff --git a/src/static/skins/colibris/src/components/buttons.css b/src/static/skins/colibris/src/components/buttons.css new file mode 100644 index 000000000..93879023e --- /dev/null +++ b/src/static/skins/colibris/src/components/buttons.css @@ -0,0 +1,21 @@ +.btn, #mycolorpickercancel, #mycolorpickersave, #save_title button, #yui-picker-panel .button-group .yui-button button, .hyperlink-dialog>.hyperlink-save, .timeslider #editbar .buttontext, +#importsubmitinput, #forcereconnect +{ + margin-right: 10px; + padding: 5px 20px; + border-radius: 4px; + font-size: 13px; + line-height: 1.5; + width: auto; + border: none !important; + font-weight: bold; + text-transform: uppercase; + position: relative; + background: none; + top: 0; + left: 0; +} + +.btn:hover, #mycolorpickercancel:hover, #mycolorpickersave:hover, #save_title button:hover, .hyperlink-dialog>.hyperlink-save:hover, #importsubmitinput:hover, #forcereconnect:hover { + cursor: pointer; +} diff --git a/src/static/skins/colibris/src/components/chat.css b/src/static/skins/colibris/src/components/chat.css new file mode 100644 index 000000000..eb1dce445 --- /dev/null +++ b/src/static/skins/colibris/src/components/chat.css @@ -0,0 +1,100 @@ +#chatbox { + background: none; + padding: 0; + background-color: white; + border: none; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + width: 400px; + height: 300px; +} + +#titlebar { + bottom: 0; + line-height: 39px; + height: 44px; + padding: 0 7px; + z-index: 20000; + border-bottom: 1px solid #d2d2d2; +} + +#titlelabel, #chatlabel { + text-transform: uppercase; + font-weight: bold; +} + +#titlelabel { font-size: 16px; } +#chatlabel { margin-right: 15px; } + +#chattext { + top: 45px; + font-size: 13px; + padding: 10px; + bottom: 45px; + overflow-y: auto; +} + +#chattext.authorColors p, #chattext.authorColors span { + background-color: transparent !important; +} + +#chattext p b { + color: #4c4c4c; +} + +#chattext::-webkit-scrollbar-track { + background-color: #f6f6f6; + border: 1px solid #f0f0f0; +} + +#chattext::-webkit-scrollbar { + width: 7px; +} + +#chattext::-webkit-scrollbar-thumb { + border-radius: 10px; + background-color: #C5C5C5; +} + +#chatinputbox { + padding: 0 5px 5px 10px; +} + +#chatinput { + width: calc(100% - 20px); + float: right; +} + +#chatbox.stickyChat { + width: 193px !important; + background-color: #f9f9f9 !important; + border: none !important; +} + +#chatbox.stickyChat #chattext { + padding: 6px; +} + +#chatbox.stickyChat #chatinputbox { + padding: 5px 5px 3px 5px; +} + +#chaticon { + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border: none; + padding: 10px 20px; + right: 30px; +} + +@media (max-width: 720px) { + #chaticon { + right: 0; + bottom: 44px; + } + + #chatbox { + width: 100%; + right: 0; + } + + #titlesticky { display: none; } +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/form.css b/src/static/skins/colibris/src/components/form.css new file mode 100644 index 000000000..74a6fb589 --- /dev/null +++ b/src/static/skins/colibris/src/components/form.css @@ -0,0 +1,8 @@ +#input_title, #chatinput, .hyperlink-dialog>.hyperlink-url { + border: 1px solid #d2d2d2; + height: 18px; + border-radius: 3px; + padding: 8px 10px; + background: none !important; + box-shadow: none !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/gritter.css b/src/static/skins/colibris/src/components/gritter.css new file mode 100644 index 000000000..7c514010c --- /dev/null +++ b/src/static/skins/colibris/src/components/gritter.css @@ -0,0 +1,28 @@ +/* Popups at the bottom of the page to indicate when the pad expires, and others stuff */ + +.gritter-title { + text-shadow: none; +} + +#gritter-notice-wrapper { + background-color: #fff; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border-radius: 3px; + padding: 15px 20px 5px 30px; + bottom: 50px !important; + right: 30px !important; +} + +@media (max-width: 1100px) { + #gritter-notice-wrapper { + display: none; + } +} + +.gritter-item { + font-family: Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important; + color: #4e545b; + font-size: 12px; + line-height: 20px; + padding: 0; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/import-export.css b/src/static/skins/colibris/src/components/import-export.css new file mode 100644 index 000000000..9407ade69 --- /dev/null +++ b/src/static/skins/colibris/src/components/import-export.css @@ -0,0 +1,33 @@ +#exportColumn { + margin-top: 0; + padding-left: 20px; + width: calc(50% - 20px); +} + +#importmessageabiword { + font-style: italic; + font-size: 13px; +} +#importmessageabiword > a { + font-weight: bold; + text-decoration: underline; +} + +.exportlink { + font-size: 14px; + margin: 0; + margin-bottom: 7px; + margin-left: 10px; +} + +.exporttype { + margin-left: 5px; + font-size: 14px; +} + +#importmessagefail { + font-size: 13px; + margin-top: 10px; +} + +#importsubmitinput[disabled] { opacity: .6; } \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/popup.css b/src/static/skins/colibris/src/components/popup.css new file mode 100644 index 000000000..7b3c0381b --- /dev/null +++ b/src/static/skins/colibris/src/components/popup.css @@ -0,0 +1,30 @@ +#users, +#mycolorpicker, +.popup, +.hyperlink-dialog { + border-radius: 3px; + padding: 20px 20px; + background: none; + background-color: white; + border: none; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); +} + +#users input[type=text], +.popup input[type=text] { + border: none !important; + border-bottom: 1px solid #d7d8da !important; +} + +.popup h1 { + margin-bottom: 15px; +} + +.popup h2 { + margin-bottom: 15px; +} + +.popup p { + margin: 10px 0; +} + diff --git a/src/static/skins/colibris/src/components/sidediv.css b/src/static/skins/colibris/src/components/sidediv.css new file mode 100644 index 000000000..88193135b --- /dev/null +++ b/src/static/skins/colibris/src/components/sidediv.css @@ -0,0 +1,30 @@ +#sidediv { + background-color: transparent; + border: none; +} + +#sidedivinner>div:before { + font-size: 13px; + padding-right: 18px !important; + color: #6a6a6b; + text-transform: uppercase; + font-size: 11px !important; + font-weight: bold; +} + +#sidedivinner>div, +#sidedivinner.authorColors>div, +#sidedivinner.authorColors>div.primary-none { + padding-right: 5px !important; + border-right: 5px solid transparent; +} + +#sidedivinner>div { + line-height: 24px; + font-size: 10px !important; + color: #a0a0a0; +} + +#sidedivinner.authorColors>div, #sidedivinner.authorColors>div.primary-none, #sidedivinner>div { + padding-right: 8px !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/table-of-content.css b/src/static/skins/colibris/src/components/table-of-content.css new file mode 100644 index 000000000..b2f26c4a2 --- /dev/null +++ b/src/static/skins/colibris/src/components/table-of-content.css @@ -0,0 +1,11 @@ +#toc { + background: none !important; + background-color: rgb(249, 249, 249) !important; + padding: 20px !important; + width: 146px !important; + padding-left: 15px !important; +} + +#tocItems { + line-height: 40px !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/components/toolbar.css b/src/static/skins/colibris/src/components/toolbar.css new file mode 100644 index 000000000..7c962e24d --- /dev/null +++ b/src/static/skins/colibris/src/components/toolbar.css @@ -0,0 +1,145 @@ +.toolbar { + height: 39px !important; + padding-top: 0; + margin: 0; + background-color: white !important; + background: none; + border: 1px solid #d2d2d2 +} + +.toolbar .buttonicon { + background-color: transparent; + font-size: 14px; + color: #767676; +} + +.toolbar ul li.separator { + padding: 0; + visibility: visible; + width: 1px; + margin: 0 10px; + margin-right: 6px; + height: 39px; + background-color: rgba(78, 85, 92, 0.22); +} + +.toolbar.condensed ul li { + margin-left: 0; +} + +.toolbar.condensed ul li.separator { + margin: 0 5px; +} + +.toolbar ul li a { + background-color: transparent; + background: none; + border: none; + margin-top: 6px; +} + +.toolbar ul li a:hover, +.toolbar ul li a.selected, +.toolbar ul li a:focus { + background: none !important; + border-radius: 0; +} + +.toolbar ul li a:hover { + background:#f7f7f7!important +} + +.toolbar ul li a.selected, +.toolbar ul li a:focus { + background: #eaeaea!important; +} + +.toolbar ul li select { + border: none; + border-bottom: 1px solid #d7d8da; + border-radius: 0; + width: 90px; + margin-top: 5px; +} + +.toolbar ul { + height: 39px; +} + +.toolbar ul.menu_left { + left: 8px; +} + +.toolbar ul.menu_right { + right: 0; + padding: 0; +} + +.toolbar ul li[data-key=showusers] a { + margin: 0; + height: 59px; + line-height: 25px; + width: 45px; + margin-left: -10px; + border-radius: 0; +} + +@media (max-width: 1000px) { + .toolbar ul li.separator { + margin: 0 2px; + background: none; + display: block; + } + + .toolbar ul li[data-key=showusers] a { + margin-left: 0 !important; + } +} + +@media (max-width: 720px) { +.toolbar ul { + height: 39px; + background: none; + background-color: transparent; + border: none !important; + padding: 0 !important; + overflow-x: visible; + } + .toolbar ul.menu_left { + padding-top: 2px !important; + } + .toolbar ul.menu_right { + left: 0; + padding-left: 8px !important; + padding-top: 8px !important; + height: 35px !important; + border-top: 1px solid #d2d2d2 !important; + background-color: white; + } + .toolbar ul li a { + padding: 4px 5px !important; + } + + .toolbar ul li[data-key=showusers] { + position: absolute; + right: 0; + top: 0; + } + .toolbar ul li[data-key=showusers] a { + padding-top: 9px !important; + } + .toolbar ul li a:hover { background-color: transparent; } + + #connectivity, #embed, #import_export, #settings { bottom: 42px; } + + li.superscript, + li.subscript, + li[data-key="undo"], + li[data-key="redo"] { + display: none; + } + + .toolbar ul li.separator { margin: 0; } +} + + diff --git a/src/static/skins/colibris/src/components/users.css b/src/static/skins/colibris/src/components/users.css new file mode 100644 index 000000000..7e05b87ae --- /dev/null +++ b/src/static/skins/colibris/src/components/users.css @@ -0,0 +1,83 @@ +table#otheruserstable { + margin-top: 10px !important; +} + +#otheruserstable .swatch { + border: none !important; + border-radius: 50%; + width: 18px; + height: 18px; + margin: 0; + margin-left: 1px; + margin-right: 15px; +} + +#myusernameform { + margin-left: 35px; +} + +#myusernameedit { + width: 110px; +} + +#myswatchbox { + background: none; + float: left; + position: relative; + left: 0; + top: 0; + border: none !important; +} + +#myswatch { + border-radius: 50%; +} + +#nootherusers { + padding: 0; +} + +#mycolorpicker { + width: auto; + height: auto; + left: -280px; +} + +#colorpicker { + margin-bottom: 25px; +} + +#mycolorpickercancel { + padding-left: 3px; +} + +#mycolorpickersave { + color: #fff; +} + +#mycolorpickerpreview { + float: right; + top: 0; + left: 0; + position: relative; + border-radius: 50%; + height: 20px; + width: 20px; +} + +@media (max-width: 720px) { + #users { + bottom: 42px; + top: initial !important; + height: auto !important; + } + + #mycolorpicker { + width: auto; + height: auto; + right: 0; + bottom: 42px; + left: initial; + top: initial !important; + } +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/layout.css b/src/static/skins/colibris/src/layout.css new file mode 100644 index 000000000..0e8b57744 --- /dev/null +++ b/src/static/skins/colibris/src/layout.css @@ -0,0 +1,133 @@ +#connectivity, +#embed, +#import_export, +#settings, +#users { + top: 38px; + right: 30px; +} + +#editorcontainer { + top: 41px !important; + padding-top: 0 !important; +} + +#outerdocbody, .timeslider #editorcontainerbox { + max-width: 900px; + margin: 0 auto; + padding-top: 20px; +} + +#outerdocbody { + overflow-y: auto; + position: relative; + background-color: transparent; + padding-left: 40px; /* space for side div */ +} + +#outerdocbody.ep_author_neat { + padding-left: 120px; /* more space for sidediv */ +} +@media (max-width:600px) { + #outerdocbody.ep_author_neat { padding-left: 0; } + #options-linenoscheck { display:none; } + #options-linenoscheck ~ label { display:none; } +} + +#outerdocbody.sidediv-hidden { + padding-left: 0; /* sidediv hidden */ +} + +#outerdocbody iframe { + display: block; + position: relative; + left: 0 !important; + top: 0; +} + +#outerdocbody iframe, .timeslider #editorcontainerbox { + padding: 55px; + box-shadow: 0 0 0 0.5px rgba(209, 209, 209, 0.32), 0 0 7pt 0pt rgba(204, 204, 204, 0.52); + border: 0; + border-radius: 5px; + background-color: white; + width: calc(100% - 110px) !important; /* 100% - padding */ +} + +#sidediv { + position: absolute; + right: calc(100% - 35px); + left: initial; + top: 74px !important; + padding: 0; +} + +#outerdocbody.ep_author_neat #sidediv { + right: calc(100% - 113px); +} + +/* Fixs comments_page & author_hover does not take in account the document padding */ +.comment-modal { margin-top: 75px !important; margin-left: 45px; } +.authortooltip { margin-top: 65px !important; margin-left: 60px; } +.caretindicator { margin-top: 61px!important; margin-left: 52px; } + +#outerdocbody.ep_author_neat .authortooltip{ margin-left: 145px; } +#outerdocbody.ep_author_neat .caretindicator{ margin-left: 52px; margin-top: 65px!important;} +@media (max-width:1000px) { + #outerdocbody.ep_author_neat .authortooltip{ margin-left: 115px; } + .caretindicator{ margin-left: 13px; } + #outerdocbody.ep_author_neat .caretindicator{ margin-left: 17px; } +} + +@media (min-width: 1381px) { + #outerdocbody.ep_comments_page { padding-right: 150px; } } + #outerdocbody.ep_comments_page #comments { left: calc(100% - 150px) } +@media (max-width: 1380px) { + #outerdocbody.ep_comments_page #comments { left: calc(100% - 220px) } + #outerdocbody.ep_comments_page { padding-right: 220px; } +} +@media (max-width: 1278px) { + #outerdocbody.ep_comments_page #comments { display: none; } + #outerdocbody.ep_comments_page { padding-right: 0px; } +} + +@media (max-width:1000px) { + #outerdocbody { + max-width: none; + padding-top: 0; + } + #outerdocbody iframe, .timeslider #editorcontainerbox { + padding: 20px !important; + border-radius: 0; + width: calc(100% - 40px) !important; /* 100% - padding */ + } + #sidediv { + top: 20px !important; /* = #outerdocbody iframe padding-top */ + } + + .comment-modal, .authortooltip { margin-top: 20px !important; } + .caretindicator { margin-top: 0px !important; } + #outerdocbody.ep_author_neat .caretindicator { margin-top: 10px !important; } + + #outerdocbody.ep_author_neat #sidedivinner>div:before { padding-right: 10px !important; } + #outerdocbody.ep_author_neat #sidedivinner.authorColors>div, + #outerdocbody.ep_author_neat #sidedivinner.authorColors>div.primary-none, + #outerdocbody.ep_author_neat #sidedivinner>div { padding-right: 6px!important; } + #outerdocbody.ep_author_neat #sidediv { padding-right: 0 !important; } +} + +@media (max-width:600px) { + html { overflow: scroll; } + #outerdocbody { + width: 100%; + } + #outerdocbody iframe, .timeslider #editorcontainerbox { + padding: 15px !important; + width: calc(100% - 30px) !important; /* 100% - padding */ + } + #sidediv { + display: none; + top: 15px !important; /* = #outerdocbody iframe padding-top */ + } +} + diff --git a/src/static/skins/colibris/src/pad-editor.css b/src/static/skins/colibris/src/pad-editor.css new file mode 100644 index 000000000..6127a30cc --- /dev/null +++ b/src/static/skins/colibris/src/pad-editor.css @@ -0,0 +1,43 @@ +#innerdocbody, #padcontent { + font-size: 15px; + line-height: 25px; + padding: 0; +} + +#innerdocbody span, #padcontent span { + padding: 4px 0 !important; +} + +#innerdocbody h1 span, #padcontent h1 span { + padding: 0; +} + +body { + overflow: hidden; +} + +option { + text-transform: capitalize; +} + +h1 { + font-size: 2.5em !important; +} + +h3 { + font-size: 1.15em; + letter-spacing: 1px; +} + +a { + color: #3f51b5; +} + +h1, h2, h3, h4, h5, h6 { + line-height: 120%; +} + + + + + diff --git a/src/static/skins/colibris/src/plugins/author_hover.css b/src/static/skins/colibris/src/plugins/author_hover.css new file mode 100644 index 000000000..6a0fbe42a --- /dev/null +++ b/src/static/skins/colibris/src/plugins/author_hover.css @@ -0,0 +1,10 @@ +.authortooltip { + opacity: 1!important; + border-radius: 2px; + padding: 4px 10px 3px!important; + text-transform: uppercase; + font-size: 13px!important; + font-weight: 700; + color: #000; + background-color: rgba(255, 255, 255, 0.85) !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/plugins/brightcolorpicker.css b/src/static/skins/colibris/src/plugins/brightcolorpicker.css new file mode 100644 index 000000000..32480cc9c --- /dev/null +++ b/src/static/skins/colibris/src/plugins/brightcolorpicker.css @@ -0,0 +1,20 @@ +#colorpicker { + left: -200px !important; + top: 0px !important; +} + +#colorpicker a.brightColorPicker-cancelButton { + background: none; + padding: 0; + padding-top: 10px; + font-family: Arial, sans-serif; + font-weight: bold; + border: none; +} + +.brightColorPicker-colorPanel { + background-color: white !important; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08) !important; + border-radius: 3px !important; + padding: 15px !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/plugins/comments_page.css b/src/static/skins/colibris/src/plugins/comments_page.css new file mode 100644 index 000000000..f11fa61c8 --- /dev/null +++ b/src/static/skins/colibris/src/plugins/comments_page.css @@ -0,0 +1,290 @@ +.comment.selected { + color: #a28239; +} + +#newComment { + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border: none; + padding: 15px; +} + +p[data-l10n-id="ep_comments_page.comment"] { + font-weight: 900; + font-size: 16px; + text-transform: uppercase; + margin-bottom: 10px; +} + +#newComment.sidebar-comment textarea:not(.comment-suggest-from) { + border: 1px solid #d2d2d2; + border-radius: 3px; + padding: 8px 10px; + height: 80px; + padding: 8px; + font-size: 14px; + width: calc(100% - 15px); +} + +#newComment.sidebar-comment .comment-suggest-from { + height: auto !important; + font-size: 14px; + padding: 8px; + width: calc(100% - 15px) !important; + background-color: #f9f9f9; + margin-top: 8px; + border: 1px solid #f3eeee; + border-left: 4px solid #a7a7a7; + border-radius: 3px; +} + +.comment-suggest { + margin-bottom: 8px; +} + +.reply-comment-suggest input, +.comment-suggest input { + vertical-align: -2px; + margin-right: 5px; +} + +.reply-comment-suggest label, +.comment-suggest label { + font-style: italic; + font-weight: normal; +} + +.sidebar-comment input, +.comment-buttons input { + border-radius: 4px; + text-transform: uppercase; + font-weight: 900; + font-size: 13px; + line-height: 1.5; + width: auto; + border: none; +} + +#newComment.sidebar-comment input[type=submit] { + color: white; + margin-right: 10px; + padding: 0 10px; + border: none; +} + +#newComment.sidebar-comment input[type=reset] { + background-color: white; + border: none; +} + +.comment-content:focus { + border: 2px solid rgba(33, 150, 243, 0.51); +} + +.comment-changeTo-approve input[type=submit] { width: 100%; font-size: 12px;} + +.reply-comment-suggest-from-p, +.comment-changeFrom-value, +.comment-changeTo-value, +.reply-suggestion .reply-comment-suggest-from, +.suggestion .comment-suggest-from { + border: none; + font-weight: normal; + font-style: italic; + padding-left: 0; + width: 100% !important; + padding: 0; + padding-top: 8px; + /* width: inherit !important; */ + /*color: rgba(41, 125, 191, 0.85);*/ +} + +textarea.comment-suggest-to { + margin-bottom: 8px; +} + +.suggestion { + font-weight: bold; + font-size: 11px; + text-transform: uppercase; +} + +#comments { + top: 82px; +} + +#comments .sidebar-comment { + background-color: transparent; + font-size: 13px; + border: none; + box-shadow: none; +} + +@media (min-width: 1200px) { + #comments .sidebar-comment:hover, + #comments .sidebar-comment.mouseover, + .comment-modal { + margin-right: -30px; + } +} + +#comments .sidebar-comment:hover, +#comments .sidebar-comment.mouseover, +.comment-modal { + background-color: #fff; + padding: 0; + padding-top: 10px; + margin-top: -8px; + width: 250px; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); +} + +#comments .sidebar-comment:hover time, +#comments .sidebar-comment.mouseover time, +.comment-modal-comment>time { + display: block !important; +} + +#comments .sidebar-comment:hover>section, +#comments .sidebar-comment.mouseover>section, +.comment-modal-comment>section { + padding: 0 15px; + padding-bottom: 10px; + margin-top: 5px; + display: block; + line-height: 20px; + margin-top: 8px; +} + +.comment-delete-container { + float: right; + margin: 0; + height: 15px; + font-size: 15px; + background-color: transparent; + padding: 6px 15px 4px 8px; + color: #7b7777; +} + +.comment-reply { + border-top: 1px solid #d2d2d2; + background-color: #f9f9f9; + margin: 0; + padding: 10px 15px 3px; +} + +.comment-reply-input-label span { + font-weight: bold; +} + +.comment-reply-input { + border: 1px solid #d2d2d2 !important; + width: calc(100% - 20px) !important; + padding: 8px 10px; + text-transform: none !important; + margin-top: 5px; + margin-bottom: 10px; + font-weight: normal; +} + +.reply-suggestion p:not(.reply-comment-suggest-from-p) { + text-transform: uppercase; + font-size: 11px; + font-weight: 400; + text-transform: uppercase; +} + +.comment-changeTo-approve input { + height: 25px; + margin: 0; + margin-top: 5px; +} + +.reply-suggestion { + margin-top: 8px; +} + +.reply-comment-suggest-from-p { + padding: 0 +} + +.comment-text { + font-weight: normal; + line-height: 20px; +} + +.sidebar-comment-reply:nth-child(even) { + background-color: transparent; +} + +.sidebar-comment-reply { + padding: 0; + margin-bottom: 10px; +} + +.comment-author-name { + font-style: normal; +} + +.comment-changeFrom-label, +.comment-changeTo-label { + text-transform: uppercase; + font-size: 11px; +} + +.comment-reply note { + line-height: 16px; +} + +.reply-comment-suggest { + display: none !important +} + +.comment-changeTo-form { + margin-bottom: 5px; + padding: 0 15px; +} + +.sidebar-comment>.comment-author-name, +.comment-modal-comment>.comment-author-name { + padding-left: 0px; +} + +#comments .sidebar-comment:hover >.comment-author-name, +#comments .sidebar-comment.mouseover >.comment-author-name { + margin-top: 2px; + display: inline-block; + padding-left: 15px; +} + +.sidebar-comment>time, +.comment-modal-comment>time { + position: absolute; + top: 11px; + font-size: 11px; + right: 36px; + color: #555555; + font-style: italic; +} + +.comment-changeTo-approve { + margin-bottom: 14px; + margin-top: 5px; +} + +.comment-reply note:first-child { + margin-top: 8px; +} + +.comment-modal { + padding: 0; + border: none; + width: 350px !important; + margin-top: 0; + padding-top: 10px; +} + +.comment-modal-comment>.comment-author-name { + padding-left: 14px; +} + + diff --git a/src/static/skins/colibris/src/plugins/cursortrace.css b/src/static/skins/colibris/src/plugins/cursortrace.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/static/skins/colibris/src/plugins/embedded_hyperlinks.css b/src/static/skins/colibris/src/plugins/embedded_hyperlinks.css new file mode 100644 index 000000000..b5ec7e61c --- /dev/null +++ b/src/static/skins/colibris/src/plugins/embedded_hyperlinks.css @@ -0,0 +1,6 @@ +.hyperlink-dialog>.hyperlink-save{ + height: 34px; + margin-top: 1px; + margin-right: 0; + margin-left: 2px; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/plugins/font_color.css b/src/static/skins/colibris/src/plugins/font_color.css new file mode 100644 index 000000000..da719cb00 --- /dev/null +++ b/src/static/skins/colibris/src/plugins/font_color.css @@ -0,0 +1,32 @@ +li.acl-write.font-color-icon.ep_font_color { + display: none; +} + +#font-color { + display: list-item !important; +} + +.color\:red, +[data-color=red] { + color: #F44336; +} + +.color\:green, +[data-color=green] { + color: #66d29c; +} + +.color\:blue, +[data-color=blue] { + color: #2196f3; +} + +.color\:yellow, +[data-color=yellow] { + color: #e0d776; +} + +.color\:orange, +[data-color=orange] { + color: #d2a564; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/plugins/set_title_on_pad.css b/src/static/skins/colibris/src/plugins/set_title_on_pad.css new file mode 100644 index 000000000..8d0c76677 --- /dev/null +++ b/src/static/skins/colibris/src/plugins/set_title_on_pad.css @@ -0,0 +1,34 @@ +#pad_title { + margin-bottom: 15px !important; + margin-top: 5px !important; + display: none; /* display only when page is loaded via javascript */ +} + +@media (max-width:720px) { + #pad_title { display: none !important; } +} + +#edit_title { + color: white; +} +#edit_title:before { + font-family: fontawesome-etherpad; + position: absolute; + top: 20px; + font-size: 14px; + content: "\E839"; + margin-left: 10px; +} + +#input_title { + background-color: #f9f9f9 !important; + height: auto !important; + margin-top: 3px; + width: calc(100% - 110px) !important; + padding: 8px 10px !important; +} + +#save_title button { + height: 30px !important; + padding: 5px 20px !important; +} \ No newline at end of file diff --git a/src/static/skins/colibris/src/plugins/tables2.css b/src/static/skins/colibris/src/plugins/tables2.css new file mode 100644 index 000000000..090bb5a7e --- /dev/null +++ b/src/static/skins/colibris/src/plugins/tables2.css @@ -0,0 +1,239 @@ +/* MENU ICON*/ +#editbar #tbl_menu_list { + width: auto !important; +} +#tbl-menu { + background: none !important; + width: 18px !important; + padding-left: 2px !important; +} +#tbl-menu:before { + content: "\F0CE"; +} + +#tbl_menu_list > a { + font-size: 16px; + margin-top: 8px; + padding-left: 0; + padding-right: 2px; + padding-bottom: 4px; +} + +/* DROP DOWN MENU */ +#tbl_context_menu { + margin-left: -24px; + border: none; + margin-top: 9px; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border-radius: 3px; + background-color: white; + font-size: 100%; + line-height: 1.7; +} + +#tbl_context_menu > .bd { + border: none; + background-color: transparent; +} + +#tbl_context_menu > .bd > ul { + padding: 6px 0; +} + +/* TABLE SIZE PICKER */ +#tbl_insert { + background-color: white; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border-radius: 3px; +} + +#tbl_insert .bd { + border: none; + text-align: center; + background-color: transparent; + padding-top: 4px; +} + +#tbl_insert .yuimenuitemlabel { text-align: center; } + +#tbl_insert .ft { + margin: 0; + border: none; + background-color: transparent; + padding: 6px; + padding-top: 0; +} + +#matrix_table tr td { + border: 1px solid #d7d7d7; + height: 1px; + padding: 7px; + width: 11px; + background-color: #fbfbfb; + border-radius: 1px; +} +#matrix_table tr td.selected { + border: 1px solid #789dce; + background-color: #b3d4ff; +} + +/* TABLE SETTINGS POPUP */ +.yui-skin-sam .yui-panel-container { + padding: 0; + margin: 0; + background-color: #fff; + box-shadow: 0 0 0 1px rgba(99, 114, 130, 0.16), 0 8px 16px rgba(27, 39, 51, 0.08); + border-radius: 5px; + padding-bottom: 15px; +} + +.yui-skin-sam .yui-panel-container .yui-panel { + border: none !important; + background: none; + box-shadow: none !important; +} + +.yui-skin-sam .yui-panel-container .yui-panel .hd { + cursor: move; + padding: 0; + border: 0; + background: 0; + margin: 0; + font-size: 14px; + line-height: 40px; + text-transform: uppercase; + padding: 0 15px; + padding-top: 5px; + font-weight: bold; + border-bottom: 1px solid #d2d2d2; +} + +.yui-skin-sam .yui-panel-container .yui-panel .container-close { + top: 15px; + border: none; + background: none; + color: white; + text-indent: 0; +} +.yui-skin-sam .yui-panel-container .yui-panel .container-close::before { + content: "x"; + color: #6f757a; + font-size: 16px; + font-weight: bold; +} + +.yui-skin-sam .yui-panel-container .yui-panel .bd { + background: none; + border: none; + box-shadow: none; + padding: 15px; + background-color: transparent !important; +} + +.yui-panel .underlay, .yui-skin-sam .yui-panel-container.shadow .underlay { + display: none !important; +} + +#div_tbl_btn_close { + float: right; + position: relative; + width: 100%; + margin-top: 10px; + right: 0; + bottom: 0; +} + +#tbl_btn_close { + border: none; + color: #ffffff; + height: 30px; + width: 100%; + border-radius: 3px; + text-transform: uppercase; +} +#tbl_btn_close:hover { cursor: pointer; } + +.yui-skin-sam .yui-button { + background: none; + background-color: white; + border: none; + height: 24px; + margin-bottom: -4px; + margin-top: 5px; +} + +.yui-skin-sam .yui-button .first-child { margin: 0; border: none; } + +.yui-skin-sam .yui-split-button button { + padding: 0; + background: none !important; +} + +.yui-skin-sam .yui-split-button button em:not(.color-picker-button) { + font-style: normal !important; + border-bottom: 1px solid #b5b7b7; + padding: 0 5px; + margin: 0 5px; + padding-bottom: 3px; +} + +button#yui-gen13-button { + margin-left: -5px; +} + +button .color-picker-button { + border: 1px solid #c1c2c2; + border-radius: 50%; + width: 16px; + height: 16px; + margin-top: 2px; +} + +#even-row-bg-color, #single-row-bg-color { + margin-right: 5px; +} +#single-col-bg-color, #odd-row-bg-color { + margin-left: 7px; +} + +#yui-tbl-prop-panel .text-input[type=text] { + border: 1px solid #d2d2d2; + float: right; + height: 10px; + border-radius: 3px; + padding: 8px 10px; +} + +#text_input_message { + background-color: #64d29b; + padding: 0 5px; + color: white; + font-size: 12px; + border-radius: 5px; + font-weight: bold; + display: none; +} + +/* TABLES INSIDE THE PAD */ +td[name=tData] { + /*border: 1px solid grey !important;*/ +} + +#yui-picker-panel_c +{ + padding-bottom: 40px; +} + +div#yui-picker-panel_h { + line-height: 1.8em; + font-size: 13px; + padding: 9px 15px 5px; +} + +#yui-picker-panel .ft { + position: relative; + border: none; + width: 100%; + padding: 0; + margin-top: 20px; +} \ No newline at end of file diff --git a/src/static/skins/colibris/timeslider.css b/src/static/skins/colibris/timeslider.css new file mode 100644 index 000000000..1f4d16456 --- /dev/null +++ b/src/static/skins/colibris/timeslider.css @@ -0,0 +1,142 @@ +@media (max-width: 600px) { html { overflow: hidden } } + +@media (max-width: 1100px) { + .timeslider #padeditor { + padding: 0 !important; + } +} + +.timeslider #import_export, .timeslider #settings{ + top: 108px !important; + right: 55px; +} + +.timeslider #export > p { + font-size: 15px; + margin-top: 0; + margin-bottom: 20px; +} + +.timeslider #padpage { + display: flex; + flex-direction: column; +} + +.timeslider #timeslider-top { + position: relative; + border-bottom: 1px solid #e4e4e4; +} + +.timeslider-bar { background: none; } +.timeslider-bar p { margin: 8px; font-size: 12px;} + +.timeslider-bar #editbar { + border: none; + background: none !important; + margin-right: 10px; +} + +.timeslider #padmain { + position: relative; + top: 0 !important; + flex: 1 auto; + overflow: auto; +} + +.timeslider #padeditor { + height: 100%; + padding-top: 30px; +} + +.timeslider #editorcontainerbox { + height: 100%; + overflow: visible; + margin-top: 0 !important; +} + +#timeslider { + margin-top: -20px; + margin-left: 0; + background-color: transparent; +} + +#timeslider, #timeslider-left, #timeslider-right { + height: 57px; + background-color: transparent; +} + +#timeslider #timer { + opacity: 0.8; + font-style: italic; + right: 158px; + top: -3px; + left: initial; + background: none; +} + +#timeslider #timeslider-slider { margin-left: 4px; } + +#ui-slider-handle { + z-index: 5; + border-radius: 3px; + height: 28px; + top: 19px; +} + +#ui-slider-bar { + height: 10px; + margin-top: 28px; + margin-right: 180px; + border-radius: 3px; + background-color: #e4e4e4; +} + +#timeslider .star { + top: 25px; +} +#timeslider .star:before { + color: #da9700; + content: "\e836"; +} + +.timeslider #editbar .buttontext { + color: white; + margin: 0; +} +.timeslider #editbar .grouped-right { + margin: 0; padding: 0; + margin-top: 5px; + margin-left: 5px; +} + +.timeslider #playpause_button { + right: 95px; + top: 1px; +} +#playpause_button_icon { + border:none; +} +#playpause_button_icon:before { color: white; } + +.timeslider #leftstep { + right: 60px; + top: 12px; +} + +.timeslider #rightstep { + top: 12px; + right: 30px; +} + +.stepper { + border: 2px solid; + text-align: center; + border-radius: 50%; + height: 25px; + padding-top: 2px; +} + +.timeslider #authorsList .author { + padding: 2px 5px; + border-radius: 3px; +} \ No newline at end of file diff --git a/src/static/skins/colibris/timeslider.js b/src/static/skins/colibris/timeslider.js new file mode 100644 index 000000000..c94a55778 --- /dev/null +++ b/src/static/skins/colibris/timeslider.js @@ -0,0 +1,6 @@ +function customStart() +{ + console.log("custom start", $('#timeslider-wrapper').length); + // inverse display order betwwen slidebar and titles + $('#timeslider-wrapper').appendTo('#timeslider-top'); +} diff --git a/src/static/custom/css.template b/src/static/skins/no-skin/index.css similarity index 100% rename from src/static/custom/css.template rename to src/static/skins/no-skin/index.css diff --git a/src/static/skins/no-skin/index.js b/src/static/skins/no-skin/index.js new file mode 100644 index 000000000..152c3d5d7 --- /dev/null +++ b/src/static/skins/no-skin/index.js @@ -0,0 +1,6 @@ +function customStart() +{ + //define your javascript here + //jquery is available - except index.js + //you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ +} diff --git a/src/static/skins/no-skin/pad.css b/src/static/skins/no-skin/pad.css new file mode 100644 index 000000000..f300b81c3 --- /dev/null +++ b/src/static/skins/no-skin/pad.css @@ -0,0 +1,5 @@ +@media (max-width:600px) { + #sidediv { + display: none !important; + } +} \ No newline at end of file diff --git a/src/static/skins/no-skin/pad.js b/src/static/skins/no-skin/pad.js new file mode 100644 index 000000000..152c3d5d7 --- /dev/null +++ b/src/static/skins/no-skin/pad.js @@ -0,0 +1,6 @@ +function customStart() +{ + //define your javascript here + //jquery is available - except index.js + //you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ +} diff --git a/src/static/skins/no-skin/timeslider.css b/src/static/skins/no-skin/timeslider.css new file mode 100644 index 000000000..236251d9c --- /dev/null +++ b/src/static/skins/no-skin/timeslider.css @@ -0,0 +1,8 @@ +/* + custom css files are loaded after core css files. Simply use the same selector to override a style. + Example: + #editbar LI {border:1px solid #000;} + overrides + #editbar LI {border:1px solid #d5d5d5;} + from pad.css +*/ diff --git a/src/static/skins/no-skin/timeslider.js b/src/static/skins/no-skin/timeslider.js new file mode 100644 index 000000000..152c3d5d7 --- /dev/null +++ b/src/static/skins/no-skin/timeslider.js @@ -0,0 +1,6 @@ +function customStart() +{ + //define your javascript here + //jquery is available - except index.js + //you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ +} diff --git a/src/templates/index.html b/src/templates/index.html index 92bea582a..4962560b6 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -156,7 +156,7 @@ } } - +
<% e.begin_block("indexWrapper"); %> @@ -171,13 +171,13 @@ <% e.end_block(); %>
- + <% e.begin_block("customScripts"); %> - + <% e.end_block(); %> diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index 395d0c478..60194af12 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -36,7 +36,8 @@ <% e.begin_block("timesliderStyles"); %> - + + <% e.end_block(); %> @@ -227,7 +228,7 @@ - +