Refactoring: Bindings and namespaces improves memory usage and resolved ghost bindings.

* Renamed controllers `@bind` and `@unbind` to `@controllerBind` and `@controllerUnbind` to not overwrite spine's methods to take advantage of spine's binding/unbinding and cleanup features.

* Cleanup of controller namespaces `App.ObserverController` -> `App.ControllerObserver`, `App.ObserverActionRow` -> `App.ControllerObserverActionRow` and `App.WizardModal` -> `App.ControllerWizardModal`

* Moved to named local controller names to have better trace backs/debugging (e. g. `class Index` to `class UserIndex`).

* Splited `app/assets/javascripts/app/controllers/_application_controller.coffee` into separate files under `app/assets/javascripts/app/controllers/_application_controller/*.coffee` to have better trace backs/debugging.

* Moved classes with executed code from `app/assets/javascripts/app/controllers/widget/*.coffee` into `app/assets/javascripts/app/controllers/_plugin/` from now `app/assets/javascripts/app/controllers/widget/*.coffee` will only contain UI widgets like popovers.

* Refactored second argument of `@navigate`. Old version was only `true` to hide `@navigate` redirect from browser back list. Now we introduced params instant of boolean argument. Now we support:
  * `hideCurrentLocationFromHistory: true` -> hide redirect from browser back list (old `true` argument)
  * `emptyEl: true` -> will empty `@el` before redirect to new location
  * `removeEl: true` -> will remove `@el` before redirect to new location
This commit is contained in:
Martin Edenhofer 2021-01-29 13:22:19 +01:00 committed by Thorsten Eckel
parent 0eac6f0fd9
commit 5ee6418c18
193 changed files with 3846 additions and 3788 deletions

View file

@ -2,12 +2,7 @@ class App.Controller extends Spine.Controller
@include App.LogInclude
@include App.RenderScreen
constructor: (params) ->
# unbind old bindings
if params && params.el && params.el.unbind
params.el.unbind()
constructor: ->
super
# generate controllerId
@ -30,11 +25,14 @@ class App.Controller extends Spine.Controller
ajaxId = App.Ajax.request(data)
@ajaxCalls.push ajaxId
navigate: (location, hideCurrentLocationFromHistory = false) ->
@log 'debug', "navigate to '#{location}', hide from history '#{hideCurrentLocationFromHistory}'"
navigate: (location, params = {}) ->
@log 'debug', "navigate to '#{location}'"
@log 'debug', "navigate hide from history '#{params.hideCurrentLocationFromHistory}'" if params.hideCurrentLocationFromHistory
@el.empty() if params.emptyEl
@el.remove() if params.removeEl
# hide current location from browser history, allow to use back button in browser
if hideCurrentLocationFromHistory
if params.hideCurrentLocationFromHistory
if window.history
history = App.Config.get('History')
oldLocation = history[history.length-2]
@ -45,22 +43,14 @@ class App.Controller extends Spine.Controller
preventDefault: (e) ->
e.preventDefault()
bind: (event, callback) =>
controllerBind: (event, callback) =>
App.Event.bind(
event
callback
@controllerId
)
one: (event, callback) =>
App.Event.bind(
event
callback
@controllerId
true
)
unbind: (event, callback) =>
controllerUnbind: (event, callback) =>
App.Event.unbind(
event
callback
@ -85,6 +75,16 @@ class App.Controller extends Spine.Controller
App.Interval.clearLevel(@controllerId)
@abortAjaxCalls()
# release bindings
if @el
@el.undelegate()
@el.unbind()
@el.empty()
# release spine bindings (see release() of spine.coffee)
@unbind()
@stopListening()
abortAjaxCalls: =>
if !@ajaxCalls
return
@ -125,11 +125,11 @@ class App.Controller extends Spine.Controller
# add @notify method to create notification
notify: (data) ->
App.Event.trigger 'notify', data
App.Event.trigger('notify', data)
# add @notifyDesktop method to create desktop notification
notifyDesktop: (data) ->
App.Event.trigger 'notifyDesktop', data
App.Event.trigger('notifyDesktop', data)
# add @navupdate method to update navigation
navupdate: (url, force = false) ->
@ -137,17 +137,7 @@ class App.Controller extends Spine.Controller
# ignore navupdate until #clues are gone
return if !force && window.location.hash is '#clues'
App.Event.trigger 'navupdate', url
# show navigation
navShow: ->
return if $('#navigation').is(':visible')
$('#navigation').removeClass('hide')
# hide navigation
navHide: ->
return if !$('#navigation').is(':visible')
$('#navigation').addClass('hide')
App.Event.trigger('navupdate', url)
updateNavMenu: =>
delay = ->
@ -402,340 +392,3 @@ class App.Controller extends Spine.Controller
return true
throw 'Cant reload page!'
class App.ControllerPermanent extends App.Controller
constructor: ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission, true)
super
@navShow()
class App.ControllerSubContent extends App.Controller
constructor: ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission)
super
show: =>
if @genericController && @genericController.show
@genericController.show()
return if !@header
@title @header, true
hide: =>
if @genericController && @genericController.hide
@genericController.hide()
class App.ControllerContent extends App.Controller
constructor: ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission)
super
# hide tasks
App.TaskManager.hideAll()
$('#content').removeClass('hide').removeClass('active')
@navShow()
class App.ControllerModal extends App.Controller
authenticateRequired: false
backdrop: true
keyboard: true
large: false
small: false
veryLarge: false
head: '?'
autoFocusOnFirstInput: true
container: null
buttonClass: 'btn--success'
centerButtons: []
leftButtons: []
buttonClose: true
buttonCancel: false
buttonCancelClass: 'btn--text btn--subtle'
buttonSubmit: true
includeForm: true
headPrefix: ''
shown: true
closeOnAnyClick: false
initalFormParams: {}
initalFormParamsIgnore: false
showTrySupport: false
showTryMax: 10
showTrydelay: 1000
events:
'submit form': 'submit'
'click .js-submit:not(.is-disabled)': 'submit'
'click .js-cancel': 'cancel'
'click .js-close': 'cancel'
className: 'modal fade'
constructor: ->
super
@showTryCount = 0
if @authenticateRequired
return if !@authenticateCheckRedirect()
# rerender view, e. g. on langauge change
@bind('ui:rerender', =>
@update()
'modal'
)
if @shown
@render()
showDelayed: =>
delay = =>
@showTryCount += 1
@render()
@delay(delay, @showTrydelay)
modalAlreadyExists: ->
return true if $('.modal').length > 0
false
content: ->
'You need to implement a one @content()!'
update: =>
if @message
content = App.i18n.translateContent(@message)
else if @contentInline
content = @contentInline
else
content = @content()
modal = $(App.view('modal')(
head: @head
headPrefix: @headPrefix
message: @message
detail: @detail
buttonClose: @buttonClose
buttonCancel: @buttonCancel
buttonCancelClass: @buttonCancelClass
buttonSubmit: @buttonSubmit
buttonClass: @buttonClass
centerButtons: @centerButtons
leftButtons: @leftButtons
includeForm: @includeForm
))
modal.find('.modal-body').html(content)
if !@initRenderingDone
@initRenderingDone = true
@html(modal)
else
@$('.modal-dialog').replaceWith(modal)
@post()
post: ->
# nothing
element: =>
@el
render: =>
if @showTrySupport is true && @modalAlreadyExists() && @showTryCount <= @showTryMax
@showDelayed()
return
@initalFormParamsIgnore = false
if @buttonSubmit is true
@buttonSubmit = 'Submit'
if @buttonCancel is true
@buttonCancel = 'Cancel & Go Back'
@update()
if @container
@el.addClass('modal--local')
if @veryLarge
@el.addClass('modal--veryLarge')
if @large
@el.addClass('modal--large')
if @small
@el.addClass('modal--small')
@el
.on(
'show.bs.modal': @localOnShow
'shown.bs.modal': @localOnShown
'hide.bs.modal': @localOnClose
'hidden.bs.modal': @localOnClosed
'dismiss.bs.modal': @localOnCancel
).modal(
keyboard: @keyboard
show: true
backdrop: @backdrop
container: @container
)
if @closeOnAnyClick
@el.on('click', =>
@close()
)
close: (e) =>
if e
e.preventDefault()
@initalFormParamsIgnore = true
@el.modal('hide')
formParams: =>
if @container
return @formParam(@container.find('.modal form'))
return @formParam(@$('.modal form'))
showAlert: (message, suffix = 'danger') ->
alert = $('<div>')
.addClass("alert alert--#{suffix}")
.text(message)
@$('.modal-alerts-container').html(alert)
clearAlerts: ->
@$('.modal-alerts-container').empty()
localOnShow: (e) =>
@onShow(e)
onShow: (e) ->
# do nothing
localOnShown: (e) =>
@onShown(e)
onShown: (e) =>
if @autoFocusOnFirstInput
# select generated form
form = @$('.form-group').first()
# if not exists, use whole @el
if !form.get(0)
form = @el
# focus first input, select or textarea
form.find('input:not([disabled]):not([type="hidden"]):not(".btn"), select:not([disabled]), textarea:not([disabled])').first().focus()
@initalFormParams = @formParams()
localOnClose: (e) =>
diff = difference(@initalFormParams, @formParams())
if @initalFormParamsIgnore is false && !_.isEmpty(diff)
if !confirm(App.i18n.translateContent('The form content has been changed. Do you want to close it and lose your changes?'))
e.preventDefault()
return
@onClose(e)
onClose: ->
# do nothing
localOnClosed: (e) =>
@onClosed(e)
@el.modal('remove')
onClosed: (e) ->
# do nothing
localOnCancel: (e) =>
@onCancel(e)
onCancel: (e) ->
# do nothing
cancel: (e) =>
@close(e)
@onCancel(e)
onSubmit: (e) ->
# do nothing
submit: (e) =>
e.stopPropagation()
e.preventDefault()
@clearAlerts()
@onSubmit(e)
startLoading: =>
@$('.modal-body').addClass('hide')
@$('.modal-loader').removeClass('hide')
stopLoading: =>
@$('.modal-body').removeClass('hide')
@$('.modal-loader').addClass('hide')
class App.SessionMessage extends App.ControllerModal
showTrySupport: true
onCancel: (e) =>
if @forceReload
@windowReload(e)
onClose: (e) =>
if @forceReload
@windowReload(e)
onSubmit: (e) =>
if @forceReload
@windowReload(e)
else
@close()
class App.UpdateHeader extends App.Controller
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = @genericObject.subscribe(@render)
release: =>
App[ @genericObject.constructor.className ].unsubscribe(@subscribeId)
render: (genericObject) =>
@el.find('.page-header h1').html(genericObject.displayName())
class App.UpdateTastbar extends App.Controller
constructor: ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = @genericObject.subscribe(@update)
release: =>
App[ @genericObject.constructor.className ].unsubscribe(@subscribeId)
update: (genericObject) =>
# update taskbar with new meta data
App.TaskManager.touch(@taskKey)
class App.ControllerWidgetPermanent extends App.Controller
constructor: (params) ->
if params.el
params.el.append('<div id="' + params.key + '"></div>')
params.el = ("##{params.key}")
super(params)
class App.ControllerWidgetOnDemand extends App.Controller
constructor: (params) ->
params.el = $("##{params.key}")
super
element: =>
$("##{@key}")
html: (raw) =>
# check if parent exists
if !$("##{@key}").get(0)
$('#app').before("<div id=\"#{@key}\" class=\"#{@className}\"></div>")
$("##{@key}").html raw

View file

@ -0,0 +1,229 @@
class App.ControllerModal extends App.Controller
authenticateRequired: false
backdrop: true
keyboard: true
large: false
small: false
veryLarge: false
head: '?'
autoFocusOnFirstInput: true
container: null
buttonClass: 'btn--success'
centerButtons: []
leftButtons: []
buttonClose: true
buttonCancel: false
buttonCancelClass: 'btn--text btn--subtle'
buttonSubmit: true
includeForm: true
headPrefix: ''
shown: true
closeOnAnyClick: false
initalFormParams: {}
initalFormParamsIgnore: false
showTrySupport: false
showTryMax: 10
showTrydelay: 1000
events:
'submit form': 'submit'
'click .js-submit:not(.is-disabled)': 'submit'
'click .js-cancel': 'cancel'
'click .js-close': 'cancel'
className: 'modal fade'
constructor: ->
super
@showTryCount = 0
if @authenticateRequired
return if !@authenticateCheckRedirect()
# rerender view, e. g. on langauge change
@controllerBind('ui:rerender', =>
@update()
'modal'
)
if @shown
@render()
showDelayed: =>
delay = =>
@showTryCount += 1
@render()
@delay(delay, @showTrydelay)
modalAlreadyExists: ->
return true if $('.modal').length > 0
false
content: ->
'You need to implement a one @content()!'
update: =>
if @message
content = App.i18n.translateContent(@message)
else if @contentInline
content = @contentInline
else
content = @content()
modal = $(App.view('modal')(
head: @head
headPrefix: @headPrefix
message: @message
detail: @detail
buttonClose: @buttonClose
buttonCancel: @buttonCancel
buttonCancelClass: @buttonCancelClass
buttonSubmit: @buttonSubmit
buttonClass: @buttonClass
centerButtons: @centerButtons
leftButtons: @leftButtons
includeForm: @includeForm
))
modal.find('.modal-body').html(content)
if !@initRenderingDone
@initRenderingDone = true
@html(modal)
else
@$('.modal-dialog').replaceWith(modal)
@post()
post: ->
# nothing
element: =>
@el
render: =>
if @showTrySupport is true && @modalAlreadyExists() && @showTryCount <= @showTryMax
@showDelayed()
return
@initalFormParamsIgnore = false
if @buttonSubmit is true
@buttonSubmit = 'Submit'
if @buttonCancel is true
@buttonCancel = 'Cancel & Go Back'
@update()
if @container
@el.addClass('modal--local')
if @veryLarge
@el.addClass('modal--veryLarge')
if @large
@el.addClass('modal--large')
if @small
@el.addClass('modal--small')
@el
.on(
'show.bs.modal': @localOnShow
'shown.bs.modal': @localOnShown
'hide.bs.modal': @localOnClose
'hidden.bs.modal': @localOnClosed
'dismiss.bs.modal': @localOnCancel
).modal(
keyboard: @keyboard
show: true
backdrop: @backdrop
container: @container
)
if @closeOnAnyClick
@el.on('click', =>
@close()
)
close: (e) =>
if e
e.preventDefault()
@initalFormParamsIgnore = true
@el.modal('hide')
formParams: =>
if @container
return @formParam(@container.find('.modal form'))
return @formParam(@$('.modal form'))
showAlert: (message, suffix = 'danger') ->
alert = $('<div>')
.addClass("alert alert--#{suffix}")
.text(message)
@$('.modal-alerts-container').html(alert)
clearAlerts: ->
@$('.modal-alerts-container').empty()
localOnShow: (e) =>
@onShow(e)
onShow: (e) ->
# do nothing
localOnShown: (e) =>
@onShown(e)
onShown: (e) =>
if @autoFocusOnFirstInput
# select generated form
form = @$('.form-group').first()
# if not exists, use whole @el
if !form.get(0)
form = @el
# focus first input, select or textarea
form.find('input:not([disabled]):not([type="hidden"]):not(".btn"), select:not([disabled]), textarea:not([disabled])').first().focus()
@initalFormParams = @formParams()
localOnClose: (e) =>
diff = difference(@initalFormParams, @formParams())
if @initalFormParamsIgnore is false && !_.isEmpty(diff)
if !confirm(App.i18n.translateContent('The form content has been changed. Do you want to close it and lose your changes?'))
e.preventDefault()
return
@onClose(e)
onClose: ->
# do nothing
localOnClosed: (e) =>
@onClosed(e)
@el.modal('remove')
onClosed: (e) ->
# do nothing
localOnCancel: (e) =>
@onCancel(e)
onCancel: (e) ->
# do nothing
cancel: (e) =>
@close(e)
@onCancel(e)
onSubmit: (e) ->
# do nothing
submit: (e) =>
e.stopPropagation()
e.preventDefault()
@clearAlerts()
@onSubmit(e)
startLoading: =>
@$('.modal-body').addClass('hide')
@$('.modal-loader').removeClass('hide')
stopLoading: =>
@$('.modal-body').removeClass('hide')
@$('.modal-loader').addClass('hide')

View file

@ -0,0 +1,15 @@
class App.ControllerAppContent extends App.Controller
constructor: (params) ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission)
# hide tasks
App.TaskManager.hideAll()
params.el = params.appEl.find('#content')
params.el.removeClass('hide').removeClass('active')
if !params.el.get(0)
params.appEl.append('<div id="content" class="content flex horizontal"></div>')
params.el = $('#content')
super(params)

View file

@ -0,0 +1,331 @@
class App.CollectionController extends App.Controller
events:
'click .js-remove': 'remove'
'click .js-item': 'click'
'click .js-locationVerify': 'location'
observe:
field1: true
field2: false
#currentItems: {}
#1:
# a: 123
# b: 'some string'
#2:
# a: 123
# b: 'some string'
#renderList: {}
#1: ..dom..ref..
#2: ..dom..ref..
template: '_need_to_be_defined_'
uniqKey: 'id'
model: '_need_to_be_defined_'
sortBy: 'name'
order: 'ASC',
insertPosition: 'after'
globalRerender: true
constructor: ->
@events = @constructor.events unless @events
@observe = @constructor.observe unless @observe
@currentItems = {}
@renderList = {}
@queue = []
@queueRunning = false
@lastOrder = []
super
@queue.push ['renderAll']
@uIRunner()
# bind to changes
if @model
@subscribeId = App[@model].subscribe(@collectionSync)
# render on generic ui call
if @globalRerender
@controllerBind('ui:rerender', =>
@queue.push ['renderAll']
@uIRunner()
)
# render on login
@controllerBind('auth:login', =>
@queue.push ['renderAll']
@uIRunner()
)
# reset current tasks on logout
@controllerBind('auth:logout', =>
@queue.push ['renderAll']
@uIRunner()
)
@log 'debug', 'Init @uniqKey', @uniqKey
@log 'debug', 'Init @observe', @observe
@log 'debug', 'Init @model', @model
release: =>
if @subscribeId
App[@model].unsubscribe(@subscribeId)
uIRunner: =>
return if !@queue[0]
return if @queueRunning
@queueRunning = true
loop
param = @queue.shift()
if param[0] is 'domChange'
@domChange(param[1])
else if param[0] is 'domRemove'
@domRemove(param[1])
else if param[0] is 'change'
@collectionSync(param[1])
else if param[0] is 'destroy'
@collectionSync(param[1], 'destroy')
else if param[0] is 'renderAll'
@renderAll()
else
@log 'error', "Unknown type #{param[0]}", param[1]
if !@queue[0]
@onRenderEnd()
@queueRunning = false
break
collectionOrderGet: =>
newOrder = []
all = @itemsAll()
for item in all
newOrder.push item[@uniqKey]
newOrder
collectionOrderSet: (newOrder = false) =>
if !newOrder
newOrder = @collectionOrderGet()
@lastOrder = newOrder
collectionSync: (items, type) =>
# remove items
if type is 'destroy'
ids = []
for item in items
ids.push item[@uniqKey]
@queue.push ['domRemove', ids]
@uIRunner()
return
# inital render
if _.isEmpty(@renderList)
@queue.push ['renderAll']
@uIRunner()
return
# check if item order is the same
newOrder = @collectionOrderGet()
removedIds = _.difference(@lastOrder, newOrder)
addedIds = _.difference(newOrder, @lastOrder)
@log 'debug', 'collectionSync removedIds', removedIds
@log 'debug', 'collectionSync addedIds', addedIds
@log 'debug', 'collectionSync @lastOrder', @lastOrder
@log 'debug', 'collectionSync newOrder', newOrder
# add items
alreadyRemoved = false
if !_.isEmpty(addedIds)
lastOrderNew = []
for id in @lastOrder
if !_.contains(removedIds, id)
lastOrderNew.push id
# try to find positions of new items
@log 'debug', 'collectionSync lastOrderNew', lastOrderNew
applyOrder = App.Utils.diffPositionAdd(lastOrderNew, newOrder)
@log 'debug', 'collectionSync applyOrder', applyOrder
if !applyOrder
@queue.push ['renderAll']
@uIRunner()
return
if !_.isEmpty(removedIds)
alreadyRemoved = true
@queue.push ['domRemove', removedIds]
@uIRunner()
newItems = []
for apply in applyOrder
item = @itemGet(apply.id)
item.meta_position = apply.position
newItems.push item
@queue.push ['domChange', newItems]
@uIRunner()
# remove items
if !alreadyRemoved && !_.isEmpty(removedIds)
@queue.push ['domRemove', removedIds]
@uIRunner()
# update items
newItems = []
for item in items
if !_.contains(removedIds, item.id) && !_.contains(addedIds, item.id)
newItems.push item
return if _.isEmpty(newItems)
@queue.push ['domChange', newItems]
@uIRunner()
#return
# rerender all items
#@queue.push ['renderAll']
#@uIRunner()
domRemove: (ids) =>
@log 'debug', 'domRemove', ids
for id in ids
@itemAttributesDelete(id)
if @renderList[id]
@renderList[id].remove()
delete @renderList[id]
@onRemoved(id)
@collectionOrderSet()
domChange: (items) =>
@log 'debug', 'domChange items', items
@log 'debug', 'domChange @currentItems', @currentItems
changedItems = []
for item in items
@log 'debug', 'domChange|item', item
attributes = @itemAttributes(item)
currentItem = @itemAttributesGet(item[@uniqKey])
if !currentItem
@log 'debug', 'domChange|add', item
changedItems.push item
@itemAttributesSet(item[@uniqKey], attributes)
else
@log 'debug', 'domChange|change', item
@log 'debug', 'domChange|change|observe attributes', @observe
@log 'debug', 'domChange|change|current', currentItem
@log 'debug', 'domChange|change|new', attributes
for field of @observe
@log 'debug', 'domChange|change|compare', field, currentItem[field], attributes[field]
diff = !_.isEqual(currentItem[field], attributes[field])
@log 'debug', 'domChange|diff', diff
if diff
changedItems.push item
@itemAttributesSet(item[@uniqKey], attributes)
break
return if _.isEmpty(changedItems)
@renderParts(changedItems)
renderAll: =>
items = @itemsAll()
@log 'debug', 'renderAll', items
localeEls = []
for item in items
attributes = @itemAttributes(item)
@itemAttributesSet(item[@uniqKey], attributes)
localeEls.push @renderItem(item, false)
@html localeEls
@collectionOrderSet()
@onRenderEnd()
itemDestroy: (id) =>
App[@model].destroy(id)
itemsAll: =>
App[@model].search(sortBy: @sortBy, order: @order)
itemAttributesDiff: (item) =>
attributes = @itemAttributes(item)
currentItem = @itemAttributesGet(item[@uniqKey])
for field of @observe
@log 'debug', 'itemAttributesDiff|compare', field, currentItem[field], attributes[field]
diff = !_.isEqual(currentItem[field], attributes[field])
if diff
@log 'debug', 'itemAttributesDiff|diff', diff
return true
false
itemAttributesDelete: (id) =>
delete @currentItems[id]
itemAttributesGet: (id) =>
@currentItems[id]
itemAttributesSet: (id, attributes) =>
@currentItems[id] = attributes
itemAttributes: (item) =>
attributes = {}
for field of @observe
attributes[field] = item[field]
attributes
itemGet: (id) =>
App[@model].find(id)
renderParts: (items) =>
@log 'debug', 'renderParts', items
for item in items
if !@renderList[item[@uniqKey]]
@renderItem(item)
else
@renderItem(item, @renderList[item[@uniqKey]])
@collectionOrderSet()
renderItem: (item, el) =>
if @prepareForObjectListItemSupport
item = @prepareForObjectListItem(item)
@log 'debug', 'renderItem', item, @template, el, @renderList[item[@uniqKey]]
html = $(App.view(@template)(
item: item
))
if @onRenderItemEnd
@onRenderItemEnd(item, html)
itemCount = Object.keys(@renderList).length
@renderList[item[@uniqKey]] = html
if el is false
return html
else if !el
position = item.meta_position
if itemCount > position
position += 1
element = @el.find(".js-item:nth-child(#{position})")
if !element.get(0)
@el.append(html)
return
if @insertPosition is 'before'
element.before(html)
else
element.after(html)
else
el.replaceWith(html)
onRenderEnd: ->
# nothing
location: (e) =>
@locationVerify(e)
click: (e) =>
row = $(e.target).closest('.js-item')
id = row.data('id')
@onClick(id, e)
onClick: (id, e) ->
# nothing
remove: (e) =>
e.preventDefault()
e.stopPropagation()
row = $(e.target).closest('.js-item')
id = row.data('id')
@onRemove(id,e)
@itemDestroy(id)
onRemove: (id, e) ->
# nothing
onRemoved: (id) ->
# nothing

View file

@ -0,0 +1,19 @@
class App.ControllerDrox extends App.Controller
constructor: (params) ->
super
if params.data && ( params.data.text || params.data.html )
@inline(params.data)
inline: (data) ->
@html App.view('generic/drox')(data)
if data.text
@$('.drox-body').text(data.text)
if data.html
@$('.drox-body').html(data.html)
template: (data) ->
drox = $( App.view('generic/drox')(data) )
content = App.view(data.file)(data.params)
drox.find('.drox-body').append(content)
drox

View file

@ -0,0 +1,20 @@
class App.ControllerFullPage extends App.Controller
constructor: (params) ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission)
super
replaceWith: (localElement) =>
@appEl.find('>').not(".#{@className}").remove() if @className
@appEl.find('>').filter(".#{@className}").remove() if @forceRender
@el = $(localElement)
container = @appEl.find('>').filter(".#{@className}")
if !container.get(0)
@el.addClass(@className)
@appEl.append(@el)
@delegateEvents(@events)
@refreshElements()
@el.on('remove', @releaseController)
@el.on('remove', @release)
else
container.html(@el.children())

View file

@ -0,0 +1,11 @@
class App.ControllerGenericDescription extends App.ControllerModal
buttonClose: true
buttonCancel: false
buttonSubmit: 'Close'
head: 'Description'
content: =>
marked(App.i18n.translateContent(@description))
onSubmit: =>
@close()

View file

@ -0,0 +1,37 @@
class App.ControllerGenericDestroyConfirm extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: 'delete'
buttonClass: 'btn--danger'
head: 'Confirm'
small: true
content: ->
App.i18n.translateContent('Sure to delete this object?')
onSubmit: =>
options = @options || {}
options.done = =>
@close()
if @callback
@callback()
options.fail = =>
@log 'errors'
@close()
@item.destroy(options)
class App.ControllerConfirm extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: 'yes'
buttonClass: 'btn--danger'
head: 'Confirm'
small: true
content: ->
App.i18n.translateContent(@message)
onSubmit: =>
@close()
if @callback
@callback()

View file

@ -0,0 +1,53 @@
class App.ControllerGenericEdit extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: true
headPrefix: 'Edit'
content: =>
@item = App[ @genericObject ].find( @id )
@head = @pageData.head || @pageData.object
@controller = new App.ControllerForm(
model: App[ @genericObject ]
params: @item
screen: @screen || 'edit'
autofocus: true
handlers: @handlers
)
@controller.form
onSubmit: (e) ->
params = @formParam(e.target)
@item.load(params)
# validate form using HTML5 validity check
element = $(e.target).closest('form').get(0)
if element && element.reportValidity && !element.reportValidity()
return false
# validate
errors = @item.validate()
if errors
@log 'error', errors
@formValidate( form: e.target, errors: errors )
return false
# disable form
@formDisable(e)
# save object
ui = @
@item.save(
done: ->
if ui.callback
item = App[ ui.genericObject ].fullLocal(@id)
ui.callback(item)
ui.close()
fail: (settings, details) ->
App[ ui.genericObject ].fetch(id: @id)
ui.log 'errors'
ui.formEnable(e)
ui.controller.showAlert(details.error_human || details.error || 'Unable to update object!')
)

View file

@ -0,0 +1,17 @@
class App.ControllerErrorModal extends App.ControllerModal
buttonClose: true
buttonCancel: false
buttonSubmit: 'Close'
#buttonClass: 'btn--danger'
head: 'Error'
#small: true
#shown: true
showTrySupport: true
content: ->
@message
onSubmit: =>
@close()
if @callback
@callback()

View file

@ -0,0 +1,112 @@
class App.GenericHistory extends App.ControllerModal
@extend App.PopoverProvidable
@registerPopovers 'User'
buttonClose: true
buttonCancel: false
buttonSubmit: false
head: 'History'
shown: false
constructor: ->
super
@fetch()
content: =>
localItem = @reworkItems(@items)
content = $ App.view('generic/history')(
items: localItem
)
content.find('a[data-type="sortorder"]').bind('click', (e) =>
e.preventDefault()
@sortorder()
)
content
onShown: =>
@renderPopovers()
sortorder: =>
@items = @items.reverse()
@update()
T: (name) ->
App.i18n.translateInline(name)
reworkItems: (items) ->
newItems = []
newItem = {}
lastUserId = undefined
lastTime = undefined
items = clone(items)
for item in items
if item.object is 'Ticket::Article'
item.object = 'Article'
data = item
data.created_by = App.User.find( item.created_by_id )
currentItemTime = new Date( item.created_at )
lastItemTime = new Date( new Date( lastTime ).getTime() + (15 * 1000) )
# start new section if user or time has changed
if lastUserId isnt item.created_by_id || currentItemTime > lastItemTime
lastTime = item.created_at
lastUserId = item.created_by_id
if !_.isEmpty(newItem)
newItems.push newItem
newItem =
created_at: item.created_at
created_by: App.User.find( item.created_by_id )
records: []
# build content
content = ''
if item.type is 'notification' || item.type is 'email'
content = "#{ @T( item.type ) } #{ @T( 'sent to' ) } '#{ item.value_to }'"
else if item.type is 'received_merge'
ticket = App.Ticket.find( item.id_from )
ticket_link = if ticket
"<a href=\"#ticket/zoom/#{ item.id_from }\">##{ ticket.number }</a>"
else
item.value_from
content = "#{ @T( 'Ticket' ) } #{ ticket_link } #{ @T( 'was merged into this ticket' ) }"
else if item.type is 'merged_into'
ticket = App.Ticket.find( item.id_to )
ticket_link = if ticket
"<a href=\"#ticket/zoom/#{ item.id_to }\">##{ ticket.number }</a>"
else
item.value_to
content = "#{ @T( 'This ticket was merged into' ) } #{ @T( 'ticket' ) } #{ ticket_link }"
else
content = "#{ @T( item.type ) } #{ @T(item.object) } "
if item.attribute
content += "#{ @T(item.attribute) }"
# convert time stamps
if item.object is 'User' && item.attribute is 'last_login'
if item.value_from
item.value_from = App.i18n.translateTimestamp( item.value_from )
if item.value_to
item.value_to = App.i18n.translateTimestamp( item.value_to )
if item.value_from
if item.value_to
content += " #{ @T( 'from' ) }"
content += " '#{ App.Utils.htmlEscape(item.value_from) }'"
if item.value_to
if item.value_from
content += ' &rarr;'
content += " '#{ App.Utils.htmlEscape(item.value_to) }'"
else if item.value_from
content += " &rarr; '-'"
newItem.records.push content
if !_.isEmpty(newItem)
newItems.push newItem
newItems

View file

@ -0,0 +1,192 @@
class App.ControllerGenericIndex extends App.Controller
events:
'click [data-type=edit]': 'edit'
'click [data-type=new]': 'new'
'click [data-type=import]': 'import'
'click .js-description': 'description'
constructor: ->
super
# set title
if @pageData.title
@title @pageData.title, true
# set nav bar
if @pageData.navupdate
@navupdate @pageData.navupdate
# bind render after a change is done
if !@disableRender
@subscribeId = App[ @genericObject ].subscribe(@render)
App[ @genericObject ].bind 'ajaxError', (rec, msg) =>
@log 'error', 'ajax', msg.status
if msg.status is 401
@log 'error', 'ajax', rec, msg, msg.status
@navigate 'login'
# execute fetch
@render()
# fetch all
if !@disableInitFetch && !@pageData.pagerAjax
App[ @genericObject ].fetchFull(
->
clear: true
)
show: =>
if @table
@table.show()
hide: =>
if @table
@table.hide()
release: =>
if @subscribeId
App[ @genericObject ].unsubscribe(@subscribeId)
paginate: (page) =>
return if page is @pageData.pagerSelected
@pageData.pagerSelected = page
@render()
render: =>
if @pageData.pagerAjax
sortBy = @table?.customOrderBy || @table?.orderBy || @defaultSortBy || 'id'
orderBy = @table?.customOrderDirection || @table?.orderDirection || @defaultOrder || 'ASC'
fallbackSortBy = sortBy
fallbackOrderBy = orderBy
if sortBy isnt 'id'
fallbackSortBy = "#{sortBy}, id"
fallbackOrderBy = "#{orderBy}, ASC"
@startLoading()
App[@genericObject].indexFull(
(collection, data) =>
@pageData.pagerTotalCount = data.total_count
@stopLoading()
@renderObjects(collection)
{
refresh: false
sort_by: fallbackSortBy
order_by: fallbackOrderBy
page: @pageData.pagerSelected
per_page: @pageData.pagerPerPage
}
)
return
objects = App[@genericObject].search(
sortBy: @defaultSortBy || 'name'
order: @defaultOrder
)
@renderObjects(objects)
renderObjects: (objects) =>
# remove ignored items from collection
if @ignoreObjectIDs
objects = _.filter( objects, (item) ->
return if item.id is 1
return item
)
if !@table
# show description button, only if content exists
showDescription = false
if App[ @genericObject ].description && !_.isEmpty(objects)
showDescription = true
@html App.view('generic/admin/index')(
head: @pageData.objects
notes: @pageData.notes
buttons: @pageData.buttons
menus: @pageData.menus
showDescription: showDescription
)
# show description in content if no no content exists
if _.isEmpty(objects) && App[ @genericObject ].description
description = marked(App[ @genericObject ].description)
@$('.table-overview').html(description)
return
# append content table
params = _.extend(
{
tableId: "#{@genericObject}-generic-overview"
el: @$('.table-overview')
model: App[ @genericObject ]
objects: objects
bindRow:
events:
click: @edit
container: @container
explanation: @pageData.explanation
groupBy: @groupBy
dndCallback: @dndCallback
},
@pageData.tableExtend
)
if @pageData.pagerAjax
params = _.extend(
{
pagerAjax: @pageData.pagerAjax
pagerBaseUrl: @pageData.pagerBaseUrl
pagerSelected: @pageData.pagerSelected
pagerPerPage: @pageData.pagerPerPage
pagerTotalCount: @pageData.pagerTotalCount
sortRenderCallback: @render
},
params
)
if !@table
@table = new App.ControllerTable(params)
else
@table.update(objects: objects, pagerSelected: @pageData.pagerSelected, pagerTotalCount: @pageData.pagerTotalCount)
edit: (id, e) =>
e.preventDefault()
item = App[ @genericObject ].find(id)
if @editCallback
@editCallback(item)
return
new App.ControllerGenericEdit(
id: item.id
pageData: @pageData
genericObject: @genericObject
container: @container
small: @small
large: @large
veryLarge: @veryLarge
)
new: (e) ->
e.preventDefault()
new App.ControllerGenericNew(
pageData: @pageData
genericObject: @genericObject
container: @container
small: @small
large: @large
veryLarge: @veryLarge
)
import: (e) ->
e.preventDefault()
@importCallback()
description: (e) =>
new App.ControllerGenericDescription(
description: App[ @genericObject ].description
container: @container
)

View file

@ -0,0 +1,53 @@
class App.ControllerGenericNew extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: true
headPrefix: 'New'
showTrySupport: true
content: =>
@head = @pageData.head || @pageData.object
@controller = new App.ControllerForm(
model: App[ @genericObject ]
params: @item
screen: @screen || 'edit'
autofocus: true
handlers: @handlers
)
@controller.form
onSubmit: (e) ->
params = @formParam(e.target)
object = new App[ @genericObject ]
object.load(params)
# validate form using HTML5 validity check
element = $(e.target).closest('form').get(0)
if element && element.reportValidity && !element.reportValidity()
return false
# validate
errors = object.validate()
if errors
@log 'error', errors
@formValidate( form: e.target, errors: errors )
return false
# disable form
@formDisable(e)
# save object
ui = @
object.save(
done: ->
if ui.callback
item = App[ ui.genericObject ].fullLocal(@id)
ui.callback(item)
ui.close()
fail: (settings, details) ->
ui.log 'errors', details
ui.formEnable(e)
ui.controller.showAlert(details.error_human || details.error || 'Unable to create object!')
)

View file

@ -0,0 +1,48 @@
class App.ControllerModalLoading extends App.Controller
className: 'modal fade'
showTrySupport: true
constructor: ->
super
if @container
@el.addClass('modal--local')
@render()
@el.modal(
keyboard: false
show: true
backdrop: 'static'
container: @container
).on(
'hidden.bs.modal': @localOnClosed
)
render: ->
@html App.view('generic/modal_loader')(
head: @head
message: App.i18n.translateContent(@message)
)
update: (message, translate = true) =>
if translate
message = App.i18n.translateContent(message)
@$('.js-loading').html(message)
hideIcon: =>
@$('.js-loadingIcon').addClass('hide')
showIcon: =>
@$('.js-loadingIcon').removeClass('hide')
localOnClosed: =>
@el.remove()
hide: (delay) =>
remove = =>
@el.modal('hide')
if !delay
remove()
return
App.Delay.set(remove, delay * 1000)

View file

@ -0,0 +1,135 @@
class App.ControllerNavSidbar extends App.Controller
constructor: (params) ->
super
if @authenticateRequired
@authenticateCheckRedirect()
@render(true)
@controllerBind('ui:rerender',
=>
@render(true)
@updateNavigation(true, params)
)
show: (params = {}) =>
@navupdate ''
@shown = true
if params
for key, value of params
if key isnt 'el' && key isnt 'shown' && key isnt 'match'
@[key] = value
@updateNavigation(false, params)
if @activeController && _.isFunction(@activeController.show)
@activeController.show(params)
hide: =>
@shown = false
if @activeController && _.isFunction(@activeController.hide)
@activeController.hide()
render: (force = false) =>
groups = @groupsSorted()
selectedItem = @selectedItem(groups)
@html App.view('generic/navbar_level2/index')(
className: @configKey
)
@$('.sidebar').html App.view('generic/navbar_level2/navbar')(
groups: groups
className: @configKey
selectedItem: selectedItem
)
updateNavigation: (force, params) =>
groups = @groupsSorted()
selectedItem = @selectedItem(groups)
return if !selectedItem
return if !force && @lastTarget && selectedItem.target is @lastTarget
@lastTarget = selectedItem.target
@$('.sidebar li').removeClass('active')
@$(".sidebar li a[href=\"#{selectedItem.target}\"]").parent().addClass('active')
@executeController(selectedItem, params)
groupsSorted: =>
# get accessable groups
groups = App.Config.get(@configKey)
groupsUnsorted = []
for key, item of groups
if !item.controller
if !item.permission
groupsUnsorted.push item
else
match = false
for permissionName in item.permission
if !match && @permissionCheck(permissionName)
match = true
groupsUnsorted.push item
_.sortBy(groupsUnsorted, (item) -> return item.prio)
selectedItem: (groups) =>
# get items of group
for group in groups
items = App.Config.get(@configKey)
itemsUnsorted = []
for key, item of items
if item.parent is group.target
if item.controller
if !item.permission
itemsUnsorted.push item
else
match = false
for permissionName in item.permission
if !match && @permissionCheck(permissionName)
match = true
itemsUnsorted.push item
group.items = _.sortBy(itemsUnsorted, (item) -> return item.prio)
# set active item
selectedItem = undefined
for group in groups
if group.items
for item in group.items
if item.target.match("/#{@target}$")
item.active = true
selectedItem = item
else
item.active = false
if !selectedItem
for group in groups
break if selectedItem
if group.items
for item in group.items
item.active = true
selectedItem = item
break
selectedItem
executeController: (selectedItem, params) =>
if @activeController
@activeController.el.remove()
@activeController = undefined
@$('.main').append('<div>')
@activeController = new selectedItem.controller(_.extend(params, el: @$('.main div')))
setPosition: (position) =>
return if @shown
return if !position
if position.main
@$('.main').scrollTop(position.main)
if position.sidebar
@$('.sidebar').scrollTop(position.sidebar)
currentPosition: =>
data =
main: @$('.main').scrollTop()
sidebar: @$('.sidebar').scrollTop()

View file

@ -0,0 +1,82 @@
class App.ControllerObserver extends App.Controller
model: 'Ticket'
template: 'tba'
globalRerender: true
###
observe:
title: true
observeNot:
title: true
###
constructor: ->
super
#console.trace()
@log 'debug', 'new', @object_id, @model
if App[@model].exists(@object_id)
@maybeRender(App[@model].fullLocal(@object_id))
else
App[@model].full(@object_id, @maybeRender)
# rerender, e. g. on language change
if @globalRerender
@controllerBind('ui:rerender', =>
@lastAttributres = undefined
@maybeRender(App[@model].fullLocal(@object_id))
)
subscribe: (object, typeOfChange) =>
@maybeRender(object, typeOfChange)
maybeRender: (object, typeOfChange) =>
if typeOfChange is 'remove'
@release()
@el.remove()
return
@log 'debug', 'maybeRender', @object_id, object, @model
if !@subscribeId
@subscribeId = object.subscribe(@subscribe)
# remember current attributes
currentAttributes = {}
if @observe
for key, active of @observe
if active
currentAttributes[key] = object[key]
if @observeNot
for key, value of object
if key isnt 'cid' && !@observeNot[key] && !_.isFunction(value) && !_.isObject(value)
currentAttributes[key] = value
if !@lastAttributres
@lastAttributres = {}
else
diff = difference(currentAttributes, @lastAttributres)
if _.isEmpty(diff)
@log 'debug', 'maybeRender no diff, no rerender'
return
@log 'debug', 'maybeRender.diff', diff, @observe, @model
@lastAttributres = currentAttributes
@render(object, diff)
render: (object, diff) =>
@log 'debug', 'render', @template, object, diff
@html App.view(@template)(
object: object
)
if @renderPost
@renderPost(object)
release: =>
#console.trace()
@log 'debug', 'release', @object_id, @model, @subscribeId
App[@model].unsubscribe(@subscribeId)

View file

@ -0,0 +1,20 @@
class App.ControllerObserverActionRow extends App.ControllerObserver
constructor: ->
super
render: (object) =>
return if _.isEmpty(object)
actions = @actions(object)
@html App.view('generic/actions')(
items: actions
type: @type
)
for item in actions
do (item) =>
@$("[data-type=\"#{item.name}\"]").on(
'click'
(e) ->
e.preventDefault()
item.callback(object)
)

View file

@ -0,0 +1,5 @@
class App.ControllerPermanent extends App.Controller
constructor: ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission, true)
super

View file

@ -0,0 +1,16 @@
class App.ControllerSubContent extends App.Controller
constructor: ->
if @requiredPermission
@permissionCheckRedirect(@requiredPermission)
super
show: =>
if @genericController && @genericController.show
@genericController.show()
return if !@header
@title @header, true
hide: =>
if @genericController && @genericController.hide
@genericController.hide()

View file

@ -0,0 +1,61 @@
class App.ControllerTabs extends App.Controller
events:
'click .nav-tabs [data-toggle="tab"]': 'tabRemember'
constructor: (params) ->
@originParams = params # remember params for sub-controller
super(params)
# check authentication
if @requiredPermission
if !@permissionCheckRedirect(@requiredPermission)
throw "No permission for #{@requiredPermission}"
show: =>
return if !@controllerList
for localeController in @controllerList
if localeController && localeController.show
localeController.show()
hide: =>
return if !@controllerList
for localeController in @controllerList
if localeController && localeController.hide
localeController.hide()
render: ->
@html App.view('generic/tabs')(
header: @header
subHeader: @subHeader
tabs: @tabs
addTab: @addTab
headerSwitchName: @headerSwitchName
headerSwitchChecked: @headerSwitchChecked
)
# insert content
for tab in @tabs
@$('.tab-content').append("<div class=\"tab-pane\" id=\"#{tab.target}\"></div>")
if tab.controller
params = tab.params || {}
params.name = tab.name
params.target = tab.target
params.el = @$("##{tab.target}")
@controllerList ||= []
@controllerList.push new tab.controller(_.extend(@originParams, params))
# check if tabs need to be show / cant' use .tab(), because tabs are note shown (only one tab exists)
if @tabs.length <= 1
@$('.tab-pane').addClass('active')
return
# set last or first tab to active
@lastActiveTab = @Config.get('lastTab')
if @lastActiveTab && @$(".nav-tabs li a[href='#{@lastActiveTab}']")[0]
@$(".nav-tabs li a[href='#{@lastActiveTab}']").tab('show')
else
@$('.nav-tabs li:first a').tab('show')
tabRemember: (e) =>
@lastActiveTab = $(e.target).attr('href')
@Config.set('lastTab', @lastActiveTab)

View file

@ -0,0 +1,52 @@
class App.ControllerWizardModal extends App.ControllerFullPage
className: 'modal fade'
constructor: ->
super
# rerender view, e. g. on langauge change
@controllerBind('ui:rerender', =>
@render()
'wizard'
)
goToSlide: (e) =>
e.preventDefault()
slide = $(e.target).data('slide')
return if !slide
@showSlide(slide)
showSlide: (name) =>
@hideAlert(name)
@$('.setup.wizard').addClass('hide')
@$(".setup.wizard.#{name}").removeClass('hide')
@$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus()
showAlert: (screen, message) =>
@$(".#{screen}").find('.alert').first().removeClass('hide').text(App.i18n.translatePlain(message))
hideAlert: (screen) =>
@$(".#{screen}").find('.alert').first().addClass('hide')
disable: (e) =>
@formDisable(e)
@$('.wizard-controls .btn').attr('disabled', true)
enable: (e) =>
@formEnable(e)
@$('.wizard-controls .btn').attr('disabled', false)
hide: (e) =>
e.preventDefault()
@el.modal('hide')
showInvalidField: (screen, fields) =>
@$(".#{screen}").find('.form-group').removeClass('has-error')
return if !fields
for field, type of fields
if type
@$(".#{screen}").find("[name=\"options::#{field}\"]").closest('.form-group').addClass('has-error')
render: ->
# do nothing

View file

@ -0,0 +1,10 @@
class App.ControllerWizardFullScreen extends App.ControllerWizardModal
forceRender: true
className: 'getstarted'
# login check / get session user
redirectToLogin: =>
App.Auth.loginCheck()
@el.remove()
App.Plugin.init()
@navigate '#', { removeEl: true }

View file

@ -1,4 +1,4 @@
class App.ChannelChat extends App.ControllerSubContent
class ChannelChat extends App.ControllerSubContent
requiredPermission: 'admin.channel_chat'
header: 'Chat'
events:
@ -353,7 +353,7 @@ class App.ChannelChat extends App.ControllerSubContent
@code.each (i, block) ->
hljs.highlightBlock block
App.Config.set('Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: App.ChannelChat, permission: ['admin.channel_chat'] }, 'NavBarAdmin')
App.Config.set('Chat', { prio: 4000, name: 'Chat', parent: '#channels', target: '#channels/chat', controller: ChannelChat, permission: ['admin.channel_chat'] }, 'NavBarAdmin')
class Topics extends App.Controller
events:

View file

@ -1,4 +1,4 @@
class App.ChannelEmail extends App.ControllerTabs
class ChannelEmail extends App.ControllerTabs
requiredPermission: 'admin.channel_email'
header: 'Email'
constructor: ->
@ -10,17 +10,17 @@ class App.ChannelEmail extends App.ControllerTabs
{
name: 'Accounts',
target: 'c-account',
controller: App.ChannelEmailAccountOverview,
controller: ChannelEmailAccountOverview,
},
{
name: 'Filter',
target: 'c-filter',
controller: App.ChannelEmailFilter,
controller: ChannelEmailFilter,
},
{
name: 'Signatures',
target: 'c-signature',
controller: App.ChannelEmailSignature,
controller: ChannelEmailSignature,
},
{
name: 'Settings',
@ -32,7 +32,7 @@ class App.ChannelEmail extends App.ControllerTabs
@render()
class App.ChannelEmailFilter extends App.Controller
class ChannelEmailFilter extends App.Controller
events:
'click [data-type=new]': 'new'
@ -79,7 +79,7 @@ class App.ChannelEmailFilter extends App.Controller
callback: @load
)
class App.ChannelEmailSignature extends App.Controller
class ChannelEmailSignature extends App.Controller
events:
'click [data-type=new]': 'new'
@ -111,19 +111,19 @@ Once you have created a signature here, you need also to edit the groups where y
new: (e) =>
e.preventDefault()
new App.ChannelEmailSignatureEdit(
new ChannelEmailSignatureEdit(
container: @el.closest('.content')
)
edit: (id, e) =>
e.preventDefault()
item = App.Signature.find(id)
new App.ChannelEmailSignatureEdit(
new ChannelEmailSignatureEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailSignatureEdit extends App.ControllerModal
class ChannelEmailSignatureEdit extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: true
@ -174,7 +174,7 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
@form.showAlert(details.error_human || details.error || 'Unable to create object!')
)
class App.ChannelEmailAccountOverview extends App.Controller
class ChannelEmailAccountOverview extends App.Controller
events:
'click .js-channelNew': 'wizard'
'click .js-channelDelete': 'delete'
@ -248,7 +248,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
wizard: (e) =>
e.preventDefault()
new App.ChannelEmailAccountWizard(
new ChannelEmailAccountWizard(
container: @el.closest('.content')
callback: @load
channelDriver: @channelDriver
@ -259,7 +259,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
id = $(e.target).closest('.action').data('id')
channel = App.Channel.find(id)
slide = 'js-inbound'
new App.ChannelEmailAccountWizard(
new ChannelEmailAccountWizard(
container: @el.closest('.content')
slide: slide
channel: channel
@ -272,7 +272,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
id = $(e.target).closest('.action').data('id')
channel = App.Channel.find(id)
slide = 'js-outbound'
new App.ChannelEmailAccountWizard(
new ChannelEmailAccountWizard(
container: @el.closest('.content')
slide: slide
channel: channel
@ -328,7 +328,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
e.preventDefault()
id = $(e.target).closest('.action').data('id')
item = App.Channel.find(id)
new App.ChannelEmailEdit(
new ChannelEmailEdit(
container: @el.closest('.content')
item: item
callback: @load
@ -374,7 +374,7 @@ class App.ChannelEmailAccountOverview extends App.Controller
id = $(e.target).closest('.action').data('id')
channel = App.Channel.find(id)
slide = 'js-outbound'
new App.ChannelEmailNotificationWizard(
new ChannelEmailNotificationWizard(
container: @el.closest('.content')
channel: channel
callback: @load
@ -391,7 +391,8 @@ class App.ChannelEmailAccountOverview extends App.Controller
id = $(e.target).closest('.action').data('id')
@navigate "#channels/microsoft365/#{id}"
class App.ChannelEmailEdit extends App.ControllerModal
class ChannelEmailEdit extends App.ControllerModal
buttonClose: true
buttonCancel: true
buttonSubmit: true
@ -442,7 +443,7 @@ class App.ChannelEmailEdit extends App.ControllerModal
@el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
class App.ChannelEmailAccountWizard extends App.WizardModal
class ChannelEmailAccountWizard extends App.ControllerWizardModal
elements:
'.modal-body': 'body'
events:
@ -896,7 +897,7 @@ class App.ChannelEmailAccountWizard extends App.WizardModal
e.preventDefault()
@el.modal('hide')
class App.ChannelEmailNotificationWizard extends App.WizardModal
class ChannelEmailNotificationWizard extends App.ControllerWizardModal
elements:
'.modal-body': 'body'
events:
@ -1030,4 +1031,4 @@ class App.ChannelEmailNotificationWizard extends App.WizardModal
@enable(e)
)
App.Config.set('Email', { prio: 3000, name: 'Email', parent: '#channels', target: '#channels/email', controller: App.ChannelEmail, permission: ['admin.channel_email'] }, 'NavBarAdmin')
App.Config.set('Email', { prio: 3000, name: 'Email', parent: '#channels', target: '#channels/email', controller: ChannelEmail, permission: ['admin.channel_email'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ChannelFacebook extends App.ControllerSubContent
requiredPermission: 'admin.channel_facebook'
header: 'Facebook'
events:
@ -254,4 +254,4 @@ class AccountEdit extends App.ControllerModal
@el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
App.Config.set('Facebook', { prio: 5100, name: 'Facebook', parent: '#channels', target: '#channels/facebook', controller: Index, permission: ['admin.channel_facebook'] }, 'NavBarAdmin')
App.Config.set('Facebook', { prio: 5100, name: 'Facebook', parent: '#channels', target: '#channels/facebook', controller: ChannelFacebook, permission: ['admin.channel_facebook'] }, 'NavBarAdmin')

View file

@ -1,5 +1,5 @@
# coffeelint: disable=no_unnecessary_double_quotes
class App.ChannelForm extends App.ControllerSubContent
class ChannelForm extends App.ControllerSubContent
requiredPermission: 'admin.channel_formular'
header: 'Form'
events:
@ -88,4 +88,4 @@ class App.ChannelForm extends App.ControllerSubContent
value = @paramsSetting.find('[name=group_id]').val()
App.Setting.set('form_ticket_create_group_id', value)
App.Config.set('Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, permission: ['admin.formular'] }, 'NavBarAdmin')
App.Config.set('Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: ChannelForm, permission: ['admin.formular'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class App.ChannelSms extends App.ControllerTabs
class ChannelSms extends App.ControllerTabs
requiredPermission: 'admin.channel_sms'
header: 'SMS'
constructor: ->
@ -9,13 +9,13 @@ class App.ChannelSms extends App.ControllerTabs
{
name: 'Accounts',
target: 'c-account',
controller: App.ChannelSmsAccountOverview,
controller: ChannelSmsAccountOverview,
},
]
@render()
class App.ChannelSmsAccountOverview extends App.Controller
class ChannelSmsAccountOverview extends App.Controller
events:
'click .js-channelEdit': 'change'
'click .js-channelDelete': 'delete'
@ -76,7 +76,7 @@ class App.ChannelSmsAccountOverview extends App.Controller
channel = new App.Channel(active: true)
else
channel = App.Channel.find(id)
new App.ChannelSmsAccount(
new ChannelSmsAccount(
container: @el.closest('.content')
channel: channel
callback: @load
@ -127,7 +127,7 @@ class App.ChannelSmsAccountOverview extends App.Controller
e.preventDefault()
id = $(e.target).closest('.action').data('id')
channel = App.Channel.find(id)
new App.ChannelSmsNotification(
new ChannelSmsNotification(
container: @el.closest('.content')
channel: channel
callback: @load
@ -135,7 +135,7 @@ class App.ChannelSmsAccountOverview extends App.Controller
config: @config
)
class App.ChannelSmsAccount extends App.ControllerModal
class ChannelSmsAccount extends App.ControllerModal
head: 'SMS Account'
buttonCancel: true
centerButtons: [
@ -272,7 +272,7 @@ class App.ChannelSmsAccount extends App.ControllerModal
container: @el.closest('.content')
)
class App.ChannelSmsNotification extends App.ControllerModal
class ChannelSmsNotification extends App.ControllerModal
head: 'SMS Notification'
buttonCancel: true
centerButtons: [
@ -439,4 +439,4 @@ class TestModal extends App.ControllerModal
.removeClass('hide')
)
App.Config.set('SMS', { prio: 3100, name: 'SMS', parent: '#channels', target: '#channels/sms', controller: App.ChannelSms, permission: ['admin.channel_sms'] }, 'NavBarAdmin')
App.Config.set('SMS', { prio: 3100, name: 'SMS', parent: '#channels', target: '#channels/sms', controller: ChannelSms, permission: ['admin.channel_sms'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ChannelTelegram extends App.ControllerSubContent
requiredPermission: 'admin.channel_telegram'
events:
'click .js-new': 'new'
@ -201,4 +201,4 @@ class BotEdit extends App.ControllerModal
@el.find('.alert').removeClass('hidden').text(error_message)
)
App.Config.set('Telegram', { prio: 5100, name: 'Telegram', parent: '#channels', target: '#channels/telegram', controller: Index, permission: ['admin.channel_telegram'] }, 'NavBarAdmin')
App.Config.set('Telegram', { prio: 5100, name: 'Telegram', parent: '#channels', target: '#channels/telegram', controller: ChannelTelegram, permission: ['admin.channel_telegram'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ChannelTwitter extends App.ControllerSubContent
requiredPermission: 'admin.channel_twitter'
events:
'click .js-new': 'new'
@ -299,4 +299,4 @@ class AccountEdit extends App.ControllerModal
@el.find('.alert').removeClass('hidden').text(data.error || 'Unable to save changes.')
)
App.Config.set('Twitter', { prio: 5000, name: 'Twitter', parent: '#channels', target: '#channels/twitter', controller: Index, permission: ['admin.channel_twitter'] }, 'NavBarAdmin')
App.Config.set('Twitter', { prio: 5000, name: 'Twitter', parent: '#channels', target: '#channels/twitter', controller: ChannelTwitter, permission: ['admin.channel_twitter'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class App.ChannelWeb extends App.ControllerTabs
class ChannelWeb extends App.ControllerTabs
requiredPermission: 'admin.channel_web'
header: 'Web'
constructor: ->
@ -16,4 +16,4 @@ class App.ChannelWeb extends App.ControllerTabs
@render()
App.Config.set('Web', { prio: 1000, name: 'Web', parent: '#channels', target: '#channels/web', controller: App.ChannelWeb, permission: ['admin.channel_web'] }, 'NavBarAdmin')
App.Config.set('Web', { prio: 1000, name: 'Web', parent: '#channels', target: '#channels/web', controller: ChannelWeb, permission: ['admin.channel_web'] }, 'NavBarAdmin')

View file

@ -13,7 +13,7 @@ class App.DashboardActivityStream extends App.CollectionController
@fetch()
# bind to rebuild view event
@bind('activity_stream_rebuild', @load)
@controllerBind('activity_stream_rebuild', @load)
fetch: =>

View file

@ -54,8 +54,9 @@ class App.FirstStepsClues extends App.Controller
constructor: (params) ->
$('#app').append('<div class="js-modal--clue"></div>')
params.el = $('#app .js-modal--clue')
el = $('<div class="js-modal--clue"></div>')
params.appEl.append(el)
params.el = el
super params
@ -69,7 +70,7 @@ class App.FirstStepsClues extends App.Controller
@position = 0
@render()
@bind('ui:rerender', =>
@controllerBind('ui:rerender', =>
@render()
'clues'
)
@ -106,7 +107,7 @@ class App.FirstStepsClues extends App.Controller
cleanUp: (callback) ->
@hideWindow =>
clue = @clues[@position]
container = $("#app #{clue.container}")
container = @appEl.find(clue.container)
container.removeClass('selected-clue')
# undo click perform by doing it again
@ -128,7 +129,7 @@ class App.FirstStepsClues extends App.Controller
showClue: =>
clue = @clues[@position]
container = $("#app #{clue.container}")
container = @appEl.find(clue.container)
container.addClass('selected-clue')
if clue.actions
@ -324,7 +325,7 @@ class App.FirstStepsClues extends App.Controller
when 'hover'
# disable active navbar elements
$('#app .navigation .is-active').removeClass('is-active')
@appEl.find('.navigation .is-active').removeClass('is-active')
if type is 'show'
target.addClass('is-hovered')

View file

@ -1,10 +1,10 @@
class App.DashboardStats extends App.Controller
constructor: ->
super
@load()
@bind('dashboard_stats_rebuild', @load)
@setupStatsWidgets()
@controllerBind('dashboard_stats_rebuild', @setupStatsWidgets)
load: =>
setupStatsWidgets: =>
@setupStatsWidget('Stats', 'stats', @el)
setupStatsWidget: (config, event, el) ->
@ -20,11 +20,11 @@ class App.DashboardStats extends App.Controller
if @permissionCheck(widget.permission)
try
new widget.controller(
el: el
parentEl: @el
className: widget.className
)
@$('.js-stat-help').tooltip()
catch e
@log 'error', "statsWidgets #{widget}:", e
App.Event.trigger(event + ':ready')

View file

@ -0,0 +1,17 @@
class App.ControllerDashboardStatsBase extends App.Controller
constructor: (params) ->
if params.parentEl
el = params.parentEl.find(".column.#{params.className}")
if !el.get(0)
el = $("<div class=\"column #{params.className}\"></div>")
params.parentEl.append(el)
params.el = el
super(params)
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()

View file

@ -1,15 +1,4 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketChannelDistribution
data.StatsTicketChannelDistribution =
@ -44,4 +33,4 @@ class Stats extends App.Controller
else
@el.append(content)
App.Config.set('ticket_channel_distribution', {controller: Stats, permission: 'ticket.agent', prio: 300 }, 'Stats')
App.Config.set('ticket_channel_distribution', { controller: Stats, permission: 'ticket.agent', prio: 300, className: 'ticket_channel_distribution' }, 'Stats')

View file

@ -1,15 +1,4 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketEscalation
data.StatsTicketEscalation =
@ -26,4 +15,4 @@ class Stats extends App.Controller
else
@el.append(content)
App.Config.set('ticket_escalation', {controller: Stats, permission: 'ticket.agent', prio: 200 }, 'Stats')
App.Config.set('ticket_escalation', { controller: Stats, permission: 'ticket.agent', prio: 200, className: 'ticket_escalation' }, 'Stats')

View file

@ -1,20 +1,10 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketInProcess
data.StatsTicketInProcess =
state: 'supergood'
percent: 0
in_process: 0
average_per_agent: 0
data.StatsTicketInProcess.description = 'What percentage of your tickets have you responded to, updated, or modified in some way today?'
@ -26,5 +16,4 @@ class Stats extends App.Controller
else
@el.append(content)
App.Config.set('ticket_in_process', {controller: Stats, permission: 'ticket.agent', prio: 500 }, 'Stats')
App.Config.set('ticket_in_process', { controller: Stats, permission: 'ticket.agent', prio: 500, className: 'ticket_in_process' }, 'Stats')

View file

@ -1,15 +1,4 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketLoadMeasure
data.StatsTicketLoadMeasure =
@ -28,4 +17,4 @@ class Stats extends App.Controller
else
@el.append(content)
App.Config.set('ticket_load_measure', {controller: Stats, permission: 'ticket.agent', prio: 400 }, 'Stats')
App.Config.set('ticket_load_measure', { controller: Stats, permission: 'ticket.agent', prio: 400, className: 'ticket_load_measure' }, 'Stats')

View file

@ -1,15 +1,4 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketReopen
data.StatsTicketReopen =
@ -25,4 +14,4 @@ class Stats extends App.Controller
else
@el.append(content)
App.Config.set('ticket_reopen', {controller: Stats, permission: 'ticket.agent', prio: 600 }, 'Stats')
App.Config.set('ticket_reopen', { controller: Stats, permission: 'ticket.agent', prio: 600, className: 'ticket_reopen' }, 'Stats')

View file

@ -1,15 +1,4 @@
class Stats extends App.Controller
constructor: ->
super
@load()
load: =>
stats_store = App.StatsStore.first()
if stats_store
@render(stats_store.data)
else
@render()
class Stats extends App.ControllerDashboardStatsBase
render: (data = {}) ->
if !data.StatsTicketWaitingTime
data.StatsTicketWaitingTime =
@ -75,4 +64,4 @@ class Stats extends App.Controller
ctx.closePath()
ctx.fill()
App.Config.set('ticket_waiting_time', {controller: Stats, permission: 'ticket.agent', prio: 100 }, 'Stats')
App.Config.set('ticket_waiting_time', { controller: Stats, permission: 'ticket.agent', prio: 100, className: 'ticket_waiting_time' }, 'Stats')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class CheckMk extends App.ControllerIntegrationBase
featureIntegration: 'check_mk_integration'
featureName: 'Checkmk'
featureConfig: 'check_mk_config'
@ -55,7 +55,7 @@ App.Config.set(
name: 'Checkmk'
target: '#system/integration/check_mk'
description: 'An open source monitoring tool.'
controller: Index
controller: CheckMk
state: State
permission: ['admin.integration.check_mk']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Clearbit extends App.ControllerIntegrationBase
featureIntegration: 'clearbit_integration'
featureName: 'Clearbit'
featureConfig: 'clearbit_config'
@ -155,7 +155,7 @@ App.Config.set(
name: 'Clearbit'
target: '#system/integration/clearbit'
description: 'A powerful service to get more information about your customers.'
controller: Index
controller: Clearbit
state: State
permission: ['admin.integration.clearbit']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Cti extends App.ControllerIntegrationBase
featureIntegration: 'cti_integration'
featureName: 'CTI (generic)'
featureConfig: 'cti_config'
@ -234,7 +234,7 @@ App.Config.set(
name: 'CTI (generic)'
target: '#system/integration/cti'
description: 'Generic API to integrate VoIP service provider with realtime push.'
controller: Index
controller: Cti
state: State
}
'NavBarIntegrations'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Exchange extends App.ControllerIntegrationBase
featureIntegration: 'exchange_integration'
featureName: 'Exchange'
featureConfig: 'exchange_config'
@ -166,7 +166,7 @@ class State
@current: ->
App.Setting.get('exchange_integration')
class ConnectionWizard extends App.WizardModal
class ConnectionWizard extends App.ControllerWizardModal
wizardConfig: {}
slideMethod:
'js-folders': 'foldersShow'
@ -557,7 +557,7 @@ App.Config.set(
name: 'Exchange'
target: '#system/integration/exchange'
description: 'Exchange integration for contacts management.'
controller: Index
controller: Exchange
state: State
permission: ['admin.integration.exchange']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Icinga extends App.ControllerIntegrationBase
featureIntegration: 'icinga_integration'
featureName: 'Icinga'
featureConfig: 'icinga_config'
@ -24,7 +24,7 @@ App.Config.set(
name: 'Icinga'
target: '#system/integration/icinga'
description: 'An open source monitoring tool.'
controller: Index
controller: Icinga
state: State
permission: ['admin.integration.icinga']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Idoit extends App.ControllerIntegrationBase
featureIntegration: 'idoit_integration'
featureName: 'i-doit'
featureConfig: 'idoit_config'
@ -58,7 +58,7 @@ class Form extends App.Controller
)
success: (data, status, xhr) =>
if data.result is 'failed'
new App.ControllerErrorModal(
new App.ErrorModal(
message: data.message
container: @el.closest('.content')
)
@ -87,7 +87,7 @@ App.Config.set(
name: 'i-doit'
target: '#system/integration/idoit'
description: 'CMDB to document complex relations of your network components.'
controller: Index
controller: Idoit
state: State
}
'NavBarIntegrations'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Ldap extends App.ControllerIntegrationBase
featureIntegration: 'ldap_integration'
featureName: 'LDAP'
featureConfig: 'ldap_config'
@ -71,6 +71,7 @@ class Form extends App.Controller
group_role_map = {}
for source, dests of @config.group_role_map
group_role_map[source] = dests.map((dest) ->
return '?' if !App.Role.exists(dest)
App.Role.find(dest).displayName()
).join ', '
@ -135,8 +136,9 @@ class Form extends App.Controller
if !job.result.roles
job.result.roles = {}
for role_id, statistic of job.result.role_ids
role = App.Role.find(role_id)
job.result.roles[role.displayName()] = statistic
if App.Role.exists(role_id)
role = App.Role.find(role_id)
job.result.roles[role.displayName()] = statistic
el = $(App.view('integration/ldap_last_import')(job: job))
@lastImport.html(el)
@ -167,9 +169,7 @@ class State
@current: ->
App.Setting.get('ldap_integration')
class ConnectionWizard extends App.WizardModal
className: 'modal fade modal--large'
class ConnectionWizard extends App.ControllerWizardModal
wizardConfig: {}
slideMethod:
'js-bind': 'bindShow'
@ -562,8 +562,9 @@ class ConnectionWizard extends App.WizardModal
if !job.result.roles
job.result.roles = {}
for role_id, statistic of job.result.role_ids
role = App.Role.find(role_id)
job.result.roles[role.displayName()] = statistic
if App.Role.find(role_id)
role = App.Role.find(role_id)
job.result.roles[role.displayName()] = statistic
@showSlide('js-try')
el = $(App.view('integration/ldap_summary')(job: job))
@el.find('.js-summary').html(el)
@ -574,7 +575,7 @@ App.Config.set(
name: 'LDAP'
target: '#system/integration/ldap'
description: 'LDAP integration for user management.'
controller: Index
controller: Ldap
state: State
permission: ['admin.integration.ldap']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Monit extends App.ControllerIntegrationBase
featureIntegration: 'monit_integration'
featureName: 'Monit'
featureConfig: 'monit_config'
@ -24,7 +24,7 @@ App.Config.set(
name: 'Monit'
target: '#system/integration/monit'
description: 'An open source monitoring tool.'
controller: Index
controller: Monit
state: State
permission: ['admin.integration.monit']
}

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Nagios extends App.ControllerIntegrationBase
featureIntegration: 'nagios_integration'
featureName: 'Nagios'
featureConfig: 'nagios_config'
@ -24,7 +24,7 @@ App.Config.set(
name: 'Nagios'
target: '#system/integration/nagios'
description: 'An open source monitoring tool.'
controller: Index
controller: Nagios
state: State
}
'NavBarIntegrations'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Placetel extends App.ControllerIntegrationBase
featureIntegration: 'placetel_integration'
featureName: 'Placetel'
featureConfig: 'placetel_config'
@ -188,7 +188,7 @@ App.Config.set(
name: 'Placetel'
target: '#system/integration/placetel'
description: 'VoIP service provider with realtime push.'
controller: Index
controller: Placetel
state: State
}
'NavBarIntegrations'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class SipgateIo extends App.ControllerIntegrationBase
featureIntegration: 'sipgate_integration'
featureName: 'sipgate.io'
featureConfig: 'sipgate_config'
@ -188,7 +188,7 @@ App.Config.set(
name: 'sipgate.io'
target: '#system/integration/sipgate'
description: 'VoIP service provider with realtime push.'
controller: Index
controller: SipgateIo
state: State
}
'NavBarIntegrations'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerIntegrationBase
class Slack extends App.ControllerIntegrationBase
featureIntegration: 'slack_integration'
featureName: 'Slack'
featureConfig: 'slack_config'
@ -85,7 +85,7 @@ App.Config.set(
target: '#system/integration/slack'
description: 'A team communication tool for the 21st century. Compatible with tools like %s.'
descriptionSubstitute: 'Mattermost, RocketChat'
controller: Index
controller: Slack
state: State
}
'NavBarIntegrations'

View file

@ -99,11 +99,9 @@ class Certificate extends App.ControllerModal
cache: false
data: params
success: (data, status, xhr) =>
console.log('success')
@close()
@callback()
error: (data) =>
console.log('error')
@close()
details = data.responseJSON || {}
@notify

View file

@ -1,7 +1,5 @@
class Widget extends App.Controller
class AppConfigUpdate
constructor: ->
super
App.Event.bind(
'config_update'
(data) ->
@ -9,4 +7,7 @@ class Widget extends App.Controller
App.Event.trigger('config_update_local', data)
)
App.Config.set('app_config_update', Widget, 'Widgets')
release: ->
App.Event.unbind('config_update')
App.Config.set('app_config_update', AppConfigUpdate, 'Plugins')

View file

@ -16,8 +16,8 @@ class DefaultLocale extends App.Controller
processData: true
)
App.Event.bind('auth:login', (session) =>
@controllerBind('auth:login', (session) =>
@delay(check, 3500, 'default_locale')
)
App.Config.set('default_locale', DefaultLocale, 'Widgets')
App.Config.set('default_locale', DefaultLocale, 'Plugins')

View file

@ -26,11 +26,11 @@ class DefaultTimezone extends App.Controller
# processData: true
#)
App.Event.bind('auth:login', (session) =>
@controllerBind('auth:login', (session) =>
@delay(check, 8500, 'default_timezone')
)
updateSetting: (timezone) ->
App.Setting.set('timezone_default', timezone)
App.Config.set('default_timezone', DefaultTimezone, 'Widgets')
App.Config.set('default_timezone', DefaultTimezone, 'Plugins')

View file

@ -1,4 +1,4 @@
class Widget
class DevBanner
constructor: ->
return if !App.Config.get('developer_mode')
return if App.Log.config('banner') is false
@ -17,4 +17,4 @@ class Widget
"""
console.log(banner)
App.Config.set('dev_banner', Widget, 'Widgets')
App.Config.set('dev_banner', DevBanner, 'Plugins')

View file

@ -1,18 +1,19 @@
class Widget
class ElectronEvents extends App.Controller
constructor: ->
return if !window.require
electron = window.require('electron')
return if !electron
remote = electron.remote
ipc = electron.ipcRenderer
super
App.Event.bind('window-title-set', (arg) ->
@controllerBind('window-title-set', (arg) ->
ipc.send('window-title-set', arg)
)
App.Event.bind('online_notification_counter', (e) ->
@controllerBind('online_notification_counter', (e) ->
setBadge(e)
)
ipc.on('global-shortcut', (e, arg) ->
ipc.off('global-shortcut').on('global-shortcut', (e, arg) ->
App.Event.trigger('global-shortcut', arg)
)
@ -91,4 +92,4 @@ class Widget
else if process.platform is 'darwin'
setBadgeOSX(content)
App.Config.set('aaa_electron_events', Widget, 'Navigations')
App.Config.set('aaa_electron_events', ElectronEvents, 'Plugins')

View file

@ -1,12 +1,13 @@
class App.GlobalSearchWidget extends Spine.Module
class App.GlobalSearchWidget extends App.Controller
shiftHeld = false
constructor: ->
$('body').on('mousedown', (e) =>
super
$('body').off('mousedown.globalsearch').on('mousedown.globalsearch', (e) =>
@shiftHeldToogle(e)
true
)
App.Event.bind('global:search:set', (data) =>
@controllerBind('global:search:set', (data) =>
item = data[0]
attribute = data[1]
item = item.replace('"', '')
@ -36,4 +37,4 @@ class App.GlobalSearchWidget extends Spine.Module
@search: (item, attribute) ->
App.Event.trigger('global:search:set', [item, attribute])
App.Config.set('global_navigation', App.GlobalSearchWidget, 'Widgets')
App.Config.set('global_navigation', App.GlobalSearchWidget, 'Plugins')

View file

@ -1,4 +1,4 @@
class Widget
class HelloBanner
constructor: ->
return if App.Config.get('developer_mode')
banner = """
@ -17,4 +17,4 @@ class Widget
"""
console.log(banner, 'text-decoration: underline;', 'text-decoration: none;')
App.Config.set('hello_banner', Widget, 'Widgets')
App.Config.set('hello_banner', HelloBanner, 'Plugins')

View file

@ -8,7 +8,7 @@ class App.KeyboardShortcutModal extends App.ControllerModal
constructor: ->
super
@bind('keyboard_shortcuts_close', @close)
@controllerBind('keyboard_shortcuts_close', @close)
content: ->
App.view('keyboard_shortcuts')(
@ -25,10 +25,11 @@ class App.KeyboardShortcutModal extends App.ControllerModal
return if window.location.hash isnt '#keyboard_shortcuts'
window.history.back()
class App.KeyboardShortcutWidget extends Spine.Module
class App.KeyboardShortcutWidget extends App.Controller
@include App.LogInclude
constructor: ->
super
@observerKeys()
@lastKey = undefined
@ -38,6 +39,7 @@ class App.KeyboardShortcutWidget extends Spine.Module
)
observerKeys: =>
$(document).unbind('keydown.shortcuts')
navigationHotkeys = App.Browser.hotkeys()
areas = App.Config.get('keyboard_shortcuts')
@ -54,7 +56,7 @@ class App.KeyboardShortcutWidget extends Spine.Module
modifier += shortcut.key
if shortcut.callback
@log 'debug', 'bind for', modifier
$(document).bind('keydown', modifier, (e) =>
$(document).bind('keydown.shortcuts', modifier, (e) =>
e.preventDefault()
if @lastKey && @lastKey.modifier is modifier && @lastKey.time + 5500 > new Date().getTime()
@lastKey.count += 1
@ -67,7 +69,7 @@ class App.KeyboardShortcutWidget extends Spine.Module
shortcut.callback(shortcut, @lastKey, modifier)
)
App.Event.bind('global-shortcut', (e) ->
@controllerBind('global-shortcut', (e) ->
for area in areas
for item in area.content
for shortcut in item.shortcuts
@ -75,7 +77,7 @@ class App.KeyboardShortcutWidget extends Spine.Module
shortcut.callback(shortcut)
)
App.Config.set('keyboard_shortcuts', App.KeyboardShortcutWidget, 'Widgets')
App.Config.set('keyboard_shortcuts', App.KeyboardShortcutWidget, 'Plugins')
App.Config.set(
'keyboard_shortcuts',
[

View file

@ -1,9 +1,8 @@
class Widget extends App.Controller
class Maintenance extends App.Controller
serverRestarted: false
constructor: ->
super
App.Event.bind(
@controllerBind(
'maintenance'
(data) =>
if data.type is 'message'
@ -18,7 +17,6 @@ class Widget extends App.Controller
@maintanaceRestartAuto(data)
if data.type is 'restart_manual'
@maintanaceRestartManual(data)
'maintenance'
)
showMessage: (message = {}) =>
@ -129,4 +127,4 @@ class Widget extends App.Controller
timeout ?= 1000
@delay(delay, timeout)
App.Config.set('maintenance', Widget, 'Widgets')
App.Config.set('maintenance', Maintenance, 'Plugins')

View file

@ -1,9 +1,7 @@
class App.Navigation extends App.ControllerWidgetPermanent
class Navigation extends App.Controller
@extend App.PopoverProvidable
@registerAllPopovers()
className: 'navigation vertical'
elements:
'#global-search': 'searchInput'
'.search': 'searchContainer'
@ -33,36 +31,37 @@ class App.Navigation extends App.ControllerWidgetPermanent
)
# rerender view, e. g. on langauge change
@bind 'ui:rerender', =>
@controllerBind('ui:rerender', =>
@renderMenu()
@renderPersonal()
)
# rerender menu
@bind 'menu:render', =>
@controllerBind('menu:render', =>
@renderMenu()
)
# rerender menu
@bind 'personal:render', =>
@controllerBind('personal:render', =>
@renderPersonal()
)
# update selected item
@bind 'navupdate', (params) =>
@controllerBind('navupdate', (params) =>
@update(params)
# rebuild nav bar with given user data
@bind 'auth', (user) =>
@render()
)
# fetch new recent viewed after collection change
@bind 'RecentView::changed', =>
@controllerBind('RecentView::changed', =>
@delay(
=> @fetchRecentView()
1000
'recent-view-changed'
)
)
# bell on / bell off
@bind 'bell', (data) =>
@controllerBind('bell', (data) =>
if data is 'on'
@$('.bell').addClass('show')
App.Audio.play( 'https://www.sounddogs.com/previews/2193/mp3/219024_SOUNDDOGS__be.mp3' )
@ -72,6 +71,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
)
else
@$('.bell').removeClass('show')
)
release: =>
if @notificationWidget
@ -187,13 +187,27 @@ class App.Navigation extends App.ControllerWidgetPermanent
@renderPopovers()
render: ->
user = App.Session.get()
@html App.view('navigation')(
user: user
)
if _.isEmpty(user)
@appEl.find('#navigation').remove()
return
@taskbar = new App.TaskbarWidget( el: @$('.tasks') )
navigation = $(App.view('navigation')(
user: user
))
@taskbar = new App.TaskbarWidget(el: navigation.find('.tasks'))
@el = navigation
if !@appEl.find('#navigation').get(0)
@appEl.prepend(navigation)
@delegateEvents(@events)
@refreshElements()
@el.on('remove', @releaseController)
@el.on('remove', @release)
else
@el = @appEl.find('#navigation')
@html(navigation)
# renderMenu
@renderMenu()
@ -204,7 +218,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
if @notificationWidget
@notificationWidget.remove()
@notificationWidget = new App.OnlineNotificationWidget()
$('#app').append @notificationWidget.el
@appEl.append @notificationWidget.el
searchFocus: (e) =>
@clearDelay('emptyAndCloseDelayed')
@ -490,6 +504,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
App.RecentView.fetchFull(load, clear: true)
toggleNotifications: (e) ->
console.log('toggleNotifications', @notificationWidget)
e.stopPropagation()
@notificationWidget.toggle()
@ -503,4 +518,4 @@ class App.Navigation extends App.ControllerWidgetPermanent
return
@navigate('#search')
App.Config.set('navigation', App.Navigation, 'Navigations')
App.Config.set('aaa_navigation', Navigation, 'Plugins')

View file

@ -1,4 +1,4 @@
class App.Notify extends App.ControllerWidgetPermanent
class App.Notify extends App.Controller
desktopNotify: {}
desktopNotifyCounter: 0
@ -8,14 +8,14 @@ class App.Notify extends App.ControllerWidgetPermanent
constructor: ->
super
@bind 'notify', (data) =>
@controllerBind('notify', (data) =>
@render(data)
@bind 'notify:removeall', =>
)
@controllerBind('notify:removeall', =>
@log 'notify:removeall', @
@destroyAll()
@bind 'notifyDesktop', (data) =>
)
@controllerBind('notifyDesktop', (data) =>
return if !window.Notification
if !data['icon']
@ -48,13 +48,14 @@ class App.Notify extends App.ControllerWidgetPermanent
-> notification.close()
data.timeout || timeout
)
)
# request desktop notification after login
@bind 'auth', (data) ->
@controllerBind('auth', (data) ->
if !_.isEmpty(data)
return if !window.Notification
window.Notification.requestPermission()
)
$(window).focus(
=>
for counter, notification of @desktopNotify
@ -63,6 +64,9 @@ class App.Notify extends App.ControllerWidgetPermanent
render: (data) ->
if !$('#notify').get(0)
$('body').append('<div id="notify"></div>')
# map noty naming
if data['type'] is 'info'
data['type'] = 'information'
@ -73,7 +77,8 @@ class App.Notify extends App.ControllerWidgetPermanent
$.noty.closeAll()
if data.link
data.msg = '<a href="' + data.link + '">' + data.msg + '</a>'
$('#notify').noty
$('#notify').noty(
text: data.msg
type: data.type
template: App.view('notify')
@ -83,6 +88,7 @@ class App.Notify extends App.ControllerWidgetPermanent
close: 'animated fadeOutDown'
timeout: data.timeout || 3800
closeWith: ['click']
)
destroy: (e) ->
e.preventDefault()
@ -90,4 +96,4 @@ class App.Notify extends App.ControllerWidgetPermanent
destroyAll: ->
$.noty.closeAll()
App.Config.set('notify', App.Notify, 'Widgets')
App.Config.set('notify', App.Notify, 'Plugins')

View file

@ -1,4 +1,4 @@
class Widget extends App.Controller
class RemoteTask extends App.Controller
serverRestarted: false
constructor: ->
super
@ -12,4 +12,4 @@ class Widget extends App.Controller
'remote_task'
)
App.Config.set('remote_task', Widget, 'Widgets')
App.Config.set('remote_task', RemoteTask, 'Plugins')

View file

@ -1,12 +1,9 @@
class Widget extends App.Controller
class SessionTakeOver extends App.Controller
constructor: ->
super
@bind()
bind: ->
# only do takeover check after spool messages are finished
App.Event.bind(
@controllerBind(
'spool:sent'
=>
@spoolSent = true
@ -22,11 +19,10 @@ class Widget extends App.Controller
data:
taskbar_id: App.TaskManager.TaskbarId()
)
'maintenance'
)
# session take over message
App.Event.bind(
@controllerBind(
'session:takeover'
(data) =>
@ -48,7 +44,6 @@ class Widget extends App.Controller
forceReload: true
)
@disconnectClient()
'maintenance'
)
App.Config.set('session_taken_over', Widget, 'Widgets')
App.Config.set('session_taken_over', SessionTakeOver, 'Plugins')

View file

@ -1,20 +1,24 @@
class Widget extends App.ControllerWidgetOnDemand
class SwitchBackToUser extends App.Controller
className: 'switchBackToUser'
constructor: ->
super
# start widget
@bind 'app:ready', =>
@controllerBind('app:ready', =>
@render()
)
# e.g. if language has changed
@bind 'ui:rerender', =>
@controllerBind('ui:rerender', =>
@render()
)
# remove widget
@bind 'auth:logout', =>
@controllerBind('auth:logout', =>
App.Config.set('switch_back_to_possible', false)
@render()
)
render: (user) ->
@ -47,4 +51,14 @@ class Widget extends App.ControllerWidgetOnDemand
800
)
App.Config.set('switch_back_to_user', Widget, 'Widgets')
element: =>
$("##{@key}")
html: (raw) =>
# check if parent exists
if !$("##{@key}").get(0)
$('#app').before("<div id=\"#{@key}\" class=\"#{@className}\"></div>")
$("##{@key}").html raw
App.Config.set('switch_back_to_user', SwitchBackToUser, 'Plugins')

View file

@ -1,9 +1,9 @@
class Widget extends App.Controller
class TranslationInline extends App.Controller
constructor: ->
super
@rebind()
App.Event.bind('auth', => @rebind())
App.Event.bind('i18n:inline_translation', => @toogle())
@controllerBind('auth', => @rebind())
@controllerBind('i18n:inline_translation', => @toogle())
rebind: =>
$(document).off('keydown.translation')
@ -130,4 +130,4 @@ class Widget extends App.Controller
# rerender controllers
App.Event.trigger('ui:rerender')
App.Config.set('translation_inline', Widget, 'Widgets')
App.Config.set('translation_inline', TranslationInline, 'Plugins')

View file

@ -24,13 +24,14 @@ class TranslationSupport extends App.Controller
# show message
new Modal(percent: percent)
@bind 'i18n:language:change', =>
@controllerBind('i18n:language:change', =>
@delay(check, 2500, 'translation_support')
@bind 'auth:login', =>
)
@controllerBind('auth:login', =>
@delay(check, 2500, 'translation_support')
)
App.Config.set( 'translaton_support', TranslationSupport, 'Widgets' )
App.Config.set( 'translaton_support', TranslationSupport, 'Plugins' )
class Modal extends App.ControllerModal
buttonClose: true

View file

@ -0,0 +1,81 @@
class UserSignupCheck extends App.Controller
constructor: ->
super
# for browser test
@controllerBind('user_signup_verify', (user) ->
new Modal(user: user)
)
@controllerBind('auth:login', (user) =>
return if !user
@verifyLater(user.id)
)
user = App.User.current()
@verifyLater(user.id) if user?
verifyLater: (userId) =>
delay = =>
@verify(userId)
@delay(delay, 5000, 'user_signup_verify_dialog')
verify: (userId) ->
return if !userId
return if !App.User.exists(userId)
user = App.User.find(userId)
return if user.source isnt 'signup'
return if user.verified is true
currentTime = new Date().getTime()
createdAt = Date.parse(user.created_at)
diff = currentTime - createdAt
max = 1000 * 60 * 30 # show message if account is older then 30 minutes
return if diff < max
new Modal(user: user)
class Modal extends App.ControllerModal
backdrop: false
keyboard: false
head: 'Account not verified'
small: true
buttonClose: false
buttonCancel: false
buttonSubmit: 'Resend verification email'
constructor: ->
super
content: =>
if !@sent
return App.i18n.translateContent('Your account has not been verified. Please click the link in the verification email.')
content = App.i18n.translateContent('We\'ve sent an email to _%s_. Click the link in the email to verify your account.', @user.email)
content += '<br><br>'
content += App.i18n.translateContent('If you don\'t see the email, check other places it might be, like your junk, spam, social, or other folders.')
content
onSubmit: =>
@ajax(
id: 'email_verify_send'
type: 'POST'
url: @apiPath + '/users/email_verify_send'
data: JSON.stringify(email: @user.email)
processData: true
success: @success
error: @error
)
success: (data) =>
@sent = true
@update()
# if in developer mode, redirect to verify
if data.token && @Config.get('developer_mode') is true
redirect = =>
@close()
@navigate "#email_verify/#{data.token}"
App.Delay.set(redirect, 4000)
error: =>
@contentInline = App.i18n.translateContent('Unable to send verify email.')
@update()
App.Config.set('user_signup', UserSignupCheck, 'Plugins')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileAvatar extends App.ControllerSubContent
requiredPermission: 'user_preferences.avatar'
header: 'Avatar'
elements:
@ -145,7 +145,7 @@ class Index extends App.ControllerSubContent
reader.readAsDataURL(@)
App.Config.set('Avatar', { prio: 1100, name: 'Avatar', parent: '#profile', target: '#profile/avatar', controller: Index, permission: ['user_preferences.avatar'] }, 'NavBarProfile')
App.Config.set('Avatar', { prio: 1100, name: 'Avatar', parent: '#profile', target: '#profile/avatar', controller: ProfileAvatar, permission: ['user_preferences.avatar'] }, 'NavBarProfile')
class ImageCropper extends App.ControllerModal
buttonClose: true

View file

@ -1,4 +1,4 @@
class CalendarSubscriptions extends App.ControllerSubContent
class ProfileCalendarSubscriptions extends App.ControllerSubContent
requiredPermission: 'user_preferences.calendar+ticket.agent'
header: 'Calendar'
elements:
@ -90,4 +90,4 @@ class CalendarSubscriptions extends App.ControllerSubContent
msg: App.i18n.translateContent(data.message)
)
App.Config.set('CalendarSubscriptions', { prio: 3000, name: 'Calendar', parent: '#profile', target: '#profile/calendar_subscriptions', permission: ['user_preferences.calendar+ticket.agent'], controller: CalendarSubscriptions }, 'NavBarProfile')
App.Config.set('CalendarSubscriptions', { prio: 3000, name: 'Calendar', parent: '#profile', target: '#profile/calendar_subscriptions', permission: ['user_preferences.calendar+ticket.agent'], controller: ProfileCalendarSubscriptions }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileDevices extends App.ControllerSubContent
requiredPermission: 'user_preferences.device'
header: 'Devices'
events:
@ -55,4 +55,4 @@ class Index extends App.ControllerSubContent
msg: App.i18n.translateContent(data.message)
)
App.Config.set('Devices', { prio: 3100, name: 'Devices', parent: '#profile', target: '#profile/devices', controller: Index, permission: ['user_preferences.device'] }, 'NavBarProfile')
App.Config.set('Devices', { prio: 3100, name: 'Devices', parent: '#profile', target: '#profile/devices', controller: ProfileDevices, permission: ['user_preferences.device'] }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileLanguage extends App.ControllerSubContent
requiredPermission: 'user_preferences.language'
header: 'Language'
events:
@ -19,7 +19,7 @@ class Index extends App.ControllerSubContent
]
@form = new App.ControllerForm(
el: html.find('.language_item')
el: html.find('.js-language')
model: { configure_attributes: configure_attributes }
autofocus: false
)
@ -69,4 +69,4 @@ class Index extends App.ControllerSubContent
msg: App.i18n.translateContent(data.message)
)
App.Config.set('Language', { prio: 1000, name: 'Language', parent: '#profile', target: '#profile/language', controller: Index, permission: ['user_preferences.language'] }, 'NavBarProfile')
App.Config.set('Language', { prio: 1000, name: 'Language', parent: '#profile', target: '#profile/language', controller: ProfileLanguage, permission: ['user_preferences.language'] }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileLinkedAccounts extends App.ControllerSubContent
requiredPermission: 'user_preferences.linked_accounts'
header: 'Linked Accounts'
events:
@ -59,7 +59,7 @@ class Index extends App.ControllerSubContent
msg: App.i18n.translateContent(data.message)
)
App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: Index, permission: ['user_preferences.linked_accounts'] }, 'NavBarProfile')
App.Config.set('LinkedAccounts', { prio: 4000, name: 'Linked Accounts', parent: '#profile', target: '#profile/linked', controller: ProfileLinkedAccounts, permission: ['user_preferences.linked_accounts'] }, 'NavBarProfile')
App.Config.set('auth_provider_all', {
facebook:
url: '/auth/facebook'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileNotification extends App.ControllerSubContent
requiredPermission: 'user_preferences.notifications+ticket.agent'
header: 'Notifications'
events:
@ -192,4 +192,4 @@ class Index extends App.ControllerSubContent
return if !params.notification_sound.file
App.OnlineNotification.play(params.notification_sound.file)
App.Config.set('Notifications', { prio: 2600, name: 'Notifications', parent: '#profile', target: '#profile/notifications', permission: ['user_preferences.notifications+ticket.agent'], controller: Index }, 'NavBarProfile')
App.Config.set('Notifications', { prio: 2600, name: 'Notifications', parent: '#profile', target: '#profile/notifications', permission: ['user_preferences.notifications+ticket.agent'], controller: ProfileNotification }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileOutOfOffice extends App.ControllerSubContent
requiredPermission: 'user_preferences.out_of_office+ticket.agent'
header: 'Out of Office'
events:
@ -161,4 +161,4 @@ class Index extends App.ControllerSubContent
msg: App.i18n.translateContent(message)
removeAll: true
App.Config.set('OutOfOffice', { prio: 2800, name: 'Out of Office', parent: '#profile', target: '#profile/out_of_office', permission: ['user_preferences.out_of_office+ticket.agent'], controller: Index }, 'NavBarProfile')
App.Config.set('OutOfOffice', { prio: 2800, name: 'Out of Office', parent: '#profile', target: '#profile/out_of_office', permission: ['user_preferences.out_of_office+ticket.agent'], controller: ProfileOutOfOffice }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfilePassword extends App.ControllerSubContent
requiredPermission: 'user_preferences.password'
header: 'Password'
events:
@ -78,4 +78,4 @@ class Index extends App.ControllerSubContent
removeAll: true
@formEnable( @$('form') )
App.Config.set('Password', { prio: 2000, name: 'Password', parent: '#profile', target: '#profile/password', controller: Index, permission: ['user_preferences.password'] }, 'NavBarProfile')
App.Config.set('Password', { prio: 2000, name: 'Password', parent: '#profile', target: '#profile/password', controller: ProfilePassword, permission: ['user_preferences.password'] }, 'NavBarProfile')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class ProfileTokenAccess extends App.ControllerSubContent
requiredPermission: 'user_preferences.access_token'
header: 'Token Access'
events:
@ -144,4 +144,4 @@ class Create extends App.ControllerModal
msg: App.i18n.translateContent(data.message || data.error)
)
App.Config.set('Token Access', { prio: 3200, name: 'Token Access', parent: '#profile', target: '#profile/token_access', controller: Index, permission: ['user_preferences.access_token'] }, 'NavBarProfile')
App.Config.set('Token Access', { prio: 3200, name: 'Token Access', parent: '#profile', target: '#profile/token_access', controller: ProfileTokenAccess, permission: ['user_preferences.access_token'] }, 'NavBarProfile')

View file

@ -76,19 +76,19 @@ class App.SettingsForm extends App.Controller
ui.formEnable(e)
count -= 1
if count == 0
App.Event.trigger 'notify', {
App.Event.trigger('notify', {
type: 'success'
msg: App.i18n.translateContent('Update successful!')
timeout: 2000
}
})
# rerender ui || get new collections and session data
App.Setting.preferencesPost(@)
fail: (settings, details) ->
App.Event.trigger 'notify', {
App.Event.trigger('notify', {
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
timeout: 2000
}
})
)

View file

@ -494,7 +494,6 @@ class App.UiElement.ticket_perform_action
elementRow.find('.js-setArticle').empty()
name = "#{attribute.name}::article.#{articleType}"
console.log('meta', meta)
selection = App.UiElement.select.render(
name: "#{name}::internal"
multiple: false

View file

@ -84,8 +84,6 @@ class App.UiElement.user_permission
input = $(@).find('input')
upcoming_state = !input.prop('checked')
value = input.val()
console.log(upcoming_state)
console.log(value)
if value is 'full' and upcoming_state is true
$(@).closest('tr').find('input:not([value=full])').prop('checked', false)

View file

@ -54,14 +54,14 @@ class App.TicketCreate extends App.Controller
@bindId = App.TicketCreateCollection.one(load)
# rerender view, e. g. on langauge change
@bind('ui:rerender', =>
@controllerBind('ui:rerender', =>
return if !@authenticateCheck()
@renderQueue()
@tokanice()
)
# listen to rerender sidebars
@bind('ui::ticket::sidebarRerender', (data) =>
@controllerBind('ui::ticket::sidebarRerender', (data) =>
return if data.taskKey isnt @taskKey
return if !@sidebarWidget
@sidebarWidget.render(@params())
@ -169,11 +169,11 @@ class App.TicketCreate extends App.Controller
show: =>
@navupdate("#ticket/create/id/#{@id}#{@split}", type: 'menu')
@autosaveStart()
@bind('ticket_create_rerender', (template) => @renderQueue(template))
@controllerBind('ticket_create_rerender', (template) => @renderQueue(template))
hide: =>
@autosaveStop()
@unbind('ticket_create_rerender', (template) => @renderQueue(template))
@controllerUnbind('ticket_create_rerender', (template) => @renderQueue(template))
changed: =>
formCurrent = @formParam( @$('.ticket-create') )

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class Api extends App.ControllerSubContent
requiredPermission: 'admin.api'
header: 'API'
events:
@ -193,4 +193,4 @@ class ViewAppTokenModal extends App.ControllerModal
@$('.js-submit').remove()
)
App.Config.set('API', { prio: 1200, name: 'API', parent: '#system', target: '#system/api', controller: Index, permission: ['admin.api'] }, 'NavBarAdmin')
App.Config.set('API', { prio: 1200, name: 'API', parent: '#system', target: '#system/api', controller: Api, permission: ['admin.api'] }, 'NavBarAdmin')

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerSubContent
class Calendar extends App.ControllerSubContent
requiredPermission: 'admin.calendar'
header: 'Calendars'
events:
@ -121,4 +121,4 @@ class Index extends App.ControllerSubContent
container: @el.closest('.content')
)
App.Config.set('Calendars', { prio: 2400, name: 'Calendars', parent: '#manage', target: '#manage/calendars', controller: Index, permission: ['admin.calendar'] }, 'NavBarAdmin')
App.Config.set('Calendars', { prio: 2400, name: 'Calendars', parent: '#manage', target: '#manage/calendars', controller: Calendar, permission: ['admin.calendar'] }, 'NavBarAdmin')

View file

@ -42,7 +42,7 @@ class App.CustomerChat extends App.Controller
@on('layout-has-changed', @propagateLayoutChange)
# update navbar on new status
@bind('chat_status_agent', (data) =>
@controllerBind('chat_status_agent', (data) =>
if data.assets
App.Collection.loadAssets(data.assets)
@meta = data
@ -52,19 +52,19 @@ class App.CustomerChat extends App.Controller
)
# add new chat window
@bind('chat_session_start', (data) =>
@controllerBind('chat_session_start', (data) =>
if data.session
@addChat(data.session)
)
# on new login or on
@bind('ws:login chat_agent_state', ->
@controllerBind('ws:login chat_agent_state', ->
App.WebSocket.send(event:'chat_status_agent')
)
App.WebSocket.send(event:'chat_status_agent')
# rerender view, e. g. on langauge change
@bind('ui:rerender chat:rerender', =>
@controllerBind('ui:rerender chat:rerender', =>
return if !@authenticateCheck()
for session_id, chat of @chatWindows
chat.el.remove()
@ -122,8 +122,8 @@ class App.CustomerChat extends App.Controller
show: (params) =>
@title 'Customer Chat', true
@navupdate '#customer_chat'
@title('Customer Chat', true)
@navupdate('#customer_chat')
if params.session_id
callback = (session) =>
@ -433,34 +433,34 @@ class ChatWindow extends App.Controller
@on('layout-change', @onLayoutChange)
@bind('chat_session_typing', (data) =>
@controllerBind('chat_session_typing', (data) =>
return if data.session_id isnt @session.session_id
return if data.self_written
@showWritingLoader()
)
@bind('chat_session_message', (data) =>
@controllerBind('chat_session_message', (data) =>
return if data.session_id isnt @session.session_id
return if data.self_written
@receiveMessage(data.message.content)
)
@bind('chat_session_notice', (data) =>
@controllerBind('chat_session_notice', (data) =>
return if data.session_id isnt @session.session_id
return if data.self_written
@addNoticeMessage(data.message)
)
@bind('chat_session_left', (data) =>
@controllerBind('chat_session_left', (data) =>
return if data.session_id isnt @session.session_id
return if data.self_written
@addStatusMessage("<strong>#{data.realname}</strong> left the conversation")
@goOffline()
)
@bind('chat_session_closed', (data) =>
@controllerBind('chat_session_closed', (data) =>
return if data.session_id isnt @session.session_id
return if data.self_written
@addStatusMessage("<strong>#{data.realname}</strong> closed the conversation")
@goOffline()
)
@bind('chat_focus', (data) =>
@controllerBind('chat_focus', (data) =>
return if data.session_id isnt @session.session_id
@focus()
)
@ -572,7 +572,7 @@ class ChatWindow extends App.Controller
onTransitionend: (event) =>
# chat window is done with animation - adjust scroll-bars
# of sibling chat windows
@trigger 'layout-has-changed'
@trigger('layout-has-changed')
if event.data and event.data.callback
event.data.callback()
@ -601,7 +601,7 @@ class ChatWindow extends App.Controller
@removeCallback(@session.session_id)
release: =>
@trigger 'closed'
@trigger('closed')
@el.remove()
super

View file

@ -1,4 +1,4 @@
class Index extends App.Controller
class Clues extends App.Controller
constructor: ->
super
@navupdate '#', true
@ -6,7 +6,7 @@ class Index extends App.Controller
clues: =>
new App.FirstStepsClues(
el: @el
appEl: @appEl
onComplete: =>
App.Ajax.request(
id: 'preferences'
@ -18,4 +18,4 @@ class Index extends App.Controller
@navigate '#'
)
App.Config.set('clues', Index, 'Routes')
App.Config.set('clues', Clues, 'Routes')

View file

@ -23,13 +23,13 @@ class App.CTI extends App.Controller
@meta.active = preferences.cti || false
@load()
@bind('cti_list_push', (data) =>
@controllerBind('cti_list_push', (data) =>
delay = =>
@load()
@delay(delay, 500, 'cti_list_push_render')
'cti_list_push'
)
@bind('cti_event', (data) =>
@controllerBind('cti_event', (data) =>
return if data.state isnt 'newCall'
return if data.direction isnt 'in'
return if @switch() isnt true
@ -37,7 +37,7 @@ class App.CTI extends App.Controller
@notify(data)
'cti_event'
)
@bind('menu:render', (data) =>
@controllerBind('menu:render', (data) =>
return if @switch() isnt true
localHtml = ''
for item in @ringingCalls()
@ -53,26 +53,25 @@ class App.CTI extends App.Controller
user_id = $(e.currentTarget).data('user-id')
if user_id
user = App.User.find(user_id)
console.log('user_id', user_id, user)
@newTicket(user)
)
)
@bind('auth', (data) =>
@controllerBind('auth', (data) =>
@meta.counter = 0
)
@bind('cti:reload', =>
@controllerBind('cti:reload', =>
@load()
'cti_reload'
)
# rerender view, e. g. on langauge change
@bind('ui:rerender', =>
@controllerBind('ui:rerender', =>
@render()
'cti_rerender'
)
# after a new websocket connection, load again
@bind('spool:sent', =>
@controllerBind('spool:sent', =>
if @initSpoolSent
@load()
return
@ -280,7 +279,7 @@ class App.CTI extends App.Controller
currentPosition: =>
@$('.main').scrollTop()
class WidgetAvatar extends App.ObserverController
class WidgetAvatar extends App.ControllerObserver
@extend App.PopoverProvidable
@registerPopovers 'User'

View file

@ -1,4 +1,4 @@
class Index extends App.ControllerContent
class CustomerTicketCreate extends App.ControllerAppContent
requiredPermission: 'ticket.customer'
events:
'submit form': 'submit',
@ -211,7 +211,7 @@ class Index extends App.ControllerContent
return
@formEnable(@$('.js-submit'), 'button')
App.Config.set('customer_ticket_new', Index, 'Routes')
App.Config.set('customer_ticket_new', CustomerTicketCreate, 'Routes')
App.Config.set('CustomerTicketNew', {
prio: 8003,
parent: '#new',

View file

@ -15,9 +15,10 @@ class App.Dashboard extends App.Controller
@render()
# rerender view, e. g. on language change
@bind 'ui:rerender', =>
@controllerBind('ui:rerender', =>
return if !@authenticateCheck()
@render()
)
@mayBeClues()
@ -71,12 +72,12 @@ class App.Dashboard extends App.Controller
# incase of being only customer, redirect to default router
if @permissionCheck('ticket.customer') && !@permissionCheck('ticket.agent')
@navigate '#ticket/view', true
@navigate '#ticket/view', { hideCurrentLocationFromHistory: true }
return
# incase of being only admin, redirect to admin interface (show no empty white content page)
if !@permissionCheck('ticket.customer') && !@permissionCheck('ticket.agent') && @permissionCheck('admin')
@navigate '#manage', true
@navigate '#manage', { hideCurrentLocationFromHistory: true }
return
# set title

View file

@ -9,19 +9,19 @@ class DefaultRouter extends App.Controller
# check if import is active
if !@Config.get('system_init_done') && @Config.get('import_mode')
@navigate '#import', true
@navigate '#import', { hideCurrentLocationFromHistory: true }
return
# route to getting started screen
if !@Config.get('system_init_done')
@navigate '#getting_started', true
@navigate '#getting_started', { hideCurrentLocationFromHistory: true }
return
if @Config.get('default_controller')
@navigate @Config.get('default_controller'), true
@navigate @Config.get('default_controller'), { hideCurrentLocationFromHistory: true }
return
@navigate '#dashboard', true
@navigate '#dashboard', { hideCurrentLocationFromHistory: true }
App.Config.set('', DefaultRouter, 'Routes')
App.Config.set('/', DefaultRouter, 'Routes')

View file

@ -1,4 +1,4 @@
class Index extends App.Controller
class EmailVerify extends App.Controller
constructor: ->
super
@verifyCall()
@ -10,23 +10,47 @@ class Index extends App.Controller
url: "#{@apiPath}/users/email_verify"
data: JSON.stringify(token: @token)
processData: true
success: (data, status, xhr) =>
App.Auth.loginCheck()
@navigate '#'
@notify
type: 'success'
msg: App.i18n.translateContent('Woo hoo! Your email address has been verified!')
removeAll: true
timeout: 2000
error: (data, status, xhr) =>
@navigate '#'
@notify
type: 'error'
msg: App.i18n.translateContent('Unable to verify email. Please contact your administrator.')
removeAll: true
success: @success
error: @error
)
App.Config.set('email_verify/:token', Index, 'Routes')
success: =>
new Success(el: @el, appEl: @appEl)
error: =>
new Fail(el: @el, appEl: @appEl)
class Success extends App.ControllerAppContent
constructor: ->
super
@render()
# rerender view, e. g. on language change
@controllerBind('ui:rerender', =>
@render()
)
render: =>
@renderScreenSuccess(
detail: 'Woo hoo! Your email address has been verified!'
)
delay = =>
@navigate '#'
@delay(delay, 2000)
class Fail extends App.ControllerAppContent
constructor: ->
super
@render()
# rerender view, e. g. on language change
@controllerBind('ui:rerender', =>
@render()
)
render: =>
@renderScreenError(
detail: 'Unable to verify email. Please contact your administrator.'
)
App.Config.set('email_verify/:token', EmailVerify, 'Routes')

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
class GettingStartedAdmin extends App.ControllerWizardFullScreen
events:
'submit form': 'submit'
constructor: ->
super
if @authenticateCheck() && !@permissionCheck('admin.wizard')
@navigate '#'
return
# set title
@title 'Create Admin'
# redirect to login if master user already exists
if @Config.get('system_init_done')
@navigate '#login'
return
@fetch()
fetch: ->
# get data
@ajax(
id: 'getting_started'
type: 'GET'
url: "#{@apiPath}/getting_started"
processData: true
success: (data, status, xhr) =>
# check if user got created right now
#if true
# @navigate '#getting_started/base', { emptyEl: true }
# return
# check if import is active
if data.import_mode == true
@navigate "#import/#{data.import_backend}", { emptyEl: true }
return
# load group collection
App.Collection.load(type: 'Group', data: data.groups)
# render page
@render()
)
render: ->
@replaceWith(App.view('getting_started/admin')())
@form = new App.ControllerForm(
el: @$('.js-admin-form')
model: App.User
screen: 'signup'
autofocus: true
)
submit: (e) =>
e.preventDefault()
@formDisable(e)
@params = @formParam(e.target)
@params.role_ids = []
user = new App.User
user.load(@params)
errors = user.validate(
screen: 'signup'
)
if errors
@log 'error new', errors
# Only highlight, but don't add message. Error text breaks layout.
Object.keys(errors).forEach (key) ->
errors[key] = null
@formValidate(form: e.target, errors: errors)
@formEnable(e)
return false
else
@formValidate(form: e.target, errors: errors)
# save user
user.save(
done: (r) =>
App.Auth.login(
data:
username: @params.email
password: @params.password
success: @relogin
error: ->
App.Event.trigger('notify', {
type: 'error'
msg: App.i18n.translateContent('Signin failed! Please contact the support team!')
timeout: 2500
})
)
@Config.set('system_init_done', true)
fail: (settings, details) =>
@formEnable(e)
@form.showAlert(details.error_human || details.error || 'Unable to create user!')
)
relogin: (data, status, xhr) =>
@log 'notice', 'relogin:success', data
App.Event.trigger('notify:removeall')
@navigate('getting_started/base', { emptyEl: true })
App.Config.set('getting_started/admin', GettingStartedAdmin, 'Routes')

View file

@ -0,0 +1,89 @@
class GettingStartedAgent extends App.ControllerWizardFullScreen
events:
'submit form': 'submit'
constructor: ->
super
@authenticateCheckRedirect()
# set title
@title 'Invite Agents'
@fetch()
fetch: ->
# get data
@ajax(
id: 'getting_started'
type: 'GET'
url: "#{@apiPath}/getting_started"
processData: true
success: (data, status, xhr) =>
# check if import is active
if data.import_mode == true
@navigate "#import/#{data.import_backend}", { emptyEl: true }
return
# load group collection
App.Collection.load(type: 'Group', data: data.groups)
# render page
@render()
)
render: ->
@replaceWith App.view('getting_started/agent')()
@form = new App.ControllerForm(
el: @$('.js-agent-form')
model: App.User
screen: 'invite_agent'
autofocus: true
)
submit: (e) =>
e.preventDefault()
@formDisable(e)
@params = @formParam(e.target)
@params.role_ids = []
# set invite flag
@params.invite = true
# find agent role
role = App.Role.findByAttribute('name', 'Agent')
if role
@params.role_ids = role.id
user = new App.User
user.load(@params)
errors = user.validate(
screen: 'invite_agent'
)
if errors
@log 'error new', errors
@formValidate(form: e.target, errors: errors)
@formEnable(e)
return false
# save user
user.save(
done: (r) =>
App.Event.trigger('notify', {
type: 'success'
msg: App.i18n.translateContent('Invitation sent!')
timeout: 3500
})
# rerender page
@render()
fail: (settings, details) =>
@formEnable(e)
@form.showAlert(details.error_human || 'Can\'t create user!')
)
App.Config.set('getting_started/agents', GettingStartedAgent, 'Routes')

View file

@ -0,0 +1,65 @@
class GettingStartedAutoWizard extends App.ControllerWizardFullScreen
constructor: ->
super
# if already logged in, got to #
if @authenticateCheck() && !@permissionCheck('admin.wizard')
@navigate '#'
return
# redirect to login if master user already exists
if @Config.get('system_init_done')
@navigate '#login'
return
# set title
@title 'Auto Wizard'
@renderSplash()
@fetch()
fetch: ->
url = "#{@apiPath}/getting_started/auto_wizard"
if @token
url += "/#{@token}"
# get data
@ajax(
id: 'auto_wizard'
type: 'GET'
url: url
processData: true
success: (data, status, xhr) =>
# check if auto wizard enabled
if data.auto_wizard is false
@redirectToLogin()
return
# auto wizard setup was successful
if data.auto_wizard_success is true
@delay(@redirectToLogin, 800)
return
if data.auto_wizard_success is false
if data.message
@renderFailed(data)
else
@renderToken()
return
# redirect to login if master user already exists
@redirectToLogin()
)
renderFailed: (data) ->
@replaceWith App.view('getting_started/auto_wizard_failed')(data)
renderSplash: ->
@replaceWith App.view('getting_started/auto_wizard_splash')()
renderToken: ->
@replaceWith App.view('getting_started/auto_wizard_enabled')()
App.Config.set('getting_started/auto_wizard', GettingStartedAutoWizard, 'Routes')
App.Config.set('getting_started/auto_wizard/:token', GettingStartedAutoWizard, 'Routes')

Some files were not shown because too many files have changed in this diff Show more