mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 13:14:16 +08:00
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:
parent
db7bc9e81c
commit
92cd752284
4 changed files with 194 additions and 1 deletions
130
spec/components/blockquote-manager-spec.coffee
Normal file
130
spec/components/blockquote-manager-spec.coffee
Normal 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> </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
|
36
src/components/contenteditable/blockquote-manager.coffee
Normal file
36
src/components/contenteditable/blockquote-manager.coffee
Normal 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
|
|
@ -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]
|
||||
|
||||
|
||||
########################################################################
|
||||
|
|
|
@ -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 =
|
||||
'&': '&',
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue