Implemented issue #707 - Edge looses text formatting on reply (may also on other browsers).
This commit is contained in:
parent
ef282e187b
commit
62e5be1ed2
4 changed files with 200 additions and 63 deletions
|
@ -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()
|
||||||
|
if !selected
|
||||||
|
selected = App.ClipBoard.getSelected('text')
|
||||||
|
if selected
|
||||||
|
selected = App.Utils.textCleanup(selected)
|
||||||
|
selected = App.Utils.text2html(selected)
|
||||||
|
if selected
|
||||||
|
selected = "<div><br><br/></div><div><blockquote type=\"cite\">#{selected}</blockquote></div><div><br></div>"
|
||||||
|
|
||||||
# clean selection
|
# add selected text to body
|
||||||
selectedText = App.Utils.textCleanup(selectedText)
|
body = selected + body
|
||||||
|
|
||||||
# convert to html
|
|
||||||
selectedText = App.Utils.text2html(selectedText)
|
|
||||||
if selectedText
|
|
||||||
selectedText = "<div><br><br/></div><div><blockquote type=\"cite\">#{selectedText}</blockquote></div><div><br></div>"
|
|
||||||
|
|
||||||
# add selected text to body
|
|
||||||
body = selectedText + body
|
|
||||||
|
|
||||||
articleNew.body = body
|
articleNew.body = body
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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) ->
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue