Removed underscoreJS uses
Removed props on main VueJS app instead uses data variables
This commit is contained in:
parent
4460724da7
commit
17c576c1ac
|
@ -7,7 +7,9 @@
|
|||
//= require_tree ./mixins
|
||||
//= require_tree ./components
|
||||
|
||||
$(function () {
|
||||
$(() => {
|
||||
const $boardApp = $('#board-app');
|
||||
|
||||
if (!window.gl) {
|
||||
window.gl = {};
|
||||
}
|
||||
|
@ -17,38 +19,42 @@ $(function () {
|
|||
}
|
||||
|
||||
gl.IssueBoardsApp = new Vue({
|
||||
el: '#board-app',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
endpoint: String,
|
||||
issueLinkBase: String
|
||||
},
|
||||
el: $boardApp.get(0),
|
||||
data: {
|
||||
state: BoardsStore.state,
|
||||
loading: true
|
||||
loading: true,
|
||||
endpoint: $boardApp.data('endpoint'),
|
||||
disabled: $boardApp.data('disabled'),
|
||||
issueLinkBase: $boardApp.data('issue-link-base')
|
||||
},
|
||||
init: function () {
|
||||
init () {
|
||||
BoardsStore.create();
|
||||
},
|
||||
created: function () {
|
||||
created () {
|
||||
this.loading = true;
|
||||
gl.boardService = new BoardService(this.endpoint);
|
||||
|
||||
$boardApp
|
||||
.removeAttr('data-endpoint')
|
||||
.removeAttr('data-disabled')
|
||||
.removeAttr('data-issue-link-base');
|
||||
},
|
||||
ready: function () {
|
||||
ready () {
|
||||
BoardsStore.disabled = this.disabled;
|
||||
gl.boardService.all()
|
||||
.then((resp) => {
|
||||
const boards = resp.json();
|
||||
|
||||
boards.forEach((board) => {
|
||||
const list = BoardsStore.addList(board);
|
||||
for (let i = 0, boardsLength = boards.length; i < boardsLength; i++) {
|
||||
const board = boards[i],
|
||||
list = BoardsStore.addList(board);
|
||||
|
||||
if (list.type === 'done') {
|
||||
list.position = 9999999;
|
||||
list.position = Infinity;
|
||||
} else if (list.type === 'backlog') {
|
||||
list.position = -1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BoardsStore.addBlankState();
|
||||
this.loading = false;
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
(function () {
|
||||
(() => {
|
||||
const Board = Vue.extend({
|
||||
props: {
|
||||
list: Object,
|
||||
disabled: Boolean,
|
||||
issueLinkBase: String
|
||||
},
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
query: '',
|
||||
filters: BoardsStore.state.filters
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'query': function () {
|
||||
query () {
|
||||
if (this.list.canSearch()) {
|
||||
this.list.filters = this.getFilterData();
|
||||
this.list.getIssues(true);
|
||||
}
|
||||
},
|
||||
'filters': {
|
||||
handler: function () {
|
||||
filters: {
|
||||
handler () {
|
||||
this.list.page = 1;
|
||||
this.list.getIssues(true);
|
||||
},
|
||||
|
@ -27,30 +27,33 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
clearSearch: function () {
|
||||
clearSearch () {
|
||||
this.query = '';
|
||||
},
|
||||
getFilterData: function () {
|
||||
const queryData = this.list.canSearch() ? { search: this.query } : {};
|
||||
getFilterData () {
|
||||
const filters = this.filters;
|
||||
let queryData = this.list.canSearch() ? { search: this.query } : {};
|
||||
|
||||
Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
|
||||
|
||||
return _.extend(queryData, this.filters);
|
||||
return queryData;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isPreset: function () {
|
||||
isPreset () {
|
||||
return this.list.type === 'backlog' || this.list.type === 'done' || this.list.type === 'blank';
|
||||
}
|
||||
},
|
||||
ready: function () {
|
||||
let options = _.extend({
|
||||
ready () {
|
||||
const options = gl.getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
group: 'boards',
|
||||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onUpdate: function (e) {
|
||||
onUpdate (e) {
|
||||
BoardsStore.moveList(e.oldIndex, e.newIndex);
|
||||
}
|
||||
}, gl.boardSortableDefaultOptions);
|
||||
});
|
||||
|
||||
if (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'xs') {
|
||||
options.handle = '.js-board-drag-handle';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(() => {
|
||||
const BoardBlankState = Vue.extend({
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
predefinedLabels: [
|
||||
new ListLabel({ title: 'Development', color: '#5CB85C' }),
|
||||
|
@ -11,11 +11,13 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
addDefaultLists: function (e) {
|
||||
addDefaultLists (e) {
|
||||
e.stopImmediatePropagation();
|
||||
BoardsStore.removeBlankState();
|
||||
|
||||
_.each(this.predefinedLabels, (label, i) => {
|
||||
for (let i = 0, labelsLength = this.predefinedLabels.length; i < labelsLength; i++) {
|
||||
const label = this.predefinedLabels[i];
|
||||
|
||||
BoardsStore.addList({
|
||||
title: label.title,
|
||||
position: i,
|
||||
|
@ -25,7 +27,7 @@
|
|||
color: label.color
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Save the labels
|
||||
gl.boardService
|
||||
|
@ -33,15 +35,16 @@
|
|||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
|
||||
_.each(data, (listObj) => {
|
||||
const list = BoardsStore.findList('title', listObj.title);
|
||||
for (let i = 0, dataLength = data.length; i < dataLength; i++) {
|
||||
const listObj = data[i],
|
||||
list = BoardsStore.findList('title', listObj.title);
|
||||
list.id = listObj.id;
|
||||
list.label.id = listObj.label.id;
|
||||
list.getIssues();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
clearBlankState: function () {
|
||||
clearBlankState () {
|
||||
BoardsStore.removeBlankState();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(function () {
|
||||
(() => {
|
||||
const BoardCard = Vue.extend({
|
||||
props: {
|
||||
list: Object,
|
||||
|
@ -7,7 +7,7 @@
|
|||
disabled: Boolean
|
||||
},
|
||||
methods: {
|
||||
filterByLabel: function (label, $event) {
|
||||
filterByLabel (label, $event) {
|
||||
let labelToggleText = label.title;
|
||||
const labelIndex = BoardsStore.state.filters['label_name'].indexOf(label.title);
|
||||
$($event.target).tooltip('hide');
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
list: Object
|
||||
},
|
||||
methods: {
|
||||
deleteBoard: function (e) {
|
||||
deleteBoard (e) {
|
||||
e.stopImmediatePropagation();
|
||||
$(this.$el).tooltip('hide');
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(function () {
|
||||
(() => {
|
||||
const BoardList = Vue.extend({
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
|
@ -7,7 +7,7 @@
|
|||
loading: Boolean,
|
||||
issueLinkBase: String
|
||||
},
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
scrollOffset: 250,
|
||||
loadingMore: false,
|
||||
|
@ -15,8 +15,8 @@
|
|||
};
|
||||
},
|
||||
watch: {
|
||||
'filters': {
|
||||
handler: function () {
|
||||
filters: {
|
||||
handler () {
|
||||
this.loadingMore = false;
|
||||
this.$els.list.scrollTop = 0;
|
||||
},
|
||||
|
@ -24,16 +24,16 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
listHeight: function () {
|
||||
listHeight () {
|
||||
return this.$els.list.getBoundingClientRect().height;
|
||||
},
|
||||
scrollHeight: function () {
|
||||
scrollHeight () {
|
||||
return this.$els.list.scrollHeight;
|
||||
},
|
||||
scrollTop: function () {
|
||||
scrollTop () {
|
||||
return this.$els.list.scrollTop + this.listHeight();
|
||||
},
|
||||
loadNextPage: function () {
|
||||
loadNextPage () {
|
||||
this.loadingMore = true;
|
||||
const getIssues = this.list.nextPage();
|
||||
|
||||
|
@ -44,24 +44,24 @@
|
|||
}
|
||||
},
|
||||
},
|
||||
ready: function () {
|
||||
const list = this.list;
|
||||
let options = _.extend({
|
||||
group: 'issues',
|
||||
sort: false,
|
||||
disabled: this.disabled,
|
||||
onAdd: (e) => {
|
||||
const card = e.item,
|
||||
fromListId = parseInt(e.from.getAttribute('data-board')),
|
||||
toListId = parseInt(e.to.getAttribute('data-board')),
|
||||
issueId = parseInt(card.getAttribute('data-issue'));
|
||||
ready () {
|
||||
const list = this.list,
|
||||
options = gl.getBoardSortableDefaultOptions({
|
||||
group: 'issues',
|
||||
sort: false,
|
||||
disabled: this.disabled,
|
||||
onAdd (e) {
|
||||
const card = e.item,
|
||||
fromListId = parseInt(e.from.getAttribute('data-board')),
|
||||
toListId = parseInt(e.to.getAttribute('data-board')),
|
||||
issueId = parseInt(card.getAttribute('data-issue'));
|
||||
|
||||
// Remove the new dom element & let vue add the element
|
||||
card.parentNode.removeChild(card);
|
||||
// Remove the new dom element & let vue add the element
|
||||
card.parentNode.removeChild(card);
|
||||
|
||||
BoardsStore.moveCardToList(fromListId, toListId, issueId);
|
||||
}
|
||||
}, gl.boardSortableDefaultOptions);
|
||||
BoardsStore.moveCardToList(fromListId, toListId, issueId);
|
||||
}
|
||||
});
|
||||
|
||||
if (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'xs') {
|
||||
options.handle = '.js-card-drag-handle';
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
$(function () {
|
||||
$(() => {
|
||||
$('.js-new-board-list').each(function () {
|
||||
const $this = $(this);
|
||||
|
||||
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('project-id'));
|
||||
|
||||
$this.glDropdown({
|
||||
data: function(term, callback) {
|
||||
data(term, callback) {
|
||||
$.ajax({
|
||||
url: $this.attr('data-labels')
|
||||
}).then((resp) => {
|
||||
callback(resp);
|
||||
});
|
||||
},
|
||||
renderRow: (label) => {
|
||||
renderRow (label) {
|
||||
const active = BoardsStore.findList('title', label.title),
|
||||
$li = $('<li />',),
|
||||
$a = $('<a />', {
|
||||
|
@ -32,7 +32,7 @@ $(function () {
|
|||
},
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
clicked: (label, $el, e) => {
|
||||
clicked (label, $el, e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!BoardsStore.findList('title', label.title)) {
|
||||
|
|
|
@ -3,19 +3,24 @@
|
|||
window.gl = {};
|
||||
}
|
||||
|
||||
gl.boardSortableDefaultOptions = {
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.has-tooltip',
|
||||
scrollSensitivity: 100,
|
||||
scrollSpeed: 20,
|
||||
onStart: function () {
|
||||
document.body.classList.add('is-dragging');
|
||||
},
|
||||
onEnd: function () {
|
||||
document.body.classList.remove('is-dragging');
|
||||
gl.getBoardSortableDefaultOptions = (obj) => {
|
||||
let defaultSortOptions = {
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.has-tooltip',
|
||||
scrollSensitivity: 100,
|
||||
scrollSpeed: 20,
|
||||
onStart () {
|
||||
document.body.classList.add('is-dragging');
|
||||
},
|
||||
onEnd () {
|
||||
document.body.classList.remove('is-dragging');
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
return defaultSortOptions;
|
||||
};
|
||||
})(window);
|
||||
|
|
|
@ -10,11 +10,12 @@ class ListIssue {
|
|||
|
||||
this.labels = [];
|
||||
|
||||
_.each(obj.labels, (label) => {
|
||||
for (let i = 0, objLabelsLength = obj.labels.length; i < objLabelsLength; i++) {
|
||||
const label = obj.labels[i];
|
||||
this.labels.push(new ListLabel(label));
|
||||
});
|
||||
}
|
||||
|
||||
this.priority = _.reduce(this.labels, (max, label) => {
|
||||
this.priority = this.labels.reduce((max, label) => {
|
||||
return (label.priority < max) ? label.priority : max;
|
||||
}, Infinity);
|
||||
}
|
||||
|
@ -30,25 +31,28 @@ class ListIssue {
|
|||
}
|
||||
|
||||
findLabel (findLabel) {
|
||||
return _.find(this.labels, (label) => {
|
||||
return this.labels.filter((label) => {
|
||||
return label.title === findLabel.title;
|
||||
});
|
||||
})[0];
|
||||
}
|
||||
|
||||
removeLabel (removeLabel) {
|
||||
if (removeLabel) {
|
||||
this.labels = _.reject(this.labels, (label) => {
|
||||
return removeLabel.title === label.title;
|
||||
this.labels = this.labels.filter((label) => {
|
||||
return removeLabel.title !== label.title;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeLabels (labels) {
|
||||
_.each(labels, this.removeLabel.bind(this));
|
||||
for (let i = 0, labelsLength = labels.length; i < labelsLength; i++) {
|
||||
const label = labels[i];
|
||||
this.removeLabel(label);
|
||||
}
|
||||
}
|
||||
|
||||
getLists () {
|
||||
return _.filter(BoardsStore.state.lists, (list) => {
|
||||
return BoardsStore.state.lists.filter((list) => {
|
||||
return list.findIssue(this.id);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ class List {
|
|||
|
||||
destroy () {
|
||||
if (this.type !== 'blank') {
|
||||
BoardsStore.state.lists = _.reject(BoardsStore.state.lists, (list) => {
|
||||
return list.id === this.id;
|
||||
BoardsStore.state.lists = BoardsStore.state.lists.filter((list) => {
|
||||
return list.id !== this.id;
|
||||
});
|
||||
BoardsStore.updateNewListDropdown();
|
||||
|
||||
|
@ -59,11 +59,14 @@ class List {
|
|||
}
|
||||
|
||||
getIssues (emptyIssues = true) {
|
||||
let data = _.extend({ page: this.page }, this.filters);
|
||||
const filters = this.filters;
|
||||
let data = { page: this.page };
|
||||
|
||||
Object.keys(filters).forEach((key) => { data[key] = filters[key]; });
|
||||
|
||||
if (this.label) {
|
||||
data.label_name = _.reject(data.label_name, (label) => {
|
||||
return label === this.label.title;
|
||||
data.label_name = data.label_name.filter((label) => {
|
||||
return label !== this.label.title;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -85,9 +88,10 @@ class List {
|
|||
}
|
||||
|
||||
createIssues (data) {
|
||||
_.each(data, (issueObj) => {
|
||||
for (let i = 0, dataLength = data.length; i < dataLength; i++) {
|
||||
const issueObj = data[i];
|
||||
this.issues.push(new ListIssue(issueObj));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addIssue (issue, listFrom) {
|
||||
|
@ -99,20 +103,20 @@ class List {
|
|||
}
|
||||
|
||||
findIssue (id) {
|
||||
return _.find(this.issues, (issue) => {
|
||||
return this.issues.filter((issue) => {
|
||||
return issue.id === id;
|
||||
});
|
||||
})[0];
|
||||
}
|
||||
|
||||
removeIssue (removeIssue) {
|
||||
this.issues = _.reject(this.issues, (issue) => {
|
||||
this.issues = this.issues.filter((issue) => {
|
||||
const matchesRemove = removeIssue.id === issue.id;
|
||||
|
||||
if (matchesRemove) {
|
||||
issue.removeLabel(this.label);
|
||||
}
|
||||
|
||||
return matchesRemove;
|
||||
return !matchesRemove;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,8 @@ class BoardService {
|
|||
}
|
||||
|
||||
getIssuesForList (id, filter = {}) {
|
||||
const data = _.extend({ id }, filter)
|
||||
let data = { id };
|
||||
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
|
||||
this.setCSRF();
|
||||
|
||||
return this.issues.get(data);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
w.BoardsStore = {
|
||||
disabled: false,
|
||||
state: {},
|
||||
create: function () {
|
||||
create () {
|
||||
this.state.lists = [];
|
||||
this.state.filters = {
|
||||
author_id: gl.utils.getParameterValues('author_id')[0],
|
||||
|
@ -11,26 +11,29 @@
|
|||
label_name: gl.utils.getParameterValues('label_name[]')
|
||||
};
|
||||
},
|
||||
addList: function (listObj) {
|
||||
addList (listObj) {
|
||||
const list = new List(listObj);
|
||||
this.state.lists.push(list);
|
||||
|
||||
return list;
|
||||
},
|
||||
new: function (listObj) {
|
||||
new (listObj) {
|
||||
const list = this.addList(listObj),
|
||||
backlogList = this.findList('type', 'backlog');
|
||||
|
||||
list
|
||||
.save()
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
// Remove any new issues from the backlog
|
||||
// as they will be visible in the new list
|
||||
_.each(list.issues, backlogList.removeIssue.bind(backlogList));
|
||||
for (let i = 0, issuesLength = list.issues.length; i < issuesLength; i++) {
|
||||
const issue = list.issues[i];
|
||||
backlogList.removeIssue(issue);
|
||||
}
|
||||
});
|
||||
this.removeBlankState();
|
||||
},
|
||||
updateNewListDropdown: function () {
|
||||
updateNewListDropdown () {
|
||||
const glDropdown = $('.js-new-board-list').data('glDropdown');
|
||||
|
||||
if (glDropdown) {
|
||||
|
@ -41,13 +44,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
shouldAddBlankState: function () {
|
||||
shouldAddBlankState () {
|
||||
// Decide whether to add the blank state
|
||||
return !(!!_.find(this.state.lists, function (list) {
|
||||
return !(this.state.lists.filter((list) => {
|
||||
return list.type !== 'backlog' && list.type !== 'done';
|
||||
}));
|
||||
})[0]);
|
||||
},
|
||||
addBlankState: function () {
|
||||
addBlankState () {
|
||||
if (this.welcomeIsHidden() || this.disabled) return;
|
||||
|
||||
if (this.shouldAddBlankState()) {
|
||||
|
@ -59,26 +62,26 @@
|
|||
});
|
||||
}
|
||||
},
|
||||
removeBlankState: function () {
|
||||
removeBlankState () {
|
||||
this.removeList('blank');
|
||||
|
||||
$.cookie('issue_board_welcome_hidden', 'true', {
|
||||
expires: 365 * 10
|
||||
});
|
||||
},
|
||||
welcomeIsHidden: function () {
|
||||
welcomeIsHidden () {
|
||||
return $.cookie('issue_board_welcome_hidden') === 'true';
|
||||
},
|
||||
removeList: function (id) {
|
||||
removeList (id) {
|
||||
const list = this.findList('id', id);
|
||||
|
||||
if (!list) return;
|
||||
|
||||
this.state.lists = _.reject(this.state.lists, (list) => {
|
||||
return list.id === id;
|
||||
this.state.lists = this.state.lists.filter((list) => {
|
||||
return list.id !== id;
|
||||
});
|
||||
},
|
||||
moveList: function (oldIndex, newIndex) {
|
||||
moveList (oldIndex, newIndex) {
|
||||
if (oldIndex === newIndex) return;
|
||||
|
||||
const listFrom = this.findList('position', oldIndex),
|
||||
|
@ -95,13 +98,13 @@
|
|||
|
||||
listFrom.update();
|
||||
},
|
||||
moveCardToList: function (listFromId, listToId, issueId) {
|
||||
moveCardToList (listFromId, listToId, issueId) {
|
||||
const listFrom = this.findList('id', listFromId),
|
||||
listTo = this.findList('id', listToId),
|
||||
issueTo = listTo.findIssue(issueId),
|
||||
issue = listFrom.findIssue(issueId),
|
||||
issueLists = issue.getLists(),
|
||||
listLabels = issueLists.map(function (issue) {
|
||||
listLabels = issueLists.map((issue) => {
|
||||
return issue.label;
|
||||
});
|
||||
|
||||
|
@ -111,20 +114,21 @@
|
|||
}
|
||||
|
||||
if (listTo.type === 'done' && listFrom.type !== 'backlog') {
|
||||
_.each(issueLists, function (list) {
|
||||
for (let i = 0, listsLength = issueLists.length; i < listsLength; i++) {
|
||||
const list = issueLists[i];
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
}
|
||||
issue.removeLabels(listLabels);
|
||||
} else {
|
||||
listFrom.removeIssue(issue);
|
||||
}
|
||||
},
|
||||
findList: function (key, val) {
|
||||
return _.find(this.state.lists, (list) => {
|
||||
findList (key, val) {
|
||||
return this.state.lists.filter((list) => {
|
||||
return list[key] === val;
|
||||
});
|
||||
})[0];
|
||||
},
|
||||
updateFiltersUrl: function () {
|
||||
updateFiltersUrl () {
|
||||
history.pushState(null, null, `?${$.param(this.state.filters)}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -240,7 +240,7 @@
|
|||
}
|
||||
|
||||
.is-ghost {
|
||||
opacity: 0.5;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.is-dragging {
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
= render 'shared/issuable/filter', type: :boards
|
||||
|
||||
.boards-list#board-app{ "v-cloak" => true,
|
||||
":endpoint" => "'#{namespace_project_board_path(@project.namespace, @project)}'",
|
||||
":disabled" => "#{!can?(current_user, :admin_list, @project)}",
|
||||
":issue-link-base" => "'#{namespace_project_issues_path(@project.namespace, @project)}'" }
|
||||
"data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}",
|
||||
"data-disabled" => "#{!can?(current_user, :admin_list, @project)}",
|
||||
"data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" }
|
||||
.boards-app-loading.text-center{ "v-if" => "loading" }
|
||||
= icon("spinner spin")
|
||||
= render "projects/boards/components/board"
|
||||
|
|
Loading…
Reference in New Issue