### # Copyright (C) 2014-2018 Taiga Agile LLC # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # # File: modules/common/lightboxes.coffee ### module = angular.module("taigaCommon") bindOnce = @.taiga.bindOnce timeout = @.taiga.timeout debounce = @.taiga.debounce sizeFormat = @.taiga.sizeFormat trim = @.taiga.trim normalizeString = @.taiga.normalizeString ############################################################################# ## Common Lightbox Services ############################################################################# # the lightboxContent hide/show doesn't have sense because is an IE hack class LightboxService extends taiga.Service constructor: (@animationFrame, @q, @rootScope) -> open: ($el, onClose, onEsc, ignoreEsc) -> @.onClose = onClose if _.isString($el) $el = $($el) defered = @q.defer() lightboxContent = $el.children().not(".close") lightboxContent.hide() @animationFrame.add -> $el.css('display', 'flex') @animationFrame.add -> $el.addClass("open") $el.one "transitionend", => firstField = $el.find('input:not(.no-focus),textarea:not(.no-focus)').first() if firstField.length firstField.focus() else if document.activeElement $(document.activeElement).blur() @animationFrame.add => lightboxContent.show() defered.resolve() if !ignoreEsc docEl = angular.element(document) docEl.on "keydown.lightbox", (e) => code = if e.keyCode then e.keyCode else e.which if code == 27 if onEsc @rootScope.$applyAsync(onEsc) else @.close($el) return defered.promise close: ($el) -> return new Promise (resolve) => if _.isString($el) $el = $($el) docEl = angular.element(document) docEl.off(".lightbox") docEl.off(".keyboard-navigation") # Hack: to fix problems in the WYSIWYG textareas when press ENTER $el.addClass('close-started') # don't attach animations @animationFrame.add => $el.addClass('close') $el.one "transitionend", => $el.removeAttr('style') $el .removeClass("open") .removeClass('close') .removeClass('close-started') if @.onClose @rootScope.$apply(@.onClose) resolve() if $el.hasClass("remove-on-close") scope = $el.data("scope") scope.$destroy() if scope $el.remove() getLightboxOpen: -> return $(".lightbox.open:not(.close-started)") closeAll: -> docEl = angular.element(document) for lightboxEl in docEl.find(".lightbox.open") @.close($(lightboxEl)) module.service("lightboxService", ["animationFrame", "$q", "$rootScope", LightboxService]) class LightboxKeyboardNavigationService extends taiga.Service stop: -> docEl = angular.element(document) docEl.off(".keyboard-navigation") dispatch: ($el, code) -> activeElement = $el.find(".selected") # Key: enter if code == 13 if $el.find(".user-list-single").length == 1 $el.find('.user-list-single:first').trigger("click") else activeElement.trigger("click") # Key: down else if code == 40 if not activeElement.length $el.find('.user-list-single:not(".is-active"):first').addClass('selected') else next = activeElement.next('.user-list-single') if next.length activeElement.removeClass('selected') next.addClass('selected') # Key: up else if code == 38 if not activeElement.length $el.find('.user-list-single:last').addClass('selected') else prev = activeElement.prev('.user-list-single:not(".is-active")') if prev.length activeElement.removeClass('selected') prev.addClass('selected') init: ($el) -> @stop() docEl = angular.element(document) docEl.on "keydown.keyboard-navigation", (event) => code = if event.keyCode then event.keyCode else event.which if code == 40 || code == 38 || code == 13 event.preventDefault() @.dispatch($el, code) module.service("lightboxKeyboardNavigationService", LightboxKeyboardNavigationService) ############################################################################# ## Generic Lighthbox Directive ############################################################################# # This adds generic behavior to all blocks with lightbox class like # close button event handlers. LightboxDirective = (lightboxService) -> link = ($scope, $el, $attrs) -> if !$attrs.$attr.visible $el.on "click", ".close", (event) -> event.preventDefault() lightboxService.close($el) return {restrict: "C", link: link} module.directive("lightbox", ["lightboxService", LightboxDirective]) ############################################################################# ## Block Lightbox Directive ############################################################################# # Issue/Userstory blocking message lightbox directive. BlockLightboxDirective = ($rootscope, $tgrepo, $confirm, lightboxService, $loading, $modelTransform, $translate) -> link = ($scope, $el, $attrs, $model) -> title = $translate.instant($attrs.title) $el.find("h2.title").text(title) unblock = (finishCallback) => transform = $modelTransform.save (item) -> item.is_blocked = false item.blocked_note = "" return item transform.then -> $confirm.notify("success") $rootscope.$broadcast("object:updated") finishCallback() transform.then null, -> $confirm.notify("error") item.revert() transform.finally -> finishCallback() return transform block = () -> currentLoading = $loading() .target($el.find(".button-green")) .start() transform = $modelTransform.save (item) -> item.is_blocked = true item.blocked_note = $el.find(".reason").val() return item transform.then -> $confirm.notify("success") $rootscope.$broadcast("object:updated") transform.then null, -> $confirm.notify("error") transform.finally -> currentLoading.finish() lightboxService.close($el) $scope.$on "block", -> $el.find(".reason").val($model.$modelValue.blocked_note) lightboxService.open($el) $scope.$on "unblock", (event, model, finishCallback) => unblock(finishCallback) $scope.$on "$destroy", -> $el.off() $el.on "click", ".button-green", (event) -> event.preventDefault() block() return { templateUrl: "common/lightbox/lightbox-block.html" link: link require: "ngModel" } module.directive("tgLbBlock", ["$rootScope", "$tgRepo", "$tgConfirm", "lightboxService", "$tgLoading", "$tgQueueModelTransformation", "$translate", BlockLightboxDirective]) ############################################################################# ## Generic Lightbox Blocking-Message Input Directive ############################################################################# BlockingMessageInputDirective = ($log, $template, $compile) -> template = $template.get("common/lightbox/lightbox-blocking-message-input.html", true) link = ($scope, $el, $attrs, $model) -> if not $attrs.watch return $log.error "No watch attribute on tg-blocking-message-input directive" $scope.$watch $attrs.watch, (value) -> if value is not undefined and value == true $el.find(".blocked-note").removeClass("hidden") else $el.find(".blocked-note").addClass("hidden") templateFn = ($el, $attrs) -> return template({ngmodel: $attrs.ngModel}) return { template: templateFn link: link require: "ngModel" restrict: "EA" } module.directive("tgBlockingMessageInput", ["$log", "$tgTemplate", "$compile", BlockingMessageInputDirective]) ############################################################################# ## Creare Bulk Userstories Lightbox Directive ############################################################################# CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $loading, $model) -> link = ($scope, $el, attrs) -> form = null $scope.$on "usform:bulk", (ctx, projectId, status) -> form.reset() if form $scope.new = { projectId: projectId statusId: status bulk: "" } lightboxService.open($el) submit = debounce 2000, (event) => event.preventDefault() form = $el.find("form").checksley({onlyOneErrorElement: true}) if not form.validate() return currentLoading = $loading() .target(submitButton) .start() promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk) promise.then (result) -> result = _.map(result.data, (x) => $model.make_model('userstories', x)) currentLoading.finish() $rootscope.$broadcast("usform:bulk:success", result) lightboxService.close($el) promise.then null, (data) -> currentLoading.finish() form.setErrors(data) if data._error_message $confirm.notify("error", data._error_message) submitButton = $el.find(".submit-button") $el.on "submit", "form", submit $scope.$on "$destroy", -> $el.off() return {link: link} module.directive("tgLbCreateBulkUserstories", [ "$tgRepo", "$tgResources", "$rootScope", "lightboxService", "$tgLoading", "$tgModel", CreateBulkUserstoriesDirective ]) ############################################################################# ## AssignedTo Lightbox Directive ############################################################################# AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService, $userListService) -> link = ($scope, $el, $attrs) -> selectedUser = null selectedItem = null usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true) render = (selected, text) -> users = $userListService.searchUsers(text, selected) visibleUsers = _.slice(users, 0, 5) visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user) if selected selected.avatar = avatarService.getAvatar(selected) if selected ctx = { selected: selected users: _.slice(users, 0, 5) showMore: users.length > 5 } html = usersTemplate(ctx) html = $compile(html)($scope) $el.find(".assigned-to-list").html(html) closeLightbox = () -> lightboxKeyboardNavigationService.stop() lightboxService.close($el) $scope.$on "assigned-to:add", (ctx, item) -> selectedItem = item assignedToId = item.assigned_to selectedUser = $scope.usersById[assignedToId] render(selectedUser) lightboxService.open($el).then -> $el.find('input').focus() lightboxKeyboardNavigationService.init($el) $scope.$watch "usersSearch", (searchingText) -> if searchingText? render(selectedUser, searchingText) $el.find('input').focus() $el.on "click", ".user-list-single", (event) -> event.preventDefault() target = angular.element(event.currentTarget) closeLightbox() $scope.$apply -> $scope.$broadcast("assigned-to:added", target.data("user-id"), selectedItem) $scope.usersSearch = null $el.on "click", ".remove-assigned-to", (event) -> event.preventDefault() event.stopPropagation() closeLightbox() $scope.$apply -> $scope.usersSearch = null $scope.$broadcast("assigned-to:added", null, selectedItem) $el.on "click", ".close", (event) -> event.preventDefault() closeLightbox() $scope.$apply -> $scope.usersSearch = null $scope.$on "$destroy", -> $el.off() return { templateUrl: "common/lightbox/lightbox-assigned-to.html" link:link } module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", "tgUserListService", AssignedToLightboxDirective]) ############################################################################# ## Assigned Users Lightbox directive ############################################################################# AssignedUsersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService, $userListService) -> link = ($scope, $el, $attrs) -> selectedUsers = [] selectedItem = null usersTemplate = $template.get("common/lightbox/lightbox-assigned-users-users.html", true) # Render the specific list of users. render = (assignedUsersIds, text) -> users = $userListService.searchUsers(text) # Add selected users selected = [] _.map users, (user) -> if user.id in assignedUsersIds user.avatar = avatarService.getAvatar(user) selected.push(user) # Filter users in searchs visible = [] _.map users, (user) -> if user.id not in assignedUsersIds user.avatar = avatarService.getAvatar(user) visible.push(user) ctx = { selected: selected users: _.slice(visible, 0, 5) showMore: users.length > 5 } html = usersTemplate(ctx) html = $compile(html)($scope) $el.find(".assigned-to-list").html(html) closeLightbox = () -> lightboxKeyboardNavigationService.stop() lightboxService.close($el) $scope.$on "assigned-user:add", (ctx, item) -> selectedItem = item selectedUsers = item.assigned_users render(selectedUsers) lightboxService.open($el).then -> $el.find("input").focus() lightboxKeyboardNavigationService.init($el) $scope.$watch "usersSearch", (searchingText) -> if searchingText? render(selectedUsers, searchingText) $el.find('input').focus() $el.on "click", ".user-list-single", debounce 200, (event) -> closeLightbox() event.preventDefault() target = angular.element(event.currentTarget) $scope.$apply -> $scope.usersSearch = null $scope.$broadcast("assigned-user:added", target.data("user-id"), selectedItem) $el.on "click", ".remove-assigned-to", (event) -> event.preventDefault() target = angular.element(event.currentTarget) event.stopPropagation() $scope.$apply -> $scope.usersSearch = null $scope.$broadcast("assigned-user:deleted", target.data("user-id"), selectedItem) closeLightbox() $el.on "click", ".close", (event) -> event.preventDefault() closeLightbox() $scope.$on "$destroy", -> $el.off() return { templateUrl: "common/lightbox/lightbox-assigned-users.html" link:link } module.directive("tgLbAssignedUsers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", "tgUserListService", AssignedUsersLightboxDirective]) ############################################################################# ## Watchers Lightbox directive ############################################################################# WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile, avatarService) -> link = ($scope, $el, $attrs) -> selectedItem = null usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true) # Get prefiltered users by text # and without now watched users. getFilteredUsers = (text="") -> _filterUsers = (text, user) -> if selectedItem && _.find(selectedItem.watchers, (x) -> x == user.id) return false username = user.full_name_display.toUpperCase() text = text.toUpperCase() return _.includes(username, text) users = _.clone($scope.activeUsers, true) users = _.filter(users, _.partial(_filterUsers, text)) return users # Render the specific list of users. render = (users) -> visibleUsers = _.slice(users, 0, 5) visibleUsers = _.map visibleUsers, (user) -> user.avatar = avatarService.getAvatar(user) return user ctx = { selected: false users: visibleUsers showMore: users.length > 5 } html = usersTemplate(ctx) html = $compile(html)($scope) $el.find(".ticket-watchers").html(html) closeLightbox = () -> lightboxKeyboardNavigationService.stop() lightboxService.close($el) $scope.$on "watcher:add", (ctx, item) -> selectedItem = item users = getFilteredUsers() render(users) lightboxService.open($el).then -> $el.find("input").focus() lightboxKeyboardNavigationService.init($el) $scope.$watch "usersSearch", (searchingText) -> if not searchingText? return users = getFilteredUsers(searchingText) render(users) $el.find("input").focus() $el.on "click", ".user-list-single", debounce 200, (event) -> closeLightbox() event.preventDefault() target = angular.element(event.currentTarget) $scope.$apply -> $scope.usersSearch = null $scope.$broadcast("watcher:added", target.data("user-id")) $el.on "click", ".close", (event) -> event.preventDefault() closeLightbox() $scope.$apply -> $scope.usersSearch = null $scope.$on "$destroy", -> $el.off() return { templateUrl: "common/lightbox/lightbox-users.html" link:link } module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", "tgAvatarService", WatchersLightboxDirective]) LightboxLeaveProjectWarningDirective = (lightboxService, $template, $compile) -> link = ($scope, $el, attrs) -> lightboxService.open($el) return { templateUrl: 'common/lightbox/lightbox-leave-project-warning.html', link: link, scope: true } module.directive("tgLightboxLeaveProjectWarning", ["lightboxService", LightboxLeaveProjectWarningDirective]) ############################################################################# ## Set Due Date Lightbox Directive ############################################################################# SetDueDateDirective = ($rootscope, lightboxService, $loading, $translate, $confirm, $modelTransform) -> link = ($scope, $el, attrs) -> prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT") lightboxService.open($el) if ($scope.object.due_date) $scope.new_due_date = moment($scope.object.due_date).format(prettyDate) $el.on "click", ".suggestion", (event) -> target = angular.element(event.currentTarget) quantity = target.data('quantity') unit = target.data('unit') value = moment().add(quantity, unit).format(prettyDate) $el.find(".due-date").val(value) save = -> currentLoading = $loading() .target($el.find(".submit-button")) .start() if $scope.notAutoSave new_due_date = $('.due-date').val() $scope.object.due_date = if (new_due_date) \ then moment(new_due_date, prettyDate).format("YYYY-MM-DD") \ else null $scope.$apply() currentLoading.finish() lightboxService.close($el) return transform = $modelTransform.save (object) -> new_due_date = $('.due-date').val() object.due_date = if (new_due_date) \ then moment(new_due_date, prettyDate).format("YYYY-MM-DD") \ else null return object transform.then -> $confirm.notify("success") transform.then null, -> $confirm.notify("error") transform.finally -> currentLoading.finish() lightboxService.close($el) $rootscope.$broadcast("object:updated") $el.on "click", ".submit-button", (event) -> event.preventDefault() save() remove = -> title = $translate.instant("LIGHTBOX.DELETE_DUE_DATE.TITLE") subtitle = $translate.instant("LIGHTBOX.DELETE_DUE_DATE.SUBTITLE") message = moment($scope.object.due_date).format(prettyDate) $confirm.askOnDelete(title, message, subtitle).then (askResponse) -> askResponse.finish() $('.due-date').val(null) $scope.object.due_date_reason = null if $scope.notAutoSave $scope.object.due_date = null lightboxService.close($el) else save() $el.on "click", ".delete-due-date", (event) -> event.preventDefault() remove() return { templateUrl: 'common/lightbox/lightbox-due-date.html', link: link, scope: true } module.directive("tgLbSetDueDate", ["$rootScope", "lightboxService", "$tgLoading", "$translate", "$tgConfirm" "$tgQueueModelTransformation", SetDueDateDirective]) ############################################################################# ## Create/Edit Lightbox Directive ############################################################################# groupBy = @.taiga.groupBy CreateEditDirective = ( $log, $repo, $model, $rs, $rootScope, lightboxService, $loading, $translate, $confirm, $q, attachmentsService, $template, $compile) -> link = ($scope, $el, attrs) -> schema = null objType = null form = null attachmentsToAdd = Immutable.List() attachmentsToDelete = Immutable.List() schemas = { us: { objName: 'User Story', model: 'userstories', params: { include_attachments: true, include_tasks: true }, data: (project) -> return { translationID: 'US' translationIDPlural: 'US' statusList: _.sortBy(project.us_statuses, "order") } initialData: (data) -> return { project: data.project.id subject: "" description: "" tags: [] points : {} status: if data.statusId then data.statusId else data.project.default_us_status is_archived: false } } task: { objName: 'Task', model: 'tasks', params: { include_attachments: true }, data: (project) -> return { translationID: 'TASK' translationIDPlural: 'TASKS' statusList: _.sortBy(project.task_statuses, "order") } initialData: (data) -> return { project: data.project.id subject: "" description: "" assigned_to: null tags: [] milestone: data.sprintId status: data.project.default_task_status user_story: data.usId is_archived: false } }, issue: { objName: 'Issue', model: 'issues', params: { include_attachments: true }, data: (project) -> return { translationID: 'ISSUE' translationIDPlural: 'ISSUES' project: project statusList: _.sortBy(project.issue_statuses, "order") typeById: groupBy(project.issue_types, (x) -> x.id) typeList: _.sortBy(project.issue_types, "order") severityById: groupBy(project.severities, (x) -> x.id) severityList: _.sortBy(project.severities, "order") priorityById: groupBy(project.priorities, (x) -> x.id) priorityList: _.sortBy(project.priorities, "order") milestonesById: groupBy(project.milestones, (x) -> x.id) } initialData: (data) -> return { assigned_to: null milestone: data.sprintId priority: data.project.default_priority project: data.project.id severity: data.project.default_severity status: data.project.default_issue_status subject: "" tags: [] type: data.project.default_issue_type } } } $scope.setMode = (value) -> $scope.mode = value $scope.$on "genericform:new", (ctx, params) -> getSchema(params) $scope.mode = 'new' $scope.getOrCreate = false mount(params) $scope.$on "genericform:new-or-existing", (ctx, params) -> getSchema(params) $scope.mode = 'add-existing' $scope.getOrCreate = true $scope.existingFilterText = '' $rs[schema.model].listInAllProjects({ project: $scope.project.id }, true).then (data) -> $scope.existingItems = angular.copy(data) mount(params) $scope.$on "genericform:edit", (ctx, params) -> getSchema(params) $scope.mode = 'edit' $scope.getOrCreate = false mount(params) getSchema = (params) -> _.map params, (value, key) -> $scope[key] = value if !$scope.objType || !schemas[$scope.objType] return $log.error("Invalid objType `#{$scope.objType}` for `genericform` event") schema = schemas[$scope.objType] mount = (params) -> $scope.objName = schema.objName if $scope.mode == 'edit' $scope.obj = params.obj $scope.attachments = Immutable.fromJS(params.attachments) else $scope.obj = $model.make_model(schema.model, schema.initialData(params)) $scope.attachments = Immutable.List() _.map schema.data($scope.project), (value, key) -> $scope[key] = value form.reset() if form resetAttachments() setStatus($scope.obj.status) render() $scope.lightboxOpen = true lightboxService.open($el, null, null, true) resetAttachments = () -> attachmentsToAdd = Immutable.List() attachmentsToDelete = Immutable.List() $scope.addAttachment = (attachment) -> attachmentsToAdd = attachmentsToAdd.push(attachment) $scope.deleteAttachment = (attachment) -> attachmentsToAdd = attachmentsToAdd.filter (it) -> return it.get('name') != attachment.get('name') if attachment.get("id") attachmentsToDelete = attachmentsToDelete.push(attachment) $scope.addTag = (tag, color) -> value = trim(tag.toLowerCase()) tags = $scope.project.tags projectTags = $scope.project.tags_colors tags = [] if not tags? projectTags = {} if not projectTags? if value not in tags tags.push(value) projectTags[tag] = color || null $scope.project.tags = tags itemtags = _.clone($scope.obj.tags) inserted = _.find itemtags, (it) -> it[0] == value if !inserted itemtags.push([value , color]) $scope.obj.tags = itemtags $scope.deleteTag = (tag) -> value = trim(tag[0].toLowerCase()) tags = $scope.project.tags itemtags = _.clone($scope.obj.tags) _.remove itemtags, (tag) -> tag[0] == value $scope.obj.tags = itemtags _.pull($scope.obj.tags, value) createAttachments = (obj) -> promises = _.map attachmentsToAdd.toJS(), (attachment) -> attachmentsService.upload(attachment.file, obj.id, $scope.obj.project, $scope.objType) return $q.all(promises) deleteAttachments = (obj) -> promises = _.map attachmentsToDelete.toJS(), (attachment) -> return attachmentsService.delete($scope.objType, attachment.id) return $q.all(promises) addExistingToSprint = (item) -> currentLoading = $loading().target($el.find(".add-existing-button")).start() if item.milestone sprintChangeConfirmAndSave(item) else onSuccess = -> close() $rootScope.$broadcast("#{$scope.objType}form:add:success", item) onError = -> close() saveItem(item, onSuccess, onError) sprintChangeConfirmAndSave = (item) -> oldSprintName = $scope.milestonesById[item.milestone].name newSprintName = $scope.milestonesById[$scope.relatedObjectId].name title = $translate.instant("ISSUES.CONFIRM_CHANGE_FROM_SPRINT.TITLE") message = $translate.instant("ISSUES.CONFIRM_CHANGE_FROM_SPRINT.MESSAGE", {issue: item.subject, oldSprintName: oldSprintName, newSprintName: newSprintName}) $confirm.ask(title, null, message).then (askResponse) -> onSuccess = -> askResponse.finish() lightboxService.closeAll() $scope.lightboxOpen = false $rootScope.$broadcast("#{$scope.objType}form:add:success", item) onError = -> askResponse.finish(false) $confirm.notify("error") saveItem(item, onSuccess, onError) saveItem = (item, onSuccess, onError) -> item.setAttr($scope.relatedField, $scope.relatedObjectId) $repo.save(item, true).then(onSuccess, onError) isDisabledExisting = (item) -> return item && item[$scope.relatedField] == $scope.relatedObjectId $scope.isDisabledExisting = (selectedItem) -> isDisabledExisting(selectedItem) $scope.addExistingToSprint = (selectedItem) -> addExistingToSprint(selectedItem) submit = debounce 2000, (event) -> form = $el.find("form").checksley() if not form.validate() return currentLoading = $loading().target($el.find(".submit-button")).start() if $scope.mode == 'new' promise = $repo.create(schema.model, $scope.obj) broadcastEvent = "#{$scope.objType}form:new:success" else if ($scope.obj.due_date instanceof moment) prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT") $scope.obj.due_date = $scope.obj.due_date.format("YYYY-MM-DD") promise = $repo.save($scope.obj, true) broadcastEvent = "#{$scope.objType}form:edit:success" promise.then (data) -> deleteAttachments(data).then () -> createAttachments(data).then () -> currentLoading.finish() close() if data.ref $rs[schema.model].getByRef(data.project, data.ref, schema.params).then (obj) -> $rootScope.$broadcast(broadcastEvent, obj) promise.then null, (data) -> currentLoading.finish() form.setErrors(data) if data._error_message $confirm.notify("error", data._error_message) checkClose = () -> if !$scope.obj.isModified() close() $scope.$apply -> $scope.obj.revert() else $confirm.ask( $translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")) .then (result) -> result.finish() close() close = () -> lightboxService.closeAll() $scope.lightboxOpen = false docEl = angular.element(document) docEl.on "keydown.lightbox-create-edit", (event) -> if $scope.lightboxOpen event.stopPropagation() code = if event.keyCode then event.keyCode else event.which if code == 27 checkClose() $el.on "submit", "form", submit $el.find('.close').on "click", (event) -> event.preventDefault() event.stopPropagation() checkClose() $el.on "click", ".status-dropdown", (event) -> event.preventDefault() event.stopPropagation() $el.find(".pop-status").popover().open() $el.on "click", ".status", (event) -> event.preventDefault() event.stopPropagation() setStatus(angular.element(event.currentTarget).data("status-id")) $scope.$apply() $scope.$broadcast("status:changed", $scope.obj.status) $el.find(".pop-status").popover().close() $el.on "click", ".team-requirement", (event) -> $scope.obj.team_requirement = not $scope.obj.team_requirement $scope.$apply() $el.on "click", ".client-requirement", (event) -> $scope.obj.client_requirement = not $scope.obj.client_requirement $scope.$apply() $el.on "click", ".is-blocked", (event) -> $scope.obj.is_blocked = not $scope.obj.is_blocked $scope.$apply() $el.on "click", ".iocaine", (event) -> $scope.obj.is_iocaine = not $scope.obj.is_iocaine $scope.$broadcast("isiocaine:changed", $scope.obj) $scope.isTeamRequirement = () -> return $scope.obj?.team_requirement $scope.isClientRequirement = () -> return $scope.obj?.client_requirement setStatus = (id) -> $scope.obj.status = id $scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id $scope.obj.is_closed = $scope.selectedStatus.is_closed render = (sprint) -> template = $template.get("common/lightbox/lightbox-create-edit/lb-create-edit.html") templateScope = $scope.$new() compiledTemplate = $compile(template)(templateScope) $el.html(compiledTemplate) return { link: link } module.directive("tgLbCreateEdit", [ "$log", "$tgRepo", "$tgModel", "$tgResources", "$rootScope", "lightboxService", "$tgLoading", "$translate", "$tgConfirm", "$q", "tgAttachmentsService", "$tgTemplate", "$compile", CreateEditDirective ]) ############################################################################# ## RelateToEpic Lightbox Directive ############################################################################# debounceLeading = @.taiga.debounceLeading RelateToEpicLightboxDirective = ($rootScope, $confirm, lightboxService, $tgCurrentUserService tgResources, $tgResources, $epicsService, tgAnalytics) -> link = ($scope, $el, $attrs) -> us = null $scope.projects = null $scope.projectEpics = Immutable.List() $scope.loading = false $scope.selectedProject = $scope.project.id newEpicForm = $el.find(".new-epic-form").checksley() existingEpicForm = $el.find(".existing-epic-form").checksley() loadProjects = -> if $scope.projects == null $scope.projects = $tgCurrentUserService.projects.get("unblocked") filterEpics = (selectedProjectId, filterText) -> tgResources.epics.listInAllProjects( { is_epics_activated: true, project__blocked_code: 'null', project: selectedProjectId, q: filterText }, true).then (data) -> excludeIds = [] if (us.epics) excludeIds = us.epics.map((epic) -> epic.id) filteredData = data.filter((epic) -> excludeIds.indexOf(epic.get('id')) == -1) $scope.projectEpics = filteredData selectProject = (selectedProjectId) -> $scope.selectedEpic = null $scope.searchEpic = "" filterEpics(selectedProjectId, $scope.searchEpic) $el.on "click", ".close", (event) -> event.preventDefault() lightboxService.close($el) $scope.$on "relate-to-epic:add", (ctx, item) -> us = item $scope.selectedEpic = null $scope.searchEpic = "" loadProjects() filterEpics($scope.selectedProject, $scope.searchEpic).then () -> lightboxService.open($el).then -> $el.find('input').focus $scope.$on "$destroy", -> $el.off() $scope.selectProject = (selectedProjectId) -> selectProject(selectedProjectId) $scope.onUpdateSearchEpic = debounceLeading 300, () -> $scope.selectedEpic = null filterEpics($scope.selectedProject, $scope.searchEpic) $scope.saveRelatedEpic = (selectedEpicId, onSavedRelatedEpic) -> return if not existingEpicForm.validate() $scope.loading = true onError = (data) -> $scope.loading = false $confirm.notify("error") existingEpicForm.setErrors(data) onSuccess = (data) -> tgAnalytics.trackEvent( "user story related epic", "create", "create related epic on user story", 1) $scope.loading = false $rootScope.$broadcast("related-epics:changed", us) lightboxService.close($el) usId = us.id tgResources.epics.addRelatedUserstory(selectedEpicId, usId).then( onSuccess, onError) $scope.createEpic = (selectedProjectId, epicSubject) -> return if not newEpicForm.validate() @.loading = true onError = (data)-> $scope.loading = false $confirm.notify("error") newEpicForm.setErrors(errors) onSuccess = () -> tgAnalytics.trackEvent( "user story related epic", "create", "create related epic on user story", 1) $scope.loading = false $rootScope.$broadcast("related-epics:changed", us) lightboxService.close($el) onCreateEpic = (epic) -> epicId = epic.get('id') usId = us.id tgResources.epics.addRelatedUserstory(epicId, usId).then(onSuccess, onError) $epicsService.createEpic( {subject: epicSubject}, null, selectedProjectId).then(onCreateEpic, onError) return { templateUrl: "common/lightbox/lightbox-relate-to-epic.html" link:link } module.directive("tgLbRelatetoepic", [ "$rootScope", "$tgConfirm", "lightboxService", "tgCurrentUserService", "tgResources", "$tgResources", "tgEpicsService", "$tgAnalytics", RelateToEpicLightboxDirective])