Merge branch '2518-saved-configuration-for-issue-board' into 'master'
[CE backport] Saved configuration for issue board See merge request gitlab-org/gitlab-ce!15009
This commit is contained in:
commit
2573818f6d
16 changed files with 174 additions and 93 deletions
|
@ -11,7 +11,8 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
// Issue boards is slightly different, we handle all the requests async
|
||||
// instead or reloading the page, we just re-fire the list ajax requests
|
||||
this.isHandledAsync = true;
|
||||
this.cantEdit = cantEdit;
|
||||
this.cantEdit = cantEdit.filter(i => typeof i === 'string');
|
||||
this.cantEditWithValue = cantEdit.filter(i => typeof i === 'object');
|
||||
}
|
||||
|
||||
updateObject(path) {
|
||||
|
@ -42,7 +43,9 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
|||
this.filteredSearchInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
canEdit(tokenName) {
|
||||
return this.cantEdit.indexOf(tokenName) === -1;
|
||||
canEdit(tokenName, tokenValue) {
|
||||
if (this.cantEdit.includes(tokenName)) return false;
|
||||
return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
|
||||
token.value === tokenValue) === -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,16 +14,18 @@ gl.issueBoards.BoardsStore = {
|
|||
},
|
||||
state: {},
|
||||
detail: {
|
||||
issue: {}
|
||||
issue: {},
|
||||
},
|
||||
moving: {
|
||||
issue: {},
|
||||
list: {}
|
||||
list: {},
|
||||
},
|
||||
create () {
|
||||
this.state.lists = [];
|
||||
this.filter.path = getUrlParamsArray().join('&');
|
||||
this.detail = { issue: {} };
|
||||
this.detail = {
|
||||
issue: {},
|
||||
};
|
||||
},
|
||||
addList (listObj, defaultAvatar) {
|
||||
const list = new List(listObj, defaultAvatar);
|
||||
|
|
|
@ -147,6 +147,16 @@ class DropdownUtils {
|
|||
return dataValue !== null;
|
||||
}
|
||||
|
||||
static getVisualTokenValues(visualToken) {
|
||||
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
|
||||
let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
|
||||
if (tokenName === 'label' && tokenValue) {
|
||||
// remove leading symbol and wrapping quotes
|
||||
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
|
||||
}
|
||||
return { tokenName, tokenValue };
|
||||
}
|
||||
|
||||
// Determines the full search query (visual tokens + input)
|
||||
static getSearchQuery(untilInput = false) {
|
||||
const container = FilteredSearchContainer.container;
|
||||
|
|
|
@ -185,8 +185,8 @@ class FilteredSearchManager {
|
|||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
const { lastVisualToken } = gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
||||
|
||||
const sanitizedTokenName = lastVisualToken && lastVisualToken.querySelector('.name').textContent.trim();
|
||||
const canEdit = sanitizedTokenName && this.canEdit && this.canEdit(sanitizedTokenName);
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(lastVisualToken);
|
||||
const canEdit = tokenName && this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
if (this.filteredSearchInput.value === '' && lastVisualToken && canEdit) {
|
||||
this.filteredSearchInput.value = gl.FilteredSearchVisualTokens.getLastTokenPartial();
|
||||
gl.FilteredSearchVisualTokens.removeLastTokenPartial();
|
||||
|
@ -336,8 +336,8 @@ class FilteredSearchManager {
|
|||
let canClearToken = t.classList.contains('js-visual-token');
|
||||
|
||||
if (canClearToken) {
|
||||
const tokenKey = t.querySelector('.name').textContent.trim();
|
||||
canClearToken = this.canEdit && this.canEdit(tokenKey);
|
||||
const { tokenName, tokenValue } = gl.DropdownUtils.getVisualTokenValues(t);
|
||||
canClearToken = this.canEdit && this.canEdit(tokenName, tokenValue);
|
||||
}
|
||||
|
||||
if (canClearToken) {
|
||||
|
@ -469,7 +469,7 @@ class FilteredSearchManager {
|
|||
}
|
||||
|
||||
hasFilteredSearch = true;
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey);
|
||||
const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
|
||||
gl.FilteredSearchVisualTokens.addFilterVisualToken(
|
||||
sanitizedKey,
|
||||
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
|
||||
|
|
|
@ -38,21 +38,14 @@ class FilteredSearchVisualTokens {
|
|||
}
|
||||
|
||||
static createVisualTokenElementHTML(canEdit = true) {
|
||||
let removeTokenMarkup = '';
|
||||
if (canEdit) {
|
||||
removeTokenMarkup = `
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="selectable" role="button">
|
||||
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
|
||||
<div class="name"></div>
|
||||
<div class="value-container">
|
||||
<div class="value"></div>
|
||||
${removeTokenMarkup}
|
||||
<div class="remove-token" role="button">
|
||||
<i class="fa fa-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -8,7 +8,7 @@ import CreateLabelDropdown from './create_label';
|
|||
|
||||
(function() {
|
||||
this.LabelsSelect = (function() {
|
||||
function LabelsSelect(els) {
|
||||
function LabelsSelect(els, options = {}) {
|
||||
var _this, $els;
|
||||
_this = this;
|
||||
|
||||
|
@ -58,6 +58,7 @@ import CreateLabelDropdown from './create_label';
|
|||
labelHTMLTemplate = _.template('<% _.each(labels, function(label){ %> <a href="<%- ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%- encodeURIComponent(label.title) %>"> <span class="label has-tooltip color-label" title="<%- label.description %>" style="background-color: <%- label.color %>; color: <%- label.text_color %>;"> <%- label.title %> </span> </a> <% }); %>');
|
||||
labelNoneHTMLTemplate = '<span class="no-value">None</span>';
|
||||
}
|
||||
const handleClick = options.handleClick;
|
||||
|
||||
$sidebarLabelTooltip.tooltip();
|
||||
|
||||
|
@ -316,9 +317,9 @@ import CreateLabelDropdown from './create_label';
|
|||
},
|
||||
multiSelect: $dropdown.hasClass('js-multiselect'),
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(options) {
|
||||
const { $el, e, isMarking } = options;
|
||||
const label = options.selectedObj;
|
||||
clicked: function(clickEvent) {
|
||||
const { $el, e, isMarking } = clickEvent;
|
||||
const label = clickEvent.selectedObj;
|
||||
|
||||
var isIssueIndex, isMRIndex, page, boardsModel;
|
||||
var fadeOutLoader = () => {
|
||||
|
@ -391,6 +392,10 @@ import CreateLabelDropdown from './create_label';
|
|||
.then(fadeOutLoader)
|
||||
.catch(fadeOutLoader);
|
||||
}
|
||||
else if (handleClick) {
|
||||
e.preventDefault();
|
||||
handleClick(label);
|
||||
}
|
||||
else {
|
||||
if ($dropdown.hasClass('js-multiselect')) {
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import _ from 'underscore';
|
|||
|
||||
(function() {
|
||||
this.MilestoneSelect = (function() {
|
||||
function MilestoneSelect(currentProject, els) {
|
||||
function MilestoneSelect(currentProject, els, options = {}) {
|
||||
var _this, $els;
|
||||
if (currentProject != null) {
|
||||
_this = this;
|
||||
|
@ -136,19 +136,26 @@ import _ from 'underscore';
|
|||
},
|
||||
opened: function(e) {
|
||||
const $el = $(e.currentTarget);
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
|
||||
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
|
||||
}
|
||||
$('a.is-active', $el).removeClass('is-active');
|
||||
$(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
|
||||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(options) {
|
||||
const { $el, e } = options;
|
||||
let selected = options.selectedObj;
|
||||
clicked: function(clickEvent) {
|
||||
const { $el, e } = clickEvent;
|
||||
let selected = clickEvent.selectedObj;
|
||||
|
||||
var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore;
|
||||
if (!selected) return;
|
||||
|
||||
if (options.handleClick) {
|
||||
e.preventDefault();
|
||||
options.handleClick(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
page = $('body').attr('data-page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
|
|
|
@ -6,7 +6,7 @@ import _ from 'underscore';
|
|||
// TODO: remove eventHub hack after code splitting refactor
|
||||
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
||||
|
||||
function UsersSelect(currentUser, els) {
|
||||
function UsersSelect(currentUser, els, options = {}) {
|
||||
var $els;
|
||||
this.users = this.users.bind(this);
|
||||
this.user = this.user.bind(this);
|
||||
|
@ -20,6 +20,8 @@ function UsersSelect(currentUser, els) {
|
|||
}
|
||||
}
|
||||
|
||||
const { handleClick } = options;
|
||||
|
||||
$els = $(els);
|
||||
|
||||
if (!els) {
|
||||
|
@ -442,6 +444,9 @@ function UsersSelect(currentUser, els) {
|
|||
}
|
||||
if ($el.closest('.add-issues-modal').length) {
|
||||
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
|
||||
} else if (handleClick) {
|
||||
e.preventDefault();
|
||||
handleClick(user, isMarking);
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
return Issuable.filterResults($dropdown.closest('form'));
|
||||
} else if ($dropdown.hasClass('js-filter-submit')) {
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
class: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -25,7 +31,7 @@
|
|||
return this.inline ? 'span' : 'div';
|
||||
},
|
||||
cssClass() {
|
||||
return `fa-${this.size}x`;
|
||||
return `fa-${this.size}x ${this.class}`.trim();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,17 +5,27 @@ export default {
|
|||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
kind: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'primary',
|
||||
},
|
||||
modalDialogClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
closeKind: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -30,6 +40,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
submitDisabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -57,43 +72,57 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal popup-dialog"
|
||||
role="dialog"
|
||||
tabindex="-1">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button"
|
||||
class="close"
|
||||
@click="close"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">{{this.title}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body" :text="text">
|
||||
<p>{{text}}</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="btnCancelKindClass"
|
||||
@click="close">
|
||||
{{ closeButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn"
|
||||
:class="btnKindClass"
|
||||
@click="emitSubmit(true)">
|
||||
{{ primaryButtonLabel }}
|
||||
</button>
|
||||
<div class="modal-open">
|
||||
<div
|
||||
class="modal popup-dialog"
|
||||
role="dialog"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
:class="modalDialogClass"
|
||||
class="modal-dialog"
|
||||
role="document"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<slot name="header">
|
||||
<h4 class="modal-title pull-left">
|
||||
{{this.title}}
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
class="close pull-right"
|
||||
@click="close"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body" :text="text">
|
||||
<p>{{this.text}}</p>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="modal-footer" v-if="!hideFooter">
|
||||
<button
|
||||
type="button"
|
||||
class="btn pull-left"
|
||||
:class="btnCancelKindClass"
|
||||
@click="close">
|
||||
{{ closeButtonLabel }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn pull-right"
|
||||
:class="btnKindClass"
|
||||
@click="emitSubmit(true)">
|
||||
{{ primaryButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop fade in" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
.cred { color: $common-red; }
|
||||
.cgreen { color: $common-green; }
|
||||
.cdark { color: $common-gray-dark; }
|
||||
.text-secondary {
|
||||
color: $gl-text-color-secondary;
|
||||
}
|
||||
|
||||
/** COMMON CLASSES **/
|
||||
.prepend-top-0 { margin-top: 0; }
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
.dropdown-menu-nav {
|
||||
@include set-visible;
|
||||
display: block;
|
||||
min-height: 40px;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
width: 100%;
|
||||
|
|
|
@ -42,3 +42,11 @@ body.modal-open {
|
|||
width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
.modal.popup-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
background-color: $modal-body-bg;
|
||||
}
|
||||
|
|
|
@ -164,3 +164,36 @@ $pre-border-color: $border-color;
|
|||
$table-bg-accent: $gray-light;
|
||||
|
||||
$zindex-popover: 900;
|
||||
|
||||
//== Modals
|
||||
//
|
||||
//##
|
||||
|
||||
//** Padding applied to the modal body
|
||||
$modal-inner-padding: $gl-padding;
|
||||
|
||||
//** Padding applied to the modal title
|
||||
$modal-title-padding: $gl-padding;
|
||||
//** Modal title line-height
|
||||
// $modal-title-line-height: $line-height-base
|
||||
|
||||
//** Background color of modal content area
|
||||
$modal-content-bg: $gray-light;
|
||||
$modal-body-bg: $white-light;
|
||||
//** Modal content border color
|
||||
// $modal-content-border-color: rgba(0,0,0,.2)
|
||||
//** Modal content border color **for IE8**
|
||||
// $modal-content-fallback-border-color: #999
|
||||
|
||||
//** Modal backdrop background color
|
||||
// $modal-backdrop-bg: #000
|
||||
//** Modal backdrop opacity
|
||||
// $modal-backdrop-opacity: .5
|
||||
//** Modal header border color
|
||||
// $modal-header-border-color: #e5e5e5
|
||||
//** Modal footer border color
|
||||
// $modal-footer-border-color: $modal-header-border-color
|
||||
|
||||
// $modal-lg: 900px
|
||||
// $modal-md: 600px
|
||||
// $modal-sm: 300px
|
||||
|
|
|
@ -7,19 +7,6 @@
|
|||
background: $black-transparent;
|
||||
}
|
||||
|
||||
.modal.popup-dialog {
|
||||
display: block;
|
||||
background-color: $black-transparent;
|
||||
z-index: 2100;
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
.modal-dialog {
|
||||
width: 600px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-refs-form,
|
||||
.project-refs-target-form {
|
||||
display: inline-block;
|
||||
|
|
|
@ -20,17 +20,6 @@ module BoardsHelper
|
|||
project_issues_path(@project)
|
||||
end
|
||||
|
||||
def current_board_json
|
||||
board = @board || @boards.first
|
||||
|
||||
board.to_json(
|
||||
only: [:id, :name, :milestone_id],
|
||||
include: {
|
||||
milestone: { only: [:title] }
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def board_base_url
|
||||
project_boards_path(@project)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue