From 1451eecaf077a62b76ea16f19f69cb613c8b3eba Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 31 Dec 2014 19:23:09 +0100 Subject: [PATCH 01/20] Re-implement ace_getAttributeOnSelection --- src/static/js/ace2_inner.js | 109 +++++++++++++++--------------------- 1 file changed, 44 insertions(+), 65 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index f1fc11600..d1b3131e9 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -2311,93 +2311,72 @@ function Ace2Inner(){ } editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection; + function getAttributeOnSelection(attributeName){ - if (!(rep.selStart && rep.selEnd)) return; - - // get the previous/next characters formatting when we have nothing selected - // To fix this we just change the focus area, we don't actually check anything yet. - if(rep.selStart[1] == rep.selEnd[1]){ - // if we're at the beginning of a line bump end forward so we get the right attribute - if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){ - rep.selEnd[1] = 1; - } - if(rep.selStart[1] < 0){ - rep.selStart[1] = 0; - } - var line = rep.lines.atIndex(rep.selStart[0]); - // if we're at the end of the line bmp the start back 1 so we get hte attribute - if(rep.selEnd[1] == line.text.length){ - rep.selStart[1] = rep.selStart[1] -1; - } - } - - // Do the detection - var selectionAllHasIt = true; + if (!(rep.selStart && rep.selEnd)) return + var withIt = Changeset.makeAttribsString('+', [ [attributeName, 'true'] ], rep.apool); var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); - function hasIt(attribs) { return withItRegex.test(attribs); } - var selStartLine = rep.selStart[0]; - var selEndLine = rep.selEnd[0]; - for (var n = selStartLine; n <= selEndLine; n++) - { - var opIter = Changeset.opIterator(rep.alines[n]); - var indexIntoLine = 0; - var selectionStartInLine = 0; - var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline - if(rep.lines.atIndex(n).text.length == 0){ - return false; // If the line length is 0 we basically treat it as having no formatting + 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]) + + // for all lines in between + for(var n=selStart[0]+1; n < selEnd[0]; n++) { + hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]) + } + + // for the last, potentially partial, line + hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]) + + return hasAttrib } - if(rep.selStart[1] == rep.selEnd[1] && rep.selStart[1] == rep.lines.atIndex(n).text.length){ - return false; // If we're at the end of a line we treat it as having no formatting - } - if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){ - rep.selEnd[1] == 1; - } - if(rep.selEnd[1] == -1){ - rep.selEnd[1] = 1; // sometimes rep.selEnd is -1, not sure why.. When it is we should look at the first char - } - if (n == selStartLine) - { - selectionStartInLine = rep.selStart[1]; - } - if (n == selEndLine) - { - selectionEndInLine = rep.selEnd[1]; - } - while (opIter.hasNext()) - { + + // 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; var opEndInLine = opStartInLine + op.chars; - if (!hasIt(op.attribs)) - { + if (!hasIt(op.attribs)) { // does op overlap selection? - if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) - { - selectionAllHasIt = false; + if (!(opEndInLine <= start || opStartInLine >= end)) { + hasAttrib = false; // since it's overlapping but hasn't got the attrib -> range hasn't got it break; } } indexIntoLine = opEndInLine; } - if (!selectionAllHasIt) - { - break; - } - } - if(selectionAllHasIt){ - return true; - }else{ - return false; + + return hasAttrib } } + editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection; function toggleAttributeOnSelection(attributeName) From b8af62fdef791e3709a2ebc0443046a4416e1b42 Mon Sep 17 00:00:00 2001 From: Gabriel Liwerant Date: Tue, 20 Jan 2015 10:53:52 -0500 Subject: [PATCH 02/20] Add subdirectory installation instructions Out of the box, etherpad-lite does not work correctly if cloned/installed into a subdirectory within an existing project. To do so, a few minor tweaks to the installation process are necessary which are documented here. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 0cddb0b0f..63916ba77 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ Now, run `start.bat` and open in your browser. Update to the latest version with `git pull origin`, then run `bin\installOnWindows.bat`, again. +If cloning to a subdirectory within another project, you may need to do the following: + +1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js]`) +2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`) +3. Add auto-generated files to the main project `.gitignore` + [Next steps](#next-steps). ## GNU/Linux and other UNIX-like systems From 5dce72d419288969cfb82ce105da111aeac25b55 Mon Sep 17 00:00:00 2001 From: John McLear Date: Fri, 23 Jan 2015 01:47:12 +0000 Subject: [PATCH 03/20] chrome list handling fix for #2412 --- src/static/js/contentcollector.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index 1f0620fef..bb20c669d 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -554,7 +554,9 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas } else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/)) { - oldListTypeOrNull = (_enterList(state, type) || 'none'); + // This has undesirable behavior in Chrome but is right in other browsers. + // See https://github.com/ether/etherpad-lite/issues/2412 for reasoning + if(!abrowser.chrome) oldListTypeOrNull = (_enterList(state, type) || 'none'); } if (className2Author && cls) { From 7719117e1e1b0d4d7b1d3740329cbea92258c6f8 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Thu, 26 Feb 2015 14:57:49 +0100 Subject: [PATCH 04/20] do not crash when encountering mismatched compositions. log the changesets and padid --- src/node/handler/PadMessageHandler.js | 12 ++++++++---- src/static/js/Changeset.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 7ea5039d8..86244d85d 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1629,10 +1629,14 @@ function composePadChangesets(padId, startNum, endNum, callback) changeset = changesets[startNum]; var pool = pad.apool(); - for(var r=startNum+1;r Date: Mon, 2 Mar 2015 11:05:33 +0100 Subject: [PATCH 05/20] callback with argument error in async.series instead --- src/node/handler/PadMessageHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 86244d85d..e585b6528 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1636,7 +1636,7 @@ function composePadChangesets(padId, startNum, endNum, callback) } } catch(e){ console.warn("failed to compose cs in pad:",padId); - return; + return callback(e); } callback(null); From 0f82cd871141a4ca465e171dd4eb0fe6a73058f6 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Mon, 2 Mar 2015 11:14:24 +0100 Subject: [PATCH 06/20] print revision numbers - not changesets - in warn-log --- src/node/handler/PadMessageHandler.js | 3 ++- src/static/js/Changeset.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index e585b6528..ac8904f8d 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1635,7 +1635,8 @@ function composePadChangesets(padId, startNum, endNum, callback) changeset = Changeset.compose(changeset, cs, pool); } } catch(e){ - console.warn("failed to compose cs in pad:",padId); + // r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3 + console.warn("failed to compose cs in pad:",padId," startrev:",startNum," current rev:",r); return callback(e); } diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index c97756b8d..df180f9cf 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -1318,7 +1318,7 @@ exports.compose = function (cs1, cs2, pool) { var unpacked2 = exports.unpack(cs2); var len1 = unpacked1.oldLen; var len2 = unpacked1.newLen; - exports.assert(len2 == unpacked2.oldLen, "mismatched composition of two changesets - cs1:",cs1," and cs2:",cs2); + exports.assert(len2 == unpacked2.oldLen, "mismatched composition of two changesets"); var len3 = unpacked2.newLen; var bankIter1 = exports.stringIterator(unpacked1.charBank); var bankIter2 = exports.stringIterator(unpacked2.charBank); From 01cd82427a4e83e656f084923f8f06a1a61582c9 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Tue, 3 Mar 2015 15:20:33 +0100 Subject: [PATCH 07/20] check author in = operator --- src/node/handler/PadMessageHandler.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 7ea5039d8..7521c05c4 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -656,7 +656,12 @@ function handleUserChanges(data, cb) , op while(iterator.hasNext()) { op = iterator.next() - if(op.opcode != '+') continue; + + //+ can add text with attribs + //= can change or add attribs + //- can have attribs, but they are discarded and don't show up in the apool + if(op.opcode == '-') continue; + op.attribs.split('*').forEach(function(attr) { if(!attr) return attr = wireApool.getAttrib(attr) From 0693c0ae970627583f435a0e8a70ab4639b9a2f8 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Tue, 3 Mar 2015 15:37:56 +0100 Subject: [PATCH 08/20] - operator do not show up in the attribs of a pad, but authors could still leak to the pool --- src/node/handler/PadMessageHandler.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index 7521c05c4..ab81ad878 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -659,8 +659,7 @@ function handleUserChanges(data, cb) //+ can add text with attribs //= can change or add attribs - //- can have attribs, but they are discarded and don't show up in the apool - if(op.opcode == '-') continue; + //- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool op.attribs.split('*').forEach(function(attr) { if(!attr) return From 393a4e54e5517624f07299a436e623a34f8104e2 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Tue, 3 Mar 2015 16:17:39 +0100 Subject: [PATCH 09/20] recognize reconnect in clear_authorship_colors test --- tests/frontend/specs/clear_authorship_colors.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/frontend/specs/clear_authorship_colors.js b/tests/frontend/specs/clear_authorship_colors.js index 5db35612a..41fabe3cf 100644 --- a/tests/frontend/specs/clear_authorship_colors.js +++ b/tests/frontend/specs/clear_authorship_colors.js @@ -47,6 +47,10 @@ describe("clear authorship colors button", function(){ var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; expect(hasAuthorClass).to.be(false); + + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + expect(disconnectVisible).to.be(false); + done(); }); From f249b42ab43c45f255c098c0a9cc904e43b0305a Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Tue, 3 Mar 2015 16:39:14 +0100 Subject: [PATCH 10/20] empty author should be allowed to support clearAuthorship functionality --- 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 ab81ad878..ef8e32b0b 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -665,7 +665,8 @@ function handleUserChanges(data, cb) if(!attr) return attr = wireApool.getAttrib(attr) if(!attr) return - if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset); + //the empty author is used in the clearAuthorship functionality so this should be the only exception + if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset); }) } From 547046830e0808b86dcde94b4e8f6da9206ddb89 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Tue, 3 Mar 2015 16:51:18 +0100 Subject: [PATCH 11/20] actually disconnect should NOT be visible... --- tests/frontend/specs/clear_authorship_colors.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/frontend/specs/clear_authorship_colors.js b/tests/frontend/specs/clear_authorship_colors.js index 41fabe3cf..1417f63c6 100644 --- a/tests/frontend/specs/clear_authorship_colors.js +++ b/tests/frontend/specs/clear_authorship_colors.js @@ -47,9 +47,10 @@ describe("clear authorship colors button", function(){ var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1; expect(hasAuthorClass).to.be(false); - - var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 - expect(disconnectVisible).to.be(false); + setTimeout(function(){ + var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1 + expect(disconnectVisible).to.be(true); + },1000); done(); }); From da1bf00a78406894d3840c8c0370aeb1c9900a53 Mon Sep 17 00:00:00 2001 From: Cristo Date: Fri, 6 Mar 2015 23:02:31 +0100 Subject: [PATCH 12/20] fixed + support for value --- src/static/js/AttributeManager.js | 57 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 974d8ad91..fbfd6b309 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -153,37 +153,36 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ return this.applyChangeset(builder); }, - /* - Removes a specified attribute on a line - @param lineNum: the number of the affected line - @param attributeKey: the name of the attribute to remove, e.g. list - + /** + * Removes a specified attribute on a line + * @param lineNum the number of the affected line + * @param attributeName the name of the attribute to remove, e.g. list + * @param attributeValue if given only attributes with equal value will be removed */ removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ - var loc = [0,0]; - var builder = Changeset.builder(this.rep.lines.totalWidth()); - var hasMarker = this.lineHasMarker(lineNum); - var attribs - var foundAttrib = false - - attribs = this.getAttributesOnLine(lineNum).map(function(attrib) { - if(attrib[0] === attributeName) { - foundAttrib = true - return [attributeName, null] // remove this attrib from the linemarker + var builder = Changeset.builder(this.rep.lines.totalWidth()); + var hasMarker = this.lineHasMarker(lineNum); + var found = false; + + var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { + if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ + found = true; + return [attributeName, '']; + } + return attrib; + }); + + if (!found) { + return; + } + ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); + var list = _.chain(attribs).filter(function(a){return !!a[1];}).map(function(a){return a[0];}).value(); + //if we have marker and any of attributes don't need to have marker. we need delete it + if(hasMarker && !_.intersection(lineAttributes,list)){ + ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); + }else{ + ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); } - return attrib - }) - - if(!foundAttrib) { - return - } - - if(hasMarker){ - ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0])); - // If length == 4, there's [author, lmkr, insertorder, + the attrib being removed] thus we can remove the marker entirely - if(attribs.length <= 4) ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1])) - else ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), attribs, this.rep.apool); - } return this.applyChangeset(builder); }, @@ -202,4 +201,4 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ } }); -module.exports = AttributeManager; \ No newline at end of file +module.exports = AttributeManager; From 382804e44c88c96ad50d50788f9f0bdfcad8dd40 Mon Sep 17 00:00:00 2001 From: cristo-rabani Date: Sun, 22 Mar 2015 23:14:17 +0100 Subject: [PATCH 13/20] fix --- src/static/js/AttributeManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index fbfd6b309..5e7823070 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -179,9 +179,9 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ var list = _.chain(attribs).filter(function(a){return !!a[1];}).map(function(a){return a[0];}).value(); //if we have marker and any of attributes don't need to have marker. we need delete it if(hasMarker && !_.intersection(lineAttributes,list)){ - ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); + ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 1], [lineNum, 2]); }else{ - ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); + ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 1], [lineNum, 2], attribs, this.rep.apool); } return this.applyChangeset(builder); From ed3ec96838eb35105e847c4691ddf57893ae7e97 Mon Sep 17 00:00:00 2001 From: cristo-rabani Date: Tue, 24 Mar 2015 20:04:28 +0100 Subject: [PATCH 14/20] own list --- src/static/js/AttributeManager.js | 54 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 5e7823070..af81bb416 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -159,33 +159,37 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ * @param attributeName the name of the attribute to remove, e.g. list * @param attributeValue if given only attributes with equal value will be removed */ - removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ - var builder = Changeset.builder(this.rep.lines.totalWidth()); - var hasMarker = this.lineHasMarker(lineNum); - var found = false; + removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ + var builder = Changeset.builder(this.rep.lines.totalWidth()); + var hasMarker = this.lineHasMarker(lineNum); + var found = false; - var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { - if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ - found = true; - return [attributeName, '']; - } - return attrib; - }); + var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { + if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ + found = true; + return [attributeName, '']; + } + return attrib; + }); - if (!found) { - return; - } - ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); - var list = _.chain(attribs).filter(function(a){return !!a[1];}).map(function(a){return a[0];}).value(); - //if we have marker and any of attributes don't need to have marker. we need delete it - if(hasMarker && !_.intersection(lineAttributes,list)){ - ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 1], [lineNum, 2]); - }else{ - ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 1], [lineNum, 2], attribs, this.rep.apool); - } - - return this.applyChangeset(builder); - }, + if (!found) { + return; + } + + ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); + + var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];}) + .map(function(a){return a[0];}).difference(['author', 'lmkr', 'insertorder', 'start']).size().value(); + + //if we have marker and any of attributes don't need to have marker. we need delete it + if(hasMarker && !countAttribsWithMarker){ + ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); + }else{ + ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); + } + + return this.applyChangeset(builder); + }, /* Sets a specified attribute on a line From e8d85c1173353693934cf0fdc396b7f0432f9504 Mon Sep 17 00:00:00 2001 From: Thomas Muehlichen Date: Wed, 25 Mar 2015 12:04:10 +0100 Subject: [PATCH 15/20] feature #2558 added functions to get all attributes at the current or an abritrary position --- src/static/js/AttributeManager.js | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 865569c53..d2ec324b4 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -98,7 +98,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ /* Gets all attributes on a line - @param lineNum: the number of the line to set the attribute for + @param lineNum: the number of the line to get the attribute for */ getAttributesOnLine: function(lineNum){ // get attributes of first char of line @@ -122,6 +122,56 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ return []; }, + /* + Gets all attributes at a position containing line number and column + @param lineNumber starting with zero + @param column starting with zero + returns a list of attributes in the format + [ ["key","value"], ["key","value"], ... ] + */ + getAttributesOnPosition: function(lineNumber, column){ + // get all attributes of the line + var aline = this.rep.alines[lineNumber]; + + if (!aline) { + return []; + } + // iterate through all operations of a line + var opIter = Changeset.opIterator(aline); + + // we need to sum up how much characters each operations take until the wanted position + var currentPointer = 0; + var attributes = []; + var currentOperation; + + while (opIter.hasNext()) { + currentOperation = opIter.next(); + currentPointer = currentPointer + currentOperation.chars; + + if (currentPointer > column) { + // we got the operation of the wanted position, now collect all its attributes + Changeset.eachAttribNumber(currentOperation.attribs, function (n) { + attributes.push([ + this.rep.apool.getAttribKey(n), + this.rep.apool.getAttribValue(n) + ]); + }.bind(this)); + + // skip the loop + return attributes; + } + } + return attributes; + + }, + + /* + Gets all attributes at caret position + */ + getAttributesOnCaret: function(){ + return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]); + }, + /* Sets a specified attribute on a line @param lineNum: the number of the line to set the attribute for From fbcbc3c8a2ffef1628ac13a80c21573e61455723 Mon Sep 17 00:00:00 2001 From: Thomas Muehlichen Date: Wed, 25 Mar 2015 13:29:03 +0100 Subject: [PATCH 16/20] feature #2558 more precise documentation --- src/static/js/AttributeManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index d2ec324b4..b822caee0 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -166,7 +166,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ }, /* - Gets all attributes at caret position + Gets all attributes at caret position + if the user selected a range, the start of the selection is taken */ getAttributesOnCaret: function(){ return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]); From 1c05933dc91c9669a2f8407ab471c204f6975e98 Mon Sep 17 00:00:00 2001 From: Thomas Muehlichen Date: Thu, 26 Mar 2015 18:49:35 +0100 Subject: [PATCH 17/20] Feature #2567 Added workaround to enable contentcollector to write key-value attributes --- src/static/js/contentcollector.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index e428c63f9..cb3cf7b0b 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -297,7 +297,23 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas { if (state.attribs[a]) { - lst.push([a, 'true']); + // The following splitting of the attribute name is a workaround + // to enable the content collector to store key-value attributes + // see https://github.com/ether/etherpad-lite/issues/2567 for more information + // in long term the contentcollector should be refactored to get rid of this workaround + var ATTRIBUTE_SPLIT_STRING = "::"; + + // see if attributeString is splittable + var attributeSplits = a.split(ATTRIBUTE_SPLIT_STRING); + if (attributeSplits.length > 1) { + // the attribute name follows the convention key::value + // so save it as a key value attribute + lst.push([attributeSplits[0], attributeSplits[1]]); + } else { + // the "normal" case, the attribute is just a switch + // so set it true + lst.push([a, 'true']); + } } } if (state.authorLevel > 0) From 1e8e64d675691b4c8dc0f461958a439ec33d6154 Mon Sep 17 00:00:00 2001 From: Thomas Muehlichen Date: Tue, 31 Mar 2015 10:50:20 +0200 Subject: [PATCH 18/20] feature #2567 added documentation --- doc/api/hooks_client-side.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/api/hooks_client-side.md b/doc/api/hooks_client-side.md index 8e2d3da79..f9ad9147c 100644 --- a/doc/api/hooks_client-side.md +++ b/doc/api/hooks_client-side.md @@ -203,6 +203,13 @@ Things in context: This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original. +E.g. if you need to apply an attribute to newly inserted characters, +call cc.doAttrib(state, "attributeName") which results in an attribute attributeName=true. + +If you want to specify also a value, call cc.doAttrib(state, "attributeName:value") +which results in an attribute attributeName=value. + + ## collectContentImage Called from: src/static/js/contentcollector.js From a930161cb9aba6524ffa344863e44e83cc578b6d Mon Sep 17 00:00:00 2001 From: Thomas Muehlichen Date: Tue, 31 Mar 2015 10:58:47 +0200 Subject: [PATCH 19/20] feature #2558 added documentation --- src/static/js/AttributeManager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index b822caee0..1d1c4a913 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -168,6 +168,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ /* Gets all attributes at caret position if the user selected a range, the start of the selection is taken + returns a list of attributes in the format + [ ["key","value"], ["key","value"], ... ] */ getAttributesOnCaret: function(){ return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]); From 7e9bc1b7b99cc97e3258e3b4a11ad9051b7397a0 Mon Sep 17 00:00:00 2001 From: LeoVerto Date: Tue, 31 Mar 2015 18:33:47 +0200 Subject: [PATCH 20/20] Fix minor typo in safeRun.sh email report --- bin/safeRun.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/safeRun.sh b/bin/safeRun.sh index 4b3485ba4..519a0b6ef 100755 --- a/bin/safeRun.sh +++ b/bin/safeRun.sh @@ -55,7 +55,7 @@ do TIME_SINCE_LAST_SEND=$(($TIME_NOW - $LAST_EMAIL_SEND)) if [ $TIME_SINCE_LAST_SEND -gt $TIME_BETWEEN_EMAILS ]; then - printf "Server was restared at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS + printf "Server was restarted at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS LAST_EMAIL_SEND=$TIME_NOW fi