diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index 45e53b83a..f465983a2 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -319,7 +319,7 @@ class App.TicketZoom extends App.Controller @form_id = App.ControllerForm.formId() - new App.TicketZoomArticleNew( + @articleNew = new App.TicketZoomArticleNew( ticket: @ticket ticket_id: @ticket.id el: elLocal.find('.article-new') @@ -495,12 +495,15 @@ class App.TicketZoom extends App.Controller e.stopPropagation() e.preventDefault() + # validate new article + return if !@articleNew.validate() + taskAction = @$('.js-secondaryActionButtonLabel').data('type') ticketParams = @formParam( @$('.edit') ) # validate ticket - ticket = App.Ticket.fullLocal( @ticket.id ) + ticket = App.Ticket.fullLocal(@ticket.id) # reset article - should not be resubmited on next ticket update ticket.article = undefined @@ -564,76 +567,9 @@ class App.TicketZoom extends App.Controller console.log('ticket validateion ok') - # validate article - articleParams = @formParam( @$('.article-add') ) - 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 - + articleParams = @articleNew.params() + if articleParams 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) errors = article.validate() if errors @@ -721,7 +657,7 @@ class App.TicketZoom extends App.Controller App.Ajax.request( type: 'DELETE' 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 ) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index 203fd6565..b0c1d9133 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -12,6 +12,8 @@ class App.TicketZoomArticleNew extends App.Controller '.js-cancel': 'cancelContainer' '.textBubble': 'textBubble' '.editControls-item': 'editControlItem' + '.js-letterCount': 'letterCount' + '.js-signature': 'signature' events: 'click .js-toggleVisibility': 'toggleVisibility' @@ -21,6 +23,7 @@ class App.TicketZoomArticleNew extends App.Controller 'click .js-writeArea': 'propagateOpenTextarea' 'click .list-entry-type div': 'changeType' 'focus .js-textarea': 'openTextarea' + 'input .js-textarea': 'updateLetterCount' constructor: -> super @@ -32,11 +35,13 @@ class App.TicketZoomArticleNew extends App.Controller name: 'note' icon: 'note' attributes: [] + features: ['attachment'] }, { name: 'email' icon: 'email' - attributes: ['to','cc'] + attributes: ['to', 'cc'] + features: ['attachment'] }, { name: 'facebook' @@ -44,21 +49,29 @@ class App.TicketZoomArticleNew extends App.Controller attributes: [] }, { - name: 'twitter status' - icon: 'twitter' - attributes: [] + name: 'twitter status' + icon: 'twitter' + attributes: [] + features: ['body:limit'] + maxTextLength: 140 + warningTextLength: 30 }, { - name: 'twitter direct-message' - icon: 'twitter' - attributes: ['to'] + name: 'twitter direct-message' + icon: 'twitter' + attributes: ['to'] + features: ['body:limit'] + maxTextLength: 10000 + warningTextLength: 500 }, { name: 'phone' icon: 'phone' attributes: [] + features: ['attachment'] }, ] + if @isRole('Customer') @type = 'note' @articleTypes = [ @@ -66,6 +79,7 @@ class App.TicketZoomArticleNew extends App.Controller name: 'note' icon: 'note' attributes: [] + features: ['attachment'] }, ] @@ -216,6 +230,106 @@ class App.TicketZoomArticleNew extends App.Controller ) @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) -> $(e.target).addClass('active').siblings('.active').removeClass('active') @@ -232,7 +346,6 @@ class App.TicketZoomArticleNew extends App.Controller .addClass 'is-public' .removeClass 'is-internal' - @$('[name=internal]').val '' showSelectableArticleType: (event) => @@ -243,7 +356,7 @@ class App.TicketZoomArticleNew extends App.Controller selectArticleType: (event) => event.stopPropagation() articleTypeToSet = $(event.target).closest('.pop-selectable').data('value') - @setArticleType( articleTypeToSet ) + @setArticleType(articleTypeToSet) @hideSelectableArticleType() $(window).off 'click.ticket-zoom-select-type' @@ -251,20 +364,13 @@ class App.TicketZoomArticleNew extends App.Controller hideSelectableArticleType: => @el.find('.js-articleTypes').addClass('is-hidden') - setArticleType: (type) -> + setArticleType: (type) => wasScrolledToBottom = @isScrolledToBottom() @type = type @$('[name=type]').val(type) @articleNewEdit.attr('data-type', type) @$('.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) ticketCurrent = App.Ticket.find(@ticket_id) group_id = ticketCurrent.group_id @@ -299,6 +405,31 @@ class App.TicketZoomArticleNew extends App.Controller else @$('[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 isScrolledToBottom: -> @@ -311,6 +442,26 @@ class App.TicketZoomArticleNew extends App.Controller event.stopPropagation() @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) => if event event.stopPropagation() @@ -450,7 +601,7 @@ class App.TicketZoomArticleNew extends App.Controller App.Ajax.request( type: 'DELETE' 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 ) diff --git a/app/assets/javascripts/app/lib/app_post/utils.coffee b/app/assets/javascripts/app/lib/app_post/utils.coffee index a31859c52..24c4c475c 100644 --- a/app/assets/javascripts/app/lib/app_post/utils.coffee +++ b/app/assets/javascripts/app/lib/app_post/utils.coffee @@ -17,24 +17,31 @@ class App.Utils ascii = '
' + ascii.replace(/\n/g, '
') + '
' ascii.replace(/
<\/div>/g, '

') - # rawText = App.Utils.html2text(html) - @html2text: (html) -> + # rawText = App.Utils.html2text(html, no_trim) + @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 $('
' + html + '
').text() # 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 - .replace(//g, "\n") - .replace(//g, "\n") + .replace(/<(br|hr)>/g, "\n") + .replace(/<(br|hr)\/>/g, "\n") .replace(/<(div)(|.+?)>/g, "") .replace(/<(p|blockquote|form|textarea|address|tr)(|.+?)>/g, "\n") .replace(/<\/(div|p|blockquote|form|textarea|address|tr)>/g, "\n") - - # trim and cleanup $('
' + html + '
').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 # htmlEscapedAndLinkified = App.Utils.linkify(rawText) diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco index 79a1e413e..68f9607b6 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_new.jst.eco @@ -52,6 +52,10 @@
<%- @article.body %>
+
diff --git a/app/models/observer/ticket/article/communicate_twitter.rb b/app/models/observer/ticket/article/communicate_twitter.rb index 4171344d6..9bad020f0 100644 --- a/app/models/observer/ticket/article/communicate_twitter.rb +++ b/app/models/observer/ticket/article/communicate_twitter.rb @@ -25,7 +25,7 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer tweet = channel.deliver( type: type['name'], to: record.to, - body: record.body.html2text, + body: record.body, in_reply_to: record.in_reply_to ) diff --git a/public/assets/tests/html-utils.js b/public/assets/tests/html-utils.js index 2a71d2ac4..c9bd78c3e 100644 --- a/public/assets/tests/html-utils.js +++ b/public/assets/tests/html-utils.js @@ -113,6 +113,48 @@ test("html2text", function() { should = "test 123\nlalala\n--\nsome test" result = App.Utils.html2text(source) equal(result, should, source) + + source = "

Was\nsoll verbessert werden:

" + should = "Was soll verbessert werden:" + result = App.Utils.html2text(source) + equal(result, should, source) + + // in raw format, without cleanup + source = "
Some
1234
" + should = "Some\n1234\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "
Some
1234
" + should = "Some\n 1234\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "\n\n
Some
\n
1234
" + should = "Some\n 1234\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "
Some
1234
" + should = "Some\n 1234\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "
Some
\n\n
1234
\n" + should = "Some\n 1234\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "
test
new line
" + should = "test\nnew line\n\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + + source = "

Was\nsoll verbessert werden:

" + should = "Was soll verbessert werden:\n" + result = App.Utils.html2text(source, true) + equal(result, should, source) + }); // linkify