feat(composer): can outdent blockquotes allowing you to reply inline

Summary:
You can now break up blockquotes (as in quoted text areas) by pressing
"delete" at the start of a line. This allows you to reply inline.

Test Plan: new tests

Reviewers: bengotow, juan

Reviewed By: bengotow, juan

Differential Revision: https://phab.nylas.com/D2421
This commit is contained in:
Evan Morikawa 2016-01-11 14:46:20 -05:00
parent 55f1c537e2
commit 1de3a47077
4 changed files with 194 additions and 1 deletions

View file

@ -0,0 +1,130 @@
{DOMUtils} = require 'nylas-exports'
BlockquoteManager = require '../../src/components/contenteditable/blockquote-manager'
describe "BlockquoteManager", ->
outdentCases = ["""
<div>|</div>
"""
,
"""
<div>
<span>|</span>
</div>
"""
,
"""
<p></p>
<span>\n</span>
<span>|</span>
"""
,
"""
<span></span>
<p></p>
<span></span>
<span>|</span>
"""
,
"""
<div>
<div>
<div>|</div>
</div>
</div>
"""
,
"""
<div>
<span></span>
<span>|</span>
</div>
"""
,
"""
<span></span>
<p><span>yo</span></p>
<span></span>
<span>
<span></span>
<span></span>
<span>|test</span>
</span>
"""
]
backspaceCases = ["""
<div>yo|</div>
"""
,
"""
<div>
yo
<span>|</span>
</div>
"""
,
"""
<p></p>
<span>&nbsp;</span>
<span>|</span>
"""
,
"""
<span></span>
<p></p>
<span>yo</span>
<span>|</span>
"""
,
"""
<div>
<div>
<div>yo|</div>
</div>
</div>
"""
,
"""
<div>
<span>yo</span>
<span>|</span>
</div>
"""
,
"""
<span></span>
<p><span>yo</span></p>
<span></span>
<span>
<span>yo</span>
<span></span>
<span>|test</span>
</span>
"""
]
setupContext = (testCase) ->
context = document.createElement("blockquote")
context.innerHTML = testCase
{node, index} = DOMUtils.findCharacter(context, "|")
if not node then throw new Error("Couldn't find where to set Selection")
mockSelection = {
isCollapsed: true
anchorNode: node
anchorOffset: index
}
return mockSelection
outdentCases.forEach (testCase) ->
it """outdents\n#{testCase}""", ->
mockSelection = setupContext(testCase)
editor = {currentSelection: -> mockSelection}
expect(BlockquoteManager._isInBlockquote(editor)).toBe true
expect(BlockquoteManager._isAtStartOfLine(editor)).toBe true
backspaceCases.forEach (testCase) ->
it """backspaces (does NOT outdent)\n#{testCase}""", ->
mockSelection = setupContext(testCase)
editor = {currentSelection: -> mockSelection}
expect(BlockquoteManager._isInBlockquote(editor)).toBe true
expect(BlockquoteManager._isAtStartOfLine(editor)).toBe false

View file

@ -0,0 +1,36 @@
{DOMUtils, ContenteditableExtension} = require 'nylas-exports'
class BlockquoteManager extends ContenteditableExtension
@onKeyDown: ({editor, event}) ->
if event.key is "Backspace"
if @_isInBlockquote(editor) and @_isAtStartOfLine(editor)
editor.outdent()
event.preventDefault()
@_isInBlockquote: (editor) ->
sel = editor.currentSelection()
return unless sel.isCollapsed
DOMUtils.closest(sel.anchorNode, "blockquote")?
@_isAtStartOfLine: (editor) ->
sel = editor.currentSelection()
return false unless sel.anchorNode
return false unless sel.isCollapsed
return false unless sel.anchorOffset is 0
return @_ancestorRelativeLooksLikeBlock(sel.anchorNode)
@_ancestorRelativeLooksLikeBlock: (node) ->
return true if DOMUtils.looksLikeBlockElement(node)
sibling = node
while sibling = sibling.previousSibling
if DOMUtils.looksLikeBlockElement(sibling)
return true
if DOMUtils.looksLikeNonEmptyNode(sibling)
return false
# never found block level element
return @_ancestorRelativeLooksLikeBlock(node.parentNode)
module.exports = BlockquoteManager

View file

@ -13,6 +13,7 @@ ListManager = require './list-manager'
MouseService = require './mouse-service'
DOMNormalizer = require './dom-normalizer'
ClipboardService = require './clipboard-service'
BlockquoteManager = require './blockquote-manager'
###
Public: A modern React-compatible contenteditable
@ -62,7 +63,7 @@ class Contenteditable extends React.Component
coreServices: [MouseService, ClipboardService]
coreExtensions: [DOMNormalizer, ListManager, TabManager]
coreExtensions: [DOMNormalizer, ListManager, TabManager, BlockquoteManager]
########################################################################

View file

@ -344,6 +344,18 @@ DOMUtils =
return nodeList
findCharacter: (context, character) ->
node = null
index = null
treeWalker = document.createTreeWalker(context, NodeFilter.SHOW_TEXT)
while currentNode = treeWalker.nextNode()
i = currentNode.data.indexOf(character)
if i >= 0
node = currentNode
index = i
break
return {node, index}
escapeHTMLCharacters: (text) ->
map =
'&': '&amp;',
@ -607,4 +619,18 @@ DOMUtils =
parent = parent.parentElement
false
looksLikeBlockElement: (node) ->
return node.nodeName in ["BR", "P", "BLOCKQUOTE", "DIV", "TABLE"]
# When detecting if we're at the start of a "visible" line, we need to look
# for text nodes that have visible content in them.
looksLikeNonEmptyNode: (node) ->
textNode = DOMUtils.findFirstTextNode(node)
if textNode
if /^[\n ]*$/.test(textNode.data)
return false
else return true
else
return false
module.exports = DOMUtils