Implemented issue #707 - Edge looses text formatting on reply (may also on other browsers).

This commit is contained in:
Martin Edenhofer 2017-06-21 17:49:35 +02:00
parent ef282e187b
commit 62e5be1ed2
4 changed files with 200 additions and 63 deletions

View file

@ -391,19 +391,19 @@ class App.TicketZoomArticleActions extends App.Controller
body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || '' body = @el.closest('.ticketZoom').find('.article-add [data-name="body"]').html() || ''
# check if quote need to be added # check if quote need to be added
selectedText = App.ClipBoard.getSelected() selected = App.ClipBoard.getSelected('html')
if selectedText if selected
selected = App.Utils.htmlCleanup(selected).html()
# clean selection if !selected
selectedText = App.Utils.textCleanup(selectedText) selected = App.ClipBoard.getSelected('text')
if selected
# convert to html selected = App.Utils.textCleanup(selected)
selectedText = App.Utils.text2html(selectedText) selected = App.Utils.text2html(selected)
if selectedText if selected
selectedText = "<div><br><br/></div><div><blockquote type=\"cite\">#{selectedText}</blockquote></div><div><br></div>" selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
# add selected text to body # add selected text to body
body = selectedText + body body = selected + body
articleNew.body = body articleNew.body = body

View file

@ -6,25 +6,25 @@ class App.ClipBoard
_instance ?= new _Singleton _instance ?= new _Singleton
_instance.bind(el) _instance.bind(el)
@getSelected: -> @getSelected: (type) ->
if _instance == undefined if _instance == undefined
_instance ?= new _Singleton _instance ?= new _Singleton
_instance.getSelected() _instance.getSelected(type)
@getSelectedLast: -> @getSelectedLast: (type) ->
if _instance == undefined if _instance == undefined
_instance ?= new _Singleton _instance ?= new _Singleton
_instance.getSelectedLast() _instance.getSelectedLast(type)
@getPosition: (el) -> @getPosition: (el) ->
if _instance == undefined if _instance == undefined
_instance ?= new _Singleton _instance ?= new _Singleton
_instance.getPosition(el) _instance.getPosition(el)
@setPosition: ( el, pos ) -> @setPosition: (el, pos) ->
if _instance == undefined if _instance == undefined
_instance ?= new _Singleton _instance ?= new _Singleton
_instance.setPosition( el, pos ) _instance.setPosition(el, pos)
@keycode: (code) -> @keycode: (code) ->
if _instance == undefined if _instance == undefined
@ -33,54 +33,68 @@ class App.ClipBoard
class _Singleton class _Singleton
constructor: -> constructor: ->
@selection = '' @selection =
@selectionLast = '' html: ''
text: ''
@selectionLast =
html: ''
text: ''
# bind to fill selected text into # bind to fill selected text into
bind: (el) -> bind: (el) ->
$(el).bind('mouseup', =>
# check selection on mouse up # check selection on mouse up
@selection = @_getSelected() $(el).bind('mouseup', =>
if @selection @_updateSelection()
@selectionLast = @selection
) )
$(el).bind('keyup', (e) => $(el).bind('keyup', (e) =>
# check selection on sonder key # check selection on sonder key
if e.keyCode == 91 if e.keyCode == 91
@selection = @_getSelected() @_updateSelection()
if @selection
@selectionLast = @selection
# check selection of arrow keys # check selection of arrow keys
if e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40 if e.keyCode == 37 || e.keyCode == 38 || e.keyCode == 39 || e.keyCode == 40
@selection = @_getSelected() @_updateSelection()
if @selection
@selectionLast = @selection
) )
_updateSelection: =>
for key in ['html', 'text']
@selection[key] = @_getSelected(key)
if @selection[key]
@selectionLast[key] = @selection[key]
# get cross browser selected string # get cross browser selected string
_getSelected: -> _getSelected: (type) ->
text = '' text = ''
html = ''
if window.getSelection if window.getSelection
text = window.getSelection() sel = window.getSelection()
text = sel.toString()
else if document.getSelection else if document.getSelection
text = document.getSelection() sel = document.getSelection()
text = sel.toString()
else if document.selection else if document.selection
text = document.selection.createRange().text sel = document.selection.createRange()
if text text = sel.text
# text = text.toString().trim() if type is 'text'
text = $.trim( text.toString() ) return $.trim(text.toString()) if text
text return ''
if sel && sel.rangeCount
container = document.createElement('div')
for i in [1..sel.rangeCount]
container.appendChild(sel.getRangeAt(i-1).cloneContents())
html = container.innerHTML
html
# get current selection # get current selection
getSelected: -> getSelected: (type) ->
@selection @selection[type]
# get latest selection # get latest selection
getSelectedLast: -> getSelectedLast: (type) ->
@selectionLast @selectionLast[type]
getPosition: (el) -> getPosition: (el) ->
pos = 0 pos = 0
@ -104,13 +118,13 @@ class _Singleton
# IE Support # IE Support
if el.setSelectionRange if el.setSelectionRange
el.focus() el.focus()
el.setSelectionRange( pos, pos ) el.setSelectionRange(pos, pos)
# Firefox support # Firefox support
else if el.createTextRange else if el.createTextRange
range = el.createTextRange() range = el.createTextRange()
range.collapse(true) range.collapse(true)
range.moveEnd( 'character', pos ) range.moveEnd('character', pos)
range.moveStart('character', pos) range.moveStart('character', pos)
range.select() range.select()

View file

@ -1,5 +1,80 @@
# coffeelint: disable=no_unnecessary_double_quotes # coffeelint: disable=no_unnecessary_double_quotes
class App.Utils class App.Utils
@mapAttributes:
'TABLE': ['align', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'sortable', 'summary', 'width', 'style']
'TD': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'valign', 'width', 'style']
'TH': ['abbr', 'align', 'axis', 'colspan', 'headers', 'rowspan', 'scope', 'sorted', 'valign', 'width', 'style']
'TR': ['width', 'style']
@mapCss:
'TABLE': [
'background', 'background-color', 'color', 'font-size', 'vertical-align',
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'text-align',
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
]
'TH': [
'background', 'background-color', 'color', 'font-size', 'vertical-align',
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'text-align',
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
]
'TR': [
'background', 'background-color', 'color', 'font-size', 'vertical-align',
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'text-align',
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
]
'TD': [
'background', 'background-color', 'color', 'font-size', 'vertical-align',
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'text-align',
'border', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-collapse', 'border-style', 'border-spacing',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
]
# textCleand = App.Utils.textCleanup(rawText) # textCleand = App.Utils.textCleanup(rawText)
@textCleanup: (ascii) -> @textCleanup: (ascii) ->
@ -252,26 +327,45 @@ class App.Utils
catch err catch err
return $("<div>#{item}</div>") return $("<div>#{item}</div>")
@_removeAttribute: (element) ->
return if !element
if @mapAttributes[element.nodeName]
atts = element.attributes
for att in atts
if att && att.name && !_.contains(@mapAttributes[element.nodeName], att.name)
element.removeAttributeNode(att)
else
$element = $(element)
$element.removeAttr('style')
$element.removeAttr('class')
$element.removeAttr('title')
$element.removeAttr('lang')
$element.removeAttr('type')
$element.removeAttr('id')
$element.removeAttr('wrap')
$element.removeAttrs(/data-/)
if @mapCss[element.nodeName]
style = element.getAttributeNode('style')
if style && style.nodeValue && style.nodeValue.split
styleNew = ''
for local_pear in style.nodeValue.split(';')
prop = local_pear.split(':')
if prop[0] && prop[0].trim
key = prop[0].trim()
if _.contains(@mapCss[element.nodeName], key)
styleNew += "#{local_pear};"
if styleNew isnt ''
style.nodeValue = styleNew
element.setAttributeNode(style)
else
element.removeAttributeNode(style)
@_removeAttributes: (html, parent = true) -> @_removeAttributes: (html, parent = true) ->
if parent if parent
html.find('*') html.each((index, element) => @_removeAttribute(element) )
.removeAttr('style') html.find('*').each((index, element) => @_removeAttribute(element) )
.removeAttr('class')
.removeAttr('title')
.removeAttr('lang')
.removeAttr('type')
.removeAttr('id')
.removeAttr('wrap')
.removeAttrs(/data-/)
html
.removeAttr('style')
.removeAttr('class')
.removeAttr('title')
.removeAttr('lang')
.removeAttr('type')
.removeAttr('id')
.removeAttr('wrap')
.removeAttrs(/data-/)
html html
@_removeComments: (html) -> @_removeComments: (html) ->

View file

@ -582,6 +582,35 @@ test("htmlCleanup", function() {
result = App.Utils.htmlCleanup(source) result = App.Utils.htmlCleanup(source)
equal(result.html(), should, source) equal(result.html(), should, source)
source = "<table bgcolor=\"green\" aaa=\"1\"><thead><tr><th colspan=\"2\" abc=\"a\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
should = "<table bgcolor=\"green\"><thead><tr><th colspan=\"2\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
result = App.Utils.htmlCleanup(source)
equal(result.get(0).outerHTML, should, source)
source = "<table bgcolor=\"green\" aaa=\"1\" style=\"color: red\"><thead><tr style=\"margin-top: 10px\"><th colspan=\"2\" abc=\"a\" style=\"margin-top: 12px\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
should = "<table bgcolor=\"green\" style=\"color: red;\"><thead><tr style=\"margin-top: 10px;\"><th colspan=\"2\" style=\"margin-top: 12px;\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
result = App.Utils.htmlCleanup(source)
//equal(result.get(0).outerHTML, should, source) / string order is different on browsers
equal(result.first().attr('bgcolor'), 'green')
equal(result.first().attr('style'), 'color: red;')
equal(result.first().attr('aaa'), undefined)
equal(result.find('tr').first().attr('style'), 'margin-top: 10px;')
equal(result.find('th').first().attr('colspan'), '2')
equal(result.find('th').first().attr('abc'), undefined)
equal(result.find('th').first().attr('style'), 'margin-top: 12px;')
source = "<table bgcolor=\"green\" aaa=\"1\" style=\"color:red; display: none;\"><thead><tr><th colspan=\"2\" abc=\"a\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
should = "<table bgcolor=\"green\" style=\"color:red;\"><thead><tr><th colspan=\"2\">aaa</th></tr></thead><tbody><tr><td>value</td></tr></tbody></table>"
result = App.Utils.htmlCleanup(source)
//equal(result.get(0).outerHTML, should, source) / string order is different on browsers
equal(result.first().attr('bgcolor'), 'green')
equal(result.first().attr('style'), 'color:red;')
equal(result.first().attr('aaa'), undefined)
equal(result.find('tr').first().attr('style'), undefined)
equal(result.find('th').first().attr('colspan'), '2')
equal(result.find('th').first().attr('abc'), undefined)
equal(result.find('th').first().attr('style'), undefined)
}); });
// wrap // wrap