In the Plain Text mode, when changing paragraph format to UL/OL, its width was not properly set, which drew the long lines unwrapped. Switching to HTML mode and back did set the expected wrap width. Similarly, when converting HTML into plain text (on send), the wrap width did not count width of the UL/OL prefix, making the line longer than it should be.
1035 lines
27 KiB
JavaScript
1035 lines
27 KiB
JavaScript
/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* Copyright (C) 2019 Red Hat (www.redhat.com)
|
|
*
|
|
* This library is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/* semi-convention: private functions start with lower-case letter,
|
|
public functions start with upper-case letter. */
|
|
|
|
var EvoConvert = {
|
|
MIN_PARAGRAPH_WIDTH : 5, // in characters
|
|
MIN_OL_WIDTH : 6, // includes ". " at the end
|
|
TAB_WIDTH : 8, // in characters
|
|
|
|
ALIGN_LEFT : 0,
|
|
ALIGN_CENTER : 1,
|
|
ALIGN_RIGHT : 2,
|
|
ALIGN_JUSTIFY : 3,
|
|
|
|
NOWRAP_CHAR_START : "\x01",
|
|
NOWRAP_CHAR_END : "\x02"
|
|
};
|
|
|
|
EvoConvert.GetOLMaxLetters = function(type, levels)
|
|
{
|
|
if (type && type.toUpperCase() == "I") {
|
|
if (levels < 2)
|
|
return 1;
|
|
if (levels < 3)
|
|
return 2;
|
|
if (levels < 8)
|
|
return 3;
|
|
if (levels < 18)
|
|
return 4;
|
|
if (levels < 28)
|
|
return 5;
|
|
if (levels < 38)
|
|
return 6;
|
|
if (levels < 88)
|
|
return 7
|
|
if (levels < 188)
|
|
return 8;
|
|
if (levels < 288)
|
|
return 9;
|
|
if (levels < 388)
|
|
return 10;
|
|
if (levels < 888)
|
|
return 11;
|
|
return 12;
|
|
} else if (type && type.toUpperCase() == "A") {
|
|
if (levels < 27)
|
|
return 1;
|
|
if (levels < 703)
|
|
return 2;
|
|
if (levels < 18279)
|
|
return 3;
|
|
return 4;
|
|
} else {
|
|
if (levels < 10)
|
|
return 1;
|
|
if (levels < 100)
|
|
return 2;
|
|
if (levels < 1000)
|
|
return 3;
|
|
if (levels < 10000)
|
|
return 4;
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
EvoConvert.getOLIndexValue = function(type, value)
|
|
{
|
|
var str = "";
|
|
|
|
if (type == "A" || type == "a") { // alpha
|
|
var add = type.charCodeAt(0);
|
|
|
|
do {
|
|
str = String.fromCharCode(((value - 1) % 26) + add) + str;
|
|
value = Math.floor((value - 1) / 26);
|
|
} while (value);
|
|
} else if (type == "I" || type == "i") { // roman
|
|
var base = "IVXLCDM";
|
|
var b, r, add = 0;
|
|
|
|
if (value > 3999) {
|
|
str = "?";
|
|
} else {
|
|
if (type == "i")
|
|
base = base.toLowerCase();
|
|
|
|
for (b = 0; value > 0 && b < 7 - 1; b += 2, value = Math.floor(value / 10)) {
|
|
r = value % 10;
|
|
if (r != 0) {
|
|
if (r < 4) {
|
|
for (; r; r--)
|
|
str = String.fromCharCode(base.charCodeAt(b) + add) + str;
|
|
} else if (r == 4) {
|
|
str = String.fromCharCode(base.charCodeAt(b + 1) + add) + str;
|
|
str = String.fromCharCode(base.charCodeAt(b) + add) + str;
|
|
} else if (r == 5) {
|
|
str = String.fromCharCode(base.charCodeAt(b + 1) + add) + str;
|
|
} else if (r < 9) {
|
|
for (; r > 5; r--)
|
|
str = String.fromCharCode(base.charCodeAt(b) + add) + str;
|
|
str = String.fromCharCode(base.charCodeAt(b + 1) + add) + str;
|
|
} else if (r == 9) {
|
|
str = String.fromCharCode(base.charCodeAt(b + 2) + add) + str;
|
|
str = String.fromCharCode(base.charCodeAt(b) + add) + str;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else { // numeric
|
|
str = "" + value;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
EvoConvert.getComputedOrNodeStyle = function(node)
|
|
{
|
|
if (!node)
|
|
return null;
|
|
|
|
var parent = node;
|
|
|
|
while (parent && !(parent === document.body)) {
|
|
parent = parent.parentElement;
|
|
}
|
|
|
|
if (parent)
|
|
return window.getComputedStyle(node);
|
|
|
|
return node.style;
|
|
}
|
|
|
|
EvoConvert.replaceList = function(element, tagName, normalDivWidth)
|
|
{
|
|
var ll, lists, type = null;
|
|
|
|
if (tagName == "OL")
|
|
type = "";
|
|
|
|
lists = element.getElementsByTagName(tagName);
|
|
|
|
for (ll = lists.length - 1; ll >= 0; ll--) {
|
|
var list;
|
|
|
|
list = lists.item(ll);
|
|
|
|
if (!list)
|
|
continue;
|
|
|
|
var style = EvoConvert.getComputedOrNodeStyle(list), ltr, ii, prefixSuffix, indent;
|
|
|
|
if (!style)
|
|
style = list.style;
|
|
|
|
ltr = !style || style.direction != "rtl";
|
|
|
|
if (type == null) {
|
|
var level = 0, parent = list;
|
|
|
|
for (parent = list.parentElement; parent; parent = parent.parentElement) {
|
|
if (parent.tagName == "UL" || parent.tagName == "OL")
|
|
level++;
|
|
}
|
|
|
|
switch (level % 3) {
|
|
default:
|
|
case 0:
|
|
prefixSuffix = " * ";
|
|
break;
|
|
case 1:
|
|
prefixSuffix = " - ";
|
|
break;
|
|
case 2:
|
|
prefixSuffix = " + ";
|
|
break;
|
|
}
|
|
|
|
indent = 3;
|
|
} else {
|
|
type = list.getAttribute("type");
|
|
|
|
if (!type)
|
|
type = "";
|
|
|
|
var nChildren = 0, child;
|
|
for (ii = 0; ii < list.children.length; ii++) {
|
|
child = list.children.item(ii);
|
|
if (child.tagName == "LI")
|
|
nChildren++;
|
|
}
|
|
|
|
prefixSuffix = ltr ? ". " : " .";
|
|
indent = EvoConvert.GetOLMaxLetters(type, nChildren) + prefixSuffix.length;
|
|
if (indent < EvoConvert.MIN_OL_WIDTH)
|
|
indent = EvoConvert.MIN_OL_WIDTH;
|
|
}
|
|
|
|
if (list.hasAttribute("x-evo-extra-indent")) {
|
|
var tmp = list.getAttribute("x-evo-extra-indent");
|
|
|
|
if (tmp) {
|
|
tmp = parseInt(tmp);
|
|
|
|
if (!Number.isInteger(tmp))
|
|
tmp = 0;
|
|
} else {
|
|
tmp = 0;
|
|
}
|
|
|
|
indent += tmp;
|
|
}
|
|
|
|
var liCount = 0;
|
|
|
|
for (ii = 0; ii < list.children.length; ii++) {
|
|
var child = list.children.item(ii), node;
|
|
|
|
if (!child)
|
|
continue;
|
|
|
|
// nested lists
|
|
if (child.tagName == "DIV" && child.hasAttribute("x-evo-extra-indent") && child.hasAttribute("x-evo-li-text")) {
|
|
node = child.cloneNode(true);
|
|
|
|
var tmp = child.getAttribute("x-evo-extra-indent");
|
|
|
|
if (tmp) {
|
|
tmp = parseInt(tmp);
|
|
|
|
if (!Number.isInteger(tmp))
|
|
tmp = 0;
|
|
} else {
|
|
tmp = 0;
|
|
}
|
|
|
|
node.setAttribute("x-evo-extra-indent", indent + tmp);
|
|
|
|
tmp = node.getAttribute("x-evo-li-text");
|
|
if (ltr)
|
|
tmp = " ".repeat(indent) + tmp;
|
|
else
|
|
tmp = tmp + " ".repeat(indent);
|
|
|
|
node.setAttribute("x-evo-li-text", tmp);
|
|
} else if (child.tagName == "LI") {
|
|
liCount++;
|
|
|
|
node = document.createElement("DIV");
|
|
if (list.style.width.endsWith("ch")) {
|
|
var width = parseInt(list.style.width.slice(0, -2));
|
|
|
|
if (Number.isInteger(width))
|
|
node.style.width = "" + width + "ch";
|
|
} else if (normalDivWidth > 0) {
|
|
var width, needs;
|
|
|
|
if (tagName == "UL") {
|
|
needs = 3;
|
|
} else {
|
|
needs = EvoConvert.GetOLMaxLetters(list.getAttribute("type"), list.children.length) + 2; // length of ". " suffix
|
|
|
|
if (needs < EvoConvert.MIN_OL_WIDTH)
|
|
needs = EvoConvert.MIN_OL_WIDTH;
|
|
}
|
|
|
|
width = normalDivWidth - needs;
|
|
|
|
if (width < EvoConvert.MIN_PARAGRAPH_WIDTH)
|
|
width = EvoConvert.MIN_PARAGRAPH_WIDTH;
|
|
|
|
node.style.width = "" + width + "ch";
|
|
}
|
|
node.style.textAlign = list.style.testAlign;
|
|
node.style.direction = list.style.direction;
|
|
node.style.marginLeft = list.style.marginLeft;
|
|
node.style.marginRight = list.style.marginRight;
|
|
node.setAttribute("x-evo-extra-indent", indent);
|
|
node.innerText = child.innerText;
|
|
|
|
if (type == null) {
|
|
node.setAttribute("x-evo-li-text", prefixSuffix);
|
|
} else {
|
|
var prefix;
|
|
|
|
prefix = EvoConvert.getOLIndexValue(type, liCount);
|
|
|
|
while (prefix.length + 2 /* prefixSuffix.length */ < indent) {
|
|
prefix = ltr ? " " + prefix : prefix + " ";
|
|
}
|
|
|
|
node.setAttribute("x-evo-li-text", ltr ? prefix + prefixSuffix : prefixSuffix + prefix);
|
|
}
|
|
} else {
|
|
node = child.cloneNode(true);
|
|
|
|
if (node.tagName == "UL" || node.tagName == "OL") {
|
|
var tmp = child.getAttribute("x-evo-extra-indent");
|
|
|
|
if (tmp) {
|
|
tmp = parseInt(tmp);
|
|
|
|
if (!Number.isInteger(tmp))
|
|
tmp = 0;
|
|
} else {
|
|
tmp = 0;
|
|
}
|
|
|
|
node.setAttribute("x-evo-extra-indent", indent + tmp);
|
|
}
|
|
}
|
|
|
|
list.parentNode.insertBefore(node, list);
|
|
}
|
|
|
|
list.parentNode.removeChild(list);
|
|
}
|
|
}
|
|
|
|
EvoConvert.calcLineLetters = function(line)
|
|
{
|
|
var len;
|
|
|
|
if (line.search("\t") >= 0) {
|
|
var jj;
|
|
|
|
len = 0;
|
|
|
|
for (jj = 0; jj < line.length; jj++) {
|
|
if (line.charAt(jj) == "\t") {
|
|
len = len - (len % EvoConvert.TAB_SIZE) + EvoConvert.TAB_SIZE;
|
|
} else {
|
|
len++;
|
|
}
|
|
}
|
|
} else {
|
|
len = line.length;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
EvoConvert.getQuotePrefix = function(quoteLevel, ltr)
|
|
{
|
|
var prefix = "";
|
|
|
|
if (quoteLevel > 0) {
|
|
prefix = ltr ? "> " : " <";
|
|
prefix = prefix.repeat(quoteLevel);
|
|
}
|
|
|
|
return prefix;
|
|
}
|
|
|
|
EvoConvert.formatParagraph = function(str, ltr, align, indent, whiteSpace, wrapWidth, extraIndent, liText, quoteLevel)
|
|
{
|
|
if (!str || str == "")
|
|
return liText ? liText : str;
|
|
|
|
var lines = [], ii;
|
|
|
|
// wrap the string first
|
|
if (wrapWidth > 0) {
|
|
var worker = {
|
|
collapseWhiteSpace : whiteSpace != "pre" && whiteSpace != "pre-wrap",
|
|
collapseEndingWhiteSpace : whiteSpace != "pre",
|
|
canWrap : whiteSpace != "nowrap",
|
|
charWrap : whiteSpace == "pre",
|
|
inAnchor : 0,
|
|
ignoreLineLetters : 0, // used for EvoConvert.NOWRAP_CHAR_START and EvoConvert.NOWRAP_CHAR_END, which should be skipped in width calculation
|
|
useWrapWidth : wrapWidth,
|
|
spacesFrom : -1, // in 'str'
|
|
lastWrapableChar : -1, // in this->line
|
|
lineLetters : 0,
|
|
line : "",
|
|
|
|
maybeRecalcIgnoreLineLetters : function() {
|
|
if (this.ignoreLineLetters) {
|
|
var ii, len = this.line.length, chr;
|
|
|
|
this.ignoreLineLetters = 0;
|
|
|
|
for (ii = 0; ii < len; ii++) {
|
|
chr = this.line[ii];
|
|
|
|
if (chr == EvoConvert.NOWRAP_CHAR_START || chr == EvoConvert.NOWRAP_CHAR_END)
|
|
this.ignoreLineLetters++;
|
|
}
|
|
}
|
|
},
|
|
|
|
mayConsumeWhitespaceAfterWrap : function(str, ii) {
|
|
return ii > 0 && this.line == "" && str[ii - 1] == EvoConvert.NOWRAP_CHAR_END;
|
|
},
|
|
|
|
isInUnwrapPart : function() {
|
|
if (this.inAnchor)
|
|
return true;
|
|
|
|
if (this.line[0] == EvoConvert.NOWRAP_CHAR_START)
|
|
return this.line.indexOf(EvoConvert.NOWRAP_CHAR_END) < 0;
|
|
|
|
return false;
|
|
},
|
|
|
|
shouldWrap : function(nextChar) {
|
|
return this.canWrap && (!this.collapseWhiteSpace || nextChar != '\n') &&
|
|
(!this.isInUnwrapPart() || this.lastWrapableChar != -1) && (this.lineLetters - this.ignoreLineLetters > this.useWrapWidth || (
|
|
((!this.charWrap && (nextChar == " " || nextChar == "\t") && this.lineLetters - this.ignoreLineLetters > this.useWrapWidth) ||
|
|
((this.charWrap || (nextChar != " " && nextChar != "\t")) && this.lineLetters - this.ignoreLineLetters == this.useWrapWidth)) && (
|
|
this.lastWrapableChar == -1/* || this.lastWrapableChar == this.line.length*/)));
|
|
},
|
|
|
|
commitSpaces : function(ii) {
|
|
if (this.spacesFrom != -1 && (!this.canWrap || this.line.length - this.ignoreLineLetters <= this.useWrapWidth)) {
|
|
var spaces;
|
|
|
|
spaces = ii - this.spacesFrom;
|
|
|
|
if (this.canWrap && this.line.length - this.ignoreLineLetters + spaces > this.useWrapWidth)
|
|
spaces = this.useWrapWidth - this.line.length + this.ignoreLineLetters;
|
|
|
|
if (!this.canWrap || (this.line.length - this.ignoreLineLetters + spaces <= this.useWrapWidth) && spaces >= 0) {
|
|
if (this.collapseWhiteSpace && (!extraIndent || lines.length))
|
|
this.line += " ";
|
|
else
|
|
this.line += " ".repeat(spaces);
|
|
}
|
|
|
|
this.spacesFrom = -1;
|
|
this.lastWrapableChar = this.line.length;
|
|
} else if (this.spacesFrom != -1) {
|
|
this.lastWrapableChar = this.line.length;
|
|
}
|
|
},
|
|
|
|
commit : function(ii, force) {
|
|
this.commitSpaces(ii);
|
|
|
|
var didWrap = false;
|
|
|
|
if (this.canWrap && this.lastWrapableChar != -1 && this.lineLetters - this.ignoreLineLetters > this.useWrapWidth) {
|
|
lines[lines.length] = this.line.substr(0, this.lastWrapableChar);
|
|
this.line = this.line.substr(this.lastWrapableChar);
|
|
this.maybeRecalcIgnoreLineLetters();
|
|
didWrap = true;
|
|
} else if (!this.isInUnwrapPart() && this.useWrapWidth != -1 && this.lineLetters - this.ignoreLineLetters > this.useWrapWidth) {
|
|
var jj;
|
|
|
|
if (this.charWrap) {
|
|
var subLineLetters = 0, ignoreSubLineLetters = 0, chr;
|
|
|
|
for(jj = 0; jj < this.line.length; jj++) {
|
|
chr = this.line.charAt(jj);
|
|
|
|
if (chr == "\t") {
|
|
subLineLetters = subLineLetters - ((subLineLetters - ignoreSubLineLetters) % EvoConvert.TAB_SIZE) + EvoConvert.TAB_SIZE;
|
|
} else {
|
|
subLineLetters++;
|
|
}
|
|
|
|
if (chr == EvoConvert.NOWRAP_CHAR_START || chr == EvoConvert.NOWRAP_CHAR_END)
|
|
ignoreSubLineLetters++;
|
|
|
|
if (subLineLetters - ignoreSubLineLetters >= this.useWrapWidth)
|
|
break;
|
|
}
|
|
} else {
|
|
jj = this.line.length;
|
|
}
|
|
|
|
lines[lines.length] = this.line.substr(0, jj);
|
|
this.line = this.line.substr(jj);
|
|
this.maybeRecalcIgnoreLineLetters();
|
|
didWrap = true;
|
|
} else {
|
|
lines[lines.length] = this.line;
|
|
this.line = "";
|
|
this.ignoreLineLetters = 0;
|
|
}
|
|
|
|
if (this.canWrap && this.collapseEndingWhiteSpace && didWrap && lines[lines.length - 1].endsWith(" ")) {
|
|
if (lines[lines.length - 1].length == 1)
|
|
lines.length = lines.length - 1;
|
|
else
|
|
lines[lines.length - 1] = lines[lines.length - 1].substr(0, lines[lines.length - 1].length - 1);
|
|
}
|
|
|
|
if (force && this.line) {
|
|
lines[lines.length] = this.line;
|
|
this.line = "";
|
|
this.ignoreLineLetters = 0;
|
|
}
|
|
|
|
this.lineLetters = this.canWrap ? EvoConvert.calcLineLetters(this.line) : this.line.length;
|
|
this.spacesFrom = -1;
|
|
this.lastWrapableChar = -1;
|
|
}
|
|
};
|
|
|
|
if (worker.useWrapWidth < EvoConvert.MIN_PARAGRAPH_WIDTH)
|
|
worker.useWrapWidth = EvoConvert.MIN_PARAGRAPH_WIDTH;
|
|
|
|
var chr, isHighSurrogate = false;
|
|
|
|
for (ii = 0; ii < str.length; ii += 1 + (isHighSurrogate ? 1 : 0)) {
|
|
// surrogate are two characters "high+low"; high: 0xD800 - 0xDBFF; low: 0xDC00 - 0xDFFF
|
|
// and cannot split after the high surrogate, because that would break the character encoding
|
|
isHighSurrogate = str.charCodeAt(ii) >= 0xd800 && str.charCodeAt(ii) <= 0xdbff;
|
|
if (isHighSurrogate)
|
|
chr = str.substr(ii, 2);
|
|
else
|
|
chr = str.charAt(ii);
|
|
|
|
if (chr == EvoConvert.NOWRAP_CHAR_START)
|
|
worker.inAnchor++;
|
|
|
|
if (chr == "\n") {
|
|
if (!worker.mayConsumeWhitespaceAfterWrap(str, ii))
|
|
worker.commit(ii, true);
|
|
} else if (!worker.charWrap && !worker.collapseWhiteSpace && chr == "\t") {
|
|
if (worker.shouldWrap(str[ii + 1]))
|
|
worker.commit(ii, false);
|
|
else
|
|
worker.commitSpaces(ii);
|
|
|
|
var add = chr; // this expands the tab, instead of leaving it '\t'... " ".repeat(EvoConvert.TAB_WIDTH - ((worker.lineLetters - worker.ignoreLineLetters) % EvoConvert.TAB_WIDTH));
|
|
|
|
worker.lineLetters = worker.lineLetters - ((worker.lineLetters - worker.ignoreLineLetters) % EvoConvert.TAB_WIDTH) + EvoConvert.TAB_WIDTH;
|
|
|
|
if (worker.shouldWrap(str[ii + 1]))
|
|
worker.commit(ii, false);
|
|
|
|
worker.line += add;
|
|
worker.lineLetters += add.length;
|
|
} else if (!worker.charWrap && (chr == " " || chr == "\t")) {
|
|
var setSpacesFrom = false;
|
|
|
|
if (chr == "\t") {
|
|
worker.lineLetters = worker.lineLetters - ((worker.lineLetters - worker.ignoreLineLetters) % EvoConvert.TAB_WIDTH) + EvoConvert.TAB_WIDTH;
|
|
setSpacesFrom = true;
|
|
} else if ((worker.spacesFrom == -1 && worker.line != "") || (!worker.collapseWhiteSpace && !worker.mayConsumeWhitespaceAfterWrap(str, ii))) {
|
|
worker.lineLetters++;
|
|
setSpacesFrom = true;
|
|
}
|
|
|
|
// all spaces at the end of paragraph line are ignored
|
|
if (setSpacesFrom && worker.spacesFrom == -1)
|
|
worker.spacesFrom = ii;
|
|
} else {
|
|
worker.commitSpaces(ii);
|
|
worker.line += chr;
|
|
|
|
if (chr == "\t")
|
|
worker.lineLetters = worker.lineLetters - ((worker.lineLetters - worker.ignoreLineLetters) % EvoConvert.TAB_WIDTH) + EvoConvert.TAB_WIDTH;
|
|
else
|
|
worker.lineLetters += chr.length;
|
|
|
|
if (chr == EvoConvert.NOWRAP_CHAR_START || chr == EvoConvert.NOWRAP_CHAR_END)
|
|
worker.ignoreLineLetters++;
|
|
|
|
if (chr == EvoConvert.NOWRAP_CHAR_END && worker.inAnchor)
|
|
worker.inAnchor--;
|
|
|
|
if (worker.shouldWrap(str[ii + 1]))
|
|
worker.commit(ii, false);
|
|
|
|
if (chr == "-" && worker.line.length && !worker.inAnchor)
|
|
worker.lastWrapableChar = worker.line.length;
|
|
}
|
|
}
|
|
|
|
while (worker.line.length || worker.spacesFrom != -1 || !lines.length) {
|
|
worker.commit(ii, false);
|
|
}
|
|
} else {
|
|
if (str.endsWith("\n"))
|
|
str = str.substr(0, str.length - 1);
|
|
|
|
lines = str.split("\n");
|
|
}
|
|
|
|
var extraIndentStr = extraIndent > 0 ? " ".repeat(extraIndent) : null;
|
|
|
|
if (wrapWidth <= 0) {
|
|
for (ii = 0; ii < lines.length; ii++) {
|
|
var len = EvoConvert.calcLineLetters(lines[ii]);
|
|
|
|
if (wrapWidth < len)
|
|
wrapWidth = len;
|
|
}
|
|
}
|
|
|
|
str = "";
|
|
|
|
for (ii = 0; ii < lines.length; ii++) {
|
|
var line = lines[ii];
|
|
|
|
if ((!ltr && align == EvoConvert.ALIGN_LEFT) ||
|
|
(ltr && align == EvoConvert.ALIGN_RIGHT)) {
|
|
var len = EvoConvert.calcLineLetters(line);
|
|
|
|
if (len < wrapWidth) {
|
|
var add = " ".repeat(wrapWidth - len);
|
|
|
|
if (ltr)
|
|
line = add + line;
|
|
else
|
|
line = line + add;
|
|
}
|
|
} else if (align == EvoConvert.ALIGN_CENTER) {
|
|
var len = EvoConvert.calcLineLetters(line);
|
|
|
|
if (len < wrapWidth) {
|
|
var add = " ".repeat((wrapWidth - len) / 2);
|
|
|
|
if (ltr)
|
|
line = add + line;
|
|
else
|
|
line = line + add;
|
|
}
|
|
} else if (align == EvoConvert.ALIGN_JUSTIFY && ii + 1 < lines.length) {
|
|
var len = EvoConvert.calcLineLetters(line);
|
|
|
|
if (len < wrapWidth) {
|
|
var words = line.split(" ");
|
|
|
|
if (words.length > 1) {
|
|
var nAdd = (wrapWidth - len);
|
|
var add = " ".repeat(nAdd / (words.length - 1) >= 1 ? nAdd / (words.length - 1) : nAdd), jj;
|
|
|
|
for (jj = 0; jj < words.length - 1 && nAdd > 0; jj++) {
|
|
words[jj] = words[jj] + add;
|
|
nAdd -= add.length;
|
|
|
|
if (nAdd > 0 && jj + 2 >= words.length) {
|
|
words[jj] = " ".repeat(nAdd) + words[jj];
|
|
}
|
|
}
|
|
|
|
line = words[0];
|
|
|
|
for (jj = 1; jj < words.length; jj++) {
|
|
line = line + " " + words[jj];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ii && liText) {
|
|
if (ltr)
|
|
line = liText + line;
|
|
else
|
|
line = line + liText;
|
|
} else if (extraIndentStr && ii > 0) {
|
|
if (ltr)
|
|
line = extraIndentStr + line;
|
|
else
|
|
line = line + extraIndentStr;
|
|
|
|
}
|
|
|
|
if (indent != "") {
|
|
if (ltr && align != EvoConvert.ALIGN_RIGHT)
|
|
line = indent + line;
|
|
else
|
|
line = line + indent;
|
|
}
|
|
|
|
str += line + "\n";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
EvoConvert.ImgToText = function(img)
|
|
{
|
|
if (!img)
|
|
return "";
|
|
|
|
var txt;
|
|
|
|
txt = img.alt;
|
|
|
|
return txt ? txt : "";
|
|
}
|
|
|
|
EvoConvert.appendNodeText = function(node, str, text)
|
|
{
|
|
/* This breaks "-- <br>", thus disable it for now. Cannot distinguish from test 70 of /EWebView/ConvertToPlain.
|
|
|
|
if (node && node.parentElement && text.startsWith('\n') && str.endsWith(" ")) {
|
|
var whiteSpace = "normal";
|
|
|
|
if (node.parentElement)
|
|
whiteSpace = window.getComputedStyle(node.parentElement).whiteSpace;
|
|
|
|
if (!whiteSpace || whiteSpace == "normal") {
|
|
return str.substr(0, str.length - 1) + text;
|
|
}
|
|
} */
|
|
|
|
return str + text;
|
|
}
|
|
|
|
EvoConvert.extractElemText = function(elem, normalDivWidth, quoteLevel)
|
|
{
|
|
if (!elem)
|
|
return "";
|
|
|
|
if (!elem.childNodes.length)
|
|
return elem.innerText;
|
|
|
|
var str = "", ii;
|
|
|
|
for (ii = 0; ii < elem.childNodes.length; ii++) {
|
|
var node = elem.childNodes.item(ii);
|
|
|
|
if (!node)
|
|
continue;
|
|
|
|
str = EvoConvert.appendNodeText(node, str, EvoConvert.processNode(node, normalDivWidth, quoteLevel));
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
EvoConvert.mergeConsecutiveSpaces = function(str)
|
|
{
|
|
if (str.indexOf(" ") >= 0) {
|
|
var words = str.split(" "), ii, word;
|
|
|
|
str = "";
|
|
|
|
for (ii = 0; ii < words.length; ii++) {
|
|
word = words[ii];
|
|
|
|
if (word) {
|
|
if (ii)
|
|
str += " ";
|
|
|
|
str += word;
|
|
}
|
|
}
|
|
|
|
if (!words[words.length - 1])
|
|
str += " ";
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
EvoConvert.RemoveInsignificantNewLines = function(node, stripSingleSpace)
|
|
{
|
|
var str = "";
|
|
|
|
if (node && node.nodeType == node.TEXT_NODE) {
|
|
str = node.nodeValue;
|
|
|
|
if (str.indexOf("\r") >= 0 ||
|
|
str.indexOf("\n") >= 0 ||
|
|
str.indexOf("\t") >= 0 ||
|
|
str.indexOf(" ") >= 0) {
|
|
var whiteSpace = "normal";
|
|
|
|
if (node.parentElement)
|
|
whiteSpace = window.getComputedStyle(node.parentElement).whiteSpace;
|
|
|
|
if (whiteSpace == "pre-line") {
|
|
str = EvoConvert.mergeConsecutiveSpaces(str.replace(/\t/g, " "));
|
|
} else if (!whiteSpace || whiteSpace == "normal" || whiteSpace == "nowrap") {
|
|
if (str == "\n" || str == "\r" || str == "\r\n") {
|
|
str = "";
|
|
} else {
|
|
if ((!node.previousSibling || node.previousSibling.tagName) && (str[0] == '\r' || str[0] == '\n')) {
|
|
var ii;
|
|
|
|
for (ii = 0; str[ii] == '\n' || str[ii] == '\r'; ii++) {
|
|
// do nothing, just skip all leading insignificant new lines
|
|
}
|
|
|
|
str = str.substr(ii);
|
|
}
|
|
|
|
if (str.length > 0 && (!node.nextSibling || node.nextSibling.tagName) && (str[str.length - 1] == '\r' || str[str.length - 1] == '\n')) {
|
|
var ii;
|
|
|
|
for (ii = str.length - 1; ii >= 0 && (str[ii] == '\n' || str[ii] == '\r'); ii--) {
|
|
// do nothing, just skip all trailing insignificant new lines
|
|
}
|
|
|
|
str = str.substr(0, ii + 1);
|
|
}
|
|
|
|
str = EvoConvert.mergeConsecutiveSpaces(str.replace(/\t/g, " ").replace(/\r/g, " ").replace(/\n/g, " "));
|
|
|
|
if ((!whiteSpace || whiteSpace == "normal") && str == " " && (stripSingleSpace || (
|
|
!node.nextElementSibling || node.nextElementSibling.tagName == "DIV" || node.nextElementSibling.tagName == "P" || node.nextElementSibling.tagName == "PRE"))) {
|
|
str = "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
EvoConvert.processNode = function(node, normalDivWidth, quoteLevel)
|
|
{
|
|
var str = "";
|
|
|
|
if (node.nodeType == node.TEXT_NODE) {
|
|
str = EvoConvert.RemoveInsignificantNewLines(node);
|
|
} else if (node.nodeType == node.ELEMENT_NODE) {
|
|
if (node.hidden ||
|
|
node.tagName == "STYLE" ||
|
|
node.tagName == "META" ||
|
|
(node.tagName == "SPAN" && node.classList.contains("-x-evo-quoted")))
|
|
return str;
|
|
|
|
var style = EvoConvert.getComputedOrNodeStyle(node), ltr, align, indent, whiteSpace;
|
|
|
|
if (!style)
|
|
style = node.style;
|
|
|
|
ltr = !style || style.direction != "rtl";
|
|
|
|
align = style ? style.textAlign : "";
|
|
if (!align || align == "")
|
|
align = node.style.textAlign;
|
|
if (align)
|
|
align = align.toLowerCase();
|
|
if (!align)
|
|
align = "";
|
|
if (align == "" || align == "start")
|
|
align = ltr ? "left" : "right";
|
|
|
|
if (align == "center")
|
|
align = EvoConvert.ALIGN_CENTER;
|
|
else if (align == "right")
|
|
align = EvoConvert.ALIGN_RIGHT;
|
|
else if (align == "justify")
|
|
align = EvoConvert.ALIGN_JUSTIFY;
|
|
else
|
|
align = EvoConvert.ALIGN_LEFT;
|
|
|
|
// mixed indent and opposite text align does nothing
|
|
if ((ltr && align == EvoConvert.ALIGN_RIGHT) ||
|
|
(!ltr && align == EvoConvert.ALIGN_LEFT)) {
|
|
indent = "";
|
|
} else {
|
|
// computed style's 'indent' uses pixels, not characters
|
|
indent = ltr ? node.style.marginLeft : node.style.marginRight;
|
|
}
|
|
|
|
if (indent && indent.endsWith("ch")) {
|
|
indent = parseInt(indent.slice(0, -2));
|
|
if (!Number.isInteger(indent) || indent < 0)
|
|
indent = 0;
|
|
} else {
|
|
indent = 0;
|
|
}
|
|
|
|
if (indent > 0)
|
|
indent = " ".repeat(indent);
|
|
else
|
|
indent = "";
|
|
|
|
whiteSpace = style ? style.whiteSpace.toLowerCase() : "";
|
|
|
|
if (node.tagName == "DIV" || node.tagName == "P") {
|
|
var liText, extraIndent, width, useDefaultWidth = false;
|
|
|
|
liText = node.getAttribute("x-evo-li-text");
|
|
if (!liText)
|
|
liText = "";
|
|
|
|
extraIndent = node.getAttribute("x-evo-extra-indent");
|
|
extraIndent = extraIndent ? parseInt(extraIndent, 10) : 0;
|
|
if (!Number.isInteger(extraIndent)) {
|
|
extraIndent = 0;
|
|
}
|
|
|
|
width = node.style.width;
|
|
if (width && width.endsWith("ch")) {
|
|
width = parseInt(width.slice(0, -2));
|
|
|
|
if (!Number.isInteger(width) || width < 0)
|
|
useDefaultWidth = true;
|
|
} else {
|
|
useDefaultWidth = true;
|
|
}
|
|
|
|
if (useDefaultWidth && normalDivWidth > 0) {
|
|
width = normalDivWidth - (quoteLevel * 2);
|
|
|
|
if (width < EvoConvert.MIN_PARAGRAPH_WIDTH)
|
|
width = EvoConvert.MIN_PARAGRAPH_WIDTH;
|
|
}
|
|
|
|
str = EvoConvert.formatParagraph(EvoConvert.extractElemText(node, normalDivWidth, quoteLevel), ltr, align, indent, whiteSpace, width, extraIndent, liText, quoteLevel);
|
|
|
|
if (!liText && node.parentElement && (node.parentElement.tagName == "DIV" || node.parentElement.tagName == "P") &&
|
|
style.display == "block" && str != "" && node.previousSibling &&
|
|
((node.previousSibling.nodeType == node.ELEMENT_NODE && node.previousSibling.tagName != "DIV" && node.previousSibling.tagName != "P" && node.previousSibling.tagName != "BR") ||
|
|
(node.previousSibling.nodeType == node.TEXT_NODE && EvoConvert.RemoveInsignificantNewLines(node.previousSibling, true) != ""))) {
|
|
str = "\n" + str;
|
|
}
|
|
} else if (node.tagName == "PRE") {
|
|
str = EvoConvert.formatParagraph(EvoConvert.extractElemText(node, normalDivWidth, quoteLevel), ltr, align, indent, "pre", -1, 0, "", quoteLevel);
|
|
} else if (node.tagName == "BR") {
|
|
// ignore new-lines added by wrapping, treat them as spaces
|
|
if (node.classList.contains("-x-evo-wrap-br"))
|
|
str += " ";
|
|
else
|
|
str = "\n";
|
|
} else if (node.tagName == "IMG") {
|
|
str = EvoConvert.ImgToText(node);
|
|
} else if (node.tagName == "A" && !node.innerText.includes(" ") && !node.innerText.includes("\n")) {
|
|
str = EvoConvert.NOWRAP_CHAR_START + node.innerText + EvoConvert.NOWRAP_CHAR_END;
|
|
} else {
|
|
var isBlockquote = node.tagName == "BLOCKQUOTE";
|
|
|
|
str = EvoConvert.extractElemText(node, normalDivWidth, quoteLevel + (isBlockquote ? 1 : 0));
|
|
|
|
if (isBlockquote) {
|
|
var ii, lines = str.split("\n"), prefix, suffix;
|
|
|
|
prefix = ltr ? EvoConvert.getQuotePrefix(1, ltr) : "";
|
|
suffix = ltr ? "" : EvoConvert.getQuotePrefix(1, ltr);
|
|
|
|
str = "";
|
|
|
|
for (ii = 0; ii < lines.length; ii++) {
|
|
if (ii + 1 == lines.length && !lines[ii])
|
|
break;
|
|
|
|
str += prefix + lines[ii] + suffix + "\n";
|
|
}
|
|
}
|
|
|
|
if ((!isBlockquote || !str.endsWith("\n")) &&
|
|
str != "\n" && ((style && style.display == "block") || node.tagName == "ADDRESS")) {
|
|
str += "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Converts element and its children to plain text. Any <div>,<ul>,<ol>, as an immediate child
|
|
* of the element, is wrapped to upto normalDivWidth characters, if it's defined and greater
|
|
* than EvoConvert.MIN_PARAGRAPH_WIDTH.
|
|
*/
|
|
EvoConvert.ToPlainText = function(element, normalDivWidth)
|
|
{
|
|
if (!element)
|
|
return null;
|
|
|
|
if (element.tagName == "HTML") {
|
|
var bodys;
|
|
|
|
bodys = element.getElementsByTagName("BODY");
|
|
|
|
if (bodys.length == 1)
|
|
element = bodys.item(0);
|
|
}
|
|
|
|
if (!element)
|
|
return null;
|
|
|
|
if (!normalDivWidth)
|
|
normalDivWidth = -1;
|
|
|
|
var disconnectFromHead = false;
|
|
|
|
if (!element.isConnected) {
|
|
// this is needed to be able to use window.getComputedStyle()
|
|
document.head.appendChild(element);
|
|
disconnectFromHead = true;
|
|
}
|
|
|
|
try {
|
|
var uls, ols, str = "", ii;
|
|
|
|
uls = element.getElementsByTagName("UL");
|
|
ols = element.getElementsByTagName("OL");
|
|
|
|
if (uls.length > 0 || ols.length > 0) {
|
|
element = element.cloneNode(true);
|
|
|
|
if (uls.length)
|
|
EvoConvert.replaceList(element, "UL", normalDivWidth);
|
|
|
|
if (ols.length)
|
|
EvoConvert.replaceList(element, "OL", normalDivWidth);
|
|
}
|
|
|
|
for (ii = 0; ii < element.childNodes.length; ii++) {
|
|
var node = element.childNodes.item(ii);
|
|
|
|
if (!node)
|
|
continue;
|
|
|
|
str = EvoConvert.appendNodeText(node, str, EvoConvert.processNode(node, normalDivWidth, 0));
|
|
}
|
|
} finally {
|
|
if (disconnectFromHead)
|
|
document.head.removeChild(element);
|
|
}
|
|
|
|
// remove EvoConvert.NOWRAP_CHAR_START and EvoConvert.NOWRAP_CHAR_END from the result
|
|
return str.replace(/\x01/g, "").replace(/\x02/g, "");
|
|
}
|