diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index 3062cd51ee3..a20c6ca7a21 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -99,7 +99,7 @@ export default class FileTemplateMediator { }); } - selectTemplateType(item, el, e) { + selectTemplateType(item, e) { if (e) { e.preventDefault(); } @@ -117,6 +117,10 @@ export default class FileTemplateMediator { this.cacheToggleText(); } + selectTemplateTypeOptions(options) { + this.selectTemplateType(options.selectedObj, options.e); + } + selectTemplateFile(selector, query, data) { selector.renderLoading(); // in case undo menu is already already there diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js index 31dd45fac89..ab5b3751c4e 100644 --- a/app/assets/javascripts/blob/file_template_selector.js +++ b/app/assets/javascripts/blob/file_template_selector.js @@ -52,9 +52,17 @@ export default class FileTemplateSelector { .removeClass('fa-spinner fa-spin'); } - reportSelection(query, el, e, data) { + reportSelection(options) { + const { query, e, data } = options; e.preventDefault(); return this.mediator.selectTemplateFile(this, query, data); } + + reportSelectionName(options) { + const opts = options; + opts.query = options.selectedObj.name; + + this.reportSelection(opts); + } } diff --git a/app/assets/javascripts/blob/target_branch_dropdown.js b/app/assets/javascripts/blob/target_branch_dropdown.js index 216f069ef71..d52d69b1274 100644 --- a/app/assets/javascripts/blob/target_branch_dropdown.js +++ b/app/assets/javascripts/blob/target_branch_dropdown.js @@ -37,8 +37,8 @@ class TargetBranchDropDown { } return SELECT_ITEM_MSG; }, - clicked(item, el, e) { - e.preventDefault(); + clicked(options) { + options.e.preventDefault(); self.onClick.call(self); }, fieldName: self.fieldName, diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index d7c1c32efbd..888883163c5 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -24,7 +24,7 @@ export default class TemplateSelector { search: { fields: ['name'], }, - clicked: (item, el, e) => this.fetchFileTemplate(item, el, e), + clicked: options => this.fetchFileTemplate(options), text: item => item.name, }); } @@ -51,7 +51,10 @@ export default class TemplateSelector { return this.$dropdownContainer.removeClass('hidden'); } - fetchFileTemplate(item, el, e) { + fetchFileTemplate(options) { + const { e } = options; + const item = options.selectedObj; + e.preventDefault(); return this.requestFile(item); } diff --git a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js index 935df07677c..f2f81af137b 100644 --- a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js +++ b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js @@ -25,7 +25,7 @@ export default class BlobCiYamlSelector extends FileTemplateSelector { search: { fields: ['name'], }, - clicked: (query, el, e) => this.reportSelection(query.name, el, e), + clicked: options => this.reportSelectionName(options), text: item => item.name, }); } diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js index b4b4d09c315..3cb7b960aaa 100644 --- a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js +++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js @@ -25,7 +25,7 @@ export default class DockerfileSelector extends FileTemplateSelector { search: { fields: ['name'], }, - clicked: (query, el, e) => this.reportSelection(query.name, el, e), + clicked: options => this.reportSelectionName(options), text: item => item.name, }); } diff --git a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js index aefae54ae71..7efda8e7f50 100644 --- a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js +++ b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js @@ -24,7 +24,7 @@ export default class BlobGitignoreSelector extends FileTemplateSelector { search: { fields: ['name'], }, - clicked: (query, el, e) => this.reportSelection(query.name, el, e), + clicked: options => this.reportSelectionName(options), text: item => item.name, }); } diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js index c8abd689ab4..1d757332f6c 100644 --- a/app/assets/javascripts/blob/template_selectors/license_selector.js +++ b/app/assets/javascripts/blob/template_selectors/license_selector.js @@ -24,13 +24,22 @@ export default class BlobLicenseSelector extends FileTemplateSelector { search: { fields: ['name'], }, - clicked: (query, el, e) => { + clicked: (options) => { + const { e } = options; + const el = options.$el; + const query = options.selectedObj; + const data = { project: this.$dropdown.data('project'), fullname: this.$dropdown.data('fullname'), }; - this.reportSelection(query.id, el, e, data); + this.reportSelection({ + query: query.id, + el, + e, + data, + }); }, text: item => item.name, }); diff --git a/app/assets/javascripts/blob/template_selectors/type_selector.js b/app/assets/javascripts/blob/template_selectors/type_selector.js index 56f23ef0568..a09381014a7 100644 --- a/app/assets/javascripts/blob/template_selectors/type_selector.js +++ b/app/assets/javascripts/blob/template_selectors/type_selector.js @@ -17,7 +17,7 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector { filterable: false, selectable: true, toggleLabel: item => item.name, - clicked: (item, el, e) => this.mediator.selectTemplateType(item, el, e), + clicked: options => this.mediator.selectTemplateTypeOptions(options), text: item => item.name, }); } diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 8c08b2d4db3..88eb4251339 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -11,7 +11,7 @@ require('./models/issue'); require('./models/label'); require('./models/list'); require('./models/milestone'); -require('./models/user'); +require('./models/assignee'); require('./stores/boards_store'); require('./stores/modal_store'); require('./services/board_service'); diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js index 0fa85b6fe14..1ce95b62138 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.js +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -26,6 +26,7 @@ export default { title: this.title, labels, subscribed: true, + assignees: [], }); this.list.newIssue(issue) diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index f0066d4ec5d..317cef9f227 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,8 +3,13 @@ /* global MilestoneSelect */ /* global LabelsSelect */ /* global Sidebar */ +/* global Flash */ import Vue from 'vue'; +import eventHub from '../../sidebar/event_hub'; + +import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; +import Assignees from '../../sidebar/components/assignees/assignees'; require('./sidebar/remove_issue'); @@ -22,6 +27,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ detail: Store.detail, issue: {}, list: {}, + loadingAssignees: false, }; }, computed: { @@ -43,6 +49,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({ this.issue = this.detail.issue; this.list = this.detail.list; + + this.$nextTick(() => { + this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate; + }); }, deep: true }, @@ -53,12 +63,57 @@ gl.issueBoards.BoardSidebar = Vue.extend({ $('.right-sidebar').getNiceScroll().resize(); }); } - } + + this.issue = this.detail.issue; + this.list = this.detail.list; + }, + deep: true }, methods: { closeSidebar () { this.detail.issue = {}; - } + }, + assignSelf () { + // Notify gl dropdown that we are now assigning to current user + this.$refs.assigneeBlock.dispatchEvent(new Event('assignYourself')); + + this.addAssignee(this.currentUser); + this.saveAssignees(); + }, + removeAssignee (a) { + gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a); + }, + addAssignee (a) { + gl.issueBoards.BoardsStore.detail.issue.addAssignee(a); + }, + removeAllAssignees () { + gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees(); + }, + saveAssignees () { + this.loadingAssignees = true; + + gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint) + .then(() => { + this.loadingAssignees = false; + }) + .catch(() => { + this.loadingAssignees = false; + return new Flash('An error occurred while saving assignees'); + }); + }, + }, + created () { + // Get events from glDropdown + eventHub.$on('sidebar.removeAssignee', this.removeAssignee); + eventHub.$on('sidebar.addAssignee', this.addAssignee); + eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees); + eventHub.$on('sidebar.saveAssignees', this.saveAssignees); + }, + beforeDestroy() { + eventHub.$off('sidebar.removeAssignee', this.removeAssignee); + eventHub.$off('sidebar.addAssignee', this.addAssignee); + eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); + eventHub.$off('sidebar.saveAssignees', this.saveAssignees); }, mounted () { new IssuableContext(this.currentUser); @@ -70,5 +125,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, components: { removeBtn: gl.issueBoards.RemoveIssueBtn, + 'assignee-title': AssigneeTitle, + assignees: Assignees, }, }); diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index fc154ee7b8b..710207db0c7 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -31,19 +31,37 @@ gl.issueBoards.IssueCardInner = Vue.extend({ default: false, }, }, + data() { + return { + limitBeforeCounter: 3, + maxRender: 4, + maxCounter: 99, + }; + }, computed: { + numberOverLimit() { + return this.issue.assignees.length - this.limitBeforeCounter; + }, + assigneeCounterTooltip() { + return `${this.assigneeCounterLabel} more`; + }, + assigneeCounterLabel() { + if (this.numberOverLimit > this.maxCounter) { + return `${this.maxCounter}+`; + } + + return `+${this.numberOverLimit}`; + }, + shouldRenderCounter() { + if (this.issue.assignees.length <= this.maxRender) { + return false; + } + + return this.issue.assignees.length > this.numberOverLimit; + }, cardUrl() { return `${this.issueLinkBase}/${this.issue.id}`; }, - assigneeUrl() { - return `${this.rootPath}${this.issue.assignee.username}`; - }, - assigneeUrlTitle() { - return `Assigned to ${this.issue.assignee.name}`; - }, - avatarUrlTitle() { - return `Avatar for ${this.issue.assignee.name}`; - }, issueId() { return `#${this.issue.id}`; }, @@ -52,6 +70,28 @@ gl.issueBoards.IssueCardInner = Vue.extend({ }, }, methods: { + isIndexLessThanlimit(index) { + return index < this.limitBeforeCounter; + }, + shouldRenderAssignee(index) { + // Eg. maxRender is 4, + // Render up to all 4 assignees if there are only 4 assigness + // Otherwise render up to the limitBeforeCounter + if (this.issue.assignees.length <= this.maxRender) { + return index < this.maxRender; + } + + return index < this.limitBeforeCounter; + }, + assigneeUrl(assignee) { + return `${this.rootPath}${assignee.username}`; + }, + assigneeUrlTitle(assignee) { + return `Assigned to ${assignee.name}`; + }, + avatarUrlTitle(assignee) { + return `Avatar for ${assignee.name}`; + }, showLabel(label) { if (!this.list) return true; @@ -105,25 +145,39 @@ gl.issueBoards.IssueCardInner = Vue.extend({ {{ issueId }} - - - +
+ + + + + {{ assigneeCounterLabel }} + +
-