diff options
Diffstat (limited to 'app')
47 files changed, 342 insertions, 220 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 84a7f277227..0692c96e767 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -87,10 +87,46 @@ export default { mounted() { const options = gl.issueBoards.getBoardSortableDefaultOptions({ scroll: true, - group: 'issues', disabled: this.disabled, filter: '.board-list-count, .is-disabled', dataIdAttr: 'data-issue-id', + group: { + name: 'issues', + /** + * Dynamically determine between which containers + * items can be moved or copied as + * Assignee lists (EE feature) require this behavior + */ + pull: (to, from, dragEl, e) => { + // As per Sortable's docs, `to` should provide + // reference to exact sortable container on which + // we're trying to drag element, but either it is + // a library's bug or our markup structure is too complex + // that `to` never points to correct container + // See https://github.com/RubaXa/Sortable/issues/1037 + // + // So we use `e.target` which is always accurate about + // which element we're currently dragging our card upon + // So from there, we can get reference to actual container + // and thus the container type to enable Copy or Move + if (e.target) { + const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list'); + const toBoardType = containerEl.dataset.boardType; + + if (toBoardType) { + const fromBoardType = this.list.type; + + if ((fromBoardType === 'assignee' && toBoardType === 'label') || + (fromBoardType === 'label' && toBoardType === 'assignee')) { + return 'clone'; + } + } + } + + return true; + }, + revertClone: true, + }, onStart: (e) => { const card = this.$refs.issue[e.oldIndex]; @@ -179,10 +215,11 @@ export default { :list="list" v-if="list.type !== 'closed' && showIssueForm"/> <ul - class="board-list" + class="board-list js-board-list" v-show="!loading" ref="list" :data-board="list.id" + :data-board-type="list.type" :class="{ 'is-smaller': showIssueForm }"> <board-card v-for="(issue, index) in issues" diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index e8dfd95f7ae..297c9eff38c 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -49,11 +49,12 @@ export default { this.error = false; const labels = this.list.label ? [this.list.label] : []; + const assignees = this.list.assignee ? [this.list.assignee] : []; const issue = new ListIssue({ title: this.title, labels, subscribed: true, - assignees: [], + assignees, project_id: this.selectedProject.id, }); @@ -141,4 +142,3 @@ export default { </div> </div> </template> - diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index 71f49319c36..6dcd4aaec43 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => { filterable: true, selectable: true, multiSelect: true, + containerSelector: '.js-tab-container-labels .dropdown-page-one .dropdown-content', clicked (options) { const { e } = options; const label = options.selectedObj; diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 29ab13b8e0b..cdad8d238e3 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -7,6 +7,7 @@ import Vue from 'vue'; import Flash from '~/flash'; import { __ } from '~/locale'; import '~/vue_shared/models/label'; +import '~/vue_shared/models/assignee'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; @@ -15,7 +16,6 @@ import './models/issue'; import './models/list'; import './models/milestone'; import './models/project'; -import './models/assignee'; import './stores/boards_store'; import ModalStore from './stores/modal_store'; import BoardService from './services/board_service'; diff --git a/app/assets/javascripts/boards/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js deleted file mode 100644 index 05dd449e4fd..00000000000 --- a/app/assets/javascripts/boards/models/assignee.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-unused-vars */ - -class ListAssignee { - constructor(user, defaultAvatar) { - this.id = user.id; - this.name = user.name; - this.username = user.username; - this.avatar = user.avatar_url || defaultAvatar; - } -} - -window.ListAssignee = ListAssignee; diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 7144f4190e7..a79dd62e2e4 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,12 +1,14 @@ /* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */ /* global ListIssue */ -/* global ListLabel */ + +import ListLabel from '~/vue_shared/models/label'; +import ListAssignee from '~/vue_shared/models/assignee'; import queryData from '../utils/query_data'; const PER_PAGE = 20; class List { - constructor (obj, defaultAvatar) { + constructor(obj, defaultAvatar) { this.id = obj.id; this._uid = this.guid(); this.position = obj.position; @@ -24,6 +26,9 @@ class List { if (obj.label) { this.label = new ListLabel(obj.label); + } else if (obj.user) { + this.assignee = new ListAssignee(obj.user); + this.title = this.assignee.name; } if (this.type !== 'blank' && this.id) { @@ -34,14 +39,25 @@ class List { } guid() { - const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + const s4 = () => + Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; } - save () { + save() { + const entity = this.label || this.assignee; + let entityType = ''; + if (this.label) { + entityType = 'label_id'; + } else { + entityType = 'assignee_id'; + } + return gl.boardService.createList(this.label.id) .then(res => res.data) - .then((data) => { + .then(data => { this.id = data.id; this.type = data.list_type; this.position = data.position; @@ -50,25 +66,23 @@ class List { }); } - destroy () { + destroy() { const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this); gl.issueBoards.BoardsStore.state.lists.splice(index, 1); gl.issueBoards.BoardsStore.updateNewListDropdown(this.id); - gl.boardService.destroyList(this.id) - .catch(() => { - // TODO: handle request error - }); + gl.boardService.destroyList(this.id).catch(() => { + // TODO: handle request error + }); } - update () { - gl.boardService.updateList(this.id, this.position) - .catch(() => { - // TODO: handle request error - }); + update() { + gl.boardService.updateList(this.id, this.position).catch(() => { + // TODO: handle request error + }); } - nextPage () { + nextPage() { if (this.issuesSize > this.issues.length) { if (this.issues.length / PER_PAGE >= 1) { this.page += 1; @@ -78,7 +92,7 @@ class List { } } - getIssues (emptyIssues = true) { + getIssues(emptyIssues = true) { const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page }); if (this.label && data.label_name) { @@ -89,7 +103,8 @@ class List { this.loading = true; } - return gl.boardService.getIssuesForList(this.id, data) + return gl.boardService + .getIssuesForList(this.id, data) .then(res => res.data) .then((data) => { this.loading = false; @@ -103,11 +118,12 @@ class List { }); } - newIssue (issue) { + newIssue(issue) { this.addIssue(issue, null, 0); this.issuesSize += 1; - return gl.boardService.newIssue(this.id, issue) + return gl.boardService + .newIssue(this.id, issue) .then(res => res.data) .then((data) => { issue.id = data.id; @@ -123,13 +139,13 @@ class List { }); } - createIssues (data) { - data.forEach((issueObj) => { + createIssues(data) { + data.forEach(issueObj => { this.addIssue(new ListIssue(issueObj, this.defaultAvatar)); }); } - addIssue (issue, listFrom, newIndex) { + addIssue(issue, listFrom, newIndex) { let moveBeforeId = null; let moveAfterId = null; @@ -152,6 +168,13 @@ class List { issue.addLabel(this.label); } + if (this.assignee) { + if (listFrom && listFrom.type === 'assignee') { + issue.removeAssignee(listFrom.assignee); + } + issue.addAssignee(this.assignee); + } + if (listFrom) { this.issuesSize += 1; @@ -160,29 +183,29 @@ class List { } } - moveIssue (issue, oldIndex, newIndex, moveBeforeId, moveAfterId) { + moveIssue(issue, oldIndex, newIndex, moveBeforeId, moveAfterId) { this.issues.splice(oldIndex, 1); this.issues.splice(newIndex, 0, issue); - gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId) - .catch(() => { - // TODO: handle request error - }); + gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => { + // TODO: handle request error + }); } updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) { - gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId) + gl.boardService + .moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId) .catch(() => { // TODO: handle request error }); } - findIssue (id) { + findIssue(id) { return this.issues.find(issue => issue.id === id); } - removeIssue (removeIssue) { - this.issues = this.issues.filter((issue) => { + removeIssue(removeIssue) { + this.issues = this.issues.filter(issue => { const matchesRemove = removeIssue.id === issue.id; if (matchesRemove) { diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js index 7c90597f77c..029b0971f2c 100644 --- a/app/assets/javascripts/boards/services/board_service.js +++ b/app/assets/javascripts/boards/services/board_service.js @@ -30,11 +30,13 @@ export default class BoardService { return axios.post(this.listsEndpointGenerate, {}); } - createList(labelId) { + createList(entityId, entityType) { + const list = { + [entityType]: entityId, + }; + return axios.post(this.listsEndpoint, { - list: { - label_id: labelId, - }, + list, }); } diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 20e78edf2a2..7dc83843e9b 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = { const listLabels = issueLists.map(listIssue => listIssue.label); if (!issueTo) { - // Add to new lists issues if it doesn't already exist - listTo.addIssue(issue, listFrom, newIndex); + // Check if target list assignee is already present in this issue + if ((listTo.type === 'assignee' && listFrom.type === 'assignee') && + issue.findAssignee(listTo.assignee)) { + const targetIssue = listTo.findIssue(issue.id); + targetIssue.removeAssignee(listFrom.assignee); + } else { + // Add to new lists issues if it doesn't already exist + listTo.addIssue(issue, listFrom, newIndex); + } } else { listTo.updateIssueLabel(issue, listFrom); issueTo.removeLabel(listFrom.label); @@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = { list.removeIssue(issue); }); issue.removeLabels(listLabels); - } else { + } else if (listTo.type === 'backlog' && listFrom.type === 'assignee') { + issue.removeAssignee(listFrom.assignee); + listFrom.removeIssue(issue); + } else if ((listTo.type !== 'label' && listFrom.type === 'assignee') || + (listTo.type !== 'assignee' && listFrom.type === 'label')) { listFrom.removeIssue(issue); } }, @@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = { list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId); }, findList (key, val, type = 'label') { - return this.state.lists.filter((list) => { - const byType = type ? list['type'] === type : true; + const filteredList = this.state.lists.filter((list) => { + const byType = type ? (list.type === type) || (list.type === 'assignee') : true; return list[key] === val && byType; - })[0]; + }); + return filteredList[0]; }, updateFiltersUrl () { history.pushState(null, null, `?${this.filter.path}`); diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js b/app/assets/javascripts/diff_notes/diff_notes_bundle.js index e17daec6a92..d5161ab7df9 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js @@ -69,9 +69,10 @@ export default () => { gl.diffNotesCompileComponents(); - if (!hasVueMRDiscussionsCookie()) { + const resolveCountAppEl = document.querySelector('#resolve-count-app'); + if (!hasVueMRDiscussionsCookie() && resolveCountAppEl) { new Vue({ - el: '#resolve-count-app', + el: resolveCountAppEl, components: { 'resolve-count': ResolveCount }, diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 746a06b7c4f..7fbba7e27cb 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -602,7 +602,11 @@ GitLabDropdown = (function() { var selector; selector = '.dropdown-content'; if (this.dropdown.find(".dropdown-toggle-page").length) { - selector = ".dropdown-page-one .dropdown-content"; + if (this.options.containerSelector) { + selector = this.options.containerSelector; + } else { + selector = '.dropdown-page-one .dropdown-content'; + } } return $(selector, this.dropdown).empty(); diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue index aafd6a15a78..dd7fc8f1e01 100644 --- a/app/assets/javascripts/ide/components/panes/right.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -5,6 +5,7 @@ import Icon from '../../../vue_shared/components/icon.vue'; import { rightSidebarViews } from '../../constants'; import PipelinesList from '../pipelines/list.vue'; import JobsDetail from '../jobs/detail.vue'; +import ResizablePanel from '../resizable_panel.vue'; export default { directives: { @@ -14,6 +15,7 @@ export default { Icon, PipelinesList, JobsDetail, + ResizablePanel, }, computed: { ...mapState(['rightPane']), @@ -40,12 +42,16 @@ export default { <div class="multi-file-commit-panel ide-right-sidebar" > - <div - class="multi-file-commit-panel-inner" + <resizable-panel v-if="rightPane" + class="multi-file-commit-panel-inner" + :collapsible="false" + :initial-width="350" + :min-size="350" + side="right" > <component :is="rightPane" /> - </div> + </resizable-panel> <nav class="ide-activity-bar"> <ul class="list-unstyled"> <li> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 93453989c08..d365745d78b 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -1,10 +1,8 @@ <script> -/* global monaco */ import { mapState, mapGetters, mapActions } from 'vuex'; import flash from '~/flash'; import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue'; import { activityBarViews, viewerTypes } from '../constants'; -import monacoLoader from '../monaco_loader'; import Editor from '../lib/editor'; import ExternalLink from './external_link.vue'; @@ -50,7 +48,7 @@ export default { // Compare key to allow for files opened in review mode to be cached differently if (oldVal.key !== this.file.key) { - this.initMonaco(); + this.initEditor(); if (this.currentActivityView !== activityBarViews.edit) { this.setFileViewMode({ @@ -84,15 +82,10 @@ export default { this.editor.dispose(); }, mounted() { - if (this.editor && monaco) { - this.initMonaco(); - } else { - monacoLoader(['vs/editor/editor.main'], () => { - this.editor = Editor.create(monaco); - - this.initMonaco(); - }); + if (!this.editor) { + this.editor = Editor.create(); } + this.initEditor(); }, methods: { ...mapActions([ @@ -105,7 +98,7 @@ export default { 'updateViewer', 'removePendingTab', ]), - initMonaco() { + initEditor() { if (this.shouldHideEditor) return; this.editor.clearEditor(); @@ -118,7 +111,7 @@ export default { this.createEditorInstance(); }) .catch(err => { - flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true); + flash('Error setting up editor. Please try again.', 'alert', document, null, false, true); throw err; }); }, diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js index e5149b1f3ad..78e6f632728 100644 --- a/app/assets/javascripts/ide/lib/common/model.js +++ b/app/assets/javascripts/ide/lib/common/model.js @@ -1,32 +1,32 @@ +import { editor as monacoEditor, Uri } from 'monaco-editor'; import Disposable from './disposable'; import eventHub from '../../eventhub'; export default class Model { - constructor(monaco, file, head = null) { - this.monaco = monaco; + constructor(file, head = null) { this.disposable = new Disposable(); this.file = file; this.head = head; this.content = file.content !== '' ? file.content : file.raw; this.disposable.add( - (this.originalModel = this.monaco.editor.createModel( + (this.originalModel = monacoEditor.createModel( head ? head.content : this.file.raw, undefined, - new this.monaco.Uri(null, null, `original/${this.path}`), + new Uri(false, false, `original/${this.path}`), )), - (this.model = this.monaco.editor.createModel( + (this.model = monacoEditor.createModel( this.content, undefined, - new this.monaco.Uri(null, null, this.path), + new Uri(false, false, this.path), )), ); if (this.file.mrChange) { this.disposable.add( - (this.baseModel = this.monaco.editor.createModel( + (this.baseModel = monacoEditor.createModel( this.file.baseRaw, undefined, - new this.monaco.Uri(null, null, `target/${this.path}`), + new Uri(false, false, `target/${this.path}`), )), ); } diff --git a/app/assets/javascripts/ide/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js index 7f643969480..bd9b8fc3fcc 100644 --- a/app/assets/javascripts/ide/lib/common/model_manager.js +++ b/app/assets/javascripts/ide/lib/common/model_manager.js @@ -3,8 +3,7 @@ import Disposable from './disposable'; import Model from './model'; export default class ModelManager { - constructor(monaco) { - this.monaco = monaco; + constructor() { this.disposable = new Disposable(); this.models = new Map(); } @@ -22,7 +21,7 @@ export default class ModelManager { return this.getModel(file.key); } - const model = new Model(this.monaco, file, head); + const model = new Model(file, head); this.models.set(model.path, model); this.disposable.add(model); diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js index f579424cf33..046e562ba2b 100644 --- a/app/assets/javascripts/ide/lib/diff/controller.js +++ b/app/assets/javascripts/ide/lib/diff/controller.js @@ -1,4 +1,4 @@ -/* global monaco */ +import { Range } from 'monaco-editor'; import { throttle } from 'underscore'; import DirtyDiffWorker from './diff_worker'; import Disposable from '../common/disposable'; @@ -16,7 +16,7 @@ export const getDiffChangeType = change => { }; export const getDecorator = change => ({ - range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1), + range: new Range(change.lineNumber, 1, change.endLineNumber, 1), options: { isWholeLine: true, linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js index 9c3bb9cc17d..02038fcb534 100644 --- a/app/assets/javascripts/ide/lib/editor.js +++ b/app/assets/javascripts/ide/lib/editor.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import { editor as monacoEditor, KeyCode, KeyMod } from 'monaco-editor'; import store from '../stores'; import DecorationsController from './decorations/controller'; import DirtyDiffController from './diff/controller'; @@ -8,6 +9,11 @@ import editorOptions, { defaultEditorOptions } from './editor_options'; import gitlabTheme from './themes/gl_theme'; import keymap from './keymap.json'; +function setupMonacoTheme() { + monacoEditor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme); + monacoEditor.setTheme('gitlab'); +} + export const clearDomElement = el => { if (!el || !el.firstChild) return; @@ -17,24 +23,22 @@ export const clearDomElement = el => { }; export default class Editor { - static create(monaco) { - if (this.editorInstance) return this.editorInstance; - - this.editorInstance = new Editor(monaco); - + static create() { + if (!this.editorInstance) { + this.editorInstance = new Editor(); + } return this.editorInstance; } - constructor(monaco) { - this.monaco = monaco; + constructor() { this.currentModel = null; this.instance = null; this.dirtyDiffController = null; this.disposable = new Disposable(); - this.modelManager = new ModelManager(this.monaco); + this.modelManager = new ModelManager(); this.decorationsController = new DecorationsController(this); - this.setupMonacoTheme(); + setupMonacoTheme(); this.debouncedUpdate = _.debounce(() => { this.updateDimensions(); @@ -46,7 +50,7 @@ export default class Editor { clearDomElement(domElement); this.disposable.add( - (this.instance = this.monaco.editor.create(domElement, { + (this.instance = monacoEditor.create(domElement, { ...defaultEditorOptions, })), (this.dirtyDiffController = new DirtyDiffController( @@ -66,7 +70,7 @@ export default class Editor { clearDomElement(domElement); this.disposable.add( - (this.instance = this.monaco.editor.createDiffEditor(domElement, { + (this.instance = monacoEditor.createDiffEditor(domElement, { ...defaultEditorOptions, quickSuggestions: false, occurrencesHighlight: false, @@ -122,17 +126,11 @@ export default class Editor { modified: model.getModel(), }); - this.monaco.editor.createDiffNavigator(this.instance, { + monacoEditor.createDiffNavigator(this.instance, { alwaysRevealFirst: true, }); } - setupMonacoTheme() { - this.monaco.editor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme); - - this.monaco.editor.setTheme('gitlab'); - } - clearEditor() { if (this.instance) { this.instance.setModel(null); @@ -200,7 +198,7 @@ export default class Editor { const getKeyCode = key => { const monacoKeyMod = key.indexOf('KEY_') === 0; - return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key]; + return monacoKeyMod ? KeyCode[key] : KeyMod[key]; }; keymap.forEach(command => { diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js deleted file mode 100644 index 142a220097b..00000000000 --- a/app/assets/javascripts/ide/monaco_loader.js +++ /dev/null @@ -1,16 +0,0 @@ -import monacoContext from 'monaco-editor/dev/vs/loader'; - -monacoContext.require.config({ - paths: { - vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase - }, -}); - -// ignore CDN config and use local assets path for service worker which cannot be cross-domain -const relativeRootPath = (gon && gon.relative_url_root) || ''; -const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`; -window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` }; - -// eslint-disable-next-line no-underscore-dangle -window.__monaco_context__ = monacoContext; -export default monacoContext.require; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index f850e818bdf..7cca32dc6fa 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -79,37 +79,37 @@ export function getTimeago() { if (!timeagoInstance) { const localeRemaining = function getLocaleRemaining(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], - [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')], - [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')], + [s__('Timeago|just now'), s__('Timeago|right now')], + [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')], + [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], - [s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')], - [s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')], - [s__('Timeago|a day ago'), s__('Timeago|1 day remaining')], + [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')], + [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')], + [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')], [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')], - [s__('Timeago|a week ago'), s__('Timeago|1 week remaining')], + [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')], [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')], - [s__('Timeago|a month ago'), s__('Timeago|1 month remaining')], + [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')], [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')], - [s__('Timeago|a year ago'), s__('Timeago|1 year remaining')], + [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')], [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')], ][index]; }; const locale = function getLocale(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], - [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')], - [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')], + [s__('Timeago|just now'), s__('Timeago|right now')], + [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')], + [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')], - [s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')], - [s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')], - [s__('Timeago|a day ago'), s__('Timeago|in 1 day')], + [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')], + [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')], + [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')], [s__('Timeago|%s days ago'), s__('Timeago|in %s days')], - [s__('Timeago|a week ago'), s__('Timeago|in 1 week')], + [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')], [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')], - [s__('Timeago|a month ago'), s__('Timeago|in 1 month')], + [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')], [s__('Timeago|%s months ago'), s__('Timeago|in %s months')], - [s__('Timeago|a year ago'), s__('Timeago|in 1 year')], + [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')], [s__('Timeago|%s years ago'), s__('Timeago|in %s years')], ][index]; }; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 9803bebfd10..c9ce838cd48 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -144,6 +144,7 @@ document.addEventListener('DOMContentLoaded', () => { $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', trigger: 'hover', + boundary: 'viewport', placement(tip, el) { return $(el).data('placement') || 'bottom'; }, diff --git a/app/assets/javascripts/vue_shared/models/assignee.js b/app/assets/javascripts/vue_shared/models/assignee.js new file mode 100644 index 00000000000..4a29b0d0581 --- /dev/null +++ b/app/assets/javascripts/vue_shared/models/assignee.js @@ -0,0 +1,13 @@ +export default class ListAssignee { + constructor(obj, defaultAvatar) { + this.id = obj.id; + this.name = obj.name; + this.username = obj.username; + this.avatar = obj.avatar_url || obj.avatar || defaultAvatar; + this.path = obj.path; + this.state = obj.state; + this.webUrl = obj.web_url || obj.webUrl; + } +} + +window.ListAssignee = ListAssignee; diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index 0c5b6171223..e3c63ae5e1a 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -107,7 +107,7 @@ code { background-color: $red-100; border-radius: 3px; - .code & { + .code > & { background-color: inherit; padding: unset; } @@ -118,10 +118,6 @@ code { } } -.code { - padding: 9.5px; -} - table { // Remove any table border lines border-spacing: 0; @@ -233,6 +229,13 @@ table { } } +.card-header { + h3.card-title, + h4.card-title { + margin-top: 0; + } +} + .nav-tabs { // Override bootstrap's default border border-bottom: 0; diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 16e999341da..1f8e61257a9 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -127,12 +127,16 @@ color: $gl-danger; } -.service-settings .form-control-label { - padding-top: 0; +.service-settings { + input[type="radio"], + input[type="checkbox"] { + margin-top: 10px; + } } .integration-settings-form { - .card.card-body { + .card.card-body, + .info-well { padding: $gl-padding / 2; box-shadow: none; } diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb index 381fd4d7508..e8b5934f2a9 100644 --- a/app/controllers/boards/lists_controller.rb +++ b/app/controllers/boards/lists_controller.rb @@ -56,8 +56,12 @@ module Boards private + def list_creation_attrs + %i[label_id] + end + def list_params - params.require(:list).permit(:label_id) + params.require(:list).permit(list_creation_attrs) end def move_params @@ -65,11 +69,15 @@ module Boards end def serialize_as_json(resource) - resource.as_json( + resource.as_json(serialization_attrs) + end + + def serialization_attrs + { only: [:id, :list_type, :position], methods: [:title], label: true - ) + } end end end diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb index 067aff408df..2a656c0d31c 100644 --- a/app/finders/group_members_finder.rb +++ b/app/finders/group_members_finder.rb @@ -3,17 +3,29 @@ class GroupMembersFinder @group = group end - def execute + def execute(include_descendants: false) group_members = @group.members + wheres = [] - return group_members unless @group.parent + return group_members unless @group.parent || include_descendants - parents_members = GroupMember.non_request - .where(source_id: @group.ancestors.select(:id)) - .where.not(user_id: @group.users.select(:id)) + wheres << "members.id IN (#{group_members.select(:id).to_sql})" - wheres = ["members.id IN (#{group_members.select(:id).to_sql})"] - wheres << "members.id IN (#{parents_members.select(:id).to_sql})" + if @group.parent + parents_members = GroupMember.non_request + .where(source_id: @group.ancestors.select(:id)) + .where.not(user_id: @group.users.select(:id)) + + wheres << "members.id IN (#{parents_members.select(:id).to_sql})" + end + + if include_descendants + descendant_members = GroupMember.non_request + .where(source_id: @group.descendants.select(:id)) + .where.not(user_id: @group.users.select(:id)) + + wheres << "members.id IN (#{descendant_members.select(:id).to_sql})" + end GroupMember.where(wheres.join(' OR ')) end diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb index 4734d97b8c7..4c893ae2de6 100644 --- a/app/finders/members_finder.rb +++ b/app/finders/members_finder.rb @@ -7,12 +7,12 @@ class MembersFinder @group = project.group end - def execute + def execute(include_descendants: false) project_members = project.project_members project_members = project_members.non_invite unless can?(current_user, :admin_project, project) if group - group_members = GroupMembersFinder.new(group).execute + group_members = GroupMembersFinder.new(group).execute(include_descendants: include_descendants) group_members = group_members.non_invite union = Gitlab::SQL::Union.new([project_members, group_members], remove_duplicates: false) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 58372edff3c..2f304b040c7 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -60,7 +60,7 @@ module IconsHelper def spinner(text = nil, visible = false) css_class = 'loading' - css_class << ' hidden' unless visible + css_class << ' hide' unless visible content_tag :div, class: css_class do icon('spinner spin') + text diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8572c2b7276..d42284868c7 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -165,7 +165,7 @@ module IssuablesHelper output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!')) output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block") - output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none d-lg-none d-xl-inline-block") + output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none") output.html_safe end diff --git a/app/models/list.rb b/app/models/list.rb index 5daf35ef845..4edcfa78835 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -2,17 +2,27 @@ class List < ActiveRecord::Base belongs_to :board belongs_to :label - enum list_type: { backlog: 0, label: 1, closed: 2 } + enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3 } validates :board, :list_type, presence: true validates :label, :position, presence: true, if: :label? validates :label_id, uniqueness: { scope: :board_id }, if: :label? - validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :label? + validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable? before_destroy :can_be_destroyed - scope :destroyable, -> { where(list_type: list_types[:label]) } - scope :movable, -> { where(list_type: list_types[:label]) } + scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) } + scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) } + + class << self + def destroyable_types + [:label] + end + + def movable_types + [:label] + end + end def destroyable? label? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 535a2c362f2..324065c1162 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1125,8 +1125,11 @@ class MergeRequest < ActiveRecord::Base project.merge_requests.merged.where(author_id: author_id).empty? end + # TODO: remove once production database rename completes + alias_attribute :allow_collaboration, :allow_maintainer_to_push + def allow_collaboration - collaborative_push_possible? && super + collaborative_push_possible? && allow_maintainer_to_push end alias_method :allow_collaboration?, :allow_collaboration diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 5a961ac89e4..b1dbe73cdf7 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -3,13 +3,18 @@ module Boards class ListService < Boards::BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute - issues = without_board_labels(issues) unless movable_list? || closed_list? - issues = with_list_label(issues) if movable_list? + issues = filter(issues) issues.order_by_position_and_priority end private + def filter(issues) + issues = without_board_labels(issues) unless list&.movable? || list&.closed? + issues = with_list_label(issues) if list&.label? + issues + end + def board @board ||= parent.boards.find(params[:board_id]) end @@ -20,18 +25,6 @@ module Boards @list = board.lists.find(params[:id]) if params.key?(:id) end - def movable_list? - return @movable_list if defined?(@movable_list) - - @movable_list = list.present? && list.movable? - end - - def closed_list? - return @closed_list if defined?(@closed_list) - - @closed_list = list.present? && list.closed? - end - def filter_params set_parent set_state diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 3ceab209f3f..ee3112c7571 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -3,7 +3,7 @@ module Boards class MoveService < Boards::BaseService def execute(issue) return false unless can?(current_user, :update_issue, issue) - return false if issue_params.empty? + return false if issue_params(issue).empty? update(issue) end @@ -28,10 +28,10 @@ module Boards end def update(issue) - ::Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) + ::Issues::UpdateService.new(issue.project, current_user, issue_params(issue)).execute(issue) end - def issue_params + def issue_params(issue) attrs = {} if move_between_lists? diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index 02f1c709374..6fd9885d4f3 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,16 +1,28 @@ module Boards module Lists class CreateService < Boards::BaseService + include Gitlab::Utils::StrongMemoize + def execute(board) List.transaction do - label = available_labels_for(board).find(params[:label_id]) + target = target(board) position = next_position(board) - create_list(board, label, position) + create_list(board, type, target, position) end end private + def type + :label + end + + def target(board) + strong_memoize(:target) do + available_labels_for(board).find(params[:label_id]) + end + end + def available_labels_for(board) options = { include_ancestor_groups: true } @@ -28,8 +40,8 @@ module Boards max_position.nil? ? 0 : max_position.succ end - def create_list(board, label, position) - board.lists.create(label: label, list_type: :label, position: position) + def create_list(board, type, target, position) + board.lists.create(type => target, list_type: type, position: position) end end end diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb index cf687b71d16..3407b312700 100644 --- a/app/services/merge_requests/create_from_issue_service.rb +++ b/app/services/merge_requests/create_from_issue_service.rb @@ -41,7 +41,9 @@ module MergeRequests end def ref - @ref || project.default_branch || 'master' + return @ref if project.repository.branch_exists?(@ref) + + project.default_branch || 'master' end def merge_request diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml index 3f440c76ee0..3ac1721f7cd 100644 --- a/app/views/admin/application_settings/show.html.haml +++ b/app/views/admin/application_settings/show.html.haml @@ -17,7 +17,7 @@ %section.settings.as-account-limit.no-animate#js-account-settings{ class: ('expanded' if expanded) } .settings-header %h4 - = _('Account and limit settings') + = _('Account and limit') %button.btn.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p @@ -317,7 +317,7 @@ %section.settings.as-mirror.no-animate#js-mirror-settings{ class: ('expanded' if expanded) } .settings-header %h4 - = _('Repository mirror settings') + = _('Repository mirror') %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index ff2b418e479..647948c7dff 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -18,7 +18,7 @@ %section.settings#runners-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - = _('Runners settings') + = _('Runners') %button.btn.btn-default.js-settings-toggle{ type: "button" } = expanded ? _('Collapse') : _('Expand') %p diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 77ba422e87e..77665a2ac23 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -7,7 +7,7 @@ %section.settings.general-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - General project settings + General project %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p @@ -85,7 +85,7 @@ %section.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] } .settings-header %h4 - Merge request settings + Merge request %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p @@ -104,7 +104,7 @@ %section.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - Advanced settings + Advanced %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml index a2cd7752fc4..9a06eca89bb 100644 --- a/app/views/projects/protected_branches/shared/_branches_list.html.haml +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -1,7 +1,7 @@ .protected-branches-list.js-protected-branches-list.qa-protected-branches-list - if @protected_branches.empty? - .card-header - %h3.card-title + .card-header.bg-white + %h3.card-title.mb-0 Protected branch (#{@protected_branches_count}) %p.settings-message.text-center There are currently no protected branches, protect a branch with the form above. diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml index 2cc2a6b2b5b..898b55e4b39 100644 --- a/app/views/projects/services/prometheus/_configuration_banner.html.haml +++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml @@ -2,7 +2,7 @@ = s_('PrometheusService|Auto configuration') - if service.manual_configuration? - .well + .info-well = s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below') - else .container-fluid diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml index 15e7362c2ba..35d655e4b32 100644 --- a/app/views/projects/services/prometheus/_help.html.haml +++ b/app/views/projects/services/prometheus/_help.html.haml @@ -5,5 +5,5 @@ = s_('PrometheusService|Manual configuration') - unless @service.editable? - .card + .info-well = s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters') diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index e93b240a007..7142a9d635e 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -7,7 +7,7 @@ = f.label :runners_token, "Runner token", class: 'label-light' .form-control.js-secret-value-placeholder = '*' * 20 - = f.text_field :runners_token, class: "form-control hidden js-secret-value", placeholder: 'xEeFCaDAB89' + = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89' %p.form-text.text-muted The secure token used by the Runner to checkout the project %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } } = _('Reveal value') diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index ed118d1bcef..3047207bca7 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -8,7 +8,7 @@ %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) } .settings-header %h4 - General pipelines settings + General pipelines %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p @@ -31,7 +31,7 @@ %section.settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - Runners settings + Runners %button.btn.js-settings-toggle{ type: 'button' } = expanded ? 'Collapse' : 'Expand' %p diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index 67476a3f573..76843ce7cc0 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -1,4 +1,4 @@ -.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded }', +.board{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }', ":data-id" => "list.id" } .board-inner %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" } @@ -7,10 +7,18 @@ ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }", "aria-hidden": "true" } + %a.user-avatar-link.js-no-trigger{ "v-if": "list.type === \"assignee\"", ":href": "list.assignee.path" } + -# haml-lint:disable AltText + %img.avatar.s20.has-tooltip{ height: "20", width: "20", ":src": "list.assignee.avatar", ":alt": "list.assignee.name" } + %span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"", - ":title" => '(list.label ? list.label.description : "")', data: { container: "body" } } + ":title" => '((list.label && list.label.description) || list.title || "")', data: { container: "body" } } {{ list.title }} + %span.board-title-sub-text.prepend-left-5.has-tooltip{ "v-if": "list.type === \"assignee\"", + ":title" => '(list.assignee && list.assignee.username || "")' } + @{{ list.assignee.username }} + %span.has-tooltip{ "v-if": "list.type === \"label\"", ":title" => '(list.label ? list.label.description : "")', data: { container: "body", placement: "bottom" }, diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml new file mode 100644 index 00000000000..23b2e1b91e5 --- /dev/null +++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml @@ -0,0 +1,8 @@ +.dropdown.prepend-left-10#js-add-list + %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data } + Add list + .dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels + = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } + - if can?(current_user, :admin_label, board.parent) + = render partial: "shared/issuable/label_page_create" + = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 9f4021802df..55edaa7eda4 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -1,6 +1,7 @@ +- show_close = local_assigns.fetch(:show_close, true) - subject = @project || @group .dropdown-page-two.dropdown-new-label - = dropdown_title(create_label_title(subject), options: { back: true }) + = dropdown_title(create_label_title(subject), options: { back: true, close: show_close }) = dropdown_content do .dropdown-labels-error.js-label-error %input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') } diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index 2bd922bca2b..aa4a5f0e0d3 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -1,15 +1,18 @@ - title = local_assigns.fetch(:title, _('Assign labels')) +- content_title = local_assigns.fetch(:content_title, _('Create lists from labels. Issues with that label appear in that list.')) +- show_title = local_assigns.fetch(:show_title, true) - show_create = local_assigns.fetch(:show_create, true) - show_footer = local_assigns.fetch(:show_footer, true) - filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search') - show_boards_content = local_assigns.fetch(:show_boards_content, false) - subject = @project || @group .dropdown-page-one - = dropdown_title(title) + - if show_title + = dropdown_title(title) - if show_boards_content .issue-board-dropdown-content %p - = _('Create lists from labels. Issues with that label appear in that list.') + = content_title = dropdown_filter(filter_placeholder) = dropdown_content - if current_board_parent && show_footer diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 644f7c4dd28..ef9ea2194ee 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -104,14 +104,7 @@ .filter-dropdown-container - if type == :boards - if can?(current_user, :admin_list, board.parent) - .dropdown.prepend-left-10#js-add-list - %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data } - Add list - .dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } - - if can?(current_user, :admin_label, board.parent) - = render partial: "shared/issuable/label_page_create" - = dropdown_loading + = render_if_exists 'shared/issuable/board_create_list_dropdown', board: board - if @project #js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } } - elsif type != :boards_modal diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml index ae437dd16d6..2d0bb722189 100644 --- a/app/views/shared/tokens/_scopes_form.html.haml +++ b/app/views/shared/tokens/_scopes_form.html.haml @@ -5,6 +5,6 @@ - scopes.each do |scope| %fieldset = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}" - = label_tag ("#{prefix}_scopes_#{scope}"), scope + = label_tag ("#{prefix}_scopes_#{scope}"), scope, class: "label-light" %span= t(scope, scope: [:doorkeeper, :scopes]) .scope-description= t scope, scope: [:doorkeeper, :scope_desc] |