diff --git a/app/src/flux/stores/draft-editing-session.es6 b/app/src/flux/stores/draft-editing-session.es6 index 72447063c..457f5a376 100644 --- a/app/src/flux/stores/draft-editing-session.es6 +++ b/app/src/flux/stores/draft-editing-session.es6 @@ -321,7 +321,7 @@ export default class DraftEditingSession extends NylasStore { } _onDraftChanged = (change) => { - if (change === undefined) { + if ((change === undefined) || (change.type !== 'persist')) { return; } diff --git a/app/src/package.es6 b/app/src/package.es6 index 0df0a9585..c9aa7c7cd 100644 --- a/app/src/package.es6 +++ b/app/src/package.es6 @@ -29,6 +29,8 @@ export default class Package { } activate() { + const start = Date.now(); // eslint-disable-line + this.loadKeymaps(); this.loadMenus(); this.loadStylesheets(); @@ -48,6 +50,9 @@ export default class Package { module.activateConfig(); } } + + // Uncomment to enable timing inspection + // console.log(`Loading ${this.name} took ${Date.now() - start}`); } deactivate() { diff --git a/app/src/services/quoted-html-transformer.es6 b/app/src/services/quoted-html-transformer.es6 index 0deb79f5d..22d766555 100644 --- a/app/src/services/quoted-html-transformer.es6 +++ b/app/src/services/quoted-html-transformer.es6 @@ -93,22 +93,38 @@ class QuotedHTMLTransformer { el.remove(); } - // Remove any trailing
in the last leaf child of doc.body. - // This specific pattern occurs often when stripping Mailspring / gmail quotes. - let last = doc.body; - while (last.children.length) { - last = last.children[last.children.length - 1]; + // Traverse down the tree of "last child" nodes to get the last child of the last child. + // The deepest node at the end of the document. + let lastOfLast = doc.body; + while (lastOfLast.lastElementChild) { + lastOfLast = lastOfLast.lastElementChild; } - last = last.parentElement; - while (last.children.length > 0) { - const innerLast = last.children[last.children.length - 1]; - if (innerLast.nodeName === 'BR') { - innerLast.remove(); - } else { + // Traverse back up the tree - at each level, attempt to remove + // whitespace from the last child and then remove the child itself + // if it's completely empty. Repeat until a child has meaningful content, + // then move up the tree. + // + // Containers with empty space at the end occur pretty often when we + // remove the quoted text and it had preceding spaces. + const removeTrailingWhitespaceChildren = (el) => { + while (el.lastElementChild) { + const child = el.lastElementChild; + if (['BR', 'P', 'DIV', 'SPAN'].includes(child.nodeName)) { + removeTrailingWhitespaceChildren(child); + if ((child.childElementCount === 0) && (child.textContent.trim() === '')) { + child.remove(); + continue; + } + } break; } } + + while (lastOfLast.parentElement) { + lastOfLast = lastOfLast.parentElement; + removeTrailingWhitespaceChildren(lastOfLast); + } } appendQuotedHTML(htmlWithoutQuotes, originalHTML) { @@ -183,6 +199,7 @@ class QuotedHTMLTransformer { this._findGmailQuotes, this._findOffice365Quotes, this._findBlockquoteQuotes, + this._findQuotesAfterMessageHeaderBlock, ]; let quoteElements = []; @@ -351,6 +368,52 @@ class QuotedHTMLTransformer { _findBlockquoteQuotes(doc) { return Array.from(doc.querySelectorAll('blockquote')); } + + _findQuotesAfterMessageHeaderBlock(doc) { + // This detector looks for a element in the DOM tree containing + // three children: To: and Cc: and Subject:. + // It then returns every node after that as quoted text. + + // Find a DOM node exactly matching To: + const to = doc.evaluate("//b[. = 'To:']", doc.body, null, XPathResult.ANY_TYPE, null).iterateNext(); + if (to) { + // check to see if the parent container also contains the other two + const headerContainer = to.parentElement; + let matches = 0; + for (const node of Array.from(headerContainer.children)) { + if ((node.textContent === "Cc:") || (node.textContent === "Subject:")) { + matches++; + } + } + if (matches === 2) { + // got a hit! let's cut some text. + const quotedTextNodes = []; + + // Special case to add "From:" if it's present in the node before the rest of + // the header fields. It's often not in the same container as To, Cc, Subject: + const possibleFromNode = headerContainer.previousElementSibling; + if (possibleFromNode && possibleFromNode.innerText.trim() === "From:") { + quotedTextNodes.push(possibleFromNode); + } + + // The headers container and everything past it in the document is quoted text. + // This traverses the DOM, walking up the tree and adding all siblings below + // our current path to the array. + let head = headerContainer; + while (head) { + quotedTextNodes.push(head); + const next = head.nextElementSibling; + if (next) { + head = next; + } else { + head = head.parentElement.nextElementSibling; + } + } + return quotedTextNodes; + } + } + return []; + } } export default new QuotedHTMLTransformer();