Fix non-breaking spaces breaking formatting in sent messages.

This ensures that non-breaking space chars (not HTML entities) are
removed from text obtainined from the composer, and moves the F=F text
formatting from JS back to Vala, to minimimse the JS footprint and return
to using the old (working) version again.

* src/client/composer/composer-web-view.vala (ClientWebView::get_text):
  Restore the old F=F formatting code previously in webkit-util, apply it
  to plain text obtained from the composer.

* test/client/components/client-web-view-test-case.vala: New base class
  for tests involving ClientWebView.

* test/client/composer/composer-web-view-test.vala: New tests for
  ComposerWebView::get_html and ::get_text.

* test/js/composer-page-state-test.vala: Reworked to use
  ClientWebViewTestCase, updated tests now that JS is returning
  QUOTE_MARKER-delinated text, not F=F text.

* test/testcase.vala (TestCase): Move ::async_complete and ::async_result
  from ComposerPageStateTest so all test cases can test async code.

* test/CMakeLists.txt: Add new source files.

* test/main.vala (main): Add new test.

* ui/composer-web-view.js: Update doc comments, remove F=F code, break
  out non-breaking space replacement so it can be tested.
This commit is contained in:
Michael James Gratton 2017-01-02 10:23:35 +11:00
parent 57f10446a9
commit 17fda41b85
8 changed files with 341 additions and 99 deletions

View file

@ -66,7 +66,7 @@ ComposerPageState.prototype = {
return document.getElementById(ComposerPageState.BODY_ID).innerHTML;
},
getText: function() {
return ComposerPageState.htmlToFlowedText(
return ComposerPageState.htmlToQuotedText(
document.getElementById(ComposerPageState.BODY_ID)
);
},
@ -93,15 +93,27 @@ ComposerPageState.prototype = {
};
/**
* Convert a HTML DOM tree to RFC 3676 format=flowed text.
* Convert a HTML DOM tree to plain text with delineated quotes.
*
* This will modify/reset the DOM.
* Lines are delinated using LF. Quoted lines are prefixed with
* `ComposerPageState.QUOTE_MARKER`, where the number of markers
* indicates the depth of nesting of the quote.
*
* This will modify/reset the DOM, since it ultimately requires
* stuffing `QUOTE_MARKER` into existing paragraphs and getting it
* back out in a way that preserves the visual presentation.
*/
ComposerPageState.htmlToFlowedText = function(root) {
var savedDoc = root.innerHTML;
var blockquotes = root.querySelectorAll("blockquote");
var nbq = blockquotes.length;
var bqtexts = new Array(nbq);
ComposerPageState.htmlToQuotedText = function(root) {
// XXX It would be nice to just clone the root and modify that, or
// see if we can implement this some other way so as to not modify
// the DOM at all, but currently unit test show that the results
// are not the same if we work on a clone, likely because of the
// use of HTMLElement::innerText. Need to look into it more.
let savedDoc = root.innerHTML;
let blockquotes = root.querySelectorAll("blockquote");
let nbq = blockquotes.length;
let bqtexts = new Array(nbq);
// Get text of blockquotes and pull them out of DOM. They are
// replaced with tokens deliminated with the characters
@ -132,61 +144,14 @@ ComposerPageState.htmlToFlowedText = function(root) {
);
}
// Reassemble plain text out of parts, replace non-breaking
// space with regular space
var doctext = ComposerPageState.resolveNesting(
root.innerText, bqtexts
).replace("\xc2\xa0", " ");
// Reassemble plain text out of parts, and replace non-breaking
// space with regular space.
let text = ComposerPageState.resolveNesting(root.innerText, bqtexts)
// Reassemble DOM
// Reassemble DOM now we have the plain text
root.innerHTML = savedDoc;
// Wrap, space stuff, quote
var lines = doctext.split("\n");
flowed = [];
for (let line of lines) {
// Strip trailing whitespace, so it doesn't look like a flowed
// line. But the signature separator "-- " is special, so
// leave that alone.
if (line != "-- ") {
line = line.trimRight();
}
let quoteLevel = 0;
while (line[quoteLevel] == ComposerPageState.QUOTE_MARKER) {
quoteLevel += 1;
}
line = line.substr(quoteLevel, line.length);
let prefix = quoteLevel > 0 ? '>'.repeat(quoteLevel) + " " : "";
let maxLen = 72 - prefix.length;
do {
let startInd = 0;
if (quoteLevel == 0 &&
(line.startsWith(">") || line.startsWith("From"))) {
line = " " + line;
startInd = 1;
}
let cutInd = line.length;
if (cutInd > maxLen) {
let beg = line.substr(0, maxLen);
cutInd = beg.lastIndexOf(" ", startInd) + 1;
if (cutInd == 0) {
cutInd = line.indexOf(" ", startInd) + 1;
if (cutInd == 0) {
cutInd = line.length;
}
if (cutInd > 998 - prefix.length) {
cutInd = 998 - prefix.length;
}
}
}
flowed.push(prefix + line.substr(0, cutInd) + "\n");
line = line.substr(cutInd, line.length);
} while (line.length > 0);
}
return flowed.join("");
return ComposerPageState.replaceNonBreakingSpace(text);
};
ComposerPageState.resolveNesting = function(text, values) {
@ -219,6 +184,9 @@ ComposerPageState.resolveNesting = function(text, values) {
});
};
/**
* Prefixes each NL-delineated line with `ComposerPageState.QUOTE_MARKER`.
*/
ComposerPageState.quoteLines = function(text) {
let lines = text.split("\n");
for (let i = 0; i < lines.length; i++)
@ -226,6 +194,16 @@ ComposerPageState.quoteLines = function(text) {
return lines.join("\n");
};
/**
* Converts all non-breaking space chars to plain spaces.
*/
ComposerPageState.replaceNonBreakingSpace = function(text) {
// XXX this is a separate function for unit testing - since when
// running as a unit test, HTMLElement.innerText appears to not
// convert &nbsp into U+00A0.
return text.replace(new RegExp(" ", "g"), " ");
};
var geary = new ComposerPageState();
window.onload = function() {