diff --git a/app/assets/javascripts/app/controllers/_application_controller.coffee b/app/assets/javascripts/app/controllers/_application_controller/_base.coffee similarity index 53% rename from app/assets/javascripts/app/controllers/_application_controller.coffee rename to app/assets/javascripts/app/controllers/_application_controller/_base.coffee index f85638e9a..35e5885bf 100644 --- a/app/assets/javascripts/app/controllers/_application_controller.coffee +++ b/app/assets/javascripts/app/controllers/_application_controller/_base.coffee @@ -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 = $('
') - .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('
') - 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("
") - $("##{@key}").html raw diff --git a/app/assets/javascripts/app/controllers/_application_controller/_modal.coffee b/app/assets/javascripts/app/controllers/_application_controller/_modal.coffee new file mode 100644 index 000000000..e7302425d --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/_modal.coffee @@ -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 = $('
') + .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') diff --git a/app/assets/javascripts/app/controllers/_application_controller/app_content.coffee b/app/assets/javascripts/app/controllers/_application_controller/app_content.coffee new file mode 100644 index 000000000..2a5ca30fc --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/app_content.coffee @@ -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('
') + params.el = $('#content') + + super(params) diff --git a/app/assets/javascripts/app/controllers/_application_controller/collection.coffee b/app/assets/javascripts/app/controllers/_application_controller/collection.coffee new file mode 100644 index 000000000..3649ac5a5 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/collection.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/_application_controller/drox.coffee b/app/assets/javascripts/app/controllers/_application_controller/drox.coffee new file mode 100644 index 000000000..d06debdce --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/drox.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/_application_controller_form.coffee b/app/assets/javascripts/app/controllers/_application_controller/form.coffee similarity index 100% rename from app/assets/javascripts/app/controllers/_application_controller_form.coffee rename to app/assets/javascripts/app/controllers/_application_controller/form.coffee diff --git a/app/assets/javascripts/app/controllers/_application_controller/full_page.coffee b/app/assets/javascripts/app/controllers/_application_controller/full_page.coffee new file mode 100644 index 000000000..71b52407d --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/full_page.coffee @@ -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()) diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_description.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_description.coffee new file mode 100644 index 000000000..d48f18b11 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_description.coffee @@ -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() diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_destroy_confirm.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_destroy_confirm.coffee new file mode 100644 index 000000000..6cb2a73b2 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_destroy_confirm.coffee @@ -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() diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_edit.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_edit.coffee new file mode 100644 index 000000000..ea1261de8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_edit.coffee @@ -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!') + ) diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_error_modal.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_error_modal.coffee new file mode 100644 index 000000000..ac2ee345c --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_error_modal.coffee @@ -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() diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee new file mode 100644 index 000000000..3ee970faa --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_history.coffee @@ -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 + "##{ ticket.number }" + 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 + "##{ ticket.number }" + 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 += ' →' + content += " '#{ App.Utils.htmlEscape(item.value_to) }'" + else if item.value_from + content += " → '-'" + + newItem.records.push content + + if !_.isEmpty(newItem) + newItems.push newItem + + newItems diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee new file mode 100644 index 000000000..bf1fb8d89 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_index.coffee @@ -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 + ) diff --git a/app/assets/javascripts/app/controllers/_application_controller/generic_new.coffee b/app/assets/javascripts/app/controllers/_application_controller/generic_new.coffee new file mode 100644 index 000000000..23f594873 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/generic_new.coffee @@ -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!') + ) diff --git a/app/assets/javascripts/app/controllers/_application_controller/modal_loading.coffee b/app/assets/javascripts/app/controllers/_application_controller/modal_loading.coffee new file mode 100644 index 000000000..b5f75049f --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/modal_loading.coffee @@ -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) diff --git a/app/assets/javascripts/app/controllers/_application_controller/nav_sidebar.coffee b/app/assets/javascripts/app/controllers/_application_controller/nav_sidebar.coffee new file mode 100644 index 000000000..e3774f47f --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/nav_sidebar.coffee @@ -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('
') + @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() diff --git a/app/assets/javascripts/app/controllers/_application_controller/observer.coffee b/app/assets/javascripts/app/controllers/_application_controller/observer.coffee new file mode 100644 index 000000000..293c02528 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/observer.coffee @@ -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) diff --git a/app/assets/javascripts/app/controllers/_application_controller/observer_action_row.coffee b/app/assets/javascripts/app/controllers/_application_controller/observer_action_row.coffee new file mode 100644 index 000000000..a93e1ad30 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/observer_action_row.coffee @@ -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) + ) diff --git a/app/assets/javascripts/app/controllers/_application_controller/permanent.coffee b/app/assets/javascripts/app/controllers/_application_controller/permanent.coffee new file mode 100644 index 000000000..20349bbe8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/permanent.coffee @@ -0,0 +1,5 @@ +class App.ControllerPermanent extends App.Controller + constructor: -> + if @requiredPermission + @permissionCheckRedirect(@requiredPermission, true) + super \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_application_controller_reorder_modal.coffee b/app/assets/javascripts/app/controllers/_application_controller/reorder_modal.coffee similarity index 100% rename from app/assets/javascripts/app/controllers/_application_controller_reorder_modal.coffee rename to app/assets/javascripts/app/controllers/_application_controller/reorder_modal.coffee diff --git a/app/assets/javascripts/app/controllers/_application_controller/sub_content.coffee b/app/assets/javascripts/app/controllers/_application_controller/sub_content.coffee new file mode 100644 index 000000000..c297a9c83 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/sub_content.coffee @@ -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() \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/_application_controller_table.coffee b/app/assets/javascripts/app/controllers/_application_controller/table.coffee similarity index 100% rename from app/assets/javascripts/app/controllers/_application_controller_table.coffee rename to app/assets/javascripts/app/controllers/_application_controller/table.coffee diff --git a/app/assets/javascripts/app/controllers/_application_controller/tabs.coffee b/app/assets/javascripts/app/controllers/_application_controller/tabs.coffee new file mode 100644 index 000000000..c60d95bcf --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/tabs.coffee @@ -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("
") + 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) diff --git a/app/assets/javascripts/app/controllers/_application_controller/wizard_modal.coffee b/app/assets/javascripts/app/controllers/_application_controller/wizard_modal.coffee new file mode 100644 index 000000000..846f77c42 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/wizard_modal.coffee @@ -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 + diff --git a/app/assets/javascripts/app/controllers/_application_controller/wizard_modal_full_screen.coffee b/app/assets/javascripts/app/controllers/_application_controller/wizard_modal_full_screen.coffee new file mode 100644 index 000000000..e06bf7b4e --- /dev/null +++ b/app/assets/javascripts/app/controllers/_application_controller/wizard_modal_full_screen.coffee @@ -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 } diff --git a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee b/app/assets/javascripts/app/controllers/_application_controller_generic.coffee deleted file mode 100644 index 341005798..000000000 --- a/app/assets/javascripts/app/controllers/_application_controller_generic.coffee +++ /dev/null @@ -1,1545 +0,0 @@ -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!') - ) - -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!') - ) - -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 - ) - -class App.ControllerGenericDescription extends App.ControllerModal - buttonClose: true - buttonCancel: false - buttonSubmit: 'Close' - head: 'Description' - - content: => - marked(App.i18n.translateContent(@description)) - - onSubmit: => - @close() - -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) - -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() - -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() - -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 - -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("
") - 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) - -class App.ControllerNavSidbar extends App.Controller - constructor: (params) -> - super - - if @authenticateRequired - @authenticateCheckRedirect() - - @render(true) - - @bind('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('
') - @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() - -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 - "##{ ticket.number }" - 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 - "##{ ticket.number }" - 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 += ' →' - content += " '#{ App.Utils.htmlEscape(item.value_to) }'" - else if item.value_from - content += " → '-'" - - newItem.records.push content - - if !_.isEmpty(newItem) - newItems.push newItem - - newItems - -class App.ActionRow extends App.Controller - constructor: -> - super - @render() - - render: -> - @html App.view('generic/actions')( - items: @items - type: @type - ) - - for item in @items - do (item) => - @$('[data-type="' + item.name + '"]').on( - 'click' - (e) -> - e.preventDefault() - item.callback() - ) - -class App.Sidebar extends App.Controller - elements: - '.tabsSidebar-tab': 'tabs' - '.sidebar': 'sidebars' - - events: - 'click .tabsSidebar-tab': 'toggleTab' - 'click .tabsSidebar-close': 'toggleSidebar' - 'click .sidebar-header .js-headline': 'toggleDropdown' - - constructor: -> - super - @render() - - # get active tab by name - if @name - name = @name - - # get active tab last state - if !name && @sidebarState - name = @sidebarState.active - - # get active tab by first tab - if !name - name = @tabs.first().data('tab') - - # activate first tab - @toggleTabAction(name) - - render: => - itemsLocal = [] - for item in @items - itemLocal = item.sidebarItem() - if itemLocal - itemsLocal.push itemLocal - - # container - localEl = $(App.view('generic/sidebar_tabs')( - items: itemsLocal - scrollbarWidth: App.Utils.getScrollBarWidth() - dir: App.i18n.dir() - )) - - # init sidebar badge - for item in itemsLocal - el = localEl.find('.tabsSidebar-tab[data-tab="' + item.name + '"]') - if item.badgeCallback - item.badgeCallback(el) - else - @badgeRender(el, item) - - # init sidebar content - for item in itemsLocal - if item.sidebarCallback - el = localEl.filter('.sidebar[data-tab="' + item.name + '"]') - item.sidebarCallback(el.find('.sidebar-content')) - if !_.isEmpty(item.sidebarActions) - new App.ActionRow( - el: el.find('.js-actions') - items: item.sidebarActions - type: 'small' - ) - - @html localEl - - badgeRender: (el, item) => - @badgeEl = el - @badgeRenderLocal(item) - - badgeRenderLocal: (item) => - @badgeEl.html(App.view('generic/sidebar_tabs_item')(icon: item.badgeIcon)) - - toggleDropdown: (e) -> - e.stopPropagation() - $(e.currentTarget).next('.js-actions').find('.dropdown-toggle').dropdown('toggle') - - toggleSidebar: => - @el.parent().find('.tabsSidebar-sidebarSpacer').toggleClass('is-closed') - @el.parent().find('.tabsSidebar').toggleClass('is-closed') - #@el.parent().next('.attributeBar').toggleClass('is-closed') - - showSidebar: -> - @el.parent().find('.tabsSidebar-sidebarSpacer').removeClass('is-closed') - @el.parent().find('.tabsSidebar').removeClass('is-closed') - #@el.parent().next('.attributeBar').addClass('is-closed') - - toggleTab: (e) => - - # get selected tab - name = $(e.target).closest('.tabsSidebar-tab').data('tab') - - if name - - # if current tab is selected again, toggle side bar - if name is @currentTab - @toggleSidebar() - - # toggle content tab - else - @toggleTabAction(name) - - toggleTabAction: (name) -> - return if !name - - # remember sidebarState for outsite - if @sidebarState - @sidebarState.active = name - - # remove active state - @tabs.removeClass('active') - - # add active state - @$('.tabsSidebar-tab[data-tab=' + name + ']').addClass('active') - - # hide all content tabs - @sidebars.addClass('hide') - - # show active tab content - tabContent = @$('.sidebar[data-tab=' + name + ']') - tabContent.removeClass('hide') - - # remember current tab - @currentTab = name - - # show sidebar if not shown - @showSidebar() - -class App.WizardModal extends App.Controller - className: 'modal fade' - - constructor: -> - super - - # rerender view, e. g. on langauge change - @bind('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 - -class App.WizardFullScreen extends App.WizardModal - className: 'getstarted fit' - - constructor: -> - super - $('.content').addClass('hide') - $('#content').removeClass('hide') - -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 - @bind('ui:rerender', => - @queue.push ['renderAll'] - @uIRunner() - ) - - # render on login - @bind('auth:login', => - @queue.push ['renderAll'] - @uIRunner() - ) - - # reset current tasks on logout - @bind('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 - -class App.ObserverController 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 - @bind('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) - -class App.ObserverActionRow extends App.ObserverController - 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) - ) - -class App.Import extends App.ControllerModal - buttonClose: true - buttonCancel: true - buttonSubmit: 'Import' - autoFocusOnFirstInput: false - head: 'Import' - large: true - templateDirectory: 'generic/object_import' - baseUrl: '/api/v1/text_modules' - - content: => - - # show start dialog - content = $(App.view("#{@templateDirectory}/index")( - head: 'Import' - import_example_url: "#{@baseUrl}/import_example" - deleteOption: @deleteOption - )) - - # check if data is processing... - if @data - result = App.view("#{@templateDirectory}/result")( - @data - ) - content.find('.js-error').html(result) - if result - content.find('.js-error').removeClass('hide') - else - content.find('.js-error').addClass('hide') - content - - onSubmit: (e) => - params = new FormData($(e.currentTarget).closest('form').get(0)) - params.set('try', true) - if _.isEmpty(params.get('data')) - params.delete('data') - @formDisable(e) - @ajax( - id: 'csv_import' - type: 'POST' - url: "#{@baseUrl}/import" - processData: false - contentType: false - cache: false - data: params - success: (data, status, xhr) => - if data.result is 'success' - new App.ImportTryResult( - container: @el.closest('.content') - result: data - params: params - templateDirectory: @templateDirectory - baseUrl: @baseUrl - ) - @close() - return - @data = data - @update() - @formEnable(e) - error: (data) => - details = data.responseJSON || {} - @notify - type: 'error' - msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') - timeout: 6000 - @formEnable(e) - ) - -class App.ImportTryResult extends App.ControllerModal - buttonClose: true - buttonCancel: true - buttonSubmit: 'Yes, start real import.' - autoFocusOnFirstInput: false - head: 'Import' - large: true - templateDirectory: 'generic/object_import/' - baseUrl: '/api/v1/text_modules' - - content: => - - # show start dialog - content = $(App.view("#{@templateDirectory}/import_try")( - head: 'Import' - import_example_url: "#{@baseUrl}/import" - result: @result - )) - - # check if data is processing... - if @data - result = App.view("#{@templateDirectory}/result")( - @data - ) - content.find('.js-error').html(result) - if result - content.find('.js-error').removeClass('hide') - else - content.find('.js-error').addClass('hide') - - content - - onSubmit: (e) => - @params.set('try', false) - @formDisable(e) - @ajax( - id: 'csv_import' - type: 'POST' - url: "#{@baseUrl}/import" - processData: false - contentType: false - cache: false - data: @params - success: (data, status, xhr) => - if data.result is 'success' - new App.ImportResult( - container: @el.closest('.content') - result: data - params: @params - templateDirectory: @templateDirectory - baseUrl: @baseUrl - ) - @close() - return - @data = data - @update() - @formEnable(e) - error: (data) => - details = data.responseJSON || {} - @notify - type: 'error' - msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') - timeout: 6000 - @formEnable(e) - ) - -class App.ImportResult extends App.ControllerModal - buttonClose: true - buttonCancel: true - buttonSubmit: 'Close' - autoFocusOnFirstInput: false - head: 'Import' - large: true - templateDirectory: 'generic/object_import/' - - content: => - - content = $(App.view("#{@templateDirectory}/imported")( - head: 'Imported' - result: @result - )) - content - - onSubmit: (e) => - @close() diff --git a/app/assets/javascripts/app/controllers/_channel/chat.coffee b/app/assets/javascripts/app/controllers/_channel/chat.coffee index 8bd48d2fd..cbea22b48 100644 --- a/app/assets/javascripts/app/controllers/_channel/chat.coffee +++ b/app/assets/javascripts/app/controllers/_channel/chat.coffee @@ -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: diff --git a/app/assets/javascripts/app/controllers/_channel/email.coffee b/app/assets/javascripts/app/controllers/_channel/email.coffee index a592a6bfb..55bcdd69f 100644 --- a/app/assets/javascripts/app/controllers/_channel/email.coffee +++ b/app/assets/javascripts/app/controllers/_channel/email.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/facebook.coffee b/app/assets/javascripts/app/controllers/_channel/facebook.coffee index 72291b24b..e73f6d964 100644 --- a/app/assets/javascripts/app/controllers/_channel/facebook.coffee +++ b/app/assets/javascripts/app/controllers/_channel/facebook.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/form.coffee b/app/assets/javascripts/app/controllers/_channel/form.coffee index 10394d8d3..d7b1e880b 100644 --- a/app/assets/javascripts/app/controllers/_channel/form.coffee +++ b/app/assets/javascripts/app/controllers/_channel/form.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/sms.coffee b/app/assets/javascripts/app/controllers/_channel/sms.coffee index 13091c844..332b872b0 100644 --- a/app/assets/javascripts/app/controllers/_channel/sms.coffee +++ b/app/assets/javascripts/app/controllers/_channel/sms.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/telegram.coffee b/app/assets/javascripts/app/controllers/_channel/telegram.coffee index 7b60054af..3591c551a 100644 --- a/app/assets/javascripts/app/controllers/_channel/telegram.coffee +++ b/app/assets/javascripts/app/controllers/_channel/telegram.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/twitter.coffee b/app/assets/javascripts/app/controllers/_channel/twitter.coffee index 1bcfc0a86..381f339ff 100644 --- a/app/assets/javascripts/app/controllers/_channel/twitter.coffee +++ b/app/assets/javascripts/app/controllers/_channel/twitter.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_channel/web.coffee b/app/assets/javascripts/app/controllers/_channel/web.coffee index 6e5915855..7e3614bc8 100644 --- a/app/assets/javascripts/app/controllers/_channel/web.coffee +++ b/app/assets/javascripts/app/controllers/_channel/web.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.coffee b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.coffee index a1df4842b..d0a521fc3 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/activity_stream.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/activity_stream.coffee @@ -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: => diff --git a/app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee b/app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee index 4b211e422..a09f8175e 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/first_steps_clues.coffee @@ -54,8 +54,9 @@ class App.FirstStepsClues extends App.Controller constructor: (params) -> - $('#app').append('
') - params.el = $('#app .js-modal--clue') + el = $('
') + 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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats.coffee index d13708c92..ee23a1737 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/_base.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/_base.coffee new file mode 100644 index 000000000..5beb6fcc6 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/_base.coffee @@ -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 = $("
") + 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() diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee index aea977636..86a14226f 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_channel_distribution.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee index 15560c9a3..6cec35679 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_escalation.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee index 457792b2f..3d820da3a 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_in_process.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee index b54649ab2..8e83f09f3 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_load_measure.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee index 17a26a421..b1c2abf28 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_reopen.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee index 0ab1d0269..832e682bc 100644 --- a/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee +++ b/app/assets/javascripts/app/controllers/_dashboard/stats/ticket_waiting_time.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_integration/check_mk.coffee b/app/assets/javascripts/app/controllers/_integration/check_mk.coffee index 3079b129d..847f6bd70 100644 --- a/app/assets/javascripts/app/controllers/_integration/check_mk.coffee +++ b/app/assets/javascripts/app/controllers/_integration/check_mk.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/clearbit.coffee b/app/assets/javascripts/app/controllers/_integration/clearbit.coffee index e6009affc..58624e4a8 100644 --- a/app/assets/javascripts/app/controllers/_integration/clearbit.coffee +++ b/app/assets/javascripts/app/controllers/_integration/clearbit.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/cti.coffee b/app/assets/javascripts/app/controllers/_integration/cti.coffee index 4068b9480..ca2e3038f 100644 --- a/app/assets/javascripts/app/controllers/_integration/cti.coffee +++ b/app/assets/javascripts/app/controllers/_integration/cti.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/exchange.coffee b/app/assets/javascripts/app/controllers/_integration/exchange.coffee index e69976c98..2a2922232 100644 --- a/app/assets/javascripts/app/controllers/_integration/exchange.coffee +++ b/app/assets/javascripts/app/controllers/_integration/exchange.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/icinga.coffee b/app/assets/javascripts/app/controllers/_integration/icinga.coffee index 6788680df..eefc9ca0d 100644 --- a/app/assets/javascripts/app/controllers/_integration/icinga.coffee +++ b/app/assets/javascripts/app/controllers/_integration/icinga.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/idoit.coffee b/app/assets/javascripts/app/controllers/_integration/idoit.coffee index d94d96503..a902b1ba7 100644 --- a/app/assets/javascripts/app/controllers/_integration/idoit.coffee +++ b/app/assets/javascripts/app/controllers/_integration/idoit.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/ldap.coffee b/app/assets/javascripts/app/controllers/_integration/ldap.coffee index 2ca2d69de..b47da749c 100644 --- a/app/assets/javascripts/app/controllers/_integration/ldap.coffee +++ b/app/assets/javascripts/app/controllers/_integration/ldap.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/monit.coffee b/app/assets/javascripts/app/controllers/_integration/monit.coffee index 208c6d507..5647483ce 100644 --- a/app/assets/javascripts/app/controllers/_integration/monit.coffee +++ b/app/assets/javascripts/app/controllers/_integration/monit.coffee @@ -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'] } diff --git a/app/assets/javascripts/app/controllers/_integration/nagios.coffee b/app/assets/javascripts/app/controllers/_integration/nagios.coffee index 4ddcf7331..d061ccdd2 100644 --- a/app/assets/javascripts/app/controllers/_integration/nagios.coffee +++ b/app/assets/javascripts/app/controllers/_integration/nagios.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/placetel.coffee b/app/assets/javascripts/app/controllers/_integration/placetel.coffee index f82d0c045..8289af1ad 100644 --- a/app/assets/javascripts/app/controllers/_integration/placetel.coffee +++ b/app/assets/javascripts/app/controllers/_integration/placetel.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee index 9f502dbaa..8c7ee0a2d 100644 --- a/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee +++ b/app/assets/javascripts/app/controllers/_integration/sipgate_io.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/slack.coffee b/app/assets/javascripts/app/controllers/_integration/slack.coffee index 9ab9476b5..599b26c5f 100644 --- a/app/assets/javascripts/app/controllers/_integration/slack.coffee +++ b/app/assets/javascripts/app/controllers/_integration/slack.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_integration/smime.coffee b/app/assets/javascripts/app/controllers/_integration/smime.coffee index b4bc5561d..0fc120970 100644 --- a/app/assets/javascripts/app/controllers/_integration/smime.coffee +++ b/app/assets/javascripts/app/controllers/_integration/smime.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/widget/app_config_update.coffee b/app/assets/javascripts/app/controllers/_plugin/app_config_update.coffee similarity index 57% rename from app/assets/javascripts/app/controllers/widget/app_config_update.coffee rename to app/assets/javascripts/app/controllers/_plugin/app_config_update.coffee index 5a0f48ed3..96d9a65b7 100644 --- a/app/assets/javascripts/app/controllers/widget/app_config_update.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/app_config_update.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/default_locale.coffee b/app/assets/javascripts/app/controllers/_plugin/default_locale.coffee similarity index 83% rename from app/assets/javascripts/app/controllers/widget/default_locale.coffee rename to app/assets/javascripts/app/controllers/_plugin/default_locale.coffee index db4437c30..98099c2ed 100644 --- a/app/assets/javascripts/app/controllers/widget/default_locale.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/default_locale.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/default_timezone.coffee b/app/assets/javascripts/app/controllers/_plugin/default_timezone.coffee similarity index 89% rename from app/assets/javascripts/app/controllers/widget/default_timezone.coffee rename to app/assets/javascripts/app/controllers/_plugin/default_timezone.coffee index a85b327c1..0f25ad6d6 100644 --- a/app/assets/javascripts/app/controllers/widget/default_timezone.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/default_timezone.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/dev_banner.coffee b/app/assets/javascripts/app/controllers/_plugin/dev_banner.coffee similarity index 91% rename from app/assets/javascripts/app/controllers/widget/dev_banner.coffee rename to app/assets/javascripts/app/controllers/_plugin/dev_banner.coffee index 28d534391..5be8a5739 100644 --- a/app/assets/javascripts/app/controllers/widget/dev_banner.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/dev_banner.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/electron_events.coffee b/app/assets/javascripts/app/controllers/_plugin/electron_events.coffee similarity index 88% rename from app/assets/javascripts/app/controllers/widget/electron_events.coffee rename to app/assets/javascripts/app/controllers/_plugin/electron_events.coffee index f79309ed9..03da56b6e 100644 --- a/app/assets/javascripts/app/controllers/widget/electron_events.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/electron_events.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/global_search.coffee b/app/assets/javascripts/app/controllers/_plugin/global_search.coffee similarity index 76% rename from app/assets/javascripts/app/controllers/widget/global_search.coffee rename to app/assets/javascripts/app/controllers/_plugin/global_search.coffee index be2e88935..655d39661 100644 --- a/app/assets/javascripts/app/controllers/widget/global_search.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/global_search.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/hello_banner.coffee b/app/assets/javascripts/app/controllers/_plugin/hello_banner.coffee similarity index 88% rename from app/assets/javascripts/app/controllers/widget/hello_banner.coffee rename to app/assets/javascripts/app/controllers/_plugin/hello_banner.coffee index 9c0d9e8fa..d95720445 100644 --- a/app/assets/javascripts/app/controllers/widget/hello_banner.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/hello_banner.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee b/app/assets/javascripts/app/controllers/_plugin/keyboard_shortcuts.coffee similarity index 98% rename from app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee rename to app/assets/javascripts/app/controllers/_plugin/keyboard_shortcuts.coffee index 868f303e7..fa3d487a9 100644 --- a/app/assets/javascripts/app/controllers/widget/keyboard_shortcuts.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/keyboard_shortcuts.coffee @@ -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', [ diff --git a/app/assets/javascripts/app/controllers/widget/maintenance.coffee b/app/assets/javascripts/app/controllers/_plugin/maintenance.coffee similarity index 96% rename from app/assets/javascripts/app/controllers/widget/maintenance.coffee rename to app/assets/javascripts/app/controllers/_plugin/maintenance.coffee index c8f0ca0fa..62f2ef9dd 100644 --- a/app/assets/javascripts/app/controllers/widget/maintenance.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/maintenance.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/navigation.coffee b/app/assets/javascripts/app/controllers/_plugin/navigation.coffee similarity index 93% rename from app/assets/javascripts/app/controllers/navigation.coffee rename to app/assets/javascripts/app/controllers/_plugin/navigation.coffee index 7626cf799..157710a05 100644 --- a/app/assets/javascripts/app/controllers/navigation.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/navigation.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/notify.coffee b/app/assets/javascripts/app/controllers/_plugin/notify.coffee similarity index 83% rename from app/assets/javascripts/app/controllers/widget/notify.coffee rename to app/assets/javascripts/app/controllers/_plugin/notify.coffee index 9228940e6..b275f13e2 100644 --- a/app/assets/javascripts/app/controllers/widget/notify.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/notify.coffee @@ -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('
') + # 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 = '' + data.msg + '' - $('#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') diff --git a/app/assets/javascripts/app/controllers/widget/remote_task.coffee b/app/assets/javascripts/app/controllers/_plugin/remote_task.coffee similarity index 72% rename from app/assets/javascripts/app/controllers/widget/remote_task.coffee rename to app/assets/javascripts/app/controllers/_plugin/remote_task.coffee index 62368517e..8470aef15 100644 --- a/app/assets/javascripts/app/controllers/widget/remote_task.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/remote_task.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/session_taken_over.coffee b/app/assets/javascripts/app/controllers/_plugin/session_taken_over.coffee similarity index 86% rename from app/assets/javascripts/app/controllers/widget/session_taken_over.coffee rename to app/assets/javascripts/app/controllers/_plugin/session_taken_over.coffee index a66e22720..7e2a0a8ea 100644 --- a/app/assets/javascripts/app/controllers/widget/session_taken_over.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/session_taken_over.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/switch_back_to_user.coffee b/app/assets/javascripts/app/controllers/_plugin/switch_back_to_user.coffee similarity index 70% rename from app/assets/javascripts/app/controllers/widget/switch_back_to_user.coffee rename to app/assets/javascripts/app/controllers/_plugin/switch_back_to_user.coffee index b5b026afd..e32fe07eb 100644 --- a/app/assets/javascripts/app/controllers/widget/switch_back_to_user.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/switch_back_to_user.coffee @@ -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("
") + $("##{@key}").html raw + +App.Config.set('switch_back_to_user', SwitchBackToUser, 'Plugins') diff --git a/app/assets/javascripts/app/controllers/widget/translation_inline.coffee b/app/assets/javascripts/app/controllers/_plugin/translation_inline.coffee similarity index 94% rename from app/assets/javascripts/app/controllers/widget/translation_inline.coffee rename to app/assets/javascripts/app/controllers/_plugin/translation_inline.coffee index b80fb772e..cb5505bca 100644 --- a/app/assets/javascripts/app/controllers/widget/translation_inline.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/translation_inline.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/widget/translation_support.coffee b/app/assets/javascripts/app/controllers/_plugin/translation_support.coffee similarity index 89% rename from app/assets/javascripts/app/controllers/widget/translation_support.coffee rename to app/assets/javascripts/app/controllers/_plugin/translation_support.coffee index 6ee5318e9..189a4e96f 100644 --- a/app/assets/javascripts/app/controllers/widget/translation_support.coffee +++ b/app/assets/javascripts/app/controllers/_plugin/translation_support.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/_plugin/user_signup_check.coffee b/app/assets/javascripts/app/controllers/_plugin/user_signup_check.coffee new file mode 100644 index 000000000..359840909 --- /dev/null +++ b/app/assets/javascripts/app/controllers/_plugin/user_signup_check.coffee @@ -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 += '

' + 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') diff --git a/app/assets/javascripts/app/controllers/_profile/avatar.coffee b/app/assets/javascripts/app/controllers/_profile/avatar.coffee index 3fc76751d..d176bbca2 100644 --- a/app/assets/javascripts/app/controllers/_profile/avatar.coffee +++ b/app/assets/javascripts/app/controllers/_profile/avatar.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/_profile/calendar_subscriptions.coffee b/app/assets/javascripts/app/controllers/_profile/calendar_subscriptions.coffee index d45545391..e38b75a37 100644 --- a/app/assets/javascripts/app/controllers/_profile/calendar_subscriptions.coffee +++ b/app/assets/javascripts/app/controllers/_profile/calendar_subscriptions.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/devices.coffee b/app/assets/javascripts/app/controllers/_profile/devices.coffee index bbe33021c..f4bbd4c78 100644 --- a/app/assets/javascripts/app/controllers/_profile/devices.coffee +++ b/app/assets/javascripts/app/controllers/_profile/devices.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/language.coffee b/app/assets/javascripts/app/controllers/_profile/language.coffee index efb22a23c..53458863c 100644 --- a/app/assets/javascripts/app/controllers/_profile/language.coffee +++ b/app/assets/javascripts/app/controllers/_profile/language.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee index f3556ff27..11d48a27d 100644 --- a/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee +++ b/app/assets/javascripts/app/controllers/_profile/linked_accounts.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/_profile/notification.coffee b/app/assets/javascripts/app/controllers/_profile/notification.coffee index b0c71aa99..03cf17993 100644 --- a/app/assets/javascripts/app/controllers/_profile/notification.coffee +++ b/app/assets/javascripts/app/controllers/_profile/notification.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/out_of_office.coffee b/app/assets/javascripts/app/controllers/_profile/out_of_office.coffee index 8f796f715..79c6c37dc 100644 --- a/app/assets/javascripts/app/controllers/_profile/out_of_office.coffee +++ b/app/assets/javascripts/app/controllers/_profile/out_of_office.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/password.coffee b/app/assets/javascripts/app/controllers/_profile/password.coffee index 5d60c79f6..e19a79ed5 100644 --- a/app/assets/javascripts/app/controllers/_profile/password.coffee +++ b/app/assets/javascripts/app/controllers/_profile/password.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_profile/token_access.coffee b/app/assets/javascripts/app/controllers/_profile/token_access.coffee index 1ef5635ae..294b9f73e 100644 --- a/app/assets/javascripts/app/controllers/_profile/token_access.coffee +++ b/app/assets/javascripts/app/controllers/_profile/token_access.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/_settings/form.coffee b/app/assets/javascripts/app/controllers/_settings/form.coffee index b3e9b0b57..895363a16 100644 --- a/app/assets/javascripts/app/controllers/_settings/form.coffee +++ b/app/assets/javascripts/app/controllers/_settings/form.coffee @@ -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 - } + }) ) diff --git a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee index 9536a56fd..42b80571b 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/ticket_perform_action.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee index eecfe4e67..1f952812b 100644 --- a/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee +++ b/app/assets/javascripts/app/controllers/_ui_element/user_permission.coffee @@ -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) diff --git a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee index 44774e636..890282624 100644 --- a/app/assets/javascripts/app/controllers/agent_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/agent_ticket_create.coffee @@ -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') ) diff --git a/app/assets/javascripts/app/controllers/api.coffee b/app/assets/javascripts/app/controllers/api.coffee index 0a325e3a5..af23fa0c5 100644 --- a/app/assets/javascripts/app/controllers/api.coffee +++ b/app/assets/javascripts/app/controllers/api.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/calendar.coffee b/app/assets/javascripts/app/controllers/calendar.coffee index 0b7007afa..50c7975c5 100644 --- a/app/assets/javascripts/app/controllers/calendar.coffee +++ b/app/assets/javascripts/app/controllers/calendar.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/chat.coffee b/app/assets/javascripts/app/controllers/chat.coffee index 54be8cfa7..da4a38a16 100644 --- a/app/assets/javascripts/app/controllers/chat.coffee +++ b/app/assets/javascripts/app/controllers/chat.coffee @@ -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("#{data.realname} 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("#{data.realname} 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 diff --git a/app/assets/javascripts/app/controllers/clues.coffee b/app/assets/javascripts/app/controllers/clues.coffee index 81037e9f0..d30eff91a 100644 --- a/app/assets/javascripts/app/controllers/clues.coffee +++ b/app/assets/javascripts/app/controllers/clues.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee index 58ee2ae46..743f92ce5 100644 --- a/app/assets/javascripts/app/controllers/cti.coffee +++ b/app/assets/javascripts/app/controllers/cti.coffee @@ -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' diff --git a/app/assets/javascripts/app/controllers/customer_ticket_create.coffee b/app/assets/javascripts/app/controllers/customer_ticket_create.coffee index b7be104c8..f7f7d5e76 100644 --- a/app/assets/javascripts/app/controllers/customer_ticket_create.coffee +++ b/app/assets/javascripts/app/controllers/customer_ticket_create.coffee @@ -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', diff --git a/app/assets/javascripts/app/controllers/dashboard.coffee b/app/assets/javascripts/app/controllers/dashboard.coffee index 5a8de62e5..bc7ef4f14 100644 --- a/app/assets/javascripts/app/controllers/dashboard.coffee +++ b/app/assets/javascripts/app/controllers/dashboard.coffee @@ -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 diff --git a/app/assets/javascripts/app/controllers/default_route.coffee b/app/assets/javascripts/app/controllers/default_route.coffee index b7a5e551b..4a09aa0aa 100644 --- a/app/assets/javascripts/app/controllers/default_route.coffee +++ b/app/assets/javascripts/app/controllers/default_route.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/email_verify.coffee b/app/assets/javascripts/app/controllers/email_verify.coffee index ae0c5b7d4..106c2442f 100644 --- a/app/assets/javascripts/app/controllers/email_verify.coffee +++ b/app/assets/javascripts/app/controllers/email_verify.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/getting_started.coffee b/app/assets/javascripts/app/controllers/getting_started.coffee index 2f8fde786..4c0142079 100644 --- a/app/assets/javascripts/app/controllers/getting_started.coffee +++ b/app/assets/javascripts/app/controllers/getting_started.coffee @@ -1,4 +1,4 @@ -class Index extends App.WizardFullScreen +class GettingStarted extends App.ControllerWizardFullScreen constructor: -> super @@ -21,9 +21,6 @@ class Index extends App.WizardFullScreen @fetch() - release: => - @el.removeClass('fit getstarted') - fetch: -> # get data @@ -51,1039 +48,9 @@ class Index extends App.WizardFullScreen ) render: -> - @html App.view('getting_started/intro')() + @replaceWith App.view('getting_started/intro')() renderAutoWizard: -> - @html App.view('getting_started/auto_wizard_enabled')() + @replaceWith App.view('getting_started/auto_wizard_enabled')() -App.Config.set('getting_started', Index, 'Routes') - -class AutoWizard extends App.WizardFullScreen - 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() - - release: => - @el.removeClass('fit getstarted') - - 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 - @navigate '#' - return - - # auto wizard setup was successful - if data.auto_wizard_success is true - - # login check / get session user - delay = => - App.Auth.loginCheck() - @navigate '#' - @delay(delay, 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 - @navigate '#login' - ) - - renderFailed: (data) -> - @html App.view('getting_started/auto_wizard_failed')(data) - - renderSplash: -> - @html App.view('getting_started/auto_wizard_splash')() - - renderToken: -> - @html App.view('getting_started/auto_wizard_enabled')() - -App.Config.set('getting_started/auto_wizard', AutoWizard, 'Routes') -App.Config.set('getting_started/auto_wizard/:token', AutoWizard, 'Routes') - -class Admin extends App.WizardFullScreen - 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() - - release: => - @el.removeClass('fit getstarted') - - 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' - # return - - # check if import is active - if data.import_mode == true - @navigate "#import/#{data.import_backend}" - return - - # load group collection - App.Collection.load(type: 'Group', data: data.groups) - - # render page - @render() - ) - - render: -> - - @html 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' - -App.Config.set('getting_started/admin', Admin, 'Routes') - -class Base extends App.WizardFullScreen - elements: - '.logo-preview': 'logoPreview' - - events: - 'submit form': 'submit' - 'change .js-upload': 'onLogoPick' - - constructor: -> - super - - # redirect if we are not admin - if !@permissionCheck('admin.wizard') - @navigate '#' - return - - # set title - @title 'Configure Base' - - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - # import config options - if data.config - for key, value of data.config - App.Config.set(key, value) - - # render page - @render() - ) - - render: -> - - fqdn = App.Config.get('fqdn') - http_type = App.Config.get('http_type') - if !fqdn || fqdn is 'zammad.example.com' - url = window.location.origin - else - url = "#{http_type}://#{fqdn}" - - organization = App.Config.get('organization') - @html App.view('getting_started/base')( - url: url - logoUrl: @logoUrl() - organization: organization - ) - @$('input, select').first().focus() - - onLogoPick: (event) => - reader = new FileReader() - - reader.onload = (e) => - @logoPreview.attr('src', e.target.result) - - file = event.target.files[0] - - @hideAlerts() - - # if no file is given, about in file upload was used - if !file - return - - maxSiteInMb = 8 - if file.size && file.size > 1024 * 1024 * maxSiteInMb - @showAlert( 'logo', App.i18n.translateInline('File too big, max. %s MB allowed.', maxSiteInMb )) - @logoPreview.attr('src', '') - return - - reader.readAsDataURL(file) - - submit: (e) => - e.preventDefault() - @hideAlerts() - @disable(e) - - @params = @formParam(e.target) - @params.logo = @logoPreview.attr('src') - @params.locale_default = App.i18n.detectBrowserLocale() - @params.timezone_default = App.i18n.detectBrowserTimezone() - - store = (logoResizeDataUrl) => - @params.logo_resize = logoResizeDataUrl - @ajax( - id: 'getting_started_base' - type: 'POST' - url: "#{@apiPath}/getting_started/base" - data: JSON.stringify(@params) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - for key, value of data.settings - App.Config.set(key, value) - if App.Config.get('system_online_service') - @navigate 'getting_started/channel/email_pre_configured' - else - @navigate 'getting_started/email_notification' - else - for key, value of data.messages - @showAlert(key, value) - @enable(e) - fail: => - @enable(e) - ) - - # add resized image - App.ImageService.resizeForApp(@params.logo, @logoPreview.width(), @logoPreview.height(), store) - - hideAlerts: => - @$('.form-group').removeClass('has-error') - @$('.alert').addClass('hide') - - showAlert: (field, message) => - @$("[name=#{field}]").closest('.form-group').addClass('has-error') - @$("[name=#{field}]").closest('.form-group').find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) ) - -App.Config.set('getting_started/base', Base, 'Routes') - -class EmailNotification extends App.WizardFullScreen - events: - 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' - 'submit .js-outbound': 'submit' - - constructor: -> - super - - # redirect if we are not admin - if !@permissionCheck('admin.wizard') - @navigate '#' - return - - # set title - @title 'Email Notifications' - - @channelDriver = - email: - inbound: {} - outbound: {} - - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - @channelDriver = data.channel_driver - - # render page - @render() - ) - - render: -> - @html App.view('getting_started/email_notification')() - configureAttributesOutbound = [ - { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: @channelDriver.email.outbound }, - ] - new App.ControllerForm( - el: @$('.base-outbound-type') - model: - configure_attributes: configureAttributesOutbound - className: '' - params: - adapter: 'sendmail' - ) - @toggleOutboundAdapter() - - toggleOutboundAdapter: => - - # show used backend - @el.find('.base-outbound-settings').html('') - adapter = @$('.js-outbound [name=adapter]').val() - if adapter is 'smtp' - configureAttributesOutbound = [ - { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, - { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off' }, - { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true }, - { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false }, - ] - @form = new App.ControllerForm( - el: @$('.base-outbound-settings') - model: - configure_attributes: configureAttributesOutbound - className: '' - ) - - submit: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - params['email'] = 'me@localhost' - @disable(e) - - @showSlide('js-test') - - @ajax( - id: 'email_notification' - type: 'POST' - url: "#{@apiPath}/channels_email_notification" - data: JSON.stringify(params) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - for key, value of data.settings - App.Config.set(key, value) - if App.Config.get('system_online_service') - @navigate 'getting_started/channel/email_pre_configured' - else - @navigate 'getting_started/channel' - else - @showSlide('js-outbound') - @showAlert('js-outbound', data.message_human || data.message ) - @showInvalidField('js-outbound', data.invalid_field) - @enable(e) - - fail: => - @showSlide('js-outbound') - @showAlert('js-outbound', data.message_human || data.message ) - @showInvalidField('js-outbound', data.invalid_field) - @enable(e) - ) - -App.Config.set('getting_started/email_notification', EmailNotification, 'Routes') - -class Channel extends App.WizardFullScreen - constructor: -> - super - - # redirect if we are not admin - if !@permissionCheck('admin.wizard') - @navigate '#' - return - - # set title - @title 'Connect Channels' - - @adapters = [ - { - name: 'Email' - class: 'email' - link: '#getting_started/channel/email' - }, - ] - - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - # render page - @render() - ) - - render: -> - @html App.view('getting_started/channel')( - adapters: @adapters - ) - -App.Config.set('getting_started/channel', Channel, 'Routes') - -class ChannelEmailPreConfigured extends App.WizardFullScreen - constructor: -> - super - - # redirect if we are not admin - if !@permissionCheck('admin.wizard') - @navigate '#' - return - - # set title - @title 'Connect Channels' - - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - # render page - @render(data) - ) - - render: (data) -> - @html App.view('getting_started/email_pre_configured')( - data - ) - -App.Config.set('getting_started/channel/email_pre_configured', ChannelEmailPreConfigured, 'Routes') - -class ChannelEmail extends App.WizardFullScreen - events: - 'submit .js-intro': 'probeBasedOnIntro' - 'submit .js-inbound': 'probeInbound' - 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' - 'submit .js-outbound': 'probleOutbound' - 'click .js-goToSlide': 'goToSlide' - - constructor: -> - super - - # redirect if we are not admin - if !@permissionCheck('admin.wizard') - @navigate '#' - return - - # set title - @title 'Email Account' - - # store account settings - @account = - inbound: {} - outbound: {} - meta: {} - - @channelDriver = - email: - inbound: {} - outbound: {} - - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - @channelDriver = data.channel_driver - - # render page - @render() - ) - - render: -> - - @html App.view('getting_started/email')() - @showSlide('js-intro') - - # outbound - configureAttributesOutbound = [ - { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: @channelDriver.email.outbound }, - ] - new App.ControllerForm( - el: @$('.base-outbound-type') - model: - configure_attributes: configureAttributesOutbound - className: '' - params: - adapter: @account.outbound.adapter || 'smtp' - ) - @toggleOutboundAdapter() - - # inbound - configureAttributesInbound = [ - { name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound }, - { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false }, - { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', }, - { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', single: true }, - { name: 'options::ssl', display: 'SSL/STARTTLS', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' }, - { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' }, - { name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' }, - { name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' }, - ] - - showHideFolder = (params, attribute, attributes, classname, form, ui) -> - return if !params - if params.adapter is 'imap' - ui.show('options::folder') - ui.show('options::keep_on_server') - return - ui.hide('options::folder') - ui.hide('options::keep_on_server') - - handlePort = (params, attribute, attributes, classname, form, ui) -> - return if !params - return if !params.options - currentPort = @$('.base-inbound-settings [name="options::port"]').val() - if params.options.ssl is true - if !currentPort - @$('.base-inbound-settings [name="options::port"]').val('993') - return - if params.options.ssl is false - if !currentPort || currentPort is '993' - @$('.base-inbound-settings [name="options::port"]').val('143') - return - - new App.ControllerForm( - el: @$('.base-inbound-settings') - model: - configure_attributes: configureAttributesInbound - className: '' - params: @account.inbound - handlers: [ - showHideFolder, - handlePort, - ] - ) - - toggleOutboundAdapter: => - - # fill user / password based on intro info - channel_used = { options: {} } - if @account['meta'] - channel_used['options']['user'] = @account['meta']['email'] - channel_used['options']['password'] = @account['meta']['password'] - channel_used['options']['folder'] = @account['meta']['folder'] - channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server'] - - # show used backend - @$('.base-outbound-settings').html('') - adapter = @$('.js-outbound [name=adapter]').val() - if adapter is 'smtp' - configureAttributesOutbound = [ - { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, - { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', }, - { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true }, - { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false }, - ] - @form = new App.ControllerForm( - el: @$('.base-outbound-settings') - model: - configure_attributes: configureAttributesOutbound - className: '' - params: @account.outbound - ) - - probeBasedOnIntro: (e) => - e.preventDefault() - params = @formParam(e.target) - - # remember account settings - @account.meta = params - - @disable(e) - @$('.js-probe .js-email').text(params.email) - @showSlide('js-probe') - - @ajax( - id: 'email_probe' - type: 'POST' - url: "#{@apiPath}/channels_email_probe" - data: JSON.stringify(params) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - if data.setting - for key, value of data.setting - @account[key] = value - - if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true) - @probeInboundMessagesFound(data, true) - @probeInboundArchive(data) - else - @verify(@account) - - else if data.result is 'duplicate' - @showSlide('js-intro') - @showAlert('js-intro', 'Account already exists!' ) - else - @showSlide('js-inbound') - @showAlert('js-inbound', 'Unable to detect your server settings. Manual configuration needed.' ) - @$('.js-inbound [name="options::user"]').val( @account['meta']['email'] ) - @$('.js-inbound [name="options::password"]').val( @account['meta']['password'] ) - - @enable(e) - fail: => - @enable(e) - @showSlide('js-intro') - ) - - probeInbound: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - @disable(e) - - @showSlide('js-test') - - @ajax( - id: 'email_inbound' - type: 'POST' - url: "#{@apiPath}/channels_email_inbound" - data: JSON.stringify(params) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - - # remember account settings - @account.inbound = params - - if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true) - @probeInboundMessagesFound(data, true) - @probeInboundArchive(data) - else - @showSlide('js-outbound') - - # fill user / password based on inbound settings - if !@channel - if @account['inbound']['options'] - @$('.js-outbound [name="options::host"]').val( @account['inbound']['options']['host'] ) - @$('.js-outbound [name="options::user"]').val( @account['inbound']['options']['user'] ) - @$('.js-outbound [name="options::password"]').val( @account['inbound']['options']['password'] ) - else - @$('.js-outbound [name="options::user"]').val( @account['meta']['email'] ) - @$('.js-outbound [name="options::password"]').val( @account['meta']['password'] ) - - else - @showSlide('js-inbound') - @showAlert('js-inbound', data.message_human || data.message ) - @showInvalidField('js-inbound', data.invalid_field) - @enable(e) - fail: => - @showSlide('js-inbound') - @showAlert('js-inbound', data.message_human || data.message ) - @showInvalidField('js-inbound', data.invalid_field) - @enable(e) - ) - - probeInboundMessagesFound: (data, verify) => - message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) - @$('.js-inbound-acknowledge .js-messageFound').html(message) - - if !verify - @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound') - @$('.js-inbound-acknowledge .js-next').unbind('click.verify') - else - @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro') - @$('.js-inbound-acknowledge .js-next').attr('data-slide', '') - @$('.js-inbound-acknowledge .js-next').unbind('click.verify').bind('click.verify', (e) => - e.preventDefault() - @verify(@account) - ) - @showSlide('js-inbound-acknowledge') - - probeInboundArchive: (data) => - if data.archive_possible isnt true - @$('.js-archiveMessage').addClass('hide') - return - - @$('.js-archiveMessage').removeClass('hide') - message = App.i18n.translateContent('In addition, we have found emails in your mailbox that are older than %s weeks. You can import such emails as an "archive", which means that no notifications are sent and the tickets have the status "closed". However, you can find them in Zammad anytime using the search function.', data.archive_week_range) - @$('.js-inbound-acknowledge .js-archiveMessageCount').html(message) - - configureAttributesAcknowledge = [ - { - name: 'archive' - tag: 'boolean' - null: true - default: no - options: { - true: 'archive' - false: 'regular' - } - translate: true - }, - ] - - new App.ControllerForm( - elReplace: @$('.js-importTypeSelect'), - model: - configure_attributes: configureAttributesAcknowledge - className: '' - noFieldset: true - ) - @$('.js-importTypeSelect select[name=archive]').on('change', (e) => - value = $(e.target).val() - @account.inbound ||= {} - @account.inbound.options ||= {} - if value is 'true' - @account.inbound.options.archive = true - @account.inbound.options.archive_before = (new Date()).toISOString() - else - delete @account.inbound.options.archive - delete @account.inbound.options.archive_before - ) - @$('.js-importTypeSelect select[name=archive]').trigger('change') - - probleOutbound: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - params['email'] = @account['meta']['email'] - @disable(e) - - @showSlide('js-test') - - @ajax( - id: 'email_outbound' - type: 'POST' - url: "#{@apiPath}/channels_email_outbound" - data: JSON.stringify(params) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - - # remember account settings - @account.outbound = params - - @verify(@account) - else - @showSlide('js-outbound') - @showAlert('js-outbound', data.message_human || data.message) - @showInvalidField('js-outbound', data.invalid_field) - @enable(e) - fail: => - @showSlide('js-outbound') - @showAlert('js-outbound', data.message_human || data.message) - @showInvalidField('js-outbound', data.invalid_field) - @enable(e) - ) - - verify: (account, count = 0) => - @showSlide('js-verify') - - @ajax( - id: 'email_verify' - type: 'POST' - url: "#{@apiPath}/channels_email_verify" - data: JSON.stringify(account) - processData: true - success: (data, status, xhr) => - if data.result is 'ok' - @navigate 'getting_started/agents' - else - if data.source is 'inbound' || data.source is 'outbound' - @showSlide("js-#{data.source}") - @showAlert("js-#{data.source}", data.message_human || data.message) - @showInvalidField("js-#{data.source}", data.invalid_field) - else - if count is 2 - @showAlert('js-verify', data.message_human || data.message) - @delay( - => - @showSlide('js-intro') - @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' ) - - 2300 - ) - else - if data.subject && @account - @account.subject = data.subject - @verify( @account, count + 1 ) - fail: => - @showSlide('js-intro') - @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.') - ) - -App.Config.set('getting_started/channel/email', ChannelEmail, 'Routes') - -class Agent extends App.WizardFullScreen - events: - 'submit form': 'submit' - - constructor: -> - super - @authenticateCheckRedirect() - - # set title - @title 'Invite Agents' - @fetch() - - release: => - @el.removeClass('fit getstarted') - - 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}" - return - - # load group collection - App.Collection.load(type: 'Group', data: data.groups) - - # render page - @render() - ) - - render: -> - - @html 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', Agent, 'Routes') - -class Channel extends App.WizardFullScreen - constructor: -> - super - @authenticateCheckRedirect() - - # set title - @title 'Setup Finished' - @render() - - release: => - @el.removeClass('fit getstarted') - - render: -> - @html App.view('getting_started/finish')() - @delay( - => @$('.wizard-slide').addClass('hide') - 2300 - ) - @delay( - => @navigate '#' - 4300 - ) - -App.Config.set('getting_started/finish', Channel, 'Routes') +App.Config.set('getting_started', GettingStarted, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/admin.coffee b/app/assets/javascripts/app/controllers/getting_started/admin.coffee new file mode 100644 index 000000000..2dfc7ed60 --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/admin.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/getting_started/agent.coffee b/app/assets/javascripts/app/controllers/getting_started/agent.coffee new file mode 100644 index 000000000..6a1bb932c --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/agent.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/getting_started/auto_wizard.coffee b/app/assets/javascripts/app/controllers/getting_started/auto_wizard.coffee new file mode 100644 index 000000000..12c79f317 --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/auto_wizard.coffee @@ -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') diff --git a/app/assets/javascripts/app/controllers/getting_started/base.coffee b/app/assets/javascripts/app/controllers/getting_started/base.coffee new file mode 100644 index 000000000..2f1b79a6a --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/base.coffee @@ -0,0 +1,130 @@ +class GettingStartedBase extends App.ControllerWizardFullScreen + elements: + '.logo-preview': 'logoPreview' + + events: + 'submit form': 'submit' + 'change .js-upload': 'onLogoPick' + + constructor: -> + super + + # redirect if we are not admin + if !@permissionCheck('admin.wizard') + @navigate '#' + return + + # set title + @title 'Configure Base' + + @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 + + # import config options + if data.config + for key, value of data.config + App.Config.set(key, value) + + # render page + @render() + ) + + render: -> + + fqdn = App.Config.get('fqdn') + http_type = App.Config.get('http_type') + if !fqdn || fqdn is 'zammad.example.com' + url = window.location.origin + else + url = "#{http_type}://#{fqdn}" + + organization = App.Config.get('organization') + @replaceWith App.view('getting_started/base')( + url: url + logoUrl: @logoUrl() + organization: organization + ) + @$('input, select').first().focus() + + onLogoPick: (event) => + reader = new FileReader() + + reader.onload = (e) => + @logoPreview.attr('src', e.target.result) + + file = event.target.files[0] + + @hideAlerts() + + # if no file is given, about in file upload was used + if !file + return + + maxSiteInMb = 8 + if file.size && file.size > 1024 * 1024 * maxSiteInMb + @showAlert( 'logo', App.i18n.translateInline('File too big, max. %s MB allowed.', maxSiteInMb )) + @logoPreview.attr('src', '') + return + + reader.readAsDataURL(file) + + submit: (e) => + e.preventDefault() + @hideAlerts() + @disable(e) + + @params = @formParam(e.target) + @params.logo = @logoPreview.attr('src') + @params.locale_default = App.i18n.detectBrowserLocale() + @params.timezone_default = App.i18n.detectBrowserTimezone() + + store = (logoResizeDataUrl) => + @params.logo_resize = logoResizeDataUrl + @ajax( + id: 'getting_started_base' + type: 'POST' + url: "#{@apiPath}/getting_started/base" + data: JSON.stringify(@params) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + for key, value of data.settings + App.Config.set(key, value) + if App.Config.get('system_online_service') + @navigate 'getting_started/channel/email_pre_configured', { emptyEl: true } + else + @navigate 'getting_started/email_notification', { emptyEl: true } + else + for key, value of data.messages + @showAlert(key, value) + @enable(e) + fail: => + @enable(e) + ) + + # add resized image + App.ImageService.resizeForApp(@params.logo, @logoPreview.width(), @logoPreview.height(), store) + + hideAlerts: => + @$('.form-group').removeClass('has-error') + @$('.alert').addClass('hide') + + showAlert: (field, message) => + @$("[name=#{field}]").closest('.form-group').addClass('has-error') + @$("[name=#{field}]").closest('.form-group').find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) ) + +App.Config.set('getting_started/base', GettingStartedBase, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/channel.coffee b/app/assets/javascripts/app/controllers/getting_started/channel.coffee new file mode 100644 index 000000000..71535e9fb --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/channel.coffee @@ -0,0 +1,47 @@ +class GettingStartedChannel extends App.ControllerWizardFullScreen + constructor: -> + super + + # redirect if we are not admin + if !@permissionCheck('admin.wizard') + @navigate '#' + return + + # set title + @title 'Connect Channels' + + @adapters = [ + { + name: 'Email' + class: 'email' + link: '#getting_started/channel/email' + }, + ] + + @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 + + # render page + @render() + ) + + render: -> + @replaceWith App.view('getting_started/channel')( + adapters: @adapters + ) + +App.Config.set('getting_started/channel', GettingStartedChannel, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/channel_email.coffee b/app/assets/javascripts/app/controllers/getting_started/channel_email.coffee new file mode 100644 index 000000000..d698b64b4 --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/channel_email.coffee @@ -0,0 +1,370 @@ +class GettingStartedChannelEmail extends App.ControllerWizardFullScreen + events: + 'submit .js-intro': 'probeBasedOnIntro' + 'submit .js-inbound': 'probeInbound' + 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' + 'submit .js-outbound': 'probleOutbound' + 'click .js-goToSlide': 'goToSlide' + + constructor: -> + super + + # redirect if we are not admin + if !@permissionCheck('admin.wizard') + @navigate '#' + return + + # set title + @title 'Email Account' + + # store account settings + @account = + inbound: {} + outbound: {} + meta: {} + + @channelDriver = + email: + inbound: {} + outbound: {} + + @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 + + @channelDriver = data.channel_driver + + # render page + @render() + ) + + render: -> + + @replaceWith App.view('getting_started/email')() + @showSlide('js-intro') + + # outbound + configureAttributesOutbound = [ + { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: @channelDriver.email.outbound }, + ] + new App.ControllerForm( + el: @$('.base-outbound-type') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: + adapter: @account.outbound.adapter || 'smtp' + ) + @toggleOutboundAdapter() + + # inbound + configureAttributesInbound = [ + { name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: @channelDriver.email.inbound }, + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, autocomplete: 'off', single: true }, + { name: 'options::ssl', display: 'SSL/STARTTLS', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, default: true, translate: true, item_class: 'formGroup--halfSize' }, + { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false, default: '993', item_class: 'formGroup--halfSize' }, + { name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, item_class: 'formGroup--halfSize' }, + { name: 'options::keep_on_server', display: 'Keep messages on server', tag: 'boolean', null: true, options: { true: 'yes', false: 'no' }, translate: true, default: false, item_class: 'formGroup--halfSize' }, + ] + + showHideFolder = (params, attribute, attributes, classname, form, ui) -> + return if !params + if params.adapter is 'imap' + ui.show('options::folder') + ui.show('options::keep_on_server') + return + ui.hide('options::folder') + ui.hide('options::keep_on_server') + + handlePort = (params, attribute, attributes, classname, form, ui) -> + return if !params + return if !params.options + currentPort = @$('.base-inbound-settings [name="options::port"]').val() + if params.options.ssl is true + if !currentPort + @$('.base-inbound-settings [name="options::port"]').val('993') + return + if params.options.ssl is false + if !currentPort || currentPort is '993' + @$('.base-inbound-settings [name="options::port"]').val('143') + return + + new App.ControllerForm( + el: @$('.base-inbound-settings') + model: + configure_attributes: configureAttributesInbound + className: '' + params: @account.inbound + handlers: [ + showHideFolder, + handlePort, + ] + ) + + toggleOutboundAdapter: => + + # fill user / password based on intro info + channel_used = { options: {} } + if @account['meta'] + channel_used['options']['user'] = @account['meta']['email'] + channel_used['options']['password'] = @account['meta']['password'] + channel_used['options']['folder'] = @account['meta']['folder'] + channel_used['options']['keep_on_server'] = @account['meta']['keep_on_server'] + + # show used backend + @$('.base-outbound-settings').html('') + adapter = @$('.js-outbound [name=adapter]').val() + if adapter is 'smtp' + configureAttributesOutbound = [ + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true }, + { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false }, + ] + @form = new App.ControllerForm( + el: @$('.base-outbound-settings') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: @account.outbound + ) + + probeBasedOnIntro: (e) => + e.preventDefault() + params = @formParam(e.target) + + # remember account settings + @account.meta = params + + @disable(e) + @$('.js-probe .js-email').text(params.email) + @showSlide('js-probe') + + @ajax( + id: 'email_probe' + type: 'POST' + url: "#{@apiPath}/channels_email_probe" + data: JSON.stringify(params) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + if data.setting + for key, value of data.setting + @account[key] = value + + if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true) + @probeInboundMessagesFound(data, true) + @probeInboundArchive(data) + else + @verify(@account) + + else if data.result is 'duplicate' + @showSlide('js-intro') + @showAlert('js-intro', 'Account already exists!' ) + else + @showSlide('js-inbound') + @showAlert('js-inbound', 'Unable to detect your server settings. Manual configuration needed.' ) + @$('.js-inbound [name="options::user"]').val( @account['meta']['email'] ) + @$('.js-inbound [name="options::password"]').val( @account['meta']['password'] ) + + @enable(e) + fail: => + @enable(e) + @showSlide('js-intro') + ) + + probeInbound: (e) => + e.preventDefault() + + # get params + params = @formParam(e.target) + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_inbound' + type: 'POST' + url: "#{@apiPath}/channels_email_inbound" + data: JSON.stringify(params) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + + # remember account settings + @account.inbound = params + + if data.content_messages && data.content_messages > 0 && (!@account['inbound']['options'] || @account['inbound']['options']['keep_on_server'] isnt true) + @probeInboundMessagesFound(data, true) + @probeInboundArchive(data) + else + @showSlide('js-outbound') + + # fill user / password based on inbound settings + if !@channel + if @account['inbound']['options'] + @$('.js-outbound [name="options::host"]').val( @account['inbound']['options']['host'] ) + @$('.js-outbound [name="options::user"]').val( @account['inbound']['options']['user'] ) + @$('.js-outbound [name="options::password"]').val( @account['inbound']['options']['password'] ) + else + @$('.js-outbound [name="options::user"]').val( @account['meta']['email'] ) + @$('.js-outbound [name="options::password"]').val( @account['meta']['password'] ) + + else + @showSlide('js-inbound') + @showAlert('js-inbound', data.message_human || data.message ) + @showInvalidField('js-inbound', data.invalid_field) + @enable(e) + fail: => + @showSlide('js-inbound') + @showAlert('js-inbound', data.message_human || data.message ) + @showInvalidField('js-inbound', data.invalid_field) + @enable(e) + ) + + probeInboundMessagesFound: (data, verify) => + message = App.i18n.translateContent('We have already found %s email(s) in your mailbox. Zammad will move it all from your mailbox into Zammad.', data.content_messages) + @$('.js-inbound-acknowledge .js-messageFound').html(message) + + if !verify + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-inbound') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify') + else + @$('.js-inbound-acknowledge .js-back').attr('data-slide', 'js-intro') + @$('.js-inbound-acknowledge .js-next').attr('data-slide', '') + @$('.js-inbound-acknowledge .js-next').unbind('click.verify').bind('click.verify', (e) => + e.preventDefault() + @verify(@account) + ) + @showSlide('js-inbound-acknowledge') + + probeInboundArchive: (data) => + if data.archive_possible isnt true + @$('.js-archiveMessage').addClass('hide') + return + + @$('.js-archiveMessage').removeClass('hide') + message = App.i18n.translateContent('In addition, we have found emails in your mailbox that are older than %s weeks. You can import such emails as an "archive", which means that no notifications are sent and the tickets have the status "closed". However, you can find them in Zammad anytime using the search function.', data.archive_week_range) + @$('.js-inbound-acknowledge .js-archiveMessageCount').html(message) + + configureAttributesAcknowledge = [ + { + name: 'archive' + tag: 'boolean' + null: true + default: no + options: { + true: 'archive' + false: 'regular' + } + translate: true + }, + ] + + new App.ControllerForm( + elReplace: @$('.js-importTypeSelect'), + model: + configure_attributes: configureAttributesAcknowledge + className: '' + noFieldset: true + ) + @$('.js-importTypeSelect select[name=archive]').on('change', (e) => + value = $(e.target).val() + @account.inbound ||= {} + @account.inbound.options ||= {} + if value is 'true' + @account.inbound.options.archive = true + @account.inbound.options.archive_before = (new Date()).toISOString() + else + delete @account.inbound.options.archive + delete @account.inbound.options.archive_before + ) + @$('.js-importTypeSelect select[name=archive]').trigger('change') + + probleOutbound: (e) => + e.preventDefault() + + # get params + params = @formParam(e.target) + params['email'] = @account['meta']['email'] + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_outbound' + type: 'POST' + url: "#{@apiPath}/channels_email_outbound" + data: JSON.stringify(params) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + + # remember account settings + @account.outbound = params + + @verify(@account) + else + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message) + @showInvalidField('js-outbound', data.invalid_field) + @enable(e) + fail: => + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message) + @showInvalidField('js-outbound', data.invalid_field) + @enable(e) + ) + + verify: (account, count = 0) => + @showSlide('js-verify') + + @ajax( + id: 'email_verify' + type: 'POST' + url: "#{@apiPath}/channels_email_verify" + data: JSON.stringify(account) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + @navigate 'getting_started/agents', { emptyEl: true } + else + if data.source is 'inbound' || data.source is 'outbound' + @showSlide("js-#{data.source}") + @showAlert("js-#{data.source}", data.message_human || data.message) + @showInvalidField("js-#{data.source}", data.invalid_field) + else + if count is 2 + @showAlert('js-verify', data.message_human || data.message) + @delay( + => + @showSlide('js-intro') + @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' ) + + 2300 + ) + else + if data.subject && @account + @account.subject = data.subject + @verify( @account, count + 1 ) + fail: => + @showSlide('js-intro') + @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.') + ) + +App.Config.set('getting_started/channel/email', GettingStartedChannelEmail, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/channel_email_pre_configured.coffee b/app/assets/javascripts/app/controllers/getting_started/channel_email_pre_configured.coffee new file mode 100644 index 000000000..83d0e6bea --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/channel_email_pre_configured.coffee @@ -0,0 +1,39 @@ +class GettingStartedChannelEmailPreConfigured extends App.ControllerWizardFullScreen + constructor: -> + super + + # redirect if we are not admin + if !@permissionCheck('admin.wizard') + @navigate '#' + return + + # set title + @title 'Connect Channels' + + @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 + + # render page + @render(data) + ) + + render: (data) -> + @replaceWith App.view('getting_started/email_pre_configured')( + data + ) + +App.Config.set('getting_started/channel/email_pre_configured', GettingStartedChannelEmailPreConfigured, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/email_notification.coffee b/app/assets/javascripts/app/controllers/getting_started/email_notification.coffee new file mode 100644 index 000000000..c9b38fc42 --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/email_notification.coffee @@ -0,0 +1,116 @@ +class GettingStartedEmailNotification extends App.ControllerWizardFullScreen + events: + 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' + 'submit .js-outbound': 'submit' + + constructor: -> + super + + # redirect if we are not admin + if !@permissionCheck('admin.wizard') + @navigate '#' + return + + # set title + @title 'Email Notifications' + + @channelDriver = + email: + inbound: {} + outbound: {} + + @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 + + @channelDriver = data.channel_driver + + # render page + @render() + ) + + render: => + @replaceWith App.view('getting_started/email_notification')() + configureAttributesOutbound = [ + { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: @channelDriver.email.outbound }, + ] + new App.ControllerForm( + el: @$('.base-outbound-type') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: + adapter: 'sendmail' + ) + @toggleOutboundAdapter() + + toggleOutboundAdapter: => + + # show used backend + @el.find('.base-outbound-settings').html('') + adapter = @$('.js-outbound [name=adapter]').val() + if adapter is 'smtp' + configureAttributesOutbound = [ + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, autocomplete: 'off' }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, autocomplete: 'off', single: true }, + { name: 'options::port', display: 'Port', tag: 'input', type: 'text', limit: 6, null: true, autocapitalize: false }, + ] + @form = new App.ControllerForm( + el: @$('.base-outbound-settings') + model: + configure_attributes: configureAttributesOutbound + className: '' + ) + + submit: (e) => + e.preventDefault() + + # get params + params = @formParam(e.target) + params['email'] = 'me@localhost' + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_notification' + type: 'POST' + url: "#{@apiPath}/channels_email_notification" + data: JSON.stringify(params) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + for key, value of data.settings + App.Config.set(key, value) + if App.Config.get('system_online_service') + @navigate 'getting_started/channel/email_pre_configured', { emptyEl: true } + else + @navigate 'getting_started/channel', { emptyEl: true } + else + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @showInvalidField('js-outbound', data.invalid_field) + @enable(e) + + fail: => + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @showInvalidField('js-outbound', data.invalid_field) + @enable(e) + ) + +App.Config.set('getting_started/email_notification', GettingStartedEmailNotification, 'Routes') diff --git a/app/assets/javascripts/app/controllers/getting_started/finish.coffee b/app/assets/javascripts/app/controllers/getting_started/finish.coffee new file mode 100644 index 000000000..8c8c8ad4a --- /dev/null +++ b/app/assets/javascripts/app/controllers/getting_started/finish.coffee @@ -0,0 +1,22 @@ +class GettingStartedFinish extends App.ControllerWizardFullScreen + constructor: -> + super + @authenticateCheckRedirect() + + # set title + @title 'Setup Finished' + @render() + + render: -> + @replaceWith App.view('getting_started/finish')() + @delay( + => @$('.setup.wizard').addClass('hide') + 2300 + ) + @delay( + => + @redirectToLogin() + 4300 + ) + +App.Config.set('getting_started/finish', GettingStartedFinish, 'Routes') diff --git a/app/assets/javascripts/app/controllers/groups.coffee b/app/assets/javascripts/app/controllers/group.coffee similarity index 89% rename from app/assets/javascripts/app/controllers/groups.coffee rename to app/assets/javascripts/app/controllers/group.coffee index 74e5f3708..c7e294fb1 100644 --- a/app/assets/javascripts/app/controllers/groups.coffee +++ b/app/assets/javascripts/app/controllers/group.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Group extends App.ControllerSubContent requiredPermission: 'admin.group' header: 'Groups' constructor: -> @@ -34,4 +34,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Index, permission: ['admin.group'] }, 'NavBarAdmin') +App.Config.set('Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Group, permission: ['admin.group'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/import.coffee b/app/assets/javascripts/app/controllers/import.coffee index 4bc8cdeda..32dec67d1 100644 --- a/app/assets/javascripts/app/controllers/import.coffee +++ b/app/assets/javascripts/app/controllers/import.coffee @@ -1,12 +1,7 @@ -class Import extends App.ControllerContent - className: 'getstarted fit' - +class Import extends App.ControllerWizardFullScreen constructor: -> super - - # set title @title 'Import' - @fetch() fetch: -> @@ -25,7 +20,7 @@ class Import extends App.ControllerContent return if data.import_mode == true - @navigate '#import/' + data.import_backend + @navigate '#import/' + data.import_backend, { emptyEl: true } return # render page @@ -36,7 +31,7 @@ class Import extends App.ControllerContent items = App.Config.get('ImportPlugins') - @html App.view('import/index')( + @replaceWith App.view('import/index')( items: items ) diff --git a/app/assets/javascripts/app/controllers/import_otrs.coffee b/app/assets/javascripts/app/controllers/import_otrs.coffee index 446ff037a..e60dce3f6 100644 --- a/app/assets/javascripts/app/controllers/import_otrs.coffee +++ b/app/assets/javascripts/app/controllers/import_otrs.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerContent +class ImportOtrs extends App.ControllerWizardFullScreen className: 'getstarted fit' elements: '.input-feedback': 'urlStatus' @@ -38,7 +38,7 @@ class Index extends App.ControllerContent # check if import is active if data.import_mode == true && data.import_backend != 'otrs' - @navigate "#import/#{data.import_backend}" + @navigate "#import/#{data.import_backend}", { emptyEl: true } return # render page @@ -50,7 +50,7 @@ class Index extends App.ControllerContent ) render: -> - @html App.view('import/otrs')() + @replaceWith App.view('import/otrs')() startDownload: (e) => @$('.js-otrs-link').removeClass('hide') @@ -170,7 +170,7 @@ class Index extends App.ControllerContent @delay(@updateMigration, 6500) ) -App.Config.set('import/otrs', Index, 'Routes') +App.Config.set('import/otrs', ImportOtrs, 'Routes') App.Config.set('otrs', { title: 'OTRS' name: 'OTRS' diff --git a/app/assets/javascripts/app/controllers/import_zendesk.coffee b/app/assets/javascripts/app/controllers/import_zendesk.coffee index c49004230..221bf88fe 100644 --- a/app/assets/javascripts/app/controllers/import_zendesk.coffee +++ b/app/assets/javascripts/app/controllers/import_zendesk.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerContent +class ImportZendesk extends App.ControllerWizardFullScreen className: 'getstarted fit' elements: '.input-feedback': 'urlStatus' @@ -44,7 +44,7 @@ class Index extends App.ControllerContent # check if import is active if data.import_mode == true && data.import_backend != 'zendesk' - @navigate "#import/#{data.import_backend}" + @navigate "#import/#{data.import_backend}", { emptyEl: true } return # render page @@ -56,7 +56,7 @@ class Index extends App.ControllerContent ) render: -> - @html App.view('import/zendesk')() + @replaceWith App.view('import/zendesk')() updateUrl: (e) => @urlStatus.attr('data-state', 'loading') @@ -189,7 +189,7 @@ class Index extends App.ControllerContent @delay(@updateMigration, 5000) ) -App.Config.set('import/zendesk', Index, 'Routes') +App.Config.set('import/zendesk', ImportZendesk, 'Routes') App.Config.set('zendesk', { title: 'Zendesk' name: 'Zendesk' diff --git a/app/assets/javascripts/app/controllers/integrations.coffee b/app/assets/javascripts/app/controllers/integrations.coffee index 996e6de51..c4c32f3af 100644 --- a/app/assets/javascripts/app/controllers/integrations.coffee +++ b/app/assets/javascripts/app/controllers/integrations.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Integrations extends App.ControllerSubContent requiredPermission: 'admin.integration' header: 'Integrations' constructor: -> @@ -64,4 +64,4 @@ class Index extends App.ControllerSubContent if @subscribeId App.Setting.unsubscribe(@subscribeId) -App.Config.set('Integration', { prio: 1000, name: 'Integrations', parent: '#system', target: '#system/integration', controller: Index, permission: ['admin.integration'] }, 'NavBarAdmin') +App.Config.set('Integration', { prio: 1000, name: 'Integrations', parent: '#system', target: '#system/integration', controller: Integrations, permission: ['admin.integration'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/job.coffee b/app/assets/javascripts/app/controllers/job.coffee index 49c379820..0962d1da8 100644 --- a/app/assets/javascripts/app/controllers/job.coffee +++ b/app/assets/javascripts/app/controllers/job.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Job extends App.ControllerSubContent requiredPermission: 'admin.scheduler' header: 'Scheduler' constructor: -> @@ -35,4 +35,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Job', { prio: 3400, name: 'Scheduler', parent: '#manage', target: '#manage/job', controller: Index, permission: ['admin.scheduler'] }, 'NavBarAdmin') +App.Config.set('Job', { prio: 3400, name: 'Scheduler', parent: '#manage', target: '#manage/job', controller: Job, permission: ['admin.scheduler'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/karma.coffee b/app/assets/javascripts/app/controllers/karma.coffee index 8a65c9ab4..94fa91b77 100644 --- a/app/assets/javascripts/app/controllers/karma.coffee +++ b/app/assets/javascripts/app/controllers/karma.coffee @@ -1,4 +1,4 @@ -class KarmaContent extends App.ControllerContent +class KarmaContent extends App.Controller constructor: -> new Karma() @@ -15,7 +15,7 @@ class Karma extends App.ControllerModal @load() # rerender view, e. g. on langauge change - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @update() 'karma' ) diff --git a/app/assets/javascripts/app/controllers/keyboard_shortcuts.coffee b/app/assets/javascripts/app/controllers/keyboard_shortcuts.coffee index 53d6b295d..84e8b617d 100644 --- a/app/assets/javascripts/app/controllers/keyboard_shortcuts.coffee +++ b/app/assets/javascripts/app/controllers/keyboard_shortcuts.coffee @@ -1,6 +1,6 @@ -class Index +class KeyboardShortcuts constructor: -> new App.KeyboardShortcutModal() -App.Config.set('keyboard_shortcuts', Index, 'Routes') +App.Config.set('keyboard_shortcuts', KeyboardShortcuts, 'Routes') App.Config.set('KeyboardShortcuts', { prio: 1700, parent: '#current_user', name: 'Keyboard Shortcuts', translate: true, target: '#keyboard_shortcuts', permission: ['admin', 'ticket.agent'] }, 'NavBarRight') diff --git a/app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee b/app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee index e516936f4..c8d9bca31 100644 --- a/app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee +++ b/app/assets/javascripts/app/controllers/knowledge_base/agent_controller.coffee @@ -9,7 +9,7 @@ class App.KnowledgeBaseAgentController extends App.Controller constructor: (params) -> super - @bind 'config_update_local', (data) => @configUpdated(data) + @controllerBind('config_update_local', (data) => @configUpdated(data)) if @permissionCheck('knowledge_base.*') and App.Config.get('kb_active') @updateNavMenu() @@ -36,7 +36,7 @@ class App.KnowledgeBaseAgentController extends App.Controller @fetchAndRender() - @bind('ui:rerender', + @controllerBind('ui:rerender', => @render(true) @contentController?.url = null @@ -44,13 +44,13 @@ class App.KnowledgeBaseAgentController extends App.Controller @show(@lastParams) ) - @bind 'kb_data_changed', (pushed_data) => + @controllerBind('kb_data_changed', (pushed_data) => key = "kb_pull_#{pushed_data.class}_#{pushed_data.id}" App.Delay.set( => @loadChange(pushed_data) , 1000, key, 'kb_data_changed_loading') - + ) @listenTo App.KnowledgeBase, 'kb_data_change_loaded', => return if !@displayingError @@ -139,7 +139,7 @@ class App.KnowledgeBaseAgentController extends App.Controller return if @loaded && @rendered && @lastParams && !params.knowledge_base_id && @contentController && @kb_locale()? - @navigate @lastParams.match[0] , true + @navigate @lastParams.match[0] , { hideCurrentLocationFromHistory: true } return if @contentController && @contentController.url is params.match[0] @@ -162,7 +162,7 @@ class App.KnowledgeBaseAgentController extends App.Controller @renderControllers(params) else if (kb = App.KnowledgeBase.all()[0]) - @navigate kb.uiUrl(App.KnowledgeBaseLocale.detect(kb)), true + @navigate kb.uiUrl(App.KnowledgeBaseLocale.detect(kb)), { hideCurrentLocationFromHistory: true } else @renderScreenErrorInContent('No Knowledge Base created') else diff --git a/app/assets/javascripts/app/controllers/knowledge_base/navigation.coffee b/app/assets/javascripts/app/controllers/knowledge_base/navigation.coffee index ae995dd44..2825db9e9 100644 --- a/app/assets/javascripts/app/controllers/knowledge_base/navigation.coffee +++ b/app/assets/javascripts/app/controllers/knowledge_base/navigation.coffee @@ -11,7 +11,7 @@ class App.KnowledgeBaseNavigation extends App.Controller super @render() - @bind 'knowledge_base::navigation::rerender', => @needsUpdate() + @controllerBind('knowledge_base::navigation::rerender', => @needsUpdate()) @listenTo App.KnowledgeBase, 'kb_data_change_loaded', => @needsUpdate() diff --git a/app/assets/javascripts/app/controllers/knowledge_base/sidebar.coffee b/app/assets/javascripts/app/controllers/knowledge_base/sidebar.coffee index 222af68a3..9543adc76 100644 --- a/app/assets/javascripts/app/controllers/knowledge_base/sidebar.coffee +++ b/app/assets/javascripts/app/controllers/knowledge_base/sidebar.coffee @@ -8,7 +8,7 @@ class App.KnowledgeBaseSidebar extends App.Controller super @show() - @bind 'knowledge_base::sidebar::rerender', => @rerender() + @controllerBind 'knowledge_base::sidebar::rerender', => @rerender() @listenTo App.KnowledgeBase, 'kb_data_change_loaded', => @rerender() diff --git a/app/assets/javascripts/app/controllers/layout_ref.coffee b/app/assets/javascripts/app/controllers/layout_ref.coffee index 276209ee0..fe9b4a6b9 100644 --- a/app/assets/javascripts/app/controllers/layout_ref.coffee +++ b/app/assets/javascripts/app/controllers/layout_ref.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerContent +class LayoutRef extends App.ControllerAppContent constructor: -> super @render() @@ -6,10 +6,10 @@ class Index extends App.ControllerContent render: -> @html App.view('layout_ref/index')() -App.Config.set( 'layout_ref', Index, 'Routes' ) +App.Config.set('layout_ref', LayoutRef, 'Routes') -class Content extends App.ControllerContent +class Content extends App.ControllerAppContent events: 'hide.bs.dropdown .js-recipientDropdown': 'hideOrganizationMembers' 'click .js-organization': 'showOrganizationMembers' @@ -103,7 +103,7 @@ class Content extends App.ControllerContent App.Config.set( 'layout_ref/content', Content, 'Routes' ) -class CommunicationOverview extends App.ControllerContent +class CommunicationOverview extends App.ControllerAppContent events: 'click .js-unfold': 'unfold' @@ -153,7 +153,7 @@ class CommunicationOverview extends App.ControllerContent App.Config.set( 'layout_ref/communication_overview', CommunicationOverview, 'Routes' ) -class LayoutRefCommunicationReply extends App.ControllerContent +class LayoutRefCommunicationReply extends App.ControllerAppContent elements: '.js-textarea' : 'textarea' '.attachmentPlaceholder': 'attachmentPlaceholder' @@ -389,7 +389,7 @@ App.Config.set( 'layout_ref/communication_reply/:content', LayoutRefCommunicatio -class ContentSidebarRight extends App.ControllerContent +class ContentSidebarRight extends App.ControllerAppContent constructor: -> super @render() @@ -400,7 +400,7 @@ class ContentSidebarRight extends App.ControllerContent App.Config.set( 'layout_ref/content_sidebar_right', ContentSidebarRight, 'Routes' ) -class ContentSidebarRightSidebarOptional extends App.ControllerContent +class ContentSidebarRightSidebarOptional extends App.ControllerAppContent constructor: -> super @render() @@ -442,7 +442,7 @@ class ModalText extends App.ControllerModal App.Config.set( 'layout_ref/modal_text', ModalText, 'Routes' ) -class ContentSidebarTabsRight extends App.ControllerContent +class ContentSidebarTabsRight extends App.ControllerAppContent elements: '.tabsSidebar' : 'sidebar' @@ -510,7 +510,7 @@ class ContentSidebarTabsRight extends App.ControllerContent App.Config.set( 'layout_ref/content_sidebar_tabs_right', ContentSidebarTabsRight, 'Routes' ) -class ContentSidebarLeft extends App.ControllerContent +class ContentSidebarLeft extends App.ControllerAppContent constructor: -> super @render() @@ -521,7 +521,7 @@ class ContentSidebarLeft extends App.ControllerContent App.Config.set( 'layout_ref/content_sidebar_left', ContentSidebarLeft, 'Routes' ) -class App.ControllerWizard extends App.ControllerContent +class App.ControllerWizard extends App.ControllerAppContent elements: '[data-slide]': 'slides' @@ -593,7 +593,7 @@ class ImportWizard extends App.ControllerWizard App.Config.set( 'layout_ref/import_wizard', ImportWizard, 'Routes' ) -class ReferenceUserProfile extends App.ControllerContent +class ReferenceUserProfile extends App.ControllerAppContent constructor: -> super @@ -604,7 +604,7 @@ class ReferenceUserProfile extends App.ControllerContent App.Config.set( 'layout_ref/user_profile', ReferenceUserProfile, 'Routes' ) -class ReferenceOrganizationProfile extends App.ControllerContent +class ReferenceOrganizationProfile extends App.ControllerAppContent constructor: -> super @@ -656,7 +656,7 @@ class ReferenceSetupWizard extends App.ControllerWizard App.Config.set( 'layout_ref/setup', ReferenceSetupWizard, 'Routes' ) -class RichText extends App.ControllerContent +class RichText extends App.ControllerAppContent constructor: -> super @render() @@ -724,7 +724,7 @@ class RichText extends App.ControllerContent App.Config.set( 'layout_ref/richtext', RichText, 'Routes' ) -class LocalModalRef extends App.ControllerContent +class LocalModalRef extends App.ControllerAppContent constructor: -> super @@ -735,7 +735,7 @@ class LocalModalRef extends App.ControllerContent App.Config.set( 'layout_ref/local_modal', LocalModalRef, 'Routes' ) -class LoadingPlaceholderRef extends App.ControllerContent +class LoadingPlaceholderRef extends App.ControllerAppContent constructor: -> super @@ -746,7 +746,7 @@ class LoadingPlaceholderRef extends App.ControllerContent App.Config.set( 'layout_ref/loading_placeholder', LoadingPlaceholderRef, 'Routes' ) -class InsufficientRightsRef extends App.ControllerContent +class InsufficientRightsRef extends App.ControllerAppContent constructor: -> super @@ -758,7 +758,7 @@ class InsufficientRightsRef extends App.ControllerContent App.Config.set( 'layout_ref/insufficient_rights', InsufficientRightsRef, 'Routes' ) -class ErrorRef extends App.ControllerContent +class ErrorRef extends App.ControllerAppContent constructor: -> super @@ -770,7 +770,7 @@ class ErrorRef extends App.ControllerContent App.Config.set( 'layout_ref/error', ErrorRef, 'Routes' ) -class TicketZoomRef extends App.ControllerContent +class TicketZoomRef extends App.ControllerAppContent elements: '.article-text': 'articles' '.js-highlight-icon': 'highlightIcon' @@ -980,7 +980,7 @@ class TicketZoomRef extends App.ControllerContent App.Config.set( 'layout_ref/ticket_zoom', TicketZoomRef, 'Routes' ) -class CluesRef extends App.ControllerContent +class CluesRef extends App.ControllerAppContent clues: [ { @@ -1298,7 +1298,7 @@ class CluesRef extends App.ControllerContent App.Config.set( 'layout_ref/clues', CluesRef, 'Routes' ) -class AdminPlaceholderRef extends App.ControllerContent +class AdminPlaceholderRef extends App.ControllerAppContent constructor: -> super @@ -1309,7 +1309,7 @@ class AdminPlaceholderRef extends App.ControllerContent App.Config.set( 'layout_ref/admin_placeholder', AdminPlaceholderRef, 'Routes' ) -class UserListRef extends App.ControllerContent +class UserListRef extends App.ControllerAppContent constructor: -> super @@ -1321,7 +1321,7 @@ class UserListRef extends App.ControllerContent App.Config.set( 'layout_ref/user_list', UserListRef, 'Routes' ) -class SlaRef extends App.ControllerContent +class SlaRef extends App.ControllerAppContent events: 'click .js-activateColumn': 'activateColumn' @@ -1377,7 +1377,7 @@ class SlaRef extends App.ControllerContent App.Config.set( 'layout_ref/sla', SlaRef, 'Routes' ) -class SchedulersRef extends App.ControllerContent +class SchedulersRef extends App.ControllerAppContent events: 'click .select-value': 'select' 'click [data-type=new]': 'createNew' @@ -1457,7 +1457,7 @@ class SchedulersRef extends App.ControllerContent App.Config.set( 'layout_ref/schedulers', SchedulersRef, 'Routes' ) -class InputsRef extends App.ControllerContent +class InputsRef extends App.ControllerAppContent constructor: -> super @@ -1584,7 +1584,7 @@ class InputsRef extends App.ControllerContent App.Config.set( 'layout_ref/inputs', InputsRef, 'Routes' ) -class CalendarSubscriptionsRef extends App.ControllerContent +class CalendarSubscriptionsRef extends App.ControllerAppContent elements: 'input[type=checkbox]': 'options' @@ -1660,7 +1660,7 @@ class CalendarSubscriptionsRef extends App.ControllerContent App.Config.set( 'layout_ref/calendar_subscriptions', CalendarSubscriptionsRef, 'Routes' ) -class ButtonsRef extends App.ControllerContent +class ButtonsRef extends App.ControllerAppContent elements: '.js-submitDropdown': 'buttonDropdown' @@ -1697,7 +1697,7 @@ class ButtonsRef extends App.ControllerContent App.Config.set( 'layout_ref/buttons', ButtonsRef, 'Routes' ) -class MergeCustomerRef extends App.ControllerContent +class MergeCustomerRef extends App.ControllerAppContent mergeTarget: firstname: 'Nicole', @@ -1747,7 +1747,7 @@ class MergeCustomerRef extends App.ControllerContent App.Config.set( 'layout_ref/merge_customer', MergeCustomerRef, 'Routes' ) -class PrimaryEmailRef extends App.ControllerContent +class PrimaryEmailRef extends App.ControllerAppContent constructor: -> super @@ -2164,7 +2164,7 @@ class ChatWindowRef extends Spine.Controller @scrollHolder.scrollTop(@scrollHolder.prop('scrollHeight')) -class AdminLoadRef extends App.ControllerContent +class AdminLoadRef extends App.ControllerAppContent constructor: -> super @@ -2176,7 +2176,7 @@ class AdminLoadRef extends App.ControllerContent App.Config.set( 'layout_ref/admin_loading', AdminLoadRef, 'Routes' ) -class TwitterConversationRef extends App.ControllerContent +class TwitterConversationRef extends App.ControllerAppContent elements: '.js-textarea': 'textarea' '.article-add': 'articleNewEdit' @@ -2264,7 +2264,7 @@ class TwitterConversationRef extends App.ControllerContent App.Config.set( 'layout_ref/twitter_conversation', TwitterConversationRef, 'Routes' ) -class UI extends App.ControllerContent +class UI extends App.ControllerAppContent constructor: -> super @render() @@ -2274,7 +2274,7 @@ class UI extends App.ControllerContent App.Config.set( 'layout_ref/ui', UI, 'Routes' ) -class ChatToTicketRef extends App.ControllerContent +class ChatToTicketRef extends App.ControllerAppContent elements: '.js-scrollHolder': 'scrollHolder' @@ -2309,7 +2309,7 @@ class ChatToTicketRef extends App.ControllerContent App.Config.set('layout_ref/chat_to_ticket', ChatToTicketRef, 'Routes') -class KnowledgeBaseAgentReaderRef extends App.ControllerContent +class KnowledgeBaseAgentReaderRef extends App.ControllerAppContent className: 'flex knowledge-base vertical' elements: @@ -2349,7 +2349,7 @@ class KnowledgeBaseAgentReaderRef extends App.ControllerContent App.Config.set('layout_ref/kb_agent_reader', KnowledgeBaseAgentReaderRef, 'Routes') -class KnowledgeBaseLinkTicketToAnswerRef extends App.ControllerContent +class KnowledgeBaseLinkTicketToAnswerRef extends App.ControllerAppContent constructor: -> super App.Utils.loadIconFont('anticon') @@ -2364,7 +2364,7 @@ class KnowledgeBaseLinkTicketToAnswerRef extends App.ControllerContent App.Config.set('layout_ref/kb_link_ticket_to_answer', KnowledgeBaseLinkTicketToAnswerRef, 'Routes') -class KnowledgeBaseLinkAnswerToAnswerRef extends App.ControllerContent +class KnowledgeBaseLinkAnswerToAnswerRef extends App.ControllerAppContent elements: '.js-form': 'form' diff --git a/app/assets/javascripts/app/controllers/login.coffee b/app/assets/javascripts/app/controllers/login.coffee index c344770c9..3874d9093 100644 --- a/app/assets/javascripts/app/controllers/login.coffee +++ b/app/assets/javascripts/app/controllers/login.coffee @@ -1,6 +1,7 @@ -class Index extends App.ControllerContent +class Login extends App.ControllerFullPage events: 'submit #login': 'login' + className: 'login' constructor: -> super @@ -15,14 +16,12 @@ class Index extends App.ControllerContent @navigate '#' return - @navHide() - @title 'Sign in' @render() @navupdate '#login' # observe config changes related to login page - @bind('config_update_local', (data) => + @controllerBind('config_update_local', (data) => return if !data.name.match(/^maintenance/) && !data.name.match(/^auth/) && data.name != 'user_lost_password' && @@ -33,7 +32,7 @@ class Index extends App.ControllerContent @render() 'rerender' ) - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @render() ) @@ -44,7 +43,7 @@ class Index extends App.ControllerContent if @Config.get(provider.config) is true || @Config.get(provider.config) is 'true' auth_providers.push provider - @html App.view('login')( + @replaceWith App.view('login')( item: data logoUrl: @logoUrl() auth_providers: auth_providers @@ -77,6 +76,7 @@ class Index extends App.ControllerContent ) success: (data, status, xhr) => + App.Plugin.init() # redirect to # requested_url = @Config.get('requested_url') @@ -110,4 +110,4 @@ class Index extends App.ControllerContent 600 ) -App.Config.set('login', Index, 'Routes') +App.Config.set('login', Login, 'Routes') diff --git a/app/assets/javascripts/app/controllers/logout.coffee b/app/assets/javascripts/app/controllers/logout.coffee index 7fb2b1bd1..639c152b4 100644 --- a/app/assets/javascripts/app/controllers/logout.coffee +++ b/app/assets/javascripts/app/controllers/logout.coffee @@ -1,8 +1,6 @@ -class Index extends App.ControllerContent +class Logout constructor: -> - super - App.Auth.logout() -App.Config.set('logout', Index, 'Routes') +App.Config.set('logout', Logout, 'Routes') App.Config.set('Logout', { prio: 1800, parent: '#current_user', name: 'Sign out', translate: true, target: '#logout', divider: true, iconClass: 'signout' }, 'NavBarRight') diff --git a/app/assets/javascripts/app/controllers/macro.coffee b/app/assets/javascripts/app/controllers/macro.coffee index 801db2459..1f923d515 100644 --- a/app/assets/javascripts/app/controllers/macro.coffee +++ b/app/assets/javascripts/app/controllers/macro.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Macro extends App.ControllerSubContent requiredPermission: 'admin.macro' header: 'Macros' constructor: -> @@ -34,4 +34,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Macros', { prio: 2310, name: 'Macros', parent: '#manage', target: '#manage/macros', controller: Index, permission: ['admin.macro'] }, 'NavBarAdmin') +App.Config.set('Macros', { prio: 2310, name: 'Macros', parent: '#manage', target: '#manage/macros', controller: Macro, permission: ['admin.macro'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/maintenance.coffee b/app/assets/javascripts/app/controllers/maintenance.coffee index a13ac5ad6..9b94cc958 100644 --- a/app/assets/javascripts/app/controllers/maintenance.coffee +++ b/app/assets/javascripts/app/controllers/maintenance.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Maintenance extends App.ControllerSubContent requiredPermission: 'admin.maintenance' header: 'Maintenance' events: @@ -88,4 +88,4 @@ class Index extends App.ControllerSubContent removeAll: true @render() -App.Config.set('Maintenance', { prio: 3600, name: 'Maintenance', parent: '#system', target: '#system/maintenance', controller: Index, permission: ['admin.maintenance'] }, 'NavBarAdmin') +App.Config.set('Maintenance', { prio: 3600, name: 'Maintenance', parent: '#system', target: '#system/maintenance', controller: Maintenance, permission: ['admin.maintenance'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/monitoring.coffee b/app/assets/javascripts/app/controllers/monitoring.coffee index 2b6900248..be4dd4e84 100644 --- a/app/assets/javascripts/app/controllers/monitoring.coffee +++ b/app/assets/javascripts/app/controllers/monitoring.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Monitoring extends App.ControllerSubContent requiredPermission: 'admin.monitoring' header: 'Monitoring' events: @@ -53,4 +53,4 @@ class Index extends App.ControllerSubContent @load() ) -App.Config.set('Monitoring', { prio: 3600, name: 'Monitoring', parent: '#system', target: '#system/monitoring', controller: Index, permission: ['admin.monitoring'] }, 'NavBarAdmin') +App.Config.set('Monitoring', { prio: 3600, name: 'Monitoring', parent: '#system', target: '#system/monitoring', controller: Monitoring, permission: ['admin.monitoring'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/object_manager.coffee b/app/assets/javascripts/app/controllers/object_manager.coffee index 01e712761..1e4d0f6e9 100644 --- a/app/assets/javascripts/app/controllers/object_manager.coffee +++ b/app/assets/javascripts/app/controllers/object_manager.coffee @@ -39,7 +39,7 @@ treeParams = (e, params) -> params.data_option.options = tree params -class Index extends App.ControllerTabs +class ObjectManager extends App.ControllerTabs requiredPermission: 'admin.object' constructor: -> super @@ -278,4 +278,4 @@ class Edit extends App.ControllerGenericEdit ui.controller.showAlert(details.error_human || details.error || 'Unable to update object!') ) -App.Config.set('SystemObject', { prio: 1700, parent: '#system', name: 'Objects', target: '#system/object_manager', controller: Index, permission: ['admin.object'] }, 'NavBarAdmin') +App.Config.set('SystemObject', { prio: 1700, parent: '#system', name: 'Objects', target: '#system/object_manager', controller: ObjectManager, permission: ['admin.object'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/organizations.coffee b/app/assets/javascripts/app/controllers/organization.coffee similarity index 91% rename from app/assets/javascripts/app/controllers/organizations.coffee rename to app/assets/javascripts/app/controllers/organization.coffee index 05a20ecb3..99ef60e06 100644 --- a/app/assets/javascripts/app/controllers/organizations.coffee +++ b/app/assets/javascripts/app/controllers/organization.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Organization extends App.ControllerSubContent requiredPermission: 'admin.organization' header: 'Organizations' constructor: -> @@ -41,4 +41,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Index, permission: ['admin.organization'] }, 'NavBarAdmin') +App.Config.set('Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Organization, permission: ['admin.organization'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/organization_profile.coffee b/app/assets/javascripts/app/controllers/organization_profile.coffee index cb4caaf0f..f3ffc80a9 100644 --- a/app/assets/javascripts/app/controllers/organization_profile.coffee +++ b/app/assets/javascripts/app/controllers/organization_profile.coffee @@ -73,7 +73,7 @@ class App.OrganizationProfile extends App.Controller currentPosition: => @$('.profile').scrollTop() -class ActionRow extends App.ObserverActionRow +class ActionRow extends App.ControllerObserverActionRow model: 'Organization' observe: member_ids: true @@ -110,7 +110,7 @@ class ActionRow extends App.ObserverActionRow } ] -class Object extends App.ObserverController +class Object extends App.ControllerObserver model: 'Organization' observe: member_ids: true @@ -182,7 +182,7 @@ class Object extends App.ObserverController org.updateAttributes(data) @log 'debug', 'update', name, value, org -class Organization extends App.ObserverController +class Organization extends App.ControllerObserver model: 'Organization' observe: name: true @@ -190,7 +190,7 @@ class Organization extends App.ObserverController render: (organization) => @html App.Utils.htmlEscape(organization.displayName()) -class Member extends App.ObserverController +class Member extends App.ControllerObserver model: 'User' observe: firstname: true diff --git a/app/assets/javascripts/app/controllers/overview.coffee b/app/assets/javascripts/app/controllers/overview.coffee index b52a04455..1aad70caa 100644 --- a/app/assets/javascripts/app/controllers/overview.coffee +++ b/app/assets/javascripts/app/controllers/overview.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Overview extends App.ControllerSubContent requiredPermission: 'admin.overview' header: 'Overviews' constructor: -> @@ -46,4 +46,4 @@ class Index extends App.ControllerSubContent ) ) -App.Config.set('Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, permission: ['admin.overview'] }, 'NavBarAdmin') +App.Config.set('Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Overview, permission: ['admin.overview'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/package.coffee b/app/assets/javascripts/app/controllers/package.coffee index 8f7bb7af0..6ec8ab518 100644 --- a/app/assets/javascripts/app/controllers/package.coffee +++ b/app/assets/javascripts/app/controllers/package.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Package extends App.ControllerSubContent requiredPermission: 'admin.package' header: 'Packages' events: @@ -54,4 +54,4 @@ class Index extends App.ControllerSubContent @load() ) -App.Config.set('Packages', { prio: 3700, name: 'Packages', parent: '#system', target: '#system/package', controller: Index, permission: ['admin.package'] }, 'NavBarAdmin') +App.Config.set('Packages', { prio: 3700, name: 'Packages', parent: '#system', target: '#system/package', controller: Package, permission: ['admin.package'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/password_reset.coffee b/app/assets/javascripts/app/controllers/password_reset.coffee index ea1060dea..bff592332 100644 --- a/app/assets/javascripts/app/controllers/password_reset.coffee +++ b/app/assets/javascripts/app/controllers/password_reset.coffee @@ -1,8 +1,10 @@ -class Index extends App.ControllerContent +class PasswordReset extends App.ControllerFullPage events: 'submit form': 'submit' 'click .submit': 'submit' 'click .retry': 'retry' + forceRender: true + className: 'reset_password' constructor: -> super @@ -17,8 +19,6 @@ class Index extends App.ControllerContent @navigate '#' return - @navHide() - # set title @title 'Reset Password' @navupdate '#password_reset' @@ -30,10 +30,10 @@ class Index extends App.ControllerContent { name: 'username', display: 'Enter your username or email address', tag: 'input', type: 'text', limit: 100, null: false, class: 'input span4' } ] - @html App.view('password/reset')(params) + @replaceWith(App.view('password/reset')(params)) @form = new App.ControllerForm( - el: @el.find('.form-password-item') + el: @el.find('.js-password') model: { configure_attributes: configure_attributes } autofocus: true ) @@ -64,142 +64,6 @@ class Index extends App.ControllerContent redirect = => @navigate "#password_reset_verify/#{data.token}" @delay(redirect, 2000) - @render(sent: true) + @html(App.view('password/reset_sent')()) -App.Config.set('password_reset', Index, 'Routes') - -class Verify extends App.ControllerContent - events: - 'submit form': 'submit' - 'click .submit': 'submit' - - constructor: -> - super - - # go back if feature is not enabled - if !@Config.get('user_lost_password') - @navigate '#' - return - - # if we are logged in, no passwort reset is wanted, redirect to app - if @authenticateCheck() - @navigate '#' - return - - @navHide() - - # set title - @title 'Reset Password' - @navupdate '#password_reset_verify' - - # get data - params = - token: @token - @ajax( - id: 'password_reset_verify' - type: 'POST' - url: "#{@apiPath}/users/password_reset_verify" - data: JSON.stringify(params) - processData: true - success: @renderChange - ) - - renderChange: (data) => - if data.message is 'ok' - configure_attributes = [ - { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 100, null: false, class: 'input' } - ] - - @html App.view('password/reset_change')() - - new App.ControllerForm( - el: @el.find('.form-password-change') - model: { configure_attributes: configure_attributes } - autofocus: true - ) - else - @html App.view('password/reset_failed')( - head: 'Reset Password failed!' - message: 'Token is invalid!' - ) - - submit: (e) -> - e.preventDefault() - params = @formParam(e.target) - params['token'] = @token - @password = params['password'] - - # disable form - @formDisable(e) - - # validate - if params['password_confirm'] isnt params['password'] - @formEnable(e) - @$('[name=password]').val('') - @$('[name=password_confirm]').val('') - @notify - type: 'error' - msg: 'Can\'t update password, your new passwords do not match. Please try again!' - removeAll: true - return - if !params['password'] - @formEnable(e) - @notify - type: 'error' - msg: 'Please supply your new password!' - removeAll: true - return - - # get data - @ajax( - id: 'password_reset_verify' - type: 'POST' - url: "#{@apiPath}/users/password_reset_verify" - data: JSON.stringify(params) - processData: true - success: @renderChanged - ) - - renderChanged: (data, status, xhr) => - if data.message is 'ok' - App.Auth.login( - data: - username: data.user_login - password: @password - success: => - - # login check - App.Auth.loginCheck() - - # add notify - @notify - type: 'success' - msg: 'Woo hoo! Your password has been changed!' - removeAll: true - - # redirect to # - @navigate '#' - - error: => - @formEnable( @$('form') ) - - # add notify - @notify - type: 'error' - msg: 'Something went wrong. Please contact your administrator.' - removeAll: true - ) - else - if data.notice - @notify - type: 'error' - msg: App.i18n.translateContent( data.notice[0], data.notice[1] ) - removeAll: true - else - @notify - type: 'error' - msg: 'Unable to set password. Please contact your administrator.' - removeAll: true - @formEnable( @$('form') ) - -App.Config.set('password_reset_verify/:token', Verify, 'Routes') +App.Config.set('password_reset', PasswordReset, 'Routes') diff --git a/app/assets/javascripts/app/controllers/password_reset_verify.coffee b/app/assets/javascripts/app/controllers/password_reset_verify.coffee new file mode 100644 index 000000000..78bc5d424 --- /dev/null +++ b/app/assets/javascripts/app/controllers/password_reset_verify.coffee @@ -0,0 +1,141 @@ +class PasswordResetVerify extends App.ControllerFullPage + events: + 'submit form': 'submit' + 'click .submit': 'submit' + forceRender: true + className: 'reset_password' + + constructor: -> + super + + # go back if feature is not enabled + if !@Config.get('user_lost_password') + @navigate '#' + return + + # if we are logged in, no passwort reset is wanted, redirect to app + if @authenticateCheck() + @navigate '#' + return + + # set title + @title 'Reset Password' + @navupdate '#password_reset_verify' + + # get data + params = + token: @token + @ajax( + id: 'password_reset_verify' + type: 'POST' + url: "#{@apiPath}/users/password_reset_verify" + data: JSON.stringify(params) + processData: true + success: @renderChange + ) + + renderChange: (data) => + if data.message is 'ok' + configure_attributes = [ + { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 100, null: false, class: 'input' } + ] + + @replaceWith(App.view('password/reset_change')()) + + new App.ControllerForm( + el: @el.find('.js-password') + model: { configure_attributes: configure_attributes } + autofocus: true + ) + else + @replaceWith(App.view('password/reset_failed')( + head: 'Reset Password failed!' + message: 'Token is invalid!' + )) + + submit: (e) -> + e.preventDefault() + params = @formParam(e.target) + params['token'] = @token + @password = params['password'] + + # disable form + @formDisable(e) + + # validate + if params['password_confirm'] isnt params['password'] + @formEnable(e) + @$('[name=password]').val('') + @$('[name=password_confirm]').val('') + @notify( + type: 'error' + msg: 'Can\'t update password, your new passwords do not match. Please try again!' + removeAll: true + ) + return + if !params['password'] + @formEnable(e) + @notify( + type: 'error' + msg: 'Please supply your new password!' + removeAll: true + ) + return + + # get data + @ajax( + id: 'password_reset_verify' + type: 'POST' + url: "#{@apiPath}/users/password_reset_verify" + data: JSON.stringify(params) + processData: true + success: @renderChanged + ) + + renderChanged: (data, status, xhr) => + if data.message is 'ok' + App.Auth.login( + data: + username: data.user_login + password: @password + success: => + + # login check + App.Auth.loginCheck() + + # add notify + @notify( + type: 'success' + msg: 'Woo hoo! Your password has been changed!' + removeAll: true + ) + + # redirect to # + @navigate '#' + + error: => + @formEnable(@$('form')) + + # add notify + @notify( + type: 'error' + msg: 'Something went wrong. Please contact your administrator.' + removeAll: true + ) + ) + else + if data.notice + @notify( + type: 'error' + msg: App.i18n.translateContent(data.notice[0], data.notice[1]) + removeAll: true + ) + else + @notify( + type: 'error' + msg: 'Unable to set password. Please contact your administrator.' + removeAll: true + ) + @formEnable(@$('form')) + +App.Config.set('password_reset_verify/:token', PasswordResetVerify, 'Routes') diff --git a/app/assets/javascripts/app/controllers/report.coffee b/app/assets/javascripts/app/controllers/report.coffee index eabcb9a0f..0e2bde4a2 100644 --- a/app/assets/javascripts/app/controllers/report.coffee +++ b/app/assets/javascripts/app/controllers/report.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerContent +class Reporting extends App.ControllerAppContent requiredPermission: 'report' constructor: -> @@ -98,13 +98,14 @@ class Index extends App.ControllerContent ui: @ ) -class Graph extends App.ControllerContent +class Graph extends App.Controller constructor: -> super # rerender view - @bind 'ui:report:rerender', => + @controllerBind('ui:report:rerender', => @render() + ) @render() @@ -380,8 +381,9 @@ class TimeRangePicker extends App.Controller super # rerender view - @bind 'ui:report:rerender', => + @controllerBind('ui:report:rerender', => @render() + ) @render() @@ -411,9 +413,9 @@ class TimePicker extends App.Controller @_timeSlotPicker() # rerender view - @bind 'ui:report:rerender', => + @controllerBind('ui:report:rerender', => @render() - + ) @render() render: => @@ -588,5 +590,5 @@ class Sidebar extends App.Controller App.Event.trigger('ui:report:rerender') @ui.storeParams() -App.Config.set('report', Index, 'Routes') +App.Config.set('report', Reporting, 'Routes') App.Config.set('Reporting', { prio: 8000, parent: '', name: 'Reporting', translate: true, target: '#report', icon: 'report', permission: ['report'] }, 'NavBarRight') diff --git a/app/assets/javascripts/app/controllers/report_profile.coffee b/app/assets/javascripts/app/controllers/report_profile.coffee index 865a4d709..f29378eac 100644 --- a/app/assets/javascripts/app/controllers/report_profile.coffee +++ b/app/assets/javascripts/app/controllers/report_profile.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class ReportProfile extends App.ControllerSubContent requiredPermission: 'admin.report_profile' header: 'Report Profile' constructor: -> @@ -34,4 +34,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('ReportProfile', { prio: 8000, name: 'Report Profiles', parent: '#manage', target: '#manage/report_profiles', controller: Index, permission: ['admin.report_profile'] }, 'NavBarAdmin') +App.Config.set('ReportProfile', { prio: 8000, name: 'Report Profiles', parent: '#manage', target: '#manage/report_profiles', controller: ReportProfile, permission: ['admin.report_profile'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/role.coffee b/app/assets/javascripts/app/controllers/role.coffee index dfa2616a4..b29c7f9e0 100644 --- a/app/assets/javascripts/app/controllers/role.coffee +++ b/app/assets/javascripts/app/controllers/role.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Role extends App.ControllerSubContent requiredPermission: 'admin.role' header: 'Roles' constructor: -> @@ -34,4 +34,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Role', { prio: 1600, name: 'Roles', parent: '#manage', target: '#manage/roles', controller: Index, permission: ['admin.role'] }, 'NavBarAdmin') +App.Config.set('Role', { prio: 1600, name: 'Roles', parent: '#manage', target: '#manage/roles', controller: Role, permission: ['admin.role'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/search.coffee b/app/assets/javascripts/app/controllers/search.coffee index 699563ec3..b2698c6e8 100644 --- a/app/assets/javascripts/app/controllers/search.coffee +++ b/app/assets/javascripts/app/controllers/search.coffee @@ -32,7 +32,7 @@ class App.Search extends App.Controller @render() # rerender view, e. g. on langauge change - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @render() ) diff --git a/app/assets/javascripts/app/controllers/session.coffee b/app/assets/javascripts/app/controllers/session.coffee index 57bdd0a16..7d0217070 100644 --- a/app/assets/javascripts/app/controllers/session.coffee +++ b/app/assets/javascripts/app/controllers/session.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Session extends App.ControllerSubContent requiredPermission: 'admin.session' header: 'Sessions' events: @@ -49,4 +49,4 @@ class Index extends App.ControllerSubContent @load() ) -App.Config.set('Session', { prio: 3800, name: 'Sessions', parent: '#system', target: '#system/sessions', controller: Index, permission: ['admin.session'] }, 'NavBarAdmin' ) +App.Config.set('Session', { prio: 3800, name: 'Sessions', parent: '#system', target: '#system/sessions', controller: Session, permission: ['admin.session'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/signup.coffee b/app/assets/javascripts/app/controllers/signup.coffee index ae75bd56b..d9da1e2d9 100644 --- a/app/assets/javascripts/app/controllers/signup.coffee +++ b/app/assets/javascripts/app/controllers/signup.coffee @@ -1,9 +1,10 @@ -class Index extends App.ControllerContent +class Signup extends App.ControllerFullPage events: 'submit form': 'submit' 'click .submit': 'submit' 'click .js-submitResend': 'resend' 'click .cancel': 'cancel' + className: 'signup' constructor: -> super @@ -13,8 +14,6 @@ class Index extends App.ControllerContent @navigate '#' return - @navHide() - # set title @title 'Sign up' @navupdate '#signup' @@ -23,7 +22,7 @@ class Index extends App.ControllerContent render: -> - @html App.view('signup')() + @replaceWith(App.view('signup')()) @form = new App.ControllerForm( el: @el.find('form') @@ -70,9 +69,9 @@ class Index extends App.ControllerContent # save user user.save( done: (r) => - @html App.view('signup/verify')( + @replaceWith(App.view('signup/verify')( email: @params.email - ) + )) fail: (settings, details) => @formEnable(e) @form.showAlert(details.error_human || details.error || 'Unable to create user!') @@ -93,10 +92,11 @@ class Index extends App.ControllerContent @formEnable(e) # add notify - @notify + @notify( type: 'success' msg: App.i18n.translateContent('Email sent to "%s". Please verify your email address.', @params.email) removeAll: true + ) if data.token && @Config.get('developer_mode') is true @navigate "#email_verify/#{data.token}" @@ -109,9 +109,9 @@ class Index extends App.ControllerContent if !_.isEmpty(detailsRaw) details = JSON.parse(detailsRaw) - @notify + @notify( type: 'error' msg: App.i18n.translateContent(details.error || 'Could not process your request') removeAll: true - -App.Config.set('signup', Index, 'Routes') + ) +App.Config.set('signup', Signup, 'Routes') diff --git a/app/assets/javascripts/app/controllers/sla.coffee b/app/assets/javascripts/app/controllers/sla.coffee index a8f5160bf..b3f50e1d3 100644 --- a/app/assets/javascripts/app/controllers/sla.coffee +++ b/app/assets/javascripts/app/controllers/sla.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Sla extends App.ControllerSubContent requiredPermission: 'admin.sla' header: 'SLAs' events: @@ -112,4 +112,4 @@ class Index extends App.ControllerSubContent "#{hours}:#{minutes}" -App.Config.set('Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, permission: ['admin.sla'] }, 'NavBarAdmin') +App.Config.set('Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Sla, permission: ['admin.sla'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/tag.coffee b/app/assets/javascripts/app/controllers/tag.coffee index 62496f3fe..1af93e693 100644 --- a/app/assets/javascripts/app/controllers/tag.coffee +++ b/app/assets/javascripts/app/controllers/tag.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Tag extends App.ControllerSubContent requiredPermission: 'admin.tag' header: 'Tags' events: @@ -149,4 +149,4 @@ class DestroyConfirm extends App.ControllerModal @close() ) -App.Config.set('Tags', { prio: 2320, name: 'Tags', parent: '#manage', target: '#manage/tags', controller: Index, permission: ['admin.tag'] }, 'NavBarAdmin') +App.Config.set('Tags', { prio: 2320, name: 'Tags', parent: '#manage', target: '#manage/tags', controller: Tag, permission: ['admin.tag'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/taskbar_widget.coffee b/app/assets/javascripts/app/controllers/taskbar_widget.coffee index 7d566799d..940d91c89 100644 --- a/app/assets/javascripts/app/controllers/taskbar_widget.coffee +++ b/app/assets/javascripts/app/controllers/taskbar_widget.coffee @@ -44,19 +44,19 @@ class App.TaskbarWidget extends App.CollectionController @el.sortable(dndOptions) # bind to changes - @bind('taskInit', => + @controllerBind('taskInit', => @queue.push ['renderAll'] @uIRunner() ) - @bind('taskUpdate', (tasks) => + @controllerBind('taskUpdate', (tasks) => @queue.push ['change', tasks] @uIRunner() ) - @bind('taskRemove', (tasks) => + @controllerBind('taskRemove', (tasks) => @queue.push ['destroy', tasks] @uIRunner() ) - @bind('taskCollectionOrderSet', (taskKeys) => + @controllerBind('taskCollectionOrderSet', (taskKeys) => @collectionOrderSet(taskKeys) ) diff --git a/app/assets/javascripts/app/controllers/text_module.coffee b/app/assets/javascripts/app/controllers/text_module.coffee index 2d39b1a72..c9bd21665 100644 --- a/app/assets/javascripts/app/controllers/text_module.coffee +++ b/app/assets/javascripts/app/controllers/text_module.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class TextModule extends App.ControllerSubContent requiredPermission: 'admin.text_module' header: 'Text modules' constructor: -> @@ -41,4 +41,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('TextModule', { prio: 2300, name: 'Text modules', parent: '#manage', target: '#manage/text_modules', controller: Index, permission: ['admin.text_module'] }, 'NavBarAdmin') +App.Config.set('TextModule', { prio: 2300, name: 'Text modules', parent: '#manage', target: '#manage/text_modules', controller: TextModule, permission: ['admin.text_module'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/ticket_overview.coffee b/app/assets/javascripts/app/controllers/ticket_overview.coffee index 7751af440..acb23f386 100644 --- a/app/assets/javascripts/app/controllers/ticket_overview.coffee +++ b/app/assets/javascripts/app/controllers/ticket_overview.coffee @@ -77,9 +77,9 @@ class App.TicketOverview extends App.Controller @render() # rerender view, e. g. on language change - @bind 'ui:rerender', => + @controllerBind('ui:rerender', => @renderBatchOverlay() - + ) load = (data) => App.Collection.loadAssets(data.assets) @formMeta = data.form_meta @@ -134,7 +134,7 @@ class App.TicketOverview extends App.Controller @renderOptions() - $('#app').append @batchDragger + @appEl.append(@batchDragger) @draggedItems.each (i, item) -> dx = $(item).data('offset').left - $(item).offset().left - x @@ -592,17 +592,20 @@ class App.TicketOverview extends App.Controller render: -> elLocal = $(App.view('ticket_overview/index')()) + @navBarControllerVertical.releaseController() if @navBarControllerVertical @navBarControllerVertical = new Navbar( el: elLocal.find('.overview-header') view: @view vertical: true ) + @navBarController.releaseController() if @navBarController @navBarController = new Navbar( el: elLocal.filter('.sidebar') view: @view ) + @contentController.releaseController() if @contentController @contentController = new Table( el: elLocal.find('.overview-table') view: @view @@ -614,14 +617,14 @@ class App.TicketOverview extends App.Controller @html elLocal - @el.find('.main').on('click', => + @$('.main').on('click', => @activeFocus = 'overview' ) - @el.find('.sidebar').on('click', => + @$('.sidebar').on('click', => @activeFocus = 'nav' ) - @bind('overview:fetch', => + @controllerBind('overview:fetch', => return if !@view update = => App.OverviewListCollection.fetch(@view) @@ -692,7 +695,7 @@ class App.TicketOverview extends App.Controller # redirect to last overview if we got called in first level @view = params['view'] if !@view && @viewLast - @navigate "ticket/view/#{@viewLast}", true + @navigate "ticket/view/#{@viewLast}", { hideCurrentLocationFromHistory: true } return # build nav bar @@ -718,6 +721,7 @@ class App.TicketOverview extends App.Controller @viewLast = @view # build content + @contentController.releaseController() if @contentController @contentController = new Table( el: @$('.overview-table') view: @view @@ -860,9 +864,9 @@ class Navbar extends App.Controller @bindId = App.OverviewIndexCollection.bind(@render) # rerender view, e. g. on language change - @bind 'ui:rerender', => + @controllerBind('ui:rerender', => @render(App.OverviewIndexCollection.get()) - + ) if @vertical $(window).on 'resize.navbar', @autoFoldTabs @@ -946,7 +950,7 @@ class Navbar extends App.Controller # redirect to first view if @activeState && !@view && !@vertical view = data[0].link - @navigate "ticket/view/#{view}", true + @navigate "ticket/view/#{view}", { hideCurrentLocationFromHistory: true } return # add new views @@ -979,10 +983,11 @@ class Table extends App.Controller @bindId = App.OverviewListCollection.bind(@view, @updateTable) # rerender view, e. g. on langauge change - @bind 'ui:rerender', => + @controllerBind('ui:rerender', => return if !@authenticateCheck() return if !@view @render(App.OverviewListCollection.get(@view)) + ) show: => if @table @@ -1333,6 +1338,7 @@ class Table extends App.Controller settings: (e) => e.preventDefault() @keyboardOff() + new App.OverviewSettings( overview_id: @overview.id view_mode: @view_mode @@ -1495,10 +1501,10 @@ class BulkForm extends App.Controller @bulkCount = @holder.find('.table-overview').find('[name="bulk"]:checked').length if @bulkCount is 0 - App.Event.trigger 'notify', { + App.Event.trigger('notify', { type: 'error' msg: App.i18n.translateContent('At least one object must be selected.') - } + }) return ticket_ids = [] @@ -1535,10 +1541,10 @@ class BulkForm extends App.Controller screen: 'edit' ) - App.Event.trigger 'notify', { + App.Event.trigger('notify', { type: 'error' msg: App.i18n.translateContent('Bulk action stopped %s!', errorString) - } + }) @cancel() return @@ -1580,10 +1586,10 @@ class BulkForm extends App.Controller @saveTicketArticle(ticket, article) @holder.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false) - App.Event.trigger 'notify', { + App.Event.trigger('notify', { type: 'success' msg: App.i18n.translateContent('Bulk action executed!') - } + }) saveTicketArticle: (ticket, article) => ticket.save( @@ -1749,6 +1755,7 @@ class TicketOverviewRouter extends App.ControllerPermanent # cleanup params clean_params = view: params.view + appEl: params.appEl App.TaskManager.execute( key: 'TicketOverview' diff --git a/app/assets/javascripts/app/controllers/ticket_zoom.coffee b/app/assets/javascripts/app/controllers/ticket_zoom.coffee index d0bcf2a6b..878c9c660 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom.coffee @@ -43,7 +43,7 @@ class App.TicketZoom extends App.Controller @interval(update, 1800000, 'pull_check') # fetch new data if triggered - @bind('Ticket:update Ticket:touch', (data) => + @controllerBind('Ticket:update Ticket:touch', (data) => # check if current ticket has changed return if data.id.toString() isnt @ticket_id.toString() @@ -54,7 +54,7 @@ class App.TicketZoom extends App.Controller ) # after a new websocket connection, check if ticket has changed - @bind('spool:sent', => + @controllerBind('spool:sent', => if @initSpoolSent @fetch(true) return @@ -62,7 +62,7 @@ class App.TicketZoom extends App.Controller ) # 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(@formCurrent()) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee index 2eaa18e1a..ad075af47 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee @@ -55,7 +55,7 @@ class App.TicketZoomArticleNew extends App.Controller @renderAttachment(attachment) # set article type and expand text area - @bind('ui::ticket::setArticleType', (data) => + @controllerBind('ui::ticket::setArticleType', (data) => return if data.ticket.id.toString() isnt @ticket_id.toString() @setArticleTypePre(data.type.name, data.signaturePosition) @@ -81,7 +81,7 @@ class App.TicketZoomArticleNew extends App.Controller ) # add article attachment - @bind('ui::ticket::addArticleAttachent', (data) => + @controllerBind('ui::ticket::addArticleAttachent', (data) => return if data.ticket?.id?.toString() isnt @ticket_id.toString() && data.form_id isnt @form_id return if _.isEmpty(data.attachments) for file in data.attachments @@ -89,7 +89,7 @@ class App.TicketZoomArticleNew extends App.Controller ) # reset new article screen - @bind('ui::ticket::taskReset', (data) => + @controllerBind('ui::ticket::taskReset', (data) => @releaseGlobalClickEvents() return if data.ticket_id.toString() isnt @ticket_id.toString() @type = 'note' @@ -98,18 +98,18 @@ class App.TicketZoomArticleNew extends App.Controller ) # set expand of text area only once - @bind('ui::ticket::shown', (data) => + @controllerBind('ui::ticket::shown', (data) => return if data.ticket_id.toString() isnt @ticket.id.toString() @tokanice(@type) ) # rerender, e. g. on language change - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @render() ) # update security options - @bind('ui::ticket::updateSecurityOptions', (data) => + @controllerBind('ui::ticket::updateSecurityOptions', (data) => return if data.taskKey isnt @taskKey @updateSecurityOptions() ) diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee index 874d05e7f..563e71a09 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee @@ -66,7 +66,7 @@ class App.TicketZoomArticleView extends App.Controller for id, viewItem of @articleController viewItem.updateFormId(newFormId) -class ArticleViewItem extends App.ObserverController +class ArticleViewItem extends App.ControllerObserver model: 'TicketArticle' observe: from: true @@ -96,7 +96,7 @@ class ArticleViewItem extends App.ObserverController @seeMore = false # set expand of text area only once - @bind('ui::ticket::shown', (data) => + @controllerBind('ui::ticket::shown', (data) => return if data.ticket_id.toString() isnt @ticket.id.toString() # set highlighter diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee index 0e95727f7..4abc20c8d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee @@ -21,11 +21,11 @@ class App.TicketZoomAttributeBar extends App.Controller @render() # rerender, e. g. on language change - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @render() ) - @bind('MacroPreconditionUpdate', (data) => + @controllerBind('MacroPreconditionUpdate', (data) => return if data.taskKey isnt @taskKey @searchCondition = data.params @render() diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/customer_avatar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/customer_avatar.coffee index 1b1e7b07e..18335ed27 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/customer_avatar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/customer_avatar.coffee @@ -1,4 +1,4 @@ -class App.TicketCustomerAvatar extends App.ObserverController +class App.TicketCustomerAvatar extends App.ControllerObserver model: 'Ticket' observe: customer_id: true diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/highlighter.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/highlighter.coffee index 1ff299a4e..636a5115f 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/highlighter.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/highlighter.coffee @@ -218,7 +218,6 @@ class App.TicketZoomHighlighter extends App.Controller article.attr('data-highlightcolor', @colors[@activeColorIndex].name) if @highlighter.selectionOverlapsHighlight selection - console.log('SELECTION EXISTS, REMOVED IT') @highlighter.unhighlightSelection() selection.removeAllRanges() @highlightDisable() @@ -226,7 +225,6 @@ class App.TicketZoomHighlighter extends App.Controller return if selection && selection.rangeCount > 0 - console.log('NEW SELECTION', selection) @highlighter.highlightSelection @highlightClass, selection: selection containerElementId: article.get(0).id diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee index ed6dfc590..8c50fa54f 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/meta.coffee @@ -1,4 +1,4 @@ -class App.TicketZoomMeta extends App.ObserverController +class App.TicketZoomMeta extends App.ControllerObserver model: 'Ticket' observe: number: true diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee index 4ebb1ab8d..20389ffc8 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar.coffee @@ -1,4 +1,4 @@ -class App.TicketZoomSidebar extends App.ObserverController +class App.TicketZoomSidebar extends App.ControllerObserver model: 'Ticket' observe: customer_id: true diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee index 1a0f401ef..2fa7bee5d 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee @@ -1,4 +1,4 @@ -class Edit extends App.ObserverController +class Edit extends App.ControllerObserver model: 'Ticket' observeNot: created_at: true @@ -50,7 +50,7 @@ class Edit extends App.ObserverController return if @resetBind @resetBind = true - @bind('ui::ticket::taskReset', (data) => + @controllerBind('ui::ticket::taskReset', (data) => return if data.ticket_id.toString() isnt ticket.id.toString() @render(ticket) ) @@ -58,7 +58,7 @@ class Edit extends App.ObserverController class SidebarTicket extends App.Controller constructor: -> super - @bind 'config_update_local', (data) => @configUpdated(data) + @controllerBind('config_update_local', (data) => @configUpdated(data)) configUpdated: (data) -> if data.name != 'kb_active' diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/time_unit.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/time_unit.coffee index d89ebe6b3..5478c6259 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/time_unit.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/time_unit.coffee @@ -1,4 +1,4 @@ -class App.TicketZoomTimeUnit extends App.ObserverController +class App.TicketZoomTimeUnit extends App.ControllerObserver model: 'Ticket' observe: time_unit: true diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee index 013199454..a9acea2ea 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/title.coffee @@ -1,4 +1,4 @@ -class App.TicketZoomTitle extends App.ObserverController +class App.TicketZoomTitle extends App.ControllerObserver model: 'Ticket' template: 'ticket_zoom/title' observe: diff --git a/app/assets/javascripts/app/controllers/time_accounting.coffee b/app/assets/javascripts/app/controllers/time_accounting.coffee index f63f3b449..7e1ec5d43 100644 --- a/app/assets/javascripts/app/controllers/time_accounting.coffee +++ b/app/assets/javascripts/app/controllers/time_accounting.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class TimeAccounting extends App.ControllerSubContent requiredPermission: 'admin.time_accounting' header: 'Time Accounting' events: @@ -222,4 +222,4 @@ class ByOrganization extends App.Controller rows: rows ) -App.Config.set('TimeAccounting', { prio: 8500, name: 'Time Accounting', parent: '#manage', target: '#manage/time_accounting', controller: Index, permission: ['admin.time_accounting'] }, 'NavBarAdmin') +App.Config.set('TimeAccounting', { prio: 8500, name: 'Time Accounting', parent: '#manage', target: '#manage/time_accounting', controller: TimeAccounting, permission: ['admin.time_accounting'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/translation.coffee b/app/assets/javascripts/app/controllers/translation.coffee index 70900c430..41e624d5f 100644 --- a/app/assets/javascripts/app/controllers/translation.coffee +++ b/app/assets/javascripts/app/controllers/translation.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Translation extends App.ControllerSubContent requiredPermission: 'admin.translation' header: 'Translations' events: @@ -10,13 +10,13 @@ class Index extends App.ControllerSubContent super @locale = App.i18n.get() @render() - @bind('i18n:translation_update_todo', => + @controllerBind('i18n:translation_update_todo', => @load('i18n:translation_update_todo') ) - @bind('i18n:translation_update_list', => + @controllerBind('i18n:translation_update_list', => @load('i18n:translation_update_list') ) - @bind('i18n:translation_update', => + @controllerBind('i18n:translation_update', => @load() ) @@ -350,4 +350,4 @@ class TranslationList extends App.Controller reset.addClass('hidden') reset.closest('tr').removeClass('warning') -App.Config.set('Translation', { prio: 1800, parent: '#system', name: 'Translations', target: '#system/translation', controller: Index, permission: ['admin.translation'] }, 'NavBarAdmin' ) +App.Config.set('Translation', { prio: 1800, parent: '#system', name: 'Translations', target: '#system/translation', controller: Translation, permission: ['admin.translation'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/trigger.coffee b/app/assets/javascripts/app/controllers/trigger.coffee index 202760626..ec1ff806e 100644 --- a/app/assets/javascripts/app/controllers/trigger.coffee +++ b/app/assets/javascripts/app/controllers/trigger.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Trigger extends App.ControllerSubContent requiredPermission: 'admin.trigger' header: 'Triggers' constructor: -> @@ -35,4 +35,4 @@ class Index extends App.ControllerSubContent @genericController.paginate( @page || 1 ) -App.Config.set('Trigger', { prio: 3300, name: 'Trigger', parent: '#manage', target: '#manage/trigger', controller: Index, permission: ['admin.trigger'] }, 'NavBarAdmin') +App.Config.set('Trigger', { prio: 3300, name: 'Trigger', parent: '#manage', target: '#manage/trigger', controller: Trigger, permission: ['admin.trigger'] }, 'NavBarAdmin') diff --git a/app/assets/javascripts/app/controllers/users.coffee b/app/assets/javascripts/app/controllers/user.coffee similarity index 97% rename from app/assets/javascripts/app/controllers/users.coffee rename to app/assets/javascripts/app/controllers/user.coffee index a7b160350..7d4501bc4 100644 --- a/app/assets/javascripts/app/controllers/users.coffee +++ b/app/assets/javascripts/app/controllers/user.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class User extends App.ControllerSubContent requiredPermission: 'admin.user' header: 'Users' elements: @@ -189,4 +189,4 @@ class Index extends App.ControllerSubContent container: @el.closest('.content') ) -App.Config.set( 'User', { prio: 1000, name: 'Users', parent: '#manage', target: '#manage/users', controller: Index, permission: ['admin.user'] }, 'NavBarAdmin' ) +App.Config.set( 'User', { prio: 1000, name: 'Users', parent: '#manage', target: '#manage/users', controller: User, permission: ['admin.user'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/user_profile.coffee b/app/assets/javascripts/app/controllers/user_profile.coffee index f8989e481..3b3ed3ee7 100644 --- a/app/assets/javascripts/app/controllers/user_profile.coffee +++ b/app/assets/javascripts/app/controllers/user_profile.coffee @@ -74,7 +74,7 @@ class App.UserProfile extends App.Controller currentPosition: => @$('.profile').scrollTop() -class ActionRow extends App.ObserverActionRow +class ActionRow extends App.ControllerObserverActionRow model: 'User' observe: verified: true @@ -159,7 +159,7 @@ class ActionRow extends App.ObserverActionRow actions -class Object extends App.ObserverController +class Object extends App.ControllerObserver model: 'User' observeNot: cid: true @@ -221,7 +221,7 @@ class Object extends App.ObserverController user.updateAttributes(data) @log 'debug', 'update', name, value, user -class Organization extends App.ObserverController +class Organization extends App.ControllerObserver model: 'Organization' observe: name: true @@ -231,7 +231,7 @@ class Organization extends App.ObserverController organization: organization ) -class User extends App.ObserverController +class User extends App.ControllerObserver model: 'User' observe: firstname: true diff --git a/app/assets/javascripts/app/controllers/version.coffee b/app/assets/javascripts/app/controllers/version.coffee index 44bad0de8..c4a0becb3 100644 --- a/app/assets/javascripts/app/controllers/version.coffee +++ b/app/assets/javascripts/app/controllers/version.coffee @@ -1,4 +1,4 @@ -class Index extends App.ControllerSubContent +class Version extends App.ControllerSubContent requiredPermission: 'admin.version' header: 'Version' @@ -25,4 +25,4 @@ class Index extends App.ControllerSubContent version: @version ) -App.Config.set('Version', { prio: 3800, name: 'Version', parent: '#system', target: '#system/version', controller: Index, permission: ['admin.version'] }, 'NavBarAdmin' ) +App.Config.set('Version', { prio: 3800, name: 'Version', parent: '#system', target: '#system/version', controller: Version, permission: ['admin.version'] }, 'NavBarAdmin' ) diff --git a/app/assets/javascripts/app/controllers/widget/avatar.coffee b/app/assets/javascripts/app/controllers/widget/avatar.coffee index 9dae76b9a..ff376ce01 100644 --- a/app/assets/javascripts/app/controllers/widget/avatar.coffee +++ b/app/assets/javascripts/app/controllers/widget/avatar.coffee @@ -1,4 +1,4 @@ -class App.WidgetAvatar extends App.ObserverController +class App.WidgetAvatar extends App.ControllerObserver @extend App.PopoverProvidable @registerPopovers 'User' diff --git a/app/assets/javascripts/app/controllers/widget/error_modal.coffee b/app/assets/javascripts/app/controllers/widget/error_modal.coffee new file mode 100644 index 000000000..6133359c6 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/error_modal.coffee @@ -0,0 +1,17 @@ +class App.ErrorModal 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() diff --git a/app/assets/javascripts/app/controllers/widget/import.coffee b/app/assets/javascripts/app/controllers/widget/import.coffee new file mode 100644 index 000000000..45cccb567 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/import.coffee @@ -0,0 +1,67 @@ +class App.Import extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Import' + autoFocusOnFirstInput: false + head: 'Import' + large: true + templateDirectory: 'generic/object_import' + baseUrl: '/api/v1/text_modules' + + content: => + + # show start dialog + content = $(App.view("#{@templateDirectory}/index")( + head: 'Import' + import_example_url: "#{@baseUrl}/import_example" + deleteOption: @deleteOption + )) + + # check if data is processing... + if @data + result = App.view("#{@templateDirectory}/result")( + @data + ) + content.find('.js-error').html(result) + if result + content.find('.js-error').removeClass('hide') + else + content.find('.js-error').addClass('hide') + content + + onSubmit: (e) => + params = new FormData($(e.currentTarget).closest('form').get(0)) + params.set('try', true) + if _.isEmpty(params.get('data')) + params.delete('data') + @formDisable(e) + @ajax( + id: 'csv_import' + type: 'POST' + url: "#{@baseUrl}/import" + processData: false + contentType: false + cache: false + data: params + success: (data, status, xhr) => + if data.result is 'success' + new App.ImportTryResult( + container: @el.closest('.content') + result: data + params: params + templateDirectory: @templateDirectory + baseUrl: @baseUrl + ) + @close() + return + @data = data + @update() + @formEnable(e) + error: (data) => + details = data.responseJSON || {} + @notify + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') + timeout: 6000 + @formEnable(e) + ) diff --git a/app/assets/javascripts/app/controllers/widget/import_result.coffee b/app/assets/javascripts/app/controllers/widget/import_result.coffee new file mode 100644 index 000000000..5a23d5b72 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/import_result.coffee @@ -0,0 +1,19 @@ +class App.ImportResult extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Close' + autoFocusOnFirstInput: false + head: 'Import' + large: true + templateDirectory: 'generic/object_import/' + + content: => + + content = $(App.view("#{@templateDirectory}/imported")( + head: 'Imported' + result: @result + )) + content + + onSubmit: (e) => + @close() diff --git a/app/assets/javascripts/app/controllers/widget/import_try_result.coffee b/app/assets/javascripts/app/controllers/widget/import_try_result.coffee new file mode 100644 index 000000000..a333e06d8 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/import_try_result.coffee @@ -0,0 +1,65 @@ +class App.ImportTryResult extends App.ControllerModal + buttonClose: true + buttonCancel: true + buttonSubmit: 'Yes, start real import.' + autoFocusOnFirstInput: false + head: 'Import' + large: true + templateDirectory: 'generic/object_import/' + baseUrl: '/api/v1/text_modules' + + content: => + + # show start dialog + content = $(App.view("#{@templateDirectory}/import_try")( + head: 'Import' + import_example_url: "#{@baseUrl}/import" + result: @result + )) + + # check if data is processing... + if @data + result = App.view("#{@templateDirectory}/result")( + @data + ) + content.find('.js-error').html(result) + if result + content.find('.js-error').removeClass('hide') + else + content.find('.js-error').addClass('hide') + + content + + onSubmit: (e) => + @params.set('try', false) + @formDisable(e) + @ajax( + id: 'csv_import' + type: 'POST' + url: "#{@baseUrl}/import" + processData: false + contentType: false + cache: false + data: @params + success: (data, status, xhr) => + if data.result is 'success' + new App.ImportResult( + container: @el.closest('.content') + result: data + params: @params + templateDirectory: @templateDirectory + baseUrl: @baseUrl + ) + @close() + return + @data = data + @update() + @formEnable(e) + error: (data) => + details = data.responseJSON || {} + @notify + type: 'error' + msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to import!') + timeout: 6000 + @formEnable(e) + ) diff --git a/app/assets/javascripts/app/controllers/widget/invite_user.coffee b/app/assets/javascripts/app/controllers/widget/invite_user.coffee index 6b10fd9bd..852455f0c 100644 --- a/app/assets/javascripts/app/controllers/widget/invite_user.coffee +++ b/app/assets/javascripts/app/controllers/widget/invite_user.coffee @@ -1,4 +1,4 @@ -class App.InviteUser extends App.WizardModal +class App.InviteUser extends App.ControllerWizardModal events: 'click .js-close': 'hide' 'submit .js-user': 'submit' diff --git a/app/assets/javascripts/app/controllers/widget/online_notification.coffee b/app/assets/javascripts/app/controllers/widget/online_notification.coffee index bbca4f306..61b3460d9 100644 --- a/app/assets/javascripts/app/controllers/widget/online_notification.coffee +++ b/app/assets/javascripts/app/controllers/widget/online_notification.coffee @@ -20,7 +20,7 @@ class App.OnlineNotificationWidget extends App.Controller super # at runtime if an online notification has changed - @bind('OnlineNotification::changed', => + @controllerBind('OnlineNotification::changed', => @delay( => @fetch() 2200 @@ -30,7 +30,7 @@ class App.OnlineNotificationWidget extends App.Controller # after new websocket connection has been established @ignoreInitLogin = false - @bind('ws:login', => + @controllerBind('ws:login', => if @ignoreInitLogin @delay( => @fetch() @@ -41,7 +41,7 @@ class App.OnlineNotificationWidget extends App.Controller ) # rebuild widget on auth - @bind('auth', (user) => + @controllerBind('auth', (user) => if !user @counterUpdate(0) return @@ -55,7 +55,7 @@ class App.OnlineNotificationWidget extends App.Controller @createContainer() # rerender view, e.g. on language change - @bind('ui:rerender', => + @controllerBind('ui:rerender', => @createContainer() 'online_notification' ) diff --git a/app/assets/javascripts/app/controllers/widget/organization.coffee b/app/assets/javascripts/app/controllers/widget/organization.coffee index 6031d45bd..5901dbdc3 100644 --- a/app/assets/javascripts/app/controllers/widget/organization.coffee +++ b/app/assets/javascripts/app/controllers/widget/organization.coffee @@ -71,7 +71,7 @@ class App.WidgetOrganization extends App.Controller org.updateAttributes(data) @log 'notice', 'update', name, value, org -class User extends App.ObserverController +class User extends App.ControllerObserver @extend App.PopoverProvidable @registerPopovers 'User' diff --git a/app/assets/javascripts/app/controllers/widget/session_message.coffee b/app/assets/javascripts/app/controllers/widget/session_message.coffee new file mode 100644 index 000000000..3c6e5bacd --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/session_message.coffee @@ -0,0 +1,16 @@ +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() diff --git a/app/assets/javascripts/app/controllers/widget/sidebar.coffee b/app/assets/javascripts/app/controllers/widget/sidebar.coffee new file mode 100644 index 000000000..fe87e6ac9 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/sidebar.coffee @@ -0,0 +1,146 @@ +class App.Sidebar extends App.Controller + elements: + '.tabsSidebar-tab': 'tabs' + '.sidebar': 'sidebars' + + events: + 'click .tabsSidebar-tab': 'toggleTab' + 'click .tabsSidebar-close': 'toggleSidebar' + 'click .sidebar-header .js-headline': 'toggleDropdown' + + constructor: -> + super + @render() + + # get active tab by name + if @name + name = @name + + # get active tab last state + if !name && @sidebarState + name = @sidebarState.active + + # get active tab by first tab + if !name + name = @tabs.first().data('tab') + + # activate first tab + @toggleTabAction(name) + + render: => + itemsLocal = [] + for item in @items + itemLocal = item.sidebarItem() + if itemLocal + itemsLocal.push itemLocal + + # container + localEl = $(App.view('generic/sidebar_tabs')( + items: itemsLocal + scrollbarWidth: App.Utils.getScrollBarWidth() + dir: App.i18n.dir() + )) + + # init sidebar badge + for item in itemsLocal + el = localEl.find('.tabsSidebar-tab[data-tab="' + item.name + '"]') + if item.badgeCallback + item.badgeCallback(el) + else + @badgeRender(el, item) + + # init sidebar content + for item in itemsLocal + if item.sidebarCallback + el = localEl.filter('.sidebar[data-tab="' + item.name + '"]') + item.sidebarCallback(el.find('.sidebar-content')) + if !_.isEmpty(item.sidebarActions) + new ActionRow( + el: el.find('.js-actions') + items: item.sidebarActions + type: 'small' + ) + + @html localEl + + badgeRender: (el, item) => + @badgeEl = el + @badgeRenderLocal(item) + + badgeRenderLocal: (item) => + @badgeEl.html(App.view('generic/sidebar_tabs_item')(icon: item.badgeIcon)) + + toggleDropdown: (e) -> + e.stopPropagation() + $(e.currentTarget).next('.js-actions').find('.dropdown-toggle').dropdown('toggle') + + toggleSidebar: => + @el.parent().find('.tabsSidebar-sidebarSpacer').toggleClass('is-closed') + @el.parent().find('.tabsSidebar').toggleClass('is-closed') + #@el.parent().next('.attributeBar').toggleClass('is-closed') + + showSidebar: -> + @el.parent().find('.tabsSidebar-sidebarSpacer').removeClass('is-closed') + @el.parent().find('.tabsSidebar').removeClass('is-closed') + #@el.parent().next('.attributeBar').addClass('is-closed') + + toggleTab: (e) => + + # get selected tab + name = $(e.target).closest('.tabsSidebar-tab').data('tab') + + if name + + # if current tab is selected again, toggle side bar + if name is @currentTab + @toggleSidebar() + + # toggle content tab + else + @toggleTabAction(name) + + toggleTabAction: (name) -> + return if !name + + # remember sidebarState for outsite + if @sidebarState + @sidebarState.active = name + + # remove active state + @tabs.removeClass('active') + + # add active state + @$('.tabsSidebar-tab[data-tab=' + name + ']').addClass('active') + + # hide all content tabs + @sidebars.addClass('hide') + + # show active tab content + tabContent = @$('.sidebar[data-tab=' + name + ']') + tabContent.removeClass('hide') + + # remember current tab + @currentTab = name + + # show sidebar if not shown + @showSidebar() + +class ActionRow extends App.Controller + constructor: -> + super + @render() + + render: -> + @html App.view('generic/actions')( + items: @items + type: @type + ) + + for item in @items + do (item) => + @$('[data-type="' + item.name + '"]').on( + 'click' + (e) -> + e.preventDefault() + item.callback() + ) diff --git a/app/assets/javascripts/app/controllers/widget/text_module.coffee b/app/assets/javascripts/app/controllers/widget/text_module.coffee index a9e8df217..1ff860c67 100644 --- a/app/assets/javascripts/app/controllers/widget/text_module.coffee +++ b/app/assets/javascripts/app/controllers/widget/text_module.coffee @@ -19,7 +19,7 @@ class App.WidgetTextModule extends App.Controller @subscribeId = App.TextModule.subscribe(@update, initFetch: true) - @bind('TextModulePreconditionUpdate', (data) => + @controllerBind('TextModulePreconditionUpdate', (data) => return if data.taskKey isnt @taskKey @searchCondition = data.params @update() diff --git a/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee b/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee index 3e6a3f44f..905363f2a 100644 --- a/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee +++ b/app/assets/javascripts/app/controllers/widget/ticket_stats.coffee @@ -19,9 +19,10 @@ class App.TicketStats extends App.Controller @subscribeIdOrganization = App.Organization.full(@organization.id, @load, false, true) # rerender view, e.g. on language change - @bind 'ui:rerender', => + @controllerBind('ui:rerender', => return if !@authenticateCheck() @render() + ) release: => if @subscribeIdUser diff --git a/app/assets/javascripts/app/controllers/widget/update_taskbar.coffee b/app/assets/javascripts/app/controllers/widget/update_taskbar.coffee new file mode 100644 index 000000000..3f419eb57 --- /dev/null +++ b/app/assets/javascripts/app/controllers/widget/update_taskbar.coffee @@ -0,0 +1,14 @@ +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) \ No newline at end of file diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee index 4901183c8..623df6842 100644 --- a/app/assets/javascripts/app/index.coffee +++ b/app/assets/javascripts/app/index.coffee @@ -5,6 +5,7 @@ #= require_tree ./lib/mixins #= require ./config.coffee #= require_tree ./models +#= require_tree ./controllers/_application_controller #= require_tree ./controllers #= require_tree ./views #= require_tree ./lib/app_post diff --git a/app/assets/javascripts/app/lib/app_post/auth.coffee b/app/assets/javascripts/app/lib/app_post/auth.coffee index 6ccc72ec0..8fd411422 100644 --- a/app/assets/javascripts/app/lib/app_post/auth.coffee +++ b/app/assets/javascripts/app/lib/app_post/auth.coffee @@ -98,9 +98,8 @@ class App.Auth # rebuild navbar with new navbar items App.Event.trigger('auth') - App.Event.trigger('auth:logout') + App.Event.trigger('auth:failed') App.Event.trigger('ui:rerender') - App.TaskManager.tasksInitial() return false # clear local store @@ -160,8 +159,10 @@ class App.Auth if _.isFunction(model_object.clearInMemory) model_object.clearInMemory() + App.Plugin.init() App.Event.trigger('auth') App.Event.trigger('auth:logout') + if rerender @loginCheck(-> window.location.href = '#login' @@ -177,6 +178,6 @@ class App.Auth # rebuild navbar App.Event.trigger('auth') - App.Event.trigger('auth:logout') + App.Event.trigger('auth:failed') App.Event.trigger('ui:rerender') App.Event.trigger('clearStore') diff --git a/app/assets/javascripts/app/lib/app_post/interface_handle.coffee b/app/assets/javascripts/app/lib/app_post/interface_handle.coffee index 96b25142d..06d25d4d1 100644 --- a/app/assets/javascripts/app/lib/app_post/interface_handle.coffee +++ b/app/assets/javascripts/app/lib/app_post/interface_handle.coffee @@ -21,82 +21,16 @@ class App.Run extends App.Controller # create web socket connection App.WebSocket.connect() + # init plugins + App.Plugin.init(@el) + + # init routes + App.Router.init(@el) + # start frontend time update @frontendTimeUpdate() - # start navbars - @setupWidget('Navigations', 'nav', @el) - - # start widgets - @setupWidget('Widgets', 'widget', @el) - # bind to fill selected text into App.ClipBoard.bind(@el) App.Event.trigger('app:ready') - - setupWidget: (config, event, el) -> - - # start widgets - App.Event.trigger(event + ':init') - widgets = App.Config.get(config) - if widgets - sortedKeys = Object.keys(widgets).sort() - for key in sortedKeys - widget = widgets[key] - try - new widget( - el: el - key: key - ) - catch e - @log 'error', "widget #{key}:", e - App.Event.trigger(event + ':ready') - -class App.Content extends App.ControllerWidgetPermanent - className: 'content flex horizontal' - - constructor: -> - super - - Routes = @Config.get('Routes') - for route, callback of Routes - do (route, callback) => - @route(route, (params) -> - - @log 'debug', 'execute page controller', route, params - - # remove events for page - App.Event.unbindLevel('page') - - # remove delay for page - App.Delay.clearLevel('page') - - # remove interval for page - App.Interval.clearLevel('page') - - # unbind in controller area - @el.unbind() - @el.undelegate() - - # remember history - # needed to mute "redirect" url to support browser back - history = App.Config.get('History') - if history[10] - history.shift() - - history.push '#' + params.match.input - - # execute controller - controller = (params) => - params.el = @el - try - new callback(params) - catch e - @log 'error', "route #{route}:", e - controller(params) - ) - - Spine.Route.setup() - -App.Config.set('content', App.Content, 'Widgets') diff --git a/app/assets/javascripts/app/lib/app_post/plugin.coffee b/app/assets/javascripts/app/lib/app_post/plugin.coffee new file mode 100644 index 000000000..9871d04a7 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/plugin.coffee @@ -0,0 +1,49 @@ +class App.Plugin + _instance = undefined + + @init: (el) -> + if _instance == undefined + _instance ?= new _pluginSingleton + _instance.init(el) + +class _pluginSingleton + backends: {} + el: undefined + + constructor: -> + + init: (el) => + @appEl = el if el + @setupAll() + + App.Event.bind('auth:login auth:logout', (user) => + @setupAll() + ) + + setupAll: => + @appEl.empty() + for key, backend of @backend + if backend.release + backend.release() + if backend.releaseController + backend.releaseController() + @backend = {} + @setup('Plugins', 'plugin') + + setup: (config, event) -> + + # start plugins + App.Event.trigger(event + ':init') + plugins = App.Config.get(config) + if plugins + sortedKeys = Object.keys(plugins).sort() + for key in sortedKeys + plugin = plugins[key] + try + @backend[key] = new plugin( + appEl: @appEl + key: key + ) + catch e + App.Log.error "plugin #{key}:", e + App.Event.trigger(event + ':ready') diff --git a/app/assets/javascripts/app/lib/app_post/router.coffee b/app/assets/javascripts/app/lib/app_post/router.coffee new file mode 100644 index 000000000..9b64d1808 --- /dev/null +++ b/app/assets/javascripts/app/lib/app_post/router.coffee @@ -0,0 +1,30 @@ +class App.Router extends Spine.Controller + @init: (el) -> + new App.Router(el) + + constructor: (el) -> + routes = App.Config.get('Routes') + for route, callback of routes + do (route, callback) => + @route(route, (params) -> + + App.Log.debug 'execute page controller', route, params + + # remember history + # needed to mute "redirect" url to support browser back + history = App.Config.get('History') + if history[10] + history.shift() + history.push window.location.hash + + # execute controller + controller = (params) -> + params.appEl = el + try + new callback(params) + catch e + App.Log.error "route #{route}:", e + controller(params) + ) + + Spine.Route.setup() diff --git a/app/assets/javascripts/app/lib/app_post/task_manager.coffee b/app/assets/javascripts/app/lib/app_post/task_manager.coffee index ad91110fd..aa6881808 100644 --- a/app/assets/javascripts/app/lib/app_post/task_manager.coffee +++ b/app/assets/javascripts/app/lib/app_post/task_manager.coffee @@ -8,6 +8,7 @@ class App.TaskManager _instance ?= new _taskManagerSingleton(params) @all: -> + return [] if !_instance _instance.all() @allWithMeta: -> @@ -112,7 +113,7 @@ class _taskManagerSingleton extends App.Controller @offlineModus = params.offlineModus @tasksInitial() - @bind('taskbar:preferences', (data) => + @controllerBind('taskbar:preferences', (data) => @tasksPreferences[data.key] = data.preferences @preferencesTrigger(data.key) ) @@ -275,7 +276,7 @@ class _taskManagerSingleton extends App.Controller # empty static content if task is shown if params.show - @$('#content').empty() + @$('#content').remove() # set all tasks to active false, only new/selected one to active if params.show @@ -311,7 +312,7 @@ class _taskManagerSingleton extends App.Controller el = $("
") @domStore[domKey] = { el: el } params_app['el'] = el - params_app['task_key'] = params.key + params_app['appEl'] = @el params_app['taskKey'] = params.key if !params.show params_app['doNotLog'] = 1 diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco index 92efdd2de..70d16d410 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_channel_distribution.jst.eco @@ -18,7 +18,8 @@
<%- channel.overal_percentage %>%
- <% end %> +
+ <% end %>
- \ No newline at end of file + diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco index afd2e71ab..900dae4ed 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_escalation.jst.eco @@ -12,4 +12,6 @@
<%- @T('%s of my tickets escalated.', @StatsTicketEscalation.own) %>
<%- @T('Total: %s', @StatsTicketEscalation.total) %>
- \ No newline at end of file +
<%- @T('%s of my tickets escalated.', @StatsTicketEscalation.own) %>
+
<%- @T('Total: %s', @StatsTicketEscalation.total) %>
+ diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco index 6909d733f..ddd126490 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_in_process.jst.eco @@ -12,4 +12,6 @@
<%- @T('%s% are currently in process', @StatsTicketInProcess.percent) %>
<%- @T('Average: %s%', @StatsTicketInProcess.average_per_agent) %>
- \ No newline at end of file +
<%- @T('%s% are currently in process', @StatsTicketInProcess.percent) %>
+
<%- @T('Average: %s%', @StatsTicketInProcess.average_per_agent) %>
+ diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco index fc34590a4..2eb0566fd 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_load_measure.jst.eco @@ -15,7 +15,8 @@ <%- @Icon('total-tickets', 'total-tickets') %> -
<%- @T('Tickets assigned to me: %s of %s', @StatsTicketLoadMeasure.own, @StatsTicketLoadMeasure.total) %>
-
<%- @T('Average: %s', @StatsTicketLoadMeasure.average_per_agent) %>
+ <%- @Icon('total-tickets', 'total-tickets') %> - \ No newline at end of file +
<%- @T('Tickets assigned to me: %s of %s', @StatsTicketLoadMeasure.own, @StatsTicketLoadMeasure.total) %>
+
<%- @T('Average: %s', @StatsTicketLoadMeasure.average_per_agent) %>
+ diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco index 18674a590..a4fc73598 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_reopen.jst.eco @@ -12,4 +12,6 @@
<%- @T('%s% have been reopened', @StatsTicketReopen.percent) %>
<%- @T('Average: %s%', @StatsTicketReopen.average_per_agent) %>
- \ No newline at end of file +
<%- @T('%s% have been reopened', @StatsTicketReopen.percent) %>
+
<%- @T('Average: %s%', @StatsTicketReopen.average_per_agent) %>
+ diff --git a/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco b/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco index ef9781e15..452224c3b 100644 --- a/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco +++ b/app/assets/javascripts/app/views/dashboard/stats/ticket_waiting_time.jst.eco @@ -13,7 +13,7 @@
-
<%- @T('My handling time: %s minutes', @StatsTicketWaitingTime.handling_time) %>
-
<%- @T('Average: %s minutes', @StatsTicketWaitingTime.average_per_agent) %>
- \ No newline at end of file +
<%- @T('My handling time: %s minutes', @StatsTicketWaitingTime.handling_time) %>
+
<%- @T('Average: %s minutes', @StatsTicketWaitingTime.average_per_agent) %>
+ diff --git a/app/assets/javascripts/app/views/navigation.jst.eco b/app/assets/javascripts/app/views/navigation.jst.eco index a4ee50396..770f570b4 100644 --- a/app/assets/javascripts/app/views/navigation.jst.eco +++ b/app/assets/javascripts/app/views/navigation.jst.eco @@ -1,31 +1,33 @@ -