From 008d4e653c8075299444f39f1376e12571ae15fb Mon Sep 17 00:00:00 2001 From: Xavid Date: Sat, 11 Jul 2015 11:32:19 -0400 Subject: [PATCH 01/31] Add a aceSelectionChanged hook to allow plugins to react when the cursor location changes. --- doc/api/hooks_client-side.md | 11 +++++++++++ src/static/js/ace2_inner.js | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index fccdaf466..367e06884 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -339,3 +339,14 @@ Things in context: 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. + +## 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. diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 10dd0e4c5..255f02906 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -2894,6 +2894,11 @@ function Ace2Inner(){ rep.selFocusAtStart = newSelFocusAtStart; currentCallStack.repChanged = true; + hooks.callAll('aceSelectionChanged', { + rep: rep, + documentAttributeManager: documentAttributeManager, + }); + return true; //console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd, //String(!!rep.selFocusAtStart)); From 21f0d12d31a076efe48cbf2239a4bded4d0abaf5 Mon Sep 17 00:00:00 2001 From: Emily Xie Date: Tue, 14 Jul 2015 17:08:35 -0400 Subject: [PATCH 02/31] clientReady hook- pass entire message, updated doc --- doc/api/hooks_server-side.md | 17 +++++++++++++++++ src/node/handler/PadMessageHandler.js | 2 ++ 2 files changed, 19 insertions(+) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 79879b2fe..6ef65cc54 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -384,3 +384,20 @@ exports.userLeave = function(hook, session, callback) { 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); +}; +``` diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 248dc1287..cc1260c25 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1009,6 +1009,8 @@ function handleClientReady(client, message) var currentTime; var padIds; + hooks.callAll("clientReady", message); + async.series([ //Get ro/rw id:s function (callback) From 275a7d31e0949aeccc40e4412db5f76da0f06f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20T=C3=A9tard?= Date: Mon, 27 Jul 2015 16:38:57 +0200 Subject: [PATCH 03/31] Avoid space removal when pasting text from word processor. Since bf380eea504662ea41aa431e30d7e30ad6a36cd3, some spaces were removed when pasting text from a word processor (at least using Libre Office). To avoid double space creations and space removal, we only remove line break which are tight to a space character. --- src/static/js/contentcollector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index 5c1e8efbf..6820da07c 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -100,7 +100,7 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas function textify(str) { 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) From 3b08ea61585485ad73afa84a1daa07c68e1482b0 Mon Sep 17 00:00:00 2001 From: CodeMichael Date: Fri, 7 Aug 2015 08:30:29 -0500 Subject: [PATCH 04/31] allow admin to run on a sub-directory --- src/static/js/admin/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/admin/plugins.js b/src/static/js/admin/plugins.js index d337da03a..48d1ab70a 100644 --- a/src/static/js/admin/plugins.js +++ b/src/static/js/admin/plugins.js @@ -11,7 +11,7 @@ $(document).ready(function () { //connect 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) { if(search.searchTerm != searchTerm) { From 6f6de64c4a14af9c191d30759a9c5706c510b754 Mon Sep 17 00:00:00 2001 From: CodeMichael Date: Fri, 7 Aug 2015 08:31:08 -0500 Subject: [PATCH 05/31] allow /admin to run on a sub-directory --- src/static/js/admin/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/admin/settings.js b/src/static/js/admin/settings.js index 8a4473d6a..42b038d52 100644 --- a/src/static/js/admin/settings.js +++ b/src/static/js/admin/settings.js @@ -10,7 +10,7 @@ $(document).ready(function () { //connect 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) { From 1a5985dc759c33e614eaa53ba9c4d2489c9e3495 Mon Sep 17 00:00:00 2001 From: Luiza Pagliari Date: Mon, 24 Aug 2015 07:58:45 -0700 Subject: [PATCH 06/31] Accepting Arrays on 'exportHtmlAdditionalTags' to handle attributes stored as ['key', 'value'] (and not only ['key', 'true']) --- doc/api/hooks_server-side.md | 11 ++++++++++- src/node/utils/ExportHtml.js | 22 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 79879b2fe..4f441f58a 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -357,7 +357,7 @@ Things in context: 1. Pad object -This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. +This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. If a value in this array is a string, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'true']`; if a value in this array is a pair `['tag_name', 'value']`, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'value']`. Example: ``` @@ -368,6 +368,15 @@ exports.exportHtmlAdditionalTags = function(hook, pad, cb){ }; ``` +Example when attributes are stores as `['color', 'red']` on the attribute pool: +``` +// Add the props to be supported in export +exports.exportHtmlAdditionalTags = function(hook, pad, cb){ + var padId = pad.id; + cb([["color", "red"], ["color", "blue"]]); +}; +``` + ## userLeave Called from src/node/handler/PadMessageHandler.js diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 9e1ba1249..c5936cf9f 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -19,6 +19,7 @@ var async = require("async"); var Changeset = require("ep_etherpad-lite/static/js/Changeset"); var padManager = require("../db/PadManager"); var ERR = require("async-stacktrace"); +var _ = require('underscore'); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var _analyzeLine = require('./ExportHelper')._analyzeLine; @@ -78,8 +79,15 @@ function getHTMLFromAtext(pad, atext, authorColors) var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){ + // newProps can be simply a string (which means it is stored as attribute in the form of ['tag', 'true']) + // or it can be a pair of values in an Array (for the case when it is stored as ['tag', 'value']). + // The later scenario will generate HTML with tags like newProps.forEach(function (propName, i){ - tags.push(propName); + if (_.isArray(propName)) { + tags.push(propName[0] + ":" + propName[1]); + } else { + tags.push(propName); + } props.push(propName); }); }); @@ -130,7 +138,12 @@ function getHTMLFromAtext(pad, atext, authorColors) // this pad, and if yes puts its attrib id->props value into anumMap 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'] + attrib = propName; + } + var propTrueNum = apool.putAttrib(attrib, true); if (propTrueNum >= 0) { anumMap[propTrueNum] = i; @@ -154,6 +167,11 @@ function getHTMLFromAtext(pad, atext, authorColors) var property = props[i]; + // we are not insterested on properties in the form of ['color', 'red'] + if (_.isArray(property)) { + return false; + } + if(property.substr(0,6) === "author"){ return stripDotFromAuthorID(property); } From ed5262650af5feeb20bab2fa6173a0fe84821492 Mon Sep 17 00:00:00 2001 From: Luiza Pagliari Date: Mon, 7 Sep 2015 03:55:56 -0700 Subject: [PATCH 07/31] Generating pad HTML with tags like instead of --- doc/api/hooks_server-side.md | 2 +- src/node/utils/ExportHtml.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index 4f441f58a..dde61fe8f 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -357,7 +357,7 @@ Things in context: 1. Pad object -This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. If a value in this array is a string, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'true']`; if a value in this array is a pair `['tag_name', 'value']`, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'value']`. +This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. If a value in this array is a string, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'true']`; if a value in this array is a pair `['tag_name', 'value']`, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'value']`. Example: ``` diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index c5936cf9f..ccdc3a59e 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -81,10 +81,10 @@ function getHTMLFromAtext(pad, atext, authorColors) hooks.aCallAll("exportHtmlAdditionalTags", pad, function(err, newProps){ // newProps can be simply a string (which means it is stored as attribute in the form of ['tag', 'true']) // or it can be a pair of values in an Array (for the case when it is stored as ['tag', 'value']). - // The later scenario will generate HTML with tags like + // The later scenario will generate HTML with tags like newProps.forEach(function (propName, i){ if (_.isArray(propName)) { - tags.push(propName[0] + ":" + propName[1]); + tags.push('span data-' + propName[0] + '="' + propName[1] + '"'); } else { tags.push(propName); } From 1d134f0b138ca23f548a2795e0c68bbc6ea17437 Mon Sep 17 00:00:00 2001 From: Luiza Pagliari Date: Thu, 17 Sep 2015 15:30:09 -0300 Subject: [PATCH 08/31] Fixing ed52626. It was closing the span with , not --- doc/api/hooks_server-side.md | 2 +- src/node/utils/ExportHtml.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/api/hooks_server-side.md b/doc/api/hooks_server-side.md index dde61fe8f..8467d0577 100644 --- a/doc/api/hooks_server-side.md +++ b/doc/api/hooks_server-side.md @@ -357,7 +357,7 @@ Things in context: 1. Pad object -This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. If a value in this array is a string, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'true']`; if a value in this array is a pair `['tag_name', 'value']`, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'value']`. +This hook will allow a plug-in developer to include more properties and attributes to support during HTML Export. An Array should be returned. If a value in this array is a string, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'true']`; if a value in this array is a pair `['tag_name', 'value']`, the exported HTML will contain tags like `` for the content where attributes are `['tag_name', 'value']`. Example: ``` diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index ccdc3a59e..53469c9b9 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -183,6 +183,11 @@ function getHTMLFromAtext(pad, atext, authorColors) return false; } + function isSpanWithData(i){ + var property = props[i]; + return _.isArray(property); + } + function emitOpenTag(i) { openTags.unshift(i); @@ -204,8 +209,9 @@ function getHTMLFromAtext(pad, atext, authorColors) { openTags.shift(); var spanClass = getSpanClassFor(i); + var spanWithData = isSpanWithData(i); - if(spanClass){ + if(spanClass || spanWithData){ assem.append(''); } else { assem.append(' Date: Sat, 26 Sep 2015 12:15:54 +0200 Subject: [PATCH 09/31] Support version 4 of node.js --- bin/installDeps.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/installDeps.sh b/bin/installDeps.sh index f2a3aafcf..fbbbbb24e 100755 --- a/bin/installDeps.sh +++ b/bin/installDeps.sh @@ -46,13 +46,14 @@ fi #check node version NODE_VERSION=$(node --version) NODE_V_MINOR=$(echo $NODE_VERSION | cut -d "." -f 1-2) +NODE_V_MAIN=$(echo $NODE_VERSION | cut -d "." -f 1) #iojs version checking added if hash iojs 2>/dev/null; then IOJS_VERSION=$(iojs --version) 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" ]; 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 fi fi From c9924ee706edca1591b1c879f8e5d593b48f254c Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Wed, 7 Oct 2015 12:42:19 +0200 Subject: [PATCH 10/31] Give better error message when rebuildPad.js hits a non-existing rev. --- bin/rebuildPad.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/rebuildPad.js b/bin/rebuildPad.js index c83833420..60c5f4ed3 100644 --- a/bin/rebuildPad.js +++ b/bin/rebuildPad.js @@ -79,6 +79,9 @@ async.series([ newPad.pool.numToAttrib = oldPad.pool.numToAttrib; for(var curRevNum = 0; curRevNum <= newRevHead; curRevNum++) { 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 newRevId = "pad:" + newPad.id + ":revs:" + newRevNum; db.db.set(newRevId, rev); From 29441a8ae156113ebc50d8a655b3bf3a93c80db3 Mon Sep 17 00:00:00 2001 From: Robin Schneider Date: Wed, 7 Oct 2015 15:43:29 +0200 Subject: [PATCH 11/31] Get git commit hash even if the repo only points to a bare repo. * Used by https://github.com/debops/ansible-etherpad --- src/node/utils/Settings.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 2c2f90bf8..e1825ef4a 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -218,8 +218,14 @@ exports.getGitCommit = function() { try { var rootPath = path.resolve(npm.dir, '..'); - var ref = fs.readFileSync(rootPath + "/.git/HEAD", "utf-8"); - var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n")); + if (fs.lstatSync(rootPath + '/.git').isFile()) { + 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 = version.substring(0, 7); } From f6cebdad9813026b2909fadc4e43adcdc4d78b10 Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 8 Oct 2015 20:46:15 +0200 Subject: [PATCH 12/31] Fix decode error if pad name contains special characters and is sanitized --- src/node/hooks/express/padurlsanitize.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index 94cbe36a1..a9972220b 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -16,6 +16,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { if(sanitizedPadId != padId) { var real_url = sanitizedPadId; + real_url = encodeURIComponent(real_url); var query = url.parse(req.url).query; if ( query ) real_url += '?' + query; res.header('Location', real_url); From 31f7c7e0f2e31b9a0573382481841215f4f01dc8 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Fri, 9 Oct 2015 14:55:19 +0200 Subject: [PATCH 13/31] check if ChangesetRequest granularity is a number (#2796) --- src/node/handler/PadMessageHandler.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 91fa37e43..13470b52f 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1365,6 +1365,11 @@ function handleChangesetRequest(client, message) messageLogger.warn("Dropped message, changeset request has no granularity!"); return; } + if(Number(message.data.granularity) !== message.data.granularity || message.data.granularity % 1 !== 0) + { + messageLogger.warn("Dropped message, changeset request granularity is not an integer!"); + return; + } if(message.data.start == null) { messageLogger.warn("Dropped message, changeset request has no start!"); From 1ee1f818dbe29e09a4733d0248663e52451e90dc Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Fri, 9 Oct 2015 14:55:59 +0200 Subject: [PATCH 14/31] if granularity is negative, endNum is negative and loop --- src/node/handler/PadMessageHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 13470b52f..6515e17e1 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1365,7 +1365,8 @@ function handleChangesetRequest(client, message) messageLogger.warn("Dropped message, changeset request has no granularity!"); return; } - if(Number(message.data.granularity) !== message.data.granularity || message.data.granularity % 1 !== 0) + //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; From 5deb06d5891ce9278b74c253fd85741757b25c82 Mon Sep 17 00:00:00 2001 From: Luiza Pagliari Date: Tue, 13 Oct 2015 18:39:23 -0300 Subject: [PATCH 15/31] Create setting to control if a new line will be indented or not Currently pressing ENTER on a line that ends with ':', '[', '(' or '{' automaticaly indents the new line with 4 spaces. The variable added by this commit to settings.json allow an Etherpad instance to not have this behavior. --- settings.json.template | 5 ++ src/node/handler/PadMessageHandler.js | 7 +- src/node/utils/Settings.js | 5 ++ src/static/js/ace2_inner.js | 40 +++++----- tests/frontend/specs/indentation.js | 102 +++++++++++++++++++++++--- 5 files changed, 128 insertions(+), 31 deletions(-) diff --git a/settings.json.template b/settings.json.template index bfd0c7e66..06d388d8e 100644 --- a/settings.json.template +++ b/settings.json.template @@ -131,6 +131,11 @@ // Allow Load Testing tools to hit the Etherpad Instance. Warning this will disable security on the instance. "loadTest": false, + // Disable indentation on new line when previous line ends with some special chars (':', '[', '(', '{') + /* + "indentationOnNewLine": false, + */ + /* The toolbar buttons configuration. "toolbar": { "left": [ diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index e72625d06..9481889f3 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -630,8 +630,8 @@ function handleUserChanges(data, cb) messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); return cb(); } - //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 + //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 //if the session was valid when the message arrived in the first place if(!sessioninfos[client.id]) { @@ -960,7 +960,7 @@ function handleSwitchToPad(client, message) roomClients[i].leave(padId); } } - + // start up the new pad createSessionInfo(client, message); handleClientReady(client, message); @@ -1231,6 +1231,7 @@ function handleClientReady(client, message) "plugins": plugins.plugins, "parts": plugins.parts, }, + "indentationOnNewLine": settings.indentationOnNewLine, "initialChangesets": [] // FIXME: REMOVE THIS SHIT } diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 2c2f90bf8..1a60860f2 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -177,6 +177,11 @@ exports.disableIPlogging = false; */ exports.loadTest = false; +/** + * Enable indentation on new lines + */ +exports.indentationOnNewLine = true; + /* * log4js appender configuration */ diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index d1657a7c4..bb85adcfa 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1894,7 +1894,11 @@ function Ace2Inner(){ var prevLine = rep.lines.prev(thisLine); var prevLineText = prevLine.text; 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( rep.lines.offsetOfIndex(lineNum), lineNum).insert( theIndent, [ @@ -2336,7 +2340,7 @@ function Ace2Inner(){ function getAttributeOnSelection(attributeName){ if (!(rep.selStart && rep.selEnd)) return - + var withIt = Changeset.makeAttribsString('+', [ [attributeName, 'true'] ], rep.apool); @@ -2347,14 +2351,14 @@ function Ace2Inner(){ } return rangeHasAttrib(rep.selStart, rep.selEnd) - + function rangeHasAttrib(selStart, selEnd) { // if range is collapsed -> no attribs in range if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false - + if(selStart[0] != selEnd[0]) { // -> More than one line selected var hasAttrib = true - + // from selStart to the end of the first line hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]) @@ -2365,22 +2369,22 @@ function Ace2Inner(){ // for the last, potentially partial, line hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]) - + return hasAttrib } - + // Logic tells us we now have a range on a single line - + var lineNum = selStart[0] , start = selStart[1] , end = selEnd[1] , hasAttrib = true - + // Iterate over attribs on this line - + var opIter = Changeset.opIterator(rep.alines[lineNum]) , indexIntoLine = 0 - + while (opIter.hasNext()) { var op = opIter.next(); var opStartInLine = indexIntoLine; @@ -2394,11 +2398,11 @@ function Ace2Inner(){ } indexIntoLine = opEndInLine; } - + return hasAttrib } } - + editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection; function toggleAttributeOnSelection(attributeName) @@ -3641,7 +3645,7 @@ function Ace2Inner(){ // Is caret potentially hidden by the chat button? var myselection = document.getSelection(); // get the current caret selection var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 - + if(myselection.focusNode.wholeText){ // Is there any content? If not lineHeight will report wrong.. var lineHeight = myselection.focusNode.parentNode.offsetHeight; // line height of populated links }else{ @@ -3713,13 +3717,13 @@ function Ace2Inner(){ // As ubuntu cannot use Alt F10.... // Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it) var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); - $(this).blur(); + $(this).blur(); firstEditbarElement.focus(); evt.preventDefault(); } if ((!specialHandled) && altKey && keyCode == 67 && type === "keydown"){ // Alt c focuses on the Chat window - $(this).blur(); + $(this).blur(); parent.parent.chat.show(); parent.parent.$("#chatinput").focus(); evt.preventDefault(); @@ -4961,7 +4965,7 @@ function Ace2Inner(){ // Disabled: https://github.com/ether/etherpad-lite/issues/2546 // Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533 - // $(document).on("cut", handleCut); + // $(document).on("cut", handleCut); $(root).on("blur", handleBlur); if (browser.msie) @@ -4972,7 +4976,7 @@ function Ace2Inner(){ // Don't paste on middle click of links $(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 // here.. :( if(e.target.a || e.target.localName === "a"){ diff --git a/tests/frontend/specs/indentation.js b/tests/frontend/specs/indentation.js index 8e851d873..71aafc7ac 100644 --- a/tests/frontend/specs/indentation.js +++ b/tests/frontend/specs/indentation.js @@ -15,7 +15,7 @@ describe("indentation button", function(){ //select this text element $firstTextElement.sendkeys('{selectall}'); - if(inner$(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"; }else{ var evtType = "keydown"; @@ -31,7 +31,7 @@ describe("indentation button", function(){ }); it("indent text with button", function(done){ - var inner$ = helper.padInner$; + var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; 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){ - var inner$ = helper.padInner$; + var inner$ = helper.padInner$; var chrome$ = helper.padChrome$; var $indentButton = chrome$(".buttonicon-indent"); @@ -51,9 +51,9 @@ describe("indentation button", function(){ //type a bit, make a line break and type again var $firstTextElement = inner$("div span").first(); - $firstTextElement.sendkeys('line 1'); - $firstTextElement.sendkeys('{enter}'); - $firstTextElement.sendkeys('line 2'); + $firstTextElement.sendkeys('line 1'); + $firstTextElement.sendkeys('{enter}'); + $firstTextElement.sendkeys('line 2'); $firstTextElement.sendkeys('{enter}'); helper.waitFor(function(){ @@ -68,13 +68,83 @@ 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(){ + return inner$("div span").first().text().indexOf("line with '{'") === -1; + }).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(){ + return inner$("div span").first().text().indexOf("line 2") === -1; + }).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() { //get the inner iframe var $inner = testHelper.$getPadInner(); - + //get the first text element out of the inner iframe var firstTextElement = $inner.find("div").first(); @@ -87,7 +157,7 @@ describe("indentation button", function(){ //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(); - + // is there a list-indent class element now? var firstChild = newFirstTextElement.children(":first"); var isUL = firstChild.is('ul'); @@ -160,12 +230,12 @@ describe("indentation button", function(){ /* this test creates the below content, both should have double indentation line1 line2 - + firstTextElement.sendkeys('{rightarrow}'); // simulate a keypress of enter firstTextElement.sendkeys('{enter}'); // simulate a keypress of enter 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 //get the second text element out of the inner iframe @@ -203,3 +273,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); +} From 0ab3f3133e4518310ad922f2849399f6993f6703 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Wed, 14 Oct 2015 12:54:49 +0200 Subject: [PATCH 16/31] Localisation updates from https://translatewiki.net. --- src/locales/ar.json | 6 +++++- src/locales/es.json | 2 +- src/locales/pa.json | 23 +++++++++++++++-------- src/locales/ro.json | 14 ++++++++------ src/locales/sq.json | 2 +- src/locales/zh-hans.json | 2 +- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/locales/ar.json b/src/locales/ar.json index 153faae82..33c6beb3f 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -6,7 +6,8 @@ "Alami", "Meno25", "Test Create account", - "محمد أحمد عبد الفتاح" + "محمد أحمد عبد الفتاح", + "Haytham morsy" ] }, "index.newPad": "باد جديد", @@ -97,6 +98,9 @@ "timeslider.exportCurrent": "تصدير النسخة الحالية ك:", "timeslider.version": "إصدار {{version}}", "timeslider.saved": "محفوظ {{month}} {{day}}, {{year}}", + "timeslider.playPause": "تشغيل / إيقاف مؤقت محتويات الباد", + "timeslider.backRevision": "عد إلى مراجعة في هذه الباد", + "timeslider.forwardRevision": "انطلق إلى مراجعة في هذه الباد", "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "يناير", "timeslider.month.february": "فبراير", diff --git a/src/locales/es.json b/src/locales/es.json index c62cacc47..b26590630 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -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.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.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.copypaste": "Intenta copiar y pegar", "pad.impexp.exportdisabled": "La exportación al formato {{type}} está desactivada. Contacta a tu administrador de sistemas." diff --git a/src/locales/pa.json b/src/locales/pa.json index 9e154e36d..531e4ac8e 100644 --- a/src/locales/pa.json +++ b/src/locales/pa.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "Aalam", - "Babanwalia" + "Babanwalia", + "ਪ੍ਰਚਾਰਕ" ] }, "index.newPad": "ਨਵਾਂ ਪੈਡ", @@ -10,29 +11,31 @@ "pad.toolbar.bold.title": "ਗੂੜ੍ਹਾ (Ctrl-B)", "pad.toolbar.italic.title": "ਤਿਰਛਾ (Ctrl-I)", "pad.toolbar.underline.title": "ਹੇਠਾਂ-ਰੇਖਾ (Ctrl-U)", - "pad.toolbar.strikethrough.title": "ਵਿੰਨ੍ਹੋ ਵਿਨੋ", - "pad.toolbar.ol.title": "ਲੜੀਵਾਰ ਲਿਸਟ", + "pad.toolbar.strikethrough.title": "ਵਿੰਨ੍ਹੋ (Ctrl+5)", + "pad.toolbar.ol.title": "ਲੜੀਵਾਰ ਸੂਚੀ", "pad.toolbar.ul.title": "ਬਿਨ-ਲੜੀਬੱਧ ਸੂਚੀ", "pad.toolbar.indent.title": "ਹਾਸ਼ੀਏ ਤੋਂ ਪਰ੍ਹੇ (ਟੈਬ)", "pad.toolbar.unindent.title": "ਹਾਸ਼ੀਏ ਵੱਲ (ਸ਼ਿਫ਼ਟ+ਟੈਬ)", "pad.toolbar.undo.title": "ਵਾਪਸ (Ctrl-Z)", "pad.toolbar.redo.title": "ਪਰਤਾਓ (Ctrl-Y)", - "pad.toolbar.clearAuthorship.title": "ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰੋ", + "pad.toolbar.clearAuthorship.title": "ਪਰਮਾਣਕਿਤਾ ਰੰਗ ਸਾਫ਼ ਕਰੋ (Ctrl+Shift+C)", "pad.toolbar.import_export.title": "ਵੱਖ-ਵੱਖ ਫਾਇਲ ਫਾਰਮੈਟ ਤੋਂ/ਵਿੱਚ ਇੰਪੋਰਟ/ਐਕਸਪੋਰਟ ਕਰੋ", "pad.toolbar.timeslider.title": "ਸਮਾਂ-ਲਕੀਰ", "pad.toolbar.savedRevision.title": "ਰੀਵਿਜ਼ਨ ਸੰਭਾਲੋ", "pad.toolbar.settings.title": "ਸੈਟਿੰਗ", "pad.toolbar.embed.title": "ਇਹ ਪੈਡ ਸਾਂਝਾ ਤੇ ਇੰਬੈੱਡ ਕਰੋ", "pad.toolbar.showusers.title": "ਇਹ ਪੈਡ ਉੱਤੇ ਯੂਜ਼ਰ ਵੇਖਾਓ", - "pad.colorpicker.save": "ਸਾਂਭੋ", + "pad.colorpicker.save": "ਸੰਭਾਲੋ", "pad.colorpicker.cancel": "ਰੱਦ ਕਰੋ", "pad.loading": "…ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ", + "pad.noCookie": "ਕੂਕੀਜ਼ ਨਹੀਂ ਲੱਭੀਅਾਂ। ਕਿਰਪਾ ਕਰਕੇ ਬ੍ਰਾੳੂਜ਼ਰ ਵਿੱਚ ਕੂਕੀਜ਼ ਲਾਗੂ ਕਰੋ।", "pad.passwordRequired": "ਇਹ ਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਤੁਹਾਨੂੰ ਪਾਸਵਰਡ ਚਾਹੀਦਾ ਹੈ", "pad.permissionDenied": "ਇਹ ਪੈਡ ਵਰਤਨ ਲਈ ਤੁਹਾਨੂੰ ਅਧਿਕਾਰ ਨਹੀਂ ਹਨ", "pad.wrongPassword": "ਤੁਹਾਡਾ ਪਾਸਵਰਡ ਗਲਤੀ ਸੀ", "pad.settings.padSettings": "ਪੈਡ ਸੈਟਿੰਗ", "pad.settings.myView": "ਮੇਰੀ ਝਲਕ", "pad.settings.stickychat": "ਹਮੇਸ਼ਾ ਸਕਰੀਨ ਉੱਤੇ ਗੱਲ ਕਰੋ", + "pad.settings.chatandusers": "ਗੱਲ-ਬਾਤ ਅਤੇ ਵਰਤੋਂਕਾਰ ਦਿਖਾਵੋ", "pad.settings.colorcheck": "ਲੇਖਕੀ ਰੰਗ", "pad.settings.linenocheck": "ਲਾਈਨ ਨੰਬਰ", "pad.settings.rtlcheck": "ਸਮੱਗਰੀ ਸੱਜੇ ਤੋਂ ਖੱਬੇ ਪੜ੍ਹਨੀ ਹੈ?", @@ -45,10 +48,11 @@ "pad.importExport.import": "ਕੋਈ ਵੀ ਟੈਕਸਟ ਫਾਇਲ ਜਾਂ ਦਸਤਾਵੇਜ਼ ਅੱਪਲੋਡ ਕਰੋ", "pad.importExport.importSuccessful": "ਸਫ਼ਲ!", "pad.importExport.export": "ਮੌਜੂਦਾ ਪੈਡ ਨੂੰ ਐਕਸਪੋਰਟ ਕਰੋ:", + "pad.importExport.exportetherpad": "ੲੈਥਰਪੈਡ", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "ਸਧਾਰਨ ਟੈਕਸਟ", "pad.importExport.exportword": "ਮਾਈਕਰੋਸਾਫਟ ਵਰਡ", - "pad.importExport.exportpdf": "ਪੀਡੀਐਫ", + "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (ਓਪਨ ਡੌਕੂਮੈਂਟ ਫਾਰਮੈਟ)", "pad.importExport.abiword.innerHTML": "ਤੁਸੀਂ ਸਿਰਫ਼ ਸਾਦੀਆਂ ਲਿਖਤੀ ਜਾਂ ਐੱਚ.ਟੀ.ਐੱਮ.ਐੱਲ. ਰੂਪ-ਰੇਖਾਵਾਂ ਤੋਂ ਦਰਾਮਦ ਕਰ ਸਕਦੇ ਹੋ। ਹੋਰ ਉੱਨਤ ਦਰਾਮਦੀ ਗੁਣਾਂ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਐਬੀਵਰਡ ਥਾਪੋ।", "pad.modals.connected": "ਕੁਨੈਕਟ ਹੈ।", @@ -88,8 +92,11 @@ "timeslider.toolbar.authorsList": "ਕੋਈ ਲੇਖਕ ਨਹੀਂ", "timeslider.toolbar.exportlink.title": "ਐਕਸਪੋਰਟ", "timeslider.exportCurrent": "ਮੌਜੂਦਾ ਵਰਜਨ ਇੰਝ ਐਕਸਪੋਰਟ ਕਰੋ:", - "timeslider.version": "ਵਰਜਨ {{version}}", + "timeslider.version": "ਵਰਜ਼ਨ {{version}}", "timeslider.saved": "{{day}} {{month}} {{year}} ਨੂੰ ਸੰਭਾਲਿਆ", + "timeslider.playPause": "ਪੈਡ ਸਮੱਗਰੀ ਚਲਾਓ / ਵਿਰਾਮ ਕਰੋ", + "timeslider.backRevision": "ਇਸ ਪੈਡ ਵਿੱਚ ਪਿਛਲੇ ਰੀਵਿਜ਼ਨ ਤੇ ਜਾਓ", + "timeslider.forwardRevision": "ਇਸ ਪੈਡ ਵਿੱਚ ਅਗਲੇ ਰੀਵਿਜ਼ਨ ਤੇ ਜਾਓ", "timeslider.dateformat": "{{day}}/{{month}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "ਜਨਵਰੀ", "timeslider.month.february": "ਫ਼ਰਵਰੀ", @@ -118,5 +125,5 @@ "pad.impexp.uploadFailed": "ਅੱਪਲੋਡ ਲਈ ਫੇਲ੍ਹ ਹੈ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।", "pad.impexp.importfailed": "ਇੰਪੋਰਟ ਫੇਲ੍ਹ ਹੈ", "pad.impexp.copypaste": "ਕਾਪੀ ਕਰੋ ਚੇਪੋ ਜੀ", - "pad.impexp.exportdisabled": "{{type}} ਰੂਪ-ਰੇਖਾ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਮਿਹਰਬਾਨੀ ਕਰਕੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪ੍ਰਬੰਧਕ ਨਾਲ਼ ਰਾਬਤਾ ਬਣਾਉ।" + "pad.impexp.exportdisabled": "{{type}} ਫਾਰਮੈਟ ਵਜੋਂ ਬਰਾਮਦ ਕਰਨਾ ਬੰਦ ਹੈ। ਵੇਰਵੇ ਵਾਸਤੇ ਆਪਣੇ ਸਿਸਟਮ ਦੇ ਪਰਬੰਧਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।" } diff --git a/src/locales/ro.json b/src/locales/ro.json index ce38d3f9b..a73bfafc3 100644 --- a/src/locales/ro.json +++ b/src/locales/ro.json @@ -4,7 +4,8 @@ "Hedwig", "ImGelu", "Minisarm", - "Strainu" + "Strainu", + "Wintereu" ] }, "index.newPad": "Pad nou", @@ -21,6 +22,7 @@ "pad.toolbar.import_export.title": "Importă/Exportă din/în diferite formate", "pad.toolbar.savedRevision.title": "Salvează revizia", "pad.toolbar.settings.title": "Setări", + "pad.toolbar.showusers.title": "Arată utilizatorii de pe acest pad", "pad.colorpicker.save": "Salvează", "pad.colorpicker.cancel": "Anulează", "pad.loading": "Se încarcă...", @@ -46,10 +48,10 @@ "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", "pad.modals.connected": "Conectat.", - "pad.modals.reconnecting": "Se reconectează la pad-ul tău..", + "pad.modals.reconnecting": "Se reconectează la pad-ul dumneavoastră..", "pad.modals.forcereconnect": "Forțează reconectarea", "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.initsocketfail": "Serverul nu este disponibil.", "pad.modals.initsocketfail.explanation": "Nu s-a putut conecta la serverul de sincronizare.", @@ -61,12 +63,12 @@ "pad.share": "Distribuie acest pad", "pad.share.readonly": "Doar în citire", "pad.share.link": "Legătură", - "pad.share.emebdcode": "Încorporează URL-ul", + "pad.share.emebdcode": "Adresa URL încorporată", "pad.chat": "Chat", "pad.chat.title": "Deschide chat-ul pentru acest pad.", "pad.chat.loadmessages": "Încarcă mai multe mesaje", "timeslider.toolbar.returnbutton": "Înapoi la pad", - "timeslider.toolbar.authors": "Aurori:", + "timeslider.toolbar.authors": "Autori:", "timeslider.toolbar.authorsList": "Niciun autor", "timeslider.toolbar.exportlink.title": "Exportă", "timeslider.exportCurrent": "Exportă versiunea curentă ca:", @@ -85,7 +87,7 @@ "timeslider.month.october": "octombrie", "timeslider.month.november": "noiembrie", "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.guest": "Oaspete", "pad.userlist.deny": "Respinge", diff --git a/src/locales/sq.json b/src/locales/sq.json index a6b3d813f..506031cc8 100644 --- a/src/locales/sq.json +++ b/src/locales/sq.json @@ -58,7 +58,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.advice": "Rilidhuni që të përdoret kjo dritare.", "pad.modals.unauth": "I paautorizuar", - "pad.modals.unauth.explanation": "Ndërkohë që shihnit këtë dritare, lejet tuaja kanë ndryshuar. Provoni të rilidheni.", + "pad.modals.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.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.", diff --git a/src/locales/zh-hans.json b/src/locales/zh-hans.json index 080fb62df..15f115912 100644 --- a/src/locales/zh-hans.json +++ b/src/locales/zh-hans.json @@ -82,7 +82,7 @@ "pad.modals.badChangeset.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。如果您认为这是错误,请联系服务管理员。要继续编辑,请尝试重新连接。", "pad.modals.corruptPad.explanation": "您试图连接的记事本已损坏。", "pad.modals.corruptPad.cause": "这可能是因为服务器配置的错误或者其他未预料到的行为。请联系服务管理员。", - "pad.modals.deleted": "已刪除。", + "pad.modals.deleted": "已删除。", "pad.modals.deleted.explanation": "此记事本已被移除。", "pad.modals.disconnected": "您已断开连接。", "pad.modals.disconnected.explanation": "到服务器的连接已丢失", From 881996ef52880437b8194e89e4460e4dc72b3829 Mon Sep 17 00:00:00 2001 From: Luke Rogers Date: Thu, 15 Oct 2015 14:02:40 +1300 Subject: [PATCH 17/31] Pedantic Space <3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ad3587c2..cc90800f1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # About 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) 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. From a675659dc28b95f8ef5664ffe5d82cd7cf2b2577 Mon Sep 17 00:00:00 2001 From: Ted Mielczarek Date: Mon, 19 Oct 2015 12:58:47 -0400 Subject: [PATCH 18/31] Add an appendText API --- src/node/db/API.js | 32 ++++++++++++++++++++ src/node/db/Pad.js | 13 +++++++++ src/node/handler/APIHandler.js | 53 +++++++++++++++++++++++++++++++++- tests/backend/specs/api/pad.js | 31 ++++++++++++++++++-- 4 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/node/db/API.js b/src/node/db/API.js index 87b6d7473..237bcb0a7 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -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 diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index eb6a3ed1b..83e15e6df 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -303,6 +303,19 @@ Pad.prototype.setText = function setText(newText) { 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) { this.chatHead++; //save the chat entry in the database diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index b4d242011..179c2b404 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -444,10 +444,61 @@ var version = , "getChatHead" : ["padID"] , "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 -exports.latestApiVersion = '1.2.12'; +exports.latestApiVersion = '1.2.13'; // exports the versions so it can be used by the new Swagger endpoint exports.version = version; diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 14e990911..e04033c04 100644 --- a/tests/backend/specs/api/pad.js +++ b/tests/backend/specs/api/pad.js @@ -79,6 +79,8 @@ describe('Permission', function(){ -> movePad(newPadID, originalPadId) -- Should provide consistant pad data -> getText(originalPadId) -- Should be "hello world" -> getLastEdited(padID) -- Should not be 0 + -> appendText(padID, "hello") + -> getText(padID) -- Should be "hello worldhello" -> setHTML(padID) -- Should fail on invalid HTML -> setHTML(padID) *3 -- Should fail on invalid 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(){ it('Sets the HTML of a Pad attempting to pass ugly HTML', function(done) { var html = "
Hello HTML
"; @@ -542,8 +568,9 @@ describe('createPad', function(){ */ -var endPoint = function(point){ - return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey; +var endPoint = function(point, version){ + version = version || apiVersion; + return '/api/'+version+'/'+point+'?apikey='+apiKey; } function makeid() From 2bfc3026d2e549064bd13e496d407169b2755df5 Mon Sep 17 00:00:00 2001 From: Simon Gaeremynck Date: Tue, 20 Oct 2015 19:46:08 +0100 Subject: [PATCH 19/31] Allow LibreOffice to be used when exporting a pad This commit adds support for LibreOffice when exporting a pad to doc, pdf, .. This commit also cleans up some export logic when exporting to txt --- settings.json.template | 6 +- src/node/handler/ExportHandler.js | 80 +++++--------------------- src/node/utils/ExportHtml.js | 31 ++++++----- src/node/utils/LibreOffice.js | 93 +++++++++++++++++++++++++++++++ src/node/utils/Settings.js | 5 ++ 5 files changed, 133 insertions(+), 82 deletions(-) create mode 100644 src/node/utils/LibreOffice.js diff --git a/settings.json.template b/settings.json.template index bfd0c7e66..321d21d6b 100644 --- a/settings.json.template +++ b/settings.json.template @@ -86,10 +86,14 @@ may cause problems during deployment. Set to 0 to disable caching */ "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" : 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. Tidy is used to improve the quality of exported pads*/ "tidyHtml" : null, diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index f861c82eb..0a8089775 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -30,9 +30,15 @@ var os = require('os'); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var TidyHtml = require('../utils/TidyHtml'); +var convertor = null; + //load abiword only if its enabled 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"; @@ -70,71 +76,11 @@ exports.doExport = function(req, res, padId, type) } else if(type == "txt") { - var 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) + exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, txt) { - if(err && err != "stop") ERR(err); - }) + if(ERR(err)) return; + res.send(txt); + }); } else { @@ -183,11 +129,11 @@ exports.doExport = function(req, res, padId, type) TidyHtml.tidy(srcFile, callback); }, - //send the convert job to abiword + //send the convert job to the convertor (abiword, libreoffice, ..) function(callback) { destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; - abiword.convertFile(srcFile, destFile, type, callback); + convertor.convertFile(srcFile, destFile, type, callback); }, //send the file function(callback) diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 53469c9b9..fef2508c5 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -123,8 +123,8 @@ function getHTMLFromAtext(pad, atext, authorColors) var newLength = props.push(propName); anumMap[a] = newLength -1; - css+=".removed {text-decoration: line-through; " + - "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+ + css+=".removed {text-decoration: line-through; " + + "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+ "filter: alpha(opacity=80); "+ "opacity: 0.8; "+ "}\n"; @@ -287,7 +287,7 @@ function getHTMLFromAtext(pad, atext, authorColors) 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 s = s.replace(String.fromCharCode(12), ""); @@ -401,7 +401,7 @@ function getHTMLFromAtext(pad, atext, authorColors) pieces.push('

'); } }*/ - 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 if( toClose > 0){ @@ -455,7 +455,7 @@ function getHTMLFromAtext(pad, atext, authorColors) } } } - + for (var k = lists.length - 1; k >= 0; k--) { if(lists[k][1] == "number") @@ -484,14 +484,17 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) stylesForExportCSS += css; }); // Core inclusion of head etc. - var head = - (noDocType ? '' : '\n') + - '\n' + (noDocType ? '' : '\n' + + var head = + (noDocType ? '' : '\n') + + '\n' + (noDocType ? '' : '\n' + '' + Security.escapeHTML(padId) + '\n' + - '\n' + - '\n' + '\n') + + stylesForExportCSS + + '\n' + '\n') + ''; var foot = '\n\n'; diff --git a/src/node/utils/LibreOffice.js b/src/node/utils/LibreOffice.js new file mode 100644 index 000000000..415772456 --- /dev/null +++ b/src/node/utils/LibreOffice.js @@ -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); + }); +} diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 2c2f90bf8..d03e2a6cd 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -152,6 +152,11 @@ exports.minify = true; */ exports.abiword = null; +/** + * The path of the libreoffice executable + */ +exports.soffice = null; + /** * The path of the tidy executable */ From 82c7ca4ac3531da85d0a618b1d386fcc9b804b21 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 22 Oct 2015 01:32:02 +0100 Subject: [PATCH 20/31] add classes for bootstrap targeting, pew pew --- src/static/js/ace.js | 4 ++-- src/static/js/ace2_inner.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/static/js/ace.js b/src/static/js/ace.js index c446939a3..455bfaa36 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -265,7 +265,7 @@ plugins.ensure(function () {\n\ iframeHTML: iframeHTML }); - iframeHTML.push(' '); + iframeHTML.push(' '); // Expose myself to global for my child frame. 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 // (throbs busy while typing) - outerHTML.push('', '', scriptTag(outerScript), '
x
'); + outerHTML.push('', '', scriptTag(outerScript), '
x
'); var outerFrame = document.createElement("IFRAME"); outerFrame.name = "ace_outer"; diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index d1657a7c4..00dcd4e9f 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -5347,8 +5347,9 @@ function Ace2Inner(){ function initLineNumbers() { lineNumbersShown = 1; - sideDiv.innerHTML = '
1
'; + sideDiv.innerHTML = '
1
'; sideDivInner = outerWin.document.getElementById("sidedivinner"); + $(sideDiv).addClass("sidediv"); } function updateLineNumbers() From f57aaa62fc310d21b1d2e1477b308cf9853157ae Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 22 Oct 2015 11:32:46 +0200 Subject: [PATCH 21/31] Localisation updates from https://translatewiki.net. --- src/locales/olo.json | 5 +++-- src/locales/sq.json | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/locales/olo.json b/src/locales/olo.json index b65e8ebb2..e8db16f7f 100644 --- a/src/locales/olo.json +++ b/src/locales/olo.json @@ -2,7 +2,8 @@ "@metadata": { "authors": [ "Denö", - "Mashoi7" + "Mashoi7", + "Ilja.mos" ] }, "pad.toolbar.underline.title": "Alleviivua (Ctrl+U)", @@ -36,7 +37,7 @@ "timeslider.month.january": "pakkaskuudu", "timeslider.month.february": "tuhukuudu", "timeslider.month.march": "kevätkuudu", - "timeslider.month.april": "kevätkuudu", + "timeslider.month.april": "sulakuudu", "timeslider.month.may": "oraskuudu", "timeslider.month.june": "kezäkuudu", "timeslider.month.july": "heinykuudu", diff --git a/src/locales/sq.json b/src/locales/sq.json index 506031cc8..a465cfaa2 100644 --- a/src/locales/sq.json +++ b/src/locales/sq.json @@ -5,7 +5,7 @@ "Kosovastar" ] }, - "index.newPad": "Bllok i Ri", + "index.newPad": "Bllok i ri", "index.createOpenPad": "ose krijoni/hapni një Bllok me emrin:", "pad.toolbar.bold.title": "Të trasha (Ctrl-B)", "pad.toolbar.italic.title": "Të pjerrëta (Ctrl-I)", @@ -13,7 +13,7 @@ "pad.toolbar.strikethrough.title": "Hequrvije (Ctrl+5)", "pad.toolbar.ol.title": "Listë e renditur (Ctrl+Shift+N)", "pad.toolbar.ul.title": "Listë e parenditur (Ctrl+Shift+L)", - "pad.toolbar.indent.title": "Brendazi (TAB)", + "pad.toolbar.indent.title": "E dhëmbëzuar (TAB)", "pad.toolbar.unindent.title": "Jashtazi (Shift+TAB)", "pad.toolbar.undo.title": "Zhbëje (Ctrl-Z)", "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.timeslider.title": "Rrjedha kohore", "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.showusers.title": "Shfaq përdoruesit në këtë bllok", "pad.colorpicker.save": "Ruaje", @@ -45,6 +45,7 @@ "pad.importExport.import": "Ngarkoni cilëndo kartelë teksti ose dokument", "pad.importExport.importSuccessful": "Me sukses!", "pad.importExport.export": "Eksportojeni bllokun e tanishëm si:", + "pad.importExport.exportetherpad": "Etherpad", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Tekst të thjeshtë", "pad.importExport.exportword": "Microsoft Word", @@ -90,6 +91,7 @@ "timeslider.exportCurrent": "Eksportojeni versionin e tanishëm si:", "timeslider.version": "Versioni {{version}}", "timeslider.saved": "Ruajtur më {{month}} {{day}}, {{year}}", + "timeslider.playPause": "Luaj përmbajtjet e Pad / Pauzo", "timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "Janar", "timeslider.month.february": "Shkurt", From 7289e8907028520fcc16d31b67827d44054145e6 Mon Sep 17 00:00:00 2001 From: Simon Gaeremynck Date: Thu, 22 Oct 2015 16:09:44 +0100 Subject: [PATCH 22/31] Use `Etherpad` rather than `Etherpad Lite` when exporting HTML --- src/node/utils/ExportHtml.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index fef2508c5..ffc7bc58c 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -488,9 +488,9 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) (noDocType ? '' : '\n') + '\n' + (noDocType ? '' : '\n' + '' + Security.escapeHTML(padId) + '\n' + - '\n' + - '\n' + - '\n' + + '\n' + + '\n' + + '\n' + '\n' + '