diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1293b578d..fc6688dc5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+# 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
+* FIX: improved Firefox compatibility (non printable keys)
+* FIX: `getPadPlainText()` was not working
+* REQUIREMENTS: minimum required Node version is 6.9.0 LTS. The next release will require at least Node 8.9.0 LTS
+* SECURITY: updated MySQL, Elasticsearch and PostgreSQL drivers
+* SECURITY: started updating deprecated code and packages
+* DOCS: documented --credentials, --apikey, --sessionkey. Better detailed contributors guidelines. Added a section on securing the installation
+
# 1.6.6
* FIX: line numbers are aligned with text again (broken in 1.6.4)
* FIX: text entered between connection loss and reconnection was not saved
@@ -490,7 +500,7 @@
* Plugin-specific settings in settings.json (finally allowing for things like a google analytics plugin)
* Serve admin dashboard at /admin (still very limited, though)
* Modify your settings.json through the newly created UI at /admin/settings
- * Fix: Import
's as 's and not as 's!
+ * Fix: Import `` as `` and not as ``!
* Added solaris compatibility (bin/installDeps.sh was broken on solaris)
* Fix a bug with IE9 and Password Protected Pads using HTTPS
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 669460801..a734643b7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,34 @@
# Contributor Guidelines
(Please talk to people on the mailing list before you change this page, see our section on [how to get in touch](https://github.com/ether/etherpad-lite#get-in-touch))
+## Pull requests
+
+* the commit series in the PR should be _linear_ (it **should not contain merge commits**). This is necessary because we want to be able to [bisect](https://en.wikipedia.org/wiki/Bisection_(software_engineering)) bugs easily. Rewrite history/perform a rebase if necessary
+* PRs should be issued against the **develop** branch: we never pull directly into **master**
+* PRs **should not have conflicts** with develop. If there are, please resolve them rebasing and force-pushing
+* when preparing your PR, please make sure that you have included the relevant **changes to the documentation** (preferably with usage examples)
+* contain meaningful and detailed **commit messages** in the form:
+ ```
+ submodule: description
+
+ longer description of the change you have made, eventually mentioning the
+ number of the issue that is being fixed, in the form: Fixes #someIssueNumber
+ ```
+* if the PR is a **bug fix**:
+ * the first commit in the series must be a test that shows the failure
+ * subsequent commits will fix the bug and make the test pass
+ * the final commit message should include the text `Fixes: #xxx` to link it to its bug report
+* think about stability: code has to be backwards compatible as much as possible. Always **assume your code will be run with an older version of the DB/config file**
+* if you want to remove a feature, **deprecate it instead**:
+ * write an issue with your deprecation plan
+ * output a `WARN` in the log informing that the feature is going to be removed
+ * remove the feature in the next version
+* if you want to add a new feature, put it under a **feature flag**:
+ * once the new feature has reached a minimal level of stability, do a PR for it, so it can be integrated early
+ * expose a mechanism for enabling/disabling the feature
+ * the new feature should be **disabled** by default. With the feature disabled, the code path should be exactly the same as before your contribution. This is a __necessary condition__ for early integration
+* think of the PR not as something that __you wrote__, but as something that __someone else is going to read__. The commit series in the PR should tell a novice developer the story of your thoughts when developing it
+
## How to write a bug report
* Please be polite, we all are humans and problems can occur.
@@ -25,12 +53,6 @@ If you send logfiles, please set the loglevel switch DEBUG in your settings.json
The logfile location is defined in startup script or the log is directly shown in the commandline after you have started etherpad.
-
-## Important note for pull requests
-**Pull requests should be issued against the develop branch**. We never pull directly into master.
-
-**Our goal is to iterate in small steps. Release often, release early. Evolution instead of a revolution**
-
## General goals of Etherpad
To make sure everybody is going in the same direction:
* easy to install for admins and easy to use for people
@@ -93,6 +115,8 @@ You can build the docs e.g. produce html, using `make docs`. At some point in th
## Testing
Front-end tests are found in the `tests/frontend/` folder in the repository. Run them by pointing your browser to `/tests/frontend`.
+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
diff --git a/README.md b/README.md
index d8d7b621e..f8c4f1227 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,3 @@
-### This project is looking for a new project lead. If you wish to help steer Etherpad forward please email contact@etherpad.org
-
-[![Deps](https://david-dm.org/ether/etherpad-lite.svg?branch=develop)](https://david-dm.org/ether/etherpad-lite)
-[![NSP Status](https://nodesecurity.io/orgs/etherpad/projects/635f6185-35c6-4ed7-931a-0bc62758ece7/badge)](https://nodesecurity.io/orgs/etherpad/projects/635f6185-35c6-4ed7-931a-0bc62758ece7)
-
# A really-real time collaborative word processor for the web
![Demo Etherpad Animated Jif](https://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad")
@@ -13,6 +8,9 @@ Etherpad is a really-real time collaborative editor scalable to thousands of sim
# Installation
+## Requirements
+- `nodejs` >= **6.9.0** (preferred: `nodejs` >= **8.9**)
+
## Uber-Quick Ubuntu
```
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
@@ -26,25 +24,26 @@ You'll need gzip, git, curl, libssl develop libraries, python and gcc.
- *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, Ideally the latest stable version, we recommend installing/compiling nodejs from source (avoiding apt).
+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).
**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`
-2. Change into the new directory containing the cloned source code `cd etherpad-lite`
+1. Move to a folder where you want to install Etherpad. Clone the git repository: `git clone git://github.com/ether/etherpad-lite.git`
+2. Change into the new directory containing the cloned source code: `cd etherpad-lite`
-Now, 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.
+Update to the latest version with `git pull origin`. The next start with `bin/run.sh` will update the dependencies.
[Next steps](#next-steps).
## Windows
-### Prebuilt windows package
+### Prebuilt Windows package
This package works out of the box on any windows machine, but it's not very useful for developing purposes...
-1. [Download the latest windows package](http://etherpad.org/#download)
+1. [Download the latest Windows package](http://etherpad.org/#download)
2. Extract the folder
Now, run `start.bat` and open in your browser. You like it? [Next steps](#next-steps).
@@ -63,17 +62,26 @@ Update to the latest version with `git pull origin`, then run `bin\installOnWind
If cloning to a subdirectory within another project, you may need to do the following:
-1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js]`)
+1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js`)
2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`)
3. Add auto-generated files to the main project `.gitignore`
# Next Steps
## Tweak the settings
-You can initially modify the settings in `settings.json`. (If you need to handle multiple settings files, you can pass the path to a settings file to `bin/run.sh` using the `-s|--settings` option. This allows you to run multiple Etherpad instances from the same installation.) Once you have access to your /admin section settings can be modified through the web browser.
+You can modify the settings in `settings.json`.
+If you need to handle multiple settings files, you can pass the path to a settings file to `bin/run.sh` using the `-s|--settings` option: this allows you to run multiple Etherpad instances from the same installation.
+Similarly, `--credentials` can be used to give a settings override file, `--apikey` to give a different APIKEY.txt file and `--sessionkey` to give a non-default SESSIONKEY.txt.
+Once you have access to your /admin section settings can be modified through the web browser.
You should use a dedicated database such as "mysql", if you are planning on using etherpad-in a production environment, since the "dirtyDB" database driver is only for testing and/or development purposes.
+## Secure your installation
+If you have enabled authentication in `users` section in `settings.json`, it is a good security practice to **store hashes instead of plain text passwords** in that file. This is _especially_ advised if you are running a production installation.
+
+Please install [ep_hash_auth plugin](https://www.npmjs.com/package/ep_hash_auth) and configure it.
+If you prefer, `ep_hash_auth` also gives you the option of storing the users in a custom directory in the file system, without having to edit `settings.json` and restart Etherpad each time.
+
## Plugins and themes
Etherpad is very customizable through plugins. Instructions for installing themes and plugins can be found in [the plugin wiki article](https://github.com/ether/etherpad-lite/wiki/Available-Plugins).
diff --git a/bin/backendTests.sh b/bin/backendTests.sh
deleted file mode 100755
index ec12775ba..000000000
--- a/bin/backendTests.sh
+++ /dev/null
@@ -1 +0,0 @@
-src/node_modules/mocha/bin/mocha --timeout 5000 --reporter nyan tests/backend/specs/api
diff --git a/bin/createRelease.sh b/bin/createRelease.sh
index 0439026bd..6768702f3 100755
--- a/bin/createRelease.sh
+++ b/bin/createRelease.sh
@@ -1,5 +1,10 @@
#!/bin/bash
#
+# WARNING: since Etherpad 1.7.0 (2018-08-17), this script is DEPRECATED, and
+# will be removed/modified in a future version.
+# It's left here just for documentation.
+# The branching policies for releases have been changed.
+#
# This script is used to publish a new release/version of etherpad on github
#
# Work that is done by this script:
@@ -16,6 +21,16 @@
# ETHER_REPO:
# - Create a new release on github
+printf "WARNING: since Etherpad 1.7.0 this script is DEPRECATED, and will be removed/modified in a future version.\n\n"
+while true; do
+ read -p "Do you want to continue? This is discouraged. [y/N]" yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) exit;;
+ * ) printf "Please answer yes or no.\n\n";;
+ esac
+done
+
ETHER_REPO="https://github.com/ether/etherpad-lite.git"
ETHER_WEB_REPO="https://github.com/ether/ether.github.com.git"
TMP_DIR="/tmp/"
diff --git a/bin/installDeps.sh b/bin/installDeps.sh
index 5ccba46dd..47373a5f6 100755
--- a/bin/installDeps.sh
+++ b/bin/installDeps.sh
@@ -1,5 +1,53 @@
#!/bin/sh
+# minimum required node version
+REQUIRED_NODE_MAJOR=6
+REQUIRED_NODE_MINOR=9
+
+# minimum required npm version
+REQUIRED_NPM_MAJOR=3
+REQUIRED_NPM_MINOR=10
+
+require_minimal_version() {
+ PROGRAM_LABEL="$1"
+ VERSION_STRING="$2"
+ REQUIRED_MAJOR="$3"
+ REQUIRED_MINOR="$4"
+
+ # Flag -s (--only-delimited on GNU cut) ensures no string is returned
+ # when there is no match
+ DETECTED_MAJOR=$(echo $VERSION_STRING | cut -s -d "." -f 1)
+ DETECTED_MINOR=$(echo $VERSION_STRING | cut -s -d "." -f 2)
+
+ if [ -z "$DETECTED_MAJOR" ]; then
+ printf 'Cannot extract %s major version from version string "%s"\n' "$PROGRAM_LABEL" "$VERSION_STRING" >&2
+ exit 1
+ fi
+
+ if [ -z "$DETECTED_MINOR" ]; then
+ printf 'Cannot extract %s minor version from version string "%s"\n' "$PROGRAM_LABEL" "$VERSION_STRING" >&2
+ exit 1
+ fi
+
+ case "$DETECTED_MAJOR" in
+ ''|*[!0-9]*)
+ printf '%s major version from "%s" is not a number. Detected: "%s"\n' "$PROGRAM_LABEL" "$VERSION_STRING" "$DETECTED_MAJOR" >&2
+ exit 1
+ ;;
+ esac
+
+ case "$DETECTED_MINOR" in
+ ''|*[!0-9]*)
+ printf '%s minor version from "%s" is not a number. Detected: "%s"\n' "$PROGRAM_LABEL" "$VERSION_STRING" "$DETECTED_MINOR" >&2
+ exit 1
+ esac
+
+ if [ "$DETECTED_MAJOR" -lt "$REQUIRED_MAJOR" ] || ([ "$DETECTED_MAJOR" -eq "$REQUIRED_MAJOR" ] && [ "$DETECTED_MINOR" -lt "$REQUIRED_MINOR" ]); then
+ printf 'Your %s version "%s" is too old. %s %d.%d.x or higher is required.\n' "$PROGRAM_LABEL" "$VERSION_STRING" "$PROGRAM_LABEL" "$REQUIRED_MAJOR" "$REQUIRED_MINOR" >&2
+ exit 1
+ fi
+}
+
#Move to the folder where ep-lite is installed
cd `dirname $0`
@@ -36,22 +84,15 @@ hash npm > /dev/null 2>&1 || {
}
#Check npm version
-NPM_VERSION=$(npm --version)
-NPM_MAIN_VERSION=$(echo $NPM_VERSION | cut -d "." -f 1)
-if [ $(echo $NPM_MAIN_VERSION) = "0" ]; then
- echo "You're running a wrong version of npm, you're using $NPM_VERSION, we need 1.x or higher" >&2
- exit 1
-fi
+NPM_VERSION_STRING=$(npm --version)
+
+require_minimal_version "npm" "$NPM_VERSION_STRING" "$REQUIRED_NPM_MAJOR" "$REQUIRED_NPM_MINOR"
#Check node version
-NODE_VERSION=$(node --version)
-NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
-NODE_V_MAIN=$(echo $NODE_VERSION | cut -d "." -f 1)
-NODE_V_MAIN=${NODE_V_MAIN#"v"}
-if [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ] && [ ! $NODE_V_MAIN -ge 4 ]; then
- echo "You're running a wrong version of node. You're using $NODE_VERSION, we need node v0.10.x or higher" >&2
- exit 1
-fi
+NODE_VERSION_STRING=$(node --version)
+NODE_VERSION_STRING=${NODE_VERSION_STRING#"v"}
+
+require_minimal_version "nodejs" "$NODE_VERSION_STRING" "$REQUIRED_NODE_MAJOR" "$REQUIRED_NODE_MINOR"
#Get the name of the settings file
settings="settings.json"
@@ -73,7 +114,7 @@ echo "Ensure that all dependencies are up to date... If this is the first time
cd node_modules
[ -e ep_etherpad-lite ] || ln -s ../src ep_etherpad-lite
cd ep_etherpad-lite
- npm install --loglevel warn
+ npm install --no-save --loglevel warn
) || {
rm -rf node_modules
exit 1
diff --git a/doc/api/http_api.md b/doc/api/http_api.md
index cacefb532..0baae7277 100644
--- a/doc/api/http_api.md
+++ b/doc/api/http_api.md
@@ -72,7 +72,7 @@ The API is accessible via HTTP. HTTP Requests are in the format /api/$APIVERSION
### Response Format
Responses are valid JSON in the following format:
-```js
+```json
{
"code": number,
"message": string,
diff --git a/doc/plugins.md b/doc/plugins.md
index e5b004087..0dccbe848 100644
--- a/doc/plugins.md
+++ b/doc/plugins.md
@@ -50,7 +50,7 @@ There are server hooks, which will be executed on the server (e.g. `expressCreat
### 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`:
-```javascript
+```json
{
"parts": [
{
@@ -99,7 +99,7 @@ Your plugin must also contain a [package definition file](https://docs.npmjs.com
"author": "USERNAME (REAL NAME) ",
"contributors": [],
"dependencies": {"MODULE": "0.3.20"},
- "engines": { "node": ">= 0.6.0"}
+ "engines": { "node": ">= 6.9.0"}
}
```
diff --git a/doc/stats.md b/doc/stats.md
index 7da69547d..a9342ee89 100644
--- a/doc/stats.md
+++ b/doc/stats.md
@@ -15,4 +15,4 @@ We currently measure:
Under the hood, we are happy to rely on [measured](https://github.com/felixge/node-measured) for all our metrics needs.
-To modify or simply access our stats in your plugin, simply `require('ep_etherpad-lite/stats')` which is a `measured.Collection`.
\ No newline at end of file
+To modify or simply access our stats in your plugin, simply `require('ep_etherpad-lite/stats')` which is a [`measured.Collection`](https://yaorg.github.io/node-measured/packages/measured-core/Collection.html).
diff --git a/settings.json.template b/settings.json.template
index 699880bde..684e9d598 100644
--- a/settings.json.template
+++ b/settings.json.template
@@ -1,64 +1,105 @@
/*
- This file must be valid JSON. But comments are allowed
-
- Please edit settings.json, not settings.json.template
-
- To still commit settings without credentials you can
- store any credential settings in credentials.json
-*/
+ * This file must be valid JSON. But comments are allowed
+ *
+ * Please edit settings.json, not settings.json.template
+ *
+ * Please note that since Etherpad 1.6.0 you can store DB credentials in a
+ * separate file (credentials.json).
+ */
{
- // Name your instance!
+ /*
+ * Name your instance!
+ */
"title": "Etherpad",
- // favicon default name
- // alternatively, set up a fully specified Url to your own favicon
+ /*
+ * favicon default name
+ * alternatively, set up a fully specified Url to your own favicon
+ */
"favicon": "favicon.ico",
- //IP and port which etherpad should bind at
+ /*
+ * IP and port which etherpad should bind at
+ */
"ip": "0.0.0.0",
"port" : 9001,
- // Option to hide/show the settings.json in admin page, default option is set to true
+ /*
+ * Option to hide/show the settings.json in admin page.
+ *
+ * Default option is set to true
+ */
"showSettingsInAdminPage" : true,
/*
- // Node native SSL support
- // this is disabled by default
- //
- // make sure to have the minimum and correct file access permissions set
- // so that the Etherpad server can access them
+ * Node native SSL support
+ *
+ * This is disabled by default.
+ * Make sure to have the minimum and correct file access permissions set so
+ * that the Etherpad server can access them
+ */
+ /*
"ssl" : {
"key" : "/path-to-your/epl-server.key",
"cert" : "/path-to-your/epl-server.crt",
"ca": ["/path-to-your/epl-intermediate-cert1.crt", "/path-to-your/epl-intermediate-cert2.crt"]
},
-
*/
- //The Type of the database. You can choose between dirty, postgres, sqlite and mysql
- //You shouldn't use "dirty" for for anything else than testing or development
+ /*
+ * The type of the database.
+ *
+ * You can choose between many DB drivers, for example: dirty, postgres,
+ * sqlite, mysql.
+ *
+ * You shouldn't use "dirty" for for anything else than testing or
+ * development.
+ *
+ * For a complete list of the supported drivers, please consult:
+ * https://www.npmjs.com/package/ueberdb2
+ */
+
"dbType" : "dirty",
- //the database specific settings
+
+ /*
+ * 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"
},
- /* An Example of MySQL Configuration
- "dbType" : "mysql",
- "dbSettings" : {
- "user" : "root",
- "host" : "localhost",
- "password": "",
- "database": "store",
- "charset" : "utf8mb4"
- },
+ /*
+ * An Example of MySQL Configuration (commented out).
+ *
+ * See: https://github.com/ether/etherpad-lite/wiki/How-to-use-Etherpad-Lite-with-MySQL
+ */
+
+ /*
+ "dbType" : "mysql",
+ "dbSettings" : {
+ "user" : "etherpaduser",
+ "host" : "localhost",
+ "port" : 3306,
+ "password": "PASSWORD",
+ "database": "etherpad_lite_db",
+ "charset" : "utf8mb4"
+ },
*/
- //the default text of a pad
+ /*
+ * The default text of a pad
+ */
"defaultPadText" : "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http:\/\/etherpad.org\n",
- /* Default Pad behavior, users can override by changing */
+ /*
+ * Default Pad behavior.
+ *
+ * Change them if you want to override.
+ */
"padOptions": {
"noColors": false,
"showControls": true,
@@ -73,7 +114,9 @@
"lang": "en-gb"
},
- /* Pad Shortcut Keys */
+ /*
+ * Pad Shortcut Keys
+ */
"padShortcutEnabled" : {
"altF9" : true, /* focus on the File Menu and/or editbar */
"altC" : true, /* focus on the Chat window */
@@ -99,113 +142,203 @@
"pageDown" : true
},
- /* Should we suppress errors from being visible in the default Pad Text? */
+ /*
+ * Should we suppress errors from being visible in the default Pad Text?
+ */
"suppressErrorsInPadText" : false,
- /* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
+ /*
+ * If this option is enabled, a user must have a session to access pads.
+ * This effectively allows only group pads to be accessed.
+ */
"requireSession" : false,
- /* Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. */
+ /*
+ * Users may edit pads but not create new ones.
+ *
+ * Pad creation is only via the API.
+ * This applies both to group pads and regular pads.
+ */
"editOnly" : false,
- /* Users, who have a valid session, automatically get granted access to password protected pads */
+ /*
+ * If set to true, those users who have a valid session will automatically be
+ * granted access to password protected pads.
+ */
"sessionNoPassword" : false,
- /* if true, all css & js will be minified before sending to the client. This will improve the loading performance massivly,
- but makes it impossible to debug the javascript/css */
+ /*
+ * If true, all css & js will be minified before sending to the client.
+ *
+ * This will improve the loading performance massively, but makes it difficult
+ * to debug the javascript/css
+ */
"minify" : true,
- /* How long may clients use served javascript code (in seconds)? Without versioning this
- may cause problems during deployment. Set to 0 to disable caching */
+ /*
+ * How long may clients use served javascript code (in seconds)?
+ *
+ * Not setting this may cause problems during deployment.
+ * Set to 0 to disable caching.
+ */
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours
- /* This is the absolute path to the Abiword executable. Setting it to null, disables abiword.
- Abiword is needed to advanced import/export features of pads*/
+ /*
+ * Absolute path to the Abiword executable.
+ *
+ * Abiword is needed to get advanced import/export features of pads. Setting
+ * it to null disables Abiword and will only allow plain text and HTML
+ * import/exports.
+ */
"abiword" : null,
- /* This is the absolute path to the soffice executable. Setting it to null, disables LibreOffice exporting.
- LibreOffice can be used in lieu of Abiword to export pads */
+ /*
+ * This is the absolute path to the soffice executable.
+ *
+ * LibreOffice can be used in lieu of Abiword to export pads.
+ * Setting it to null disables LibreOffice exporting.
+ */
"soffice" : null,
- /* This is the path to the Tidy executable. Setting it to null, disables Tidy.
- Tidy is used to improve the quality of exported pads*/
+ /*
+ * Path to the Tidy executable.
+ *
+ * Tidy is used to improve the quality of exported pads.
+ * Setting it to null disables Tidy.
+ */
"tidyHtml" : null,
- /* Allow import of file types other than the supported types: txt, doc, docx, rtf, odt, html & htm */
+ /*
+ * Allow import of file types other than the supported ones:
+ * txt, doc, docx, rtf, odt, html & htm
+ */
"allowUnknownFileEnds" : true,
- /* This setting is used if you require authentication of all users.
- Note: /admin always requires authentication. */
+ /*
+ * This setting is used if you require authentication of all users.
+ *
+ * Note: "/admin" always requires authentication.
+ */
"requireAuthentication" : false,
- /* Require authorization by a module, or a user with is_admin set, see below. */
+ /*
+ * Require authorization by a module, or a user with is_admin set, see below.
+ */
"requireAuthorization" : false,
- /*when you use NginX or another proxy/ load-balancer set this to true*/
+ /*
+ * When you use NGINX or another proxy/load-balancer set this to true.
+ */
"trustProxy" : false,
- /* Privacy: disable IP logging */
+ /*
+ * Privacy: disable IP logging
+ */
"disableIPlogging" : false,
- /* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
- message is shown to user. Set to 0 to disable automatic reconnection */
- "automaticReconnectionTimeout" : 0,
/*
- * By default, when caret is moved out of viewport, it scrolls the minimum height needed to make this
- * line visible.
- */
- "scrollWhenFocusLineIsOutOfViewport": {
- /*
- * Percentage of viewport height to be additionally scrolled.
- * E.g use "percentage.editionAboveViewport": 0.5, to place caret line in the
- * middle of viewport, when user edits a line above of the viewport
- * Set to 0 to disable extra scrolling
+ * Time (in seconds) to automatically reconnect pad when a "Force reconnect"
+ * message is shown to user.
+ *
+ * Set to 0 to disable automatic reconnection.
*/
+ "automaticReconnectionTimeout" : 0,
+
+ /*
+ * By default, when caret is moved out of viewport, it scrolls the minimum
+ * height needed to make this line visible.
+ */
+ "scrollWhenFocusLineIsOutOfViewport": {
+
+ /*
+ * Percentage of viewport height to be additionally scrolled.
+ *
+ * E.g.: use "percentage.editionAboveViewport": 0.5, to place caret line in
+ * the middle of viewport, when user edits a line above of the
+ * viewport
+ *
+ * Set to 0 to disable extra scrolling
+ */
"percentage": {
"editionAboveViewport": 0,
"editionBelowViewport": 0
},
- /* Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation */
+
+ /*
+ * Time (in milliseconds) used to animate the scroll transition.
+ * Set to 0 to disable animation
+ */
"duration": 0,
+
/*
- * Flag to control if it should scroll when user places the caret in the last line of the viewport
- */
+ * Flag to control if it should scroll when user places the caret in the
+ * last line of the viewport
+ */
"scrollWhenCaretIsInTheLastLineOfViewport": false,
+
/*
- * Percentage of viewport height to be additionally scrolled when user presses arrow up
- * in the line of the top of the viewport.
- * Set to 0 to let the scroll to be handled as default by the Etherpad
- */
+ * Percentage of viewport height to be additionally scrolled when user
+ * presses arrow up in the line of the top of the viewport.
+ *
+ * Set to 0 to let the scroll to be handled as default by Etherpad
+ */
"percentageToScrollWhenUserPressesArrowUp": 0
},
- /* Users for basic authentication. is_admin = true gives access to /admin.
- If you do not uncomment this, /admin will not be available! */
+ /*
+ * Users for basic authentication.
+ *
+ * is_admin = true gives access to /admin.
+ * If you do not uncomment this, /admin will not be available!
+ *
+ * WARNING: passwords should not be stored in plaintext in this file.
+ * If you want to mitigate this, please install ep_hash_auth and
+ * follow the section "secure your installation" in README.md
+ */
+
/*
"users": {
"admin": {
+ // "password" can be replaced with "hash" if you install ep_hash_auth
"password": "changeme1",
"is_admin": true
},
"user": {
+ // "password" can be replaced with "hash" if you install ep_hash_auth
"password": "changeme1",
"is_admin": false
}
},
*/
- // restrict socket.io transport methods
+ /*
+ * Restrict socket.io transport methods
+ */
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
- // Allow Load Testing tools to hit the Etherpad Instance. Warning this will disable security on the instance.
+ /*
+ * Allow Load Testing tools to hit the Etherpad Instance.
+ *
+ * WARNING: this will disable security on the instance.
+ */
"loadTest": false,
- // Disable indentation on new line when previous line ends with some special chars (':', '[', '(', '{')
+ /*
+ * Disable indentation on new line when previous line ends with some special
+ * chars (':', '[', '(', '{')
+ */
+
/*
"indentationOnNewLine": false,
*/
- /* The toolbar buttons configuration.
+ /*
+ * Toolbar buttons configuration.
+ *
+ * Uncomment to customize.
+ */
+
+ /*
"toolbar": {
"left": [
["bold", "italic", "underline", "strikethrough"],
@@ -224,31 +357,43 @@
},
*/
- /* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
+ /*
+ * The log level we are using.
+ *
+ * Valid values: DEBUG, INFO, WARN, ERROR
+ */
"loglevel": "INFO",
- //Logging configuration. See log4js documentation for further information
- // https://github.com/nomiddlename/log4js-node
- // You can add as many appenders as you want here:
+ /*
+ * Logging configuration. See log4js documentation for further information:
+ * https://github.com/nomiddlename/log4js-node
+ *
+ * You can add as many appenders as you want here.
+ */
"logconfig" :
{ "appenders": [
{ "type": "console"
//, "category": "access"// only logs pad access
}
- /*
+
+ /*
, { "type": "file"
, "filename": "your-log-file-here.log"
, "maxLogSize": 1024
, "backups": 3 // how many log files there're gonna be at max
//, "category": "test" // only log a specific category
- }*/
- /*
+ }
+ */
+
+ /*
, { "type": "logLevelFilter"
, "level": "warn" // filters out all log messages that have a lower level than "error"
, "appender":
{ Use whatever appender you want here }
- }*/
- /*
+ }
+ */
+
+ /*
, { "type": "logLevelFilter"
, "level": "error" // filters out all log messages that have a lower level than "error"
, "appender":
@@ -265,7 +410,9 @@
}
}
}
- }*/
+ }
+ */
+
]
- }
+ } // logconfig
}
diff --git a/src/locales/bn.json b/src/locales/bn.json
index 010272077..d8ee8bb1e 100644
--- a/src/locales/bn.json
+++ b/src/locales/bn.json
@@ -5,7 +5,8 @@
"Nasir8891",
"Sankarshan",
"Aftab1995",
- "Aftabuzzaman"
+ "Aftabuzzaman",
+ "আফতাবুজ্জামান"
]
},
"index.newPad": "নতুন প্যাড",
diff --git a/src/locales/diq.json b/src/locales/diq.json
index a823232f5..453867566 100644
--- a/src/locales/diq.json
+++ b/src/locales/diq.json
@@ -107,7 +107,7 @@
"timeslider.month.february": "Zemherı",
"timeslider.month.march": "Adar",
"timeslider.month.april": "Nisane",
- "timeslider.month.may": "Gúlan",
+ "timeslider.month.may": "Gulane",
"timeslider.month.june": "Heziran",
"timeslider.month.july": "Temuz",
"timeslider.month.august": "Tebaxe",
diff --git a/src/locales/et.json b/src/locales/et.json
index 3ea8b3e63..84430daf7 100644
--- a/src/locales/et.json
+++ b/src/locales/et.json
@@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
- "Kristian.kankainen"
+ "Kristian.kankainen",
+ "Tiblu"
]
},
"index.newPad": "Uus klade",
@@ -88,7 +89,7 @@
"timeslider.toolbar.exportlink.title": "Eksport",
"timeslider.exportCurrent": "Ekspordi käesolev versioon kuiː",
"timeslider.version": "Versioon {{version}}",
- "timeslider.saved": "Salvestatud {{day}}. {{month}}il {{year}}. aastal",
+ "timeslider.saved": "Salvestatud {{day}} {{month}} {{year}}",
"timeslider.dateformat": "{{day}}.{{month}}.{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "Jaanuar",
"timeslider.month.february": "Veebruar",
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 29583d092..b7f59885b 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -120,7 +120,7 @@
"timeslider.playPause": "Lecture / Pause des contenus du pad",
"timeslider.backRevision": "Reculer d’une révision dans ce pad",
"timeslider.forwardRevision": "Avancer d’une révision dans ce pad",
- "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{seconds}}:{{minutes}}:{{hours}}",
+ "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "janvier",
"timeslider.month.february": "février",
"timeslider.month.march": "mars",
diff --git a/src/locales/hu.json b/src/locales/hu.json
index e8045a7ce..af273f695 100644
--- a/src/locales/hu.json
+++ b/src/locales/hu.json
@@ -7,7 +7,8 @@
"Tgr",
"Csega",
"BanKris",
- "Notramo"
+ "Notramo",
+ "Bencemac"
]
},
"index.newPad": "Új notesz",
@@ -58,7 +59,7 @@
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document formátum)",
- "pad.importExport.abiword.innerHTML": "Csak szöveges, vagy HTML formátumokból importálhatsz. A speciális importálási funkciókért kérjük telepítsd az abiword-öt.",
+ "pad.importExport.abiword.innerHTML": "Csak szöveges, vagy HTML formátumokból importálhatsz. A speciális importálási funkciókért kérjük telepítsd az AbiWord-öt.",
"pad.modals.connected": "Kapcsolódva.",
"pad.modals.reconnecting": "Újrakapcsolódás a noteszhez...",
"pad.modals.forcereconnect": "Újrakapcsolódás kényszerítése",
diff --git a/src/locales/ml.json b/src/locales/ml.json
index 371e1ceb5..7d6dd66b9 100644
--- a/src/locales/ml.json
+++ b/src/locales/ml.json
@@ -6,7 +6,8 @@
"Hrishikesh.kb",
"Praveenp",
"Santhosh.thottingal",
- "Nesi"
+ "Nesi",
+ "Jinoytommanjaly"
]
},
"index.newPad": "പുതിയ പാഡ്",
@@ -61,6 +62,7 @@
"pad.modals.connected": "ബന്ധിപ്പിച്ചിരിക്കുന്നു.",
"pad.modals.reconnecting": "താങ്കളുടെ പാഡിലേയ്ക്ക് വീണ്ടും ബന്ധിപ്പിക്കുന്നു...",
"pad.modals.forcereconnect": "എന്തായാലും ബന്ധിപ്പിക്കുക",
+ "pad.modals.cancel": "റദ്ദാക്കുക",
"pad.modals.userdup": "മറ്റൊരു ജാലകത്തിൽ തുറന്നിരിക്കുന്നു",
"pad.modals.userdup.explanation": "ഈ കമ്പ്യൂട്ടറിൽ ഈ പാഡ് ഒന്നിലധികം ബ്രൗസർ ജാലകങ്ങളിൽ തുറന്നതായി കാണുന്നു.",
"pad.modals.userdup.advice": "ഈ ജാലകം തന്നെ ഉപയോഗിക്കാനായി ബന്ധിപ്പിക്കുക",
diff --git a/src/locales/nap.json b/src/locales/nap.json
index c31d525a1..a5cec4986 100644
--- a/src/locales/nap.json
+++ b/src/locales/nap.json
@@ -2,7 +2,8 @@
"@metadata": {
"authors": [
"Chelin",
- "C.R."
+ "C.R.",
+ "Ruthven"
]
},
"index.newPad": "Nuovo Pad",
@@ -15,7 +16,7 @@
"pad.toolbar.ul.title": "Ennece puntato (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "Rientro (TAB)",
"pad.toolbar.unindent.title": "Riduce rientro (Shift+TAB)",
- "pad.toolbar.undo.title": "Annulla (Ctrl-Z)",
+ "pad.toolbar.undo.title": "Sfàjere (Ctrl-Z)",
"pad.toolbar.redo.title": "Ripete (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Elimina 'e culure ca 'ndicanno 'e auture (Ctrl+Shift+C)",
"pad.toolbar.import_export.title": "'Mporta/esporta 'e/a diverse furmate 'e file",
@@ -67,7 +68,7 @@
"pad.modals.initsocketfail.explanation": "Nun se può cunnettà 'o server e sincronizzaziona.",
"pad.modals.initsocketfail.cause": "Stu fatto è succiesso, probabbilmente pe' bbìa 'e nu probblema c' 'o navigatóre 'o ll'internet.",
"pad.modals.slowcommit.explanation": "'O server nun risponne.",
- "pad.modals.slowcommit.cause": "Stu fatto può darse ca è causato pe' bbìa 'e prubbleme 'e connettività 'e rezza.",
+ "pad.modals.slowcommit.cause": "'Sto fatto putiss' essere causato 'a prubblemi 'e connettività 'e rezza.",
"pad.modals.badChangeset.explanation": "Nu cagnamento ca stavate facenno è stato classeficato comme illegale p' 'o server 'e sincronizzaziona.",
"pad.modals.badChangeset.cause": "Chistu fatto può darse ca è causato pe' bbìa 'e na mpustazione errata d' 'o server o cocch'atu comportamento nun preveduto. Pe' piacere cuntattate l'ammenistratore d' 'o servizio, si se pienza ca chist'è n'errore. Tentate a ve riconnettà pe' cuntinuà 'a edità.",
"pad.modals.corruptPad.explanation": "'O pad addò vulevate trasì è scassato.",
diff --git a/src/locales/pl.json b/src/locales/pl.json
index c26328786..e384241b3 100644
--- a/src/locales/pl.json
+++ b/src/locales/pl.json
@@ -8,7 +8,8 @@
"Macofe",
"Pan Cube",
"Mateon1",
- "Teeed"
+ "Teeed",
+ "DeRudySoulStorm"
]
},
"index.newPad": "Nowy dokument",
@@ -59,7 +60,7 @@
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
- "pad.importExport.abiword.innerHTML": "Możesz importować pliki tylko w formacie zwykłego tekstu lub html. Aby umożliwić bardziej zaawansowane funkcje importu, zainstaluj abiword.",
+ "pad.importExport.abiword.innerHTML": "Możesz importować pliki tylko w formacie zwykłego tekstu lub HTML. Aby umożliwić bardziej zaawansowane funkcje importu, zainstaluj AbiWord.",
"pad.modals.connected": "Połączony.",
"pad.modals.reconnecting": "Ponowne łączenie z dokumentem...",
"pad.modals.forcereconnect": "Wymuś ponowne połączenie",
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 801ceaa39..5d9911466 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -8,7 +8,8 @@
"Volkov",
"Nzeemin",
"Facenapalm",
- "Patrick Star"
+ "Patrick Star",
+ "Movses"
]
},
"index.newPad": "Создать",
@@ -59,7 +60,7 @@
"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. Для более продвинутых функций импорта, пожалуйста,\n установите AbiWord.",
"pad.modals.connected": "Подключен.",
"pad.modals.reconnecting": "Повторное подключение к вашему документу",
"pad.modals.forcereconnect": "Принудительное переподключение",
diff --git a/src/locales/sl.json b/src/locales/sl.json
index 94ccfa73a..f4168f2c1 100644
--- a/src/locales/sl.json
+++ b/src/locales/sl.json
@@ -4,7 +4,8 @@
"Dbc334",
"Mateju",
"Skalcaa",
- "HairyFotr"
+ "HairyFotr",
+ "Upwinxp"
]
},
"index.newPad": "Nov dokument",
@@ -68,7 +69,7 @@
"pad.modals.unauth.explanation": "Med pregledovanjem te strani so se dovoljenja za ogled spremenila. Poskusite se ponovno povezati.",
"pad.modals.looping.explanation": "Zaznane so težave pri komunikaciji s strežnikom za usklajevanje.",
"pad.modals.looping.cause": "Morda ste se povezali preko neustrezno nastavljenega požarnega zidu ali posredniškega strežnika.",
- "pad.modals.initsocketfail": "Strežnika je nedosegljiv.",
+ "pad.modals.initsocketfail": "Strežnik je nedosegljiv.",
"pad.modals.initsocketfail.explanation": "Povezava s strežnikom za usklajevanje ni mogoča.",
"pad.modals.initsocketfail.cause": "Najverjetneje gre za težavo z vašim brskalnikom, ali internetno povezavo.",
"pad.modals.slowcommit.explanation": "Strežnik se ne odziva.",
diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js
index 34fce29ed..ef41865e3 100644
--- a/src/node/hooks/express/static.js
+++ b/src/node/hooks/express/static.js
@@ -17,11 +17,12 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side.
+ // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
var jsServer = new (Yajsml.Server)({
rootPath: 'javascripts/src/'
- , rootURI: 'http://localhost:' + settings.port + '/static/js/'
+ , rootURI: 'http://invalid.invalid/static/js/'
, libraryPath: 'javascripts/lib/'
- , libraryURI: 'http://localhost:' + settings.port + '/static/plugins/'
+ , libraryURI: 'http://invalid.invalid/static/plugins/'
, requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround.
});
diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js
index 4cb4b9d3e..fd3a579de 100644
--- a/src/node/hooks/express/webaccess.js
+++ b/src/node/hooks/express/webaccess.js
@@ -33,7 +33,7 @@ exports.basicAuth = function (req, res, next) {
var authenticate = function (cb) {
// If auth headers are present use them to authenticate...
if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
- var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
+ var userpass = Buffer.from(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
var username = userpass.shift();
var password = userpass.join(':');
var fallback = function(success) {
diff --git a/src/node/server.js b/src/node/server.js
index 1907c29f8..9ccedfa60 100755
--- a/src/node/server.js
+++ b/src/node/server.js
@@ -24,6 +24,7 @@
var log4js = require('log4js')
, async = require('async')
, stats = require('./stats')
+ , NodeVersion = require('./utils/NodeVersion')
;
log4js.replaceConsole();
@@ -39,6 +40,16 @@ var settings
var npm = require("npm/lib/npm.js");
async.waterfall([
+ function(callback)
+ {
+ NodeVersion.enforceMinNodeVersion('6.9.0', callback);
+ },
+
+ function(callback)
+ {
+ NodeVersion.checkDeprecationStatus('8.9.0', '1.8.0', callback);
+ },
+
// load npm
function(callback) {
npm.load({}, function(er) {
diff --git a/src/node/stats.js b/src/node/stats.js
index 24efaf4a8..5ffa8a07b 100644
--- a/src/node/stats.js
+++ b/src/node/stats.js
@@ -1,3 +1,19 @@
-var measured = require('measured')
+/*
+ * TODO: this polyfill is needed for Node 6.9 support.
+ *
+ * Once minimum supported Node version is raised to 8.9.0, it will be removed.
+ */
+if (!Object.values) {
+ var log4js = require('log4js');
+ var statsLogger = log4js.getLogger("stats");
-module.exports = measured.createCollection();
\ No newline at end of file
+ statsLogger.warn(`Enabling a polyfill to run on this Node version (${process.version}). Next Etherpad version will remove support for Node version < 8.9.0. Please update your runtime.`);
+
+ var values = require('object.values');
+
+ values.shim();
+}
+
+var measured = require('measured-core')
+
+module.exports = measured.createCollection();
diff --git a/src/node/utils/Cli.js b/src/node/utils/Cli.js
index 154590dc7..04c532fa0 100644
--- a/src/node/utils/Cli.js
+++ b/src/node/utils/Cli.js
@@ -40,12 +40,12 @@ for ( var i = 0; i < argv.length; i++ ) {
}
// Override location of settings.json file
- if ( prevArg == '--sessionkey' || prevArg == '-k' ) {
+ if ( prevArg == '--sessionkey' ) {
exports.argv.sessionkey = arg;
}
// Override location of settings.json file
- if ( prevArg == '--apikey' || prevArg == '-k' ) {
+ if ( prevArg == '--apikey' ) {
exports.argv.apikey = arg;
}
diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js
index 297c2d7a6..e8d47fb2a 100644
--- a/src/node/utils/ExportHelper.js
+++ b/src/node/utils/ExportHelper.js
@@ -21,10 +21,11 @@
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
exports.getPadPlainText = function(pad, revNum){
- var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
+ var _analyzeLine = exports._analyzeLine;
+ var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
- var apool = pad.pool();
+ var apool = pad.pool;
var pieces = [];
for (var i = 0; i < textLines.length; i++){
diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js
index bd177ac47..f001fe452 100644
--- a/src/node/utils/ExportHtml.js
+++ b/src/node/utils/ExportHtml.js
@@ -268,7 +268,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
}
// close all tags upto the outer most
- if (outermostTag != -1)
+ if (outermostTag !== -1)
{
while ( outermostTag >= 0 )
{
@@ -282,7 +282,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
{
if (openTags.indexOf(usedAttribs[i]) === -1)
{
- emitOpenTag(usedAttribs[i])
+ emitOpenTag(usedAttribs[i]);
}
}
@@ -304,7 +304,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
// close all the tags that are open after the last op
while (openTags.length > 0)
{
- emitCloseTag(openTags[0])
+ emitCloseTag(openTags[0]);
}
} // end processNextChars
if (urls)
@@ -333,148 +333,128 @@ function getHTMLFromAtext(pad, atext, authorColors)
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
- var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
- var listLevels = []
+ var openLists = [];
for (var i = 0; i < textLines.length; i++)
{
+ var context;
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline);
- listLevels.push(line.listLevel)
if (line.listLevel)//If we are inside a list
{
- // do list stuff
- var whichList = -1; // index into lists or -1
- if (line.listLevel)
- {
- whichList = lists.length;
- for (var j = lists.length - 1; j >= 0; j--)
- {
- if (line.listLevel <= lists[j][0])
- {
- whichList = j;
- }
- }
- }
-
- if (whichList >= lists.length)//means we are on a deeper level of indentation than the previous line
- {
- if(lists.length > 0){
- pieces.push('')
- }
- lists.push([line.listLevel, line.listTypeName]);
-
- // if there is a previous list we need to open x tags, where x is the difference of the levels
- // if there is no previous list we need to open x tags, where x is the wanted level
- var toOpen = lists.length > 1 ? line.listLevel - lists[lists.length - 2][0] - 1 : line.listLevel - 1
-
- if(line.listTypeName == "number")
- {
- if(toOpen > 0){
- pieces.push(new Array(toOpen + 1).join(''))
- }
- pieces.push('- ', lineContent || '
');
- }
- else
- {
- if(toOpen > 0){
- pieces.push(new Array(toOpen + 1).join(''))
- }
- pieces.push('- ', lineContent || '
');
- }
- }
- //the following code *seems* dead after my patch.
- //I keep it just in case I'm wrong...
- /*else if (whichList == -1)//means we are not inside a list
- {
- if (line.text)
- {
- console.log('trace 1');
- // non-blank line, end all lists
- if(line.listTypeName == "number")
- {
- pieces.push(new Array(lists.length + 1).join('
'));
- }
- else
- {
- pieces.push(new Array(lists.length + 1).join('
'));
- }
- lists.length = 0;
- pieces.push(lineContent, '
');
- }
- else
- {
- console.log('trace 2');
- pieces.push('
');
- }
- }*/
- else//means we are getting closer to the lowest level of indentation or are at the same level
- {
- var toClose = lists.length > 0 ? listLevels[listLevels.length - 2] - line.listLevel : 0
- if( toClose > 0){
- pieces.push('')
- if(lists[lists.length - 1][1] == "number")
- {
- pieces.push(new Array(toClose+1).join('
'))
- pieces.push('- ', lineContent || '
');
- }
- else
- {
- pieces.push(new Array(toClose+1).join('
'))
- pieces.push('- ', lineContent || '
');
- }
- lists = lists.slice(0,whichList+1)
- } else {
- pieces.push(' - ', lineContent || '
');
- }
- }
- }
- else//outside any list, need to close line.listLevel of lists
- {
- if(lists.length > 0){
- if(lists[lists.length - 1][1] == "number"){
- pieces.push('
');
- pieces.push(new Array(listLevels[listLevels.length - 2]).join('
'))
- } else {
- pieces.push('');
- pieces.push(new Array(listLevels[listLevels.length - 2]).join(''))
- }
- }
- lists = []
-
- var context = {
+ context = {
line: line,
lineContent: lineContent,
apool: apool,
attribLine: attribLines[i],
text: textLines[i],
padId: pad.id
- }
-
- var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", context, " ", " ", "");
-
- if (lineContentFromHook)
+ };
+ var prevLine = null;
+ var nextLine = null;
+ if (i > 0)
{
- pieces.push(lineContentFromHook, '');
+ prevLine = _analyzeLine(textLines[i -1], attribLines[i -1], apool);
}
- else
+ if (i < textLines.length)
{
- pieces.push(lineContent, '
');
+ nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
+ }
+ hooks.aCallAll('getLineHTMLForExport', context);
+ //To create list parent elements
+ if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName))
+ {
+ var exists = _.find(openLists, function (item)
+ {
+ return (item.level === line.listLevel && item.type === line.listTypeName);
+ });
+ if (!exists) {
+ var prevLevel = 0;
+ if (prevLine && prevLine.listLevel) {
+ prevLevel = prevLine.listLevel;
+ }
+ if (prevLine && line.listTypeName !== prevLine.listTypeName)
+ {
+ prevLevel = 0;
+ }
+
+ for (var diff = prevLevel; diff < line.listLevel; diff++) {
+ openLists.push({level: diff, type: line.listTypeName});
+ var prevPiece = pieces[pieces.length - 1];
+
+ if (prevPiece.indexOf("") === 0)
+ {
+ pieces.push("- ");
+ }
+
+ if (line.listTypeName === "number")
+ {
+ pieces.push("
");
+ }
+ else
+ {
+ pieces.push("");
+ }
+ }
+ }
+ }
+
+ pieces.push("- ", context.lineContent);
+
+ // To close list elements
+ if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName)
+ {
+ pieces.push("
");
+ }
+ if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName))
+ {
+ var nextLevel = 0;
+ if (nextLine && nextLine.listLevel) {
+ nextLevel = nextLine.listLevel;
+ }
+ if (nextLine && line.listTypeName !== nextLine.listTypeName)
+ {
+ nextLevel = 0;
+ }
+
+ for (var diff = nextLevel; diff < line.listLevel; diff++)
+ {
+ openLists = openLists.filter(function(el)
+ {
+ return el.level !== diff && el.type !== line.listTypeName;
+ });
+
+ if (pieces[pieces.length - 1].indexOf("
");
+ }
+
+ if (line.listTypeName === "number")
+ {
+ pieces.push("
");
+ }
+ else
+ {
+ pieces.push("
");
+ }
+ }
}
}
- }
+ else//outside any list, need to close line.listLevel of lists
+ {
+ context = {
+ line: line,
+ lineContent: lineContent,
+ apool: apool,
+ attribLine: attribLines[i],
+ text: textLines[i],
+ padId: pad.id
+ };
- for (var k = lists.length - 1; k >= 0; k--)
- {
- if(lists[k][1] == "number")
- {
- pieces.push('');
+ hooks.aCallAll("getLineHTMLForExport", context);
+ pieces.push(context.lineContent, "
");
+ }
}
- else
- {
- pieces.push('');
- }
- }
return pieces.join('');
}
diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js
index a56e347db..4596f404c 100644
--- a/src/node/utils/Minify.js
+++ b/src/node/utils/Minify.js
@@ -264,7 +264,8 @@ function getAceFile(callback) {
async.forEach(founds, function (item, callback) {
var filename = item.match(/"([^"]*)"/)[1];
- var baseURI = 'http://localhost:' + settings.port;
+ // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
+ var baseURI = 'http://invalid.invalid';
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)
diff --git a/src/node/utils/NodeVersion.js b/src/node/utils/NodeVersion.js
new file mode 100644
index 000000000..49c1efe85
--- /dev/null
+++ b/src/node/utils/NodeVersion.js
@@ -0,0 +1,54 @@
+/**
+ * Checks related to Node runtime version
+ */
+
+/*
+ * 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.
+ */
+
+/**
+ * Quits if Etherpad is not running on a given minimum Node version
+ *
+ * @param {String} minNodeVersion Minimum required Node version
+ * @param {Function} callback Standard callback function
+ */
+exports.enforceMinNodeVersion = function(minNodeVersion, callback) {
+ const semver = require('semver');
+ const currentNodeVersion = process.version;
+
+ // we cannot use template literals, since we still do not know if we are
+ // running under Node >= 4.0
+ if (semver.lt(currentNodeVersion, minNodeVersion)) {
+ console.error('Running Etherpad on Node ' + currentNodeVersion + ' is not supported. Please upgrade at least to Node ' + minNodeVersion);
+ } else {
+ console.debug('Running on Node ' + currentNodeVersion + ' (minimum required Node version: ' + minNodeVersion + ')');
+ callback();
+ }
+};
+
+/**
+ * Prints a warning if running on a supported but deprecated Node version
+ *
+ * @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated
+ * @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases
+ */
+exports.checkDeprecationStatus = function(lowestNonDeprecatedNodeVersion, epRemovalVersion, callback) {
+ const semver = require('semver');
+ const currentNodeVersion = process.version;
+
+ if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {
+ console.warn(`Support for Node ${currentNodeVersion} will be removed in Etherpad ${epRemovalVersion}. Please consider updating at least to Node ${lowestNonDeprecatedNodeVersion}`);
+ }
+
+ callback();
+};
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js
index 65fe5d2f1..c01a95fb3 100644
--- a/src/node/utils/caching_middleware.js
+++ b/src/node/utils/caching_middleware.js
@@ -49,7 +49,7 @@ CachingMiddleware.prototype = new function () {
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path;
- var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');
+ var cacheKey = Buffer.from(path).toString('base64').replace(/[/+=]/g, '');
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
var modifiedSince = (req.headers['if-modified-since']
diff --git a/src/package.json b/src/package.json
index cb243ccb1..fcf5b97c0 100644
--- a/src/package.json
+++ b/src/package.json
@@ -1,60 +1,84 @@
{
- "name" : "ep_etherpad-lite",
- "description" : "A Etherpad based on node.js",
- "homepage" : "http://etherpad.org",
- "keywords" : ["etherpad", "realtime", "collaborative", "editor"],
- "author" : "Etherpad Foundation",
- "contributors" : [
- { "name": "John McLear" },
- { "name": "Hans Pinckaers" },
- { "name": "Robin Buse" },
- { "name": "Marcel Klehr" },
- { "name": "Peter Martischka" }
- ],
- "dependencies" : {
- "etherpad-yajsml" : "0.0.2",
- "request" : "2.83.0",
- "etherpad-require-kernel" : "1.0.9",
- "resolve" : "1.1.7",
- "socket.io" : "1.7.3",
- "ueberdb2" : "0.3.8",
- "express" : "4.13.4",
- "express-session" : "1.13.0",
- "cookie-parser" : "1.3.4",
- "async" : "0.9.0",
- "clean-css" : "3.4.19",
- "uglify-js" : "2.6.2",
- "formidable" : "1.2.1",
- "log4js" : "0.6.35",
- "cheerio" : "0.20.0",
- "async-stacktrace" : "0.0.2",
- "npm" : ">=4.0.2",
- "ejs" : "2.5.7",
- "graceful-fs" : "4.1.3",
- "slide" : "1.1.6",
- "semver" : "5.1.0",
- "security" : "1.0.0",
- "tinycon" : "0.0.1",
- "underscore" : "1.8.3",
- "unorm" : "1.4.1",
- "languages4translatewiki" : "0.1.3",
- "swagger-node-express" : "2.1.3",
- "channels" : "0.0.4",
- "jsonminify" : "0.4.1",
- "measured" : "1.1.0",
- "mocha" : "5.0.5",
- "supertest" : "3.0.0"
+ "name": "ep_etherpad-lite",
+ "description": "A Etherpad based on node.js",
+ "homepage": "http://etherpad.org",
+ "keywords": [
+ "etherpad",
+ "realtime",
+ "collaborative",
+ "editor"
+ ],
+ "author": "Etherpad Foundation",
+ "contributors": [
+ {
+ "name": "John McLear"
+ },
+ {
+ "name": "Hans Pinckaers"
+ },
+ {
+ "name": "Robin Buse"
+ },
+ {
+ "name": "Marcel Klehr"
+ },
+ {
+ "name": "Peter Martischka"
+ }
+ ],
+ "dependencies": {
+ "async": "0.9.0",
+ "async-stacktrace": "0.0.2",
+ "channels": "0.0.4",
+ "cheerio": "0.20.0",
+ "clean-css": "3.4.19",
+ "cookie-parser": "1.3.4",
+ "ejs": "2.5.7",
+ "etherpad-require-kernel": "1.0.9",
+ "etherpad-yajsml": "0.0.2",
+ "express": "4.16.3",
+ "express-session": "1.15.6",
+ "formidable": "1.2.1",
+ "graceful-fs": "4.1.3",
+ "jsonminify": "0.4.1",
+ "languages4translatewiki": "0.1.3",
+ "log4js": "0.6.35",
+ "measured-core": "1.11.2",
+ "npm": "6.4.0",
+ "object.values": "^1.0.4",
+ "request": "2.83.0",
+ "resolve": "1.1.7",
+ "security": "1.0.0",
+ "semver": "5.1.0",
+ "slide": "1.1.6",
+ "socket.io": "1.7.3",
+ "swagger-node-express": "2.1.3",
+ "tinycon": "0.0.1",
+ "ueberdb2": "0.4.0",
+ "uglify-js": "2.6.2",
+ "underscore": "1.8.3",
+ "unorm": "1.4.1"
+ },
+ "bin": {
+ "etherpad-lite": "./node/server.js"
},
- "bin": { "etherpad-lite": "./node/server.js" },
"devDependencies": {
- "wd" : "1.6.1"
- },
- "engines" : { "node" : ">=0.10.0",
- "npm" : ">=1.0"
- },
- "repository" : { "type" : "git",
- "url" : "http://github.com/ether/etherpad-lite.git"
- },
- "version" : "1.6.6",
- "license" : "Apache-2.0"
+ "mocha": "5.2.0",
+ "nyc": "^12.0.2",
+ "supertest": "3.0.0",
+ "wd": "1.10.3"
+ },
+ "engines": {
+ "node": ">=6.9.0",
+ "npm": ">=3.10.8"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/ether/etherpad-lite.git"
+ },
+ "scripts": {
+ "test": "nyc mocha --timeout 5000 ../tests/backend/specs/api"
+ },
+ "version": "1.7.0",
+ "license": "Apache-2.0"
}
diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js
index 53b233e07..17b216624 100644
--- a/src/static/js/AttributeManager.js
+++ b/src/static/js/AttributeManager.js
@@ -4,6 +4,10 @@ var _ = require('./underscore');
var lineMarkerAttribute = 'lmkr';
+// Some of these attributes are kept for compatibility purposes.
+// Not sure if we need all of them
+var DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
+
// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
@@ -35,6 +39,7 @@ var AttributeManager = function(rep, applyChangesetCallback)
// it will be considered as a line marker
};
+AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES;
AttributeManager.lineAttributes = lineAttributes;
AttributeManager.prototype = _(AttributeManager.prototype).extend({
@@ -375,7 +380,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];})
- .map(function(a){return a[0];}).difference(['author', 'lmkr', 'insertorder', 'start']).size().value();
+ .map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
//if we have marker and any of attributes don't need to have marker. we need delete it
if(hasMarker && !countAttribsWithMarker){
diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js
index 90cefa506..8b0e2c3e9 100644
--- a/src/static/js/ace2_inner.js
+++ b/src/static/js/ace2_inner.js
@@ -1778,19 +1778,15 @@ function Ace2Inner(){
strikethrough: true,
list: true
};
- var OTHER_INCORPED_ATTRIBS = {
- insertorder: true,
- author: true
- };
function isStyleAttribute(aname)
{
return !!STYLE_ATTRIBS[aname];
}
- function isOtherIncorpedAttribute(aname)
+ function isDefaultLineAttribute(aname)
{
- return !!OTHER_INCORPED_ATTRIBS[aname];
+ return AttributeManager.DEFAULT_LINE_ATTRIBUTES.indexOf(aname) !== -1;
}
function insertDomLines(nodeToAddAfter, infoStructs, isTimeUp)
@@ -2757,9 +2753,12 @@ function Ace2Inner(){
function analyzeChange(oldText, newText, oldAttribs, newAttribs, optSelStartHint, optSelEndHint)
{
+ // we need to take into account both the styles attributes & attributes defined by
+ // the plugins, so basically we can ignore only the default line attribs used by
+ // Etherpad
function incorpedAttribFilter(anum)
{
- return !isOtherIncorpedAttribute(rep.apool.getAttribKey(anum));
+ return !isDefaultLineAttribute(rep.apool.getAttribKey(anum));
}
function attribRuns(attribs)
@@ -3708,8 +3707,8 @@ function Ace2Inner(){
return; // This stops double enters in Opera but double Tabs still show on single tab keypress, adding keyCode == 9 to this doesn't help as the event is fired twice
}
var specialHandled = false;
- var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
- var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
+ var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == "keydown") : (type == "keypress"));
+ var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == "keydown") : (type == "keypress"));
var stopped = false;
inCallStackIfNecessary("handleKeyEvent", function()
diff --git a/src/static/js/domline.js b/src/static/js/domline.js
index 447c1497a..a7501fcc6 100644
--- a/src/static/js/domline.js
+++ b/src/static/js/domline.js
@@ -214,7 +214,7 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument)
result.clearSpans = function()
{
html = [];
- lineClass = ''; // non-null to cause update
+ lineClass = 'ace-line';
result.lineMarker = 0;
};
diff --git a/src/templates/admin/plugins-info.html b/src/templates/admin/plugins-info.html
index 8dd0bf88e..8c259ac20 100644
--- a/src/templates/admin/plugins-info.html
+++ b/src/templates/admin/plugins-info.html
@@ -27,7 +27,7 @@
Git sha: <%= gitCommit %>
Installed plugins
- <%- plugins.formatPlugins().replace(", ","\n") %>
+ <%- plugins.formatPlugins().replace(/, /g,"\n") %>
Installed parts
<%= plugins.formatParts() %>
diff --git a/src/templates/export_html.html b/src/templates/export_html.html
index b8893b717..5c017c8c1 100644
--- a/src/templates/export_html.html
+++ b/src/templates/export_html.html
@@ -20,7 +20,7 @@ ol {
padding-left: 0;
}
body > ol {
- counter-reset: first second third fourth fifth sixth seventh eigth ninth tenth eleventh twelth thirteenth fourteenth fifteenth sixteenth;
+ counter-reset: first second third fourth fifth sixth seventh eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth sixteenth;
}
ol > li:before {
content: counter(first) ". ";
@@ -51,40 +51,40 @@ ol > ol > ol > ol > ol > ol > ol > li:before {
counter-increment: seventh;
}
ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) ". ";
- counter-increment: eigth;
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) ". ";
+ counter-increment: eighth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) ". ";
counter-increment: ninth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) ". ";
counter-increment: tenth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) ". ";
counter-increment: eleventh;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) ". ";
- counter-increment: twelth;
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelfth) ". ";
+ counter-increment: twelfth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelfth) "." counter(thirteenth) ". ";
counter-increment: thirteenth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelfth) "." counter(thirteenth) "." counter(fourteenth) ". ";
counter-increment: fourteenth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". ";
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelfth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) ". ";
counter-increment: fifteenth;
}
ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > li:before {
- content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eigth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixthteenth) ". ";
- counter-increment: sixthteenth;
+ content: counter(first) "." counter(second) "." counter(third) "." counter(fourth) "." counter(fifth) "." counter(sixth) "." counter(seventh) "." counter(eighth) "." counter(ninth) "." counter(tenth) "." counter(eleventh) "." counter(twelfth) "." counter(thirteenth) "." counter(fourteenth) "." counter(fifteenth) "." counter(sixteenth) ". ";
+ counter-increment: sixteenth;
}
ol {
text-indent: 0px;
diff --git a/tests/README.md b/tests/README.md
index 201ee4c8c..6ab5dd23b 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,9 +1,11 @@
# About this folder: Tests
+Before running the tests, start an Etherpad instance on your machine.
+
## Frontend
-To run the tests, point your browser to `/tests/frontend`
+To run the frontend tests, point your browser to `/tests/frontend`
## Backend
-To run the tests, run ``bin/backendTests.sh``
+To run the backend tests, run `cd src` and then `npm test`
diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js
index 1db90580e..26abfd2c8 100644
--- a/tests/backend/specs/api/pad.js
+++ b/tests/backend/specs/api/pad.js
@@ -14,7 +14,19 @@ var apiVersion = 1;
var testPadId = makeid();
var lastEdited = "";
var text = generateLongText();
-var ULhtml = '
';
+
+/*
+ * Html document with nested lists of different types, to test its import and
+ * verify it is exported back correctly
+ */
+var ulHtml = '- item
- item1
- item2
';
+
+/*
+ * When exported back, Etherpad produces an html which is not exactly the same
+ * textually, but at least it remains standard compliant and has an equal DOM
+ * structure.
+ */
+var expectedHtml = '- item
- item1
- item2
';
describe('Connectivity', function(){
it('errors if can not connect', function(done) {
@@ -522,8 +534,8 @@ describe('setHTML', function(){
})
describe('setHTML', function(){
- it('Sets the HTML of a Pad with a bunch of weird unordered lists inserted', function(done) {
- api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ULhtml)
+ it('Sets the HTML of a Pad with complex nested lists of different types', function(done) {
+ api.get(endPoint('setHTML')+"&padID="+testPadId+"&html="+ulHtml)
.expect(function(res){
if(res.body.code !== 0) throw new Error("List HTML cant be imported")
})
@@ -533,12 +545,22 @@ describe('setHTML', function(){
})
describe('getHTML', function(){
- it('Gets the HTML of a Pad with a bunch of weird unordered lists inserted', function(done) {
+ it('Gets back the HTML of a Pad with complex nested lists of different types', function(done) {
api.get(endPoint('getHTML')+"&padID="+testPadId)
.expect(function(res){
- var ehtml = res.body.data.html.replace("