From d16b15f3eb7d5753b60fbc7907e85ca996674bdb Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 17:34:34 +0000 Subject: [PATCH 01/39] begin support for better txt output --- src/node/handler/ExportHandler.js | 78 ++++- src/node/utils/ExportTxt.js | 477 ++++++++++++++++++++++++++++++ 2 files changed, 543 insertions(+), 12 deletions(-) create mode 100644 src/node/utils/ExportTxt.js diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 1b7fcc26d..8ff5bc488 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -20,6 +20,7 @@ var ERR = require("async-stacktrace"); var exporthtml = require("../utils/ExportHtml"); +var exporttxt = require("../utils/ExportTxt"); var exportdokuwiki = require("../utils/ExportDokuWiki"); var padManager = require("../db/PadManager"); var async = require("async"); @@ -48,22 +49,75 @@ exports.doExport = function(req, res, padId, type) res.attachment(padId + "." + type); //if this is a plain text export, we can do this directly + // We have to over engineer this because tabs are stored as attributes and not plain text + if(type == "txt") { - padManager.getPad(padId, function(err, pad) - { - ERR(err); - if(req.params.rev){ - pad.getInternalRevisionAText(req.params.rev, function(junk, text) - { - res.send(text.text ? text.text : null); - }); - } - else + var txt; + var randNum; + var srcFile, destFile; + + async.series([ + //render the txt document + function(callback) { - res.send(pad.text()); + 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 + "/eplite_export_" + randNum + "." + type; + abiword.convertFile(srcFile, destFile, type, callback); + }, + //send the file + function(callback) + { + res.sendfile(destFile, null, callback); + }, + //clean up temporary files + function(callback) + { + async.parallel([ + function(callback) + { + fs.unlink(srcFile, callback); + }, + function(callback) + { + //100ms delay to accomidate for slow windows fs + if(os.type().indexOf("Windows") > -1) + { + setTimeout(function() + { + fs.unlink(destFile, callback); + }, 100); + } + else + { + fs.unlink(destFile, callback); + } + } + ], callback); } - }); + ], function(err) + { + if(err && err != "stop") ERR(err); + }) } else if(type == 'dokuwiki') { diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js new file mode 100644 index 000000000..99f6085e3 --- /dev/null +++ b/src/node/utils/ExportTxt.js @@ -0,0 +1,477 @@ +/** + * Copyright 2009 Google Inc. + * + * 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 Changeset = require("ep_etherpad-lite/static/js/Changeset"); +var padManager = require("../db/PadManager"); +var ERR = require("async-stacktrace"); +var Security = require('ep_etherpad-lite/static/js/security'); +var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +function getPadPlainText(pad, revNum) +{ + var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); + var textLines = atext.text.slice(0, -1).split('\n'); + var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + var apool = pad.pool(); + + var pieces = []; + for (var i = 0; i < textLines.length; i++) + { + var line = _analyzeLine(textLines[i], attribLines[i], apool); + if (line.listLevel) + { + var numSpaces = line.listLevel * 2 - 1; + var bullet = '*'; + pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); + } + else + { + pieces.push(line.text, '\n'); + } + } + + return pieces.join(''); +} + +function getPadTXT(pad, revNum, callback) +{ + var atext = pad.atext; + var html; + async.waterfall([ + // fetch revision atext + + + function (callback) + { + if (revNum != undefined) + { + pad.getInternalRevisionAText(revNum, function (err, revisionAtext) + { + if(ERR(err, callback)) return; + atext = revisionAtext; + callback(); + }); + } + else + { + callback(null); + } + }, + + // convert atext to html + + + function (callback) + { + html = getTXTFromAtext(pad, atext); + callback(null); + }], + // run final callback + + + function (err) + { + if(ERR(err, callback)) return; + callback(null, html); + }); +} + +exports.getPadTXT = getPadTXT; +exports.getTXTFromAtext = getTXTFromAtext; + +function getTXTFromAtext(pad, atext, authorColors) +{ + var apool = pad.apool(); + var textLines = atext.text.slice(0, -1).split('\n'); + var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + + var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; + var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; + var anumMap = {}; + var css = ""; + + props.forEach(function (propName, i) + { + var propTrueNum = apool.putAttrib([propName, true], true); + if (propTrueNum >= 0) + { + anumMap[propTrueNum] = i; + } + }); + + function getLineTXT(text, attribs) + { + var propVals = [false, false, false]; + var ENTER = 1; + var STAY = 2; + var LEAVE = 0; + + // Use order of tags (b/i/u) as order of nesting, for simplicity + // and decent nesting. For example, + // Just bold Bold and italics Just italics + // becomes + // Just bold Bold and italics Just italics + var taker = Changeset.stringIterator(text); + var assem = Changeset.stringAssembler(); + var openTags = []; + + var urls = _findURLs(text); + + var idx = 0; + + function processNextChars(numChars) + { + if (numChars <= 0) + { + return; + } + + var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); + idx += numChars; + + while (iter.hasNext()) + { + var o = iter.next(); + var propChanged = false; + Changeset.eachAttribNumber(o.attribs, function (a) + { + if (a in anumMap) + { + var i = anumMap[a]; // i = 0 => bold, etc. + if (!propVals[i]) + { + propVals[i] = ENTER; + propChanged = true; + } + else + { + propVals[i] = STAY; + } + } + }); + for (var i = 0; i < propVals.length; i++) + { + if (propVals[i] === true) + { + propVals[i] = LEAVE; + propChanged = true; + } + else if (propVals[i] === STAY) + { + propVals[i] = true; // set it back + } + } + // now each member of propVal is in {false,LEAVE,ENTER,true} + // according to what happens at start of span + if (propChanged) + { + // leaving bold (e.g.) also leaves italics, etc. + var left = false; + for (var i = 0; i < propVals.length; i++) + { + var v = propVals[i]; + if (!left) + { + if (v === LEAVE) + { + left = true; + } + } + else + { + if (v === true) + { + propVals[i] = STAY; // tag will be closed and re-opened + } + } + } + + var tags2close = []; + + for (var i = propVals.length - 1; i >= 0; i--) + { + if (propVals[i] === LEAVE) + { + //emitCloseTag(i); + tags2close.push(i); + propVals[i] = false; + } + else if (propVals[i] === STAY) + { + //emitCloseTag(i); + tags2close.push(i); + } + } + + for (var i = 0; i < propVals.length; i++) + { + if (propVals[i] === ENTER || propVals[i] === STAY) + { + emitOpenTag(i); + propVals[i] = true; + } + } + // propVals is now all {true,false} again + } // end if (propChanged) + var chars = o.chars; + if (o.lines) + { + chars--; // exclude newline at end of line, if present + } + + var s = taker.take(chars); + + //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), ""); + + assem.append(_encodeWhitespace(Security.escapeHTML(s))); + } // end iteration over spans in line + + var tags2close = []; + for (var i = propVals.length - 1; i >= 0; i--) + { + if (propVals[i]) + { + tags2close.push(i); + propVals[i] = false; + } + } + + } // end processNextChars + if (urls) + { + urls.forEach(function (urlData) + { + var startIndex = urlData[0]; + var url = urlData[1]; + var urlLength = url.length; + processNextChars(startIndex - idx); + console.warn(url); + // assem.append(''); + assem.append(url); + processNextChars(urlLength); + // assem.append(''); + }); + } + processNextChars(text.length - idx); + + return _processSpaces(assem.toString()); + } // end getLineHTML + var pieces = [css]; + + // Need to deal with constraints imposed on HTML lists; can + // only gain one level of nesting at once, can't change type + // mid-list, etc. + // People might use weird indenting, e.g. skip a level, + // so we want to do something reasonable there. We also + // want to deal gracefully with blank lines. + // => keeps track of the parents level of indentation + var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...] + for (var i = 0; i < textLines.length; i++) + { + var line = _analyzeLine(textLines[i], attribLines[i], apool); + var lineContent = getLineTXT(line.text, line.aline); + if(line.listTypeName == "bullet"){ + lineContent = "* " + lineContent; // add a bullet + } + if(line.listLevel > 0){ + for (var j = line.listLevel - 1; j >= 0; j--){ + pieces.push('\t'); + } + if(line.listTypeName == "number"){ + pieces.push(line.listLevel + ". "); + // This is bad because it doesn't truly reflect what the user + // sees because browsers do magic on nested
  1. s + } + pieces.push(lineContent, '\n'); + }else{ + console.warn(line); + pieces.push(lineContent, '\n'); + } + + // I'm not too keen about using teh HTML export filters here, they could cause real pain in the future + // I'd suggest supporting getLineTXTForExport + var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", + { + line: line, + apool: apool, + attribLine: attribLines[i], + text: textLines[i] + }, " ", " ", ""); + if (lineContentFromHook) + { + pieces.push(lineContentFromHook, ''); + } + else + { + // pieces.push(lineContent, '\n'); + } + } + + return pieces.join(''); +} + +function _analyzeLine(text, aline, apool) +{ + var line = {}; + + // identify list + var lineMarker = 0; + line.listLevel = 0; + if (aline) + { + var opIter = Changeset.opIterator(aline); + if (opIter.hasNext()) + { + var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); + if (listType) + { + lineMarker = 1; + listType = /([a-z]+)([12345678])/.exec(listType); + if (listType) + { + line.listTypeName = listType[1]; + line.listLevel = Number(listType[2]); + } + } + } + } + if (lineMarker) + { + line.text = text.substring(1); + line.aline = Changeset.subattribution(aline, 1); + } + else + { + line.text = text; + line.aline = aline; + } + + return line; +} + +exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) +{ + padManager.getPad(padId, function (err, pad) + { + if(ERR(err, callback)) return; + + getPadTXT(pad, revNum, function (err, html) + { + if(ERR(err, callback)) return; + callback(null, html); + }); + }); +} + +function _encodeWhitespace(s) { + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) + { + return "&#" +c.charCodeAt(0) + ";" + }); +} + +// copied from ACE +function _processSpaces(s) +{ + var doesWrap = true; + if (s.indexOf("<") < 0 && !doesWrap) + { + // short-cut + return s.replace(/ /g, ' '); + } + var parts = []; + s.replace(/<[^>]*>?| |[^ <]+/g, function (m) + { + parts.push(m); + }); + if (doesWrap) + { + var endOfLine = true; + var beforeSpace = false; + // last space in a run is normal, others are nbsp, + // end of line is nbsp + for (var i = parts.length - 1; i >= 0; i--) + { + var p = parts[i]; + if (p == " ") + { + if (endOfLine || beforeSpace) parts[i] = ' '; + endOfLine = false; + beforeSpace = true; + } + else if (p.charAt(0) != "<") + { + endOfLine = false; + beforeSpace = false; + } + } + // beginning of line is nbsp + for (var i = 0; i < parts.length; i++) + { + var p = parts[i]; + if (p == " ") + { + parts[i] = ' '; + break; + } + else if (p.charAt(0) != "<") + { + break; + } + } + } + else + { + for (var i = 0; i < parts.length; i++) + { + var p = parts[i]; + if (p == " ") + { + parts[i] = ' '; + } + } + } + return parts.join(''); +} + + +// copied from ACE +var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; +var _REGEX_SPACE = /\s/; +var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); +var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); + +// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] + +function _findURLs(text) +{ + _REGEX_URL.lastIndex = 0; + var urls = null; + var execResult; + while ((execResult = _REGEX_URL.exec(text))) + { + urls = (urls || []); + var startIndex = execResult.index; + var url = execResult[0]; + urls.push([startIndex, url]); + } + + return urls; +} + From a378f48c00fca7b78d888e3fd5957668fbff8811 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 17:39:02 +0000 Subject: [PATCH 02/39] remove console warns --- src/node/utils/ExportTxt.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 99f6085e3..d9ee708f9 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -261,7 +261,6 @@ function getTXTFromAtext(pad, atext, authorColors) var url = urlData[1]; var urlLength = url.length; processNextChars(startIndex - idx); - console.warn(url); // assem.append(''); assem.append(url); processNextChars(urlLength); @@ -300,7 +299,6 @@ function getTXTFromAtext(pad, atext, authorColors) } pieces.push(lineContent, '\n'); }else{ - console.warn(line); pieces.push(lineContent, '\n'); } From a67a0950dd05bbe494ede5dfd696fcc47dcc9107 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 19:21:27 +0000 Subject: [PATCH 03/39] stop urls being encoded, not sure about other security implications here... --- src/node/utils/ExportTxt.js | 40 ++----------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index d9ee708f9..462583d3a 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -129,8 +129,6 @@ function getTXTFromAtext(pad, atext, authorColors) var assem = Changeset.stringAssembler(); var openTags = []; - var urls = _findURLs(text); - var idx = 0; function processNextChars(numChars) @@ -239,7 +237,8 @@ function getTXTFromAtext(pad, atext, authorColors) //from but they break the abiword parser and are completly useless s = s.replace(String.fromCharCode(12), ""); - assem.append(_encodeWhitespace(Security.escapeHTML(s))); + // assem.append(_encodeWhitespace(Security.escapeHTML(s))); + assem.append(_encodeWhitespace(s)); } // end iteration over spans in line var tags2close = []; @@ -253,20 +252,6 @@ function getTXTFromAtext(pad, atext, authorColors) } } // end processNextChars - if (urls) - { - urls.forEach(function (urlData) - { - var startIndex = urlData[0]; - var url = urlData[1]; - var urlLength = url.length; - processNextChars(startIndex - idx); - // assem.append(''); - assem.append(url); - processNextChars(urlLength); - // assem.append(''); - }); - } processNextChars(text.length - idx); return _processSpaces(assem.toString()); @@ -452,24 +437,3 @@ function _processSpaces(s) // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; var _REGEX_SPACE = /\s/; -var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); -var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); - -// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] - -function _findURLs(text) -{ - _REGEX_URL.lastIndex = 0; - var urls = null; - var execResult; - while ((execResult = _REGEX_URL.exec(text))) - { - urls = (urls || []); - var startIndex = execResult.index; - var url = execResult[0]; - urls.push([startIndex, url]); - } - - return urls; -} - From 626ee97669767ea0f18fc41a99591211c35238ec Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 19:36:46 +0000 Subject: [PATCH 04/39] kinda brutal way of stopping plugins being able to pass *s instead of attributes --- src/node/db/PadManager.js | 4 ++-- src/node/utils/ExportTxt.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 5e0af4643..2be9da369 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -146,12 +146,12 @@ exports.getPad = function(id, text, callback) else { pad = new Pad(id); - + //initalize the pad pad.init(text, function(err) { if(ERR(err, callback)) return; - + console.warn(pad); globalPads.set(id, pad); callback(null, pad); }); diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 462583d3a..c236df479 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -236,6 +236,10 @@ function getTXTFromAtext(pad, atext, authorColors) //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), ""); + + // remove * from s, it's just not needed on a blank line.. This stops + // plugins from being able to display * at the beginning of a line + s = s.replace("*", ""); // assem.append(_encodeWhitespace(Security.escapeHTML(s))); assem.append(_encodeWhitespace(s)); From bcf9c23b4e9b52bd86e4b7936b713d71c7cd3e93 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 19:38:16 +0000 Subject: [PATCH 05/39] dont use HTML filter hooks on txt export --- src/node/utils/ExportTxt.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index c236df479..9451fd6e8 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -290,24 +290,6 @@ function getTXTFromAtext(pad, atext, authorColors) }else{ pieces.push(lineContent, '\n'); } - - // I'm not too keen about using teh HTML export filters here, they could cause real pain in the future - // I'd suggest supporting getLineTXTForExport - var lineContentFromHook = hooks.callAllStr("getLineHTMLForExport", - { - line: line, - apool: apool, - attribLine: attribLines[i], - text: textLines[i] - }, " ", " ", ""); - if (lineContentFromHook) - { - pieces.push(lineContentFromHook, ''); - } - else - { - // pieces.push(lineContent, '\n'); - } } return pieces.join(''); From 28f6d50011abd7c12b2b3948b14bd547d2ef4aee Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 22:14:05 +0000 Subject: [PATCH 06/39] remove console warn --- src/node/db/PadManager.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index 2be9da369..7d546fc71 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -151,7 +151,6 @@ exports.getPad = function(id, text, callback) pad.init(text, function(err) { if(ERR(err, callback)) return; - console.warn(pad); globalPads.set(id, pad); callback(null, pad); }); From 60ef5f210ae47529325abf0968966ddf8348bfa7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 10 Feb 2013 23:41:14 +0000 Subject: [PATCH 07/39] remove duplicate code to the best of my ability right now --- src/node/utils/ExportHtml.js | 7 +- src/node/utils/ExportTxt.js | 145 +++-------------------------------- 2 files changed, 15 insertions(+), 137 deletions(-) diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 069194880..74b6546f1 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -92,6 +92,7 @@ function getPadHTML(pad, revNum, callback) exports.getPadHTML = getPadHTML; exports.getHTMLFromAtext = getHTMLFromAtext; +exports.getPadPlainText = getPadPlainText; function getHTMLFromAtext(pad, atext, authorColors) { @@ -542,6 +543,8 @@ function _analyzeLine(text, aline, apool) return line; } +exports._analyzeLine = _analyzeLine; + exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) { padManager.getPad(padId, function (err, pad) @@ -584,10 +587,9 @@ function _encodeWhitespace(s) { return "&#" +c.charCodeAt(0) + ";" }); } +exports._encodeWhitespace = _encodeWhitespace; // copied from ACE - - function _processSpaces(s) { var doesWrap = true; @@ -651,6 +653,7 @@ function _processSpaces(s) return parts.join(''); } +exports._processSpaces = _processSpaces; // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 9451fd6e8..edcccfd77 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -21,32 +21,12 @@ var padManager = require("../db/PadManager"); var ERR = require("async-stacktrace"); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -function getPadPlainText(pad, revNum) -{ - var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var apool = pad.pool(); - - var pieces = []; - for (var i = 0; i < textLines.length; i++) - { - var line = _analyzeLine(textLines[i], attribLines[i], apool); - if (line.listLevel) - { - var numSpaces = line.listLevel * 2 - 1; - var bullet = '*'; - pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); - } - else - { - pieces.push(line.text, '\n'); - } - } - - return pieces.join(''); -} +var getPadPlainText = require('./ExportHtml').getPadPlainText; +var _processSpaces = require('./ExportHtml')._processSpaces; +var _analyzeLine = require('./ExportHtml')._analyzeLine; +var _encodeWhitespace = require('./ExportHtml')._encodeWhitespace; +// This is slightly different than the HTML method as it passes the output to getTXTFromAText function getPadTXT(pad, revNum, callback) { var atext = pad.atext; @@ -77,7 +57,7 @@ function getPadTXT(pad, revNum, callback) function (callback) { - html = getTXTFromAtext(pad, atext); + html = getTXTFromAtext(pad, atext); // only this line is different to the HTML function callback(null); }], // run final callback @@ -91,8 +71,10 @@ function getPadTXT(pad, revNum, callback) } exports.getPadTXT = getPadTXT; -exports.getTXTFromAtext = getTXTFromAtext; + +// This is different than the functionality provided in ExportHtml as it provides formatting +// functionality that is designed specifically for TXT exports function getTXTFromAtext(pad, atext, authorColors) { var apool = pad.apool(); @@ -294,45 +276,7 @@ function getTXTFromAtext(pad, atext, authorColors) return pieces.join(''); } - -function _analyzeLine(text, aline, apool) -{ - var line = {}; - - // identify list - var lineMarker = 0; - line.listLevel = 0; - if (aline) - { - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()) - { - var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); - if (listType) - { - lineMarker = 1; - listType = /([a-z]+)([12345678])/.exec(listType); - if (listType) - { - line.listTypeName = listType[1]; - line.listLevel = Number(listType[2]); - } - } - } - } - if (lineMarker) - { - line.text = text.substring(1); - line.aline = Changeset.subattribution(aline, 1); - } - else - { - line.text = text; - line.aline = aline; - } - - return line; -} +exports.getTXTFromAtext = getTXTFromAtext; exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) { @@ -354,72 +298,3 @@ function _encodeWhitespace(s) { return "&#" +c.charCodeAt(0) + ";" }); } - -// copied from ACE -function _processSpaces(s) -{ - var doesWrap = true; - if (s.indexOf("<") < 0 && !doesWrap) - { - // short-cut - return s.replace(/ /g, ' '); - } - var parts = []; - s.replace(/<[^>]*>?| |[^ <]+/g, function (m) - { - parts.push(m); - }); - if (doesWrap) - { - var endOfLine = true; - var beforeSpace = false; - // last space in a run is normal, others are nbsp, - // end of line is nbsp - for (var i = parts.length - 1; i >= 0; i--) - { - var p = parts[i]; - if (p == " ") - { - if (endOfLine || beforeSpace) parts[i] = ' '; - endOfLine = false; - beforeSpace = true; - } - else if (p.charAt(0) != "<") - { - endOfLine = false; - beforeSpace = false; - } - } - // beginning of line is nbsp - for (var i = 0; i < parts.length; i++) - { - var p = parts[i]; - if (p == " ") - { - parts[i] = ' '; - break; - } - else if (p.charAt(0) != "<") - { - break; - } - } - } - else - { - for (var i = 0; i < parts.length; i++) - { - var p = parts[i]; - if (p == " ") - { - parts[i] = ' '; - } - } - } - return parts.join(''); -} - - -// copied from ACE -var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; -var _REGEX_SPACE = /\s/; From 0b5c948549a03e858302bb9740529741c3be4e22 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 12 Feb 2013 19:45:46 +0000 Subject: [PATCH 08/39] Move code from Html export to a Helper file --- src/node/utils/ExportHelper.js | 139 +++++++++++++++++++++++++++++++ src/node/utils/ExportHtml.js | 144 +-------------------------------- src/node/utils/ExportTxt.js | 8 +- 3 files changed, 147 insertions(+), 144 deletions(-) create mode 100644 src/node/utils/ExportHelper.js diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js new file mode 100644 index 000000000..030b0dc75 --- /dev/null +++ b/src/node/utils/ExportHelper.js @@ -0,0 +1,139 @@ +/** + * Helpers for export requests + */ + +/* + * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) + * + * 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 Changeset = require("ep_etherpad-lite/static/js/Changeset"); +var padManager = require("../db/PadManager"); +var ERR = require("async-stacktrace"); +var Security = require('ep_etherpad-lite/static/js/security'); +var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); + +exports.getPadPlainText = function(pad, revNum){ + var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); + var textLines = atext.text.slice(0, -1).split('\n'); + var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + var apool = pad.pool(); + + var pieces = []; + for (var i = 0; i < textLines.length; i++){ + var line = _analyzeLine(textLines[i], attribLines[i], apool); + if (line.listLevel){ + var numSpaces = line.listLevel * 2 - 1; + var bullet = '*'; + pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); + } + else{ + pieces.push(line.text, '\n'); + } + } + + return pieces.join(''); +} + +// copied from ACE +exports._processSpaces = function(s){ + var doesWrap = true; + if (s.indexOf("<") < 0 && !doesWrap){ + // short-cut + return s.replace(/ /g, ' '); + } + var parts = []; + s.replace(/<[^>]*>?| |[^ <]+/g, function (m){ + parts.push(m); + }); + if (doesWrap){ + var endOfLine = true; + var beforeSpace = false; + // last space in a run is normal, others are nbsp, + // end of line is nbsp + for (var i = parts.length - 1; i >= 0; i--){ + var p = parts[i]; + if (p == " "){ + if (endOfLine || beforeSpace) parts[i] = ' '; + endOfLine = false; + beforeSpace = true; + } + else if (p.charAt(0) != "<"){ + endOfLine = false; + beforeSpace = false; + } + } + // beginning of line is nbsp + for (var i = 0; i < parts.length; i++){ + var p = parts[i]; + if (p == " "){ + parts[i] = ' '; + break; + } + else if (p.charAt(0) != "<"){ + break; + } + } + } + else + { + for (var i = 0; i < parts.length; i++){ + var p = parts[i]; + if (p == " "){ + parts[i] = ' '; + } + } + } + return parts.join(''); +} + + +exports._analyzeLine = function(text, aline, apool){ + var line = {}; + + // identify list + var lineMarker = 0; + line.listLevel = 0; + if (aline){ + var opIter = Changeset.opIterator(aline); + if (opIter.hasNext()){ + var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); + if (listType){ + lineMarker = 1; + listType = /([a-z]+)([12345678])/.exec(listType); + if (listType){ + line.listTypeName = listType[1]; + line.listLevel = Number(listType[2]); + } + } + } + } + if (lineMarker){ + line.text = text.substring(1); + line.aline = Changeset.subattribution(aline, 1); + } + else{ + line.text = text; + line.aline = aline; + } + return line; +} + + +exports._encodeWhitespace = function(s){ + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ + return "&#" +c.charCodeAt(0) + ";" + }); +} diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 74b6546f1..a54f566c2 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -21,31 +21,10 @@ var padManager = require("../db/PadManager"); var ERR = require("async-stacktrace"); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -function getPadPlainText(pad, revNum) -{ - var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext()); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var apool = pad.pool(); - - var pieces = []; - for (var i = 0; i < textLines.length; i++) - { - var line = _analyzeLine(textLines[i], attribLines[i], apool); - if (line.listLevel) - { - var numSpaces = line.listLevel * 2 - 1; - var bullet = '*'; - pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); - } - else - { - pieces.push(line.text, '\n'); - } - } - - return pieces.join(''); -} +var getPadPlainText = require('./ExportHelper').getPadPlainText +var _processSpaces = require('./ExportHelper')._processSpaces; +var _analyzeLine = require('./ExportHelper')._analyzeLine; +var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; function getPadHTML(pad, revNum, callback) { @@ -92,7 +71,6 @@ function getPadHTML(pad, revNum, callback) exports.getPadHTML = getPadHTML; exports.getHTMLFromAtext = getHTMLFromAtext; -exports.getPadPlainText = getPadPlainText; function getHTMLFromAtext(pad, atext, authorColors) { @@ -504,47 +482,6 @@ function getHTMLFromAtext(pad, atext, authorColors) return pieces.join(''); } -function _analyzeLine(text, aline, apool) -{ - var line = {}; - - // identify list - var lineMarker = 0; - line.listLevel = 0; - if (aline) - { - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()) - { - var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); - if (listType) - { - lineMarker = 1; - listType = /([a-z]+)([12345678])/.exec(listType); - if (listType) - { - line.listTypeName = listType[1]; - line.listLevel = Number(listType[2]); - } - } - } - } - if (lineMarker) - { - line.text = text.substring(1); - line.aline = Changeset.subattribution(aline, 1); - } - else - { - line.text = text; - line.aline = aline; - } - - return line; -} - -exports._analyzeLine = _analyzeLine; - exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) { padManager.getPad(padId, function (err, pad) @@ -581,79 +518,6 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback) }); } -function _encodeWhitespace(s) { - return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) - { - return "&#" +c.charCodeAt(0) + ";" - }); -} -exports._encodeWhitespace = _encodeWhitespace; - -// copied from ACE -function _processSpaces(s) -{ - var doesWrap = true; - if (s.indexOf("<") < 0 && !doesWrap) - { - // short-cut - return s.replace(/ /g, ' '); - } - var parts = []; - s.replace(/<[^>]*>?| |[^ <]+/g, function (m) - { - parts.push(m); - }); - if (doesWrap) - { - var endOfLine = true; - var beforeSpace = false; - // last space in a run is normal, others are nbsp, - // end of line is nbsp - for (var i = parts.length - 1; i >= 0; i--) - { - var p = parts[i]; - if (p == " ") - { - if (endOfLine || beforeSpace) parts[i] = ' '; - endOfLine = false; - beforeSpace = true; - } - else if (p.charAt(0) != "<") - { - endOfLine = false; - beforeSpace = false; - } - } - // beginning of line is nbsp - for (var i = 0; i < parts.length; i++) - { - var p = parts[i]; - if (p == " ") - { - parts[i] = ' '; - break; - } - else if (p.charAt(0) != "<") - { - break; - } - } - } - else - { - for (var i = 0; i < parts.length; i++) - { - var p = parts[i]; - if (p == " ") - { - parts[i] = ' '; - } - } - } - return parts.join(''); -} - -exports._processSpaces = _processSpaces; // copied from ACE var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index edcccfd77..0f3b1a634 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -21,10 +21,10 @@ var padManager = require("../db/PadManager"); var ERR = require("async-stacktrace"); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var getPadPlainText = require('./ExportHtml').getPadPlainText; -var _processSpaces = require('./ExportHtml')._processSpaces; -var _analyzeLine = require('./ExportHtml')._analyzeLine; -var _encodeWhitespace = require('./ExportHtml')._encodeWhitespace; +var getPadPlainText = require('./ExportHelper').getPadPlainText; +var _processSpaces = require('./ExportHelper')._processSpaces; +var _analyzeLine = require('./ExportHelper')._analyzeLine; +var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; // This is slightly different than the HTML method as it passes the output to getTXTFromAText function getPadTXT(pad, revNum, callback) From da246d183daa1a5a012937f0dc0f54ca96873553 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 12 Feb 2013 19:47:53 +0000 Subject: [PATCH 09/39] Correct license header --- src/node/utils/ExportTxt.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 0f3b1a634..30673a981 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -1,5 +1,9 @@ /** - * Copyright 2009 Google Inc. + * TXT export + */ + +/* + * 2013 John McLear * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +18,6 @@ * limitations under the License. */ - var async = require("async"); var Changeset = require("ep_etherpad-lite/static/js/Changeset"); var padManager = require("../db/PadManager"); From e855bafdf9e5d6217d929e1f4d1933e673e3acac Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 12 Feb 2013 21:47:40 +0100 Subject: [PATCH 10/39] Update src/node/hooks/express/apicalls.js --- src/node/hooks/express/apicalls.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/node/hooks/express/apicalls.js b/src/node/hooks/express/apicalls.js index e57e1d350..0971a877b 100644 --- a/src/node/hooks/express/apicalls.js +++ b/src/node/hooks/express/apicalls.js @@ -57,4 +57,9 @@ exports.expressCreateServer = function (hook_name, args, cb) { res.end("OK"); }); }); + + //Provide a possibility to query the latest available API version + args.app.get('/api', function (req, res) { + res.json({"currentVersion" : apiHandler.latestApiVersion}); + }); } From 8c9ad6ee504bc1aa8d057118354a4eaacb077a50 Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 12 Feb 2013 21:50:14 +0100 Subject: [PATCH 11/39] Update src/node/handler/APIHandler.js --- src/node/handler/APIHandler.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 9f86277a0..8be5b5fe3 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -216,6 +216,9 @@ var version = } }; +// set the latest available API version here +exports.latestApiVersion = '1.2.7'; + /** * Handles a HTTP API call * @param functionName the name of the called function From fde10052ca5d9c80bc0837e00bdd87a2eeff4c72 Mon Sep 17 00:00:00 2001 From: Manuel Knitza Date: Tue, 12 Feb 2013 21:57:01 +0100 Subject: [PATCH 12/39] Update doc/api/http_api.md Updated --- doc/api/http_api.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 0543ef71a..7e05ff86d 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -61,7 +61,9 @@ Portal submits content into new blog post ## Usage ### API version -The latest version is `1.2` +The latest version is `1.2.7` + +The current version can be queried via /api. ### Request Format From a4e5adab3e42d6436d89a1155ecf303dabc2dd9d Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 02:47:24 +0000 Subject: [PATCH 13/39] Update Ueber DB to fix MySQL Bug! --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 03df87f9b..5b21334d8 100644 --- a/src/package.json +++ b/src/package.json @@ -16,7 +16,7 @@ "require-kernel" : "1.0.5", "resolve" : "0.2.x", "socket.io" : "0.9.x", - "ueberDB" : "0.1.91", + "ueberDB" : "0.1.92", "async" : "0.1.x", "express" : "3.x", "connect" : "2.4.x", From 500f9b8b48cb774c7ce73bf15a73c862fe0c4f11 Mon Sep 17 00:00:00 2001 From: Mark Fisher Date: Wed, 13 Feb 2013 15:25:27 +0000 Subject: [PATCH 14/39] Fixed typo thats made server hang --- 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 7cd945af3..eaf24b7a2 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1429,7 +1429,7 @@ function composePadChangesets(padId, startNum, endNum, callback) */ exports.padUsersCount = function (padID, callback) { callback(null, { - padUsersCount: socketio.sockets.clients(padId).length + padUsersCount: socketio.sockets.clients(padID).length }); } From d3f730e2ba060b0d2b26d03b55088f266fefb1b9 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 18:01:15 +0000 Subject: [PATCH 15/39] fix various issues dont stop random *'s appearing --- src/node/utils/ExportHelper.js | 52 -------------------------------- src/node/utils/ExportHtml.js | 55 +++++++++++++++++++++++++++++++++- src/node/utils/ExportTxt.js | 19 +++++------- 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index 030b0dc75..a939a8b6e 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -47,58 +47,6 @@ exports.getPadPlainText = function(pad, revNum){ return pieces.join(''); } -// copied from ACE -exports._processSpaces = function(s){ - var doesWrap = true; - if (s.indexOf("<") < 0 && !doesWrap){ - // short-cut - return s.replace(/ /g, ' '); - } - var parts = []; - s.replace(/<[^>]*>?| |[^ <]+/g, function (m){ - parts.push(m); - }); - if (doesWrap){ - var endOfLine = true; - var beforeSpace = false; - // last space in a run is normal, others are nbsp, - // end of line is nbsp - for (var i = parts.length - 1; i >= 0; i--){ - var p = parts[i]; - if (p == " "){ - if (endOfLine || beforeSpace) parts[i] = ' '; - endOfLine = false; - beforeSpace = true; - } - else if (p.charAt(0) != "<"){ - endOfLine = false; - beforeSpace = false; - } - } - // beginning of line is nbsp - for (var i = 0; i < parts.length; i++){ - var p = parts[i]; - if (p == " "){ - parts[i] = ' '; - break; - } - else if (p.charAt(0) != "<"){ - break; - } - } - } - else - { - for (var i = 0; i < parts.length; i++){ - var p = parts[i]; - if (p == " "){ - parts[i] = ' '; - } - } - } - return parts.join(''); -} - exports._analyzeLine = function(text, aline, apool){ var line = {}; diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index a54f566c2..585694d4b 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -22,7 +22,6 @@ var ERR = require("async-stacktrace"); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText -var _processSpaces = require('./ExportHelper')._processSpaces; var _analyzeLine = require('./ExportHelper')._analyzeLine; var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; @@ -543,3 +542,57 @@ function _findURLs(text) return urls; } + + +// copied from ACE +function _processSpaces(s){ + var doesWrap = true; + if (s.indexOf("<") < 0 && !doesWrap){ + // short-cut + return s.replace(/ /g, ' '); + } + var parts = []; + s.replace(/<[^>]*>?| |[^ <]+/g, function (m){ + parts.push(m); + }); + if (doesWrap){ + var endOfLine = true; + var beforeSpace = false; + // last space in a run is normal, others are nbsp, + // end of line is nbsp + for (var i = parts.length - 1; i >= 0; i--){ + var p = parts[i]; + if (p == " "){ + if (endOfLine || beforeSpace) parts[i] = ' '; + endOfLine = false; + beforeSpace = true; + } + else if (p.charAt(0) != "<"){ + endOfLine = false; + beforeSpace = false; + } + } + // beginning of line is nbsp + for (var i = 0; i < parts.length; i++){ + var p = parts[i]; + if (p == " "){ + parts[i] = ' '; + break; + } + else if (p.charAt(0) != "<"){ + break; + } + } + } + else + { + for (var i = 0; i < parts.length; i++){ + var p = parts[i]; + if (p == " "){ + parts[i] = ' '; + } + } + } + return parts.join(''); +} + diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 30673a981..05847f162 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -25,7 +25,6 @@ var ERR = require("async-stacktrace"); var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText; -var _processSpaces = require('./ExportHelper')._processSpaces; var _analyzeLine = require('./ExportHelper')._analyzeLine; var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; @@ -204,12 +203,12 @@ function getTXTFromAtext(pad, atext, authorColors) { if (propVals[i] === ENTER || propVals[i] === STAY) { - emitOpenTag(i); propVals[i] = true; } } // propVals is now all {true,false} again } // end if (propChanged) + var chars = o.chars; if (o.lines) { @@ -217,16 +216,15 @@ function getTXTFromAtext(pad, atext, authorColors) } var s = taker.take(chars); - - //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), ""); + + // 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), ""); // remove * from s, it's just not needed on a blank line.. This stops // plugins from being able to display * at the beginning of a line - s = s.replace("*", ""); - - // assem.append(_encodeWhitespace(Security.escapeHTML(s))); + // s = s.replace("*", ""); // Then remove it + assem.append(_encodeWhitespace(s)); } // end iteration over spans in line @@ -242,8 +240,7 @@ function getTXTFromAtext(pad, atext, authorColors) } // end processNextChars processNextChars(text.length - idx); - - return _processSpaces(assem.toString()); + return(assem.toString()); } // end getLineHTML var pieces = [css]; From be56272e66f4921f68fe12cd05cbaad8eb54894b Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 18:30:55 +0000 Subject: [PATCH 16/39] allow non ascii chars in txt export --- src/node/utils/ExportHelper.js | 6 +- src/node/utils/ExportHtml.js | 7 +- src/node/utils/ExportTxt.js | 10 +- src/node/utils/padDiffHTML.js | 554 +++++++++++++++++++++++++++++++++ 4 files changed, 562 insertions(+), 15 deletions(-) create mode 100644 src/node/utils/padDiffHTML.js diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index a939a8b6e..41d440f30 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -80,8 +80,4 @@ exports._analyzeLine = function(text, aline, apool){ } -exports._encodeWhitespace = function(s){ - return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ - return "&#" +c.charCodeAt(0) + ";" - }); -} + diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 585694d4b..51a4b2c3d 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -23,7 +23,6 @@ var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText var _analyzeLine = require('./ExportHelper')._analyzeLine; -var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; function getPadHTML(pad, revNum, callback) { @@ -596,3 +595,9 @@ function _processSpaces(s){ return parts.join(''); } +function _encodeWhitespace(s){ + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ + return "&#" +c.charCodeAt(0) + ";" + }); +} + diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 05847f162..4a3e458b4 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -26,7 +26,6 @@ var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText; var _analyzeLine = require('./ExportHelper')._analyzeLine; -var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; // This is slightly different than the HTML method as it passes the output to getTXTFromAText function getPadTXT(pad, revNum, callback) @@ -112,7 +111,6 @@ function getTXTFromAtext(pad, atext, authorColors) var taker = Changeset.stringIterator(text); var assem = Changeset.stringAssembler(); var openTags = []; - var idx = 0; function processNextChars(numChars) @@ -225,7 +223,7 @@ function getTXTFromAtext(pad, atext, authorColors) // plugins from being able to display * at the beginning of a line // s = s.replace("*", ""); // Then remove it - assem.append(_encodeWhitespace(s)); + assem.append(s); } // end iteration over spans in line var tags2close = []; @@ -292,9 +290,3 @@ exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) }); } -function _encodeWhitespace(s) { - return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) - { - return "&#" +c.charCodeAt(0) + ";" - }); -} diff --git a/src/node/utils/padDiffHTML.js b/src/node/utils/padDiffHTML.js new file mode 100644 index 000000000..00e967287 --- /dev/null +++ b/src/node/utils/padDiffHTML.js @@ -0,0 +1,554 @@ +var Changeset = require("../../static/js/Changeset"); +var async = require("async"); +var exportHtml = require('./ExportHtml'); + +function padDiffHTML (pad, fromRev, toRev){ + //check parameters + if(!pad || !pad.id || !pad.atext || !pad.pool) + { + throw new Error('Invalid pad'); + } + + var range = pad.getValidRevisionRange(fromRev, toRev); + if(!range) { throw new Error('Invalid revision range.' + + ' startRev: ' + fromRev + + ' endRev: ' + toRev); } + + this._pad = pad; + this._fromRev = range.startRev; + this._toRev = range.endRev; + this._html = null; + this._authors = []; +} + +padDiffHTML.prototype._isClearAuthorship = function(changeset){ + //unpack + var unpacked = Changeset.unpack(changeset); + + //check if there is nothing in the charBank + if(unpacked.charBank !== "") + return false; + + //check if oldLength == newLength + if(unpacked.oldLen !== unpacked.newLen) + return false; + + //lets iterator over the operators + var iterator = Changeset.opIterator(unpacked.ops); + + //get the first operator, this should be a clear operator + var clearOperator = iterator.next(); + + //check if there is only one operator + if(iterator.hasNext() === true) + return false; + + //check if this operator doesn't change text + if(clearOperator.opcode !== "=") + return false; + + //check that this operator applys to the complete text + //if the text ends with a new line, its exactly one character less, else it has the same length + if(clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) + return false; + + var attributes = []; + Changeset.eachAttribNumber(changeset, function(attrNum){ + attributes.push(attrNum); + }); + + //check that this changeset uses only one attribute + if(attributes.length !== 1) + return false; + + var appliedAttribute = this._pad.pool.getAttrib(attributes[0]); + + //check if the applied attribute is an anonymous author attribute + if(appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") + return false; + + return true; +} + +padDiffHTML.prototype._createClearAuthorship = function(rev, callback){ + var self = this; + this._pad.getInternalRevisionAText(rev, function(err, atext){ + if(err){ + return callback(err); + } + + //build clearAuthorship changeset + var builder = Changeset.builder(atext.text.length); + builder.keepText(atext.text, [['author','']], self._pad.pool); + var changeset = builder.toString(); + + callback(null, changeset); + }); +} + +padDiffHTML.prototype._createClearStartAtext = function(rev, callback){ + var self = this; + + //get the atext of this revision + this._pad.getInternalRevisionAText(rev, function(err, atext){ + if(err){ + return callback(err); + } + + //create the clearAuthorship changeset + self._createClearAuthorship(rev, function(err, changeset){ + if(err){ + return callback(err); + } + + //apply the clearAuthorship changeset + var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); + + callback(null, newAText); + }); + }); +} + +padDiffHTML.prototype._getChangesetsInBulk = function(startRev, count, callback) { + var self = this; + + //find out which revisions we need + var revisions = []; + for(var i=startRev;i<(startRev+count) && i<=this._pad.head;i++){ + revisions.push(i); + } + + var changesets = [], authors = []; + + //get all needed revisions + async.forEach(revisions, function(rev, callback){ + self._pad.getRevision(rev, function(err, revision){ + if(err){ + return callback(err) + } + + var arrayNum = rev-startRev; + + changesets[arrayNum] = revision.changeset; + authors[arrayNum] = revision.meta.author; + + callback(); + }); + }, function(err){ + callback(err, changesets, authors); + }); +} + +padDiffHTML.prototype._addAuthors = function(authors) { + var self = this; + //add to array if not in the array + authors.forEach(function(author){ + if(self._authors.indexOf(author) == -1){ + self._authors.push(author); + } + }); +} + +padDiffHTML.prototype._createDiffAtext = function(callback) { + var self = this; + var bulkSize = 100; + + //get the cleaned startAText + self._createClearStartAtext(self._fromRev, function(err, atext){ + if(err) { return callback(err); } + + var superChangeset = null; + + var rev = self._fromRev + 1; + + //async while loop + async.whilst( + //loop condition + function () { return rev <= self._toRev; }, + + //loop body + function (callback) { + //get the bulk + self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors){ + var addedAuthors = []; + + //run trough all changesets + for(var i=0;i= curChar) { + curLineNextOp.chars -= (curChar - indexIntoLine); + done = true; + } else { + indexIntoLine += curLineNextOp.chars; + } + } + } + + while (numChars > 0) { + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + curLineOpIterLine = curLine; + curLineNextOp.chars = 0; + curLineOpIter = Changeset.opIterator(alines_get(curLine)); + } + if (!curLineNextOp.chars) { + curLineOpIter.next(curLineNextOp); + } + var charsToUse = Math.min(numChars, curLineNextOp.chars); + func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); + numChars -= charsToUse; + curLineNextOp.chars -= charsToUse; + curChar += charsToUse; + } + + if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { + curLine++; + curChar = 0; + } + } + + function skip(N, L) { + if (L) { + curLine += L; + curChar = 0; + } else { + if (curLineOpIter && curLineOpIterLine == curLine) { + consumeAttribRuns(N, function () {}); + } else { + curChar += N; + } + } + } + + function nextText(numChars) { + var len = 0; + var assem = Changeset.stringAssembler(); + var firstString = lines_get(curLine).substring(curChar); + len += firstString.length; + assem.append(firstString); + + var lineNum = curLine + 1; + while (len < numChars) { + var nextString = lines_get(lineNum); + len += nextString.length; + assem.append(nextString); + lineNum++; + } + + return assem.toString().substring(0, numChars); + } + + function cachedStrFunc(func) { + var cache = {}; + return function (s) { + if (!cache[s]) { + cache[s] = func(s); + } + return cache[s]; + }; + } + + var attribKeys = []; + var attribValues = []; + + //iterate over all operators of this changeset + while (csIter.hasNext()) { + var csOp = csIter.next(); + + if (csOp.opcode == '=') { + var textBank = nextText(csOp.chars); + + // decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set. + // If the text this operator applies to is only a star, than this is a false positive and should be ignored + if (csOp.attribs && textBank != "*") { + var deletedAttrib = apool.putAttrib(["removed", true]); + var authorAttrib = apool.putAttrib(["author", ""]);; + + attribKeys.length = 0; + attribValues.length = 0; + Changeset.eachAttribNumber(csOp.attribs, function (n) { + attribKeys.push(apool.getAttribKey(n)); + attribValues.push(apool.getAttribValue(n)); + + if(apool.getAttribKey(n) === "author"){ + authorAttrib = n; + }; + }); + + var undoBackToAttribs = cachedStrFunc(function (attribs) { + var backAttribs = []; + for (var i = 0; i < attribKeys.length; i++) { + var appliedKey = attribKeys[i]; + var appliedValue = attribValues[i]; + var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); + if (appliedValue != oldValue) { + backAttribs.push([appliedKey, oldValue]); + } + } + return Changeset.makeAttribsString('=', backAttribs, apool); + }); + + var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib); + + var textLeftToProcess = textBank; + + while(textLeftToProcess.length > 0){ + //process till the next line break or process only one line break + var lengthToProcess = textLeftToProcess.indexOf("\n"); + var lineBreak = false; + switch(lengthToProcess){ + case -1: + lengthToProcess=textLeftToProcess.length; + break; + case 0: + lineBreak = true; + lengthToProcess=1; + break; + } + + //get the text we want to procceed in this step + var processText = textLeftToProcess.substr(0, lengthToProcess); + textLeftToProcess = textLeftToProcess.substr(lengthToProcess); + + if(lineBreak){ + builder.keep(1, 1); //just skip linebreaks, don't do a insert + keep for a linebreak + + //consume the attributes of this linebreak + consumeAttribRuns(1, function(){}); + } else { + //add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it + var textBankIndex = 0; + consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { + //get the old attributes back + var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; + + builder.insert(processText.substr(textBankIndex, len), attribs); + textBankIndex += len; + }); + + builder.keep(lengthToProcess, 0); + } + } + } else { + skip(csOp.chars, csOp.lines); + builder.keep(csOp.chars, csOp.lines); + } + } else if (csOp.opcode == '+') { + builder.keep(csOp.chars, csOp.lines); + } else if (csOp.opcode == '-') { + var textBank = nextText(csOp.chars); + var textBankIndex = 0; + + consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); + textBankIndex += len; + }); + } + } + + return Changeset.checkRep(builder.toString()); +}; + +//export the constructor +module.exports = padDiffHTML; From dea892213e080b7637955413222384959b3b267f Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 18:41:04 +0000 Subject: [PATCH 17/39] Revert "allow non ascii chars in txt export" This reverts commit be56272e66f4921f68fe12cd05cbaad8eb54894b. --- src/node/utils/ExportHelper.js | 6 +- src/node/utils/ExportHtml.js | 7 +- src/node/utils/ExportTxt.js | 10 +- src/node/utils/padDiffHTML.js | 554 --------------------------------- 4 files changed, 15 insertions(+), 562 deletions(-) delete mode 100644 src/node/utils/padDiffHTML.js diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index 41d440f30..a939a8b6e 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -80,4 +80,8 @@ exports._analyzeLine = function(text, aline, apool){ } - +exports._encodeWhitespace = function(s){ + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ + return "&#" +c.charCodeAt(0) + ";" + }); +} diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 51a4b2c3d..585694d4b 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -23,6 +23,7 @@ var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText var _analyzeLine = require('./ExportHelper')._analyzeLine; +var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; function getPadHTML(pad, revNum, callback) { @@ -595,9 +596,3 @@ function _processSpaces(s){ return parts.join(''); } -function _encodeWhitespace(s){ - return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){ - return "&#" +c.charCodeAt(0) + ";" - }); -} - diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 4a3e458b4..05847f162 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -26,6 +26,7 @@ var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText; var _analyzeLine = require('./ExportHelper')._analyzeLine; +var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; // This is slightly different than the HTML method as it passes the output to getTXTFromAText function getPadTXT(pad, revNum, callback) @@ -111,6 +112,7 @@ function getTXTFromAtext(pad, atext, authorColors) var taker = Changeset.stringIterator(text); var assem = Changeset.stringAssembler(); var openTags = []; + var idx = 0; function processNextChars(numChars) @@ -223,7 +225,7 @@ function getTXTFromAtext(pad, atext, authorColors) // plugins from being able to display * at the beginning of a line // s = s.replace("*", ""); // Then remove it - assem.append(s); + assem.append(_encodeWhitespace(s)); } // end iteration over spans in line var tags2close = []; @@ -290,3 +292,9 @@ exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) }); } +function _encodeWhitespace(s) { + return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) + { + return "&#" +c.charCodeAt(0) + ";" + }); +} diff --git a/src/node/utils/padDiffHTML.js b/src/node/utils/padDiffHTML.js deleted file mode 100644 index 00e967287..000000000 --- a/src/node/utils/padDiffHTML.js +++ /dev/null @@ -1,554 +0,0 @@ -var Changeset = require("../../static/js/Changeset"); -var async = require("async"); -var exportHtml = require('./ExportHtml'); - -function padDiffHTML (pad, fromRev, toRev){ - //check parameters - if(!pad || !pad.id || !pad.atext || !pad.pool) - { - throw new Error('Invalid pad'); - } - - var range = pad.getValidRevisionRange(fromRev, toRev); - if(!range) { throw new Error('Invalid revision range.' + - ' startRev: ' + fromRev + - ' endRev: ' + toRev); } - - this._pad = pad; - this._fromRev = range.startRev; - this._toRev = range.endRev; - this._html = null; - this._authors = []; -} - -padDiffHTML.prototype._isClearAuthorship = function(changeset){ - //unpack - var unpacked = Changeset.unpack(changeset); - - //check if there is nothing in the charBank - if(unpacked.charBank !== "") - return false; - - //check if oldLength == newLength - if(unpacked.oldLen !== unpacked.newLen) - return false; - - //lets iterator over the operators - var iterator = Changeset.opIterator(unpacked.ops); - - //get the first operator, this should be a clear operator - var clearOperator = iterator.next(); - - //check if there is only one operator - if(iterator.hasNext() === true) - return false; - - //check if this operator doesn't change text - if(clearOperator.opcode !== "=") - return false; - - //check that this operator applys to the complete text - //if the text ends with a new line, its exactly one character less, else it has the same length - if(clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) - return false; - - var attributes = []; - Changeset.eachAttribNumber(changeset, function(attrNum){ - attributes.push(attrNum); - }); - - //check that this changeset uses only one attribute - if(attributes.length !== 1) - return false; - - var appliedAttribute = this._pad.pool.getAttrib(attributes[0]); - - //check if the applied attribute is an anonymous author attribute - if(appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") - return false; - - return true; -} - -padDiffHTML.prototype._createClearAuthorship = function(rev, callback){ - var self = this; - this._pad.getInternalRevisionAText(rev, function(err, atext){ - if(err){ - return callback(err); - } - - //build clearAuthorship changeset - var builder = Changeset.builder(atext.text.length); - builder.keepText(atext.text, [['author','']], self._pad.pool); - var changeset = builder.toString(); - - callback(null, changeset); - }); -} - -padDiffHTML.prototype._createClearStartAtext = function(rev, callback){ - var self = this; - - //get the atext of this revision - this._pad.getInternalRevisionAText(rev, function(err, atext){ - if(err){ - return callback(err); - } - - //create the clearAuthorship changeset - self._createClearAuthorship(rev, function(err, changeset){ - if(err){ - return callback(err); - } - - //apply the clearAuthorship changeset - var newAText = Changeset.applyToAText(changeset, atext, self._pad.pool); - - callback(null, newAText); - }); - }); -} - -padDiffHTML.prototype._getChangesetsInBulk = function(startRev, count, callback) { - var self = this; - - //find out which revisions we need - var revisions = []; - for(var i=startRev;i<(startRev+count) && i<=this._pad.head;i++){ - revisions.push(i); - } - - var changesets = [], authors = []; - - //get all needed revisions - async.forEach(revisions, function(rev, callback){ - self._pad.getRevision(rev, function(err, revision){ - if(err){ - return callback(err) - } - - var arrayNum = rev-startRev; - - changesets[arrayNum] = revision.changeset; - authors[arrayNum] = revision.meta.author; - - callback(); - }); - }, function(err){ - callback(err, changesets, authors); - }); -} - -padDiffHTML.prototype._addAuthors = function(authors) { - var self = this; - //add to array if not in the array - authors.forEach(function(author){ - if(self._authors.indexOf(author) == -1){ - self._authors.push(author); - } - }); -} - -padDiffHTML.prototype._createDiffAtext = function(callback) { - var self = this; - var bulkSize = 100; - - //get the cleaned startAText - self._createClearStartAtext(self._fromRev, function(err, atext){ - if(err) { return callback(err); } - - var superChangeset = null; - - var rev = self._fromRev + 1; - - //async while loop - async.whilst( - //loop condition - function () { return rev <= self._toRev; }, - - //loop body - function (callback) { - //get the bulk - self._getChangesetsInBulk(rev,bulkSize,function(err, changesets, authors){ - var addedAuthors = []; - - //run trough all changesets - for(var i=0;i= curChar) { - curLineNextOp.chars -= (curChar - indexIntoLine); - done = true; - } else { - indexIntoLine += curLineNextOp.chars; - } - } - } - - while (numChars > 0) { - if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { - curLine++; - curChar = 0; - curLineOpIterLine = curLine; - curLineNextOp.chars = 0; - curLineOpIter = Changeset.opIterator(alines_get(curLine)); - } - if (!curLineNextOp.chars) { - curLineOpIter.next(curLineNextOp); - } - var charsToUse = Math.min(numChars, curLineNextOp.chars); - func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); - numChars -= charsToUse; - curLineNextOp.chars -= charsToUse; - curChar += charsToUse; - } - - if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) { - curLine++; - curChar = 0; - } - } - - function skip(N, L) { - if (L) { - curLine += L; - curChar = 0; - } else { - if (curLineOpIter && curLineOpIterLine == curLine) { - consumeAttribRuns(N, function () {}); - } else { - curChar += N; - } - } - } - - function nextText(numChars) { - var len = 0; - var assem = Changeset.stringAssembler(); - var firstString = lines_get(curLine).substring(curChar); - len += firstString.length; - assem.append(firstString); - - var lineNum = curLine + 1; - while (len < numChars) { - var nextString = lines_get(lineNum); - len += nextString.length; - assem.append(nextString); - lineNum++; - } - - return assem.toString().substring(0, numChars); - } - - function cachedStrFunc(func) { - var cache = {}; - return function (s) { - if (!cache[s]) { - cache[s] = func(s); - } - return cache[s]; - }; - } - - var attribKeys = []; - var attribValues = []; - - //iterate over all operators of this changeset - while (csIter.hasNext()) { - var csOp = csIter.next(); - - if (csOp.opcode == '=') { - var textBank = nextText(csOp.chars); - - // decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set. - // If the text this operator applies to is only a star, than this is a false positive and should be ignored - if (csOp.attribs && textBank != "*") { - var deletedAttrib = apool.putAttrib(["removed", true]); - var authorAttrib = apool.putAttrib(["author", ""]);; - - attribKeys.length = 0; - attribValues.length = 0; - Changeset.eachAttribNumber(csOp.attribs, function (n) { - attribKeys.push(apool.getAttribKey(n)); - attribValues.push(apool.getAttribValue(n)); - - if(apool.getAttribKey(n) === "author"){ - authorAttrib = n; - }; - }); - - var undoBackToAttribs = cachedStrFunc(function (attribs) { - var backAttribs = []; - for (var i = 0; i < attribKeys.length; i++) { - var appliedKey = attribKeys[i]; - var appliedValue = attribValues[i]; - var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); - if (appliedValue != oldValue) { - backAttribs.push([appliedKey, oldValue]); - } - } - return Changeset.makeAttribsString('=', backAttribs, apool); - }); - - var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib); - - var textLeftToProcess = textBank; - - while(textLeftToProcess.length > 0){ - //process till the next line break or process only one line break - var lengthToProcess = textLeftToProcess.indexOf("\n"); - var lineBreak = false; - switch(lengthToProcess){ - case -1: - lengthToProcess=textLeftToProcess.length; - break; - case 0: - lineBreak = true; - lengthToProcess=1; - break; - } - - //get the text we want to procceed in this step - var processText = textLeftToProcess.substr(0, lengthToProcess); - textLeftToProcess = textLeftToProcess.substr(lengthToProcess); - - if(lineBreak){ - builder.keep(1, 1); //just skip linebreaks, don't do a insert + keep for a linebreak - - //consume the attributes of this linebreak - consumeAttribRuns(1, function(){}); - } else { - //add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it - var textBankIndex = 0; - consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { - //get the old attributes back - var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; - - builder.insert(processText.substr(textBankIndex, len), attribs); - textBankIndex += len; - }); - - builder.keep(lengthToProcess, 0); - } - } - } else { - skip(csOp.chars, csOp.lines); - builder.keep(csOp.chars, csOp.lines); - } - } else if (csOp.opcode == '+') { - builder.keep(csOp.chars, csOp.lines); - } else if (csOp.opcode == '-') { - var textBank = nextText(csOp.chars); - var textBankIndex = 0; - - consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { - builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); - textBankIndex += len; - }); - } - } - - return Changeset.checkRep(builder.toString()); -}; - -//export the constructor -module.exports = padDiffHTML; From aefd8d8d0dd33a93a4a930816555332f66156c47 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 18:45:45 +0000 Subject: [PATCH 18/39] nice chars n no cruft --- src/node/utils/ExportTxt.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 05847f162..c57424f1d 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -26,7 +26,6 @@ var Security = require('ep_etherpad-lite/static/js/security'); var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); var getPadPlainText = require('./ExportHelper').getPadPlainText; var _analyzeLine = require('./ExportHelper')._analyzeLine; -var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; // This is slightly different than the HTML method as it passes the output to getTXTFromAText function getPadTXT(pad, revNum, callback) @@ -225,7 +224,7 @@ function getTXTFromAtext(pad, atext, authorColors) // plugins from being able to display * at the beginning of a line // s = s.replace("*", ""); // Then remove it - assem.append(_encodeWhitespace(s)); + assem.append(s); } // end iteration over spans in line var tags2close = []; @@ -292,9 +291,3 @@ exports.getPadTXTDocument = function (padId, revNum, noDocType, callback) }); } -function _encodeWhitespace(s) { - return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c) - { - return "&#" +c.charCodeAt(0) + ";" - }); -} From 1607af87531eb8bbaf76e82ff1e79b2cdff0e6a3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 20:12:46 +0000 Subject: [PATCH 19/39] Bitcoin yo. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e254b81d3..732a13d12 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and mak # Donate! * [Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation) * Paypal - Press the donate button on [etherpad.org](http://etherpad.org) +* Bitcoin - (https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a) # License [Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html) From c3972bcb87ccd9ac5fe0129a6a0dfe724f2be9ae Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 13 Feb 2013 20:14:15 +0000 Subject: [PATCH 20/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 732a13d12..fa0da3639 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and mak # Donate! * [Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation) * Paypal - Press the donate button on [etherpad.org](http://etherpad.org) -* Bitcoin - (https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a) +* [Bitcoin] (https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a) # License [Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html) From 025a3aa7246be46c310f2d75e0c704ce1319c036 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Sat, 16 Feb 2013 05:10:29 +0000 Subject: [PATCH 21/39] Localisation updates from http://translatewiki.net. --- src/locales/br.json | 56 ++++++++++++++++++++++++++++++++++++++++ src/locales/ja.json | 4 +-- src/locales/ksh.json | 18 ++++++++++++- src/locales/pt-br.json | 4 ++- src/locales/zh-hans.json | 2 +- src/locales/zh-hant.json | 7 ++--- 6 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 src/locales/br.json diff --git a/src/locales/br.json b/src/locales/br.json new file mode 100644 index 000000000..d53ce591b --- /dev/null +++ b/src/locales/br.json @@ -0,0 +1,56 @@ +{ + "index.newPad": "Pad nevez", + "index.createOpenPad": "pe kroui\u00f1\/digeri\u00f1 ur pad gant an anv :", + "pad.toolbar.bold.title": "Tev (Ctrl-B)", + "pad.toolbar.italic.title": "Italek (Ctrl-I)", + "pad.toolbar.underline.title": "Islinenna\u00f1 (Ctrl-U)", + "pad.toolbar.indent.title": "Endanta\u00f1", + "pad.toolbar.unindent.title": "Diendanta\u00f1", + "pad.toolbar.settings.title": "Arventenno\u00f9", + "pad.colorpicker.save": "Enrolla\u00f1", + "pad.colorpicker.cancel": "Nulla\u00f1", + "pad.loading": "O karga\u00f1...", + "pad.settings.myView": "Ma diskwel", + "pad.settings.fontType.normal": "Reizh", + "pad.settings.language": "Yezh :", + "pad.importExport.import_export": "Enporzhia\u00f1\/Ezporzhia\u00f1", + "pad.importExport.import": "Enkarga\u00f1 un destenn pe ur restr", + "pad.importExport.exporthtml": "HTML", + "pad.importExport.exportplain": "Testenn blaen", + "pad.importExport.exportword": "Microsoft Word", + "pad.importExport.exportpdf": "PDF", + "pad.importExport.exportopen": "ODF (Open Document Format)", + "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.modals.connected": "Kevreet.", + "pad.modals.slowcommit": "Digevreet.", + "pad.share.readonly": "Lenn hepken", + "pad.share.link": "Liamm", + "pad.chat": "Flap", + "timeslider.toolbar.authors": "Aozerien :", + "timeslider.toolbar.authorsList": "Aozer ebet", + "timeslider.toolbar.exportlink.title": "Ezporzhia\u00f1", + "timeslider.month.january": "Genver", + "timeslider.month.february": "C'hwevrer", + "timeslider.month.march": "Meurzh", + "timeslider.month.april": "Ebrel", + "timeslider.month.may": "Mae", + "timeslider.month.june": "Mezheven", + "timeslider.month.july": "Gouere", + "timeslider.month.august": "Eost", + "timeslider.month.september": "Gwengolo", + "timeslider.month.october": "Here", + "timeslider.month.november": "Du", + "timeslider.month.december": "Kerzu", + "pad.userlist.guest": "Den pedet", + "pad.userlist.deny": "Nac'h", + "pad.userlist.approve": "Aproui\u00f1", + "pad.impexp.importbutton": "Enporzhia\u00f1 brema\u00f1", + "pad.impexp.importing": "Oc'h enporzhia\u00f1...", + "pad.impexp.importfailed": "C'hwitet eo an enporzhiadenn", + "@metadata": { + "authors": [ + "Fulup", + "Y-M D" + ] + } +} \ No newline at end of file diff --git a/src/locales/ja.json b/src/locales/ja.json index c2f6da27b..f7173dd4f 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "\u4f5c\u8005\u306e\u8272\u5206\u3051\u3092\u6d88\u53bb", "pad.toolbar.import_export.title": "\u4ed6\u306e\u5f62\u5f0f\u306e\u30d5\u30a1\u30a4\u30eb\u306e\u30a4\u30f3\u30dd\u30fc\u30c8\/\u30a8\u30af\u30b9\u30dd\u30fc\u30c8", "pad.toolbar.timeslider.title": "\u30bf\u30a4\u30e0\u30b9\u30e9\u30a4\u30c0\u30fc", - "pad.toolbar.savedRevision.title": "\u4fdd\u5b58\u6e08\u307f\u306e\u7248", + "pad.toolbar.savedRevision.title": "\u7248\u3092\u4fdd\u5b58", "pad.toolbar.settings.title": "\u8a2d\u5b9a", "pad.toolbar.embed.title": "\u3053\u306e\u30d1\u30c3\u30c9\u3092\u57cb\u3081\u8fbc\u3080", "pad.toolbar.showusers.title": "\u3053\u306e\u30d1\u30c3\u30c9\u306e\u30e6\u30fc\u30b6\u30fc\u3092\u8868\u793a", @@ -87,7 +87,7 @@ "timeslider.exportCurrent": "\u73fe\u5728\u306e\u7248\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3059\u308b\u5f62\u5f0f:", "timeslider.version": "\u30d0\u30fc\u30b8\u30e7\u30f3 {{version}}", "timeslider.saved": "| {{year}}\u5e74{{month}}{{day}}\u65e5\u306b\u4fdd\u5b58", - "timeslider.dateformat": "{{year}}\u5e74{{month}}{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.dateformat": "{{year}}\u5e74{{month}}\u6708{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "1\u6708", "timeslider.month.february": "2\u6708", "timeslider.month.march": "3\u6708", diff --git a/src/locales/ksh.json b/src/locales/ksh.json index e4557e855..648f95060 100644 --- a/src/locales/ksh.json +++ b/src/locales/ksh.json @@ -19,13 +19,16 @@ "pad.toolbar.clearAuthorship.title": "d\u00e4 Schriiver ier F\u00e4rve fottn\u00e4mme", "pad.toolbar.import_export.title": "Vun ongerscheidlijje Dattei_Fommaate empotteere udder \u00e4xpotteere", "pad.toolbar.timeslider.title": "Verjangeheid afschpelle", - "pad.toolbar.savedRevision.title": "Fa\u00dfjehallde Versione", + "pad.toolbar.savedRevision.title": "de Versjohn fa\u00dfhallde", "pad.toolbar.settings.title": "Enscht\u00e4llonge", "pad.toolbar.embed.title": "Donn dat Padd enbenge", "pad.toolbar.showusers.title": "Verbonge Metschriiver aanzeije", "pad.colorpicker.save": "Fa\u00dfhallde", "pad.colorpicker.cancel": "Oph\u00fc\u00fcre", "pad.loading": "Aam Laade …", + "pad.passwordRequired": "Do bruchs e Pa\u00dfwoot f\u00f6r heh dat P\u00e4dd.", + "pad.permissionDenied": "Do h\u00e4s nit dat R\u00e4\u00e4sch, op heh dat P\u00e4dd zohzejriife.", + "pad.wrongPassword": "Ding Pa\u00dfwoot wohr verkeht.", "pad.settings.padSettings": "Dam P\u00e4dd sin Enscht\u00e4llonge", "pad.settings.myView": "Anseesch", "pad.settings.stickychat": "Donn der Klaaf emmer aanzeije", @@ -38,6 +41,7 @@ "pad.settings.language": "Schprooch:", "pad.importExport.import_export": "Empoot\/\u00c4xpoot", "pad.importExport.import": "Donn jeede T\u00e4x udder jeede Zoot Dokem\u00e4nt huhlaade", + "pad.importExport.importSuccessful": "Jeschaff!", "pad.importExport.export": "Don dat P\u00e4dd \u00e4xpoteere al\u00df:", "pad.importExport.exporthtml": "HTML", "pad.importExport.exportplain": "Eijfach T\u00e4x", @@ -45,9 +49,11 @@ "pad.importExport.exportpdf": "PDF (Poteerbaa Dokem\u00e4nte Fommaat)", "pad.importExport.exportopen": "ODF (Offe Dokem\u00e4nte-Fommaat)", "pad.importExport.exportdokuwiki": "DokuWiki", + "pad.importExport.abiword.innerHTML": "Mer k\u00fcnne blo\u00df eijfaache T\u00e4xte udder HTML_Fommaate empoteere. Opw\u00e4ndejere M\u00fcjjeleschkeite f\u00f6 der Empoot jon och, dof\u00f6r bruch mer en Enschtallazjuhn met Abiword<\/i><\/a>.", "pad.modals.connected": "Verbonge.", "pad.modals.reconnecting": "Ben wider aam Verbenge …", "pad.modals.forcereconnect": "Wider verbenge", + "pad.modals.userdup": "En enem andere Finster en \u00c4rbeid", "pad.modals.userdup.explanation": "Heh dat Padd schingk en mieh wi einem Finster vun enem Brauser op heh d\u00e4m R\u00e4\u00e4schner op ze sin.", "pad.modals.userdup.advice": "En heh d\u00e4m Finster wider verbenge.", "pad.modals.unauth": "Nit ber\u00e4\u00e4schtesch", @@ -72,10 +78,12 @@ "pad.share.emebdcode": "URL enboue", "pad.chat": "Klaaf", "pad.chat.title": "Maach d\u00e4 Klaaf f\u00f6r heh dat P\u00e4dd op", + "pad.chat.loadmessages": "Mieh Nohresschte laade...", "timeslider.pageTitle": "{{appTitle}} - Verjangeheid affschpelle", "timeslider.toolbar.returnbutton": "Jangk retuur nohm P\u00e4dd", "timeslider.toolbar.authors": "Schriiver:", "timeslider.toolbar.authorsList": "Kein Schriivere", + "timeslider.toolbar.exportlink.title": "\u00c4xpoot", "timeslider.exportCurrent": "Donn de meu\u00dfte V\u00e4sjohn \u00e4xpotteere al\u00df:", "timeslider.version": "V\u00e4sjon {{version}}", "timeslider.saved": "Fa\u00dfjehallde aam {{day}}. {{month}} {{year}}", @@ -92,11 +100,19 @@ "timeslider.month.october": "Oktoober", "timeslider.month.november": "Nov\u00e4mber", "timeslider.month.december": "Dez\u00e4mber", + "timeslider.unnamedauthor": "{{num}} naameloose Schriever", + "timeslider.unnamedauthors": "{{num}} naameloose Schriever", + "pad.savedrevs.marked": "Heh di V\u00e4sjohn es j\u00e4z fa\u00dfjehallde.", "pad.userlist.entername": "Jif Dinge Naame en", "pad.userlist.unnamed": "naamelo\u00df\u00df", "pad.userlist.guest": "Ja\u00df\u00df", "pad.userlist.deny": "Aflehne", + "pad.userlist.approve": "Joodhei\u00dfe", + "pad.editbar.clearcolors": "Sulle mer de F\u00e4rve f\u00f6r de Schriiver uss_em janze T\u00e4x fott maache?", + "pad.impexp.importbutton": "J\u00e4z empoteere", "pad.impexp.importing": "Ben aam Empotteere …", + "pad.impexp.confirmimport": "En Dattei ze empotteere m\u00e4\u00e4t der janze T\u00e4x em P\u00e4dd fott. Wess De dat verfaftesch hann?", + "pad.impexp.convertFailed": "Mer kunnte di Dattei nit empoteere. Nemm en ander Dattei-Fommaat udder donn d\u00e4 T\u00e4x vun Hand kopeere un ennf\u00f6\u00f6je.", "pad.impexp.uploadFailed": "Et Huhlaade es don\u00e4vve jejange, bes esu jood un probeer et norr_ens", "pad.impexp.importfailed": "Et Empoteere es don\u00e4vve jejange", "pad.impexp.copypaste": "Bes esu jood un donn et koppeere un enf\u00f6\u00f6je", diff --git a/src/locales/pt-br.json b/src/locales/pt-br.json index 6562681a6..e029165db 100644 --- a/src/locales/pt-br.json +++ b/src/locales/pt-br.json @@ -19,7 +19,7 @@ "pad.toolbar.clearAuthorship.title": "Limpar as cores de identifica\u00e7\u00e3o de autoria", "pad.toolbar.import_export.title": "Importar\/Exportar de\/para diferentes formatos de arquivo", "pad.toolbar.timeslider.title": "Linha do tempo", - "pad.toolbar.savedRevision.title": "Revis\u00f5es Salvas", + "pad.toolbar.savedRevision.title": "Salvar revis\u00e3o", "pad.toolbar.settings.title": "Configura\u00e7\u00f5es", "pad.toolbar.embed.title": "Incorporar esta Nota", "pad.toolbar.showusers.title": "Mostrar os usuarios nesta Nota", @@ -100,6 +100,8 @@ "timeslider.month.october": "Outubro", "timeslider.month.november": "Novembro", "timeslider.month.december": "Dezembro", + "timeslider.unnamedauthor": "{{num}} autor desconhecido", + "timeslider.unnamedauthors": "{{num}} autores desconhecidos", "pad.savedrevs.marked": "Esta revis\u00e3o foi marcada como salva", "pad.userlist.entername": "Insira o seu nome", "pad.userlist.unnamed": "Sem t\u00edtulo", diff --git a/src/locales/zh-hans.json b/src/locales/zh-hans.json index 7e29a3aef..a17358266 100644 --- a/src/locales/zh-hans.json +++ b/src/locales/zh-hans.json @@ -20,7 +20,7 @@ "pad.toolbar.redo.title": "\u91cd\u505a (Ctrl-Y)", "pad.toolbar.clearAuthorship.title": "\u6e05\u9664\u4f5c\u540d\u989c\u8272", "pad.toolbar.timeslider.title": "\u65f6\u95f4\u8f74", - "pad.toolbar.savedRevision.title": "\u5df2\u4fdd\u5b58\u7684\u4fee\u8ba2", + "pad.toolbar.savedRevision.title": "\u4fdd\u5b58\u4fee\u8ba2", "pad.toolbar.settings.title": "\u8bbe\u7f6e", "pad.toolbar.embed.title": "\u5d4c\u5165\u6b64\u8bb0\u4e8b\u672c", "pad.toolbar.showusers.title": "\u663e\u793a\u6b64\u8bb0\u4e8b\u672c\u7684\u7528\u6237", diff --git a/src/locales/zh-hant.json b/src/locales/zh-hant.json index a5027a521..7c01860e0 100644 --- a/src/locales/zh-hant.json +++ b/src/locales/zh-hant.json @@ -1,7 +1,8 @@ { "@metadata": { "authors": { - "1": "Simon Shek" + "0": "Shirayuki", + "2": "Simon Shek" } }, "index.newPad": "\u65b0Pad", @@ -86,8 +87,8 @@ "timeslider.toolbar.exportlink.title": "\u532f\u51fa", "timeslider.exportCurrent": "\u532f\u51fa\u7576\u524d\u7248\u672c\u70ba\uff1a", "timeslider.version": "\u7248\u672c{{version}}", - "timeslider.saved": "{{year}}{{month}}{{day}}\u4fdd\u5b58", - "timeslider.dateformat": "{{year}}{{month}}{{day}} {{hours}}:{{minutes}}:{{seconds}}", + "timeslider.saved": "{{year}}\u5e74{{month}}{{day}}\u65e5\u4fdd\u5b58", + "timeslider.dateformat": "{{year}}\u5e74{{month}}\u6708{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}", "timeslider.month.january": "1\u6708", "timeslider.month.february": "2\u6708", "timeslider.month.march": "3\u6708", From 93d58b93be53fdefa62e4e3dbe4316799bbab6ba Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Feb 2013 17:13:02 +0000 Subject: [PATCH 22/39] Update src/static/css/iframe_editor.css --- src/static/css/iframe_editor.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/static/css/iframe_editor.css b/src/static/css/iframe_editor.css index dd7452b25..3e19cbbea 100644 --- a/src/static/css/iframe_editor.css +++ b/src/static/css/iframe_editor.css @@ -177,6 +177,10 @@ p { #overlaysdiv { position: absolute; left: -1000px; top: -1000px; } +/* Stops super long lines without being spaces such as aaaaaaaaaaaaaa*100 breaking the editor + Commented out because it stops IE from being able to render the document, crazy IE bug is crazy. */ +/* .ace-line{ - overflow:hidden; /* Stops super long lines without being spaces such as aaaaaaaaaaaaaa*100 breaking the editor */ + overflow:hidden; } +*/ From 6b47fb69d077069521998470b6b8d692181a0555 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Feb 2013 18:01:25 +0000 Subject: [PATCH 23/39] seems to be working pretty well --- src/static/js/ace2_inner.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 8209c9bf8..70b5443bd 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3731,6 +3731,33 @@ function Ace2Inner(){ updateBrowserSelectionFromRep(); }, 200); } + + /* Attempt to apply some sanity to cursor handling in Chrome after a copy / paste event + We have to do this the way we do because rep. doesn't hold the value for keyheld events IE if the user + presses and holds the arrow key */ + if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){ + var isLeftArrow = evt.which === 37; + var isUpArrow = evt.which === 38; + var isRightArrow = evt.which === 39; + var isDownArrow = evt.which === 40; + + var newVisibleLineRange = getVisibleLineRange(); // get the current visible range -- This works great. + var lineHeight = textLineHeight(); // what Is the height of each line? + var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current + var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214 + + if((isUpArrow || isLeftArrow || isRightArrow || isDownArrow) && caretOffsetTop){ // was it an up arrow or left arrow? + var lineNum = Math.round(caretOffsetTop / lineHeight) ; // Get the current Line Number IE 84 + var caretIsVisible = (lineNum > newVisibleLineRange[0] && lineNum < newVisibleLineRange[1]); // Is the cursor in the visible Range IE ie 84 > 14 and 84 < 90? + if(!caretIsVisible){ // is the cursor no longer visible to the user? + // Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum. + // Get the new Y by getting the line number and multiplying by the height of each line. + var newY = lineHeight * (lineNum -1); // -1 to go to the line above + setScrollY(newY); // set the scroll height of the browser + } + } + } + } if (type == "keydown") @@ -3836,7 +3863,6 @@ function Ace2Inner(){ selection.endPoint = getPointForLineAndChar(se); selection.focusAtStart = !! rep.selFocusAtStart; - setSelection(selection); } From 9ca22754328c96b98b7ae8d98e5f1ab0f6b9e291 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 17 Feb 2013 21:35:46 +0100 Subject: [PATCH 24/39] Fixes #1498: Two-part locale specs in lang cookie wouldn't be read correctly --- src/static/js/l10n.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/l10n.js b/src/static/js/l10n.js index a67a7c1a4..c79ea706d 100644 --- a/src/static/js/l10n.js +++ b/src/static/js/l10n.js @@ -1,8 +1,8 @@ (function(document) { // Set language for l10n - var language = document.cookie.match(/language=((\w{2,3})(-w+)?)/); + var language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/); if(language) language = language[1]; - + html10n.bind('indexed', function() { html10n.localize([language, navigator.language, navigator.userLanguage, 'en']) }) From 7e023ce8e1093977717c930237693d0bb5b874c2 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Feb 2013 21:03:19 +0000 Subject: [PATCH 25/39] make the focus jump back to the left if it's required --- src/static/js/ace2_inner.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 8209c9bf8..a4d3580a0 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1622,10 +1622,19 @@ function Ace2Inner(){ lines = ccData.lines; var lineAttribs = ccData.lineAttribs; var linesWrapped = ccData.linesWrapped; + var scrollToTheLeftNeeded = false; if (linesWrapped > 0) { + if(browser.chrome){ + // chrome decides in it's infinite wisdom that its okay to put the browsers visisble window in the middle of the span + // an outcome of this is that the first chars of the string are no longer visible to the user.. Yay chrome.. + // Move the browsers visible area to the left hand side of the span + var scrollToTheLeftNeeded = true; + } // console.log("Editor warning: " + linesWrapped + " long line" + (linesWrapped == 1 ? " was" : "s were") + " hard-wrapped into " + ccData.numLinesAfter + " lines."); + }else{ + var scrollToTheLeftNeeded = false; } if (ss[0] >= 0) selStart = [ss[0] + a + netNumLinesChangeSoFar, ss[1]]; @@ -1692,6 +1701,10 @@ function Ace2Inner(){ //console.log("removed: "+id); }); + if(scrollToTheLeftNeeded){ // needed to stop chrome from breaking the ui when long strings without spaces are pasted + $("#innerdocbody").scrollLeft(0); + } + p.mark("findsel"); // if the nodes that define the selection weren't encountered during // content collection, figure out where those nodes are now. From 51a9ecf1f0ff9a4d3238b90f49a9e7de0809c412 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 17 Feb 2013 21:19:15 +0000 Subject: [PATCH 26/39] better support for other browsers --- src/static/js/ace2_inner.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index a4d3580a0..182e40be5 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1626,15 +1626,14 @@ function Ace2Inner(){ if (linesWrapped > 0) { - if(browser.chrome){ + if(!browser.ie){ // chrome decides in it's infinite wisdom that its okay to put the browsers visisble window in the middle of the span // an outcome of this is that the first chars of the string are no longer visible to the user.. Yay chrome.. // Move the browsers visible area to the left hand side of the span + // Firefox isn't quite so bad, but it's still pretty quirky. var scrollToTheLeftNeeded = true; } // console.log("Editor warning: " + linesWrapped + " long line" + (linesWrapped == 1 ? " was" : "s were") + " hard-wrapped into " + ccData.numLinesAfter + " lines."); - }else{ - var scrollToTheLeftNeeded = false; } if (ss[0] >= 0) selStart = [ss[0] + a + netNumLinesChangeSoFar, ss[1]]; From 48ffbde7316a2aa21d76e300d0a272651b98b389 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 01:10:54 +0000 Subject: [PATCH 27/39] allow colon to indent line --- src/static/js/ace2_inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 70b5443bd..5c0af0f15 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -1897,7 +1897,7 @@ function Ace2Inner(){ var prevLine = rep.lines.prev(thisLine); var prevLineText = prevLine.text; var theIndent = /^ *(?:)/.exec(prevLineText)[0]; - if (/[\[\(\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB; + if (/[\[\(\:\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB; var cs = Changeset.builder(rep.lines.totalWidth()).keep( rep.lines.offsetOfIndex(lineNum), lineNum).insert( theIndent, [ From 77d03d34731f1ec12d77f82fe4c260c4bb9f559a Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 01:40:34 +0000 Subject: [PATCH 28/39] Try to add some sanity to indentation --- src/static/js/ace2_inner.js | 9 +++++---- src/static/js/pad_editbar.js | 5 +---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 5c0af0f15..151a18068 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3287,7 +3287,7 @@ function Ace2Inner(){ listType = /([a-z]+)([12345678])/.exec(listType); var type = listType[1]; var level = Number(listType[2]); - + //detect empty list item; exclude indentation if(text === '*' && type !== "indent") { @@ -3317,8 +3317,10 @@ function Ace2Inner(){ function doIndentOutdent(isOut) { - if (!(rep.selStart && rep.selEnd) || - ((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1)) + if (!((rep.selStart && rep.selEnd) || + ((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1)) && + (isOut != true) + ) { return false; } @@ -3326,7 +3328,6 @@ function Ace2Inner(){ var firstLine, lastLine; firstLine = rep.selStart[0]; lastLine = Math.max(firstLine, rep.selEnd[0] - ((rep.selEnd[1] === 0) ? 1 : 0)); - var mods = []; for (var n = firstLine; n <= lastLine; n++) { diff --git a/src/static/js/pad_editbar.js b/src/static/js/pad_editbar.js index 91a07bf96..cc9f8758c 100644 --- a/src/static/js/pad_editbar.js +++ b/src/static/js/pad_editbar.js @@ -156,10 +156,7 @@ var padeditbar = (function() else if (cmd == 'insertorderedlist') ace.ace_doInsertOrderedList(); else if (cmd == 'indent') { - if (!ace.ace_doIndentOutdent(false)) - { - ace.ace_doInsertUnorderedList(); - } + ace.ace_doIndentOutdent(false); } else if (cmd == 'outdent') { From b30e1de3e50e718769627b68ee3d70c9988b2f89 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 15:12:11 +0000 Subject: [PATCH 29/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa0da3639..aae935d66 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ documented codebase makes it easier for developers to improve the code and contr Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) -that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/johnyma22/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. +that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/etherpad/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. There's also a full-featured plugin framework, allowing you to easily add your own features. Finally, Etherpad Lite comes with translations into tons of different languages! From abadfb7b6a2cb92f3fc736231ed2344e0344f32b Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 15:12:37 +0000 Subject: [PATCH 30/39] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aae935d66..331353a3b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ documented codebase makes it easier for developers to improve the code and contr Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API) -that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/etherpad/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. +that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website. There's also a full-featured plugin framework, allowing you to easily add your own features. Finally, Etherpad Lite comes with translations into tons of different languages! From 4cfac2f62452dfa80707d22d1a4575e6f8659c1d Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 08:29:25 -0800 Subject: [PATCH 31/39] fix extract and checkPad --- bin/extractPadData.js | 4 +++- src/static/js/pluginfw/hooks.js | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bin/extractPadData.js b/bin/extractPadData.js index ea00f953a..1dbab3aa7 100644 --- a/bin/extractPadData.js +++ b/bin/extractPadData.js @@ -36,16 +36,18 @@ async.series([ settings = require('../src/node/utils/Settings'); db = require('../src/node/db/DB'); dirty = require("../src/node_modules/ueberDB/node_modules/dirty")(padId + ".db"); + callback(); }, //intallize the database function (callback) { +console.log("dbe init"); db.init(callback); }, //get the pad function (callback) { - padManager = require('../node/db/PadManager'); + padManager = require('../src/node/db/PadManager'); padManager.getPad(padId, function(err, _pad) { diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js index d9a14d857..4d5057949 100644 --- a/src/static/js/pluginfw/hooks.js +++ b/src/static/js/pluginfw/hooks.js @@ -70,10 +70,12 @@ exports.flatten = function (lst) { exports.callAll = function (hook_name, args) { if (!args) args = {}; - if (exports.plugins.hooks[hook_name] === undefined) return []; - return _.flatten(_.map(exports.plugins.hooks[hook_name], function (hook) { - return hookCallWrapper(hook, hook_name, args); - }), true); + if (exports.plugins){ + if (exports.plugins.hooks[hook_name] === undefined) return []; + return _.flatten(_.map(exports.plugins.hooks[hook_name], function (hook) { + return hookCallWrapper(hook, hook_name, args); + }), true); + } } exports.aCallAll = function (hook_name, args, cb) { From f76cfad42b89cf58feb7bb91203d6544803009e6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 08:30:42 -0800 Subject: [PATCH 32/39] remove cruft --- bin/extractPadData.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/extractPadData.js b/bin/extractPadData.js index 1dbab3aa7..4210e18d8 100644 --- a/bin/extractPadData.js +++ b/bin/extractPadData.js @@ -41,7 +41,6 @@ async.series([ //intallize the database function (callback) { -console.log("dbe init"); db.init(callback); }, //get the pad From 3f87a32a1289738ddbfebec44865c1532e3ec6e3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 18:07:01 +0000 Subject: [PATCH 33/39] timestlider script block --- src/templates/timeslider.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index 4a8543c56..902c71918 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -40,8 +40,10 @@ <% e.end_block(); %> + <% e.begin_block("timesliderScripts"); %> + <% e.end_block(); %> <% e.begin_block("timesliderBody"); %> From fb97920163b740097f67c00f19ec06ec8a891679 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 19:32:07 +0000 Subject: [PATCH 34/39] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e3a5d77f..107bd2587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,13 @@ * NEW: Plugins can now provide their own frontend tests * NEW: Improved server-side logging * NEW: Admin dashboard mobile device support and new hooks for Admin dashboard + * NEW: Get current API version from API + * Fix: Text Export indentation now supports multiple indentations * Fix: Bugfix getChatHistory API method + * Fix: Make colons on end of line create 4 spaces on indent + * Fix: Make indent when on middle of the line stop creating list + * Fix: Stop long strings breaking the UX by moving focus away from beginning of line + * Fix: padUsersCount no longer hangs server * Fix: Make plugin search case insensitive * Fix: Indentation and bullets on text export * Fix: Resolve various warnings on dependencies during install From c37875e09ada4b1c70a6d520bf04b1d79db02e9b Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 19:33:31 +0000 Subject: [PATCH 35/39] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107bd2587..c1b38380a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,12 @@ * NEW: Get current API version from API * Fix: Text Export indentation now supports multiple indentations * Fix: Bugfix getChatHistory API method + * Fix: Stop Chrome losing caret after paste is texted * Fix: Make colons on end of line create 4 spaces on indent * Fix: Make indent when on middle of the line stop creating list * Fix: Stop long strings breaking the UX by moving focus away from beginning of line * Fix: padUsersCount no longer hangs server + * Fix: Issue with two part locale specs not working * Fix: Make plugin search case insensitive * Fix: Indentation and bullets on text export * Fix: Resolve various warnings on dependencies during install From 19964498f3b66a1c98da29c92f3f3f7532fd4d9e Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 20:38:32 +0000 Subject: [PATCH 36/39] no need to parse already parsed json --- bin/extractPadData.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/extractPadData.js b/bin/extractPadData.js index 4210e18d8..c18af5b0e 100644 --- a/bin/extractPadData.js +++ b/bin/extractPadData.js @@ -83,7 +83,10 @@ async.series([ db.db.db.wrappedDB.get(dbkey, function(err, dbvalue) { if(err) { callback(err); return} - dbvalue=JSON.parse(dbvalue); + + if(typeof dbvalue != 'object'){ + dbvalue=JSON.parse(dbvalue); // if its not json then parse it as json + } dirty.set(dbkey, dbvalue, callback); }); From 0a195895097bff67e2c06652938d22393b91bbf6 Mon Sep 17 00:00:00 2001 From: John McLear Date: Mon, 18 Feb 2013 20:40:34 +0000 Subject: [PATCH 37/39] fix path for windows --- bin/extractPadData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/extractPadData.js b/bin/extractPadData.js index c18af5b0e..0d1acaaee 100644 --- a/bin/extractPadData.js +++ b/bin/extractPadData.js @@ -35,7 +35,7 @@ async.series([ function(callback) { settings = require('../src/node/utils/Settings'); db = require('../src/node/db/DB'); - dirty = require("../src/node_modules/ueberDB/node_modules/dirty")(padId + ".db"); + dirty = require("../node_modules/ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty")(padId + ".db"); callback(); }, //intallize the database From fb3e4a6232a9e58de22bcb37aa23bb568ac1fcc3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 20 Feb 2013 16:10:27 +0000 Subject: [PATCH 38/39] only show clients on this pad resolves issue #1544 --- 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 eaf24b7a2..7ffeb6e5a 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -1035,7 +1035,7 @@ function handleClientReady(client, message) } // notify all existing users about new user - client.broadcast.to(padIds.padIds).json.send(messageToTheOtherUsers); + client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers); //Run trough all sessions of this pad async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback) From 813583c9cebbf96381358c607fc57874d3321c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uli=20K=C3=B6hler?= Date: Wed, 20 Feb 2013 21:38:24 +0100 Subject: [PATCH 39/39] Update link to dev guidelines --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 331353a3b..91410b873 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ You know all this and just want to know how you can help? Look at the [TODO list](https://github.com/ether/etherpad-lite/wiki/TODO) and our [Issue tracker](https://github.com/ether/etherpad-lite/issues). (Please consider using [jshint](http://www.jshint.com/about/), if you plan to contribute code.) -Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/wiki/Developer-Guidelines), really! +Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), really! # Get in touch Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)!