From f4341f5c4bf9ec91b4001fe33193101de12ac8e6 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 12 Jan 2015 12:06:07 +0100 Subject: [PATCH 1/8] Added dropped test. --- test/unit/email_process_test.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unit/email_process_test.rb b/test/unit/email_process_test.rb index 9bead59fb..4cd6a3072 100644 --- a/test/unit/email_process_test.rb +++ b/test/unit/email_process_test.rb @@ -2050,6 +2050,28 @@ Some Text', }, }, }, + { + :data => 'From: Some Body +To: Bob +Cc: any@example.com +Subject: some subject + +Some Text', + :trusted => false, + :success => true, + :result => { + 0 => { + :group => group2.name, + :priority => '2 normal', + :title => 'some subject', + }, + 1 => { + :sender => 'Customer', + :type => 'email', + :internal => true, + }, + }, + }, ] process(files) PostmasterFilter.destroy_all From 1fdc9858161cc9c4446afa6175e48eaf3c58a8ec Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 12 Jan 2015 23:15:39 +0100 Subject: [PATCH 2/8] Added signature support to new and zoom ticket. --- .../controllers/agent_ticket_create.js.coffee | 87 ++++++++++++------ .../app/controllers/ticket_zoom.js.coffee | 44 ++++++---- .../app/lib/app_post/utils.js.coffee | 24 +++++ app/controllers/tickets_controller.rb | 17 ---- public/assets/tests/html-utils.js | 88 +++++++++++++++++++ 5 files changed, 200 insertions(+), 60 deletions(-) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index 62262e123..03b5ff425 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -3,10 +3,10 @@ class App.TicketCreate extends App.Controller '.tabsSidebar' : 'sidebar' events: - 'click .type-tabs .tab': 'changeFormType' - 'submit form': 'submit' - 'click .submit': 'submit' - 'click .cancel': 'cancel' + 'click .type-tabs .tab': 'changeFormType' + 'submit form': 'submit' + 'click .submit': 'submit' + 'click .cancel': 'cancel' constructor: (params) -> super @@ -87,6 +87,9 @@ class App.TicketCreate extends App.Controller # update form @el.find('[name="formSenderType"]').val(type) + # force changing signature + @el.find('[name="group_id"]').trigger('change') + meta: => text = '' if @articleAttributes @@ -167,10 +170,10 @@ class App.TicketCreate extends App.Controller a = App.TicketArticle.find( params.article_id ) # reset owner - t.owner_id = 0 - t.customer_id_autocompletion = a.from - t.subject = a.subject || t.title - t.body = a.body + t.owner_id = 0 + t.customer_id_completion = a.from + t.subject = a.subject || t.title + t.body = a.body # render page @render( options: t ) @@ -191,10 +194,10 @@ class App.TicketCreate extends App.Controller @form_id = App.ControllerForm.formId() @html App.view('agent_ticket_create')( - head: 'New Ticket' - agent: @isRole('Agent') - admin: @isRole('Admin') - form_id: @form_id + head: 'New Ticket' + agent: @isRole('Agent') + admin: @isRole('Admin') + form_id: @form_id ) formChanges = (params, attribute, attributes, classname, form, ui) => @@ -220,6 +223,35 @@ class App.TicketCreate extends App.Controller # replace new option list form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) + signatureChanges = (params, attribute, attributes, classname, form, ui) => + if attribute && attribute.name is 'group_id' + signature = undefined + if params['group_id'] + group = App.Group.find( params['group_id'] ) + if group && group.signature_id + signature = App.Signature.find( group.signature_id ) + + # check if signature need to be added + type = @$('[name="formSenderType"]').val() + + if signature isnt undefined && signature.body && type is 'email-out' + signatureFinished = App.Utils.text2html( + App.Utils.replaceTags( signature.body, { user: App.Session.get() } ) + ) + + # get current body + body = @$('[data-name="body"]').html() || '' + if App.Utils.signatureCheck( body, signatureFinished ) + if !App.Utils.lastLineEmpty(body) + body = body + '
' + body = body + "
#{signatureFinished}
" + + @$('[data-name="body"]').html(body) + + # remove old signature + else + @$('[data-name="body"]').find("[data-signature=true]").remove() + new App.ControllerForm( el: @el.find('.ticket-form-top') form_id: @form_id @@ -228,7 +260,8 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges + formChanges, + signatureChanges, ] filter: @form_meta.filter autofocus: true @@ -236,21 +269,22 @@ class App.TicketCreate extends App.Controller ) new App.ControllerForm( - el: @el.find('.article-form-top') - form_id: @form_id - model: App.TicketArticle - screen: 'create_top' - params: params + el: @el.find('.article-form-top') + form_id: @form_id + model: App.TicketArticle + screen: 'create_top' + params: params ) new App.ControllerForm( - el: @el.find('.ticket-form-middle') - form_id: @form_id - model: App.Ticket - screen: 'create_middle' + el: @el.find('.ticket-form-middle') + form_id: @form_id + model: App.Ticket + screen: 'create_middle' events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges + formChanges, + signatureChanges, ] filter: @form_meta.filter params: params @@ -264,7 +298,8 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges + formChanges, + signatureChanges, ] filter: @form_meta.filter params: params @@ -330,7 +365,7 @@ class App.TicketCreate extends App.Controller if sender.name is 'Customer' params['article'] = { to: (group && group.name) || '' - from: params.customer_id_autocompletion + from: params.customer_id_completion cc: params.cc subject: params.subject body: params.body @@ -342,7 +377,7 @@ class App.TicketCreate extends App.Controller else params['article'] = { from: (group && group.name) || '' - to: params.customer_id_autocompletion + to: params.customer_id_completion cc: params.cc subject: params.subject body: params.body diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 85f897f11..8fd4bb0f4 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -18,10 +18,9 @@ class App.TicketZoom extends App.Controller @navupdate '#' - @form_meta = undefined - @ticket_id = params.ticket_id - @article_id = params.article_id - @signature = undefined + @form_meta = undefined + @ticket_id = params.ticket_id + @article_id = params.article_id @key = 'ticket::' + @ticket_id cache = App.Store.get( @key ) @@ -137,9 +136,6 @@ class App.TicketZoom extends App.Controller # get edit form attributes @form_meta = data.form_meta - # get signature - @signature = data.signature - # load assets App.Collection.loadAssets( data.assets ) @@ -860,14 +856,15 @@ class Edit extends App.Controller if data.ticket.id is @ticket.id #@setArticleType(data.type.name) - # preselect article type - @setArticleType( 'email' ) @open_textarea(null, true) for key, value of data.article if key is 'body' @$('[data-name="' + key + '"]').html(value) else @$('[name="' + key + '"]').val(value) + + # preselect article type + @setArticleType( 'email' ) ) isIE10: -> @@ -905,7 +902,7 @@ class Edit extends App.Controller @$('[data-name="body"]').ce({ mode: 'richtext' multiline: true - maxlength: 2500 + maxlength: 5000 }) html5Upload.initialize( @@ -1075,10 +1072,29 @@ class Edit extends App.Controller # show/hide attributes for articleType in @articleTypes if articleType.name is type - @$(".form-group").addClass('hide') + @$('.form-group').addClass('hide') for name in articleType.attributes @$("[name=#{name}]").closest('.form-group').removeClass('hide') + # check if signature need to be added + body = @$('[data-name="body"]').html() || '' + signature = undefined + if @ticket.group.signature_id + signature = App.Signature.find( @ticket.group.signature_id ) + if signature && signature.body && @type is 'email' + signatureFinished = App.Utils.text2html( + App.Utils.replaceTags( signature.body, { user: App.Session.get(), ticket: @ticket } ) + ) + if App.Utils.signatureCheck( body, signatureFinished ) + if !App.Utils.lastLineEmpty(body) + body = body + '
' + body = body + "
#{signatureFinished}
" + @$('[data-name="body"]').html(body) + + # remove old signature + else + @$('[data-name="body"]').find("[data-signature=true]").remove() + detect_empty_textarea: => if !@textarea.text().trim() @add_textarea_catcher() @@ -1488,12 +1504,6 @@ class ArticleView extends App.Controller # get current body body = @ui.el.find('[data-name="body"]').html() || '' - # check if signature need to be added - if @ui.signature && @ui.signature.body && type.name is 'email' - signature = App.Utils.text2html( @ui.signature.body ) - if App.Utils.signatureCheck( body, signature ) - body = body + signature - # check if quote need to be added selectedText = App.ClipBoard.getSelected() if selectedText diff --git a/app/assets/javascripts/app/lib/app_post/utils.js.coffee b/app/assets/javascripts/app/lib/app_post/utils.js.coffee index 5f576a74f..6690bf4e7 100644 --- a/app/assets/javascripts/app/lib/app_post/utils.js.coffee +++ b/app/assets/javascripts/app/lib/app_post/utils.js.coffee @@ -166,3 +166,27 @@ class App.Utils else true + # textReplaced = App.Utils.replaceTags( template, { user: { firstname: 'Bob', lastname: 'Smith' } } ) + @replaceTags: (template, objects) -> + template = template.replace( /#\{\s{0,2}(.+?)\s{0,2}\}/g, ( index, key ) -> + levels = key.split(/\./) + dataRef = objects + for level in levels + if dataRef[level] + dataRef = dataRef[level] + if typeof dataRef is 'function' + value = dataRef() + else if typeof dataRef is 'string' + value = dataRef + else + value = '' + #console.log( "tag replacement #{key}, #{value} env: ", objects) + value + ) + + # true|false = App.Utils.lastLineEmpty( message ) + @lastLineEmpty: (message) -> + messageCleanup = message.replace(/>\s+<').replace(/(\n|\r|\t)/g, '').trim() + return true if messageCleanup.match(/<(br|\s+?|\/)>$/im) + return true if messageCleanup.match(/<\/div>$/im) + false diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index 696181dbd..b8fc5b27b 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -228,22 +228,6 @@ class TicketsController < ApplicationController ticket = Ticket.find( params[:id] ) return if !ticket_permission( ticket ) - # get signature - signature = {} - if ticket.group.signature - signature = ticket.group.signature.attributes - - # replace tags - signature['body'] = NotificationFactory.build( - :locale => current_user.preferences[:locale], - :string => signature['body'], - :objects => { - :ticket => ticket, - :user => current_user, - } - ) - end - # get attributes to update attributes_to_change = Ticket::ScreenOptions.attributes_to_change( :user => current_user, :ticket => ticket ) @@ -292,7 +276,6 @@ class TicketsController < ApplicationController render :json => { :ticket_id => ticket.id, :ticket_article_ids => article_ids, - :signature => signature, :assets => assets, :links => link_list, :tags => tags, diff --git a/public/assets/tests/html-utils.js b/public/assets/tests/html-utils.js index 392e7febb..fff291fe2 100644 --- a/public/assets/tests/html-utils.js +++ b/public/assets/tests/html-utils.js @@ -439,4 +439,92 @@ test( "check signature", function() { }); +// replace tags +test( "check replace tags", function() { + + var message = "
#{user.firstname} #{user.lastname}
" + var result = '
Bob Smith
' + var data = { + user: { + firstname: 'Bob', + lastname: 'Smith', + }, + } + var verify = App.Utils.replaceTags( message, data ) + equal( verify, result ) + + message = "
#{user.firstname} #{user.lastname}
" + result = '
Bob Smith
' + data = { + user: { + firstname: function() { return 'Bob' }, + lastname: function() { return 'Smith' }, + }, + } + verify = App.Utils.replaceTags( message, data ) + equal( verify, result ) + + message = "
#{user.firstname} #{user.lastname}
" + result = '
Bob
' + data = { + user: { + firstname: 'Bob', + }, + } + verify = App.Utils.replaceTags( message, data ) + equal( verify, result ) + +}); + +// check if last line is a empty line +test( "check if last line is a empty line", function() { + + var message = "123" + var result = false + var verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
123
" + result = false + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "

123

" + result = false + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
\n \n\t" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
\n \n\t" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + message = "
\n
\n \n\t" + result = true + verify = App.Utils.lastLineEmpty( message ) + equal( verify, result, message ) + + +}); + } \ No newline at end of file From 83b4360c368d71f3ead08fdffe8c8422f34dfc28 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Tue, 13 Jan 2015 02:19:45 +0100 Subject: [PATCH 3/8] add a delay to article meta toggle to allow double click select its 150ms - maybe we need more. plz test. but the fewer delay we need the better cause it'll feel snappier. --- .../app/controllers/ticket_zoom.js.coffee | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 8fd4bb0f4..c4e523d1c 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -1262,7 +1262,7 @@ class ArticleView extends App.Controller 'click .show_toogle': 'show_toogle' 'click [data-type=reply]': 'reply' 'click [data-type=replyAll]': 'replyAll' - 'click .text-bubble': 'toggle_meta' + 'click .text-bubble': 'toggle_meta_with_delay' 'click .text-bubble a': 'stopPropagation' constructor: -> @@ -1327,7 +1327,17 @@ class ArticleView extends App.Controller stopPropagation: (e) -> e.stopPropagation() - toggle_meta: (e) -> + toggle_meta_with_delay: (e) => + # allow double click select + # by adding a delay to the toggle + + if @lastClick and +new Date - @lastClick < 150 + clearTimeout(@toggleMetaTimeout) + else + @toggleMetaTimeout = setTimeout(@toggle_meta, 150, e) + @lastClick = +new Date + + toggle_meta: (e) => e.preventDefault() animSpeed = 300 From 8237c57effa1b008970796c0b91c7eb4b8168c2b Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Tue, 13 Jan 2015 02:20:46 +0100 Subject: [PATCH 4/8] round big smiley icon edges --- public/assets/images/sprite.svg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/assets/images/sprite.svg b/public/assets/images/sprite.svg index 992dec70f..654d819c3 100644 --- a/public/assets/images/sprite.svg +++ b/public/assets/images/sprite.svg @@ -48,11 +48,11 @@ - - - - - + + + + + From 4a9a2d8d1038addfa6a508df67b63689a1716d38 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 13 Jan 2015 08:26:45 +0100 Subject: [PATCH 5/8] Improved replacing signature. --- .../app/controllers/agent_ticket_create.js.coffee | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee index 03b5ff425..5347ddd79 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -242,6 +242,15 @@ class App.TicketCreate extends App.Controller # get current body body = @$('[data-name="body"]').html() || '' if App.Utils.signatureCheck( body, signatureFinished ) + + # if signature has changed, replace it + signature_id = @$('[data-signature=true]').data('signature-id') + if signature_id && signature_id.toString() isnt signature.id.toString() + + # remove old signature + @$('[data-signature="true"]').remove() + body = @$('[data-name="body"]').html() || '' + if !App.Utils.lastLineEmpty(body) body = body + '
' body = body + "
#{signatureFinished}
" @@ -250,7 +259,7 @@ class App.TicketCreate extends App.Controller # remove old signature else - @$('[data-name="body"]').find("[data-signature=true]").remove() + @$('[data-name="body"]').find('[data-signature=true]').remove() new App.ControllerForm( el: @el.find('.ticket-form-top') From 71d8af7a5260c9b062c348a7aed7ebce173502a8 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 13 Jan 2015 15:53:15 +0100 Subject: [PATCH 6/8] Moved from Marshal to JSON session files. --- app/controllers/long_polling_controller.rb | 2 +- lib/sessions.rb | 27 ++++++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/controllers/long_polling_controller.rb b/app/controllers/long_polling_controller.rb index 5512175e8..fa5a99b6b 100644 --- a/app/controllers/long_polling_controller.rb +++ b/app/controllers/long_polling_controller.rb @@ -59,7 +59,7 @@ class LongPollingController < ApplicationController user_id = session[:user_id] user = {} if user_id - user = User.find( user_id ) + user = User.find( user_id ).attributes end log 'notice', "send auth login (user_id #{user_id})", client_id Sessions.create( client_id, user, { :type => 'ajax' } ) diff --git a/lib/sessions.rb b/lib/sessions.rb index 7d299ee7e..0962312d9 100644 --- a/lib/sessions.rb +++ b/lib/sessions.rb @@ -37,7 +37,7 @@ returns :user => session, :meta => meta, } - file.write Marshal.dump(data) + file.write data.to_json } # send update to browser @@ -195,7 +195,7 @@ returns path = @path + '/' + client_id.to_s data[:meta][:last_ping] = Time.new.to_i.to_s File.open( path + '/session', 'wb' ) { |file| - file.write Marshal.dump(data) + file.write data.to_json } true end @@ -234,7 +234,11 @@ returns file.flock( File::LOCK_EX ) all = file.read file.flock( File::LOCK_UN ) - data = Marshal.load( all ) + dataJSON = JSON.parse( all ) + if dataJSON + data = self.symbolize_keys(dataJSON) + data[:user] = dataJSON['user'] # for compat. reasons + end } rescue Exception => e puts e.inspect @@ -567,4 +571,19 @@ returns puts "/LOOP #{client_id} - #{try_count}" end -end + def self.symbolize_keys(hash) + hash.inject({}){|result, (key, value)| + new_key = case key + when String then key.to_sym + else key + end + new_value = case value + when Hash then symbolize_keys(value) + else value + end + result[new_key] = new_value + result + } + end + +end \ No newline at end of file From f38149e82338a55a3127556f231d0e40034e7b9d Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 13 Jan 2015 17:03:58 +0100 Subject: [PATCH 7/8] Improved comments. --- script/websocket-server.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/script/websocket-server.rb b/script/websocket-server.rb index 7d784c31d..749261950 100755 --- a/script/websocket-server.rb +++ b/script/websocket-server.rb @@ -295,7 +295,7 @@ EventMachine.run { idle_time_in_min = 4 - # web sockets + # close unused web socket sessions @clients.each { |client_id, client| if ( client[:last_ping] + ( 60 * idle_time_in_min ) ) < Time.now log 'notice', "closing idle websocket connection", client_id @@ -312,10 +312,10 @@ EventMachine.run { end } - # close unused sessions + # close unused ajax long polling sessions clients = Sessions.destory_idle_sessions(idle_time_in_min) clients.each { |client_id| - log 'notice', "closing idle connection", client_id + log 'notice', "closing idle long polling connection", client_id } end @@ -327,4 +327,4 @@ EventMachine.run { # puts "#{Time.now}:#{ level }:client(#{ client_id }) #{ data }" end -} +} \ No newline at end of file From c5622e54323a2fa89bfe03579c32a335852e41f5 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Tue, 13 Jan 2015 17:04:54 +0100 Subject: [PATCH 8/8] Removed not needed rule. --- app/assets/stylesheets/zammad.css.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index ccb71dbb5..992376e41 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -2041,10 +2041,6 @@ footer { text-decoration: none; } - .task .modified.priority.icon { - margin-right: 8px; - } - .task.active { background: #389ed9; } @@ -2106,7 +2102,7 @@ footer { .level-3 .modified.priority.icon:after { background-color: #faab00; border-color: #faab00; - } + } .task .closeTask { position: absolute;