diff --git a/app/coffee/modules/common/confirm.coffee b/app/coffee/modules/common/confirm.coffee
index af2948c8..0e7657df 100644
--- a/app/coffee/modules/common/confirm.coffee
+++ b/app/coffee/modules/common/confirm.coffee
@@ -61,7 +61,7 @@ class ConfirmService extends taiga.Service
# Render content
el.find(".title").text(title) if title
el.find(".subtitle").text(subtitle) if subtitle
- el.find(".message").text(message) if message
+ el.find(".message").html(message) if message
# Assign event handlers
el.on "click.confirm-dialog", ".button-green", debounce 2000, (event) =>
diff --git a/app/coffee/modules/common/lightboxes.coffee b/app/coffee/modules/common/lightboxes.coffee
index 4bd1ab9a..38724a29 100644
--- a/app/coffee/modules/common/lightboxes.coffee
+++ b/app/coffee/modules/common/lightboxes.coffee
@@ -840,6 +840,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
}
}
+ $scope.setMode = (value) ->
+ $scope.mode = value
+
$scope.$on "genericform:new", (ctx, params) ->
getSchema(params)
$scope.mode = 'new'
@@ -851,9 +854,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.mode = 'add-existing'
$scope.getOrCreate = true
$scope.existingFilterText = ''
- $scope.existingItems = []
+
$rs[schema.model].listInAllProjects({ project: $scope.project.id }, true).then (data) ->
- $scope.existingItems = data
+ $scope.existingItems = angular.copy(data)
mount(params)
$scope.$on "genericform:edit", (ctx, params) ->
@@ -885,6 +888,7 @@ $confirm, $q, attachmentsService, $template, $compile) ->
form.reset() if form
resetAttachments()
setStatus($scope.obj.status)
+ render()
$scope.lightboxOpen = true
lightboxService.open($el)
@@ -950,6 +954,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
lightboxService.close($el)
$rootScope.$broadcast("#{$scope.objType}form:add:success", item)
+ $scope.isDisabledExisting = (item) ->
+ return item && item[$scope.relatedField] == $scope.relatedObjectId
+
$scope.addExisting = (selectedItem) ->
event.preventDefault()
addExisting(selectedItem)
@@ -976,7 +983,7 @@ $confirm, $q, attachmentsService, $template, $compile) ->
deleteAttachments(data).then () ->
createAttachments(data).then () ->
currentLoading.finish()
- close()
+ lightboxService.close($el)
$rs[schema.model].getByRef(data.project, data.ref, schema.params).then (obj) ->
$rootScope.$broadcast(broadcastEvent, obj)
promise.then null, (data) ->
@@ -987,20 +994,14 @@ $confirm, $q, attachmentsService, $template, $compile) ->
checkClose = () ->
if !$scope.obj.isModified()
- close()
+ lightboxService.close($el)
$scope.$apply ->
$scope.obj.revert()
else
$confirm.ask(
$translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")).then (result) ->
result.finish()
- close()
-
- close = () ->
- delete $scope.objType
- delete $scope.mode
- $scope.lightboxOpen = false
- lightboxService.close($el)
+ lightboxService.close($el)
$el.on "submit", "form", submit
@@ -1060,9 +1061,14 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$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
- templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"
}
module.directive("tgLbCreateEdit", [
diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee
index b76df140..7bcf2661 100644
--- a/app/coffee/modules/issues/detail.coffee
+++ b/app/coffee/modules/issues/detail.coffee
@@ -704,22 +704,47 @@ module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfi
## Add Issue to Sprint button directive
#############################################################################
-AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $translate, lightboxService) ->
+AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $translate,
+lightboxService, $confirm) ->
link = ($scope, $el, $attrs, $model) ->
avaliableMilestones = []
issue = null
- $el.on "click", "a", (event) ->
+ $el.on "click", ".assign-issue-button.button-unset", (event) ->
event.preventDefault()
event.stopPropagation()
- title = $translate.instant("ISSUES.ACTION_ASSIGN_SPRINT")
+ title = $translate.instant("ISSUES.ACTION_ATTACH_SPRINT")
issue = $model.$modelValue
$rs.sprints.list($scope.projectId, null).then (data) ->
$scope.milestones = data.milestones
- $scope.selectedSprintId = issue.milestone
+ $scope.selectedSprint = issue.milestone
avaliableMilestones = angular.copy($scope.milestones)
lightboxService.open($el.find(".lightbox-assign-sprint-to-issue"))
+ $el.on "click", ".assign-issue-button.button-set", (event) ->
+ event.preventDefault()
+ event.stopPropagation()
+ issue = $model.$modelValue
+ $rs.sprints.list($scope.projectId, null).then (data) ->
+ currentSprint = _.find(data.milestones, { "id": issue.milestone })
+
+ title = $translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.TITLE")
+ message = $translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.MESSAGE")
+ message += " #{currentSprint.name}"
+
+ $confirm.ask(title, null, message).then (askResponse) ->
+ onSuccess = ->
+ $scope.$broadcast("assign-sprint-to-issue:success", null)
+ askResponse.finish()
+ lightboxService.close($el)
+
+ onError = ->
+ askResponse.finish(false)
+ $confirm.notify("error")
+
+ issue.setAttr('milestone', null)
+ $repo.save(issue, true).then(onSuccess, onError)
+
$scope.$on "$destroy", ->
$el.off()
@@ -750,5 +775,5 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
}
module.directive("tgAssignSprintToIssueButton", ["$rootScope", "$tgResources", "$tgRepo",
- "$tgLoading", "$translate", "lightboxService",
+ "$tgLoading", "$translate", "lightboxService", "$tgConfirm"
AssignSprintToIssueButtonDirective] )
diff --git a/app/coffee/modules/taskboard/main.coffee b/app/coffee/modules/taskboard/main.coffee
index 1e829018..b781a49e 100644
--- a/app/coffee/modules/taskboard/main.coffee
+++ b/app/coffee/modules/taskboard/main.coffee
@@ -564,8 +564,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@rs.issues.getByRef(issue.getIn(['model', 'project']), issue.getIn(['model', 'ref']))
.then (removingIssue) =>
issue = issue.set('loading-delete', false)
- title = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.TITLE")
- subtitle = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.MESSAGE")
+ title = @translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.TITLE")
+ subtitle = @translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.MESSAGE")
message = removingIssue.subject
@confirm.askOnDelete(title, message, subtitle).then (askResponse) =>
removingIssue.milestone = null
diff --git a/app/locales/taiga/locale-en.json b/app/locales/taiga/locale-en.json
index d9b07c77..da3e94f8 100644
--- a/app/locales/taiga/locale-en.json
+++ b/app/locales/taiga/locale-en.json
@@ -1102,7 +1102,7 @@
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?",
"EXISTING_OBJECT": "Existing {{ objName }}",
"NEW_OBJECT": "New {{ objName }}",
- "CHOOSE_EXISTING": "What's the {{ objName }}?",
+ "CHOOSE_EXISTING": "Which {{ objName }}?",
"NO_ITEMS_FOUND": "It looks like nothing was found with your search criteria"
},
"DELETE_DUE_DATE": {
@@ -1164,11 +1164,11 @@
"CREATE_RELATED_USERSTORIES": "Create a relationship with",
"NEW_USERSTORY": "New user story",
"EXISTING_USERSTORY": "Existing user story",
- "CHOOSE_PROJECT_FOR_CREATION": "What's the project?",
+ "CHOOSE_PROJECT_FOR_CREATION": "Which project?",
"SUBJECT": "Subject",
"SUBJECT_BULK_MODE": "Subject (bulk insert)",
- "CHOOSE_PROJECT_FROM": "What's the project?",
- "CHOOSE_USERSTORY": "What's the user story?",
+ "CHOOSE_PROJECT_FROM": "Which project?",
+ "CHOOSE_USERSTORY": "Which user story?",
"NO_USERSTORIES": "This project has no User Stories yet. Please select another project.",
"NO_USERSTORIES_FOUND": "It looks like nothing was found with your search criteria",
"FILTER_USERSTORIES": "Filter user stories",
@@ -1424,7 +1424,8 @@
"SECTION_NAME": "Issue",
"ACTION_NEW_ISSUE": "+ NEW ISSUE",
"ACTION_PROMOTE_TO_US": "Promote to User Story",
- "ACTION_ASSIGN_SPRINT": "Add issue to Sprint",
+ "ACTION_ATTACH_SPRINT": "Attach issue to Sprint",
+ "ACTION_DETACH_SPRINT": "Detach issue from Sprint",
"ACTION_REMOVE_FROM_SPRINT": "Remove issue from Sprint {{ sprintName }}",
"PROMOTED": "This issue has been promoted to US:",
"EXTERNAL_REFERENCE": "This issue has been created from",
@@ -1434,15 +1435,16 @@
"LINK_TASKBOARD": "Taskboard",
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
"FILTER_SPRINTS": "Filter Sprints",
- "CHOOSE_SPRINT": "What's the Sprint?",
+ "CHOOSE_SPRINT": "Which Sprint?",
"FIELDS": {
"PRIORITY": "Priority",
"SEVERITY": "Severity",
"TYPE": "Type"
},
- "CONFIRM_REMOVE_FROM_SPRINT": {
- "TITLE": "Remove issue from sprint",
- "MESSAGE": "Are you sure you want to remove this issue from the sprint?"
+ "FILTER_ISSUES": "Filter Issues",
+ "CONFIRM_DETACH_FROM_SPRINT": {
+ "TITLE": "Detach issue from Sprint",
+ "MESSAGE": "You are about to detach the issue from the sprint"
},
"CONFIRM_PROMOTE": {
"TITLE": "Promote this issue to a new user story",
diff --git a/app/modules/components/due-date/due-date-popover.directive.coffee b/app/modules/components/due-date/due-date-popover.directive.coffee
index 629a260e..3088cc9e 100644
--- a/app/modules/components/due-date/due-date-popover.directive.coffee
+++ b/app/modules/components/due-date/due-date-popover.directive.coffee
@@ -39,7 +39,7 @@ dueDatePopoverDirective = ($translate, datePickerConfigService) ->
return
event.preventDefault()
event.stopPropagation()
- if !el.picker.getDate()
+ if !el.picker.getDate() && ctrl.dueDate
el.picker.setDate(moment(ctrl.dueDate).format('YYYY-MM-DD'))
el.find(".date-picker-popover").popover().open()
diff --git a/app/modules/components/search-list/search-list.directive.coffee b/app/modules/components/search-list/search-list.directive.coffee
index 21812cfa..ddc0cd42 100644
--- a/app/modules/components/search-list/search-list.directive.coffee
+++ b/app/modules/components/search-list/search-list.directive.coffee
@@ -33,6 +33,13 @@ searchListDirective = ($translate) ->
if scope.itemType == 'issue'
scope.milestonesById = groupBy(scope.project.milestones, (e) -> e.id)
+ if scope.filterClosed
+ scope.showClosed = false
+
+ if scope.itemType == 'sprint'
+ scope.textShowClosed = $translate.instant("BACKLOG.SPRINTS.ACTION_SHOW_CLOSED_SPRINTS")
+ scope.textHideClosed = $translate.instant("BACKLOG.SPRINTS.ACTION_HIDE_CLOSED_SPRINTS")
+
el.on "click", ".choice", (event) ->
choiceId = parseInt($(event.currentTarget).data("choice-id"))
value = if attrs.ngModel?.id != choiceId then itemsById[choiceId] else null
@@ -48,30 +55,45 @@ searchListDirective = ($translate) ->
value = value.toString()
return normalizeString(value.toUpperCase())
- el.on "blur", "#items-filter", (event) ->
- filtering = false
+ resetSelected = () ->
+ scope.currentSelected = null
+ model.$setViewValue(null)
- el.on "focus", "#items-filter", (event) ->
- filtering = true
+ resetAll = () ->
+ resetSelected()
+ scope.searchText = ''
+ avaliableItems = angular.copy(scope.items)
+ itemsById = groupBy(avaliableItems, (x) -> x.id)
+
+
+ scope.isVisible = (item) ->
+ if !scope.filterClosed || scope.showClosed
+ return true
+ if (scope.itemType == 'sprint' && (item.closed || item.is_closed))
+ if (scope.currentSelected?.id == item.id)
+ resetSelected()
+ return false
+ return true
+
+ scope.toggleShowClosed = (item) ->
+ scope.showClosed = !scope.showClosed
scope.filterItems = (searchText) ->
- scope.items = avaliableItems.filter((item) ->
+ scope.itemDisabled(null)
+ scope.filtering = true
+ scope.items = _.filter(avaliableItems, (item) ->
itemAttrs = item.getAttrs()
if Array.isArray(scope.filterBy)
_.some(scope.filterBy, (attr) -> isContainedIn(searchText, itemAttrs[attr]))
else
isContainedIn(searchText, itemAttrs[scope.filterBy])
)
- if scope.value
- scope.value = _.find(scope.items, scope.currentSelected)
+ if !_.find(scope.items, scope.currentSelected)
+ resetSelected()
scope.$watch 'items', (items) ->
- if !filtering && items?.length
- if scope.resetOnChange
- scope.currentSelected = null
- model.$setViewValue(null)
- avaliableItems = angular.copy(items)
- itemsById = groupBy(avaliableItems, (x) -> x.id)
+ if !scope.filtering && items
+ resetAll()
return {
link: link,
@@ -84,7 +106,8 @@ searchListDirective = ($translate) ->
filterBy: '=',
items: '=',
itemType: '@',
- resetOnChange: "="
+ filterClosed: '=',
+ itemDisabled: '='
}
}
diff --git a/app/modules/components/search-list/search-list.jade b/app/modules/components/search-list/search-list.jade
index 8990adb2..97b27c0b 100644
--- a/app/modules/components/search-list/search-list.jade
+++ b/app/modules/components/search-list/search-list.jade
@@ -2,6 +2,18 @@ fieldset.search-list
label(
for="items-filter"
) {{ label }}
+
+ a.show-closed(
+ href=""
+ ng-if="showClosedVisible"
+ ng-click="toggleShowClosed()"
+ )
+ span(ng-if="!showClosed")
+ tg-svg(svg-icon="icon-archive")
+ span {{ textShowClosed }}
+ span(ng-if="showClosed")
+ tg-svg(svg-icon="icon-archive")
+ span {{ textHideClosed }}
input.items-filter(
id="items-filter"
type="text"
@@ -9,12 +21,13 @@ fieldset.search-list
ng-model="searchText"
ng-change="filterItems(searchText)"
)
-
ul
li.choice(
ng-repeat="item in items track by item.id"
ng-class="{ 'selected': item.id == currentSelected.id }"
data-choice-id="{{ item.id }}"
+ ng-if="isVisible(item)"
+ ng-disabled="itemDisabled(item)"
)
ng-include(src="templateUrl")
diff --git a/app/modules/components/search-list/search-list.scss b/app/modules/components/search-list/search-list.scss
index f0ef9350..7c26786d 100644
--- a/app/modules/components/search-list/search-list.scss
+++ b/app/modules/components/search-list/search-list.scss
@@ -1,4 +1,16 @@
.search-list {
+ .show-closed {
+ align-content: center;
+ align-items: center;
+ display: flex;
+ float: right;
+ font-size: .9em;
+ svg {
+ height: 1em;
+ margin-right: .25em;
+ width: 1em;
+ }
+ }
ul {
background: $mass-white;
border: 1px solid $gray-light;
@@ -7,7 +19,7 @@
max-height: 200px;
overflow-y: auto;
}
- li {
+ .choice {
cursor: pointer;
padding: .25em .5em;
&.selected {
@@ -17,6 +29,13 @@
color: $white;
}
}
+ &[disabled] {
+ color: $gray-lighter;
+ cursor: not-allowed;
+ .info {
+ color: $gray-lighter;
+ }
+ }
ng-include {
display: flex;
width: 100%;
@@ -25,7 +44,7 @@
text-align: left;
}
.info {
- color: $gray-lighter;
+ color: $gray-light;
text-align: right;
}
}
diff --git a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit.jade b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit.jade
index a6f49bc3..a685ea2a 100644
--- a/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit.jade
+++ b/app/partials/common/lightbox/lightbox-create-edit/lb-create-edit.jade
@@ -4,10 +4,10 @@ form(ng-if="lightboxOpen")
h2.title(ng-switch="mode")
span(ng-switch-when="new") {{ 'LIGHTBOX.CREATE_EDIT.NEW' | translate: { objName: objName } }}
span(ng-switch-when="edit") {{ 'LIGHTBOX.CREATE_EDIT.EDIT' | translate: { objName: objName } }}
- span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: existingOptions.title } }}
+ span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: title } }}
.existing-or-new-selector(ng-show="getOrCreate == true")
- .existing-or-new-selector-single
+ .existing-or-new-selector-single(ng-click="setMode('add-existing')")
input(
type="radio"
name="related-with-selector"
@@ -15,10 +15,10 @@ form(ng-if="lightboxOpen")
value="add-existing"
ng-model="mode"
)
- label.e2e-existing-user-story-label(for="add-existing")
+ label.e2e-existing-item-label(for="add-existing")
span.name {{ 'LIGHTBOX.CREATE_EDIT.EXISTING_OBJECT' | translate: { objName: objName } }}
- .existing-or-new-selector-single
+ .existing-or-new-selector-single(ng-click="setMode('new')")
input(
type="radio"
name="related-with-selector"
@@ -26,20 +26,20 @@ form(ng-if="lightboxOpen")
value="new"
ng-model="mode"
)
- label.e2e-new-userstory-label(for="new")
+ label.e2e-new-item-label(for="new")
span.name {{ 'LIGHTBOX.CREATE_EDIT.NEW_OBJECT' | translate: { objName: objName } }}
div(ng-if="mode == 'add-existing'")
.existing-item-wrapper
tg-search-list(
label="{{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}"
- placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
+ placeholder="{{ 'ISSUES.FILTER_ISSUES' | translate }}"
items="existingItems"
ng-model="selectedItem"
filter-by="['ref', 'subject']"
project="project"
item-type="{{ objType }}"
- reset-on-change="true"
+ item-disabled="isDisabledExisting"
)
button.button-green.add-existing-button(
diff --git a/app/partials/issue/assign-sprint-to-issue-button.jade b/app/partials/issue/assign-sprint-to-issue-button.jade
index d13c4b34..4c5b152c 100644
--- a/app/partials/issue/assign-sprint-to-issue-button.jade
+++ b/app/partials/issue/assign-sprint-to-issue-button.jade
@@ -1,17 +1,24 @@
-a.assign-issue-button.button-gray.is-editable(
+a.assign-issue-button.button-gray.is-editable.button-unset(
href=""
- tg-check-permission="add_us"
- title="{{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}"
- ng-class="{'button-set': issue.milestone}"
+ ng-show="!issue.milestone"
+ tg-check-permission="modify_issue"
+ title="{{ 'ISSUES.ACTION_ATTACH_SPRINT' | translate }}"
)
- tg-svg(svg-icon="icon-promote")
+ tg-svg(svg-icon="icon-attach")
+a.assign-issue-button.button-gray.is-editable.button-set(
+ href=""
+ ng-show="issue.milestone"
+ tg-check-permission="modify_issue"
+ title="{{ 'ISSUES.ACTION_DETACH_SPRINT' | translate }}"
+)
+ tg-svg(svg-icon="icon-detach")
.lightbox.lightbox-assign-sprint-to-issue
tg-lightbox-close
div.lightbox-assign-related-sprint
- h2.title {{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}
+ h2.title {{ 'ISSUES.ACTION_ATTACH_SPRINT' | translate }}
tg-search-list(
label="{{ 'ISSUES.CHOOSE_SPRINT' | translate }}"
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
@@ -26,4 +33,5 @@ a.assign-issue-button.button-gray.is-editable(
ng-click="saveIssueToSprint(selectedSprint, $event)"
tg-loading="vm.loading"
translate="COMMON.SAVE"
+ ng-disabled="!selectedSprint"
)
diff --git a/app/styles/modules/common/ticket-data.scss b/app/styles/modules/common/ticket-data.scss
index d6815fc1..8db3802b 100644
--- a/app/styles/modules/common/ticket-data.scss
+++ b/app/styles/modules/common/ticket-data.scss
@@ -200,6 +200,9 @@
border-color: $yellow-green;
}
}
+ .assign-issue-button.button-set:hover {
+ background: $red-light;
+ }
.item-block,
.item-unblock {
display: none;
diff --git a/app/svg/sprite.svg b/app/svg/sprite.svg
index 8be33777..e925ea21 100644
--- a/app/svg/sprite.svg
+++ b/app/svg/sprite.svg
@@ -467,5 +467,15 @@
+
+
+
+
+
+
+
+
+
+