Skip to content
Snippets Groups Projects
Commit ff2ffb66 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@42-42-stable-ee

parent 50d93f8d
No related branches found
No related tags found
No related merge requests found
Showing
with 352 additions and 117 deletions
/* eslint-disable func-names, no-var, prefer-arrow-callback */
/* eslint-disable func-names, no-var */
 
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
Loading
Loading
@@ -45,26 +45,22 @@ MarkdownPreview.prototype.showPreview = function($form) {
this.hideReferencedUsers($form);
} else {
preview.addClass('md-preview-loading').text(__('Loading...'));
this.fetchMarkdownPreview(
mdText,
url,
function(response) {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
}.bind(this),
);
this.fetchMarkdownPreview(mdText, url, response => {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
});
}
};
 
Loading
Loading
@@ -132,12 +128,12 @@ const markdownToolbar = $('.md-header-toolbar');
 
$.fn.setupMarkdownPreview = function() {
var $form = $(this);
$form.find('textarea.markdown-area').on('input', function() {
$form.find('textarea.markdown-area').on('input', () => {
markdownPreview.hideReferencedUsers($form);
});
};
 
$(document).on('markdown-preview:show', function(e, $form) {
$(document).on('markdown-preview:show', (e, $form) => {
if (!$form) {
return;
}
Loading
Loading
@@ -162,7 +158,7 @@ $(document).on('markdown-preview:show', function(e, $form) {
markdownPreview.showPreview($form);
});
 
$(document).on('markdown-preview:hide', function(e, $form) {
$(document).on('markdown-preview:hide', (e, $form) => {
if (!$form) {
return;
}
Loading
Loading
@@ -191,7 +187,7 @@ $(document).on('markdown-preview:hide', function(e, $form) {
markdownPreview.hideReferencedCommands($form);
});
 
$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
$(document).on('markdown-preview:toggle', (e, keyboardEvent) => {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
Loading
Loading
Loading
Loading
@@ -26,7 +26,7 @@ $.fn.requiresInput = function requiresInput() {
const values = _.map($(fieldSelector, $form), field => field.value);
 
// Disable the button if any required fields are empty
if (values.length && _.any(values, _.isEmpty)) {
if (values.length && _.some(values, _.isEmpty)) {
$button.disable();
} else {
$button.enable();
Loading
Loading
import sqljs from 'sql.js';
import { template as _template } from 'underscore';
import axios from '~/lib/utils/axios_utils';
import { successCodes } from '~/lib/utils/http_status';
 
const PREVIEW_TEMPLATE = _template(`
<div class="card">
Loading
Loading
@@ -16,30 +18,25 @@ class BalsamiqViewer {
}
 
loadFile(endpoint) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', endpoint, true);
xhr.responseType = 'arraybuffer';
xhr.onload = loadEvent => this.fileLoaded(loadEvent, resolve, reject);
xhr.onerror = reject;
xhr.send();
});
}
fileLoaded(loadEvent, resolve, reject) {
if (loadEvent.target.status !== 200) return reject();
this.renderFile(loadEvent);
return resolve();
return axios
.get(endpoint, {
responseType: 'arraybuffer',
validateStatus(status) {
return status !== successCodes.OK;
},
})
.then(({ data }) => {
this.renderFile(data);
})
.catch(e => {
throw new Error(e);
});
}
 
renderFile(loadEvent) {
renderFile(fileBuffer) {
const container = document.createElement('ul');
 
this.initDatabase(loadEvent.target.response);
this.initDatabase(fileBuffer);
 
const previews = this.getPreviews();
previews.forEach(preview => {
Loading
Loading
/* eslint-disable func-names, prefer-arrow-callback */
/* eslint-disable func-names */
 
import $ from 'jquery';
import Dropzone from 'dropzone';
Loading
Loading
@@ -43,18 +43,18 @@ export default class BlobFileDropzone {
previewsContainer: '.dropzone-previews',
headers: csrf.headers,
init() {
this.on('addedfile', function() {
this.on('addedfile', () => {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts')
.html('')
.hide();
});
this.on('removedfile', function() {
this.on('removedfile', () => {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS);
});
this.on('success', function(header, response) {
this.on('success', (header, response) => {
$('#modal-upload-blob').modal('hide');
visitUrl(response.filePath);
});
Loading
Loading
@@ -62,7 +62,7 @@ export default class BlobFileDropzone {
dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
this.on('sending', function(file, xhr, formData) {
this.on('sending', (file, xhr, formData) => {
formData.append('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
Loading
Loading
Loading
Loading
@@ -7,6 +7,8 @@ import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
import DockerfileSelector from './template_selectors/dockerfile_selector';
import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
 
export default class FileTemplateMediator {
constructor({ editor, currentAction, projectId }) {
Loading
Loading
@@ -19,6 +21,7 @@ export default class FileTemplateMediator {
this.initDomElements();
this.initDropdowns();
this.initPageEvents();
this.cacheFileContents();
}
 
initTemplateSelectors() {
Loading
Loading
@@ -40,6 +43,7 @@ export default class FileTemplateMediator {
return {
name: cfg.name,
key: cfg.key,
id: cfg.key,
};
}),
});
Loading
Loading
@@ -58,6 +62,7 @@ export default class FileTemplateMediator {
this.$fileContent = $fileEditor.find('#file-content');
this.$commitForm = $fileEditor.find('form');
this.$navLinks = $fileEditor.find('.nav-links');
this.$templateTypes = this.$templateSelectors.find('.template-type-selector');
}
 
initDropdowns() {
Loading
Loading
@@ -113,7 +118,11 @@ export default class FileTemplateMediator {
}
});
 
this.typeSelector.setToggleText(item.name);
this.setFilename(item.name);
if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name);
}
 
this.cacheToggleText();
}
Loading
Loading
@@ -123,15 +132,24 @@ export default class FileTemplateMediator {
}
 
selectTemplateFile(selector, query, data) {
const self = this;
selector.renderLoading();
// in case undo menu is already there
this.destroyUndoMenu();
this.fetchFileTemplate(selector.config.type, query, data)
.then(file => {
this.showUndoMenu();
this.setEditorContent(file);
this.setFilename(selector.config.name);
selector.renderLoaded();
this.typeSelector.setToggleText(selector.config.name);
toast(__(`${query} template applied`), {
action: {
text: __('Undo'),
onClick: (e, toastObj) => {
self.restoreFromCache();
toastObj.goAway(0);
},
},
});
})
.catch(err => new Flash(`An error occurred while fetching the template: ${err}`));
}
Loading
Loading
@@ -173,22 +191,6 @@ export default class FileTemplateMediator {
return this.templateSelectors.find(selector => selector.config.key === key);
}
 
showUndoMenu() {
this.$undoMenu.removeClass('hidden');
this.$undoBtn.on('click', () => {
this.restoreFromCache();
this.destroyUndoMenu();
});
}
destroyUndoMenu() {
this.cacheFileContents();
this.cacheToggleText();
this.$undoMenu.addClass('hidden');
this.$undoBtn.off('click');
}
hideTemplateSelectorMenu() {
this.$templatesMenu.hide();
}
Loading
Loading
@@ -210,6 +212,7 @@ export default class FileTemplateMediator {
this.setEditorContent(this.cachedContent);
this.setFilename(this.cachedFilename);
this.setTemplateSelectorToggleText();
this.setTypeSelectorToggleText(__('Select a template type'));
}
 
getTemplateSelectorToggleText() {
Loading
Loading
@@ -228,6 +231,10 @@ export default class FileTemplateMediator {
return this.typeSelector.getToggleText();
}
 
setTypeSelectorToggleText(text) {
this.typeSelector.setToggleText(text);
}
getFilename() {
return this.$filenameInput.val();
}
Loading
Loading
/* eslint-disable class-methods-use-this */
 
import $ from 'jquery';
import '~/gl_dropdown';
 
export default class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
Loading
Loading
@@ -26,11 +27,16 @@ export default class TemplateSelector {
search: {
fields: ['name'],
},
clicked: options => this.fetchFileTemplate(options),
clicked: options => this.onDropdownClicked(options),
text: item => item.name,
});
}
 
// Subclasses can override this method to conditionally prevent fetching file templates
onDropdownClicked(options) {
this.fetchFileTemplate(options);
}
initAutosizeUpdateEvent() {
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
Loading
Loading
@@ -77,9 +83,14 @@ export default class TemplateSelector {
 
if (this.editor instanceof $) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
this.editor.trigger('input');
}
}
 
getEditorContent() {
return this.editor.getValue();
}
startLoadingSpinner() {
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
Loading
Loading
Loading
Loading
@@ -19,7 +19,6 @@ export default class BlobCiYamlSelector extends FileTemplateSelector {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,
toggleLabel: item => item.name,
search: {
fields: ['name'],
},
Loading
Loading
Loading
Loading
@@ -20,7 +20,6 @@ export default class DockerfileSelector extends FileTemplateSelector {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,
toggleLabel: item => item.name,
search: {
fields: ['name'],
},
Loading
Loading
Loading
Loading
@@ -18,7 +18,6 @@ export default class BlobGitignoreSelector extends FileTemplateSelector {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,
toggleLabel: item => item.name,
search: {
fields: ['name'],
},
Loading
Loading
Loading
Loading
@@ -18,7 +18,6 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
data: this.$dropdown.data('data'),
filterable: true,
selectable: true,
toggleLabel: item => item.name,
search: {
fields: ['name'],
},
Loading
Loading
Loading
Loading
@@ -16,7 +16,6 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
data: this.config.dropdownData,
filterable: false,
selectable: true,
toggleLabel: item => item.name,
clicked: options => this.mediator.selectTemplateTypeOptions(options),
text: item => item.name,
});
Loading
Loading
Loading
Loading
@@ -107,18 +107,18 @@ export default class BlobViewer {
toggleCopyButtonState() {
if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) {
this.copySourceBtn.setAttribute('title', __('Copy source to clipboard'));
this.copySourceBtn.setAttribute('title', __('Copy file contents'));
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute(
'title',
__('Wait for the source to load to copy it to the clipboard'),
__('Wait for the file to load to copy its contents'),
);
this.copySourceBtn.classList.add('disabled');
} else {
this.copySourceBtn.setAttribute(
'title',
__('Switch to the source to copy it to the clipboard'),
__('Switch to the source to copy the file contents'),
);
this.copySourceBtn.classList.add('disabled');
}
Loading
Loading
Loading
Loading
@@ -29,25 +29,25 @@ export default {
});
});
 
const loadListIssues = listObj => {
const list = boardsStore.findList('title', listObj.title);
if (!list) {
return null;
}
list.id = listObj.id;
list.label.id = listObj.label.id;
return list.getIssues().catch(() => {
// TODO: handle request error
});
};
// Save the labels
boardsStore
.generateDefaultLists()
.then(res => res.data)
.then(data => {
data.forEach(listObj => {
const list = boardsStore.findList('title', listObj.title);
if (!list) {
return;
}
list.id = listObj.id;
list.label.id = listObj.label.id;
list.getIssues().catch(() => {
// TODO: handle request error
});
});
})
.then(data => Promise.all(data.map(loadListIssues)))
.catch(() => {
boardsStore.removeList(undefined, 'label');
Cookies.remove('issue_board_welcome_hidden', {
Loading
Loading
Loading
Loading
@@ -42,12 +42,19 @@ export default {
return {
showDetail: false,
detailIssue: boardsStore.detail,
multiSelect: boardsStore.multiSelect,
};
},
computed: {
issueDetailVisible() {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
},
multiSelectVisible() {
return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1;
},
canMultiSelect() {
return gon.features && gon.features.multiSelectBoard;
},
},
methods: {
mouseDown() {
Loading
Loading
@@ -58,14 +65,20 @@ export default {
},
showIssue(e) {
if (e.target.classList.contains('js-no-trigger')) return;
if (this.showDetail) {
this.showDetail = false;
 
// If CMD or CTRL is clicked
const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
eventHub.$emit('clearDetailIssue');
eventHub.$emit('clearDetailIssue', isMultiSelect);
if (isMultiSelect) {
eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
}
} else {
eventHub.$emit('newDetailIssue', this.issue);
eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
boardsStore.setListDetail(this.list);
}
}
Loading
Loading
@@ -77,6 +90,7 @@ export default {
<template>
<li
:class="{
'multi-select': multiSelectVisible,
'user-can-drag': !disabled && issue.id,
'is-disabled': disabled || !issue.id,
'is-active': issueDetailVisible,
Loading
Loading
Loading
Loading
@@ -194,6 +194,7 @@ export default {
ref="name"
v-model="board.name"
class="form-control"
data-qa-selector="board_name_field"
type="text"
:placeholder="__('Enter board name')"
@keyup.enter="submit"
Loading
Loading
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import Sortable from 'sortablejs';
import { Sortable, MultiDrag } from 'sortablejs';
import { GlLoadingIcon } from '@gitlab/ui';
import _ from 'underscore';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { getBoardSortableDefaultOptions, sortableStart } from '../mixins/sortable_default_options';
import { sprintf, __ } from '~/locale';
import createFlash from '~/flash';
import {
getBoardSortableDefaultOptions,
sortableStart,
sortableEnd,
} from '../mixins/sortable_default_options';
if (gon.features && gon.features.multiSelectBoard) {
Sortable.mount(new MultiDrag());
}
 
export default {
name: 'BoardList',
Loading
Loading
@@ -54,6 +64,14 @@ export default {
showIssueForm: false,
};
},
computed: {
paginatedIssueText() {
return sprintf(__('Showing %{pageSize} of %{total} issues'), {
pageSize: this.list.issues.length,
total: this.list.issuesSize,
});
},
},
watch: {
filters: {
handler() {
Loading
Loading
@@ -87,11 +105,20 @@ export default {
eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop);
},
mounted() {
const multiSelectOpts = {};
if (gon.features && gon.features.multiSelectBoard) {
multiSelectOpts.multiDrag = true;
multiSelectOpts.selectedClass = 'js-multi-select';
multiSelectOpts.animation = 500;
}
const options = getBoardSortableDefaultOptions({
scroll: true,
disabled: this.disabled,
filter: '.board-list-count, .is-disabled',
dataIdAttr: 'data-issue-id',
removeCloneOnHide: false,
...multiSelectOpts,
group: {
name: 'issues',
/**
Loading
Loading
@@ -145,25 +172,66 @@ export default {
card.showDetail = false;
 
const { list } = card;
const issue = list.findIssue(Number(e.item.dataset.issueId));
boardsStore.startMoving(list, issue);
 
sortableStart();
},
onAdd: e => {
boardsStore.moveIssueToList(
boardsStore.moving.list,
this.list,
boardsStore.moving.issue,
e.newIndex,
);
const { items = [], newIndicies = [] } = e;
if (items.length) {
// Not using e.newIndex here instead taking a min of all
// the newIndicies. Basically we have to find that during
// a drop what is the index we're going to start putting
// all the dropped elements from.
const newIndex = Math.min(...newIndicies.map(obj => obj.index).filter(i => i !== -1));
const issues = items.map(item =>
boardsStore.moving.list.findIssue(Number(item.dataset.issueId)),
);
 
this.$nextTick(() => {
e.item.remove();
});
boardsStore.moveMultipleIssuesToList({
listFrom: boardsStore.moving.list,
listTo: this.list,
issues,
newIndex,
});
} else {
boardsStore.moveIssueToList(
boardsStore.moving.list,
this.list,
boardsStore.moving.issue,
e.newIndex,
);
this.$nextTick(() => {
e.item.remove();
});
}
},
onUpdate: e => {
const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
const { items = [], newIndicies = [], oldIndicies = [] } = e;
if (items.length) {
const newIndex = Math.min(...newIndicies.map(obj => obj.index));
const issues = items.map(item =>
boardsStore.moving.list.findIssue(Number(item.dataset.issueId)),
);
boardsStore.moveMultipleIssuesInList({
list: this.list,
issues,
oldIndicies: oldIndicies.map(obj => obj.index),
newIndex,
idArray: sortedArray,
});
e.items.forEach(el => {
Sortable.utils.deselect(el);
});
boardsStore.clearMultiSelect();
return;
}
boardsStore.moveIssueInList(
this.list,
boardsStore.moving.issue,
Loading
Loading
@@ -172,9 +240,133 @@ export default {
sortedArray,
);
},
onEnd: e => {
const { items = [], clones = [], to } = e;
// This is not a multi select operation
if (!items.length && !clones.length) {
sortableEnd();
return;
}
let toList;
if (to) {
const containerEl = to.closest('.js-board-list');
toList = boardsStore.findList('id', Number(containerEl.dataset.board));
}
/**
* onEnd is called irrespective if the cards were moved in the
* same list or the other list. Don't remove items if it's same list.
*/
const isSameList = toList && toList.id === this.list.id;
if (toList && !isSameList && boardsStore.shouldRemoveIssue(this.list, toList)) {
const issues = items.map(item => this.list.findIssue(Number(item.dataset.issueId)));
if (_.compact(issues).length && !boardsStore.issuesAreContiguous(this.list, issues)) {
const indexes = [];
const ids = this.list.issues.map(i => i.id);
issues.forEach(issue => {
const index = ids.indexOf(issue.id);
if (index > -1) {
indexes.push(index);
}
});
// Descending sort because splice would cause index discrepancy otherwise
const sortedIndexes = indexes.sort((a, b) => (a < b ? 1 : -1));
sortedIndexes.forEach(i => {
/**
* **setTimeout and splice each element one-by-one in a loop
* is intended.**
*
* The problem here is all the indexes are in the list but are
* non-contiguous. Due to that, when we splice all the indexes,
* at once, Vue -- during a re-render -- is unable to find reference
* nodes and the entire app crashes.
*
* If the indexes are contiguous, this piece of code is not
* executed. If it is, this is a possible regression. Only when
* issue indexes are far apart, this logic should ever kick in.
*/
setTimeout(() => {
this.list.issues.splice(i, 1);
}, 0);
});
}
}
if (!toList) {
createFlash(__('Something went wrong while performing the action.'));
}
if (!isSameList) {
boardsStore.clearMultiSelect();
// Since Vue's list does not re-render the same keyed item, we'll
// remove `multi-select` class to express it's unselected
if (clones && clones.length) {
clones.forEach(el => el.classList.remove('multi-select'));
}
// Due to some bug which I am unable to figure out
// Sortable does not deselect some pending items from the
// source list.
// We'll just do it forcefully here.
Array.from(document.querySelectorAll('.js-multi-select') || []).forEach(item => {
Sortable.utils.deselect(item);
});
/**
* SortableJS leaves all the moving items "as is" on the DOM.
* Vue picks up and rehydrates the DOM, but we need to explicity
* remove the "trash" items from the DOM.
*
* This is in parity to the logic on single item move from a list/in
* a list. For reference, look at the implementation of onAdd method.
*/
this.$nextTick(() => {
if (items && items.length) {
items.forEach(item => {
item.remove();
});
}
});
}
sortableEnd();
},
onMove(e) {
return !e.related.classList.contains('board-list-count');
},
onSelect(e) {
const {
item: { classList },
} = e;
if (
classList &&
classList.contains('js-multi-select') &&
!classList.contains('multi-select')
) {
Sortable.utils.deselect(e.item);
}
},
onDeselect: e => {
const {
item: { dataset, classList },
} = e;
if (
classList &&
classList.contains('multi-select') &&
!classList.contains('js-multi-select')
) {
const issue = this.list.findIssue(Number(dataset.issueId));
boardsStore.toggleMultiSelect(issue);
}
},
});
 
this.sortable = Sortable.create(this.$refs.list, options);
Loading
Loading
@@ -260,7 +452,7 @@ export default {
<li v-if="showCount" class="board-list-count text-center" data-issue-id="-1">
<gl-loading-icon v-show="list.loadingMore" label="Loading more issues" />
<span v-if="list.issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
<span v-else> Showing {{ list.issues.length }} of {{ list.issuesSize }} issues </span>
<span v-else>{{ paginatedIssueText }}</span>
</li>
</ul>
</div>
Loading
Loading
Loading
Loading
@@ -305,13 +305,18 @@ export default {
<div v-if="canAdminBoard">
<gl-dropdown-divider />
 
<gl-dropdown-item v-if="multipleIssueBoardsAvailable" @click.prevent="showPage('new')">
<gl-dropdown-item
v-if="multipleIssueBoardsAvailable"
data-qa-selector="create_new_board_button"
@click.prevent="showPage('new')"
>
{{ s__('IssueBoards|Create new board') }}
</gl-dropdown-item>
 
<gl-dropdown-item
v-if="showDelete"
class="text-danger"
data-qa-selector="delete_board_button"
@click.prevent="showPage('delete')"
>
{{ s__('IssueBoards|Delete board') }}
Loading
Loading
Loading
Loading
@@ -99,7 +99,10 @@ export default {
return !groupId ? referencePath.split('#')[0] : null;
},
orderedLabels() {
return _.sortBy(this.issue.labels, 'title');
return _.chain(this.issue.labels)
.filter(this.isNonListLabel)
.sortBy('title')
.value();
},
helpLink() {
return boardsStore.scopedLabels.helpLink;
Loading
Loading
@@ -130,6 +133,9 @@ export default {
if (!label.id) return false;
return true;
},
isNonListLabel(label) {
return label.id && !(this.list.type === 'label' && this.list.title === label.title);
},
filterByLabel(label) {
if (!this.updateFilters) return;
const labelTitle = encodeURIComponent(label.title);
Loading
Loading
@@ -167,7 +173,7 @@ export default {
</h4>
</div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
<template v-for="label in orderedLabels" v-if="showLabel(label)">
<template v-for="label in orderedLabels">
<issue-card-inner-scoped-label
v-if="showScopedLabel(label)"
:key="label.id"
Loading
Loading
@@ -212,7 +218,7 @@ export default {
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" />
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
<issue-card-weight
v-if="issue.weight"
v-if="validIssueWeight"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
/>
Loading
Loading
Loading
Loading
@@ -34,7 +34,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
<icon name="hourglass" css-classes="board-card-info-icon align-top" /><time
<icon name="hourglass" class="board-card-info-icon align-top" /><time
class="board-card-info-text"
>{{ timeEstimate }}</time
>
Loading
Loading
export const ListType = {
assignee: 'assignee',
milestone: 'milestone',
backlog: 'backlog',
closed: 'closed',
label: 'label',
};
export default {
ListType,
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment