Merge branch 'develop' of github.com:martini/zammad into develop

# Conflicts:
#	app/assets/javascripts/app/controllers/navigation.coffee
#	app/assets/javascripts/app/controllers/widget/online_notification.coffee
This commit is contained in:
Martin Edenhofer 2016-03-20 21:10:04 +01:00
commit cc2a8ce0c1
7 changed files with 162 additions and 160 deletions

View file

@ -24,15 +24,15 @@ class App.ControllerTable extends App.Controller
for key, value of data['headerWidth'] for key, value of data['headerWidth']
@headerWidth[key] = value @headerWidth[key] = value
@availableWidth = @el.width()
@render() @render()
$(window).on 'resize.table', @readjustHeaderWidths $(window).on 'resize.table', @onResize
release: => release: =>
$(window).off 'resize.table', @readjustHeaderWidths $(window).off 'resize.table', @onResize
render: => render: =>
@html @tableGen() @html @tableGen()
@readjustHeaderWidths()
if @dndCallback if @dndCallback
dndOptions = dndOptions =
@ -172,7 +172,7 @@ class App.ControllerTable extends App.Controller
# e.g. column: owner # e.g. column: owner
headerFound = true headerFound = true
if @headerWidth[attribute.name] if @headerWidth[attribute.name]
attribute.displayWidth = @headerWidth[attribute.name] attribute.displayWidth = @headerWidth[attribute.name] * @availableWidth
else if !attribute.width else if !attribute.width
attribute.displayWidth = @baseColWidth attribute.displayWidth = @baseColWidth
else else
@ -191,7 +191,7 @@ class App.ControllerTable extends App.Controller
if attributeName is rowWithoutId if attributeName is rowWithoutId
headerFound = true headerFound = true
if @headerWidth[attribute.name] if @headerWidth[attribute.name]
attribute.displayWidth = @headerWidth[attribute.name] attribute.displayWidth = @headerWidth[attribute.name] * @availableWidth
else if !attribute.width else if !attribute.width
attribute.displayWidth = @baseColWidth attribute.displayWidth = @baseColWidth
else else
@ -281,6 +281,8 @@ class App.ControllerTable extends App.Controller
@objects = @objects.concat groupObjects[group] @objects = @objects.concat groupObjects[group]
groupObjects[group] = [] # release old array groupObjects[group] = [] # release old array
@calculateHeaderWidths()
# get content # get content
table = App.view('generic/table')( table = App.view('generic/table')(
table_id: @table_id table_id: @table_id
@ -360,7 +362,6 @@ class App.ControllerTable extends App.Controller
# if we have a personalised table # if we have a personalised table
if @table_id if @table_id
# enable resize column # enable resize column
table.on 'mousedown', '.js-col-resize', @onColResizeMousedown table.on 'mousedown', '.js-col-resize', @onColResizeMousedown
table.on 'click', '.js-col-resize', @stopPropagation table.on 'click', '.js-col-resize', @stopPropagation
@ -404,17 +405,15 @@ class App.ControllerTable extends App.Controller
item: item item: item
container: @container container: @container
adjustHeaderWidths: -> calculateHeaderWidths: ->
if !@headers if !@headers
return return
availableWidth = @el.width() if @availableWidth is 0
@availableWidth = @minTableWidth
if availableWidth is 0
availableWidth = @minTableWidth
widths = @getHeaderWidths() widths = @getHeaderWidths()
shrinkBy = Math.ceil (widths - availableWidth) / @getShrinkableHeadersCount() shrinkBy = Math.ceil (widths - @availableWidth) / @getShrinkableHeadersCount()
# make all cols evenly smaller # make all cols evenly smaller
@headers = _.map @headers, (col) => @headers = _.map @headers, (col) =>
@ -423,11 +422,13 @@ class App.ControllerTable extends App.Controller
return col return col
# give left-over space from rounding to last column to get to 100% # give left-over space from rounding to last column to get to 100%
roundingLeftOver = availableWidth - @getHeaderWidths() roundingLeftOver = @availableWidth - @getHeaderWidths()
# but only if there is something left over (will get negative when there are too many columns for each column to stay in their min width) # but only if there is something left over (will get negative when there are too many columns for each column to stay in their min width)
if roundingLeftOver > 0 if roundingLeftOver > 0
@headers[@headers.length - 1].displayWidth = @headers[@headers.length - 1].displayWidth + roundingLeftOver @headers[@headers.length - 1].displayWidth = @headers[@headers.length - 1].displayWidth + roundingLeftOver
@storeHeaderWidths()
getShrinkableHeadersCount: -> getShrinkableHeadersCount: ->
_.reduce @headers, (memo, col) -> _.reduce @headers, (memo, col) ->
return if col.unresizable then memo else memo+1 return if col.unresizable then memo else memo+1
@ -449,12 +450,24 @@ class App.ControllerTable extends App.Controller
return widths return widths
readjustHeaderWidths: => setHeaderWidths: =>
@adjustHeaderWidths() @calculateHeaderWidths()
@tableHead.each (i, el) => @tableHead.each (i, el) =>
el.style.width = @headers[i].displayWidth + 'px' el.style.width = @headers[i].displayWidth + 'px'
storeHeaderWidths: ->
widths = {}
for header in @headers
widths[header.name] = header.displayWidth / @availableWidth
App.LocalStorage.set(@preferencesStoreKey(), { headerWidth: widths }, @Session.get('id'))
onResize: =>
@availableWidth = @el.width()
@setHeaderWidths()
stopPropagation: (event) -> stopPropagation: (event) ->
event.stopPropagation() event.stopPropagation()
@ -488,18 +501,12 @@ class App.ControllerTable extends App.Controller
# switch to percentage # switch to percentage
resizeBaseWidth = @resizeTargetLeft.parents('table').width() resizeBaseWidth = @resizeTargetLeft.parents('table').width()
leftWidth = @resizeTargetLeft.width() leftWidth = @resizeTargetLeft.width() / resizeBaseWidth
rightWidth = @resizeTargetRight.width() rightWidth = @resizeTargetRight.width() / resizeBaseWidth
leftColumnKey = @resizeTargetLeft.attr('data-column-key') leftColumnKey = @resizeTargetLeft.attr('data-column-key')
rightColumnKey = @resizeTargetRight.attr('data-column-key') rightColumnKey = @resizeTargetRight.attr('data-column-key')
# save table changed widths
storeColWidths = [
{ key: leftColumnKey, width: leftWidth }
{ key: rightColumnKey, width: rightWidth }
]
# update store and runtime @headerWidth # update store and runtime @headerWidth
@preferencesStore('headerWidth', leftColumnKey, leftWidth) @preferencesStore('headerWidth', leftColumnKey, leftWidth)
_.find(@headers, (column) -> column.name is leftColumnKey).displayWidth = leftWidth _.find(@headers, (column) -> column.name is leftColumnKey).displayWidth = leftWidth
@ -543,4 +550,4 @@ class App.ControllerTable extends App.Controller
data data
preferencesStoreKey: => preferencesStoreKey: =>
"tablePreferences:#{@table_id}" "tablePrefs:#{@table_id}"

View file

@ -1,6 +1,9 @@
class App.Navigation extends App.ControllerWidgetPermanent class App.Navigation extends App.ControllerWidgetPermanent
className: 'navigation vertical' className: 'navigation vertical'
events:
'click .js-toggleNotifications': 'toggleNotifications'
constructor: -> constructor: ->
super super
@render() @render()
@ -202,9 +205,8 @@ class App.Navigation extends App.ControllerWidgetPermanent
@emptyAndClose() @emptyAndClose()
) )
new App.OnlineNotificationWidget( @notificationWidget = new App.OnlineNotificationWidget()
el: @el $('#app').append @notificationWidget.el
)
listNavigate: (e) => listNavigate: (e) =>
if e.keyCode is 27 # close on esc if e.keyCode is 27 # close on esc
@ -468,4 +470,8 @@ class App.Navigation extends App.ControllerWidgetPermanent
@renderPersonal() @renderPersonal()
App.RecentView.fetchFull(load) App.RecentView.fetchFull(load)
toggleNotifications: (event) ->
event.stopPropagation()
@notificationWidget.toggle()
App.Config.set('navigation', App.Navigation, 'Navigations') App.Config.set('navigation', App.Navigation, 'Navigations')

View file

@ -1,8 +1,22 @@
class App.OnlineNotificationWidget extends App.Controller class App.OnlineNotificationWidget extends App.Controller
alreadyShown: {} alreadyShown: {}
shown: false
className: 'popover popover--notifications right'
attributes:
role: 'tooltip'
events:
'click .js-mark': 'markAllAsRead'
'click .js-item': 'hide'
'click .js-remove': 'removeItem'
'click .js-locationVerify': 'onItemClick'
'keydown': 'listNavigate'
elements: elements:
'.js-toggleNotifications': 'toggle' '.js-notificationsContainer': 'container'
'.js-mark': 'mark'
'.js-item': 'item'
'.js-content': 'content'
constructor: -> constructor: ->
super super
@ -29,27 +43,26 @@ class App.OnlineNotificationWidget extends App.Controller
# rebuild widget on auth # rebuild widget on auth
@bind 'auth', (user) => @bind 'auth', (user) =>
if !user if !user
@el.find('.js-counter').text('') @counterUpdate(0)
else else
if !@access() if !@access()
@el.find('.js-counter').text('') @counterUpdate(0)
return return
@createContainer()
if @access() if @access()
@createContainer() @subscribeId = App.OnlineNotification.subscribe(@show)
@subscribeId = App.OnlineNotification.subscribe(@updateContent)
@bind('ui:rerender', => @bind('ui:reshow', =>
@updateContent() @show()
'popover' 'popover'
) )
$(window).on 'click.notifications', @hide
release: -> release: ->
@removeContainer()
$(window).off 'click.notifications' $(window).off 'click.notifications'
$(window).off 'keydown.notifications'
App.OnlineNotification.unsubscribe(@subscribeId) App.OnlineNotification.unsubscribe(@subscribeId)
super
access: -> access: ->
return false if !@Session.get() return false if !@Session.get()
@ -58,9 +71,8 @@ class App.OnlineNotificationWidget extends App.Controller
return false return false
listNavigate: (e) => listNavigate: (e) =>
if e.keyCode is 27 # close on esc if e.keyCode is 27 # close on esc
@hidePopover() @hide()
return return
else if e.keyCode is 38 # up else if e.keyCode is 38 # up
@nudge(e, -1) @nudge(e, -1)
@ -69,25 +81,24 @@ class App.OnlineNotificationWidget extends App.Controller
@nudge(e, 1) @nudge(e, 1)
return return
else if e.keyCode is 13 # enter else if e.keyCode is 13 # enter
$('.js-notificationsContainer .popover-content .activity-entry.is-hover .js-locationVerify').click() @item.filter('.is-hover').find('.js-locationVerify').click()
nudge: (e, position) -> nudge: (e, position) ->
# get current # get current
navigation = $('.js-notificationsContainer .popover-content') current = @item.filter('.is-hover')
current = navigation.find('.activity-entry.is-hover') if !current.size()
if !current.get(0) @item.first().addClass('is-hover')
navigation.find('.activity-entry').first().addClass('is-hover')
return return
if position is 1 if position is 1
next = current.next('.activity-entry') next = current.next('.js-item')
if next.get(0) if next.size()
current.removeClass('is-hover') current.removeClass('is-hover')
next.addClass('is-hover') next.addClass('is-hover')
else else
prev = current.prev('.activity-entry') prev = current.prev('.is-item')
if prev.get(0) if prev.size()
current.removeClass('is-hover') current.removeClass('is-hover')
prev.addClass('is-hover') prev.addClass('is-hover')
@ -97,13 +108,19 @@ class App.OnlineNotificationWidget extends App.Controller
@scrollToIfNeeded(prev, true) @scrollToIfNeeded(prev, true)
counterUpdate: (count) => counterUpdate: (count) =>
count = '' if count is 0
$('.js-notificationsCounter').text(count)
@count = count
# show mark all as read if needed
if !count if !count
@$('.js-counter').text('') @mark.addClass('hidden')
return else
@mark.removeClass('hidden')
@$('.js-counter').text(count) markAllAsRead: (event) ->
event.preventDefault()
markAllAsRead: =>
@counterUpdate(0) @counterUpdate(0)
@ajax( @ajax(
id: 'markAllAsRead' id: 'markAllAsRead'
@ -115,80 +132,49 @@ class App.OnlineNotificationWidget extends App.Controller
updateHeight: -> updateHeight: ->
# set height of notification popover # set height of notification popover
notificationsContainer = $('.js-notificationsContainer')
heightApp = $('#app').height() heightApp = $('#app').height()
heightPopoverSpacer = 22 heightPopoverSpacer = 22
heightPopoverHeader = notificationsContainer.find('.popover-notificationsHeader').outerHeight(true) heightPopoverHeader = @header.outerHeight(true)
heightPopoverContent = notificationsContainer.find('.popover-content').prop('scrollHeight') heightPopoverContent = 0
heightPopoverContentNew = heightPopoverContent @item.each -> heightPopoverContent += @clientHeight
if (heightPopoverHeader + heightPopoverContent + heightPopoverSpacer) > heightApp if (heightPopoverHeader + heightPopoverContent + heightPopoverSpacer) > heightApp
heightPopoverContentNew = heightApp - heightPopoverHeader - heightPopoverSpacer heightPopoverContent = heightApp - heightPopoverHeader - heightPopoverSpacer
notificationsContainer.addClass('is-overflowing') @container.addClass('is-overflowing')
else else
notificationsContainer.removeClass('is-overflowing') @container.removeClass('is-overflowing')
notificationsContainer.find('.popover-content').css('height', "#{heightPopoverContentNew}px") @content.css('height', heightPopoverContent)
onShow: =>
@updateContent()
@updateHeight()
# mark all notifications as read
notificationsContainer = $('.js-notificationsContainer')
notificationsContainer.find('.js-markAllAsRead').on('click', (e) =>
e.preventDefault()
@markAllAsRead()
@hidePopover()
)
notificationsContainer.on 'click', @stopPropagation
$(window).on 'click.notifications', @hidePopover
$(window).on 'keydown.notifications', @listNavigate
onHide: ->
$(window).off 'click.notifications'
$(window).off 'keydown.notifications'
hidePopover: =>
@toggle.popover('hide')
fetch: => fetch: =>
load = (data) => load = (data) =>
@fetchedData = true @fetchedData = true
App.OnlineNotification.refresh(data.stream, clear: true) App.OnlineNotification.refresh(data.stream, clear: true)
@updateContent() @show()
App.OnlineNotification.fetchFull(load) App.OnlineNotification.fetchFull(load)
updateContent: => toggle: =>
if @shown
@hide()
else
@show()
show: =>
@shown = true
if !@Session.get() if !@Session.get()
$('.js-notificationsContainer .popover-content').html('') @content.html('')
return return
items = App.OnlineNotification.search(sortBy: 'created_at', order: 'DESC') items = App.OnlineNotification.search(sortBy: 'created_at', order: 'DESC')
counter = 0 @count = 0
for item in items for item in items
if !item.seen if !item.seen
counter = counter + 1 @count++
@counterUpdate(counter)
# update title @counterUpdate(@count)
$('.js-notificationsContainer .popover-title').html(
App.i18n.translateInline('Notifications') + " <span class='popover-notificationsCounter'>#{counter}</span>"
)
# show mark all as read if needed
if counter is 0
$('.js-notificationsContainer .js-markAllAsRead').addClass('hidden')
else
$('.js-notificationsContainer .js-markAllAsRead').removeClass('hidden')
# update content # update content
items = @prepareForObjectList(items) items = @prepareForObjectList(items)
$('.js-notificationsContainer .popover-content').html(
$( App.view('widget/online_notification_content')(items: items) )
)
notificationsContainer = $('.js-notificationsContainer .popover-content')
# generate desktop notifications # generate desktop notifications
for item in items for item in items
@ -206,54 +192,25 @@ class App.OnlineNotificationWidget extends App.Controller
) )
App.OnlineNotification.play() App.OnlineNotification.play()
# execute controller again of already open (because hash hasn't changed, we need to do it manually) @html App.view('widget/online_notification')(
notificationsContainer.find('.js-locationVerify').on('click', (e) => items: items
@locationVerify(e) count: @count
@hidePopover()
) )
# close notification list on click @el.show()
notificationsContainer.find('.activity-entry').on('click', (e) =>
@hidePopover()
)
# remove hide: =>
notificationsContainer.find('.js-remove').on('click', (e) => @shown = false
e.preventDefault() @el.hide()
e.stopPropagation()
row = $(e.target).closest('.activity-entry') onItemClick: (event) ->
@locationVerify(event)
@hide()
removeItem: (event) ->
event.preventDefault()
event.stopPropagation()
row = $(e.target).closest('.js-item')
id = row.data('id') id = row.data('id')
App.OnlineNotification.destroy(id) App.OnlineNotification.destroy(id)
@updateHeight() @updateHeight()
)
createContainer: =>
@removeContainer()
# show popover
waitUntilOldPopoverIsRemoved = =>
@toggle.popover
trigger: 'click'
container: 'body'
html: true
placement: 'right'
viewport: { selector: '#app', padding: 10 }
template: App.view('widget/online_notification')()
title: ' '
content: ' '
.on
'shown.bs.popover': @onShow
'hide.bs.popover': @onHide
@updateContent()
@delay(
-> waitUntilOldPopoverIsRemoved()
600
'popover'
)
removeContainer: =>
@counterUpdate(0)
@toggle.popover('destroy')

View file

@ -56,7 +56,7 @@
<td class="table-draggable"><%- @Icon('draggable') %></td> <td class="table-draggable"><%- @Icon('draggable') %></td>
<% end %> <% end %>
<% if @checkbox: %> <% if @checkbox: %>
<td class="table-checkbox"> <td class="table-checkbox js-checkbox-field">
<label class="checkbox-replacement"> <label class="checkbox-replacement">
<input type="checkbox" value="<%= object.id %>" name="bulk"> <input type="checkbox" value="<%= object.id %>" name="bulk">
<%- @Icon('checkbox', 'icon-unchecked') %> <%- @Icon('checkbox', 'icon-unchecked') %>

View file

@ -8,7 +8,7 @@
</div> </div>
<div class="logo js-toggleNotifications"> <div class="logo js-toggleNotifications">
<%- @Icon('logo') %> <%- @Icon('logo') %>
<div class="activity-counter js-counter"></div> <div class="activity-counter js-notificationsCounter"></div>
</div> </div>
<ul id="global-search-result" class="custom-dropdown-menu" role="menu"></ul> <ul id="global-search-result" class="custom-dropdown-menu" role="menu"></ul>
</form> </form>

View file

@ -1,8 +1,37 @@
<div class="popover popover--notifications js-notificationsContainer" role="tooltip"> <div class="arrow js-arrow"></div>
<div class="arrow"></div> <div class="popover-notificationsHeader js-header">
<div class="popover-notificationsHeader"> <div class="popover-title"><%- @T('Notifications') %><span class='popover-notificationsCounter js-notificationsCounter'><%- @count %></span></div>
<div class="popover-title"></div> <a class="btn btn--text btn--subtle js-mark<%- ' hidden' if !@count %>"><%- @T( 'Mark all as read' ) %></a>
<a class="btn btn--text btn--subtle js-markAllAsRead"><%- @T( 'Mark all as read' ) %></a> </div>
</div> <div class="popover-content js-content">
<div class="popover-content"></div> <% if @items.length: %>
<% for item in @items: %>
<div class="js-item activity-entry activity-entry--removeable<% if item.seen: %> is-inactive<% end %>" data-id="<%- item.id %>">
<a class="activity-avatar user-popover" data-id="<%= item.created_by_id %>" <% if item.created_by_id isnt 1: %>href="<%- item.created_by.uiUrl() %>"<% end %>>
<%- item.created_by.avatar() %>
</a>
<div class="activity-body">
<a class="activity-message js-locationVerify" href="<%- item.link %>">
<span class="activity-text">
<% if item.objectNative && item.objectNative.activityMessage: %>
<%- item.objectNative.activityMessage(item) %>
<% else: %>
Need objectNative in item <%= item.object %>.find(<%= item.o_id %>)
<% end %>
</span>
<%- @humanTime(item.created_at, false, 'activity-time') %>
</a>
<div class="activity-remove js-remove">
<div class="activity-remove-icon-holder">
<%- @Icon('diagonal-cross') %>
</div>
</div>
</div>
</div>
<% end %>
<% else: %>
<div class="activity-placeholder">
<%- @T("No unread Notifications for you. :) ") %>
</div>
<% end %>
</div> </div>

View file

@ -3654,6 +3654,8 @@ footer {
} }
.popover--notifications { .popover--notifications {
left: $navigationWidth;
margin: 8px 2px;
min-height: 91px; min-height: 91px;
width: auto; width: auto;
max-width: 400px; max-width: 400px;
@ -3667,6 +3669,7 @@ footer {
.arrow { .arrow {
top: 23px !important; top: 23px !important;
left: -11px;
} }
.popover-content { .popover-content {