Remove IIFEs in boards_bundle.js
This commit is contained in:
parent
84da388d43
commit
6c4d611772
|
@ -7,100 +7,98 @@ import boardBlankState from './board_blank_state';
|
|||
require('./board_delete');
|
||||
require('./board_list');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.Board = Vue.extend({
|
||||
template: '#js-board-template',
|
||||
components: {
|
||||
boardList,
|
||||
'board-delete': gl.issueBoards.BoardDelete,
|
||||
boardBlankState,
|
||||
},
|
||||
props: {
|
||||
list: Object,
|
||||
disabled: Boolean,
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailIssue: Store.detail,
|
||||
filter: Store.filter,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
filter: {
|
||||
handler() {
|
||||
this.list.page = 1;
|
||||
this.list.getIssues(true);
|
||||
},
|
||||
deep: true,
|
||||
gl.issueBoards.Board = Vue.extend({
|
||||
template: '#js-board-template',
|
||||
components: {
|
||||
boardList,
|
||||
'board-delete': gl.issueBoards.BoardDelete,
|
||||
boardBlankState,
|
||||
},
|
||||
props: {
|
||||
list: Object,
|
||||
disabled: Boolean,
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
detailIssue: Store.detail,
|
||||
filter: Store.filter,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
filter: {
|
||||
handler() {
|
||||
this.list.page = 1;
|
||||
this.list.getIssues(true);
|
||||
},
|
||||
detailIssue: {
|
||||
handler () {
|
||||
if (!Object.keys(this.detailIssue.issue).length) return;
|
||||
deep: true,
|
||||
},
|
||||
detailIssue: {
|
||||
handler () {
|
||||
if (!Object.keys(this.detailIssue.issue).length) return;
|
||||
|
||||
const issue = this.list.findIssue(this.detailIssue.issue.id);
|
||||
const issue = this.list.findIssue(this.detailIssue.issue.id);
|
||||
|
||||
if (issue) {
|
||||
const offsetLeft = this.$el.offsetLeft;
|
||||
const boardsList = document.querySelectorAll('.boards-list')[0];
|
||||
const left = boardsList.scrollLeft - offsetLeft;
|
||||
let right = (offsetLeft + this.$el.offsetWidth);
|
||||
if (issue) {
|
||||
const offsetLeft = this.$el.offsetLeft;
|
||||
const boardsList = document.querySelectorAll('.boards-list')[0];
|
||||
const left = boardsList.scrollLeft - offsetLeft;
|
||||
let right = (offsetLeft + this.$el.offsetWidth);
|
||||
|
||||
if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
|
||||
// -290 here because width of boardsList is animating so therefore
|
||||
// getting the width here is incorrect
|
||||
// 290 is the width of the sidebar
|
||||
right -= (boardsList.offsetWidth - 290);
|
||||
} else {
|
||||
right -= boardsList.offsetWidth;
|
||||
}
|
||||
|
||||
if (right - boardsList.scrollLeft > 0) {
|
||||
$(boardsList).animate({
|
||||
scrollLeft: right
|
||||
}, this.sortableOptions.animation);
|
||||
} else if (left > 0) {
|
||||
$(boardsList).animate({
|
||||
scrollLeft: offsetLeft
|
||||
}, this.sortableOptions.animation);
|
||||
}
|
||||
if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
|
||||
// -290 here because width of boardsList is animating so therefore
|
||||
// getting the width here is incorrect
|
||||
// 290 is the width of the sidebar
|
||||
right -= (boardsList.offsetWidth - 290);
|
||||
} else {
|
||||
right -= boardsList.offsetWidth;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showNewIssueForm() {
|
||||
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
group: 'boards',
|
||||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onEnd: (e) => {
|
||||
gl.issueBoards.onEnd();
|
||||
|
||||
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||
const order = this.sortable.toArray();
|
||||
const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
|
||||
|
||||
this.$nextTick(() => {
|
||||
Store.moveList(list, order);
|
||||
});
|
||||
if (right - boardsList.scrollLeft > 0) {
|
||||
$(boardsList).animate({
|
||||
scrollLeft: right
|
||||
}, this.sortableOptions.animation);
|
||||
} else if (left > 0) {
|
||||
$(boardsList).animate({
|
||||
scrollLeft: offsetLeft
|
||||
}, this.sortableOptions.animation);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showNewIssueForm() {
|
||||
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
group: 'boards',
|
||||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onEnd: (e) => {
|
||||
gl.issueBoards.onEnd();
|
||||
|
||||
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
|
||||
},
|
||||
});
|
||||
})();
|
||||
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||
const order = this.sortable.toArray();
|
||||
const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
|
||||
|
||||
this.$nextTick(() => {
|
||||
Store.moveList(list, order);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,22 +2,20 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardDelete = Vue.extend({
|
||||
props: {
|
||||
list: Object
|
||||
},
|
||||
methods: {
|
||||
deleteBoard () {
|
||||
$(this.$el).tooltip('hide');
|
||||
gl.issueBoards.BoardDelete = Vue.extend({
|
||||
props: {
|
||||
list: Object
|
||||
},
|
||||
methods: {
|
||||
deleteBoard () {
|
||||
$(this.$el).tooltip('hide');
|
||||
|
||||
if (confirm('Are you sure you want to delete this list?')) {
|
||||
this.list.destroy();
|
||||
}
|
||||
if (confirm('Are you sure you want to delete this list?')) {
|
||||
this.list.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,66 +8,64 @@ import Vue from 'vue';
|
|||
|
||||
require('./sidebar/remove_issue');
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardSidebar = Vue.extend({
|
||||
props: {
|
||||
currentUser: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: Store.detail,
|
||||
issue: {},
|
||||
list: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showSidebar () {
|
||||
return Object.keys(this.issue).length;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
detail: {
|
||||
handler () {
|
||||
if (this.issue.id !== this.detail.issue.id) {
|
||||
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
|
||||
$(el).data('glDropdown').clearMenu();
|
||||
});
|
||||
}
|
||||
|
||||
this.issue = this.detail.issue;
|
||||
this.list = this.detail.list;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
issue () {
|
||||
if (this.showSidebar) {
|
||||
this.$nextTick(() => {
|
||||
$('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
|
||||
$('.right-sidebar').getNiceScroll().resize();
|
||||
gl.issueBoards.BoardSidebar = Vue.extend({
|
||||
props: {
|
||||
currentUser: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: Store.detail,
|
||||
issue: {},
|
||||
list: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showSidebar () {
|
||||
return Object.keys(this.issue).length;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
detail: {
|
||||
handler () {
|
||||
if (this.issue.id !== this.detail.issue.id) {
|
||||
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
|
||||
$(el).data('glDropdown').clearMenu();
|
||||
});
|
||||
}
|
||||
|
||||
this.issue = this.detail.issue;
|
||||
this.list = this.detail.list;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
issue () {
|
||||
if (this.showSidebar) {
|
||||
this.$nextTick(() => {
|
||||
$('.right-sidebar').getNiceScroll(0).doScrollTop(0, 0);
|
||||
$('.right-sidebar').getNiceScroll().resize();
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeSidebar () {
|
||||
this.detail.issue = {};
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
new IssuableContext(this.currentUser);
|
||||
new MilestoneSelect();
|
||||
new gl.DueDateSelectors();
|
||||
new LabelsSelect();
|
||||
new Sidebar();
|
||||
gl.Subscription.bindAll('.subscription');
|
||||
},
|
||||
components: {
|
||||
removeBtn: gl.issueBoards.RemoveIssueBtn,
|
||||
},
|
||||
});
|
||||
})();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeSidebar () {
|
||||
this.detail.issue = {};
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
new IssuableContext(this.currentUser);
|
||||
new MilestoneSelect();
|
||||
new gl.DueDateSelectors();
|
||||
new LabelsSelect();
|
||||
new Sidebar();
|
||||
gl.Subscription.bindAll('.subscription');
|
||||
},
|
||||
components: {
|
||||
removeBtn: gl.issueBoards.RemoveIssueBtn,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,141 +1,139 @@
|
|||
import Vue from 'vue';
|
||||
import eventHub from '../eventhub';
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.IssueCardInner = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
updateFilters: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
gl.issueBoards.IssueCardInner = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
cardUrl() {
|
||||
return `${this.issueLinkBase}/${this.issue.id}`;
|
||||
},
|
||||
assigneeUrl() {
|
||||
return `${this.rootPath}${this.issue.assignee.username}`;
|
||||
},
|
||||
assigneeUrlTitle() {
|
||||
return `Assigned to ${this.issue.assignee.name}`;
|
||||
},
|
||||
avatarUrlTitle() {
|
||||
return `Avatar for ${this.issue.assignee.name}`;
|
||||
},
|
||||
issueId() {
|
||||
return `#${this.issue.id}`;
|
||||
},
|
||||
showLabelFooter() {
|
||||
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
showLabel(label) {
|
||||
if (!this.list) return true;
|
||||
|
||||
return !this.list.label || label.id !== this.list.label.id;
|
||||
},
|
||||
filterByLabel(label, e) {
|
||||
if (!this.updateFilters) return;
|
||||
|
||||
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
|
||||
const labelTitle = encodeURIComponent(label.title);
|
||||
const param = `label_name[]=${labelTitle}`;
|
||||
const labelIndex = filterPath.indexOf(param);
|
||||
$(e.currentTarget).tooltip('hide');
|
||||
|
||||
if (labelIndex === -1) {
|
||||
filterPath.push(param);
|
||||
} else {
|
||||
filterPath.splice(labelIndex, 1);
|
||||
}
|
||||
|
||||
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
|
||||
|
||||
Store.updateFiltersUrl();
|
||||
|
||||
eventHub.$emit('updateTokens');
|
||||
},
|
||||
labelStyle(label) {
|
||||
return {
|
||||
backgroundColor: label.color,
|
||||
color: label.textColor,
|
||||
};
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">
|
||||
<i
|
||||
class="fa fa-eye-slash confidential-icon"
|
||||
v-if="issue.confidential"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
class="js-no-trigger"
|
||||
:href="cardUrl"
|
||||
:title="issue.title">{{ issue.title }}</a>
|
||||
<span
|
||||
class="card-number"
|
||||
v-if="issue.id"
|
||||
>
|
||||
{{ issueId }}
|
||||
</span>
|
||||
</h4>
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
updateFilters: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cardUrl() {
|
||||
return `${this.issueLinkBase}/${this.issue.id}`;
|
||||
},
|
||||
assigneeUrl() {
|
||||
return `${this.rootPath}${this.issue.assignee.username}`;
|
||||
},
|
||||
assigneeUrlTitle() {
|
||||
return `Assigned to ${this.issue.assignee.name}`;
|
||||
},
|
||||
avatarUrlTitle() {
|
||||
return `Avatar for ${this.issue.assignee.name}`;
|
||||
},
|
||||
issueId() {
|
||||
return `#${this.issue.id}`;
|
||||
},
|
||||
showLabelFooter() {
|
||||
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showLabel(label) {
|
||||
if (!this.list) return true;
|
||||
|
||||
return !this.list.label || label.id !== this.list.label.id;
|
||||
},
|
||||
filterByLabel(label, e) {
|
||||
if (!this.updateFilters) return;
|
||||
|
||||
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
|
||||
const labelTitle = encodeURIComponent(label.title);
|
||||
const param = `label_name[]=${labelTitle}`;
|
||||
const labelIndex = filterPath.indexOf(param);
|
||||
$(e.currentTarget).tooltip('hide');
|
||||
|
||||
if (labelIndex === -1) {
|
||||
filterPath.push(param);
|
||||
} else {
|
||||
filterPath.splice(labelIndex, 1);
|
||||
}
|
||||
|
||||
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
|
||||
|
||||
Store.updateFiltersUrl();
|
||||
|
||||
eventHub.$emit('updateTokens');
|
||||
},
|
||||
labelStyle(label) {
|
||||
return {
|
||||
backgroundColor: label.color,
|
||||
color: label.textColor,
|
||||
};
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">
|
||||
<i
|
||||
class="fa fa-eye-slash confidential-icon"
|
||||
v-if="issue.confidential"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
class="card-assignee has-tooltip js-no-trigger"
|
||||
:href="assigneeUrl"
|
||||
:title="assigneeUrlTitle"
|
||||
v-if="issue.assignee"
|
||||
data-container="body"
|
||||
class="js-no-trigger"
|
||||
:href="cardUrl"
|
||||
:title="issue.title">{{ issue.title }}</a>
|
||||
<span
|
||||
class="card-number"
|
||||
v-if="issue.id"
|
||||
>
|
||||
<img
|
||||
class="avatar avatar-inline s20 js-no-trigger"
|
||||
:src="issue.assignee.avatar"
|
||||
width="20"
|
||||
height="20"
|
||||
:alt="avatarUrlTitle"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer" v-if="showLabelFooter">
|
||||
<button
|
||||
class="label color-label has-tooltip js-no-trigger"
|
||||
v-for="label in issue.labels"
|
||||
type="button"
|
||||
v-if="showLabel(label)"
|
||||
@click="filterByLabel(label, $event)"
|
||||
:style="labelStyle(label)"
|
||||
:title="label.description"
|
||||
data-container="body">
|
||||
{{ label.title }}
|
||||
</button>
|
||||
</div>
|
||||
{{ issueId }}
|
||||
</span>
|
||||
</h4>
|
||||
<a
|
||||
class="card-assignee has-tooltip js-no-trigger"
|
||||
:href="assigneeUrl"
|
||||
:title="assigneeUrlTitle"
|
||||
v-if="issue.assignee"
|
||||
data-container="body"
|
||||
>
|
||||
<img
|
||||
class="avatar avatar-inline s20 js-no-trigger"
|
||||
:src="issue.assignee.avatar"
|
||||
width="20"
|
||||
height="20"
|
||||
:alt="avatarUrlTitle"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
<div class="card-footer" v-if="showLabelFooter">
|
||||
<button
|
||||
class="label color-label has-tooltip js-no-trigger"
|
||||
v-for="label in issue.labels"
|
||||
type="button"
|
||||
v-if="showLabel(label)"
|
||||
@click="filterByLabel(label, $event)"
|
||||
:style="labelStyle(label)"
|
||||
:title="label.description"
|
||||
data-container="body">
|
||||
{{ label.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,71 +1,69 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalEmptyState = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
gl.issueBoards.ModalEmptyState = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
contents() {
|
||||
const obj = {
|
||||
title: 'You haven\'t added any issues to your project yet',
|
||||
content: `
|
||||
An issue can be a bug, a todo or a feature request that needs to be
|
||||
discussed in a project. Besides, issues are searchable and filterable.
|
||||
`,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
contents() {
|
||||
const obj = {
|
||||
title: 'You haven\'t added any issues to your project yet',
|
||||
content: `
|
||||
An issue can be a bug, a todo or a feature request that needs to be
|
||||
discussed in a project. Besides, issues are searchable and filterable.
|
||||
`,
|
||||
};
|
||||
|
||||
if (this.activeTab === 'selected') {
|
||||
obj.title = 'You haven\'t selected any issues yet';
|
||||
obj.content = `
|
||||
Go back to <strong>Open issues</strong> and select some issues
|
||||
to add to your board.
|
||||
`;
|
||||
}
|
||||
if (this.activeTab === 'selected') {
|
||||
obj.title = 'You haven\'t selected any issues yet';
|
||||
obj.content = `
|
||||
Go back to <strong>Open issues</strong> and select some issues
|
||||
to add to your board.
|
||||
`;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
return obj;
|
||||
},
|
||||
template: `
|
||||
<section class="empty-state">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-sm-push-6">
|
||||
<aside class="svg-content" v-html="image"></aside>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-sm-pull-6">
|
||||
<div class="text-content">
|
||||
<h4>{{ contents.title }}</h4>
|
||||
<p v-html="contents.content"></p>
|
||||
<a
|
||||
:href="newIssuePath"
|
||||
class="btn btn-success btn-inverted"
|
||||
v-if="activeTab === 'all'">
|
||||
New issue
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
@click="changeTab('all')"
|
||||
v-if="activeTab === 'selected'">
|
||||
Open issues
|
||||
</button>
|
||||
</div>
|
||||
},
|
||||
template: `
|
||||
<section class="empty-state">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-sm-push-6">
|
||||
<aside class="svg-content" v-html="image"></aside>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-sm-pull-6">
|
||||
<div class="text-content">
|
||||
<h4>{{ contents.title }}</h4>
|
||||
<p v-html="contents.content"></p>
|
||||
<a
|
||||
:href="newIssuePath"
|
||||
class="btn btn-success btn-inverted"
|
||||
v-if="activeTab === 'all'">
|
||||
New issue
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
@click="changeTab('all')"
|
||||
v-if="activeTab === 'selected'">
|
||||
Open issues
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -5,80 +5,78 @@ import Vue from 'vue';
|
|||
|
||||
require('./lists_dropdown');
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalFooter = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
gl.issueBoards.ModalFooter = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
submitDisabled() {
|
||||
return !ModalStore.selectedCount();
|
||||
},
|
||||
computed: {
|
||||
submitDisabled() {
|
||||
return !ModalStore.selectedCount();
|
||||
},
|
||||
submitText() {
|
||||
const count = ModalStore.selectedCount();
|
||||
submitText() {
|
||||
const count = ModalStore.selectedCount();
|
||||
|
||||
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
|
||||
},
|
||||
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
|
||||
},
|
||||
methods: {
|
||||
addIssues() {
|
||||
const list = this.modal.selectedList || this.state.lists[0];
|
||||
const selectedIssues = ModalStore.getSelectedIssues();
|
||||
const issueIds = selectedIssues.map(issue => issue.globalId);
|
||||
},
|
||||
methods: {
|
||||
addIssues() {
|
||||
const list = this.modal.selectedList || this.state.lists[0];
|
||||
const selectedIssues = ModalStore.getSelectedIssues();
|
||||
const issueIds = selectedIssues.map(issue => issue.globalId);
|
||||
|
||||
// Post the data to the backend
|
||||
gl.boardService.bulkUpdate(issueIds, {
|
||||
add_label_ids: [list.label.id],
|
||||
}).catch(() => {
|
||||
new Flash('Failed to update issues, please try again.', 'alert');
|
||||
// Post the data to the backend
|
||||
gl.boardService.bulkUpdate(issueIds, {
|
||||
add_label_ids: [list.label.id],
|
||||
}).catch(() => {
|
||||
new Flash('Failed to update issues, please try again.', 'alert');
|
||||
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.removeIssue(issue);
|
||||
list.issuesSize -= 1;
|
||||
});
|
||||
});
|
||||
|
||||
// Add the issues on the frontend
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.addIssue(issue);
|
||||
list.issuesSize += 1;
|
||||
list.removeIssue(issue);
|
||||
list.issuesSize -= 1;
|
||||
});
|
||||
});
|
||||
|
||||
this.toggleModal(false);
|
||||
},
|
||||
// Add the issues on the frontend
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.addIssue(issue);
|
||||
list.issuesSize += 1;
|
||||
});
|
||||
|
||||
this.toggleModal(false);
|
||||
},
|
||||
components: {
|
||||
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
|
||||
},
|
||||
template: `
|
||||
<footer
|
||||
class="form-actions add-issues-footer">
|
||||
<div class="pull-left">
|
||||
<button
|
||||
class="btn btn-success"
|
||||
type="button"
|
||||
:disabled="submitDisabled"
|
||||
@click="addIssues">
|
||||
{{ submitText }}
|
||||
</button>
|
||||
<span class="inline add-issues-footer-to-list">
|
||||
to list
|
||||
</span>
|
||||
<lists-dropdown></lists-dropdown>
|
||||
</div>
|
||||
},
|
||||
components: {
|
||||
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
|
||||
},
|
||||
template: `
|
||||
<footer
|
||||
class="form-actions add-issues-footer">
|
||||
<div class="pull-left">
|
||||
<button
|
||||
class="btn btn-default pull-right"
|
||||
class="btn btn-success"
|
||||
type="button"
|
||||
@click="toggleModal(false)">
|
||||
Cancel
|
||||
:disabled="submitDisabled"
|
||||
@click="addIssues">
|
||||
{{ submitText }}
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
<span class="inline add-issues-footer-to-list">
|
||||
to list
|
||||
</span>
|
||||
<lists-dropdown></lists-dropdown>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-default pull-right"
|
||||
type="button"
|
||||
@click="toggleModal(false)">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -3,80 +3,78 @@ import modalFilters from './filters';
|
|||
|
||||
require('./tabs');
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalHeader = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
props: {
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
gl.issueBoards.ModalHeader = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
props: {
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
selectAllText() {
|
||||
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
|
||||
return 'Select all';
|
||||
}
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
computed: {
|
||||
selectAllText() {
|
||||
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
|
||||
return 'Select all';
|
||||
}
|
||||
|
||||
return 'Deselect all';
|
||||
},
|
||||
showSearch() {
|
||||
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
|
||||
},
|
||||
return 'Deselect all';
|
||||
},
|
||||
methods: {
|
||||
toggleAll() {
|
||||
this.$refs.selectAllBtn.blur();
|
||||
showSearch() {
|
||||
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleAll() {
|
||||
this.$refs.selectAllBtn.blur();
|
||||
|
||||
ModalStore.toggleAll();
|
||||
},
|
||||
ModalStore.toggleAll();
|
||||
},
|
||||
components: {
|
||||
'modal-tabs': gl.issueBoards.ModalTabs,
|
||||
modalFilters,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<header class="add-issues-header form-actions">
|
||||
<h2>
|
||||
Add issues
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
@click="toggleModal(false)">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</h2>
|
||||
</header>
|
||||
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
|
||||
<div
|
||||
class="add-issues-search append-bottom-10"
|
||||
v-if="showSearch">
|
||||
<modal-filters :store="filter" />
|
||||
},
|
||||
components: {
|
||||
'modal-tabs': gl.issueBoards.ModalTabs,
|
||||
modalFilters,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<header class="add-issues-header form-actions">
|
||||
<h2>
|
||||
Add issues
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success btn-inverted prepend-left-10"
|
||||
ref="selectAllBtn"
|
||||
@click="toggleAll">
|
||||
{{ selectAllText }}
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
@click="toggleModal(false)">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</h2>
|
||||
</header>
|
||||
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
|
||||
<div
|
||||
class="add-issues-search append-bottom-10"
|
||||
v-if="showSearch">
|
||||
<modal-filters :store="filter" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success btn-inverted prepend-left-10"
|
||||
ref="selectAllBtn"
|
||||
@click="toggleAll">
|
||||
{{ selectAllText }}
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -8,160 +8,158 @@ require('./list');
|
|||
require('./footer');
|
||||
require('./empty_state');
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.IssuesModal = Vue.extend({
|
||||
props: {
|
||||
blankStateImage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
gl.issueBoards.IssuesModal = Vue.extend({
|
||||
props: {
|
||||
blankStateImage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
watch: {
|
||||
page() {
|
||||
this.loadIssues();
|
||||
},
|
||||
showAddIssuesModal() {
|
||||
if (this.showAddIssuesModal && !this.issues.length) {
|
||||
this.loading = true;
|
||||
|
||||
this.loadIssues()
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (!this.showAddIssuesModal) {
|
||||
this.issues = [];
|
||||
this.selectedIssues = [];
|
||||
this.issuesCount = false;
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
handler() {
|
||||
if (this.$el.tagName) {
|
||||
this.page = 1;
|
||||
this.filterLoading = true;
|
||||
|
||||
this.loadIssues(true)
|
||||
.then(() => {
|
||||
this.filterLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
loadIssues(clearIssues = false) {
|
||||
if (!this.showAddIssuesModal) return false;
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
watch: {
|
||||
page() {
|
||||
this.loadIssues();
|
||||
},
|
||||
showAddIssuesModal() {
|
||||
if (this.showAddIssuesModal && !this.issues.length) {
|
||||
this.loading = true;
|
||||
|
||||
return gl.boardService.getBacklog(queryData(this.filter.path, {
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
})).then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
if (clearIssues) {
|
||||
this.issues = [];
|
||||
}
|
||||
|
||||
data.issues.forEach((issueObj) => {
|
||||
const issue = new ListIssue(issueObj);
|
||||
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
|
||||
issue.selected = !!foundSelectedIssue;
|
||||
|
||||
this.issues.push(issue);
|
||||
this.loadIssues()
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (!this.showAddIssuesModal) {
|
||||
this.issues = [];
|
||||
this.selectedIssues = [];
|
||||
this.issuesCount = false;
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
handler() {
|
||||
if (this.$el.tagName) {
|
||||
this.page = 1;
|
||||
this.filterLoading = true;
|
||||
|
||||
this.loadingNewPage = false;
|
||||
this.loadIssues(true)
|
||||
.then(() => {
|
||||
this.filterLoading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadIssues(clearIssues = false) {
|
||||
if (!this.showAddIssuesModal) return false;
|
||||
|
||||
if (!this.issuesCount) {
|
||||
this.issuesCount = data.size;
|
||||
}
|
||||
return gl.boardService.getBacklog(queryData(this.filter.path, {
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
})).then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
if (clearIssues) {
|
||||
this.issues = [];
|
||||
}
|
||||
|
||||
data.issues.forEach((issueObj) => {
|
||||
const issue = new ListIssue(issueObj);
|
||||
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
|
||||
issue.selected = !!foundSelectedIssue;
|
||||
|
||||
this.issues.push(issue);
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showList() {
|
||||
if (this.activeTab === 'selected') {
|
||||
return this.selectedIssues.length > 0;
|
||||
}
|
||||
|
||||
return this.issuesCount > 0;
|
||||
},
|
||||
showEmptyState() {
|
||||
if (!this.loading && this.issuesCount === 0) {
|
||||
return true;
|
||||
}
|
||||
this.loadingNewPage = false;
|
||||
|
||||
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
|
||||
},
|
||||
if (!this.issuesCount) {
|
||||
this.issuesCount = data.size;
|
||||
}
|
||||
});
|
||||
},
|
||||
created() {
|
||||
this.page = 1;
|
||||
},
|
||||
computed: {
|
||||
showList() {
|
||||
if (this.activeTab === 'selected') {
|
||||
return this.selectedIssues.length > 0;
|
||||
}
|
||||
|
||||
return this.issuesCount > 0;
|
||||
},
|
||||
components: {
|
||||
'modal-header': gl.issueBoards.ModalHeader,
|
||||
'modal-list': gl.issueBoards.ModalList,
|
||||
'modal-footer': gl.issueBoards.ModalFooter,
|
||||
'empty-state': gl.issueBoards.ModalEmptyState,
|
||||
showEmptyState() {
|
||||
if (!this.loading && this.issuesCount === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="add-issues-modal"
|
||||
v-if="showAddIssuesModal">
|
||||
<div class="add-issues-container">
|
||||
<modal-header
|
||||
:project-id="projectId"
|
||||
:milestone-path="milestonePath"
|
||||
:label-path="labelPath">
|
||||
</modal-header>
|
||||
<modal-list
|
||||
:image="blankStateImage"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"
|
||||
v-if="!loading && showList && !filterLoading"></modal-list>
|
||||
<empty-state
|
||||
v-if="showEmptyState"
|
||||
:image="blankStateImage"
|
||||
:new-issue-path="newIssuePath"></empty-state>
|
||||
<section
|
||||
class="add-issues-list text-center"
|
||||
v-if="loading || filterLoading">
|
||||
<div class="add-issues-list-loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</section>
|
||||
<modal-footer></modal-footer>
|
||||
</div>
|
||||
},
|
||||
created() {
|
||||
this.page = 1;
|
||||
},
|
||||
components: {
|
||||
'modal-header': gl.issueBoards.ModalHeader,
|
||||
'modal-list': gl.issueBoards.ModalList,
|
||||
'modal-footer': gl.issueBoards.ModalFooter,
|
||||
'empty-state': gl.issueBoards.ModalEmptyState,
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="add-issues-modal"
|
||||
v-if="showAddIssuesModal">
|
||||
<div class="add-issues-container">
|
||||
<modal-header
|
||||
:project-id="projectId"
|
||||
:milestone-path="milestonePath"
|
||||
:label-path="labelPath">
|
||||
</modal-header>
|
||||
<modal-list
|
||||
:image="blankStateImage"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"
|
||||
v-if="!loading && showList && !filterLoading"></modal-list>
|
||||
<empty-state
|
||||
v-if="showEmptyState"
|
||||
:image="blankStateImage"
|
||||
:new-issue-path="newIssuePath"></empty-state>
|
||||
<section
|
||||
class="add-issues-list text-center"
|
||||
v-if="loading || filterLoading">
|
||||
<div class="add-issues-list-loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</section>
|
||||
<modal-footer></modal-footer>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -3,159 +3,157 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalList = Vue.extend({
|
||||
props: {
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
gl.issueBoards.ModalList = Vue.extend({
|
||||
props: {
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
if (this.activeTab === 'all') {
|
||||
ModalStore.purgeUnselectedIssues();
|
||||
}
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
loopIssues() {
|
||||
if (this.activeTab === 'all') {
|
||||
return this.issues;
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
if (this.activeTab === 'all') {
|
||||
ModalStore.purgeUnselectedIssues();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
loopIssues() {
|
||||
if (this.activeTab === 'all') {
|
||||
return this.issues;
|
||||
}
|
||||
|
||||
return this.selectedIssues;
|
||||
},
|
||||
groupedIssues() {
|
||||
const groups = [];
|
||||
this.loopIssues.forEach((issue, i) => {
|
||||
const index = i % this.columns;
|
||||
|
||||
if (!groups[index]) {
|
||||
groups.push([]);
|
||||
}
|
||||
|
||||
return this.selectedIssues;
|
||||
},
|
||||
groupedIssues() {
|
||||
const groups = [];
|
||||
this.loopIssues.forEach((issue, i) => {
|
||||
const index = i % this.columns;
|
||||
groups[index].push(issue);
|
||||
});
|
||||
|
||||
if (!groups[index]) {
|
||||
groups.push([]);
|
||||
}
|
||||
|
||||
groups[index].push(issue);
|
||||
});
|
||||
|
||||
return groups;
|
||||
},
|
||||
return groups;
|
||||
},
|
||||
methods: {
|
||||
scrollHandler() {
|
||||
const currentPage = Math.floor(this.issues.length / this.perPage);
|
||||
},
|
||||
methods: {
|
||||
scrollHandler() {
|
||||
const currentPage = Math.floor(this.issues.length / this.perPage);
|
||||
|
||||
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
|
||||
&& currentPage === this.page) {
|
||||
this.loadingNewPage = true;
|
||||
this.page += 1;
|
||||
}
|
||||
},
|
||||
toggleIssue(e, issue) {
|
||||
if (e.target.tagName !== 'A') {
|
||||
ModalStore.toggleIssue(issue);
|
||||
}
|
||||
},
|
||||
listHeight() {
|
||||
return this.$refs.list.getBoundingClientRect().height;
|
||||
},
|
||||
scrollHeight() {
|
||||
return this.$refs.list.scrollHeight;
|
||||
},
|
||||
scrollTop() {
|
||||
return this.$refs.list.scrollTop + this.listHeight();
|
||||
},
|
||||
showIssue(issue) {
|
||||
if (this.activeTab === 'all') return true;
|
||||
|
||||
const index = ModalStore.selectedIssueIndex(issue);
|
||||
|
||||
return index !== -1;
|
||||
},
|
||||
setColumnCount() {
|
||||
const breakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (breakpoint === 'lg' || breakpoint === 'md') {
|
||||
this.columns = 3;
|
||||
} else if (breakpoint === 'sm') {
|
||||
this.columns = 2;
|
||||
} else {
|
||||
this.columns = 1;
|
||||
}
|
||||
},
|
||||
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
|
||||
&& currentPage === this.page) {
|
||||
this.loadingNewPage = true;
|
||||
this.page += 1;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
|
||||
this.setColumnCountWrapper = this.setColumnCount.bind(this);
|
||||
this.setColumnCount();
|
||||
toggleIssue(e, issue) {
|
||||
if (e.target.tagName !== 'A') {
|
||||
ModalStore.toggleIssue(issue);
|
||||
}
|
||||
},
|
||||
listHeight() {
|
||||
return this.$refs.list.getBoundingClientRect().height;
|
||||
},
|
||||
scrollHeight() {
|
||||
return this.$refs.list.scrollHeight;
|
||||
},
|
||||
scrollTop() {
|
||||
return this.$refs.list.scrollTop + this.listHeight();
|
||||
},
|
||||
showIssue(issue) {
|
||||
if (this.activeTab === 'all') return true;
|
||||
|
||||
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.addEventListener('resize', this.setColumnCountWrapper);
|
||||
const index = ModalStore.selectedIssueIndex(issue);
|
||||
|
||||
return index !== -1;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.removeEventListener('resize', this.setColumnCountWrapper);
|
||||
setColumnCount() {
|
||||
const breakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (breakpoint === 'lg' || breakpoint === 'md') {
|
||||
this.columns = 3;
|
||||
} else if (breakpoint === 'sm') {
|
||||
this.columns = 2;
|
||||
} else {
|
||||
this.columns = 1;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
template: `
|
||||
<section
|
||||
class="add-issues-list add-issues-list-columns"
|
||||
ref="list">
|
||||
},
|
||||
mounted() {
|
||||
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
|
||||
this.setColumnCountWrapper = this.setColumnCount.bind(this);
|
||||
this.setColumnCount();
|
||||
|
||||
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.addEventListener('resize', this.setColumnCountWrapper);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.removeEventListener('resize', this.setColumnCountWrapper);
|
||||
},
|
||||
components: {
|
||||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
template: `
|
||||
<section
|
||||
class="add-issues-list add-issues-list-columns"
|
||||
ref="list">
|
||||
<div
|
||||
class="empty-state add-issues-empty-state-filter text-center"
|
||||
v-if="issuesCount > 0 && issues.length === 0">
|
||||
<div
|
||||
class="empty-state add-issues-empty-state-filter text-center"
|
||||
v-if="issuesCount > 0 && issues.length === 0">
|
||||
class="svg-content"
|
||||
v-html="image">
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h4>
|
||||
There are no issues to show.
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="group in groupedIssues"
|
||||
class="add-issues-list-column">
|
||||
<div
|
||||
v-for="issue in group"
|
||||
v-if="showIssue(issue)"
|
||||
class="card-parent">
|
||||
<div
|
||||
class="svg-content"
|
||||
v-html="image">
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h4>
|
||||
There are no issues to show.
|
||||
</h4>
|
||||
class="card"
|
||||
:class="{ 'is-active': issue.selected }"
|
||||
@click="toggleIssue($event, issue)">
|
||||
<issue-card-inner
|
||||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath">
|
||||
</issue-card-inner>
|
||||
<span
|
||||
:aria-label="'Issue #' + issue.id + ' selected'"
|
||||
aria-checked="true"
|
||||
v-if="issue.selected"
|
||||
class="issue-card-selected text-center">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="group in groupedIssues"
|
||||
class="add-issues-list-column">
|
||||
<div
|
||||
v-for="issue in group"
|
||||
v-if="showIssue(issue)"
|
||||
class="card-parent">
|
||||
<div
|
||||
class="card"
|
||||
:class="{ 'is-active': issue.selected }"
|
||||
@click="toggleIssue($event, issue)">
|
||||
<issue-card-inner
|
||||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath">
|
||||
</issue-card-inner>
|
||||
<span
|
||||
:aria-label="'Issue #' + issue.id + ' selected'"
|
||||
aria-checked="true"
|
||||
v-if="issue.selected"
|
||||
class="issue-card-selected text-center">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,57 +1,55 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.modal.selectedList || this.state.lists[0];
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.modal.selectedList || this.state.lists[0];
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.modal.selectedList = null;
|
||||
},
|
||||
template: `
|
||||
<div class="dropdown inline">
|
||||
<button
|
||||
class="dropdown-menu-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: selected.label.color }">
|
||||
</span>
|
||||
{{ selected.title }}
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
|
||||
<ul>
|
||||
<li
|
||||
v-for="list in state.lists"
|
||||
v-if="list.type == 'label'">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
:class="{ 'is-active': list.id == selected.id }"
|
||||
@click.prevent="modal.selectedList = list">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: list.label.color }">
|
||||
</span>
|
||||
{{ list.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
},
|
||||
destroyed() {
|
||||
this.modal.selectedList = null;
|
||||
},
|
||||
template: `
|
||||
<div class="dropdown inline">
|
||||
<button
|
||||
class="dropdown-menu-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: selected.label.color }">
|
||||
</span>
|
||||
{{ selected.title }}
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
|
||||
<ul>
|
||||
<li
|
||||
v-for="list in state.lists"
|
||||
v-if="list.type == 'label'">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
:class="{ 'is-active': list.id == selected.id }"
|
||||
@click.prevent="modal.selectedList = list">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: list.label.color }">
|
||||
</span>
|
||||
{{ list.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,48 +1,46 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalTabs = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
gl.issueBoards.ModalTabs = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
computed: {
|
||||
selectedCount() {
|
||||
return ModalStore.selectedCount();
|
||||
},
|
||||
computed: {
|
||||
selectedCount() {
|
||||
return ModalStore.selectedCount();
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.activeTab = 'all';
|
||||
},
|
||||
template: `
|
||||
<div class="top-area prepend-top-10 append-bottom-10">
|
||||
<ul class="nav-links issues-state-filters">
|
||||
<li :class="{ 'active': activeTab == 'all' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('all')">
|
||||
Open issues
|
||||
<span class="badge">
|
||||
{{ issuesCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ 'active': activeTab == 'selected' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('selected')">
|
||||
Selected issues
|
||||
<span class="badge">
|
||||
{{ selectedCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
},
|
||||
destroyed() {
|
||||
this.activeTab = 'all';
|
||||
},
|
||||
template: `
|
||||
<div class="top-area prepend-top-10 append-bottom-10">
|
||||
<ul class="nav-links issues-state-filters">
|
||||
<li :class="{ 'active': activeTab == 'all' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('all')">
|
||||
Open issues
|
||||
<span class="badge">
|
||||
{{ issuesCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ 'active': activeTab == 'selected' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('selected')">
|
||||
Selected issues
|
||||
<span class="badge">
|
||||
{{ selectedCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,76 +1,74 @@
|
|||
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
$(document).off('created.label').on('created.label', (e, label) => {
|
||||
Store.new({
|
||||
$(document).off('created.label').on('created.label', (e, label) => {
|
||||
Store.new({
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
color: label.color
|
||||
color: label.color
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gl.issueBoards.newListDropdownInit = () => {
|
||||
$('.js-new-board-list').each(function () {
|
||||
const $this = $(this);
|
||||
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
|
||||
|
||||
$this.glDropdown({
|
||||
data(term, callback) {
|
||||
$.get($this.attr('data-labels'))
|
||||
.then((resp) => {
|
||||
callback(resp);
|
||||
});
|
||||
},
|
||||
renderRow (label) {
|
||||
const active = Store.findList('title', label.title);
|
||||
const $li = $('<li />');
|
||||
const $a = $('<a />', {
|
||||
class: (active ? `is-active js-board-list-${active.id}` : ''),
|
||||
text: label.title,
|
||||
href: '#'
|
||||
});
|
||||
const $labelColor = $('<span />', {
|
||||
class: 'dropdown-label-box',
|
||||
style: `background-color: ${label.color}`
|
||||
});
|
||||
|
||||
return $li.append($a.prepend($labelColor));
|
||||
},
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
multiSelect: true,
|
||||
clicked (label, $el, e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!Store.findList('title', label.title)) {
|
||||
Store.new({
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
color: label.color
|
||||
}
|
||||
});
|
||||
|
||||
Store.state.lists = _.sortBy(Store.state.lists, 'position');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
gl.issueBoards.newListDropdownInit = () => {
|
||||
$('.js-new-board-list').each(function () {
|
||||
const $this = $(this);
|
||||
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
|
||||
|
||||
$this.glDropdown({
|
||||
data(term, callback) {
|
||||
$.get($this.attr('data-labels'))
|
||||
.then((resp) => {
|
||||
callback(resp);
|
||||
});
|
||||
},
|
||||
renderRow (label) {
|
||||
const active = Store.findList('title', label.title);
|
||||
const $li = $('<li />');
|
||||
const $a = $('<a />', {
|
||||
class: (active ? `is-active js-board-list-${active.id}` : ''),
|
||||
text: label.title,
|
||||
href: '#'
|
||||
});
|
||||
const $labelColor = $('<span />', {
|
||||
class: 'dropdown-label-box',
|
||||
style: `background-color: ${label.color}`
|
||||
});
|
||||
|
||||
return $li.append($a.prepend($labelColor));
|
||||
},
|
||||
search: {
|
||||
fields: ['title']
|
||||
},
|
||||
filterable: true,
|
||||
selectable: true,
|
||||
multiSelect: true,
|
||||
clicked (label, $el, e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!Store.findList('title', label.title)) {
|
||||
Store.new({
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
color: label.color
|
||||
}
|
||||
});
|
||||
|
||||
Store.state.lists = _.sortBy(Store.state.lists, 'position');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
})();
|
||||
};
|
||||
|
|
|
@ -3,59 +3,57 @@
|
|||
|
||||
import Vue from 'vue';
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
removeIssue() {
|
||||
const issue = this.issue;
|
||||
const lists = issue.getLists();
|
||||
const labelIds = lists.map(list => list.label.id);
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeIssue() {
|
||||
const issue = this.issue;
|
||||
const lists = issue.getLists();
|
||||
const labelIds = lists.map(list => list.label.id);
|
||||
|
||||
// Post the remove data
|
||||
gl.boardService.bulkUpdate([issue.globalId], {
|
||||
remove_label_ids: labelIds,
|
||||
}).catch(() => {
|
||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||
// Post the remove data
|
||||
gl.boardService.bulkUpdate([issue.globalId], {
|
||||
remove_label_ids: labelIds,
|
||||
}).catch(() => {
|
||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||
|
||||
lists.forEach((list) => {
|
||||
list.addIssue(issue);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove from the frontend store
|
||||
lists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
list.addIssue(issue);
|
||||
});
|
||||
});
|
||||
|
||||
Store.detail.issue = {};
|
||||
},
|
||||
// Remove from the frontend store
|
||||
lists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
|
||||
Store.detail.issue = {};
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="block list"
|
||||
v-if="list.type !== 'closed'">
|
||||
<button
|
||||
class="btn btn-default btn-block"
|
||||
type="button"
|
||||
@click="removeIssue">
|
||||
Remove from board
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="block list"
|
||||
v-if="list.type !== 'closed'">
|
||||
<button
|
||||
class="btn btn-default btn-block"
|
||||
type="button"
|
||||
@click="removeIssue">
|
||||
Remove from board
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalMixins = {
|
||||
methods: {
|
||||
toggleModal(toggle) {
|
||||
ModalStore.store.showAddIssuesModal = toggle;
|
||||
},
|
||||
changeTab(tab) {
|
||||
ModalStore.store.activeTab = tab;
|
||||
},
|
||||
gl.issueBoards.ModalMixins = {
|
||||
methods: {
|
||||
toggleModal(toggle) {
|
||||
ModalStore.store.showAddIssuesModal = toggle;
|
||||
},
|
||||
};
|
||||
})();
|
||||
changeTab(tab) {
|
||||
ModalStore.store.activeTab = tab;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,39 +1,37 @@
|
|||
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
|
||||
/* global DocumentTouch */
|
||||
|
||||
((w) => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.onStart = () => {
|
||||
$('.has-tooltip').tooltip('hide')
|
||||
.tooltip('disable');
|
||||
document.body.classList.add('is-dragging');
|
||||
gl.issueBoards.onStart = () => {
|
||||
$('.has-tooltip').tooltip('hide')
|
||||
.tooltip('disable');
|
||||
document.body.classList.add('is-dragging');
|
||||
};
|
||||
|
||||
gl.issueBoards.onEnd = () => {
|
||||
$('.has-tooltip').tooltip('enable');
|
||||
document.body.classList.remove('is-dragging');
|
||||
};
|
||||
|
||||
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
|
||||
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||
const defaultSortOptions = {
|
||||
animation: 200,
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.board-delete, .btn',
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
onStart: gl.issueBoards.onStart,
|
||||
onEnd: gl.issueBoards.onEnd
|
||||
};
|
||||
|
||||
gl.issueBoards.onEnd = () => {
|
||||
$('.has-tooltip').tooltip('enable');
|
||||
document.body.classList.remove('is-dragging');
|
||||
};
|
||||
|
||||
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
|
||||
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||
const defaultSortOptions = {
|
||||
animation: 200,
|
||||
forceFallback: true,
|
||||
fallbackClass: 'is-dragging',
|
||||
fallbackOnBody: true,
|
||||
ghostClass: 'is-ghost',
|
||||
filter: '.board-delete, .btn',
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
onStart: gl.issueBoards.onStart,
|
||||
onEnd: gl.issueBoards.onEnd
|
||||
};
|
||||
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
return defaultSortOptions;
|
||||
};
|
||||
})(window);
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
return defaultSortOptions;
|
||||
};
|
||||
|
|
|
@ -3,125 +3,123 @@
|
|||
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardsStore = {
|
||||
disabled: false,
|
||||
filter: {
|
||||
path: '',
|
||||
},
|
||||
state: {},
|
||||
detail: {
|
||||
issue: {}
|
||||
},
|
||||
moving: {
|
||||
issue: {},
|
||||
list: {}
|
||||
},
|
||||
create () {
|
||||
this.state.lists = [];
|
||||
this.filter.path = gl.utils.getUrlParamsArray().join('&');
|
||||
},
|
||||
addList (listObj) {
|
||||
const list = new List(listObj);
|
||||
this.state.lists.push(list);
|
||||
gl.issueBoards.BoardsStore = {
|
||||
disabled: false,
|
||||
filter: {
|
||||
path: '',
|
||||
},
|
||||
state: {},
|
||||
detail: {
|
||||
issue: {}
|
||||
},
|
||||
moving: {
|
||||
issue: {},
|
||||
list: {}
|
||||
},
|
||||
create () {
|
||||
this.state.lists = [];
|
||||
this.filter.path = gl.utils.getUrlParamsArray().join('&');
|
||||
},
|
||||
addList (listObj) {
|
||||
const list = new List(listObj);
|
||||
this.state.lists.push(list);
|
||||
|
||||
return list;
|
||||
},
|
||||
new (listObj) {
|
||||
const list = this.addList(listObj);
|
||||
return list;
|
||||
},
|
||||
new (listObj) {
|
||||
const list = this.addList(listObj);
|
||||
|
||||
list
|
||||
.save()
|
||||
.then(() => {
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
});
|
||||
this.removeBlankState();
|
||||
},
|
||||
updateNewListDropdown (listId) {
|
||||
$(`.js-board-list-${listId}`).removeClass('is-active');
|
||||
},
|
||||
shouldAddBlankState () {
|
||||
// Decide whether to add the blank state
|
||||
return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
|
||||
},
|
||||
addBlankState () {
|
||||
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
|
||||
|
||||
this.addList({
|
||||
id: 'blank',
|
||||
list_type: 'blank',
|
||||
title: 'Welcome to your Issue Board!',
|
||||
position: 0
|
||||
list
|
||||
.save()
|
||||
.then(() => {
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
});
|
||||
this.removeBlankState();
|
||||
},
|
||||
updateNewListDropdown (listId) {
|
||||
$(`.js-board-list-${listId}`).removeClass('is-active');
|
||||
},
|
||||
shouldAddBlankState () {
|
||||
// Decide whether to add the blank state
|
||||
return !(this.state.lists.filter(list => list.type !== 'closed')[0]);
|
||||
},
|
||||
addBlankState () {
|
||||
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
|
||||
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
},
|
||||
removeBlankState () {
|
||||
this.removeList('blank');
|
||||
this.addList({
|
||||
id: 'blank',
|
||||
list_type: 'blank',
|
||||
title: 'Welcome to your Issue Board!',
|
||||
position: 0
|
||||
});
|
||||
|
||||
Cookies.set('issue_board_welcome_hidden', 'true', {
|
||||
expires: 365 * 10,
|
||||
path: ''
|
||||
});
|
||||
},
|
||||
welcomeIsHidden () {
|
||||
return Cookies.get('issue_board_welcome_hidden') === 'true';
|
||||
},
|
||||
removeList (id, type = 'blank') {
|
||||
const list = this.findList('id', id, type);
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
},
|
||||
removeBlankState () {
|
||||
this.removeList('blank');
|
||||
|
||||
if (!list) return;
|
||||
Cookies.set('issue_board_welcome_hidden', 'true', {
|
||||
expires: 365 * 10,
|
||||
path: ''
|
||||
});
|
||||
},
|
||||
welcomeIsHidden () {
|
||||
return Cookies.get('issue_board_welcome_hidden') === 'true';
|
||||
},
|
||||
removeList (id, type = 'blank') {
|
||||
const list = this.findList('id', id, type);
|
||||
|
||||
this.state.lists = this.state.lists.filter(list => list.id !== id);
|
||||
},
|
||||
moveList (listFrom, orderLists) {
|
||||
orderLists.forEach((id, i) => {
|
||||
const list = this.findList('id', parseInt(id, 10));
|
||||
if (!list) return;
|
||||
|
||||
list.position = i;
|
||||
});
|
||||
listFrom.update();
|
||||
},
|
||||
moveIssueToList (listFrom, listTo, issue, newIndex) {
|
||||
const issueTo = listTo.findIssue(issue.id);
|
||||
const issueLists = issue.getLists();
|
||||
const listLabels = issueLists.map(listIssue => listIssue.label);
|
||||
this.state.lists = this.state.lists.filter(list => list.id !== id);
|
||||
},
|
||||
moveList (listFrom, orderLists) {
|
||||
orderLists.forEach((id, i) => {
|
||||
const list = this.findList('id', parseInt(id, 10));
|
||||
|
||||
if (!issueTo) {
|
||||
// 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);
|
||||
}
|
||||
list.position = i;
|
||||
});
|
||||
listFrom.update();
|
||||
},
|
||||
moveIssueToList (listFrom, listTo, issue, newIndex) {
|
||||
const issueTo = listTo.findIssue(issue.id);
|
||||
const issueLists = issue.getLists();
|
||||
const listLabels = issueLists.map(listIssue => listIssue.label);
|
||||
|
||||
if (listTo.type === 'closed') {
|
||||
issueLists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
issue.removeLabels(listLabels);
|
||||
} else {
|
||||
listFrom.removeIssue(issue);
|
||||
}
|
||||
},
|
||||
moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
|
||||
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
|
||||
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
|
||||
|
||||
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;
|
||||
|
||||
return list[key] === val && byType;
|
||||
})[0];
|
||||
},
|
||||
updateFiltersUrl () {
|
||||
history.pushState(null, null, `?${this.filter.path}`);
|
||||
if (!issueTo) {
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
if (listTo.type === 'closed') {
|
||||
issueLists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
issue.removeLabels(listLabels);
|
||||
} else {
|
||||
listFrom.removeIssue(issue);
|
||||
}
|
||||
},
|
||||
moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
|
||||
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
|
||||
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
|
||||
|
||||
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;
|
||||
|
||||
return list[key] === val && byType;
|
||||
})[0];
|
||||
},
|
||||
updateFiltersUrl () {
|
||||
history.pushState(null, null, `?${this.filter.path}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,100 +1,98 @@
|
|||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
class ModalStore {
|
||||
constructor() {
|
||||
this.store = {
|
||||
columns: 3,
|
||||
issues: [],
|
||||
issuesCount: false,
|
||||
selectedIssues: [],
|
||||
showAddIssuesModal: false,
|
||||
activeTab: 'all',
|
||||
selectedList: null,
|
||||
searchTerm: '',
|
||||
loading: false,
|
||||
loadingNewPage: false,
|
||||
filterLoading: false,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
filter: {
|
||||
path: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
class ModalStore {
|
||||
constructor() {
|
||||
this.store = {
|
||||
columns: 3,
|
||||
issues: [],
|
||||
issuesCount: false,
|
||||
selectedIssues: [],
|
||||
showAddIssuesModal: false,
|
||||
activeTab: 'all',
|
||||
selectedList: null,
|
||||
searchTerm: '',
|
||||
loading: false,
|
||||
loadingNewPage: false,
|
||||
filterLoading: false,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
filter: {
|
||||
path: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
selectedCount() {
|
||||
return this.getSelectedIssues().length;
|
||||
}
|
||||
selectedCount() {
|
||||
return this.getSelectedIssues().length;
|
||||
}
|
||||
|
||||
toggleIssue(issueObj) {
|
||||
const issue = issueObj;
|
||||
const selected = issue.selected;
|
||||
toggleIssue(issueObj) {
|
||||
const issue = issueObj;
|
||||
const selected = issue.selected;
|
||||
|
||||
issue.selected = !selected;
|
||||
issue.selected = !selected;
|
||||
|
||||
if (!selected) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
const select = this.selectedCount() !== this.store.issues.length;
|
||||
|
||||
this.store.issues.forEach((issue) => {
|
||||
const issueUpdate = issue;
|
||||
|
||||
if (issueUpdate.selected !== select) {
|
||||
issueUpdate.selected = select;
|
||||
|
||||
if (select) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedIssues() {
|
||||
return this.store.selectedIssues.filter(issue => issue.selected);
|
||||
}
|
||||
|
||||
addSelectedIssue(issue) {
|
||||
const index = this.selectedIssueIndex(issue);
|
||||
|
||||
if (index === -1) {
|
||||
this.store.selectedIssues.push(issue);
|
||||
}
|
||||
}
|
||||
|
||||
removeSelectedIssue(issue, forcePurge = false) {
|
||||
if (this.store.activeTab === 'all' || forcePurge) {
|
||||
this.store.selectedIssues = this.store.selectedIssues
|
||||
.filter(fIssue => fIssue.id !== issue.id);
|
||||
}
|
||||
}
|
||||
|
||||
purgeUnselectedIssues() {
|
||||
this.store.selectedIssues.forEach((issue) => {
|
||||
if (!issue.selected) {
|
||||
this.removeSelectedIssue(issue, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectedIssueIndex(issue) {
|
||||
return this.store.selectedIssues.indexOf(issue);
|
||||
}
|
||||
|
||||
findSelectedIssue(issue) {
|
||||
return this.store.selectedIssues
|
||||
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
|
||||
if (!selected) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
|
||||
gl.issueBoards.ModalStore = new ModalStore();
|
||||
})();
|
||||
toggleAll() {
|
||||
const select = this.selectedCount() !== this.store.issues.length;
|
||||
|
||||
this.store.issues.forEach((issue) => {
|
||||
const issueUpdate = issue;
|
||||
|
||||
if (issueUpdate.selected !== select) {
|
||||
issueUpdate.selected = select;
|
||||
|
||||
if (select) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedIssues() {
|
||||
return this.store.selectedIssues.filter(issue => issue.selected);
|
||||
}
|
||||
|
||||
addSelectedIssue(issue) {
|
||||
const index = this.selectedIssueIndex(issue);
|
||||
|
||||
if (index === -1) {
|
||||
this.store.selectedIssues.push(issue);
|
||||
}
|
||||
}
|
||||
|
||||
removeSelectedIssue(issue, forcePurge = false) {
|
||||
if (this.store.activeTab === 'all' || forcePurge) {
|
||||
this.store.selectedIssues = this.store.selectedIssues
|
||||
.filter(fIssue => fIssue.id !== issue.id);
|
||||
}
|
||||
}
|
||||
|
||||
purgeUnselectedIssues() {
|
||||
this.store.selectedIssues.forEach((issue) => {
|
||||
if (!issue.selected) {
|
||||
this.removeSelectedIssue(issue, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectedIssueIndex(issue) {
|
||||
return this.store.selectedIssues.indexOf(issue);
|
||||
}
|
||||
|
||||
findSelectedIssue(issue) {
|
||||
return this.store.selectedIssues
|
||||
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
|
||||
}
|
||||
}
|
||||
|
||||
gl.issueBoards.ModalStore = new ModalStore();
|
||||
|
|
|
@ -1,93 +1,90 @@
|
|||
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
|
||||
(function() {
|
||||
(function(w) {
|
||||
var base;
|
||||
if (w.gl == null) {
|
||||
w.gl = {};
|
||||
var base;
|
||||
var w = window;
|
||||
if (w.gl == null) {
|
||||
w.gl = {};
|
||||
}
|
||||
if ((base = w.gl).utils == null) {
|
||||
base.utils = {};
|
||||
}
|
||||
// Returns an array containing the value(s) of the
|
||||
// of the key passed as an argument
|
||||
w.gl.utils.getParameterValues = function(sParam) {
|
||||
var i, sPageURL, sParameterName, sURLVariables, values;
|
||||
sPageURL = decodeURIComponent(window.location.search.substring(1));
|
||||
sURLVariables = sPageURL.split('&');
|
||||
sParameterName = void 0;
|
||||
values = [];
|
||||
i = 0;
|
||||
while (i < sURLVariables.length) {
|
||||
sParameterName = sURLVariables[i].split('=');
|
||||
if (sParameterName[0] === sParam) {
|
||||
values.push(sParameterName[1].replace(/\+/g, ' '));
|
||||
}
|
||||
if ((base = w.gl).utils == null) {
|
||||
base.utils = {};
|
||||
i += 1;
|
||||
}
|
||||
return values;
|
||||
};
|
||||
// @param {Object} params - url keys and value to merge
|
||||
// @param {String} url
|
||||
w.gl.utils.mergeUrlParams = function(params, url) {
|
||||
var lastChar, newUrl, paramName, paramValue, pattern;
|
||||
newUrl = decodeURIComponent(url);
|
||||
for (paramName in params) {
|
||||
paramValue = params[paramName];
|
||||
pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
|
||||
if (paramValue == null) {
|
||||
newUrl = newUrl.replace(pattern, '');
|
||||
} else if (url.search(pattern) !== -1) {
|
||||
newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
|
||||
} else {
|
||||
newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
|
||||
}
|
||||
// Returns an array containing the value(s) of the
|
||||
// of the key passed as an argument
|
||||
w.gl.utils.getParameterValues = function(sParam) {
|
||||
var i, sPageURL, sParameterName, sURLVariables, values;
|
||||
sPageURL = decodeURIComponent(window.location.search.substring(1));
|
||||
sURLVariables = sPageURL.split('&');
|
||||
sParameterName = void 0;
|
||||
values = [];
|
||||
i = 0;
|
||||
while (i < sURLVariables.length) {
|
||||
sParameterName = sURLVariables[i].split('=');
|
||||
if (sParameterName[0] === sParam) {
|
||||
values.push(sParameterName[1].replace(/\+/g, ' '));
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
// Remove a trailing ampersand
|
||||
lastChar = newUrl[newUrl.length - 1];
|
||||
if (lastChar === '&') {
|
||||
newUrl = newUrl.slice(0, -1);
|
||||
}
|
||||
return newUrl;
|
||||
};
|
||||
// removes parameter query string from url. returns the modified url
|
||||
w.gl.utils.removeParamQueryString = function(url, param) {
|
||||
var urlVariables, variables;
|
||||
url = decodeURIComponent(url);
|
||||
urlVariables = url.split('&');
|
||||
return ((function() {
|
||||
var j, len, results;
|
||||
results = [];
|
||||
for (j = 0, len = urlVariables.length; j < len; j += 1) {
|
||||
variables = urlVariables[j];
|
||||
if (variables.indexOf(param) === -1) {
|
||||
results.push(variables);
|
||||
}
|
||||
return values;
|
||||
};
|
||||
// @param {Object} params - url keys and value to merge
|
||||
// @param {String} url
|
||||
w.gl.utils.mergeUrlParams = function(params, url) {
|
||||
var lastChar, newUrl, paramName, paramValue, pattern;
|
||||
newUrl = decodeURIComponent(url);
|
||||
for (paramName in params) {
|
||||
paramValue = params[paramName];
|
||||
pattern = new RegExp("\\b(" + paramName + "=).*?(&|$)");
|
||||
if (paramValue == null) {
|
||||
newUrl = newUrl.replace(pattern, '');
|
||||
} else if (url.search(pattern) !== -1) {
|
||||
newUrl = newUrl.replace(pattern, "$1" + paramValue + "$2");
|
||||
} else {
|
||||
newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
|
||||
}
|
||||
}
|
||||
// Remove a trailing ampersand
|
||||
lastChar = newUrl[newUrl.length - 1];
|
||||
if (lastChar === '&') {
|
||||
newUrl = newUrl.slice(0, -1);
|
||||
}
|
||||
return newUrl;
|
||||
};
|
||||
// removes parameter query string from url. returns the modified url
|
||||
w.gl.utils.removeParamQueryString = function(url, param) {
|
||||
var urlVariables, variables;
|
||||
url = decodeURIComponent(url);
|
||||
urlVariables = url.split('&');
|
||||
return ((function() {
|
||||
var j, len, results;
|
||||
results = [];
|
||||
for (j = 0, len = urlVariables.length; j < len; j += 1) {
|
||||
variables = urlVariables[j];
|
||||
if (variables.indexOf(param) === -1) {
|
||||
results.push(variables);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
})()).join('&');
|
||||
};
|
||||
w.gl.utils.removeParams = (params) => {
|
||||
const url = new URL(window.location.href);
|
||||
params.forEach((param) => {
|
||||
url.search = w.gl.utils.removeParamQueryString(url.search, param);
|
||||
});
|
||||
return url.href;
|
||||
};
|
||||
w.gl.utils.getLocationHash = function(url) {
|
||||
var hashIndex;
|
||||
if (typeof url === 'undefined') {
|
||||
// Note: We can't use window.location.hash here because it's
|
||||
// not consistent across browsers - Firefox will pre-decode it
|
||||
url = window.location.href;
|
||||
}
|
||||
hashIndex = url.indexOf('#');
|
||||
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
|
||||
};
|
||||
}
|
||||
return results;
|
||||
})()).join('&');
|
||||
};
|
||||
w.gl.utils.removeParams = (params) => {
|
||||
const url = new URL(window.location.href);
|
||||
params.forEach((param) => {
|
||||
url.search = w.gl.utils.removeParamQueryString(url.search, param);
|
||||
});
|
||||
return url.href;
|
||||
};
|
||||
w.gl.utils.getLocationHash = function(url) {
|
||||
var hashIndex;
|
||||
if (typeof url === 'undefined') {
|
||||
// Note: We can't use window.location.hash here because it's
|
||||
// not consistent across browsers - Firefox will pre-decode it
|
||||
url = window.location.href;
|
||||
}
|
||||
hashIndex = url.indexOf('#');
|
||||
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
|
||||
};
|
||||
|
||||
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
|
||||
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
|
||||
|
||||
w.gl.utils.visitUrl = (url) => {
|
||||
document.location.href = url;
|
||||
};
|
||||
})(window);
|
||||
}).call(window);
|
||||
w.gl.utils.visitUrl = (url) => {
|
||||
document.location.href = url;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue