From 1fd691b9d997d823b67fda1a1e20ed97f08ab94f Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 13:11:18 +0100 Subject: [PATCH 01/27] Added search result cache feature. --- .../app/controllers/navigation.js.coffee | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/navigation.js.coffee b/app/assets/javascripts/app/controllers/navigation.js.coffee index 0d888dce8..53ede62f5 100644 --- a/app/assets/javascripts/app/controllers/navigation.js.coffee +++ b/app/assets/javascripts/app/controllers/navigation.js.coffee @@ -115,6 +115,9 @@ class App.Navigation extends App.Controller render: () -> + # reset result cache + @searchResultCache = {} + user = App.Session.get() @html App.view('navigation')( user: user @@ -127,6 +130,11 @@ class App.Navigation extends App.Controller @renderPersonal() searchFunction = => + + # use cache for search result + if @searchResultCache[@term] + @renderResult( @searchResultCache[@term] ) + App.Ajax.request( id: 'search' type: 'GET' @@ -139,6 +147,9 @@ class App.Navigation extends App.Controller # load assets App.Collection.loadAssets( data.assets ) + # cache search result + @searchResultCache[@term] = data.result + result = data.result for area in result if area.name is 'Ticket' @@ -222,7 +233,7 @@ class App.Navigation extends App.Controller return if term is @term @term = term @$('.search').toggleClass('filled', !!@term) - @delay( searchFunction, 220, 'search' ) + @delay( searchFunction, 200, 'search' ) ) # bind to empty search From 5cb22714db8cdaa33bdff3c728126a9645e9eab3 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 13:12:52 +0100 Subject: [PATCH 02/27] Improved way to set notification popover height. Added immediately set counter to 0 on client side after "mark all as read" click. --- .../widget/online_notification.js.coffee | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee index ac7c0348e..3e4daa2a3 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee @@ -47,6 +47,7 @@ class App.OnlineNotificationWidget extends App.Controller @toggle.append('
' + count.toString() + '
') markAllAsRead: => + @counterUpdate(0) @ajax( id: 'markAllAsRead' type: 'POST' @@ -64,13 +65,19 @@ class App.OnlineNotificationWidget extends App.Controller @updateContent() # set heigth of notification popover - height = $('#app').height() - $('.js-notificationsContainer').css('height', "#{height-12}px") + heightApp = $('#navigation').height() + heightPopoverHeader = $('.js-notificationsContainer .popover-notificationsHeader').height() + heightPopoverContent = $('.js-notificationsContainer .popover-content').get(0).scrollHeight + height = heightPopoverHeader + heightPopoverContent + if height > heightApp + height = heightApp - heightPopoverHeader - 56 + $('.js-notificationsContainer .popover-content').css('height', "#{height}px") + + # set popover arrow $('.js-notificationsContainer .arrow').css('top', '20px') # close notification list on click $('.js-notificationsContainer').on('click', (e) => - #console.log('CL') @hidePopover() ) From aee4c7bf92325fa6bd98a3bc18f0696c30c30c0b Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 13:15:05 +0100 Subject: [PATCH 03/27] Fixed issue to have logo clickable on focused search box. Click on "x" == clock on logo also (because of opacity=0) and will start notification popover. We do not want this. moved logo to layer behind. --- app/assets/stylesheets/zammad.css.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 776106986..f414a0997 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -2251,6 +2251,7 @@ footer { .search.focused .logo { opacity: 0; + z-index: -1; } .search .logo { From f55b6c76c76e2ed2049247ae793b5cb6d9aba388 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 13:35:42 +0100 Subject: [PATCH 04/27] Syntax error. Workaround for missing class .justify. --- app/assets/stylesheets/zammad.css.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index c549300ae..3d27efde8 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -843,7 +843,9 @@ textarea, margin-top: 6px; margin-left: auto; @extend .horizontal; + /* @extend .justify; + */ @extend .self-start; .btn { From 47910a4ef5abb070c401b5587388e67a2c14e9ff Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Wed, 21 Jan 2015 14:31:50 +0100 Subject: [PATCH 05/27] fix justify error --- app/assets/stylesheets/zammad.css.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 3d27efde8..37a20b5e8 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -843,9 +843,6 @@ textarea, margin-top: 6px; margin-left: auto; @extend .horizontal; - /* - @extend .justify; - */ @extend .self-start; .btn { From 72fb2907f37e7e251c4a605c59f9c1d06c64cac5 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Wed, 21 Jan 2015 16:41:01 +0100 Subject: [PATCH 06/27] fix local modal dismiss on click into empty space --- app/assets/stylesheets/zammad.css.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 37a20b5e8..d6a9c5839 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -4160,11 +4160,14 @@ footer { display: block; padding-left: 40px; position: absolute; - overflow: auto; - background: hsla(210,17%,98%,.55); + bottom: auto; + min-height: 100vh; .modal-backdrop { - display: none; + position: absolute; + background: hsla(210,17%,93%,.55); + height: 100% !important; + opacity: 1; } .modal-dialog { From 36d65462b030a9bcefe9593894872bda9361fd45 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Wed, 21 Jan 2015 16:52:16 +0100 Subject: [PATCH 07/27] make modified priority animation slightly better --- app/assets/stylesheets/zammad.css.scss | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index d6a9c5839..dc8de228a 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -1335,17 +1335,18 @@ ol.tabs li { height: 6px; border-radius: 100%; background: #2c2d36; + will-change: opacity; } .modified.priority.icon:after { - -webkit-animation: fade 1s ease 2s infinite alternate; - -moz-animation: fade 1s ease 2s infinite alternate; - animation: fade 1s ease 2s infinite alternate; + -webkit-animation: fade 2s ease-in-out infinite; + -moz-animation: fade 2s ease-in-out infinite; + animation: fade 2s ease-in-out infinite; } - @-webkit-keyframes fade { from { opacity: 0 } to { opacity: 1 } } - @-moz-keyframes fade { from { opacity: 0 } to { opacity: 1 } } - @keyframes fade { from { opacity: 0 } to { opacity: 1 } } + @-webkit-keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } + @-moz-keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } + @keyframes fade { from { opacity: 1 } 50% { opacity: 0 } to { opacity: 1 } } .organization.icon { height: 13px; From 0d12baf36f1935c192d1adabada70bd9d0d151b7 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Wed, 21 Jan 2015 16:54:09 +0100 Subject: [PATCH 08/27] fix options button height in Safari --- app/assets/stylesheets/zammad.css.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index dc8de228a..b43baea13 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -284,6 +284,7 @@ span[data-tooltip]:hover:before { &.btn--action { @extend label; + height: 31px; padding: 7px 11px 5px !important; } From 8a2d2c9f0311dbdedb25012890c461f615e34821 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Wed, 21 Jan 2015 17:18:53 +0100 Subject: [PATCH 09/27] promote modified dot into own rendering plane --- app/assets/stylesheets/zammad.css.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index b43baea13..39866e9bf 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -1337,6 +1337,7 @@ ol.tabs li { border-radius: 100%; background: #2c2d36; will-change: opacity; + transform: translateZ(0); } .modified.priority.icon:after { From 0404fe31aee411cc72c1a47275c3e1b1c443a05a Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 17:20:34 +0100 Subject: [PATCH 10/27] Improved positioning of notification popover (way done in controller and css with unmodified BS popover.js). --- .../widget/online_notification.js.coffee | 24 ++++++++++--------- app/assets/stylesheets/zammad.css.scss | 11 ++++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee index 3e4daa2a3..2dd7e937e 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.js.coffee @@ -64,17 +64,19 @@ class App.OnlineNotificationWidget extends App.Controller onShow: => @updateContent() - # set heigth of notification popover - heightApp = $('#navigation').height() - heightPopoverHeader = $('.js-notificationsContainer .popover-notificationsHeader').height() - heightPopoverContent = $('.js-notificationsContainer .popover-content').get(0).scrollHeight - height = heightPopoverHeader + heightPopoverContent - if height > heightApp - height = heightApp - heightPopoverHeader - 56 - $('.js-notificationsContainer .popover-content').css('height', "#{height}px") - - # set popover arrow - $('.js-notificationsContainer .arrow').css('top', '20px') + # set height of notification popover + notificationsContainer = $('.js-notificationsContainer') + heightApp = $('#app').height() + heightPopoverSpacer = 36 + heightPopoverHeader = notificationsContainer.find('.popover-notificationsHeader').outerHeight() + heightPopoverContent = notificationsContainer.find('.popover-content').get(0).scrollHeight + heightPopoverContentNew = heightPopoverContent + if (heightPopoverHeader + heightPopoverContent + heightPopoverSpacer) > heightApp + heightPopoverContentNew = heightApp - heightPopoverHeader - heightPopoverSpacer + notificationsContainer.addClass('is-overflowing') + else + notificationsContainer.removeClass('is-overflowing') + notificationsContainer.find('.popover-content').css('height', "#{heightPopoverContentNew}px") # close notification list on click $('.js-notificationsContainer').on('click', (e) => diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 3d27efde8..ee1bee5a3 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -2710,18 +2710,23 @@ footer { } .popover--notifications { + min-height: 100px; @extend .zIndex-5; - + &.is-visible { @extend .vertical; } + .arrow { + top: 23px !important; + } + .popover-content { @extend .flex; } &.is-overflowing .popover-notificationsHeader { - box-shadow: + box-shadow: 0 1px hsla(240,4%,95%,.5), 0 2px hsla(240,4%,95%,.2); } @@ -2732,7 +2737,7 @@ footer { @extend .end; padding-bottom: 22px; margin: 21px 17px 0; - + .popover-title { @extend h1; padding: 0; From aa51a9a5dc2dae1f517a5c4e6ec3000d35fc3a58 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 21 Jan 2015 21:45:22 +0100 Subject: [PATCH 11/27] Fixed user who initiate the notification. --- .../ticket/notification/background_job.rb | 2 +- test/unit/online_notifiaction_test.rb | 142 ++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 test/unit/online_notifiaction_test.rb diff --git a/app/models/observer/ticket/notification/background_job.rb b/app/models/observer/ticket/notification/background_job.rb index cafc133fd..c285f05c1 100644 --- a/app/models/observer/ticket/notification/background_job.rb +++ b/app/models/observer/ticket/notification/background_job.rb @@ -69,7 +69,7 @@ class Observer::Ticket::Notification::BackgroundJob :object => 'Ticket', :o_id => ticket.id, :seen => false, - :created_by_id => ticket.created_by_id || 1, + :created_by_id => ticket.updated_by_id || 1, :user_id => user.id, ) diff --git a/test/unit/online_notifiaction_test.rb b/test/unit/online_notifiaction_test.rb new file mode 100644 index 000000000..53d2231e7 --- /dev/null +++ b/test/unit/online_notifiaction_test.rb @@ -0,0 +1,142 @@ +# encoding: utf-8 +require 'test_helper' + +class OnlineNotificationTest < ActiveSupport::TestCase + role = Role.lookup( :name => 'Agent' ) + group = Group.lookup( :name => 'Users' ) + agent_user1 = User.create_or_update( + :login => 'agent_online_notify1', + :firstname => 'Bob', + :lastname => 'Smith', + :email => 'agent_online_notify1@example.com', + :password => 'some_pass', + :active => true, + :role_ids => [role.id], + :group_ids => [group.id], + :updated_by_id => 1, + :created_by_id => 1 + ) + agent_user2 = User.create_or_update( + :login => 'agent_online_notify2', + :firstname => 'Bob', + :lastname => 'Smith', + :email => 'agent_online_notify2@example.com', + :password => 'some_pass', + :active => true, + :role_ids => [role.id], + :group_ids => [group.id], + :updated_by_id => 1, + :created_by_id => 1 + ) + customer_user = User.lookup( :login => 'nicole.braun@zammad.org' ) + + test 'ticket notifiaction' do + tests = [ + + # test 1 + { + :create => { + :ticket => { + :group_id => Group.lookup( :name => 'Users' ).id, + :customer_id => customer_user.id, + :owner_id => User.lookup( :login => '-' ).id, + :title => 'Unit Test 1 (äöüß)!', + :state_id => Ticket::State.lookup( :name => 'new' ).id, + :priority_id => Ticket::Priority.lookup( :name => '2 normal' ).id, + :updated_by_id => agent_user1.id, + :created_by_id => agent_user1.id, + }, + :article => { + :updated_by_id => agent_user1.id, + :created_by_id => agent_user1.id, + :type_id => Ticket::Article::Type.lookup( :name => 'phone' ).id, + :sender_id => Ticket::Article::Sender.lookup( :name => 'Customer' ).id, + :from => 'Unit Test ', + :body => 'Unit Test 123', + :internal => false + }, + }, + :update => { + :ticket => { + :title => 'Unit Test 1 (äöüß) - update!', + :state_id => Ticket::State.lookup( :name => 'open' ).id, + :priority_id => Ticket::Priority.lookup( :name => '1 low' ).id, + :updated_by_id => customer_user.id, + }, + }, + :check => [ + { + :type => 'create', + :object => 'Ticket', + :created_by_id => agent_user1.id, + }, + { + :type => 'update', + :object => 'Ticket', + :created_by_id => customer_user.id, + }, + ] + }, + ] + tickets = [] + tests.each { |test| + + ticket = Ticket.create( test[:create][:ticket] ) + test[:check][0][:o_id] = ticket.id + test[:check][1][:o_id] = ticket.id + + test[:create][:article][:ticket_id] = ticket.id + article = Ticket::Article.create( test[:create][:article] ) + + assert_equal( ticket.class.to_s, 'Ticket' ) + + # execute ticket events + Observer::Ticket::Notification.transaction + #puts Delayed::Job.all.inspect + Delayed::Worker.new.work_off + + # update ticket + if test[:update][:ticket] + ticket.update_attributes( test[:update][:ticket] ) + end + + # execute ticket events + Observer::Ticket::Notification.transaction + #puts Delayed::Job.all.inspect + Delayed::Worker.new.work_off + + # remember ticket + tickets.push ticket + + # check online notifications + notification_check( OnlineNotification.list(agent_user2, 10), test[:check] ) + } + + # delete tickets + tickets.each { |ticket| + ticket_id = ticket.id + ticket.destroy + found = Ticket.where( :id => ticket_id ).first + assert( !found, "Ticket destroyed") + } + end + + def notification_check( onine_notifications, checks ) + checks.each { |check_item| + hit = false + onine_notifications.each {|onine_notification| + if onine_notification['o_id'] == check_item[:o_id] + if onine_notification['object'] == check_item[:object] + if onine_notification['type'] == check_item[:type] + if onine_notification['created_by_id'] == check_item[:created_by_id] + hit = true + end + end + end + end + } + assert( hit, "online notification exists #{ check_item.inspect }" ) + } + end + +end \ No newline at end of file From 0e03e6d216438966a106ab48e25072860ce79f8e Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 22 Jan 2015 08:17:06 +0100 Subject: [PATCH 12/27] Fixed not needed html encoding in subject of html emails. --- .../ticket/notification/background_job.rb | 4 ++-- test/unit/ticket_notification_test.rb | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/models/observer/ticket/notification/background_job.rb b/app/models/observer/ticket/notification/background_job.rb index c285f05c1..49bc28538 100644 --- a/app/models/observer/ticket/notification/background_job.rb +++ b/app/models/observer/ticket/notification/background_job.rb @@ -292,7 +292,7 @@ State: i18n(#{ticket.state.name.text2html})

' end if user.preferences[:locale] =~ /^de/i - subject = 'Ticket aktualisiert (#{ticket.title.text2html})' + subject = 'Ticket aktualisiert (#{ticket.title})' body = '
Hallo #{recipient.firstname.text2html},

@@ -309,7 +309,7 @@ Ticket (#{ticket.title.text2html}) wurde von "#{ticket.updated_by.fullname.te
' else - subject = 'Updated Ticket (#{ticket.title.text2html})' + subject = 'Updated Ticket (#{ticket.title})' body = '
Hi #{recipient.firstname.text2html},

diff --git a/test/unit/ticket_notification_test.rb b/test/unit/ticket_notification_test.rb index 27018e0f2..18c3219e1 100644 --- a/test/unit/ticket_notification_test.rb +++ b/test/unit/ticket_notification_test.rb @@ -364,7 +364,7 @@ class TicketNotificationTest < ActiveSupport::TestCase # create ticket in group ticket1 = Ticket.create( - :title => 'some notification template test 1', + :title => 'some notification template test 1 Bobs\'s resumé', :group => Group.lookup( :name => 'Users'), :customer => customer, :state => Ticket::State.lookup( :name => 'new' ), @@ -413,6 +413,16 @@ class TicketNotificationTest < ActiveSupport::TestCase assert_match( /updated/i, template[:subject] ) # en notification + subject = NotificationFactory.build( + :locale => agent2.preferences[:locale], + :string => template[:subject], + :objects => { + :ticket => ticket1, + :article => article, + :recipient => agent2, + } + ) + assert_match( /Bobs's resumé/, subject ) body = NotificationFactory.build( :locale => agent2.preferences[:locale], :string => template[:body], @@ -430,6 +440,8 @@ class TicketNotificationTest < ActiveSupport::TestCase # de template template = bg.template_update(agent1, ticket1, article, human_changes) assert( template[:subject] ) + assert( template[:subject] ) + assert_match( /Bobs's resumé/, template[:subject] ) assert( template[:body] ) assert_match( /Priority/, template[:body] ) assert_match( /1 low/, template[:body] ) @@ -437,6 +449,16 @@ class TicketNotificationTest < ActiveSupport::TestCase assert_match( /aktualis/, template[:subject] ) # de notification + subject = NotificationFactory.build( + :locale => agent1.preferences[:locale], + :string => template[:subject], + :objects => { + :ticket => ticket1, + :article => article, + :recipient => agent2, + } + ) + assert_match( /Bobs's resumé/, subject ) body = NotificationFactory.build( :locale => agent1.preferences[:locale], :string => template[:body], From 218c2474780f642fbfb9decd3fcb700368bc8fd1 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 22 Jan 2015 11:47:11 +0100 Subject: [PATCH 13/27] Fixed form handler with noFieldset param. Browser tests on next commit. --- .../controllers/_application_controller_form.js.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee index 15d66648e..35397afa9 100644 --- a/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller_form.js.coffee @@ -35,7 +35,11 @@ class App.ControllerForm extends App.Controller formGen: -> App.Log.debug 'ControllerForm', 'formGen', @model.configure_attributes - fieldset = $('
') + # check if own fieldset should be generated + if @noFieldset + fieldset = @el + else + fieldset = $('
') # collect form attributes @attributes = [] @@ -81,9 +85,6 @@ class App.ControllerForm extends App.Controller item = @formGenItem( attribute, className, fieldset, attribute_count ) item.appendTo(fieldset) - if @noFieldset - fieldset = fieldset.children() - if @fullForm if !@formClass @formClass = '' From ec20859ba0748f1eb0655ef0e26e4a48676c7de7 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Thu, 22 Jan 2015 15:50:33 +0100 Subject: [PATCH 14/27] add cloud, package and list task-icons and clean up --- .../app/views/task_widget_tasks.jst.eco | 4 +- app/assets/stylesheets/zammad.css.scss | 40 +++++++-- public/assets/images/sprite.svg | 88 ++++++++++++------- 3 files changed, 90 insertions(+), 42 deletions(-) diff --git a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco index 4eb28dadf..127d3cfe3 100644 --- a/app/assets/javascripts/app/views/task_widget_tasks.jst.eco +++ b/app/assets/javascripts/app/views/task_widget_tasks.jst.eco @@ -1,7 +1,7 @@ <% for item in @item_list: %> - +
-
+
<%= item.data.head %>
diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index 61fa1e12c..f101f0110 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -1357,7 +1357,6 @@ ol.tabs li { } .icon-switch:hover .organization.icon, - .task.active .organization.icon, .white.organization.icon { background-position: 0 -132px; } @@ -1369,7 +1368,6 @@ ol.tabs li { } .icon-switch:hover .user.icon, - .task.active .user.icon, .white.user.icon { background-position: -15px -132px; } @@ -1381,7 +1379,6 @@ ol.tabs li { } .icon-switch:hover .note.icon, - .task.active .note.icon, .white.note.icon { background-position: -30px -132px; } @@ -1393,7 +1390,6 @@ ol.tabs li { } .icon-switch:hover .pen.icon, - .task.active .pen.icon, .white.pen.icon { background-position: -45px -132px; } @@ -1404,7 +1400,6 @@ ol.tabs li { background-position: -60px -118px; } .icon-switch:hover .important.icon, - .task.active .important.icon, .white.important.icon { background-position: -60px -132px; } @@ -1415,7 +1410,6 @@ ol.tabs li { background-position: -75px -118px; } .icon-switch:hover .tools.icon, - .task.active .tools.icon, .white.tools.icon { background-position: -75px -132px; } @@ -1426,7 +1420,6 @@ ol.tabs li { background-position: -90px -118px; } .icon-switch:hover .clock.icon, - .task.active .clock.icon, .white.clock.icon { background-position: -90px -132px; } @@ -1434,10 +1427,37 @@ ol.tabs li { .team.icon { height: 13px; width: 20px; - background-position: -105px -118px; + background-position: -104px -118px; } .white.team.icon { - background-position: -105px -132px; + background-position: -104px -132px; + } + + .cloud.icon { + height: 12px; + width: 15px; + background-position: -125px -119px; + } + .white.cloud.icon { + background-position: -125px -133px; + } + + .package.icon { + height: 16px; + width: 15px; + background-position: -141px -112px; + } + .white.package.icon { + background-position: -141px -129px; + } + + .list.icon { + height: 14px; + width: 15px; + background-position: -157px -116px; + } + .white.list.icon { + background-position: -157px -131px; } .channel.icon { @@ -2076,6 +2096,8 @@ footer { padding: 10px 15px 7px 0; position: relative; @extend .u-clickable; + @extend .horizontal; + @extend .center; } .tasks-navigation .task { diff --git a/public/assets/images/sprite.svg b/public/assets/images/sprite.svg index dae98acea..1afd254df 100644 --- a/public/assets/images/sprite.svg +++ b/public/assets/images/sprite.svg @@ -1,26 +1,46 @@ - + - Slice 2 + Slice 1 Created with Sketch. + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + - + - + @@ -28,7 +48,7 @@ - + @@ -40,22 +60,22 @@ - + - + - - - + + + @@ -126,39 +146,45 @@ - - - + + + - - + + - + + + + + + + - - - + + + - - - + + + - - + + @@ -170,7 +196,7 @@ - + From f90915a93a9ac1057824e3b1acc0b42c7bc0e2c3 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Thu, 22 Jan 2015 19:13:38 +0100 Subject: [PATCH 15/27] attempt to make the error message look nicer if this doesn't work because of too long detail texts I'd first try to make them more compat. otherwise we could have toggle-able details like chrome http://cl.ly/image/1u1e1g1f1L2o/Screen%20Shot%202015-01-22%20at%2019.12.58.png --- .../javascripts/app/views/generic/error/generic.jst.eco | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/javascripts/app/views/generic/error/generic.jst.eco b/app/assets/javascripts/app/views/generic/error/generic.jst.eco index 746df4876..c07a4dc06 100644 --- a/app/assets/javascripts/app/views/generic/error/generic.jst.eco +++ b/app/assets/javascripts/app/views/generic/error/generic.jst.eco @@ -1,5 +1,3 @@
-

<%- @T('Opps.. I\'m sorry, something went wrong!' ) %>

-

<%- @T('Status Code') %>: <%= @status %>

-

<%= @detail %>

+

<%- @T('Status Code') %>: <%= @status %>. <%= @detail %>

\ No newline at end of file From 5132248142f62272b2ce772b9d6178fd703510f1 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 23 Jan 2015 10:23:04 +0100 Subject: [PATCH 16/27] Added layout ref for error. --- .../javascripts/app/controllers/layout_ref.js.coffee | 12 ++++++++++++ .../javascripts/app/views/layout_ref/error.jst.eco | 3 +++ .../javascripts/app/views/layout_ref/index.jst.eco | 1 + 3 files changed, 16 insertions(+) create mode 100644 app/assets/javascripts/app/views/layout_ref/error.jst.eco diff --git a/app/assets/javascripts/app/controllers/layout_ref.js.coffee b/app/assets/javascripts/app/controllers/layout_ref.js.coffee index 9ff018978..1bc2bac8e 100644 --- a/app/assets/javascripts/app/controllers/layout_ref.js.coffee +++ b/app/assets/javascripts/app/controllers/layout_ref.js.coffee @@ -777,4 +777,16 @@ class insufficientRightsRef extends App.ControllerContent App.Config.set( 'layout_ref/insufficient_rights', insufficientRightsRef, 'Routes' ) + +class errorRef extends App.ControllerContent + + constructor: -> + super + @render() + + render: -> + @html App.view('layout_ref/error')() + +App.Config.set( 'layout_ref/error', errorRef, 'Routes' ) + App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' ) \ No newline at end of file diff --git a/app/assets/javascripts/app/views/layout_ref/error.jst.eco b/app/assets/javascripts/app/views/layout_ref/error.jst.eco new file mode 100644 index 000000000..6db816262 --- /dev/null +++ b/app/assets/javascripts/app/views/layout_ref/error.jst.eco @@ -0,0 +1,3 @@ +
+

<%- @T('Status Code') %>: 1234. may be not internet connection...

+
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/layout_ref/index.jst.eco b/app/assets/javascripts/app/views/layout_ref/index.jst.eco index 6305abbf3..921d98bb4 100644 --- a/app/assets/javascripts/app/views/layout_ref/index.jst.eco +++ b/app/assets/javascripts/app/views/layout_ref/index.jst.eco @@ -22,6 +22,7 @@
  • Local Modal
  • Loading Placeholder
  • Insufficient Rights Warning
  • +
  • Error
  • \ No newline at end of file From 37f63cbac335e5730cd1a27086d5ea2e62fa1e90 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 23 Jan 2015 10:46:17 +0100 Subject: [PATCH 17/27] Centralised ticket form changes observer. --- .../_application_controller.js.coffee | 31 +++++++++++++++++-- .../controllers/agent_ticket_create.js.coffee | 29 ++--------------- .../customer_ticket_create.js.coffee | 27 ++-------------- .../app/controllers/ticket_zoom.js.coffee | 25 +-------------- app/controllers/tickets_controller.rb | 6 ++-- 5 files changed, 38 insertions(+), 80 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 078e3dd82..5db49b072 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -467,6 +467,31 @@ class App.Controller extends Spine.Controller ws_send: (data) -> App.Event.trigger( 'ws:send', JSON.stringify(data) ) + # central method, is getting called on every ticket form change + ticketFormChanges: (params, attribute, attributes, classname, form, ui) => + if @form_meta.dependencies && @form_meta.dependencies[attribute.name] + dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] + if !dependency + dependency = @form_meta.dependencies[attribute.name][ params[attribute.name] ] + if dependency + for fieldNameToChange of dependency + filter = [] + if dependency[fieldNameToChange] + filter = dependency[fieldNameToChange] + + # find element to replace + for item in attributes + if item.name is fieldNameToChange + item['filter'] = {} + item['filter'][ fieldNameToChange ] = filter + item.default = params[item.name] + #if !item.default + # delete item['default'] + newElement = ui.formGenItem( item, classname, form ) + + # replace new option list + form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) + class App.ControllerPermanent extends App.Controller constructor: -> super @@ -485,10 +510,10 @@ class App.ControllerModal extends App.Controller '.modal-body': 'body' events: - 'submit form': 'onSubmit' - 'click .js-submit:not(.is-disabled)': 'onSubmit' + 'submit form': 'onSubmit' + 'click .js-submit: not(.is-disabled)': 'onSubmit' 'click .js-cancel': 'hide' - 'click .js-close': 'hide' + 'click .js-close': 'hide' className: 'modal fade' 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 aa7632475..387657240 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee @@ -205,29 +205,6 @@ class App.TicketCreate extends App.Controller form_id: @form_id ) - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # 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 @@ -274,7 +251,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter @@ -297,7 +274,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter @@ -312,7 +289,7 @@ class App.TicketCreate extends App.Controller events: 'change [name=customer_id]': @localUserInfo handlers: [ - formChanges, + @ticketFormChanges, signatureChanges, ] filter: @form_meta.filter diff --git a/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee b/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee index 62b295826..fbcf2bd60 100644 --- a/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee +++ b/app/assets/javascripts/app/controllers/customer_ticket_create.js.coffee @@ -64,29 +64,6 @@ class Index extends App.ControllerContent groupFilter = [groupFilter] @form_meta.filter.group_id = groupFilter - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # replace new option list - form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) - @html App.view('customer_ticket_create')( head: 'New Ticket' ) new App.ControllerForm( @@ -95,7 +72,7 @@ class Index extends App.ControllerContent model: App.Ticket screen: 'create_top' handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter autofocus: true @@ -115,7 +92,7 @@ class Index extends App.ControllerContent model: App.Ticket screen: 'create_middle' handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter params: defaults diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 501225356..0967cb093 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -267,29 +267,6 @@ class App.TicketZoom extends App.Controller console.log('SHOW', ticket.id) el.find('.edit').html('') - formChanges = (params, attribute, attributes, classname, form, ui) => - if @form_meta.dependencies && @form_meta.dependencies[attribute.name] - dependency = @form_meta.dependencies[attribute.name][ parseInt(params[attribute.name]) ] - if dependency - - for fieldNameToChange of dependency - filter = [] - if dependency[fieldNameToChange] - filter = dependency[fieldNameToChange] - - # find element to replace - for item in attributes - if item.name is fieldNameToChange - item['filter'] = {} - item['filter'][ fieldNameToChange ] = filter - item.default = params[item.name] - #if !item.default - # delete item['default'] - newElement = ui.formGenItem( item, classname, form ) - - # replace new option list - form.find('[name="' + fieldNameToChange + '"]').closest('.form-group').replaceWith( newElement ) - defaults = ticket.attributes() task_state = @taskGet('ticket') modelDiff = @getDiff( defaults, task_state ) @@ -305,7 +282,7 @@ class App.TicketZoom extends App.Controller screen: 'edit' params: App.Ticket.find(ticket.id) handlers: [ - formChanges + @ticketFormChanges ] filter: @form_meta.filter params: defaults diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index b8fc5b27b..10e134267 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -279,8 +279,10 @@ class TicketsController < ApplicationController :assets => assets, :links => link_list, :tags => tags, - :form_meta => attributes_to_change, - :edit_form => attributes_to_change, + :form_meta => { + :filter => attributes_to_change[:filter], + :dependencies => attributes_to_change[:dependencies], + } } end From b49d0878176f44d7f60b9e7be488bf90a27c53ca Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 23 Jan 2015 11:39:16 +0100 Subject: [PATCH 18/27] Fixed browser test (syntax error in selector). --- .../app/controllers/_application_controller.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/app/controllers/_application_controller.js.coffee b/app/assets/javascripts/app/controllers/_application_controller.js.coffee index 5db49b072..1820ef8a7 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.js.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller.js.coffee @@ -510,10 +510,10 @@ class App.ControllerModal extends App.Controller '.modal-body': 'body' events: - 'submit form': 'onSubmit' - 'click .js-submit: not(.is-disabled)': 'onSubmit' - 'click .js-cancel': 'hide' - 'click .js-close': 'hide' + 'submit form': 'onSubmit' + 'click .js-submit:not(.is-disabled)': 'onSubmit' + 'click .js-cancel': 'hide' + 'click .js-close': 'hide' className: 'modal fade' From fbba548e0b623b79dcc7b9d4814d49bf16681188 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 23 Jan 2015 11:44:12 +0100 Subject: [PATCH 19/27] Removed wrong test. --- test/unit/ticket_notification_test.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/ticket_notification_test.rb b/test/unit/ticket_notification_test.rb index 18c3219e1..ad3134599 100644 --- a/test/unit/ticket_notification_test.rb +++ b/test/unit/ticket_notification_test.rb @@ -440,8 +440,6 @@ class TicketNotificationTest < ActiveSupport::TestCase # de template template = bg.template_update(agent1, ticket1, article, human_changes) assert( template[:subject] ) - assert( template[:subject] ) - assert_match( /Bobs's resumé/, template[:subject] ) assert( template[:body] ) assert_match( /Priority/, template[:body] ) assert_match( /1 low/, template[:body] ) From 30042e451d0866108b63fe596183d690f755d34c Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Fri, 23 Jan 2015 14:36:36 +0100 Subject: [PATCH 20/27] hide custom select arrow only in firefox < 35 with the help of some javascript code martin will write :) --- app/assets/stylesheets/zammad.css.scss | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/zammad.css.scss b/app/assets/stylesheets/zammad.css.scss index f101f0110..9d18ab2a7 100644 --- a/app/assets/stylesheets/zammad.css.scss +++ b/app/assets/stylesheets/zammad.css.scss @@ -733,16 +733,17 @@ textarea, Firefox only hack ----------------- - Firefox doesn't allow us to hide the dropdown arrow - but we want to replace it with our own icon. - So we have to hide our own icon in Firefox. + Firefox below version 35 doesn't allow us to + hide the dropdown arrow but we want to replace + it with our own icon. So we have to hide our own + icon in Firefox versions under 35. + + The class is set via Javascript */ - @-moz-document url-prefix() { - .form-control + .select-arrow { - display: none; - } + html.ff-lt-35 .form-control + .select-arrow { + display: none; } select::-ms-expand { From a18157ba4d7ec3822d8f92aa311292479a405e8b Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 23 Jan 2015 23:24:03 +0100 Subject: [PATCH 21/27] Added browser tests for signature. --- .../app/controllers/ticket_zoom.js.coffee | 29 +- .../views/ticket_zoom/article_view.jst.eco | 2 +- .../agent_ticket_actions_level5_test.rb | 362 ++++++++++++++++++ test/browser_test_helper.rb | 85 +++- 4 files changed, 463 insertions(+), 15 deletions(-) create mode 100644 test/browser/agent_ticket_actions_level5_test.rb diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index 0967cb093..f02fa22ae 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -1479,10 +1479,10 @@ class ArticleView extends App.Controller e.preventDefault() # get reference article - article_id = $(e.target).parents('[data-id]').data('id') - article = App.TicketArticle.fullLocal( article_id ) - type = App.TicketArticleType.find( article.type_id ) - customer = App.User.find( article.created_by_id ) + article_id = $(e.target).parents('[data-id]').data('id') + article = App.TicketArticle.fullLocal( article_id ) + type = App.TicketArticleType.find( article.type_id ) + customer = App.User.find( article.created_by_id ) @ui.el.find('.article-add').ScrollTo() @@ -1511,7 +1511,8 @@ class ArticleView extends App.Controller to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid articleNew.to = to - else if type.name is 'email' + else if type.name is 'email' || type.name is 'phone' || type.name is 'web' + if article.sender.name is 'Agent' articleNew.to = article.to else @@ -1655,22 +1656,28 @@ class Article extends App.Controller ] #if @article.type.name is 'note' # actions.push [] - if @article.type.name is 'email' + if @article.type.name is 'email' || @article.type.name is 'phone' || @article.type.name is 'web' actions.push { name: 'reply' type: 'reply' href: '#' } recipients = [] - if @article.to - localRecipients = emailAddresses.parseAddressList(@article.to) - if localRecipients - recipients = recipients.concat localRecipients + if @article.sender.name is 'Agent' + if @article.to + localRecipients = emailAddresses.parseAddressList(@article.to) + if localRecipients + recipients = recipients.concat localRecipients + else + if @article.from + localRecipients = emailAddresses.parseAddressList(@article.from) + if localRecipients + recipients = recipients.concat localRecipients if @article.cc localRecipients = emailAddresses.parseAddressList(@article.cc) if localRecipients recipients = recipients.concat localRecipients - if recipients.length > 0 + if recipients.length > 1 actions.push { name: 'reply all' type: 'replyAll' diff --git a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco index bbd619500..dd0660010 100644 --- a/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco +++ b/app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco @@ -86,7 +86,7 @@ <% if article.actions: %>
    -
    +
    <% for action in article.actions: %> <%- @T( action.name ) %> diff --git a/test/browser/agent_ticket_actions_level5_test.rb b/test/browser/agent_ticket_actions_level5_test.rb new file mode 100644 index 000000000..0fa2cb90b --- /dev/null +++ b/test/browser/agent_ticket_actions_level5_test.rb @@ -0,0 +1,362 @@ +# encoding: utf-8 +require 'browser_test_helper' + +class AgentTicketActionLevel5Test < TestCase + def test_agent_signature_check + suffix = rand(99999999999999999).to_s + signature_name1 = 'sig name 1 äöüß ' + suffix + signature_body1 = "--\nsig body 1 äöüß " + suffix + signature_name2 = 'sig name 2 äöüß ' + suffix + signature_body2 = "--\nsig body 2 äöüß " + suffix + group_name1 = "group name 1 " + suffix + group_name2 = "group name 2 " + suffix + group_name3 = "group name 3 " + suffix + + tests = [ + { + :name => 'create groups and signatures', + :action => [ + + { + :execute => 'close_all_tasks', + }, + + # create signatures + { + :execute => 'create_signature', + :name => signature_name1, + :body => signature_body1, + }, + { + :execute => 'create_signature', + :name => signature_name2, + :body => signature_body2, + }, + + # create groups + { + :execute => 'create_group', + :name => group_name1, + :signature => signature_name1, + :member => [ + 'master@example.com' + ], + }, + { + :execute => 'create_group', + :name => group_name2, + :signature => signature_name2, + :member => [ + 'master@example.com' + ], + }, + { + :execute => 'create_group', + :name => group_name3, + :member => [ + 'master@example.com' + ], + }, + ], + }, + { + :name => 'check signature in new ticket', + :action => [ + + # reload instances to get new group permissions + { + :execute => 'reload', + }, + + { + :execute => 'create_ticket', + :group => 'Users', + :subject => 'some subject 4 - 123äöü', + :body => 'some body 4 - 123äöü', + :do_not_submit => true, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # select group + { + :execute => 'select', + :css => '.active [name="group_id"]', + :value => group_name1, + }, + + # select group + { + :execute => 'select', + :css => '.active [name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select create channel + { + :execute => 'click', + :css => '.active [data-type="email-out"]', + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name2, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => true, + }, + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name3, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + + # select group + { + :execute => 'select', + :css => '.active select[name="group_id"]', + :value => group_name1, + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # select create channel + { + :execute => 'click', + :css => '.active [data-type="phone-out"]', + }, + + # check content + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => 'some body 4', + :no_quote => true, + :match_result => true, + }, + + # check signature + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + ], + }, + { + :name => 'check signature in zoom ticket', + :action => [ + + { + :execute => 'create_ticket', + :group => group_name1, + :subject => 'some subject 5 - 123äöü', + :body => 'some body 5 - 123äöü', + }, + { + :execute => 'wait', + :value => 3, + }, + + # execute reply + { + :execute => 'click', + :css => '.active [data-type="reply"]', + }, + + # check if signature exists + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => true, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + # discard changes + { + :execute => 'click', + :css => '.active .js-reset', + }, + { + :execute => 'wait', + :value => 3, + }, + + # check if signature exists + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body1, + :no_quote => true, + :match_result => false, + }, + { + :execute => 'match', + :css => '.active [data-name="body"]', + :value => signature_body2, + :no_quote => true, + :match_result => false, + }, + + ], + }, + ] + browser_signle_test_with_login(tests, { :username => 'master@example.com' }) + end +end \ No newline at end of file diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 271a741f9..536a02558 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -30,8 +30,12 @@ class TestCase < Test::Unit::TestCase end caps = Selenium::WebDriver::Remote::Capabilities.send( browser ) - caps.platform = ENV['BROWSER_OS'] || 'Windows 2008' - caps.version = ENV['BROWSER_VERSION'] || '8' + if ENV['BROWSER_OS'] + caps.platform = ENV['BROWSER_OS'] + end + if ENV['BROWSER_VERSION'] + caps.version = ENV['BROWSER_VERSION'] + end local_browser = Selenium::WebDriver.for( :remote, :url => ENV['REMOTE_URL'], @@ -291,7 +295,6 @@ class TestCase < Test::Unit::TestCase assert( false, "(#{test[:name]} / #{test[:area]}) still exsists" ) return elsif action[:execute] == 'create_user' - instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click instance.find_elements( { :css => 'a[href="#manage/users"]' } )[0].click sleep 2 @@ -329,6 +332,82 @@ class TestCase < Test::Unit::TestCase assert( true, "(#{test[:name]}) user creation failed" ) return + elsif action[:execute] == 'create_signature' + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#channels/email"]' } )[0].click + instance.find_elements( { :css => 'a[href="#c-signature"]' } )[0].click + sleep 8 + instance.find_elements( { :css => '#content #c-signature a[data-type="new"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '.modal input[name=name]' } )[0] + element.clear + element.send_keys( action[:name] ) + element = instance.find_elements( { :css => '.modal textarea[name=body]' } )[0] + element.clear + element.send_keys( action[:body] ) + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + (1..12).each {|loop| + element = instance.find_elements( { :css => 'body' } )[0] + text = element.text + if text =~ /#{Regexp.quote(action[:name])}/ + assert( true, "(#{test[:name]}) signature created" ) + return + end + sleep 1 + } + assert( true, "(#{test[:name]}) signature creation failed" ) + return + + elsif action[:execute] == 'create_group' + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#manage/groups"]' } )[0].click + sleep 2 + instance.find_elements( { :css => 'a[data-type="new"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '.modal input[name=name]' } )[0] + element.clear + element.send_keys( action[:name] ) + element = instance.find_elements( { :css => '.modal select[name="email_address_id"]' } )[0] + dropdown = Selenium::WebDriver::Support::Select.new(element) + dropdown.select_by( :index, 1 ) + #dropdown.select_by( :text, action[:group]) + if action[:signature] + element = instance.find_elements( { :css => '.modal select[name="signature_id"]' } )[0] + dropdown = Selenium::WebDriver::Support::Select.new(element) + dropdown.select_by( :text, action[:signature]) + end + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + (1..12).each {|loop| + element = instance.find_elements( { :css => 'body' } )[0] + text = element.text + if text =~ /#{Regexp.quote(action[:name])}/ + assert( true, "(#{test[:name]}) group created" ) + + # add member + if action[:member] + action[:member].each {|login| + instance.find_elements( { :css => 'a[href="#manage"]' } )[0].click + instance.find_elements( { :css => 'a[href="#manage/users"]' } )[0].click + sleep 2 + element = instance.find_elements( { :css => '#content [name="search"]' } )[0] + element.clear + element.send_keys( login ) + sleep 2 + #instance.find_elements( { :css => '#content table [data-id]' } )[0].click + instance.execute_script( '$("#content table [data-id] td").first().click()' ) + sleep 2 + #instance.find_elements( { :css => 'label:contains(" ' + action[:name] + '")' } )[0].click + instance.execute_script( '$(\'label:contains(" ' + action[:name] + '")\').first().click()' ) + instance.find_elements( { :css => '.modal button.js-submit' } )[0].click + } + end + return + end + sleep 1 + } + assert( true, "(#{test[:name]}) group creation failed" ) + return + elsif action[:execute] == 'verify_task_attributes' if action[:title] text = instance.find_elements( { :css => '.tasks .active' } )[0].text.strip From 07824ab6333dc2b434895ad4418480c34162c32c Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 24 Jan 2015 10:05:02 +0100 Subject: [PATCH 22/27] Improved error handling. --- test/browser_test_helper.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 536a02558..156ad33ac 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -261,18 +261,22 @@ class TestCase < Test::Unit::TestCase if action[:timeout] timeout = action[:timeout] end - loops = (timeout / 2).to_i + loops = (timeout).to_i text = '' (1..loops).each { |loop| element = instance.find_elements( { :css => action[:area] } )[0] if element #&& element.displayed? - text = element.text - if text =~ /#{action[:value]}/i - assert( true, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) - return + begin + text = element.text + if text =~ /#{action[:value]}/i + assert( true, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) + return + end + rescue + # just try again end end - sleep 2 + sleep 1 } assert( false, "(#{test[:name]}) '#{action[:value]}' found in '#{text}'" ) return From 84c2452f8106c50ab01051ccdae2fd2da48200c9 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 24 Jan 2015 11:15:52 +0100 Subject: [PATCH 23/27] Added check if init owner selection of new ticket screen is empty. --- test/browser_test_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/browser_test_helper.rb b/test/browser_test_helper.rb index 156ad33ac..4be4ad056 100644 --- a/test/browser_test_helper.rb +++ b/test/browser_test_helper.rb @@ -472,6 +472,11 @@ class TestCase < Test::Unit::TestCase return end sleep 2 + + # check count of agents, should be only 1 / - selection on init screen + count = instance.find_elements( { :css => '.active .newTicket select[name="owner_id"] option' } ).count + assert_equal( 1, count, 'check if owner selection is empty per default' ) + if action[:group] element = instance.find_elements( { :css => '.active .newTicket select[name="group_id"]' } )[0] dropdown = Selenium::WebDriver::Support::Select.new(element) From afc241e72a9ae86aec3c8646c1d05b135d6db203 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 24 Jan 2015 11:23:00 +0100 Subject: [PATCH 24/27] Add email address if not existing to address line. --- app/assets/javascripts/app/controllers/ticket_zoom.js.coffee | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee index f02fa22ae..2a6f337b6 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.js.coffee @@ -1518,6 +1518,11 @@ class ArticleView extends App.Controller else articleNew.to = article.from + # if sender is customer but in article.from is no email, try to get + # customers email via customer user + if articleNew.to && !articleNew.to.match(/@/) + articleNew.to = article.created_by.email + # filter for uniq recipients recipientAddresses = {} recipient = emailAddresses.parseAddressList(articleNew.to) From c8737893851f62786e01a15b83db59a6feffee91 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 24 Jan 2015 13:51:04 +0100 Subject: [PATCH 25/27] Fixed check if init owner selection of new ticket screen is empty. --- app/models/ticket/screen_options.rb | 34 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/app/models/ticket/screen_options.rb b/app/models/ticket/screen_options.rb index fb435ebab..008cf8289 100644 --- a/app/models/ticket/screen_options.rb +++ b/app/models/ticket/screen_options.rb @@ -32,12 +32,12 @@ list attributes returns result = { - :type_id => type_ids, - :state_id => state_ids, - :priority_id => priority_ids, - :owner_id => owner_ids, - :group_id => group_ids, - :group_id__owner_id => groups_users, + :type_id => type_ids, + :state_id => state_ids, + :priority_id => priority_ids, + :owner_id => owner_ids, + :group_id => group_ids, + :group_id__owner_id => groups_users, } =end @@ -103,7 +103,7 @@ returns agents[ user.id ] = 1 } - dependencies = { :group_id => { '' => [] } } + dependencies = { :group_id => { '' => { :owner_id => [] } } } Group.where( :active => true ).each { |group| assets = group.assets(assets) dependencies[:group_id][group.id] = { :owner_id => [] } @@ -115,9 +115,9 @@ returns } return { - :assets => assets, - :filter => filter, - :dependencies => dependencies, + :assets => assets, + :filter => filter, + :dependencies => dependencies, } end @@ -126,8 +126,8 @@ returns list tickets by customer groupd in state categroie open and closed result = Ticket::ScreenOptions.list_by_customer( - :customer_id => 123, - :limit => 15, # optional, default 15 + :customer_id => 123, + :limit => 15, # optional, default 15 ) returns @@ -147,13 +147,13 @@ returns # get tickets tickets_open = Ticket.where( - :customer_id => data[:customer_id], - :state_id => state_list_open + :customer_id => data[:customer_id], + :state_id => state_list_open ).limit( data[:limit] || 15 ).order('created_at DESC') tickets_closed = Ticket.where( - :customer_id => data[:customer_id], - :state_id => state_list_closed + :customer_id => data[:customer_id], + :state_id => state_list_closed ).limit( data[:limit] || 15 ).order('created_at DESC') return { @@ -162,4 +162,4 @@ returns } end -end +end \ No newline at end of file From 7533bdeea5d42cd8206359814a4ab3364677e89e Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Sat, 24 Jan 2015 14:08:54 +0100 Subject: [PATCH 26/27] For firefox lower 35 we need to set a class to hide own dropdown images whole file can be removed after dropping firefox 34 and lower support. --- .../app/controllers/widget/ff_lt_35.js.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee diff --git a/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee b/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee new file mode 100644 index 000000000..31260a917 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/ff_lt_35.js.coffee @@ -0,0 +1,10 @@ +class FFlt35 + constructor: -> + data = App.Browser.detection() + if data.browser is 'Firefox' && data.version && data.version < 35 + + # for firefox lower 35 we need to set a class to hide own dropdown images + # whole file can be removed after dropping firefox 34 and lower support + $('html').addClass('ff-lt-35') + +App.Config.set( 'aaa_ff-lt-35', FFlt35, 'Widgets' ) \ No newline at end of file From d09a15e50b6d8b4e1e47165ddb0e9d0c6ff66eb1 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Sat, 24 Jan 2015 16:00:26 +0100 Subject: [PATCH 27/27] new modernizr build without css classes --- .../app/lib/base/modernizr.custom.63196.js | 828 ------------------ .../app/lib/base/modernizr.custom.92138.js | 4 + 2 files changed, 4 insertions(+), 828 deletions(-) delete mode 100644 app/assets/javascripts/app/lib/base/modernizr.custom.63196.js create mode 100644 app/assets/javascripts/app/lib/base/modernizr.custom.92138.js diff --git a/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js b/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js deleted file mode 100644 index 1d3d5aed3..000000000 --- a/app/assets/javascripts/app/lib/base/modernizr.custom.63196.js +++ /dev/null @@ -1,828 +0,0 @@ -/* Modernizr 2.8.3 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load - */ -; - - - -window.Modernizr = (function( window, document, undefined ) { - - var version = '2.8.3', - - Modernizr = {}, - - enableClasses = true, - - docElement = document.documentElement, - - mod = 'modernizr', - modElem = document.createElement(mod), - mStyle = modElem.style, - - inputElem = document.createElement('input') , - - smile = ':)', - - toString = {}.toString, - - prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), - - - - omPrefixes = 'Webkit Moz O ms', - - cssomPrefixes = omPrefixes.split(' '), - - domPrefixes = omPrefixes.toLowerCase().split(' '), - - ns = {'svg': 'http://www.w3.org/2000/svg'}, - - tests = {}, - inputs = {}, - attrs = {}, - - classes = [], - - slice = classes.slice, - - featureName, - - - injectElementWithStyles = function( rule, callback, nodes, testnames ) { - - var style, ret, node, docOverflow, - div = document.createElement('div'), - body = document.body, - fakeBody = body || document.createElement('body'); - - if ( parseInt(nodes, 10) ) { - while ( nodes-- ) { - node = document.createElement('div'); - node.id = testnames ? testnames[nodes] : mod + (nodes + 1); - div.appendChild(node); - } - } - - style = ['­',''].join(''); - div.id = mod; - (body ? div : fakeBody).innerHTML += style; - fakeBody.appendChild(div); - if ( !body ) { - fakeBody.style.background = ''; - fakeBody.style.overflow = 'hidden'; - docOverflow = docElement.style.overflow; - docElement.style.overflow = 'hidden'; - docElement.appendChild(fakeBody); - } - - ret = callback(div, rule); - if ( !body ) { - fakeBody.parentNode.removeChild(fakeBody); - docElement.style.overflow = docOverflow; - } else { - div.parentNode.removeChild(div); - } - - return !!ret; - - }, - - - - isEventSupported = (function() { - - var TAGNAMES = { - 'select': 'input', 'change': 'input', - 'submit': 'form', 'reset': 'form', - 'error': 'img', 'load': 'img', 'abort': 'img' - }; - - function isEventSupported( eventName, element ) { - - element = element || document.createElement(TAGNAMES[eventName] || 'div'); - eventName = 'on' + eventName; - - var isSupported = eventName in element; - - if ( !isSupported ) { - if ( !element.setAttribute ) { - element = document.createElement('div'); - } - if ( element.setAttribute && element.removeAttribute ) { - element.setAttribute(eventName, ''); - isSupported = is(element[eventName], 'function'); - - if ( !is(element[eventName], 'undefined') ) { - element[eventName] = undefined; - } - element.removeAttribute(eventName); - } - } - - element = null; - return isSupported; - } - return isEventSupported; - })(), - - - _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; - - if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { - hasOwnProp = function (object, property) { - return _hasOwnProperty.call(object, property); - }; - } - else { - hasOwnProp = function (object, property) { - return ((property in object) && is(object.constructor.prototype[property], 'undefined')); - }; - } - - - if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { - - var target = this; - - if (typeof target != "function") { - throw new TypeError(); - } - - var args = slice.call(arguments, 1), - bound = function () { - - if (this instanceof bound) { - - var F = function(){}; - F.prototype = target.prototype; - var self = new F(); - - var result = target.apply( - self, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return self; - - } else { - - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - - } - - }; - - return bound; - }; - } - - function setCss( str ) { - mStyle.cssText = str; - } - - function setCssAll( str1, str2 ) { - return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); - } - - function is( obj, type ) { - return typeof obj === type; - } - - function contains( str, substr ) { - return !!~('' + str).indexOf(substr); - } - - function testProps( props, prefixed ) { - for ( var i in props ) { - var prop = props[i]; - if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { - return prefixed == 'pfx' ? prop : true; - } - } - return false; - } - - function testDOMProps( props, obj, elem ) { - for ( var i in props ) { - var item = obj[props[i]]; - if ( item !== undefined) { - - if (elem === false) return props[i]; - - if (is(item, 'function')){ - return item.bind(elem || obj); - } - - return item; - } - } - return false; - } - - function testPropsAll( prop, prefixed, elem ) { - - var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), - props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); - - if(is(prefixed, "string") || is(prefixed, "undefined")) { - return testProps(props, prefixed); - - } else { - props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); - return testDOMProps(props, prefixed, elem); - } - } tests['flexbox'] = function() { - return testPropsAll('flexWrap'); - }; tests['canvas'] = function() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); - }; - - tests['canvastext'] = function() { - return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); - }; - - - - tests['webgl'] = function() { - return !!window.WebGLRenderingContext; - }; - - - tests['touch'] = function() { - var bool; - - if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { - bool = true; - } else { - injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { - bool = node.offsetTop === 9; - }); - } - - return bool; - }; - - - - tests['geolocation'] = function() { - return 'geolocation' in navigator; - }; - - - tests['postmessage'] = function() { - return !!window.postMessage; - }; - - - tests['websqldatabase'] = function() { - return !!window.openDatabase; - }; - - tests['indexedDB'] = function() { - return !!testPropsAll("indexedDB", window); - }; - - tests['hashchange'] = function() { - return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); - }; - - tests['history'] = function() { - return !!(window.history && history.pushState); - }; - - tests['draganddrop'] = function() { - var div = document.createElement('div'); - return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); - }; - - tests['websockets'] = function() { - return 'WebSocket' in window || 'MozWebSocket' in window; - }; - - - tests['rgba'] = function() { - setCss('background-color:rgba(150,255,150,.5)'); - - return contains(mStyle.backgroundColor, 'rgba'); - }; - - tests['hsla'] = function() { - setCss('background-color:hsla(120,40%,100%,.5)'); - - return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); - }; - - tests['multiplebgs'] = function() { - setCss('background:url(https://),url(https://),red url(https://)'); - - return (/(url\s*\(.*?){3}/).test(mStyle.background); - }; tests['backgroundsize'] = function() { - return testPropsAll('backgroundSize'); - }; - - tests['borderimage'] = function() { - return testPropsAll('borderImage'); - }; - - - - tests['borderradius'] = function() { - return testPropsAll('borderRadius'); - }; - - tests['boxshadow'] = function() { - return testPropsAll('boxShadow'); - }; - - tests['textshadow'] = function() { - return document.createElement('div').style.textShadow === ''; - }; - - - tests['opacity'] = function() { - setCssAll('opacity:.55'); - - return (/^0.55$/).test(mStyle.opacity); - }; - - - tests['cssanimations'] = function() { - return testPropsAll('animationName'); - }; - - - tests['csscolumns'] = function() { - return testPropsAll('columnCount'); - }; - - - tests['cssgradients'] = function() { - var str1 = 'background-image:', - str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', - str3 = 'linear-gradient(left top,#9f9, white);'; - - setCss( - (str1 + '-webkit- '.split(' ').join(str2 + str1) + - prefixes.join(str3 + str1)).slice(0, -str1.length) - ); - - return contains(mStyle.backgroundImage, 'gradient'); - }; - - - tests['cssreflections'] = function() { - return testPropsAll('boxReflect'); - }; - - - tests['csstransforms'] = function() { - return !!testPropsAll('transform'); - }; - - - tests['csstransforms3d'] = function() { - - var ret = !!testPropsAll('perspective'); - - if ( ret && 'webkitPerspective' in docElement.style ) { - - injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { - ret = node.offsetLeft === 9 && node.offsetHeight === 3; - }); - } - return ret; - }; - - - tests['csstransitions'] = function() { - return testPropsAll('transition'); - }; - - - - tests['fontface'] = function() { - var bool; - - injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { - var style = document.getElementById('smodernizr'), - sheet = style.sheet || style.styleSheet, - cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; - - bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; - }); - - return bool; - }; - - tests['generatedcontent'] = function() { - var bool; - - injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { - bool = node.offsetHeight >= 3; - }); - - return bool; - }; - tests['video'] = function() { - var elem = document.createElement('video'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); - - bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); - - bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); - } - - } catch(e) { } - - return bool; - }; - - tests['audio'] = function() { - var elem = document.createElement('audio'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); - bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); - - bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); - bool.m4a = ( elem.canPlayType('audio/x-m4a;') || - elem.canPlayType('audio/aac;')) .replace(/^no$/,''); - } - } catch(e) { } - - return bool; - }; - - - tests['localstorage'] = function() { - try { - localStorage.setItem(mod, mod); - localStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - tests['sessionstorage'] = function() { - try { - sessionStorage.setItem(mod, mod); - sessionStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - - tests['webworkers'] = function() { - return !!window.Worker; - }; - - - tests['applicationcache'] = function() { - return !!window.applicationCache; - }; - - - tests['svg'] = function() { - return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; - }; - - tests['inlinesvg'] = function() { - var div = document.createElement('div'); - div.innerHTML = ''; - return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; - }; - - tests['smil'] = function() { - return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); - }; - - - tests['svgclippaths'] = function() { - return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); - }; - - function webforms() { - Modernizr['input'] = (function( props ) { - for ( var i = 0, len = props.length; i < len; i++ ) { - attrs[ props[i] ] = !!(props[i] in inputElem); - } - if (attrs.list){ - attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); - } - return attrs; - })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); - Modernizr['inputtypes'] = (function(props) { - - for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { - - inputElem.setAttribute('type', inputElemType = props[i]); - bool = inputElem.type !== 'text'; - - if ( bool ) { - - inputElem.value = smile; - inputElem.style.cssText = 'position:absolute;visibility:hidden;'; - - if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { - - docElement.appendChild(inputElem); - defaultView = document.defaultView; - - bool = defaultView.getComputedStyle && - defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && - (inputElem.offsetHeight !== 0); - - docElement.removeChild(inputElem); - - } else if ( /^(search|tel)$/.test(inputElemType) ){ - } else if ( /^(url|email)$/.test(inputElemType) ) { - bool = inputElem.checkValidity && inputElem.checkValidity() === false; - - } else { - bool = inputElem.value != smile; - } - } - - inputs[ props[i] ] = !!bool; - } - return inputs; - })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); - } - for ( var feature in tests ) { - if ( hasOwnProp(tests, feature) ) { - featureName = feature.toLowerCase(); - Modernizr[featureName] = tests[feature](); - - classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); - } - } - - Modernizr.input || webforms(); - - - Modernizr.addTest = function ( feature, test ) { - if ( typeof feature == 'object' ) { - for ( var key in feature ) { - if ( hasOwnProp( feature, key ) ) { - Modernizr.addTest( key, feature[ key ] ); - } - } - } else { - - feature = feature.toLowerCase(); - - if ( Modernizr[feature] !== undefined ) { - return Modernizr; - } - - test = typeof test == 'function' ? test() : test; - - if (typeof enableClasses !== "undefined" && enableClasses) { - docElement.className += ' ' + (test ? '' : 'no-') + feature; - } - Modernizr[feature] = test; - - } - - return Modernizr; - }; - - - setCss(''); - modElem = inputElem = null; - - ;(function(window, document) { - var version = '3.7.0'; - - var options = window.html5 || {}; - - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - var supportsHtml5Styles; - - var expando = '_html5shiv'; - - var expanID = 0; - - var expandoData = {}; - - var supportsUnknownElements; - - (function() { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - supportsHtml5Styles = ('hidden' in a); - - supportsUnknownElements = a.childNodes.length == 1 || (function() { - (document.createElement)('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - } catch(e) { - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - - }()); - - function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - function createElement(nodeName, ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; - } - - function createDocumentFragment(ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for(;i