Merge pull request #1 from ether/develop

Update to most recent etherpad-lite version
This commit is contained in:
fcassin 2015-11-10 15:50:40 +01:00
commit f2836125dc
32 changed files with 574 additions and 171 deletions

View file

@ -4,7 +4,7 @@
# About # About
Etherpad is a really-real time collaborative editor maintained by the Etherpad Community. Etherpad is a really-real time collaborative editor maintained by the Etherpad Community.
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. 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.
Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) Etherpad is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API.

View file

@ -46,13 +46,14 @@ fi
#check node version #check node version
NODE_VERSION=$(node --version) NODE_VERSION=$(node --version)
NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2)
NODE_V_MAIN=$(echo $NODE_VERSION | cut -d "." -f 1)
#iojs version checking added #iojs version checking added
if hash iojs 2>/dev/null; then if hash iojs 2>/dev/null; then
IOJS_VERSION=$(iojs --version) IOJS_VERSION=$(iojs --version)
fi fi
if [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ]; then if [ ! $NODE_V_MINOR = "v0.10" ] && [ ! $NODE_V_MINOR = "v0.11" ] && [ ! $NODE_V_MINOR = "v0.12" ] && [ ! $NODE_V_MAIN = "v4" ] && [ ! $NODE_V_MAIN = "v5" ]; then
if [ ! $IOJS_VERSION ]; then if [ ! $IOJS_VERSION ]; then
echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need v0.10.x, v0.11.x or v0.12.x" >&2 echo "You're running a wrong version of node, or io.js is not installed. You're using $NODE_VERSION, we need node v0.10.x or higher" >&2
exit 1 exit 1
fi fi
fi fi

View file

@ -6,11 +6,6 @@ cd /D "%~dp0\.."
:: Is node installed? :: Is node installed?
cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && exit /B 1 ) cmd /C node -e "" || ( echo "Please install node.js ( http://nodejs.org )" && exit /B 1 )
echo _
echo Checking node version...
set check_version="if(['10','11','12'].indexOf(process.version.split('.')[1]) === -1 && process.version.split('.')[0] !== '1') { console.log('You are running a wrong version of Node. Etherpad requires v0.10+'); process.exit(1) }"
cmd /C node -e %check_version% || exit /B 1
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 cmd /C npm install src/ --loglevel warn || exit /B 1

View file

@ -79,6 +79,9 @@ async.series([
newPad.pool.numToAttrib = oldPad.pool.numToAttrib; newPad.pool.numToAttrib = oldPad.pool.numToAttrib;
for(var curRevNum = 0; curRevNum <= newRevHead; curRevNum++) { for(var curRevNum = 0; curRevNum <= newRevHead; curRevNum++) {
db.db.get("pad:" + padId + ":revs:" + curRevNum, function(err, rev) { db.db.get("pad:" + padId + ":revs:" + curRevNum, function(err, rev) {
if (rev.meta) {
throw "The specified revision number could not be found.";
}
var newRevNum = ++newPad.head; var newRevNum = ++newPad.head;
var newRevId = "pad:" + newPad.id + ":revs:" + newRevNum; var newRevId = "pad:" + newPad.id + ":revs:" + newRevNum;
db.db.set(newRevId, rev); db.db.set(newRevId, rev);

View file

@ -339,3 +339,14 @@ Things in context:
This hook is provided to allow author highlight style to be modified. This hook is provided to allow author highlight style to be modified.
Registered hooks should return 1 if the plugin handles highlighting. If no plugin returns 1, the core will use the default background-based highlighting. Registered hooks should return 1 if the plugin handles highlighting. If no plugin returns 1, the core will use the default background-based highlighting.
## aceSelectionChanged
Called from: src/static/js/ace2_inner.js
Things in context:
1. rep - information about where the user's cursor is
2. documentAttributeManager - information about attributes in the document
This hook allows a plugin to react to a cursor or selection change,
perhaps to update a UI element based on the style at the cursor location.

View file

@ -81,7 +81,7 @@ Available blocks in `pad.html` are:
* `modals` - Contains all connectivity messages * `modals` - Contains all connectivity messages
* `embedPopup` - the embed dropdown * `embedPopup` - the embed dropdown
* `scripts` - Add your script tags here, if you really have to (consider use client-side hooks instead) * `scripts` - Add your script tags here, if you really have to (consider use client-side hooks instead)
`timeslider.html` blocks: `timeslider.html` blocks:
* `timesliderStyles` * `timesliderStyles`
@ -90,9 +90,9 @@ Available blocks in `pad.html` are:
* `timesliderTop` * `timesliderTop`
* `timesliderEditbarRight` * `timesliderEditbarRight`
* `modals` * `modals`
`index.html` blocks: `index.html` blocks:
* `indexWrapper` - contains the form for creating new pads * `indexWrapper` - contains the form for creating new pads
## padInitToolbar ## padInitToolbar
@ -334,7 +334,7 @@ exports.aceAttribClasses = function(hook_name, attr, cb){
``` ```
## exportFileName ## exportFileName
Called from src/node/handler/ExportHandler.js Called from src/node/handler/ExportHandler.js
Things in context: Things in context:
@ -357,7 +357,7 @@ Things in context:
1. Pad object 1. Pad object
This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. If tags are stored as `['color', 'red']` on the attribute pool, use `exportHtmlAdditionalTagsWithData` instead. An Array should be returned.
Example: Example:
``` ```
@ -368,6 +368,24 @@ exports.exportHtmlAdditionalTags = function(hook, pad, cb){
}; };
``` ```
## exportHtmlAdditionalTagsWithData
Called from src/node/utils/ExportHtml.js
Things in context:
1. Pad object
Identical to `exportHtmlAdditionalTags`, but for tags that are stored with an specific value (not simply `true`) on the attribute pool. For example `['color', 'red']`, instead of `['bold', true]`. This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array of arrays should be returned. The exported HTML will contain tags like `<span data-color="red">` for the content where attributes are `['color', 'red']`.
Example:
```
// Add the props to be supported in export
exports.exportHtmlAdditionalTagsWithData = function(hook, pad, cb){
var padId = pad.id;
cb([["color", "red"], ["color", "blue"]]);
};
```
## userLeave ## userLeave
Called from src/node/handler/PadMessageHandler.js Called from src/node/handler/PadMessageHandler.js
@ -384,3 +402,20 @@ exports.userLeave = function(hook, session, callback) {
console.log('%s left pad %s', session.author, session.padId); console.log('%s left pad %s', session.author, session.padId);
}; };
``` ```
### clientReady
Called from src/node/handler/PadMessageHandler.js
This in context:
1. message
This hook gets called when handling a CLIENT_READY which is the first message from the client to the server.
Example:
```
exports.clientReady = function(hook, message) {
console.log('Client has entered the pad' + message.padId);
};
```

View file

@ -86,10 +86,14 @@
may cause problems during deployment. Set to 0 to disable caching */ may cause problems during deployment. Set to 0 to disable caching */
"maxAge" : 21600, // 60 * 60 * 6 = 6 hours "maxAge" : 21600, // 60 * 60 * 6 = 6 hours
/* This is the path to the Abiword executable. Setting it to null, disables abiword. /* 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*/ Abiword is needed to advanced import/export features of pads*/
"abiword" : null, "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 */
"soffice" : null,
/* This is the path to the Tidy executable. Setting it to null, disables Tidy. /* This is the path to the Tidy executable. Setting it to null, disables Tidy.
Tidy is used to improve the quality of exported pads*/ Tidy is used to improve the quality of exported pads*/
"tidyHtml" : null, "tidyHtml" : null,
@ -131,6 +135,11 @@
// 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, "loadTest": false,
// Disable indentation on new line when previous line ends with some special chars (':', '[', '(', '{')
/*
"indentationOnNewLine": false,
*/
/* The toolbar buttons configuration. /* The toolbar buttons configuration.
"toolbar": { "toolbar": {
"left": [ "left": [

View file

@ -6,7 +6,8 @@
"Alami", "Alami",
"Meno25", "Meno25",
"Test Create account", "Test Create account",
"محمد أحمد عبد الفتاح" "محمد أحمد عبد الفتاح",
"Haytham morsy"
] ]
}, },
"index.newPad": "باد جديد", "index.newPad": "باد جديد",
@ -97,6 +98,9 @@
"timeslider.exportCurrent": "تصدير النسخة الحالية ك:", "timeslider.exportCurrent": "تصدير النسخة الحالية ك:",
"timeslider.version": "إصدار {{version}}", "timeslider.version": "إصدار {{version}}",
"timeslider.saved": "محفوظ {{month}} {{day}}, {{year}}", "timeslider.saved": "محفوظ {{month}} {{day}}, {{year}}",
"timeslider.playPause": "تشغيل / إيقاف مؤقت محتويات الباد",
"timeslider.backRevision": "عد إلى مراجعة في هذه الباد",
"timeslider.forwardRevision": "انطلق إلى مراجعة في هذه الباد",
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "يناير", "timeslider.month.january": "يناير",
"timeslider.month.february": "فبراير", "timeslider.month.february": "فبراير",

View file

@ -132,7 +132,7 @@
"pad.impexp.confirmimport": "Al importar un archivo se borrará el contenido actual del pad. ¿Estás seguro de que quieres continuar?", "pad.impexp.confirmimport": "Al importar un archivo se borrará el contenido actual del pad. ¿Estás seguro de que quieres continuar?",
"pad.impexp.convertFailed": "No pudimos importar este archivo. Inténtalo con un formato diferente o copia y pega manualmente.", "pad.impexp.convertFailed": "No pudimos importar este archivo. Inténtalo con un formato diferente o copia y pega manualmente.",
"pad.impexp.padHasData": "No hemos podido importar este archivo porque este pad ya ha tenido cambios. Importa a un nuevo pad.", "pad.impexp.padHasData": "No hemos podido importar este archivo porque este pad ya ha tenido cambios. Importa a un nuevo pad.",
"pad.impexp.uploadFailed": "El envío falló. Intentalo de nuevo.", "pad.impexp.uploadFailed": "El envío falló. Inténtalo de nuevo.",
"pad.impexp.importfailed": "Fallo al importar", "pad.impexp.importfailed": "Fallo al importar",
"pad.impexp.copypaste": "Intenta copiar y pegar", "pad.impexp.copypaste": "Intenta copiar y pegar",
"pad.impexp.exportdisabled": "La exportación al formato {{type}} está desactivada. Contacta a tu administrador de sistemas." "pad.impexp.exportdisabled": "La exportación al formato {{type}} está desactivada. Contacta a tu administrador de sistemas."

View file

@ -2,7 +2,8 @@
"@metadata": { "@metadata": {
"authors": [ "authors": [
"Denö", "Denö",
"Mashoi7" "Mashoi7",
"Ilja.mos"
] ]
}, },
"pad.toolbar.underline.title": "Alleviivua (Ctrl+U)", "pad.toolbar.underline.title": "Alleviivua (Ctrl+U)",
@ -36,7 +37,7 @@
"timeslider.month.january": "pakkaskuudu", "timeslider.month.january": "pakkaskuudu",
"timeslider.month.february": "tuhukuudu", "timeslider.month.february": "tuhukuudu",
"timeslider.month.march": "kevätkuudu", "timeslider.month.march": "kevätkuudu",
"timeslider.month.april": "kevätkuudu", "timeslider.month.april": "sulakuudu",
"timeslider.month.may": "oraskuudu", "timeslider.month.may": "oraskuudu",
"timeslider.month.june": "kezäkuudu", "timeslider.month.june": "kezäkuudu",
"timeslider.month.july": "heinykuudu", "timeslider.month.july": "heinykuudu",

View file

@ -2,7 +2,8 @@
"@metadata": { "@metadata": {
"authors": [ "authors": [
"Aalam", "Aalam",
"Babanwalia" "Babanwalia",
"ਪ੍ਰਚਾਰਕ"
] ]
}, },
"index.newPad": "ਨਵਾਂ ਪੈਡ", "index.newPad": "ਨਵਾਂ ਪੈਡ",
@ -10,29 +11,31 @@
"pad.toolbar.bold.title": "ਗੂੜ੍ਹਾ (Ctrl-B)", "pad.toolbar.bold.title": "ਗੂੜ੍ਹਾ (Ctrl-B)",
"pad.toolbar.italic.title": "ਤਿਰਛਾ (Ctrl-I)", "pad.toolbar.italic.title": "ਤਿਰਛਾ (Ctrl-I)",
"pad.toolbar.underline.title": "ਹੇਠਾਂ-ਰੇਖਾ (Ctrl-U)", "pad.toolbar.underline.title": "ਹੇਠਾਂ-ਰੇਖਾ (Ctrl-U)",
"pad.toolbar.strikethrough.title": "ਵਿੰਨ੍ਹੋ ਵਿਨੋ", "pad.toolbar.strikethrough.title": "ਵਿੰਨ੍ਹੋ (Ctrl+5)",
"pad.toolbar.ol.title": "ਲੜੀਵਾਰ ਲਿਸਟ", "pad.toolbar.ol.title": "ਲੜੀਵਾਰ ਸੂਚੀ",
"pad.toolbar.ul.title": "ਬਿਨ-ਲੜੀਬੱਧ ਸੂਚੀ", "pad.toolbar.ul.title": "ਬਿਨ-ਲੜੀਬੱਧ ਸੂਚੀ",
"pad.toolbar.indent.title": "ਹਾਸ਼ੀਏ ਤੋਂ ਪਰ੍ਹੇ (ਟੈਬ)", "pad.toolbar.indent.title": "ਹਾਸ਼ੀਏ ਤੋਂ ਪਰ੍ਹੇ (ਟੈਬ)",
"pad.toolbar.unindent.title": "ਹਾਸ਼ੀਏ ਵੱਲ (ਸ਼ਿਫ਼ਟ+ਟੈਬ)", "pad.toolbar.unindent.title": "ਹਾਸ਼ੀਏ ਵੱਲ (ਸ਼ਿਫ਼ਟ+ਟੈਬ)",
"pad.toolbar.undo.title": "ਵਾਪਸ (Ctrl-Z)", "pad.toolbar.undo.title": "ਵਾਪਸ (Ctrl-Z)",
"pad.toolbar.redo.title": "ਪਰਤਾਓ (Ctrl-Y)", "pad.toolbar.redo.title": "ਪਰਤਾਓ (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰੋ", "pad.toolbar.clearAuthorship.title": "ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰੋ (Ctrl+Shift+C)",
"pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ", "pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ",
"pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ", "pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ",
"pad.toolbar.savedRevision.title": "ਰੀਵਿਜ਼ਨ ਸੰਭਾਲੋ", "pad.toolbar.savedRevision.title": "ਰੀਵਿਜ਼ਨ ਸੰਭਾਲੋ",
"pad.toolbar.settings.title": "ਸੈਟਿੰਗ", "pad.toolbar.settings.title": "ਸੈਟਿੰਗ",
"pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ", "pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ",
"pad.toolbar.showusers.title": "ਇਹ ਪੈਡ ਉੱਤੇ ਯੂਜ਼ਰ ਵੇਖਾਓ", "pad.toolbar.showusers.title": "ਇਹ ਪੈਡ ਉੱਤੇ ਯੂਜ਼ਰ ਵੇਖਾਓ",
"pad.colorpicker.save": "ਸਾਂਭੋ", "pad.colorpicker.save": "ਸੰਭਾਲੋ",
"pad.colorpicker.cancel": "ਰੱਦ ਕਰੋ", "pad.colorpicker.cancel": "ਰੱਦ ਕਰੋ",
"pad.loading": "…ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ", "pad.loading": "…ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ",
"pad.noCookie": "ਕੂਕੀਜ਼ ਨਹੀਂ ਲੱਭੀਅਾਂ। ਕਿਰਪਾ ਕਰਕੇ ਬ੍ਰਾੳੂਜ਼ਰ ਵਿੱਚ ਕੂਕੀਜ਼ ਲਾਗੂ ਕਰੋ।",
"pad.passwordRequired": "ਇਹ ਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ", "pad.passwordRequired": "ਇਹ ਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ",
"pad.permissionDenied": "ਇਹ ਪੈਡ ਵਰਤਨ ਲਈ ਤੁਹਾਨੂੰ ਅਧਿਕਾਰ ਨਹੀਂ ਹਨ", "pad.permissionDenied": "ਇਹ ਪੈਡ ਵਰਤਨ ਲਈ ਤੁਹਾਨੂੰ ਅਧਿਕਾਰ ਨਹੀਂ ਹਨ",
"pad.wrongPassword": "ਤੁਹਾਡਾ ਪਾਸਵਰਡ ਗਲਤੀ ਸੀ", "pad.wrongPassword": "ਤੁਹਾਡਾ ਪਾਸਵਰਡ ਗਲਤੀ ਸੀ",
"pad.settings.padSettings": "ਪੈਡ ਸੈਟਿੰਗ", "pad.settings.padSettings": "ਪੈਡ ਸੈਟਿੰਗ",
"pad.settings.myView": "ਮੇਰੀ ਝਲਕ", "pad.settings.myView": "ਮੇਰੀ ਝਲਕ",
"pad.settings.stickychat": "ਹਮੇਸ਼ਾ ਸਕਰੀਨ ਉੱਤੇ ਗੱਲ ਕਰੋ", "pad.settings.stickychat": "ਹਮੇਸ਼ਾ ਸਕਰੀਨ ਉੱਤੇ ਗੱਲ ਕਰੋ",
"pad.settings.chatandusers": "ਗੱਲ-ਬਾਤ ਅਤੇ ਵਰਤੋਂਕਾਰ ਦਿਖਾਵੋ",
"pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ", "pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ",
"pad.settings.linenocheck": "ਲਾਈਨ ਨੰਬਰ", "pad.settings.linenocheck": "ਲਾਈਨ ਨੰਬਰ",
"pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?", "pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?",
@ -45,10 +48,11 @@
"pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ", "pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ",
"pad.importExport.importSuccessful": "ਸਫ਼ਲ!", "pad.importExport.importSuccessful": "ਸਫ਼ਲ!",
"pad.importExport.export": "ਮੌਜੂਦਾ ਪੈਡ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ:", "pad.importExport.export": "ਮੌਜੂਦਾ ਪੈਡ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ:",
"pad.importExport.exportetherpad": "ੲੈਥਰਪੈਡ",
"pad.importExport.exporthtml": "HTML", "pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "ਸਧਾਰਨ ਟੈਕਸਟ", "pad.importExport.exportplain": "ਸਧਾਰਨ ਟੈਕਸਟ",
"pad.importExport.exportword": "ਮਾਈਕਰੋਸਾਫਟ ਵਰਡ", "pad.importExport.exportword": "ਮਾਈਕਰੋਸਾਫਟ ਵਰਡ",
"pad.importExport.exportpdf": "ਪੀਡੀਐਫ", "pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (ਓਪਨ ਡੌਕੂਮੈਂਟ ਫਾਰਮੈਟ)", "pad.importExport.exportopen": "ODF (ਓਪਨ ਡੌਕੂਮੈਂਟ ਫਾਰਮੈਟ)",
"pad.importExport.abiword.innerHTML": "ਤੁਸੀਂ ਸਿਰਫ਼ ਸਾਦੀਆਂ ਲਿਖਤੀ ਜਾਂ ਐੱਚ.ਟੀ.ਐੱਮ.ਐੱਲ. ਰੂਪ-ਰੇਖਾਵਾਂ ਤੋਂ ਦਰਾਮਦ ਕਰ ਸਕਦੇ ਹੋ। ਹੋਰ ਉੱਨਤ ਦਰਾਮਦੀ ਗੁਣਾਂ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">ਐਬੀਵਰਡ ਥਾਪੋ</a>।", "pad.importExport.abiword.innerHTML": "ਤੁਸੀਂ ਸਿਰਫ਼ ਸਾਦੀਆਂ ਲਿਖਤੀ ਜਾਂ ਐੱਚ.ਟੀ.ਐੱਮ.ਐੱਲ. ਰੂਪ-ਰੇਖਾਵਾਂ ਤੋਂ ਦਰਾਮਦ ਕਰ ਸਕਦੇ ਹੋ। ਹੋਰ ਉੱਨਤ ਦਰਾਮਦੀ ਗੁਣਾਂ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">ਐਬੀਵਰਡ ਥਾਪੋ</a>।",
"pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।", "pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।",
@ -88,8 +92,11 @@
"timeslider.toolbar.authorsList": "ਕੋਈ ਲੇਖਕ ਨਹੀਂ", "timeslider.toolbar.authorsList": "ਕੋਈ ਲੇਖਕ ਨਹੀਂ",
"timeslider.toolbar.exportlink.title": "ਐਕਸਪੋਰਟ", "timeslider.toolbar.exportlink.title": "ਐਕਸਪੋਰਟ",
"timeslider.exportCurrent": "ਮੌਜੂਦਾ ਵਰਜਨ ਇੰਝ ਐਕਸਪੋਰਟ ਕਰੋ:", "timeslider.exportCurrent": "ਮੌਜੂਦਾ ਵਰਜਨ ਇੰਝ ਐਕਸਪੋਰਟ ਕਰੋ:",
"timeslider.version": "ਵਰਜਨ {{version}}", "timeslider.version": "ਵਰਜਨ {{version}}",
"timeslider.saved": "{{day}} {{month}} {{year}} ਨੂੰ ਸੰਭਾਲਿਆ", "timeslider.saved": "{{day}} {{month}} {{year}} ਨੂੰ ਸੰਭਾਲਿਆ",
"timeslider.playPause": "ਪੈਡ ਸਮੱਗਰੀ ਚਲਾਓ / ਵਿਰਾਮ ਕਰੋ",
"timeslider.backRevision": "ਇਸ ਪੈਡ ਵਿੱਚ ਪਿਛਲੇ ਰੀਵਿਜ਼ਨ ਤੇ ਜਾਓ",
"timeslider.forwardRevision": "ਇਸ ਪੈਡ ਵਿੱਚ ਅਗਲੇ ਰੀਵਿਜ਼ਨ ਤੇ ਜਾਓ",
"timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "ਜਨਵਰੀ", "timeslider.month.january": "ਜਨਵਰੀ",
"timeslider.month.february": "ਫ਼ਰਵਰੀ", "timeslider.month.february": "ਫ਼ਰਵਰੀ",
@ -118,5 +125,5 @@
"pad.impexp.uploadFailed": "ਅੱਪਲੋਡ ਲਈ ਫੇਲ੍ਹ ਹੈ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।", "pad.impexp.uploadFailed": "ਅੱਪਲੋਡ ਲਈ ਫੇਲ੍ਹ ਹੈ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।",
"pad.impexp.importfailed": "ਇੰਪੋਰਟ ਫੇਲ੍ਹ ਹੈ", "pad.impexp.importfailed": "ਇੰਪੋਰਟ ਫੇਲ੍ਹ ਹੈ",
"pad.impexp.copypaste": "ਕਾਪੀ ਕਰੋ ਚੇਪੋ ਜੀ", "pad.impexp.copypaste": "ਕਾਪੀ ਕਰੋ ਚੇਪੋ ਜੀ",
"pad.impexp.exportdisabled": "{{type}} ਰੂਪ-ਰੇਖਾ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪ੍ਰਬੰਧਕ ਨਾਲ਼ ਰਾਬਤਾ ਬਣਾਉ।" "pad.impexp.exportdisabled": "{{type}} ਫਾਰਮੈਟ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪਰਬੰਧਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"
} }

View file

@ -4,7 +4,8 @@
"Hedwig", "Hedwig",
"ImGelu", "ImGelu",
"Minisarm", "Minisarm",
"Strainu" "Strainu",
"Wintereu"
] ]
}, },
"index.newPad": "Pad nou", "index.newPad": "Pad nou",
@ -21,6 +22,7 @@
"pad.toolbar.import_export.title": "Importă/Exportă din/în diferite formate", "pad.toolbar.import_export.title": "Importă/Exportă din/în diferite formate",
"pad.toolbar.savedRevision.title": "Salvează revizia", "pad.toolbar.savedRevision.title": "Salvează revizia",
"pad.toolbar.settings.title": "Setări", "pad.toolbar.settings.title": "Setări",
"pad.toolbar.showusers.title": "Arată utilizatorii de pe acest pad",
"pad.colorpicker.save": "Salvează", "pad.colorpicker.save": "Salvează",
"pad.colorpicker.cancel": "Anulează", "pad.colorpicker.cancel": "Anulează",
"pad.loading": "Se încarcă...", "pad.loading": "Se încarcă...",
@ -46,10 +48,10 @@
"pad.importExport.exportpdf": "PDF", "pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)", "pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.modals.connected": "Conectat.", "pad.modals.connected": "Conectat.",
"pad.modals.reconnecting": "Se reconectează la pad-ul u..", "pad.modals.reconnecting": "Se reconectează la pad-ul dumneavoastră..",
"pad.modals.forcereconnect": "Forțează reconectarea", "pad.modals.forcereconnect": "Forțează reconectarea",
"pad.modals.userdup": "Deschis în altă fereastră", "pad.modals.userdup": "Deschis în altă fereastră",
"pad.modals.userdup.advice": "Reconectează pentru a folosi această fereastră în schimb", "pad.modals.userdup.advice": "Reconectați-vă dacă doriți să utilizați această fereastră.",
"pad.modals.unauth": "Nu ești autorizat", "pad.modals.unauth": "Nu ești autorizat",
"pad.modals.initsocketfail": "Serverul nu este disponibil.", "pad.modals.initsocketfail": "Serverul nu este disponibil.",
"pad.modals.initsocketfail.explanation": "Nu s-a putut conecta la serverul de sincronizare.", "pad.modals.initsocketfail.explanation": "Nu s-a putut conecta la serverul de sincronizare.",
@ -61,12 +63,12 @@
"pad.share": "Distribuie acest pad", "pad.share": "Distribuie acest pad",
"pad.share.readonly": "Doar în citire", "pad.share.readonly": "Doar în citire",
"pad.share.link": "Legătură", "pad.share.link": "Legătură",
"pad.share.emebdcode": "Încorporează URL-ul", "pad.share.emebdcode": "Adresa URL încorporată",
"pad.chat": "Chat", "pad.chat": "Chat",
"pad.chat.title": "Deschide chat-ul pentru acest pad.", "pad.chat.title": "Deschide chat-ul pentru acest pad.",
"pad.chat.loadmessages": "Încarcă mai multe mesaje", "pad.chat.loadmessages": "Încarcă mai multe mesaje",
"timeslider.toolbar.returnbutton": "Înapoi la pad", "timeslider.toolbar.returnbutton": "Înapoi la pad",
"timeslider.toolbar.authors": "Aurori:", "timeslider.toolbar.authors": "Autori:",
"timeslider.toolbar.authorsList": "Niciun autor", "timeslider.toolbar.authorsList": "Niciun autor",
"timeslider.toolbar.exportlink.title": "Exportă", "timeslider.toolbar.exportlink.title": "Exportă",
"timeslider.exportCurrent": "Exportă versiunea curentă ca:", "timeslider.exportCurrent": "Exportă versiunea curentă ca:",
@ -85,7 +87,7 @@
"timeslider.month.october": "octombrie", "timeslider.month.october": "octombrie",
"timeslider.month.november": "noiembrie", "timeslider.month.november": "noiembrie",
"timeslider.month.december": "decembrie", "timeslider.month.december": "decembrie",
"pad.userlist.entername": "Introdu numele tău", "pad.userlist.entername": "Introduceți numele dumneavoastră",
"pad.userlist.unnamed": "fără nume", "pad.userlist.unnamed": "fără nume",
"pad.userlist.guest": "Oaspete", "pad.userlist.guest": "Oaspete",
"pad.userlist.deny": "Respinge", "pad.userlist.deny": "Respinge",

View file

@ -5,7 +5,7 @@
"Kosovastar" "Kosovastar"
] ]
}, },
"index.newPad": "Bllok i Ri", "index.newPad": "Bllok i ri",
"index.createOpenPad": "ose krijoni/hapni një Bllok me emrin:", "index.createOpenPad": "ose krijoni/hapni një Bllok me emrin:",
"pad.toolbar.bold.title": "Të trasha (Ctrl-B)", "pad.toolbar.bold.title": "Të trasha (Ctrl-B)",
"pad.toolbar.italic.title": "Të pjerrëta (Ctrl-I)", "pad.toolbar.italic.title": "Të pjerrëta (Ctrl-I)",
@ -13,7 +13,7 @@
"pad.toolbar.strikethrough.title": "Hequrvije (Ctrl+5)", "pad.toolbar.strikethrough.title": "Hequrvije (Ctrl+5)",
"pad.toolbar.ol.title": "Listë e renditur (Ctrl+Shift+N)", "pad.toolbar.ol.title": "Listë e renditur (Ctrl+Shift+N)",
"pad.toolbar.ul.title": "Listë e parenditur (Ctrl+Shift+L)", "pad.toolbar.ul.title": "Listë e parenditur (Ctrl+Shift+L)",
"pad.toolbar.indent.title": "Brendazi (TAB)", "pad.toolbar.indent.title": "E dhëmbëzuar (TAB)",
"pad.toolbar.unindent.title": "Jashtazi (Shift+TAB)", "pad.toolbar.unindent.title": "Jashtazi (Shift+TAB)",
"pad.toolbar.undo.title": "Zhbëje (Ctrl-Z)", "pad.toolbar.undo.title": "Zhbëje (Ctrl-Z)",
"pad.toolbar.redo.title": "Ribëje (Ctrl-Y)", "pad.toolbar.redo.title": "Ribëje (Ctrl-Y)",
@ -21,7 +21,7 @@
"pad.toolbar.import_export.title": "Importoni/Eksportoni nga/në formate të tjera kartelash", "pad.toolbar.import_export.title": "Importoni/Eksportoni nga/në formate të tjera kartelash",
"pad.toolbar.timeslider.title": "Rrjedha kohore", "pad.toolbar.timeslider.title": "Rrjedha kohore",
"pad.toolbar.savedRevision.title": "Ruaje Rishikimin", "pad.toolbar.savedRevision.title": "Ruaje Rishikimin",
"pad.toolbar.settings.title": "Rregullime", "pad.toolbar.settings.title": "Parametrat",
"pad.toolbar.embed.title": "Ndajeni me të tjerët dhe Trupëzojeni këtë bllok", "pad.toolbar.embed.title": "Ndajeni me të tjerët dhe Trupëzojeni këtë bllok",
"pad.toolbar.showusers.title": "Shfaq përdoruesit në këtë bllok", "pad.toolbar.showusers.title": "Shfaq përdoruesit në këtë bllok",
"pad.colorpicker.save": "Ruaje", "pad.colorpicker.save": "Ruaje",
@ -45,6 +45,7 @@
"pad.importExport.import": "Ngarkoni cilëndo kartelë teksti ose dokument", "pad.importExport.import": "Ngarkoni cilëndo kartelë teksti ose dokument",
"pad.importExport.importSuccessful": "Me sukses!", "pad.importExport.importSuccessful": "Me sukses!",
"pad.importExport.export": "Eksportojeni bllokun e tanishëm si:", "pad.importExport.export": "Eksportojeni bllokun e tanishëm si:",
"pad.importExport.exportetherpad": "Etherpad",
"pad.importExport.exporthtml": "HTML", "pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Tekst të thjeshtë", "pad.importExport.exportplain": "Tekst të thjeshtë",
"pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportword": "Microsoft Word",
@ -58,7 +59,7 @@
"pad.modals.userdup.explanation": "Ky bllok duket se gjendet i hapur në më shumë se një dritare shfletuesi në këtë kompjuter.", "pad.modals.userdup.explanation": "Ky bllok duket se gjendet i hapur në më shumë se një dritare shfletuesi në këtë kompjuter.",
"pad.modals.userdup.advice": "Rilidhuni që të përdoret kjo dritare.", "pad.modals.userdup.advice": "Rilidhuni që të përdoret kjo dritare.",
"pad.modals.unauth": "I paautorizuar", "pad.modals.unauth": "I paautorizuar",
"pad.modals.unauth.explanation": "Ndërkohë që shihnit këtë dritare, lejet tuaja kanë ndryshuar. Provoni të rilidheni.", "pad.modals.unauth.explanation": "Ndërkohë që sheh këtë dritare, lejet e tua kanë ndryshuar. Provo të rilidhesh.",
"pad.modals.looping.explanation": "Ka probleme komunikimi me shërbyesin e njëkohësimit.", "pad.modals.looping.explanation": "Ka probleme komunikimi me shërbyesin e njëkohësimit.",
"pad.modals.looping.cause": "Ndoshta jeni lidhur përmes një firewall-i ose ndërmjetësi të papërputhshëm.", "pad.modals.looping.cause": "Ndoshta jeni lidhur përmes një firewall-i ose ndërmjetësi të papërputhshëm.",
"pad.modals.initsocketfail": "Nuk kapet dot shërbyesi.", "pad.modals.initsocketfail": "Nuk kapet dot shërbyesi.",
@ -90,6 +91,7 @@
"timeslider.exportCurrent": "Eksportojeni versionin e tanishëm si:", "timeslider.exportCurrent": "Eksportojeni versionin e tanishëm si:",
"timeslider.version": "Versioni {{version}}", "timeslider.version": "Versioni {{version}}",
"timeslider.saved": "Ruajtur më {{month}} {{day}}, {{year}}", "timeslider.saved": "Ruajtur më {{month}} {{day}}, {{year}}",
"timeslider.playPause": "Luaj përmbajtjet e Pad / Pauzo",
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "Janar", "timeslider.month.january": "Janar",
"timeslider.month.february": "Shkurt", "timeslider.month.february": "Shkurt",

View file

@ -82,7 +82,7 @@
"pad.modals.badChangeset.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。如果您认为这是错误,请联系服务管理员。要继续编辑,请尝试重新连接。", "pad.modals.badChangeset.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。如果您认为这是错误,请联系服务管理员。要继续编辑,请尝试重新连接。",
"pad.modals.corruptPad.explanation": "您试图连接的记事本已损坏。", "pad.modals.corruptPad.explanation": "您试图连接的记事本已损坏。",
"pad.modals.corruptPad.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。请联系服务管理员。", "pad.modals.corruptPad.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。请联系服务管理员。",
"pad.modals.deleted": "已除。", "pad.modals.deleted": "已除。",
"pad.modals.deleted.explanation": "此记事本已被移除。", "pad.modals.deleted.explanation": "此记事本已被移除。",
"pad.modals.disconnected": "您已断开连接。", "pad.modals.disconnected": "您已断开连接。",
"pad.modals.disconnected.explanation": "到服务器的连接已丢失", "pad.modals.disconnected.explanation": "到服务器的连接已丢失",

View file

@ -307,6 +307,38 @@ exports.setText = function(padID, text, callback)
}); });
} }
/**
appendText(padID, text) appends text to a pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
exports.appendText = function(padID, text, callback)
{
//text is required
if(typeof text != "string")
{
callback(new customError("text is no string","apierror"));
return;
}
//get the pad
getPadSafe(padID, true, function(err, pad)
{
if(ERR(err, callback)) return;
pad.appendText(text);
//update the clients on the pad
padMessageHandler.updatePadClients(pad, callback);
});
};
/** /**
getHTML(padID, [rev]) returns the html of a pad getHTML(padID, [rev]) returns the html of a pad

View file

@ -127,7 +127,7 @@ exports.createAuthor = function(name, callback)
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()*32), "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);

View file

@ -303,6 +303,19 @@ Pad.prototype.setText = function setText(newText) {
this.appendRevision(changeset); this.appendRevision(changeset);
}; };
Pad.prototype.appendText = function appendText(newText) {
//clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
//create the changeset
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
//append the changeset
this.appendRevision(changeset);
};
Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) { Pad.prototype.appendChatMessage = function appendChatMessage(text, userId, time) {
this.chatHead++; this.chatHead++;
//save the chat entry in the database //save the chat entry in the database

View file

@ -444,10 +444,61 @@ var version =
, "getChatHead" : ["padID"] , "getChatHead" : ["padID"]
, "restoreRevision" : ["padID", "rev"] , "restoreRevision" : ["padID", "rev"]
} }
, "1.2.13":
{ "createGroup" : []
, "createGroupIfNotExistsFor" : ["groupMapper"]
, "deleteGroup" : ["groupID"]
, "listPads" : ["groupID"]
, "listAllPads" : []
, "createDiffHTML" : ["padID", "startRev", "endRev"]
, "createPad" : ["padID", "text"]
, "createGroupPad" : ["groupID", "padName", "text"]
, "createAuthor" : ["name"]
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
, "listPadsOfAuthor" : ["authorID"]
, "createSession" : ["groupID", "authorID", "validUntil"]
, "deleteSession" : ["sessionID"]
, "getSessionInfo" : ["sessionID"]
, "listSessionsOfGroup" : ["groupID"]
, "listSessionsOfAuthor" : ["authorID"]
, "getText" : ["padID", "rev"]
, "setText" : ["padID", "text"]
, "getHTML" : ["padID", "rev"]
, "setHTML" : ["padID", "html"]
, "getAttributePool" : ["padID"]
, "getRevisionsCount" : ["padID"]
, "getSavedRevisionsCount" : ["padID"]
, "listSavedRevisions" : ["padID"]
, "saveRevision" : ["padID", "rev"]
, "getRevisionChangeset" : ["padID", "rev"]
, "getLastEdited" : ["padID"]
, "deletePad" : ["padID"]
, "copyPad" : ["sourceID", "destinationID", "force"]
, "movePad" : ["sourceID", "destinationID", "force"]
, "getReadOnlyID" : ["padID"]
, "getPadID" : ["roID"]
, "setPublicStatus" : ["padID", "publicStatus"]
, "getPublicStatus" : ["padID"]
, "setPassword" : ["padID", "password"]
, "isPasswordProtected" : ["padID"]
, "listAuthorsOfPad" : ["padID"]
, "padUsersCount" : ["padID"]
, "getAuthorName" : ["authorID"]
, "padUsers" : ["padID"]
, "sendClientsMessage" : ["padID", "msg"]
, "listAllGroups" : []
, "checkToken" : []
, "appendChatMessage" : ["padID", "text", "authorID", "time"]
, "getChatHistory" : ["padID"]
, "getChatHistory" : ["padID", "start", "end"]
, "getChatHead" : ["padID"]
, "restoreRevision" : ["padID", "rev"]
, "appendText" : ["padID", "text"]
}
}; };
// set the latest available API version here // set the latest available API version here
exports.latestApiVersion = '1.2.12'; exports.latestApiVersion = '1.2.13';
// exports the versions so it can be used by the new Swagger endpoint // exports the versions so it can be used by the new Swagger endpoint
exports.version = version; exports.version = version;

View file

@ -30,9 +30,15 @@ var os = require('os');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var TidyHtml = require('../utils/TidyHtml'); var TidyHtml = require('../utils/TidyHtml');
var convertor = null;
//load abiword only if its enabled //load abiword only if its enabled
if(settings.abiword != null) if(settings.abiword != null)
var abiword = require("../utils/Abiword"); convertor = require("../utils/Abiword");
// Use LibreOffice if an executable has been defined in the settings
if(settings.soffice != null)
convertor = require("../utils/LibreOffice");
var tempDirectory = "/tmp"; var tempDirectory = "/tmp";
@ -70,71 +76,11 @@ exports.doExport = function(req, res, padId, type)
} }
else if(type == "txt") else if(type == "txt")
{ {
var txt; exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, txt)
var randNum;
var srcFile, destFile;
async.series([
//render the txt document
function(callback)
{
exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, _txt)
{
if(ERR(err, callback)) return;
txt = _txt;
callback();
});
},
//decide what to do with the txt export
function(callback)
{
//if this is a txt export, we can send this from here directly
res.send(txt);
callback("stop");
},
//send the convert job to abiword
function(callback)
{
//ensure html can be collected by the garbage collector
txt = null;
destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
abiword.convertFile(srcFile, destFile, type, callback);
},
//send the file
function(callback)
{
res.sendFile(destFile, null, callback);
},
//clean up temporary files
function(callback)
{
async.parallel([
function(callback)
{
fs.unlink(srcFile, callback);
},
function(callback)
{
//100ms delay to accomidate for slow windows fs
if(os.type().indexOf("Windows") > -1)
{
setTimeout(function()
{
fs.unlink(destFile, callback);
}, 100);
}
else
{
fs.unlink(destFile, callback);
}
}
], callback);
}
], function(err)
{ {
if(err && err != "stop") ERR(err); if(ERR(err)) return;
}) res.send(txt);
});
} }
else else
{ {
@ -183,11 +129,11 @@ exports.doExport = function(req, res, padId, type)
TidyHtml.tidy(srcFile, callback); TidyHtml.tidy(srcFile, callback);
}, },
//send the convert job to abiword //send the convert job to the convertor (abiword, libreoffice, ..)
function(callback) function(callback)
{ {
destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
abiword.convertFile(srcFile, destFile, type, callback); convertor.convertFile(srcFile, destFile, type, callback);
}, },
//send the file //send the file
function(callback) function(callback)

View file

@ -630,8 +630,8 @@ function handleUserChanges(data, cb)
messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!");
return cb(); return cb();
} }
//TODO: this might happen with other messages too => find one place to copy the session //TODO: this might happen with other messages too => find one place to copy the session
//and always use the copy. atm a message will be ignored if the session is gone even //and always use the copy. atm a message will be ignored if the session is gone even
//if the session was valid when the message arrived in the first place //if the session was valid when the message arrived in the first place
if(!sessioninfos[client.id]) if(!sessioninfos[client.id])
{ {
@ -960,7 +960,7 @@ function handleSwitchToPad(client, message)
roomClients[i].leave(padId); roomClients[i].leave(padId);
} }
} }
// start up the new pad // start up the new pad
createSessionInfo(client, message); createSessionInfo(client, message);
handleClientReady(client, message); handleClientReady(client, message);
@ -1020,6 +1020,8 @@ function handleClientReady(client, message)
var currentTime; var currentTime;
var padIds; var padIds;
hooks.callAll("clientReady", message);
async.series([ async.series([
//Get ro/rw id:s //Get ro/rw id:s
function (callback) function (callback)
@ -1229,6 +1231,7 @@ function handleClientReady(client, message)
"plugins": plugins.plugins, "plugins": plugins.plugins,
"parts": plugins.parts, "parts": plugins.parts,
}, },
"indentationOnNewLine": settings.indentationOnNewLine,
"initialChangesets": [] // FIXME: REMOVE THIS SHIT "initialChangesets": [] // FIXME: REMOVE THIS SHIT
} }
@ -1365,6 +1368,12 @@ function handleChangesetRequest(client, message)
messageLogger.warn("Dropped message, changeset request has no granularity!"); messageLogger.warn("Dropped message, changeset request has no granularity!");
return; return;
} }
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger#Polyfill
if(Math.floor(message.data.granularity) !== message.data.granularity)
{
messageLogger.warn("Dropped message, changeset request granularity is not an integer!");
return;
}
if(message.data.start == null) if(message.data.start == null)
{ {
messageLogger.warn("Dropped message, changeset request has no start!"); messageLogger.warn("Dropped message, changeset request has no start!");

View file

@ -16,6 +16,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
if(sanitizedPadId != padId) if(sanitizedPadId != padId)
{ {
var real_url = sanitizedPadId; var real_url = sanitizedPadId;
real_url = encodeURIComponent(real_url);
var query = url.parse(req.url).query; var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query; if ( query ) real_url += '?' + query;
res.header('Location', real_url); res.header('Location', real_url);

View file

@ -19,6 +19,7 @@ var async = require("async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager"); var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace"); var ERR = require("async-stacktrace");
var _ = require('underscore');
var Security = require('ep_etherpad-lite/static/js/security'); var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var _analyzeLine = require('./ExportHelper')._analyzeLine; var _analyzeLine = require('./ExportHelper')._analyzeLine;
@ -77,12 +78,21 @@ function getHTMLFromAtext(pad, atext, authorColors)
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
// prepare tags stored as ['tag', true] to be exported
hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){ hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){
newProps.forEach(function (propName, i){ newProps.forEach(function (propName, i){
tags.push(propName); tags.push(propName);
props.push(propName); props.push(propName);
}); });
}); });
// prepare tags stored as ['tag', 'value'] to be exported. This will generate HTML
// with tags like <span data-tag="value">
hooks.aCallAll("exportHtmlAdditionalTagsWithData", pad, function(err, newProps){
newProps.forEach(function (propName, i){
tags.push('span data-' + propName[0] + '="' + propName[1] + '"');
props.push(propName);
});
});
// holds a map of used styling attributes (*1, *2, etc) in the apool // holds a map of used styling attributes (*1, *2, etc) in the apool
// and maps them to an index in props // and maps them to an index in props
@ -115,8 +125,8 @@ function getHTMLFromAtext(pad, atext, authorColors)
var newLength = props.push(propName); var newLength = props.push(propName);
anumMap[a] = newLength -1; anumMap[a] = newLength -1;
css+=".removed {text-decoration: line-through; " + css+=".removed {text-decoration: line-through; " +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+ "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+
"filter: alpha(opacity=80); "+ "filter: alpha(opacity=80); "+
"opacity: 0.8; "+ "opacity: 0.8; "+
"}\n"; "}\n";
@ -130,7 +140,13 @@ function getHTMLFromAtext(pad, atext, authorColors)
// this pad, and if yes puts its attrib id->props value into anumMap // this pad, and if yes puts its attrib id->props value into anumMap
props.forEach(function (propName, i) props.forEach(function (propName, i)
{ {
var propTrueNum = apool.putAttrib([propName, true], true); var attrib = [propName, true];
if (_.isArray(propName)) {
// propName can be in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData
attrib = propName;
}
var propTrueNum = apool.putAttrib(attrib, true);
if (propTrueNum >= 0) if (propTrueNum >= 0)
{ {
anumMap[propTrueNum] = i; anumMap[propTrueNum] = i;
@ -154,6 +170,12 @@ function getHTMLFromAtext(pad, atext, authorColors)
var property = props[i]; var property = props[i];
// we are not insterested on properties in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData
if (_.isArray(property)) {
return false;
}
if(property.substr(0,6) === "author"){ if(property.substr(0,6) === "author"){
return stripDotFromAuthorID(property); return stripDotFromAuthorID(property);
} }
@ -165,6 +187,13 @@ function getHTMLFromAtext(pad, atext, authorColors)
return false; return false;
} }
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes
function isSpanWithData(i){
var property = props[i];
return _.isArray(property);
}
function emitOpenTag(i) function emitOpenTag(i)
{ {
openTags.unshift(i); openTags.unshift(i);
@ -186,8 +215,9 @@ function getHTMLFromAtext(pad, atext, authorColors)
{ {
openTags.shift(); openTags.shift();
var spanClass = getSpanClassFor(i); var spanClass = getSpanClassFor(i);
var spanWithData = isSpanWithData(i);
if(spanClass){ if(spanClass || spanWithData){
assem.append('</span>'); assem.append('</span>');
} else { } else {
assem.append('</'); assem.append('</');
@ -263,7 +293,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
var s = taker.take(chars); var s = taker.take(chars);
//removes the characters with the code 12. Don't know where they come //removes the characters with the code 12. Don't know where they come
//from but they break the abiword parser and are completly useless //from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), ""); s = s.replace(String.fromCharCode(12), "");
@ -377,7 +407,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
pieces.push('<br><br>'); pieces.push('<br><br>');
} }
}*/ }*/
else//means we are getting closer to the lowest level of indentation or are at the same level 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 var toClose = lists.length > 0 ? listLevels[listLevels.length - 2] - line.listLevel : 0
if( toClose > 0){ if( toClose > 0){
@ -431,7 +461,7 @@ function getHTMLFromAtext(pad, atext, authorColors)
} }
} }
} }
for (var k = lists.length - 1; k >= 0; k--) for (var k = lists.length - 1; k >= 0; k--)
{ {
if(lists[k][1] == "number") if(lists[k][1] == "number")
@ -460,14 +490,17 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
stylesForExportCSS += css; stylesForExportCSS += css;
}); });
// Core inclusion of head etc. // Core inclusion of head etc.
var head = var head =
(noDocType ? '' : '<!doctype html>\n') + (noDocType ? '' : '<!doctype html>\n') +
'<html lang="en">\n' + (noDocType ? '' : '<head>\n' + '<html lang="en">\n' + (noDocType ? '' : '<head>\n' +
'<title>' + Security.escapeHTML(padId) + '</title>\n' + '<title>' + Security.escapeHTML(padId) + '</title>\n' +
'<meta charset="utf-8">\n' + '<meta name="generator" content="Etherpad">\n' +
'<style> * { font-family: arial, sans-serif;\n' + '<meta name="author" content="Etherpad">\n' +
'font-size: 13px;\n' + '<meta name="changedby" content="Etherpad">\n' +
'line-height: 17px; }' + '<meta charset="utf-8">\n' +
'<style> * { font-family: arial, sans-serif;\n' +
'font-size: 13px;\n' +
'line-height: 17px; }' +
'ul.indent { list-style-type: none; }' + 'ul.indent { list-style-type: none; }' +
'ol { list-style-type: none; padding-left:0;}' + 'ol { list-style-type: none; padding-left:0;}' +
@ -553,8 +586,8 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 140px; }' + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 140px; }' +
'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 150px; }' + 'ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol > ol{ text-indent: 150px; }' +
stylesForExportCSS + stylesForExportCSS +
'</style>\n' + '</head>\n') + '</style>\n' + '</head>\n') +
'<body>'; '<body>';
var foot = '</body>\n</html>\n'; var foot = '</body>\n</html>\n';

View file

@ -0,0 +1,93 @@
/**
* Controls the communication with LibreOffice
*/
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var async = require("async");
var fs = require("fs");
var os = require("os");
var path = require("path");
var settings = require("./Settings");
var spawn = require("child_process").spawn;
// Conversion tasks will be queued up, so we don't overload the system
var queue = async.queue(doConvertTask, 1);
/**
* Convert a file from one type to another
*
* @param {String} srcFile The path on disk to convert
* @param {String} destFile The path on disk where the converted file should be stored
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
exports.convertFile = function(srcFile, destFile, type, callback) {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
};
function doConvertTask(task, callback) {
var tmpDir = os.tmpdir();
async.series([
// Generate a PDF file with LibreOffice
function(callback) {
var soffice = spawn(settings.soffice, [
'--headless',
'--invisible',
'--nologo',
'--nolockcheck',
'--convert-to', task.type,
task.srcFile,
'--outdir', tmpDir
]);
var stdoutBuffer = '';
// Delegate the processing of stdout to another function
soffice.stdout.on('data', function(data) {
stdoutBuffer += data.toString();
});
// Append error messages to the buffer
soffice.stderr.on('data', function(data) {
stdoutBuffer += data.toString();
});
// Throw an exception if libreoffice failed
soffice.on('exit', function(code) {
if (code != 0) {
return callback("LibreOffice died with exit code " + code + " and message: " + stdoutBuffer);
}
callback();
})
},
// Move the PDF file to the correct place
function(callback) {
var filename = path.basename(task.srcFile);
var pdfFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.type;
var pdfPath = path.join(tmpDir, pdfFilename);
fs.rename(pdfPath, task.destFile, callback);
}
], function(err) {
// Invoke the callback for the local queue
callback();
// Invoke the callback for the task
task.callback(err);
});
}

View file

@ -152,6 +152,11 @@ exports.minify = true;
*/ */
exports.abiword = null; exports.abiword = null;
/**
* The path of the libreoffice executable
*/
exports.soffice = null;
/** /**
* The path of the tidy executable * The path of the tidy executable
*/ */
@ -177,6 +182,11 @@ exports.disableIPlogging = false;
*/ */
exports.loadTest = false; exports.loadTest = false;
/**
* Enable indentation on new lines
*/
exports.indentationOnNewLine = true;
/* /*
* log4js appender configuration * log4js appender configuration
*/ */
@ -218,8 +228,14 @@ exports.getGitCommit = function() {
try try
{ {
var rootPath = path.resolve(npm.dir, '..'); var rootPath = path.resolve(npm.dir, '..');
var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); if (fs.lstatSync(rootPath + '/.git').isFile()) {
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); rootPath = fs.readFileSync(rootPath + '/.git', "utf8");
rootPath = rootPath.split(' ').pop().trim();
} else {
rootPath += '/.git';
}
var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8");
var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8"); version = fs.readFileSync(refPath, "utf-8");
version = version.substring(0, 7); version = version.substring(0, 7);
} }

View file

@ -265,7 +265,7 @@ plugins.ensure(function () {\n\
iframeHTML: iframeHTML iframeHTML: iframeHTML
}); });
iframeHTML.push('</head><body id="innerdocbody" role="application" class="syntax" spellcheck="false">&nbsp;</body></html>'); iframeHTML.push('</head><body id="innerdocbody" class="innerdocbody" role="application" class="syntax" spellcheck="false">&nbsp;</body></html>');
// Expose myself to global for my child frame. // Expose myself to global for my child frame.
var thisFunctionsName = "ChildAccessibleAce2Editor"; var thisFunctionsName = "ChildAccessibleAce2Editor";
@ -315,7 +315,7 @@ window.onload = function () {\n\
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing) // (throbs busy while typing)
outerHTML.push('<style type="text/css" title="dynamicsyntax"></style>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody"><div id="sidediv"><!-- --></div><div id="linemetricsdiv">x</div></body></html>'); outerHTML.push('<style type="text/css" title="dynamicsyntax"></style>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>', scriptTag(outerScript), '</head><body id="outerdocbody" class="outerdocbody"><div id="sidediv" class="sidediv"><!-- --></div><div id="linemetricsdiv">x</div></body></html>');
var outerFrame = document.createElement("IFRAME"); var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer"; outerFrame.name = "ace_outer";

View file

@ -1894,7 +1894,11 @@ function Ace2Inner(){
var prevLine = rep.lines.prev(thisLine); var prevLine = rep.lines.prev(thisLine);
var prevLineText = prevLine.text; var prevLineText = prevLine.text;
var theIndent = /^ *(?:)/.exec(prevLineText)[0]; var theIndent = /^ *(?:)/.exec(prevLineText)[0];
if (/[\[\(\:\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB; var shouldIndent = parent.parent.clientVars.indentationOnNewLine;
if (shouldIndent && /[\[\(\:\{]\s*$/.exec(prevLineText))
{
theIndent += THE_TAB;
}
var cs = Changeset.builder(rep.lines.totalWidth()).keep( var cs = Changeset.builder(rep.lines.totalWidth()).keep(
rep.lines.offsetOfIndex(lineNum), lineNum).insert( rep.lines.offsetOfIndex(lineNum), lineNum).insert(
theIndent, [ theIndent, [
@ -2336,7 +2340,7 @@ function Ace2Inner(){
function getAttributeOnSelection(attributeName){ function getAttributeOnSelection(attributeName){
if (!(rep.selStart && rep.selEnd)) return if (!(rep.selStart && rep.selEnd)) return
var withIt = Changeset.makeAttribsString('+', [ var withIt = Changeset.makeAttribsString('+', [
[attributeName, 'true'] [attributeName, 'true']
], rep.apool); ], rep.apool);
@ -2347,14 +2351,14 @@ function Ace2Inner(){
} }
return rangeHasAttrib(rep.selStart, rep.selEnd) return rangeHasAttrib(rep.selStart, rep.selEnd)
function rangeHasAttrib(selStart, selEnd) { function rangeHasAttrib(selStart, selEnd) {
// if range is collapsed -> no attribs in range // if range is collapsed -> no attribs in range
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false
if(selStart[0] != selEnd[0]) { // -> More than one line selected if(selStart[0] != selEnd[0]) { // -> More than one line selected
var hasAttrib = true var hasAttrib = true
// from selStart to the end of the first line // from selStart to the end of the first line
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]) hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length])
@ -2365,22 +2369,22 @@ function Ace2Inner(){
// for the last, potentially partial, line // for the last, potentially partial, line
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]) hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]])
return hasAttrib return hasAttrib
} }
// Logic tells us we now have a range on a single line // Logic tells us we now have a range on a single line
var lineNum = selStart[0] var lineNum = selStart[0]
, start = selStart[1] , start = selStart[1]
, end = selEnd[1] , end = selEnd[1]
, hasAttrib = true , hasAttrib = true
// Iterate over attribs on this line // Iterate over attribs on this line
var opIter = Changeset.opIterator(rep.alines[lineNum]) var opIter = Changeset.opIterator(rep.alines[lineNum])
, indexIntoLine = 0 , indexIntoLine = 0
while (opIter.hasNext()) { while (opIter.hasNext()) {
var op = opIter.next(); var op = opIter.next();
var opStartInLine = indexIntoLine; var opStartInLine = indexIntoLine;
@ -2394,11 +2398,11 @@ function Ace2Inner(){
} }
indexIntoLine = opEndInLine; indexIntoLine = opEndInLine;
} }
return hasAttrib return hasAttrib
} }
} }
editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection; editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection;
function toggleAttributeOnSelection(attributeName) function toggleAttributeOnSelection(attributeName)
@ -2897,6 +2901,12 @@ function Ace2Inner(){
rep.selFocusAtStart = newSelFocusAtStart; rep.selFocusAtStart = newSelFocusAtStart;
currentCallStack.repChanged = true; currentCallStack.repChanged = true;
hooks.callAll('aceSelectionChanged', {
rep: rep,
callstack: currentCallStack,
documentAttributeManager: documentAttributeManager,
});
return true; return true;
//console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd, //console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd,
//String(!!rep.selFocusAtStart)); //String(!!rep.selFocusAtStart));
@ -3632,16 +3642,10 @@ function Ace2Inner(){
var altKey = evt.altKey; var altKey = evt.altKey;
var shiftKey = evt.shiftKey; var shiftKey = evt.shiftKey;
// prevent ESC key
if (keyCode == 27)
{
evt.preventDefault();
return;
}
// Is caret potentially hidden by the chat button? // Is caret potentially hidden by the chat button?
var myselection = document.getSelection(); // get the current caret selection var myselection = document.getSelection(); // get the current caret selection
var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214
if(myselection.focusNode.wholeText){ // Is there any content? If not lineHeight will report wrong.. if(myselection.focusNode.wholeText){ // Is there any content? If not lineHeight will report wrong..
var lineHeight = myselection.focusNode.parentNode.offsetHeight; // line height of populated links var lineHeight = myselection.focusNode.parentNode.offsetHeight; // line height of populated links
}else{ }else{
@ -3706,20 +3710,25 @@ function Ace2Inner(){
documentAttributeManager: documentAttributeManager, documentAttributeManager: documentAttributeManager,
evt:evt evt:evt
}); });
specialHandled = (specialHandledInHook&&specialHandledInHook.length>0)?specialHandledInHook[0]:specialHandled;
// if any hook returned true, set specialHandled with true
if (specialHandledInHook) {
specialHandled = _.contains(specialHandledInHook, true);
}
if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120){ if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120){
// Alt F9 focuses on the File Menu and/or editbar. // Alt F9 focuses on the File Menu and/or editbar.
// Note that while most editors use Alt F10 this is not desirable // Note that while most editors use Alt F10 this is not desirable
// As ubuntu cannot use Alt F10.... // As ubuntu cannot use Alt F10....
// Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it) // Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it)
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first();
$(this).blur(); $(this).blur();
firstEditbarElement.focus(); firstEditbarElement.focus();
evt.preventDefault(); evt.preventDefault();
} }
if ((!specialHandled) && altKey && keyCode == 67 && type === "keydown"){ if ((!specialHandled) && altKey && keyCode == 67 && type === "keydown"){
// Alt c focuses on the Chat window // Alt c focuses on the Chat window
$(this).blur(); $(this).blur();
parent.parent.chat.show(); parent.parent.chat.show();
parent.parent.$("#chatinput").focus(); parent.parent.$("#chatinput").focus();
evt.preventDefault(); evt.preventDefault();
@ -3830,6 +3839,15 @@ function Ace2Inner(){
}, 0); }, 0);
specialHandled = true; specialHandled = true;
} }
if ((!specialHandled) && isTypeForSpecialKey && keyCode == 27)
{
// prevent esc key;
// in mozilla versions 14-19 avoid reconnecting pad.
fastIncorp(4);
evt.preventDefault();
specialHandled = true;
}
if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey) && !evt.altKey) /* Do a saved revision on ctrl S */ if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey) && !evt.altKey) /* Do a saved revision on ctrl S */
{ {
evt.preventDefault(); evt.preventDefault();
@ -4961,7 +4979,7 @@ function Ace2Inner(){
// Disabled: https://github.com/ether/etherpad-lite/issues/2546 // Disabled: https://github.com/ether/etherpad-lite/issues/2546
// Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533 // Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533
// $(document).on("cut", handleCut); // $(document).on("cut", handleCut);
$(root).on("blur", handleBlur); $(root).on("blur", handleBlur);
if (browser.msie) if (browser.msie)
@ -4972,12 +4990,19 @@ function Ace2Inner(){
// Don't paste on middle click of links // Don't paste on middle click of links
$(root).on("paste", function(e){ $(root).on("paste", function(e){
// TODO: this breaks pasting strings into URLS when using // TODO: this breaks pasting strings into URLS when using
// Control C and Control V -- the Event is never available // Control C and Control V -- the Event is never available
// here.. :( // here.. :(
if(e.target.a || e.target.localName === "a"){ if(e.target.a || e.target.localName === "a"){
e.preventDefault(); e.preventDefault();
} }
// Call paste hook
hooks.callAll('acePaste', {
editorInfo: editorInfo,
rep: rep,
documentAttributeManager: documentAttributeManager
});
}) })
// CompositionEvent is not implemented below IE version 8 // CompositionEvent is not implemented below IE version 8
@ -5347,8 +5372,9 @@ function Ace2Inner(){
function initLineNumbers() function initLineNumbers()
{ {
lineNumbersShown = 1; lineNumbersShown = 1;
sideDiv.innerHTML = '<table border="0" cellpadding="0" cellspacing="0" align="right"><tr><td id="sidedivinner"><div>1</div></td></tr></table>'; sideDiv.innerHTML = '<table border="0" cellpadding="0" cellspacing="0" align="right"><tr><td id="sidedivinner" class="sidedivinner"><div>1</div></td></tr></table>';
sideDivInner = outerWin.document.getElementById("sidedivinner"); sideDivInner = outerWin.document.getElementById("sidedivinner");
$(sideDiv).addClass("sidediv");
} }
function updateLineNumbers() function updateLineNumbers()

View file

@ -11,7 +11,7 @@ $(document).ready(function () {
//connect //connect
var room = url + "pluginfw/installer"; var room = url + "pluginfw/installer";
socket = io.connect(room, {resource : resource}); socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
function search(searchTerm, limit) { function search(searchTerm, limit) {
if(search.searchTerm != searchTerm) { if(search.searchTerm != searchTerm) {

View file

@ -10,7 +10,7 @@ $(document).ready(function () {
//connect //connect
var room = url + "settings"; var room = url + "settings";
socket = io.connect(room, {resource : resource}); socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
socket.on('settings', function (settings) { socket.on('settings', function (settings) {

View file

@ -100,7 +100,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
function textify(str) function textify(str)
{ {
return sanitizeUnicode( return sanitizeUnicode(
str.replace(/\n/g, '').replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' ')); str.replace(/(\n | \n)/g, ' ').replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '));
} }
function getAssoc(node, name) function getAssoc(node, name)

View file

@ -100,7 +100,7 @@
</div> </div>
<div id="editorcontainerbox"> <div id="editorcontainerbox">
<div id="editorcontainer"></div> <div id="editorcontainer" class="editorcontainer"></div>
<div id="editorloadingbox"> <div id="editorloadingbox">
<div id="passwordRequired"> <div id="passwordRequired">
<p data-l10n-id="pad.passwordRequired">You need a password to access this pad</p> <p data-l10n-id="pad.passwordRequired">You need a password to access this pad</p>

View file

@ -79,6 +79,8 @@ describe('Permission', function(){
-> movePad(newPadID, originalPadId) -- Should provide consistant pad data -> movePad(newPadID, originalPadId) -- Should provide consistant pad data
-> getText(originalPadId) -- Should be "hello world" -> getText(originalPadId) -- Should be "hello world"
-> getLastEdited(padID) -- Should not be 0 -> getLastEdited(padID) -- Should not be 0
-> appendText(padID, "hello")
-> getText(padID) -- Should be "hello worldhello"
-> setHTML(padID) -- Should fail on invalid HTML -> setHTML(padID) -- Should fail on invalid HTML
-> setHTML(padID) *3 -- Should fail on invalid HTML -> setHTML(padID) *3 -- Should fail on invalid HTML
-> getHTML(padID) -- Should return HTML close to posted HTML -> getHTML(padID) -- Should return HTML close to posted HTML
@ -483,6 +485,30 @@ describe('getLastEdited', function(){
}); });
}) })
describe('appendText', function(){
it('Append text to a pad Id', function(done) {
api.get(endPoint('appendText', '1.2.13')+"&padID="+testPadId+"&text=hello")
.expect(function(res){
if(res.body.code !== 0) throw new Error("Pad Append Text failed");
})
.expect('Content-Type', /json/)
.expect(200, done);
});
});
describe('getText', function(){
it('Gets text on a pad Id', function(done) {
api.get(endPoint('getText')+"&padID="+testPadId)
.expect(function(res){
if(res.body.code !== 0) throw new Error("Pad Get Text failed");
if(res.body.data.text !== text+"\nhello") throw new Error("Pad Text not set properly");
})
.expect('Content-Type', /json/)
.expect(200, done);
});
});
describe('setHTML', function(){ describe('setHTML', function(){
it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) { it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) {
var html = "<div><b>Hello HTML</title></head></div>"; var html = "<div><b>Hello HTML</title></head></div>";
@ -542,8 +568,9 @@ describe('createPad', function(){
*/ */
var endPoint = function(point){ var endPoint = function(point, version){
return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey; version = version || apiVersion;
return '/api/'+version+'/'+point+'?apikey='+apiKey;
} }
function makeid() function makeid()

View file

@ -15,7 +15,7 @@ describe("indentation button", function(){
//select this text element //select this text element
$firstTextElement.sendkeys('{selectall}'); $firstTextElement.sendkeys('{selectall}');
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
var evtType = "keypress"; var evtType = "keypress";
}else{ }else{
var evtType = "keydown"; var evtType = "keydown";
@ -31,7 +31,7 @@ describe("indentation button", function(){
}); });
it("indent text with button", function(done){ it("indent text with button", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var $indentButton = chrome$(".buttonicon-indent"); var $indentButton = chrome$(".buttonicon-indent");
@ -43,7 +43,7 @@ describe("indentation button", function(){
}); });
it("keeps the indent on enter for the new line", function(done){ it("keeps the indent on enter for the new line", function(done){
var inner$ = helper.padInner$; var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$; var chrome$ = helper.padChrome$;
var $indentButton = chrome$(".buttonicon-indent"); var $indentButton = chrome$(".buttonicon-indent");
@ -51,9 +51,9 @@ describe("indentation button", function(){
//type a bit, make a line break and type again //type a bit, make a line break and type again
var $firstTextElement = inner$("div span").first(); var $firstTextElement = inner$("div span").first();
$firstTextElement.sendkeys('line 1'); $firstTextElement.sendkeys('line 1');
$firstTextElement.sendkeys('{enter}'); $firstTextElement.sendkeys('{enter}');
$firstTextElement.sendkeys('line 2'); $firstTextElement.sendkeys('line 2');
$firstTextElement.sendkeys('{enter}'); $firstTextElement.sendkeys('{enter}');
helper.waitFor(function(){ helper.waitFor(function(){
@ -68,13 +68,87 @@ describe("indentation button", function(){
}); });
}); });
it("indents text with spaces on enter if previous line ends with ':', '[', '(', or '{'", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
//type a bit, make a line break and type again
var $firstTextElement = inner$("div").first();
$firstTextElement.sendkeys("line with ':'{enter}");
$firstTextElement.sendkeys("line with '['{enter}");
$firstTextElement.sendkeys("line with '('{enter}");
$firstTextElement.sendkeys("line with '{{}'{enter}");
helper.waitFor(function(){
// wait for Etherpad to split four lines into separated divs
var $fourthLine = inner$("div").first().next().next().next();
return $fourthLine.text().indexOf("line with '{'") === 0;
}).done(function(){
// we validate bottom to top for easier implementation
// curly braces
var $lineWithCurlyBraces = inner$("div").first().next().next().next();
$lineWithCurlyBraces.sendkeys('{{}');
pressEnter(); // cannot use sendkeys('{enter}') here, browser does not read the command properly
var $lineAfterCurlyBraces = inner$("div").first().next().next().next().next();
expect($lineAfterCurlyBraces.text()).to.match(/\s{4}/); // tab === 4 spaces
// parenthesis
var $lineWithParenthesis = inner$("div").first().next().next();
$lineWithParenthesis.sendkeys('(');
pressEnter();
var $lineAfterParenthesis = inner$("div").first().next().next().next();
expect($lineAfterParenthesis.text()).to.match(/\s{4}/);
// bracket
var $lineWithBracket = inner$("div").first().next();
$lineWithBracket.sendkeys('[');
pressEnter();
var $lineAfterBracket = inner$("div").first().next().next();
expect($lineAfterBracket.text()).to.match(/\s{4}/);
// colon
var $lineWithColon = inner$("div").first();
$lineWithColon.sendkeys(':');
pressEnter();
var $lineAfterColon = inner$("div").first().next();
expect($lineAfterColon.text()).to.match(/\s{4}/);
done();
});
});
it("appends indentation to the indent of previous line if previous line ends with ':', '[', '(', or '{'", function(done){
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
//type a bit, make a line break and type again
var $firstTextElement = inner$("div").first();
$firstTextElement.sendkeys(" line with some indentation and ':'{enter}");
$firstTextElement.sendkeys("line 2{enter}");
helper.waitFor(function(){
// wait for Etherpad to split two lines into separated divs
var $secondLine = inner$("div").first().next();
return $secondLine.text().indexOf("line 2") === 0;
}).done(function(){
var $lineWithColon = inner$("div").first();
$lineWithColon.sendkeys(':');
pressEnter();
var $lineAfterColon = inner$("div").first().next();
expect($lineAfterColon.text()).to.match(/\s{6}/); // previous line indentation + regular tab (4 spaces)
done();
});
});
/* /*
it("makes text indented and outdented", function() { it("makes text indented and outdented", function() {
//get the inner iframe //get the inner iframe
var $inner = testHelper.$getPadInner(); var $inner = testHelper.$getPadInner();
//get the first text element out of the inner iframe //get the first text element out of the inner iframe
var firstTextElement = $inner.find("div").first(); var firstTextElement = $inner.find("div").first();
@ -87,7 +161,7 @@ describe("indentation button", function(){
//ace creates a new dom element when you press a button, so just get the first text element again //ace creates a new dom element when you press a button, so just get the first text element again
var newFirstTextElement = $inner.find("div").first(); var newFirstTextElement = $inner.find("div").first();
// is there a list-indent class element now? // is there a list-indent class element now?
var firstChild = newFirstTextElement.children(":first"); var firstChild = newFirstTextElement.children(":first");
var isUL = firstChild.is('ul'); var isUL = firstChild.is('ul');
@ -160,12 +234,12 @@ describe("indentation button", function(){
/* this test creates the below content, both should have double indentation /* this test creates the below content, both should have double indentation
line1 line1
line2 line2
firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
firstTextElement.sendkeys('line 1'); // simulate writing the first line firstTextElement.sendkeys('line 1'); // simulate writing the first line
firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter
firstTextElement.sendkeys('line 2'); // simulate writing the second line firstTextElement.sendkeys('line 2'); // simulate writing the second line
//get the second text element out of the inner iframe //get the second text element out of the inner iframe
@ -203,3 +277,15 @@ describe("indentation button", function(){
});*/ });*/
}); });
function pressEnter(){
var inner$ = helper.padInner$;
if(inner$(window)[0].bowser.firefox || inner$(window)[0].bowser.modernIE){ // if it's a mozilla or IE
var evtType = "keypress";
}else{
var evtType = "keydown";
}
var e = inner$.Event(evtType);
e.keyCode = 13; // enter :|
inner$("#innerdocbody").trigger(e);
}