Added twitter length count support in new article.

This commit is contained in:
Martin Edenhofer 2016-01-13 00:55:17 +01:00
parent c91b782f16
commit 7f27fefdb6
6 changed files with 241 additions and 101 deletions

View file

@ -319,7 +319,7 @@ class App.TicketZoom extends App.Controller
@form_id = App.ControllerForm.formId() @form_id = App.ControllerForm.formId()
new App.TicketZoomArticleNew( @articleNew = new App.TicketZoomArticleNew(
ticket: @ticket ticket: @ticket
ticket_id: @ticket.id ticket_id: @ticket.id
el: elLocal.find('.article-new') el: elLocal.find('.article-new')
@ -495,6 +495,9 @@ class App.TicketZoom extends App.Controller
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
# validate new article
return if !@articleNew.validate()
taskAction = @$('.js-secondaryActionButtonLabel').data('type') taskAction = @$('.js-secondaryActionButtonLabel').data('type')
ticketParams = @formParam( @$('.edit') ) ticketParams = @formParam( @$('.edit') )
@ -564,76 +567,9 @@ class App.TicketZoom extends App.Controller
console.log('ticket validateion ok') console.log('ticket validateion ok')
# validate article articleParams = @articleNew.params()
articleParams = @formParam( @$('.article-add') ) if articleParams
console.log 'submit article', articleParams
# check if attachment exists but no body
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
if !articleParams['body'] && attachmentCount > 0
new App.ControllerModal(
head: 'Text missing'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: 'Please fill also some text in!'
shown: true
small: true
container: @el.closest('.content')
)
@formEnable(e)
@autosaveStart()
return
if articleParams['body']
articleParams.from = @Session.get().displayName()
articleParams.ticket_id = ticket.id
articleParams.form_id = @form_id
articleParams.content_type = 'text/html'
if !articleParams['internal']
articleParams['internal'] = false
if @isRole('Customer')
sender = App.TicketArticleSender.findByAttribute( 'name', 'Customer' )
type = App.TicketArticleType.findByAttribute( 'name', 'web' )
articleParams.type_id = type.id
articleParams.sender_id = sender.id
else
sender = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
articleParams.sender_id = sender.id
type = App.TicketArticleType.findByAttribute( 'name', articleParams['type'] )
articleParams.type_id = type.id
article = new App.TicketArticle article = new App.TicketArticle
for key, value of articleParams
article[key] = value
# validate email params
if type.name is 'email'
# check if recipient exists
if !articleParams['to'] && !articleParams['cc']
alert( App.i18n.translateContent('Need recipient in "To" or "Cc".') )
@formEnable(e)
@autosaveStart()
return
# check if message exists
if !articleParams['body']
alert( App.i18n.translateContent('Text needed') )
@formEnable(e)
@autosaveStart()
return
# check attachment
if articleParams['body']
if App.Utils.checkAttachmentReference( articleParams['body'] ) && attachmentCount < 1
if !confirm( App.i18n.translateContent('You use attachment in text but no attachment is attached. Do you want to continue?') )
@formEnable(e)
@autosaveStart()
return
article.load(articleParams) article.load(articleParams)
errors = article.validate() errors = article.validate()
if errors if errors
@ -721,7 +657,7 @@ class App.TicketZoom extends App.Controller
App.Ajax.request( App.Ajax.request(
type: 'DELETE' type: 'DELETE'
url: App.Config.get('api_path') + '/ticket_attachment_upload' url: App.Config.get('api_path') + '/ticket_attachment_upload'
data: JSON.stringify( { form_id: @form_id } ) data: JSON.stringify(form_id: @form_id)
processData: false processData: false
) )

View file

@ -12,6 +12,8 @@ class App.TicketZoomArticleNew extends App.Controller
'.js-cancel': 'cancelContainer' '.js-cancel': 'cancelContainer'
'.textBubble': 'textBubble' '.textBubble': 'textBubble'
'.editControls-item': 'editControlItem' '.editControls-item': 'editControlItem'
'.js-letterCount': 'letterCount'
'.js-signature': 'signature'
events: events:
'click .js-toggleVisibility': 'toggleVisibility' 'click .js-toggleVisibility': 'toggleVisibility'
@ -21,6 +23,7 @@ class App.TicketZoomArticleNew extends App.Controller
'click .js-writeArea': 'propagateOpenTextarea' 'click .js-writeArea': 'propagateOpenTextarea'
'click .list-entry-type div': 'changeType' 'click .list-entry-type div': 'changeType'
'focus .js-textarea': 'openTextarea' 'focus .js-textarea': 'openTextarea'
'input .js-textarea': 'updateLetterCount'
constructor: -> constructor: ->
super super
@ -32,11 +35,13 @@ class App.TicketZoomArticleNew extends App.Controller
name: 'note' name: 'note'
icon: 'note' icon: 'note'
attributes: [] attributes: []
features: ['attachment']
}, },
{ {
name: 'email' name: 'email'
icon: 'email' icon: 'email'
attributes: ['to', 'cc'] attributes: ['to', 'cc']
features: ['attachment']
}, },
{ {
name: 'facebook' name: 'facebook'
@ -47,18 +52,26 @@ class App.TicketZoomArticleNew extends App.Controller
name: 'twitter status' name: 'twitter status'
icon: 'twitter' icon: 'twitter'
attributes: [] attributes: []
features: ['body:limit']
maxTextLength: 140
warningTextLength: 30
}, },
{ {
name: 'twitter direct-message' name: 'twitter direct-message'
icon: 'twitter' icon: 'twitter'
attributes: ['to'] attributes: ['to']
features: ['body:limit']
maxTextLength: 10000
warningTextLength: 500
}, },
{ {
name: 'phone' name: 'phone'
icon: 'phone' icon: 'phone'
attributes: [] attributes: []
features: ['attachment']
}, },
] ]
if @isRole('Customer') if @isRole('Customer')
@type = 'note' @type = 'note'
@articleTypes = [ @articleTypes = [
@ -66,6 +79,7 @@ class App.TicketZoomArticleNew extends App.Controller
name: 'note' name: 'note'
icon: 'note' icon: 'note'
attributes: [] attributes: []
features: ['attachment']
}, },
] ]
@ -216,6 +230,106 @@ class App.TicketZoomArticleNew extends App.Controller
) )
@subscribeIdTextModule = ticket.subscribe( callback ) @subscribeIdTextModule = ticket.subscribe( callback )
params: =>
params = @formParam( @$('.article-add') )
if params.body
params.from = @Session.get().displayName()
params.ticket_id = @ticket_id
params.form_id = @form_id
params.content_type = 'text/html'
if !params['internal']
params['internal'] = false
if @isRole('Customer')
sender = App.TicketArticleSender.findByAttribute('name', 'Customer')
type = App.TicketArticleType.findByAttribute('name', 'web')
params.type_id = type.id
params.sender_id = sender.id
else
sender = App.TicketArticleSender.findByAttribute('name', 'Agent')
params.sender_id = sender.id
type = App.TicketArticleType.findByAttribute('name', params['type'])
params.type_id = type.id
if params.type is 'twitter status'
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'))
params.content_type = 'text/plain'
params.body = "#{App.Utils.html2text(params.body, true)}\n#{@signature.text()}"
if params.type is 'twitter direct-message'
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'))
params.content_type = 'text/plain'
params.body = "#{App.Utils.html2text(params.body, true)}\n#{@signature.text()}"
params
validate: =>
params = @params()
# check if attachment exists but no body
attachmentCount = @$('.article-add .textBubble .attachments .attachment').length
if !params.body && attachmentCount > 0
new App.ControllerModal(
head: 'Text missing'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: 'Please fill also some text in!'
shown: true
small: true
container: @el.closest('.content')
)
return false
# validate email params
if params.type is 'email'
# check if recipient exists
if !params.to && !params.cc
new App.ControllerModal(
head: 'Text missing'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: 'Need recipient in "To" or "Cc".'
shown: true
small: true
container: @el.closest('.content')
)
return false
# check if message exists
if !params.body
new App.ControllerModal(
head: 'Text missing'
buttonCancel: 'Cancel'
buttonCancelClass: 'btn--danger'
buttonSubmit: false
message: 'Text needed'
shown: true
small: true
container: @el.closest('.content')
)
return false
# check attachment
if params.body
if App.Utils.checkAttachmentReference(params.body) && attachmentCount < 1
if !confirm( App.i18n.translateContent('You use attachment in text but no attachment is attached. Do you want to continue?') )
return false
if params.type is 'twitter status'
params.body
textLength = @maxTextLength - params.body.length
return false if textLength < 0
if params.type is 'twitter direct-message'
textLength = @maxTextLength - params.body.length
return false if textLength < 0
true
changeType: (e) -> changeType: (e) ->
$(e.target).addClass('active').siblings('.active').removeClass('active') $(e.target).addClass('active').siblings('.active').removeClass('active')
@ -232,7 +346,6 @@ class App.TicketZoomArticleNew extends App.Controller
.addClass 'is-public' .addClass 'is-public'
.removeClass 'is-internal' .removeClass 'is-internal'
@$('[name=internal]').val '' @$('[name=internal]').val ''
showSelectableArticleType: (event) => showSelectableArticleType: (event) =>
@ -251,20 +364,13 @@ class App.TicketZoomArticleNew extends App.Controller
hideSelectableArticleType: => hideSelectableArticleType: =>
@el.find('.js-articleTypes').addClass('is-hidden') @el.find('.js-articleTypes').addClass('is-hidden')
setArticleType: (type) -> setArticleType: (type) =>
wasScrolledToBottom = @isScrolledToBottom() wasScrolledToBottom = @isScrolledToBottom()
@type = type @type = type
@$('[name=type]').val(type) @$('[name=type]').val(type)
@articleNewEdit.attr('data-type', type) @articleNewEdit.attr('data-type', type)
@$('.js-selectableTypes').addClass('hide').filter("[data-type='#{ type }']").removeClass('hide') @$('.js-selectableTypes').addClass('hide').filter("[data-type='#{ type }']").removeClass('hide')
# show/hide attributes
for articleType in @articleTypes
if articleType.name is type
@$('.form-group').addClass('hide')
for name in articleType.attributes
@$("[name=#{name}]").closest('.form-group').removeClass('hide')
# detect current signature (use current group_id, if not set, use ticket.group_id) # detect current signature (use current group_id, if not set, use ticket.group_id)
ticketCurrent = App.Ticket.find(@ticket_id) ticketCurrent = App.Ticket.find(@ticket_id)
group_id = ticketCurrent.group_id group_id = ticketCurrent.group_id
@ -299,6 +405,31 @@ class App.TicketZoomArticleNew extends App.Controller
else else
@$('[data-name=body] [data-signature=true]').remove() @$('[data-name=body] [data-signature=true]').remove()
# remove richtext
if @type is 'twitter status'
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'))
if @type is 'twitter direct-message'
App.Utils.htmlRemoveRichtext(@$('[data-name=body]'))
# show/hide attributes/features
@maxTextLength = undefined
@warningTextLength = undefined
for articleType in @articleTypes
if articleType.name is type
@$('.form-group').addClass('hide')
for name in articleType.attributes
@$("[name=#{name}]").closest('.form-group').removeClass('hide')
@$('.article-attachment, .attachments, .js-textSizeLimit').addClass('hide')
for name in articleType.features
if name is 'attachment'
@$('.article-attachment, .attachments').removeClass('hide')
if name is 'body:limit'
@maxTextLength = articleType.maxTextLength
@warningTextLength = articleType.warningTextLength
@delay(@updateLetterCount, 600)
@updateInitials()
@$('.js-textSizeLimit').removeClass('hide')
@scrollToBottom() if wasScrolledToBottom @scrollToBottom() if wasScrolledToBottom
isScrolledToBottom: -> isScrolledToBottom: ->
@ -311,6 +442,26 @@ class App.TicketZoomArticleNew extends App.Controller
event.stopPropagation() event.stopPropagation()
@textarea.focus() @textarea.focus()
updateLetterCount: =>
return if !@maxTextLength
return if !@warningTextLength
params = @params()
textLength = @maxTextLength - params.body.length
className = switch
when textLength < 0 then 'label-danger'
when textLength < @warningTextLength then 'label-warning'
else ''
@letterCount
.text textLength
.removeClass 'label-danger label-warning'
.addClass className
updateInitials: (value) =>
if value is undefined
value = "/#{App.User.find(@Session.get('id')).initials()}"
@signature.text(value)
openTextarea: (event, withoutAnimation) => openTextarea: (event, withoutAnimation) =>
if event if event
event.stopPropagation() event.stopPropagation()
@ -450,7 +601,7 @@ class App.TicketZoomArticleNew extends App.Controller
App.Ajax.request( App.Ajax.request(
type: 'DELETE' type: 'DELETE'
url: App.Config.get('api_path') + '/ticket_attachment_upload' url: App.Config.get('api_path') + '/ticket_attachment_upload'
data: JSON.stringify( { store_id: store_id } ) data: JSON.stringify(store_id: store_id)
processData: false processData: false
) )

View file

@ -17,24 +17,31 @@ class App.Utils
ascii = '<div>' + ascii.replace(/\n/g, '</div><div>') + '</div>' ascii = '<div>' + ascii.replace(/\n/g, '</div><div>') + '</div>'
ascii.replace(/<div><\/div>/g, '<div><br></div>') ascii.replace(/<div><\/div>/g, '<div><br></div>')
# rawText = App.Utils.html2text(html) # rawText = App.Utils.html2text(html, no_trim)
@html2text: (html) -> @html2text: (html, no_trim) ->
if no_trim
html = html
.replace(/([A-z])\n([A-z])/gm, '$1 $2')
.replace(/\n|\r/g, '')
.replace(/<(br|hr)>/g, "\n")
.replace(/<(br|hr)\/>/g, "\n")
.replace(/<\/(div|p|blockquote|form|textarea|address|tr)>/g, "\n")
return $('<div>' + html + '</div>').text()
# remove not needed new lines # remove not needed new lines
html = html.replace(/>\n/g, '>') html = html.replace(/([A-z])\n([A-z])/gm, '$1 $2')
.replace(/>\n/g, '>')
.replace(/\n|\r/g, '')
# insert new lines # trim and cleanup
html = html html = html
.replace(/<br(|.+?)>/g, "\n") .replace(/<(br|hr)>/g, "\n")
.replace(/<br\/>/g, "\n") .replace(/<(br|hr)\/>/g, "\n")
.replace(/<(div)(|.+?)>/g, "") .replace(/<(div)(|.+?)>/g, "")
.replace(/<(p|blockquote|form|textarea|address|tr)(|.+?)>/g, "\n") .replace(/<(p|blockquote|form|textarea|address|tr)(|.+?)>/g, "\n")
.replace(/<\/(div|p|blockquote|form|textarea|address|tr)>/g, "\n") .replace(/<\/(div|p|blockquote|form|textarea|address|tr)>/g, "\n")
# trim and cleanup
$('<div>' + html + '</div>').text().trim() $('<div>' + html + '</div>').text().trim()
.replace(/(\r\n|\n\r)/g, "\n") # cleanup
.replace(/\r/g, "\n") # cleanup
.replace(/\n{3,20}/g, "\n\n") # remove multiple empty lines .replace(/\n{3,20}/g, "\n\n") # remove multiple empty lines
# htmlEscapedAndLinkified = App.Utils.linkify(rawText) # htmlEscapedAndLinkified = App.Utils.linkify(rawText)

View file

@ -52,6 +52,10 @@
<div class="bubble-arrow"></div> <div class="bubble-arrow"></div>
<div class="js-textarea articleNewEdit-body" contenteditable="true" data-name="body"><%- @article.body %></div> <div class="js-textarea articleNewEdit-body" contenteditable="true" data-name="body"><%- @article.body %></div>
<!-- .textBubble grows with textarea (and expanding clone) --> <!-- .textBubble grows with textarea (and expanding clone) -->
<div class="textBubble-footer js-textSizeLimit">
<div class="textBubble-signatur"><span class="js-signature"></span></div>
<div class="textBubble-letterCount js-letterCount"></div>
</div>
<div class="attachments"></div> <div class="attachments"></div>
<div class="article-attachment"> <div class="article-attachment">
<div class="attachmentPlaceholder"> <div class="attachmentPlaceholder">

View file

@ -25,7 +25,7 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer
tweet = channel.deliver( tweet = channel.deliver(
type: type['name'], type: type['name'],
to: record.to, to: record.to,
body: record.body.html2text, body: record.body,
in_reply_to: record.in_reply_to in_reply_to: record.in_reply_to
) )

View file

@ -113,6 +113,48 @@ test("html2text", function() {
should = "test 123\nlalala\n--\nsome test" should = "test 123\nlalala\n--\nsome test"
result = App.Utils.html2text(source) result = App.Utils.html2text(source)
equal(result, should, source) equal(result, should, source)
source = "<p><span>Was\nsoll verbessert werden:</span></p>"
should = "Was soll verbessert werden:"
result = App.Utils.html2text(source)
equal(result, should, source)
// in raw format, without cleanup
source = "<div>Some</div><div>1234</div>"
should = "Some\n1234\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "<div>Some</div><div> 1234</div>"
should = "Some\n 1234\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "\n\n<div>Some</div>\n<div> 1234</div>"
should = "Some\n 1234\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "<div>Some</div><div> 1234</div>"
should = "Some\n 1234\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "<div>Some</div>\n\n<div> 1234</div>\n"
should = "Some\n 1234\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "<div>test<br>new line<br></div>"
should = "test\nnew line\n\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
source = "<p><span>Was\nsoll verbessert werden:</span></p>"
should = "Was soll verbessert werden:\n"
result = App.Utils.html2text(source, true)
equal(result, should, source)
}); });
// linkify // linkify