mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-24 13:35:01 +08:00
Summary: We need to remove quoted text completely from bodies in a composer. Unfortunately, that makes it very difficult to determine how to put it back in. For now the scheme is to append the quoted text at the end. However, that means that we need to only pull out quoted text at the end of a message. Unfortunately there are lots of places that quoted text appears inline with regular text. Determining whether or not some content is at the "end" of a message turned out to be non-trivial. We now have a new `DOMUtils` that looks for empty areas at the end. We also have a new quoted HTML parser that finds trailing quotes. Fixes T2335 Test Plan: lots of new quoted text tests Reviewers: bengotow Reviewed By: bengotow Maniphest Tasks: T2335 Differential Revision: https://phab.nylas.com/D1773
117 lines
3.8 KiB
CoffeeScript
117 lines
3.8 KiB
CoffeeScript
_ = require 'underscore'
|
|
|
|
DOMUtils =
|
|
removeElements: (elements=[]) ->
|
|
for el in elements
|
|
try
|
|
if el.parentNode then el.parentNode.removeChild(el)
|
|
catch
|
|
# This can happen if we've already removed ourselves from the node
|
|
# or it no longer exists
|
|
continue
|
|
return elements
|
|
|
|
# Checks to see if a particular node is visible and any of its parents
|
|
# are visible.
|
|
#
|
|
# WARNING. This is a fairly expensive operation and should be used
|
|
# sparingly.
|
|
nodeIsVisible: (node) ->
|
|
while node and node isnt window.document
|
|
style = window.getComputedStyle(node)
|
|
node = node.parentNode
|
|
continue unless style?
|
|
# NOTE: opacity must be soft ==
|
|
if style.opacity is 0 or style.opacity is "0" or style.visibility is "hidden" or style.display is "none"
|
|
return false
|
|
return true
|
|
|
|
# Finds all of the non blank node in a {Document} object or HTML string.
|
|
#
|
|
# - `elementOrHTML` a dom element or an HTML string. If passed a
|
|
# string, it will use `DOMParser` to convert it into a DOM object.
|
|
#
|
|
# "Non blank" is defined as any node whose `textContent` returns a
|
|
# whitespace string.
|
|
#
|
|
# It will also reject nodes we see are invisible due to basic CSS
|
|
# properties.
|
|
#
|
|
# Returns an array of DOM Nodes
|
|
nodesWithContent: (elementOrHTML) ->
|
|
nodes = []
|
|
if _.isString(elementOrHTML)
|
|
domParser = new DOMParser()
|
|
doc = domParser.parseFromString(elementOrHTML, "text/html")
|
|
allNodes = doc.body.childNodes
|
|
else if elementOrHTML?.childNodes
|
|
allNodes = elementOrHTML.childNodes
|
|
else return nodes
|
|
|
|
# We need to check `childNodes` instead of `children` to look for
|
|
# plain Text nodes.
|
|
for node in allNodes by -1
|
|
if node.nodeName is "IMG"
|
|
nodes.unshift node
|
|
|
|
# It's important to use `textContent` and NOT `innerText`.
|
|
# `innerText` causes a full reflow on every call because it
|
|
# calcaultes CSS styles to determine if the text is truly visible or
|
|
# not. This utility method must NOT cause a reflow. We instead will
|
|
# check for basic cases ourselves.
|
|
if (node.textContent ? "").trim().length is 0
|
|
continue
|
|
|
|
if node.style?.opacity is 0 or node.style?.opacity is "0" or node.style?.visibility is "hidden" or node.style?.display is "none"
|
|
continue
|
|
|
|
nodes.unshift node
|
|
|
|
# No nodes with content found!
|
|
return nodes
|
|
|
|
parents: (node) ->
|
|
nodes = []
|
|
nodes.unshift(node) while node = node.parentNode
|
|
return nodes
|
|
|
|
commonAncestor: (nodes=[]) ->
|
|
nodes = Array::slice.call(nodes)
|
|
|
|
minDepth = Number.MAX_VALUE
|
|
# Sometimes we can potentially have tons of REALLY deeply nested
|
|
# nodes. Since we're looking for a common ancestor we can really speed
|
|
# this up by keeping track of the min depth reached. We know that we
|
|
# won't need to check past that.
|
|
parents = ->
|
|
nodes = []
|
|
depth = 0
|
|
while node = node.parentNode
|
|
nodes.unshift(node)
|
|
depth += 1
|
|
if depth > minDepth then break
|
|
minDepth = Math.min(minDepth, depth)
|
|
return nodes
|
|
|
|
# _.intersection will preserve the ordering of the parent node arrays.
|
|
# parents are ordered top to bottom, so the last node is the most
|
|
# specific common ancenstor
|
|
_.last(_.intersection.apply(null, nodes.map(DOMUtils.parents)))
|
|
|
|
scrollAdjustmentToMakeNodeVisibleInContainer: (node, container) ->
|
|
return unless node
|
|
|
|
nodeRect = node.getBoundingClientRect()
|
|
containerRect = container.getBoundingClientRect()
|
|
|
|
distanceBelowBottom = (nodeRect.top + nodeRect.height) - (containerRect.top + containerRect.height)
|
|
if distanceBelowBottom > 0
|
|
return distanceBelowBottom
|
|
|
|
distanceAboveTop = containerRect.top - nodeRect.top
|
|
if distanceAboveTop > 0
|
|
return -distanceAboveTop
|
|
|
|
return 0
|
|
|
|
module.exports = DOMUtils
|