Merge remote-tracking branch 'upstream/develop' into develop

pull/3268/head
Mikk Andresen 2018-04-04 13:52:27 +03:00
commit 20428bb427
29 changed files with 396 additions and 122 deletions

110
README.md
View File

@ -1,28 +1,43 @@
### 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 # A really-real time collaborative word processor for the web
![alt text](https://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad") ![Demo Etherpad Animated Jif](https://i.imgur.com/zYrGkg3.gif "Etherpad in action on PrimaryPad")
# About # About
Etherpad is a really-real time collaborative editor maintained by the Etherpad Community. 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 written in JavaScript (99.9%) on both the server and client so it's easy for developers to maintain and add new features. Because of this Etherpad has tons of customizations that you can leverage. **[Try it out](http://beta.etherpad.org)**
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.
There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
There's also a full-featured plugin framework, allowing you to easily add your own features. By default your Etherpad is rather sparse and because Etherpad takes a lot of its inspiration from WordPress, plugins are really easy to install and update. Once you have Etherpad installed you should visit the plugin page and take control.
Finally, Etherpad comes with translations into most languages! Users are automatically delivered the correct language for their local settings.
**Visit [beta.etherpad.org](http://beta.etherpad.org) to test it live.**
Also, check out the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**, really!
# Installation # Installation
Etherpad works with node v0.10+ (except 6.0 and 6.1). ## Uber-Quick Ubuntu
```
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
sudo apt-get install -y nodejs
git clone git://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, Ideally the latest stable version, we recommend installing/compiling nodejs from source (avoiding apt).
**As any user (we recommend creating a separate user called etherpad):**
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 <http://127.0.0.1:9001> in your browser.
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 ## Windows
@ -52,27 +67,6 @@ If cloning to a subdirectory within another project, you may need to do the foll
2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`) 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` 3. Add auto-generated files to the main project `.gitignore`
[Next steps](#next-steps).
## GNU/Linux and other UNIX-like systems
You'll need gzip, git, curl, libssl develop libraries, python and gcc.
- *For Debian/Ubuntu*: `apt-get install gzip git 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, Ideally the latest stable version, we recommend installing/compiling nodejs from source (avoiding apt).
**As any user (we recommend creating a separate user called etherpad):**
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 <http://127.0.0.1:9001> in your browser.
Update to the latest version with `git pull origin`. The next start with bin/run.sh will update the dependencies.
You like it? [Next steps](#next-steps).
# Next Steps # Next Steps
## Tweak the settings ## Tweak the settings
@ -85,9 +79,9 @@ You should use a dedicated database such as "mysql", if you are planning on usin
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). Etherpad is very customizable through plugins. Instructions for installing themes and plugins can be found in [the plugin wiki article](https://github.com/ether/etherpad-lite/wiki/Available-Plugins).
## Helpful resources ## Helpful resources
The [wiki](https://github.com/ether/etherpad-lite/wiki) is your one-stop resource for Tutorials and How-to's, really check it out! Also, feel free to improve these wiki pages. The [wiki](https://github.com/ether/etherpad-lite/wiki) is your one-stop resource for Tutorials and How-to's.
Documentation can be found in `docs/`. Documentation can be found in `doc/`.
# Development # Development
@ -100,26 +94,38 @@ You can debug Etherpad using `bin/debugRun.sh`.
If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading). If you want to find out how Etherpad's `Easysync` works (the library that makes it really realtime), start with this [PDF](https://github.com/ether/etherpad-lite/raw/master/doc/easysync/easysync-full-description.pdf) (complex, but worth reading).
## Getting started ## Contributing
You know all this and just want to know how you can help? Read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md)
Look at the [TODO list](https://github.com/ether/etherpad-lite/wiki/TODO) and our [Issue tracker](https://github.com/ether/etherpad-lite/issues). (Please consider using [jshint](http://www.jshint.com/about/), if you plan to contribute code.)
Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), really!
# Get in touch # Get in touch
Join the [mailinglist](https://groups.google.com/group/etherpad-lite-dev) and make some noise on our busy freenode irc channel [#etherpad-lite-dev](https://webchat.freenode.net?channels=#etherpad-lite-dev)! [mailinglist](https://groups.google.com/group/etherpad-lite-dev)
[#etherpad-lite-dev freenode IRC](https://webchat.freenode.net?channels=#etherpad-lite-dev)!
# Modules created for this project # Languages
Etherpad is written in JavaScript on both the server and client so it's easy for developers to maintain and add new features.
* [ueberDB](https://github.com/Pita/ueberDB) "transforms every database into a object key value store" - manages all database access # HTTP API
* [channels](https://github.com/Pita/channels) "Event channels in node.js" - ensures that ueberDB operations are atomic and in series for each key Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
* [async-stacktrace](https://github.com/Pita/async-stacktrace) "Improves node.js stacktraces and makes it easier to handle errors" 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
There is a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
# Plugin Framework
Etherpad offers a plugin framework, allowing you to easily add your own features. By default your Etherpad is extremely light-weight and it's up to you to customize your experience. Once you have Etherpad installed you should visit the plugin page and take control.
# Translations / Localizations (i18n / l10n)
Etherpad comes with translations into all languages thanks to the team at TranslateWiki.
# FAQ
Visit the **[FAQ](https://github.com/ether/etherpad-lite/wiki/FAQ)**.
# Donate! # Donate!
* [Flattr](https://flattr.com/thing/71378/Etherpad-Foundation) * [Flattr](https://flattr.com/thing/71378/Etherpad-Foundation)
* Paypal - Press the donate button on [etherpad.org](http://etherpad.org) * Paypal - Press the donate button on [etherpad.org](http://etherpad.org)
* [Bitcoin](https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a) * [Bitcoin](https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a)
All donations go to the Etherpad foundation which is part of Software Freedom Conservency
# License # License
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html) [Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)

145
bin/checkAllPads.js Normal file
View File

@ -0,0 +1,145 @@
/*
This is a debug tool. It checks all revisions for data corruption
*/
if(process.argv.length != 2)
{
console.error("Use: node bin/checkAllPads.js");
process.exit(1);
}
//initalize the variables
var db, settings, padManager;
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
var Changeset = require("../src/static/js/Changeset");
async.series([
//load npm
function(callback) {
npm.load({}, callback);
},
//load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
//initalize the database
db.init(callback);
},
//load pads
function (callback)
{
padManager = require('../src/node/db/PadManager');
padManager.listAllPads(function(err, res)
{
padIds = res.padIDs;
callback(err);
});
},
function (callback)
{
async.forEach(padIds, function(padId, callback)
{
padManager.getPad(padId, function(err, pad) {
if (err) {
callback(err);
}
//check if the pad has a pool
if(pad.pool === undefined )
{
console.error("[" + pad.id + "] Missing attribute pool");
callback();
return;
}
//create an array with key kevisions
//key revisions always save the full pad atext
var head = pad.getHeadRevisionNumber();
var keyRevisions = [];
for(var i=0;i<head;i+=100)
{
keyRevisions.push(i);
}
//run trough all key revisions
async.forEachSeries(keyRevisions, function(keyRev, callback)
{
//create an array of revisions we need till the next keyRevision or the End
var revisionsNeeded = [];
for(var i=keyRev;i<=keyRev+100 && i<=head; i++)
{
revisionsNeeded.push(i);
}
//this array will hold all revision changesets
var revisions = [];
//run trough all needed revisions and get them from the database
async.forEach(revisionsNeeded, function(revNum, callback)
{
db.db.get("pad:"+pad.id+":revs:" + revNum, function(err, revision)
{
revisions[revNum] = revision;
callback(err);
});
}, function(err)
{
if(err)
{
callback(err);
return;
}
//check if the revision exists
if (revisions[keyRev] == null) {
console.error("[" + pad.id + "] Missing revision " + keyRev);
callback();
return;
}
//check if there is a atext in the keyRevisions
if(revisions[keyRev].meta === undefined || revisions[keyRev].meta.atext === undefined)
{
console.error("[" + pad.id + "] Missing atext in revision " + keyRev);
callback();
return;
}
var apool = pad.pool;
var atext = revisions[keyRev].meta.atext;
for(var i=keyRev+1;i<=keyRev+100 && i<=head; i++)
{
try
{
//console.log("[" + pad.id + "] check revision " + i);
var cs = revisions[i].changeset;
atext = Changeset.applyToAText(cs, atext, apool);
}
catch(e)
{
console.error("[" + pad.id + "] Bad changeset at revision " + i + " - " + e.message);
callback();
return;
}
}
callback();
});
}, callback);
});
}, callback);
}
], function (err)
{
if(err) throw err;
else
{
console.log("finished");
process.exit(0);
}
});

View File

@ -38,4 +38,4 @@ bin/installDeps.sh $* || exit 1
echo "Started Etherpad..." echo "Started Etherpad..."
SCRIPTPATH=`pwd -P` SCRIPTPATH=`pwd -P`
node $SCRIPTPATH/node_modules/ep_etherpad-lite/node/server.js $* node "${$SCRIPTPATH}/node_modules/ep_etherpad-lite/node/server.js" $*

View File

@ -1,4 +1,4 @@
#!/usr/bin/python -u #!/usr/bin/env PYTHONUNBUFFERED=1 python2
# #
# Created by Bjarni R. Einarsson, placed in the public domain. Go wild! # Created by Bjarni R. Einarsson, placed in the public domain. Go wild!
# #

View File

@ -8,7 +8,15 @@ cmd /C node -e "" || ( echo "Please install node.js ( https://nodejs.org )" && e
echo _ echo _
echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient. echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient.
cmd /C npm install src/ --loglevel warn || exit /B 1
mkdir node_modules
cd /D node_modules
mklink /D "ep_etherpad-lite" "..\src"
cd /D "ep_etherpad-lite"
cmd /C npm install --loglevel warn || exit /B 1
cd /D "%~dp0\.."
echo _ echo _
echo Copying custom templates... echo Copying custom templates...
@ -31,4 +39,4 @@ IF NOT EXIST settings.json (
) )
echo _ echo _
echo Installed Etherpad! To run Etherpad type start.bat echo Installed Etherpad! To run Etherpad type start.bat

View File

@ -108,6 +108,18 @@ Usage examples:
* https://github.com/tiblu/ep_authorship_toggle * https://github.com/tiblu/ep_authorship_toggle
## onAccessCheck
Called from: src/node/db/SecurityManager.js
Things in context:
1. padID - the pad the user wants to access
2. password - the password the user has given to access the pad
3. token - the token of the author
4. sessionCookie - the session the use has
This hook gets called when the access to the concrete pad is being checked. Return `false` to deny access.
## padCreate ## padCreate
Called from: src/node/db/Pad.js Called from: src/node/db/Pad.js

View File

@ -8,7 +8,8 @@
"Quinn", "Quinn",
"Aktron", "Aktron",
"Mormegil", "Mormegil",
"Dvorapa" "Dvorapa",
"Clon"
] ]
}, },
"index.newPad": "Založ nový Pad", "index.newPad": "Založ nový Pad",
@ -63,6 +64,8 @@
"pad.modals.connected": "Připojeno.", "pad.modals.connected": "Připojeno.",
"pad.modals.reconnecting": "Znovupřipojování k Padu…", "pad.modals.reconnecting": "Znovupřipojování k Padu…",
"pad.modals.forcereconnect": "Vynutit znovupřipojení", "pad.modals.forcereconnect": "Vynutit znovupřipojení",
"pad.modals.reconnecttimer": "Zkouším to znovu připojit",
"pad.modals.cancel": "Zrušit",
"pad.modals.userdup": "Otevřeno v jiném okně", "pad.modals.userdup": "Otevřeno v jiném okně",
"pad.modals.userdup.explanation": "Zdá se, že tento Pad je na tomto počítači otevřen ve více než jednom okně.", "pad.modals.userdup.explanation": "Zdá se, že tento Pad je na tomto počítači otevřen ve více než jednom okně.",
"pad.modals.userdup.advice": "Pro použití tohoto okna je třeba se znovu připojit.", "pad.modals.userdup.advice": "Pro použití tohoto okna je třeba se znovu připojit.",

View File

@ -47,7 +47,7 @@
"pad.settings.fontType.monospaced": "Yewca", "pad.settings.fontType.monospaced": "Yewca",
"pad.settings.globalView": "Asayışo Global", "pad.settings.globalView": "Asayışo Global",
"pad.settings.language": "Zıwan:", "pad.settings.language": "Zıwan:",
"pad.importExport.import_export": "Zeredayış/Teberdayış", "pad.importExport.import_export": "Zerredayış/Teberdayış",
"pad.importExport.import": "Dosya ya zi dokumanê meqaleyê de tesadufi bar ke", "pad.importExport.import": "Dosya ya zi dokumanê meqaleyê de tesadufi bar ke",
"pad.importExport.importSuccessful": "Mıwafaq biye", "pad.importExport.importSuccessful": "Mıwafaq biye",
"pad.importExport.export": "Mewcud bloknoti ateberd:", "pad.importExport.export": "Mewcud bloknoti ateberd:",
@ -95,7 +95,7 @@
"timeslider.toolbar.returnbutton": "Peyser şo ped", "timeslider.toolbar.returnbutton": "Peyser şo ped",
"timeslider.toolbar.authors": "Nuştoği:", "timeslider.toolbar.authors": "Nuştoği:",
"timeslider.toolbar.authorsList": "Nuştoği çıniyê", "timeslider.toolbar.authorsList": "Nuştoği çıniyê",
"timeslider.toolbar.exportlink.title": "Teber de", "timeslider.toolbar.exportlink.title": "Teberdayış",
"timeslider.exportCurrent": "Versiyonê enewki teber de:", "timeslider.exportCurrent": "Versiyonê enewki teber de:",
"timeslider.version": "Versiyonê {{version}}", "timeslider.version": "Versiyonê {{version}}",
"timeslider.saved": "{{day}} {{month}}, {{year}} de biyo qeyd", "timeslider.saved": "{{day}} {{month}}, {{year}} de biyo qeyd",

View File

@ -65,7 +65,7 @@
"pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF", "pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)", "pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.abiword.innerHTML": "Tuonti on tuettu vain HTML- ja raakatekstitiedostoista. Lisätietoja tuonnin lisäasetuksista on sivulla <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">install abiword</a>.", "pad.importExport.abiword.innerHTML": "Tuonti on tuettu vain HTML- ja raakatekstitiedostoista. Monipuoliset tuontiominaisuudet ovat käytettävissä <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">asentamalla AbiWord</a>.",
"pad.modals.connected": "Yhdistetty.", "pad.modals.connected": "Yhdistetty.",
"pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen...", "pad.modals.reconnecting": "Muodostetaan yhteyttä muistioon uudelleen...",
"pad.modals.forcereconnect": "Pakota yhdistämään uudelleen", "pad.modals.forcereconnect": "Pakota yhdistämään uudelleen",

View File

@ -23,7 +23,8 @@
"Fylip22", "Fylip22",
"C13m3n7", "C13m3n7",
"Wladek92", "Wladek92",
"Urhixidur" "Urhixidur",
"Envlh"
] ]
}, },
"index.newPad": "Nouveau pad", "index.newPad": "Nouveau pad",
@ -48,7 +49,7 @@
"pad.colorpicker.save": "Enregistrer", "pad.colorpicker.save": "Enregistrer",
"pad.colorpicker.cancel": "Annuler", "pad.colorpicker.cancel": "Annuler",
"pad.loading": "Chargement…", "pad.loading": "Chargement…",
"pad.noCookie": "Le témoin (cookie) na pas pu être trouvé. Veuillez autoriser les témoins dans votre navigateur!", "pad.noCookie": "Le cookie na pas pu être trouvé. Veuillez autoriser les cookies dans votre navigateur!",
"pad.passwordRequired": "Vous avez besoin d'un mot de passe pour accéder à ce pad", "pad.passwordRequired": "Vous avez besoin d'un mot de passe pour accéder à ce pad",
"pad.permissionDenied": "Vous n'avez pas la permission daccéder à ce pad", "pad.permissionDenied": "Vous n'avez pas la permission daccéder à ce pad",
"pad.wrongPassword": "Votre mot de passe est incorrect", "pad.wrongPassword": "Votre mot de passe est incorrect",

View File

@ -5,7 +5,8 @@
"Dilyaramude", "Dilyaramude",
"George Animal", "George Animal",
"Gomada", "Gomada",
"Mehk63" "Mehk63",
"Ghybu"
] ]
}, },
"index.newPad": "Bloknota nû", "index.newPad": "Bloknota nû",
@ -40,6 +41,7 @@
"pad.importExport.exportpdf": "PDF", "pad.importExport.exportpdf": "PDF",
"pad.modals.connected": "Hate girêdan.", "pad.modals.connected": "Hate girêdan.",
"pad.modals.reconnecting": "Ji bloknota te re dîsa tê girêdan...", "pad.modals.reconnecting": "Ji bloknota te re dîsa tê girêdan...",
"pad.modals.cancel": "Betal bike",
"pad.modals.userdup": "Di pencereyek din de vebû", "pad.modals.userdup": "Di pencereyek din de vebû",
"pad.modals.userdup.advice": "Ji bo di vê pencereye de bikarbînîy dîsa giredanek çeke.", "pad.modals.userdup.advice": "Ji bo di vê pencereye de bikarbînîy dîsa giredanek çeke.",
"pad.modals.unauth": "Desthilatdar nîne", "pad.modals.unauth": "Desthilatdar nîne",

48
src/locales/pms.json Normal file
View File

@ -0,0 +1,48 @@
{
"@metadata": {
"authors": [
"Borichèt"
]
},
"index.newPad": "Feuj neuv",
"index.createOpenPad": "o creé/duverté un feuj antitolà:",
"pad.toolbar.bold.title": "Grassèt (Ctrl+B)",
"pad.toolbar.italic.title": "Corsiv (Ctrl+I)",
"pad.toolbar.underline.title": "Sotlignà (Ctrl+U)",
"pad.toolbar.strikethrough.title": "Barà (Ctrl+5)",
"pad.toolbar.ol.title": "Lista ordinà (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "Lista nen ordinà (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "Andenté (TAB)",
"pad.toolbar.unindent.title": "Disandenté (Maj+TAB)",
"pad.toolbar.undo.title": "Anulé (Ctrl+Z)",
"pad.toolbar.redo.title": "Ristabilì (Ctrl+Y)",
"pad.toolbar.clearAuthorship.title": "Dëscancelé ij color ch'a identìfico j'autor (Ctrl+Shift+C)",
"pad.toolbar.import_export.title": "Amporté/Esporté da/vers dij formà d'archivi diferent",
"pad.toolbar.timeslider.title": "Stòria dinàmica",
"pad.toolbar.savedRevision.title": "Argistré la revision",
"pad.toolbar.settings.title": "Paràmeter",
"pad.toolbar.embed.title": "Partagé e antëgré ës feuj",
"pad.toolbar.showusers.title": "Smon-e j'utent ansima a 's feuj",
"pad.colorpicker.save": "Argistré",
"pad.colorpicker.cancel": "Anulé",
"pad.loading": "Antramentr ch'as caria…",
"pad.noCookie": "Ël bëscotin a l'é nen ëstàit trovà. Për piasì, ch'a autorisa ij bëscotin su sò navigador!",
"pad.passwordRequired": "A l'ha da manca ëd na ciav për acede a cost feuj-sì",
"pad.permissionDenied": "A l'ha nen ël përmess d'acede a 's feuj-sì",
"pad.wrongPassword": "Soa ciav a l'era nen giusta",
"pad.settings.padSettings": "Paràmeter dël feuj",
"pad.settings.myView": "Mia vista",
"pad.settings.stickychat": "Ciaciarade sempe an slë scren",
"pad.settings.chatandusers": "Smon-e le ciaciarade e j'utent",
"pad.settings.colorcheck": "Color d'identificassion",
"pad.settings.linenocheck": "Nùmer ëd linia",
"pad.settings.rtlcheck": "Ël contnù, dev-lo esse lesù da drita a snistra?",
"pad.settings.fontType": "Sòrt ëd caràter:",
"pad.settings.globalView": "Vista d'ansem",
"pad.settings.language": "Lenga:",
"pad.importExport.import_export": "Amporté/Esporté",
"pad.importExport.import": "Carié n'archivi o document ëd test",
"pad.importExport.importSuccessful": "Bele fàit!",
"pad.importExport.export": "Esporté ël feuj atual coma:",
"pad.importExport.exportetherpad": "Etherpad"
}

View File

@ -7,7 +7,8 @@
"Okras", "Okras",
"Volkov", "Volkov",
"Nzeemin", "Nzeemin",
"Facenapalm" "Facenapalm",
"Patrick Star"
] ]
}, },
"index.newPad": "Создать", "index.newPad": "Создать",
@ -58,7 +59,7 @@
"pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF", "pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (документ OpenOffice)", "pad.importExport.exportopen": "ODF (документ OpenOffice)",
"pad.importExport.abiword.innerHTML": "Вы можете импортировать только из обычного текста или HTML. Для более продвинутых функций импорта, пожалуйста, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">установите AbiWord</a>.", "pad.importExport.abiword.innerHTML": "Вы можете импортировать только из обычного текста или HTML. Для более продвинутых функций импорта, пожалуйста, <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">установите AbiWord</a>.",
"pad.modals.connected": "Подключен.", "pad.modals.connected": "Подключен.",
"pad.modals.reconnecting": "Повторное подключение к вашему документу", "pad.modals.reconnecting": "Повторное подключение к вашему документу",
"pad.modals.forcereconnect": "Принудительное переподключение", "pad.modals.forcereconnect": "Принудительное переподключение",

View File

@ -11,12 +11,12 @@
}, },
"index.newPad": "కొత్త పలక", "index.newPad": "కొత్త పలక",
"index.createOpenPad": "ఒక పేరుతో పలకని సృష్టించండి లేదా అదే పేరుతో ఉన్న పలకని తెరవండి", "index.createOpenPad": "ఒక పేరుతో పలకని సృష్టించండి లేదా అదే పేరుతో ఉన్న పలకని తెరవండి",
"pad.toolbar.bold.title": "మందం", "pad.toolbar.bold.title": "బొద్దు (Ctrl+B)",
"pad.toolbar.italic.title": "వాలు అక్షరాలు", "pad.toolbar.italic.title": "వాలు (Ctrl+I)",
"pad.toolbar.underline.title": "క్రిందగీత", "pad.toolbar.underline.title": "క్రిందగీత",
"pad.toolbar.strikethrough.title": "కొట్టివేత (Ctrl+5)", "pad.toolbar.strikethrough.title": "కొట్టివేత (Ctrl+5)",
"pad.toolbar.ol.title": "నిర్ధేశింపబడిన జాబితా", "pad.toolbar.ol.title": "క్రమ జాబితా (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "అనిర్దేశిత జాబితా, ( క్రమపద్ధతి లేని జాబితా )", "pad.toolbar.ul.title": "బిందు జాబితా (Ctrl+Shift+L)",
"pad.toolbar.undo.title": "చేయవద్దు", "pad.toolbar.undo.title": "చేయవద్దు",
"pad.toolbar.redo.title": "తిరిగిచెయ్యి", "pad.toolbar.redo.title": "తిరిగిచెయ్యి",
"pad.toolbar.clearAuthorship.title": "మూలకర్తపు వర్ణాలను తీసివేయండి", "pad.toolbar.clearAuthorship.title": "మూలకర్తపు వర్ణాలను తీసివేయండి",
@ -53,6 +53,7 @@
"pad.modals.connected": "సంబంధం కుదిరింది.", "pad.modals.connected": "సంబంధం కుదిరింది.",
"pad.modals.reconnecting": "మీ పలకకు మరల సంబంధం కలుపుతుంది...", "pad.modals.reconnecting": "మీ పలకకు మరల సంబంధం కలుపుతుంది...",
"pad.modals.forcereconnect": "బలవంతంగానైనా సంబంధం కుదిరించు", "pad.modals.forcereconnect": "బలవంతంగానైనా సంబంధం కుదిరించు",
"pad.modals.cancel": "రద్దుచేయి",
"pad.modals.userdup.explanation": "ఈ పలక, ఈ కంప్యూటర్లో ఒకటికన్న ఎక్కువ గవాక్షములలో తెరుచుకున్నట్లు అనిపిస్తుంది.", "pad.modals.userdup.explanation": "ఈ పలక, ఈ కంప్యూటర్లో ఒకటికన్న ఎక్కువ గవాక్షములలో తెరుచుకున్నట్లు అనిపిస్తుంది.",
"pad.modals.userdup.advice": "బదులుగా ఈ గవాక్షమును వాడడానికి మరల సంబంధం కలపండి", "pad.modals.userdup.advice": "బదులుగా ఈ గవాక్షమును వాడడానికి మరల సంబంధం కలపండి",
"pad.modals.unauth": "అధికారం లేదు", "pad.modals.unauth": "అధికారం లేదు",

View File

@ -25,7 +25,7 @@ var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.getColorPalette = function(){ exports.getColorPalette = function(){
return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd", "#4c9c82", "#12d1ad", "#2d8e80", "#7485c3", "#a091c7", "#3185ab", "#6818b4", "#e6e76d", "#a42c64", "#f386e5", "#4ecc0c", "#c0c236", "#693224", "#b5de6a", "#9b88fd", "#358f9b", "#496d2f", "#e267fe", "#d23056", "#1a1a64", "#5aa335", "#d722bb", "#86dc6c", "#b5a714", "#955b6a", "#9f2985", "#4b81c8", "#3d6a5b", "#434e16", "#d16084", "#af6a0e", "#8c8bd8"]; return ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5", "#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6", "#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9", "#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8", "#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7", "#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8", "#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"];
}; };
/** /**
@ -42,9 +42,9 @@ exports.doesAuthorExists = function (authorID, callback)
} }
/** /**
* Returns the AuthorID for a token. * Returns the AuthorID for a token.
* @param {String} token The token * @param {String} token The token
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
exports.getAuthor4Token = function (token, callback) exports.getAuthor4Token = function (token, callback)
{ {
@ -57,21 +57,21 @@ exports.getAuthor4Token = function (token, callback)
} }
/** /**
* Returns the AuthorID for a mapper. * Returns the AuthorID for a mapper.
* @param {String} token The mapper * @param {String} token The mapper
* @param {String} name The name of the author (optional) * @param {String} name The name of the author (optional)
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
{ {
mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//set the name of this author //set the name of this author
if(name) if(name)
exports.setAuthorName(author.authorID, name); exports.setAuthorName(author.authorID, name);
//return the authorID //return the authorID
callback(null, author); callback(null, author);
}); });
@ -80,27 +80,27 @@ exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
/** /**
* Returns the AuthorID for a mapper. We can map using a mapperkey, * Returns the AuthorID for a mapper. We can map using a mapperkey,
* so far this is token2author and mapper2author * so far this is token2author and mapper2author
* @param {String} mapperkey The database key name for this mapper * @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper * @param {String} mapper The mapper
* @param {Function} callback callback (err, author) * @param {Function} callback callback (err, author)
*/ */
function mapAuthorWithDBKey (mapperkey, mapper, callback) function mapAuthorWithDBKey (mapperkey, mapper, callback)
{ {
//try to map to an author //try to map to an author
db.get(mapperkey + ":" + mapper, function (err, author) db.get(mapperkey + ":" + mapper, function (err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//there is no author with this mapper, so create one //there is no author with this mapper, so create one
if(author == null) if(author == null)
{ {
exports.createAuthor(null, function(err, author) exports.createAuthor(null, function(err, author)
{ {
if(ERR(err, callback)) return; if(ERR(err, callback)) return;
//create the token2author relation //create the token2author relation
db.set(mapperkey + ":" + mapper, author.authorID); db.set(mapperkey + ":" + mapper, author.authorID);
//return the author //return the author
callback(null, author); callback(null, author);
}); });
@ -110,7 +110,7 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback)
{ {
//update the timestamp of this author //update the timestamp of this author
db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime());
//return the author //return the author
callback(null, {authorID: author}); callback(null, {authorID: author});
} }
@ -118,20 +118,20 @@ function mapAuthorWithDBKey (mapperkey, mapper, callback)
} }
/** /**
* Internal function that creates the database entry for an author * Internal function that creates the database entry for an author
* @param {String} name The name of the author * @param {String} name The name of the author
*/ */
exports.createAuthor = function(name, callback) exports.createAuthor = function(name, callback)
{ {
//create the new author name //create the new author name
var author = "a." + randomString(16); var author = "a." + randomString(16);
//create the globalAuthors db entry //create the globalAuthors db entry
var authorObj = {"colorId" : Math.floor(Math.random()*(exports.getColorPalette().length)), "name": name, "timestamp": new Date().getTime()}; var authorObj = {"colorId" : Math.floor(Math.random()*(exports.getColorPalette().length)), "name": name, "timestamp": new Date().getTime()};
//set the global author db entry //set the global author db entry
db.set("globalAuthor:" + author, authorObj); db.set("globalAuthor:" + author, authorObj);
callback(null, {authorID: author}); callback(null, {authorID: author});
} }
@ -212,7 +212,7 @@ exports.listPadsOfAuthor = function (authorID, callback)
} }
//everything is fine, return the pad IDs //everything is fine, return the pad IDs
else else
{ {
var pads = []; var pads = [];
if(author.padIDs != null) if(author.padIDs != null)
{ {
@ -238,16 +238,16 @@ exports.addPad = function (authorID, padID)
{ {
if(ERR(err)) return; if(ERR(err)) return;
if(author == null) return; if(author == null) return;
//the entry doesn't exist so far, let's create it //the entry doesn't exist so far, let's create it
if(author.padIDs == null) if(author.padIDs == null)
{ {
author.padIDs = {}; author.padIDs = {};
} }
//add the entry for this pad //add the entry for this pad
author.padIDs[padID] = 1;// anything, because value is not used author.padIDs[padID] = 1;// anything, because value is not used
//save the new element back //save the new element back
db.set("globalAuthor:" + authorID, author); db.set("globalAuthor:" + authorID, author);
}); });
@ -264,11 +264,11 @@ exports.removePad = function (authorID, padID)
{ {
if(ERR(err)) return; if(ERR(err)) return;
if(author == null) return; if(author == null) return;
if(author.padIDs != null) if(author.padIDs != null)
{ {
//remove pad from author //remove pad from author
delete author.padIDs[padID]; delete author.padIDs[padID];
db.set("globalAuthor:" + authorID, author); db.set("globalAuthor:" + authorID, author);
} }
}); });

View File

@ -464,9 +464,10 @@ Pad.prototype.copy = function copy(destinationID, force, callback) {
} }
else force = true; else force = true;
//kick everyone from this pad // Kick everyone from this pad.
// TODO: this presents a message on the client saying that the pad was 'deleted'. Fix this? // This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
padMessageHandler.kickSessionsFromPad(sourceID); // Do we really need to kick everyone out?
// padMessageHandler.kickSessionsFromPad(sourceID);
// flush the source pad: // flush the source pad:
_this.saveToDatabase(); _this.saveToDatabase();

View File

@ -22,6 +22,7 @@
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var async = require("async"); var async = require("async");
var authorManager = require("./AuthorManager"); var authorManager = require("./AuthorManager");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var padManager = require("./PadManager"); var padManager = require("./PadManager");
var sessionManager = require("./SessionManager"); var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings"); var settings = require("../utils/Settings");
@ -45,6 +46,14 @@ exports.checkAccess = function (padID, sessionCookie, token, password, callback)
return; return;
} }
// allow plugins to deny access
var deniedByHook = hooks.callAll("onAccessCheck", {'padID': padID, 'password': password, 'token': token, 'sessionCookie': sessionCookie}).indexOf(false) > -1;
if(deniedByHook)
{
callback(null, {accessStatus: "deny"});
return;
}
// a valid session is required (api-only mode) // a valid session is required (api-only mode)
if(settings.requireSession) if(settings.requireSession)
{ {

View File

@ -24,17 +24,19 @@ var fs = require("fs");
var api = require("../db/API"); var api = require("../db/API");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var randomString = require("../utils/randomstring"); var randomString = require("../utils/randomstring");
var argv = require('../utils/Cli').argv;
//ensure we have an apikey //ensure we have an apikey
var apikey = null; var apikey = null;
var apikeyFilename = argv.apikey || "./APIKEY.txt";
try try
{ {
apikey = fs.readFileSync("./APIKEY.txt","utf8"); apikey = fs.readFileSync(apikeyFilename,"utf8");
} }
catch(e) catch(e)
{ {
apikey = randomString(32); apikey = randomString(32);
fs.writeFileSync("./APIKEY.txt",apikey,"utf8"); fs.writeFileSync(apikeyFilename,apikey,"utf8");
} }
//a list of all functions //a list of all functions

View File

@ -90,7 +90,7 @@ exports.doImport = function(req, res, padId)
//this allows us to accept source code files like .c or .java //this allows us to accept source code files like .c or .java
function(callback) { function(callback) {
var fileEnding = path.extname(srcFile).toLowerCase() var fileEnding = path.extname(srcFile).toLowerCase()
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad"] , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"]
, fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1); , fileEndingKnown = (knownFileEndings.indexOf(fileEnding) > -1);
//if the file ending is known, continue as normal //if the file ending is known, continue as normal

View File

@ -2,7 +2,7 @@ var eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin', function(req, res) { args.app.get('/admin', function(req, res) {
if('/' != req.path[req.path.length-1]) return res.redirect('/admin/'); if('/' != req.path[req.path.length-1]) return res.redirect('./admin/');
res.send( eejs.require("ep_etherpad-lite/templates/admin/index.html", {}) ); res.send( eejs.require("ep_etherpad-lite/templates/admin/index.html", {}) );
}); });
} }

View File

@ -19,7 +19,7 @@ var apiCaller = function(req, res, fields) {
apiLogger.info("RESPONSE, " + req.params.func + ", " + response); apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
//is this a jsonp call, if yes, add the function call //is this a jsonp call, if yes, add the function call
if(req.query.jsonp && isVarName(response)) if(req.query.jsonp && isVarName(req.query.jsonp))
response = req.query.jsonp + "(" + response + ")"; response = req.query.jsonp + "(" + response + ")";
res._____send(response); res._____send(response);

View File

@ -36,13 +36,16 @@ exports.basicAuth = function (req, res, next) {
var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":") var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
var username = userpass.shift(); var username = userpass.shift();
var password = userpass.join(':'); var password = userpass.join(':');
var fallback = function(success) {
if (settings.users[username] != undefined && settings.users[username].password == password) { if (success) return cb(true);
settings.users[username].username = username; if (settings.users[username] != undefined && settings.users[username].password == password) {
req.session.user = settings.users[username]; settings.users[username].username = username;
return cb(true); req.session.user = settings.users[username];
} return cb(true);
return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(cb)); }
return cb(false);
};
return hooks.aCallFirst("authenticate", {req: req, res:res, next:next, username: username, password: password}, hookResultMangle(fallback));
} }
hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb)); hooks.aCallFirst("authenticate", {req: req, res:res, next:next}, hookResultMangle(cb));
} }

View File

@ -39,5 +39,15 @@ for ( var i = 0; i < argv.length; i++ ) {
exports.argv.credentials = arg; exports.argv.credentials = arg;
} }
// Override location of settings.json file
if ( prevArg == '--sessionkey' || prevArg == '-k' ) {
exports.argv.sessionkey = arg;
}
// Override location of settings.json file
if ( prevArg == '--apikey' || prevArg == '-k' ) {
exports.argv.apikey = arg;
}
prevArg = arg; prevArg = arg;
} }

View File

@ -35,7 +35,21 @@ var queue = async.queue(doConvertTask, 1);
* @param {Function} callback Standard callback function * @param {Function} callback Standard callback function
*/ */
exports.convertFile = function(srcFile, destFile, type, callback) { exports.convertFile = function(srcFile, destFile, type, callback) {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback}); // soffice can't convert from html to doc directly (verified with LO 5 and 6)
// we need to convert to odt first, then to doc
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') {
queue.push({
"srcFile": srcFile,
"destFile": destFile.replace(/\.doc$/, '.odt'),
"type": 'odt',
"callback": function () {
queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback});
}
});
} else {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
}
}; };
function doConvertTask(task, callback) { function doConvertTask(task, callback) {

View File

@ -476,11 +476,12 @@ exports.reloadSettings = function reloadSettings() {
} }
if (!exports.sessionKey) { if (!exports.sessionKey) {
var sessionkeyFilename = argv.sessionkey || "./SESSIONKEY.txt";
try { try {
exports.sessionKey = fs.readFileSync("./SESSIONKEY.txt","utf8"); exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8");
} catch(e) { } catch(e) {
exports.sessionKey = randomString(32); exports.sessionKey = randomString(32);
fs.writeFileSync("./SESSIONKEY.txt",exports.sessionKey,"utf8"); fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8");
} }
} else { } else {
console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file."); console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file.");

View File

@ -17,7 +17,7 @@
"etherpad-require-kernel" : "1.0.9", "etherpad-require-kernel" : "1.0.9",
"resolve" : "1.1.7", "resolve" : "1.1.7",
"socket.io" : "1.7.3", "socket.io" : "1.7.3",
"ueberdb2" : "0.3.0", "ueberdb2" : "0.3.6",
"express" : "4.13.4", "express" : "4.13.4",
"express-session" : "1.13.0", "express-session" : "1.13.0",
"cookie-parser" : "1.3.4", "cookie-parser" : "1.3.4",
@ -28,7 +28,7 @@
"log4js" : "0.6.35", "log4js" : "0.6.35",
"cheerio" : "0.20.0", "cheerio" : "0.20.0",
"async-stacktrace" : "0.0.2", "async-stacktrace" : "0.0.2",
"npm" : "4.0.2", "npm" : ">=4.0.2",
"ejs" : "2.5.7", "ejs" : "2.5.7",
"graceful-fs" : "4.1.3", "graceful-fs" : "4.1.3",
"slide" : "1.1.6", "slide" : "1.1.6",

View File

@ -31,13 +31,17 @@ body {
body.grayedout { background-color: #eee !important } body.grayedout { background-color: #eee !important }
#innerdocbody { #innerdocbody {
font-size: 12px; /* overridden by body.style */ font-size: 16px; /* overridden by body.style */
font-family:Arial, sans-serif; /* overridden by body.style */ font-family:Arial, sans-serif; /* overridden by body.style */
line-height: 16px; /* overridden by body.style */ line-height: 16px; /* overridden by body.style */
background-color: white; background-color: white;
color: black; color: black;
} }
.innerdocbody>div{
padding: 1px;
}
body.doesWrap { body.doesWrap {
/* white-space: pre-wrap; */ /* white-space: pre-wrap; */
@ -58,9 +62,11 @@ body.doesWrap {
white-space: normal; white-space: normal;
} }
body.doesWrap:not(.noprewrap) > div{ @-moz-document url-prefix() {
/* Related to #1766 */ body.doesWrap:not(.noprewrap) > div{
white-space: pre-wrap; /* Related to #1766 */
white-space: pre-wrap;
}
} }
#innerdocbody { #innerdocbody {

View File

@ -3,8 +3,9 @@ html,
body, body,
p { p {
margin: 0; margin: 0;
padding: 0; padding: 0px;
} }
.clear { .clear {
clear: both clear: both
} }

View File

@ -5404,8 +5404,8 @@ function Ace2Inner(){
// height is taken to be the top offset of the next line. If we // height is taken to be the top offset of the next line. If we
// didn't do this special case, we would miss out on any top margin // 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 // included on the first line. The default stylesheet doesn't add
// extra margins, but plugins might. // extra margins/padding, but plugins might.
h = b.nextSibling.offsetTop; h = b.nextSibling.offsetTop - window.getComputedStyle(doc.body).getPropertyValue("padding-top");
} else { } else {
h = b.nextSibling.offsetTop - b.offsetTop; h = b.nextSibling.offsetTop - b.offsetTop;
} }