mirror of
https://github.com/wekan/wekan.git
synced 2026-03-13 17:06:13 +01:00
Migrate board and list components from BlazeComponent to Template
Convert boardBody, boardHeader, boardsList, boardArchive, originalPositionsView, list, listBody, and listHeader to use native Meteor Template.onCreated/helpers/events pattern.
This commit is contained in:
parent
f1625ad1f5
commit
d9e2e8f97e
8 changed files with 2789 additions and 2712 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
import { ReactiveCache } from '/imports/reactiveCache';
|
import { ReactiveCache } from '/imports/reactiveCache';
|
||||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.archivedBoards.onCreated(function () {
|
||||||
onCreated() {
|
this.subscribe('archivedBoards');
|
||||||
this.subscribe('archivedBoards');
|
});
|
||||||
},
|
|
||||||
|
|
||||||
|
Template.archivedBoards.helpers({
|
||||||
isBoardAdmin() {
|
isBoardAdmin() {
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||||
},
|
},
|
||||||
|
|
@ -19,38 +19,34 @@ BlazeComponent.extendComponent({
|
||||||
);
|
);
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.archivedBoards.events({
|
||||||
return [
|
async 'click .js-restore-board'() {
|
||||||
{
|
// TODO : Make isSandstorm variable global
|
||||||
async 'click .js-restore-board'() {
|
const isSandstorm =
|
||||||
// TODO : Make isSandstorm variable global
|
Meteor.settings &&
|
||||||
const isSandstorm =
|
Meteor.settings.public &&
|
||||||
Meteor.settings &&
|
Meteor.settings.public.sandstorm;
|
||||||
Meteor.settings.public &&
|
if (isSandstorm && Utils.getCurrentBoardId()) {
|
||||||
Meteor.settings.public.sandstorm;
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
if (isSandstorm && Utils.getCurrentBoardId()) {
|
await currentBoard.archive();
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
}
|
||||||
await currentBoard.archive();
|
const board = this;
|
||||||
}
|
await board.restore();
|
||||||
const board = this.currentData();
|
Utils.goBoardId(board._id);
|
||||||
await board.restore();
|
|
||||||
Utils.goBoardId(board._id);
|
|
||||||
},
|
|
||||||
'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() {
|
|
||||||
Popup.back();
|
|
||||||
const isSandstorm =
|
|
||||||
Meteor.settings &&
|
|
||||||
Meteor.settings.public &&
|
|
||||||
Meteor.settings.public.sandstorm;
|
|
||||||
if (isSandstorm && Utils.getCurrentBoardId()) {
|
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
|
||||||
await Boards.removeAsync(currentBoard._id);
|
|
||||||
}
|
|
||||||
await Boards.removeAsync(this._id);
|
|
||||||
FlowRouter.go('home');
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
}).register('archivedBoards');
|
'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() {
|
||||||
|
Popup.back();
|
||||||
|
const isSandstorm =
|
||||||
|
Meteor.settings &&
|
||||||
|
Meteor.settings.public &&
|
||||||
|
Meteor.settings.public.sandstorm;
|
||||||
|
if (isSandstorm && Utils.getCurrentBoardId()) {
|
||||||
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
|
await Boards.removeAsync(currentBoard._id);
|
||||||
|
}
|
||||||
|
await Boards.removeAsync(this._id);
|
||||||
|
FlowRouter.go('home');
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -20,22 +20,23 @@ Template.boardChangeTitlePopup.events({
|
||||||
.val()
|
.val()
|
||||||
.trim();
|
.trim();
|
||||||
if (newTitle) {
|
if (newTitle) {
|
||||||
await this.rename(newTitle);
|
const board = Utils.getCurrentBoard();
|
||||||
await this.setDescription(newDesc);
|
if (board) {
|
||||||
|
await board.rename(newTitle);
|
||||||
|
await board.setDescription(newDesc);
|
||||||
|
}
|
||||||
Popup.back();
|
Popup.back();
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.boardHeaderBar.helpers({
|
||||||
watchLevel() {
|
watchLevel() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
|
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
isStarred() {
|
isStarred() {
|
||||||
const boardId = Session.get('currentBoard');
|
const boardId = Session.get('currentBoard');
|
||||||
const user = ReactiveCache.getCurrentUser();
|
const user = ReactiveCache.getCurrentUser();
|
||||||
|
|
@ -47,130 +48,7 @@ BlazeComponent.extendComponent({
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return currentBoard && currentBoard.stars >= 2;
|
return currentBoard && currentBoard.stars >= 2;
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
showSort() {
|
|
||||||
return ReactiveCache.getCurrentUser().hasSortBy();
|
|
||||||
},
|
|
||||||
directionClass() {
|
|
||||||
return this.currentDirection() === -1 ? DOWNCLS : UPCLS;
|
|
||||||
},
|
|
||||||
changeDirection() {
|
|
||||||
const direction = 0 - this.currentDirection() === -1 ? '-' : '';
|
|
||||||
Meteor.call('setListSortBy', direction + this.currentListSortBy());
|
|
||||||
},
|
|
||||||
currentDirection() {
|
|
||||||
return ReactiveCache.getCurrentUser().getListSortByDirection();
|
|
||||||
},
|
|
||||||
currentListSortBy() {
|
|
||||||
return ReactiveCache.getCurrentUser().getListSortBy();
|
|
||||||
},
|
|
||||||
listSortShortDesc() {
|
|
||||||
return `list-label-short-${this.currentListSortBy()}`;
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
|
|
||||||
'click .js-star-board'() {
|
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
if (boardId) {
|
|
||||||
Meteor.call('toggleBoardStar', boardId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'click .js-open-board-menu': Popup.open('boardMenu'),
|
|
||||||
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
|
|
||||||
'click .js-watch-board': Popup.open('boardChangeWatch'),
|
|
||||||
'click .js-open-archived-board'() {
|
|
||||||
Modal.open('archivedBoards');
|
|
||||||
},
|
|
||||||
'click .js-toggle-board-view': Popup.open('boardChangeView'),
|
|
||||||
'click .js-toggle-sidebar'() {
|
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.log('Hamburger menu clicked');
|
|
||||||
}
|
|
||||||
// Use the same approach as keyboard shortcuts
|
|
||||||
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
|
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.log('Using Sidebar.toggle()');
|
|
||||||
}
|
|
||||||
Sidebar.toggle();
|
|
||||||
} else {
|
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.warn('Sidebar not available, trying alternative approach');
|
|
||||||
}
|
|
||||||
// Try to trigger the sidebar through the global Blaze helper
|
|
||||||
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
|
|
||||||
const sidebar = Blaze._globalHelpers.Sidebar();
|
|
||||||
if (sidebar && typeof sidebar.toggle === 'function') {
|
|
||||||
if (process.env.DEBUG === 'true') {
|
|
||||||
console.log('Using Blaze helper Sidebar.toggle()');
|
|
||||||
}
|
|
||||||
sidebar.toggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'click .js-open-filter-view'() {
|
|
||||||
if (Sidebar) {
|
|
||||||
Sidebar.setView('filter');
|
|
||||||
} else {
|
|
||||||
console.warn('Sidebar not available for setView');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'click .js-sort-cards': Popup.open('cardsSort'),
|
|
||||||
/*
|
|
||||||
'click .js-open-sort-view'(evt) {
|
|
||||||
const target = evt.target;
|
|
||||||
if (target.tagName === 'I') {
|
|
||||||
// click on the text, popup choices
|
|
||||||
this.changeDirection();
|
|
||||||
} else {
|
|
||||||
// change the sort order
|
|
||||||
Popup.open('listsort')(evt);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
'click .js-filter-reset'(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (Sidebar) {
|
|
||||||
Sidebar.setView();
|
|
||||||
} else {
|
|
||||||
console.warn('Sidebar not available for setView');
|
|
||||||
}
|
|
||||||
Filter.reset();
|
|
||||||
},
|
|
||||||
'click .js-sort-reset'() {
|
|
||||||
Session.set('sortBy', '');
|
|
||||||
},
|
|
||||||
'click .js-open-search-view'() {
|
|
||||||
if (Sidebar) {
|
|
||||||
Sidebar.setView('search');
|
|
||||||
} else {
|
|
||||||
console.warn('Sidebar not available for setView');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'click .js-multiselection-activate'() {
|
|
||||||
const currentCard = Utils.getCurrentCardId();
|
|
||||||
MultiSelection.activate();
|
|
||||||
if (currentCard) {
|
|
||||||
MultiSelection.add(currentCard);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'click .js-multiselection-reset'(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
MultiSelection.disable();
|
|
||||||
},
|
|
||||||
'click .js-log-in'() {
|
|
||||||
FlowRouter.go('atSignIn');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
}).register('boardHeaderBar');
|
|
||||||
|
|
||||||
Template.boardHeaderBar.helpers({
|
|
||||||
boardView() {
|
boardView() {
|
||||||
return Utils.boardView();
|
return Utils.boardView();
|
||||||
},
|
},
|
||||||
|
|
@ -196,6 +74,102 @@ Template.boardHeaderBar.helpers({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.boardHeaderBar.events({
|
||||||
|
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
|
||||||
|
'click .js-star-board'() {
|
||||||
|
const boardId = Session.get('currentBoard');
|
||||||
|
if (boardId) {
|
||||||
|
Meteor.call('toggleBoardStar', boardId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-open-board-menu': Popup.open('boardMenu'),
|
||||||
|
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
|
||||||
|
'click .js-watch-board': Popup.open('boardChangeWatch'),
|
||||||
|
'click .js-open-archived-board'() {
|
||||||
|
Modal.open('archivedBoards');
|
||||||
|
},
|
||||||
|
'click .js-toggle-board-view': Popup.open('boardChangeView'),
|
||||||
|
'click .js-toggle-sidebar'() {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Hamburger menu clicked');
|
||||||
|
}
|
||||||
|
// Use the same approach as keyboard shortcuts
|
||||||
|
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Using Sidebar.toggle()');
|
||||||
|
}
|
||||||
|
Sidebar.toggle();
|
||||||
|
} else {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.warn('Sidebar not available, trying alternative approach');
|
||||||
|
}
|
||||||
|
// Try to trigger the sidebar through the global Blaze helper
|
||||||
|
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
|
||||||
|
const sidebar = Blaze._globalHelpers.Sidebar();
|
||||||
|
if (sidebar && typeof sidebar.toggle === 'function') {
|
||||||
|
if (process.env.DEBUG === 'true') {
|
||||||
|
console.log('Using Blaze helper Sidebar.toggle()');
|
||||||
|
}
|
||||||
|
sidebar.toggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-open-filter-view'() {
|
||||||
|
if (Sidebar) {
|
||||||
|
Sidebar.setView('filter');
|
||||||
|
} else {
|
||||||
|
console.warn('Sidebar not available for setView');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-sort-cards': Popup.open('cardsSort'),
|
||||||
|
/*
|
||||||
|
'click .js-open-sort-view'(evt) {
|
||||||
|
const target = evt.target;
|
||||||
|
if (target.tagName === 'I') {
|
||||||
|
// click on the text, popup choices
|
||||||
|
this.changeDirection();
|
||||||
|
} else {
|
||||||
|
// change the sort order
|
||||||
|
Popup.open('listsort')(evt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
'click .js-filter-reset'(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (Sidebar) {
|
||||||
|
Sidebar.setView();
|
||||||
|
} else {
|
||||||
|
console.warn('Sidebar not available for setView');
|
||||||
|
}
|
||||||
|
Filter.reset();
|
||||||
|
},
|
||||||
|
'click .js-sort-reset'() {
|
||||||
|
Session.set('sortBy', '');
|
||||||
|
},
|
||||||
|
'click .js-open-search-view'() {
|
||||||
|
if (Sidebar) {
|
||||||
|
Sidebar.setView('search');
|
||||||
|
} else {
|
||||||
|
console.warn('Sidebar not available for setView');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-multiselection-activate'() {
|
||||||
|
const currentCard = Utils.getCurrentCardId();
|
||||||
|
MultiSelection.activate();
|
||||||
|
if (currentCard) {
|
||||||
|
MultiSelection.add(currentCard);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-multiselection-reset'(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
MultiSelection.disable();
|
||||||
|
},
|
||||||
|
'click .js-log-in'() {
|
||||||
|
FlowRouter.go('atSignIn');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.boardChangeViewPopup.events({
|
Template.boardChangeViewPopup.events({
|
||||||
'click .js-open-lists-view'() {
|
'click .js-open-lists-view'() {
|
||||||
Utils.setBoardView('board-view-lists');
|
Utils.setBoardView('board-view-lists');
|
||||||
|
|
@ -215,219 +189,257 @@ Template.boardChangeViewPopup.events({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateBoard = BlazeComponent.extendComponent({
|
// Shared setup for all create board popups
|
||||||
template() {
|
function setupCreateBoardState(tpl) {
|
||||||
return 'createBoard';
|
tpl.visibilityMenuIsOpen = new ReactiveVar(false);
|
||||||
},
|
tpl.visibility = new ReactiveVar('private');
|
||||||
|
tpl.boardId = new ReactiveVar('');
|
||||||
|
Meteor.subscribe('tableVisibilityModeSettings');
|
||||||
|
}
|
||||||
|
|
||||||
onCreated() {
|
function createBoardHelpers() {
|
||||||
this.visibilityMenuIsOpen = new ReactiveVar(false);
|
return {
|
||||||
this.visibility = new ReactiveVar('private');
|
visibilityMenuIsOpen() {
|
||||||
this.boardId = new ReactiveVar('');
|
return Template.instance().visibilityMenuIsOpen.get();
|
||||||
Meteor.subscribe('tableVisibilityModeSettings');
|
},
|
||||||
},
|
visibility() {
|
||||||
|
return Template.instance().visibility.get();
|
||||||
|
},
|
||||||
|
notAllowPrivateVisibilityOnly() {
|
||||||
|
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
|
||||||
|
},
|
||||||
|
visibilityCheck() {
|
||||||
|
return Template.currentData() === Template.instance().visibility.get();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
notAllowPrivateVisibilityOnly(){
|
function createBoardSubmit(tpl, event) {
|
||||||
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
|
event.preventDefault();
|
||||||
},
|
const title = tpl.find('.js-new-board-title').value;
|
||||||
|
|
||||||
visibilityCheck() {
|
const addTemplateContainer = $('#add-template-container.is-checked').length > 0;
|
||||||
return this.currentData() === this.visibility.get();
|
if (addTemplateContainer) {
|
||||||
},
|
//const templateContainerId = Meteor.call('setCreateTemplateContainer');
|
||||||
|
//Utils.goBoardId(templateContainerId);
|
||||||
|
//alert('niinku template ' + Meteor.call('setCreateTemplateContainer'));
|
||||||
|
|
||||||
setVisibility(visibility) {
|
tpl.boardId.set(
|
||||||
this.visibility.set(visibility);
|
Boards.insert({
|
||||||
this.visibilityMenuIsOpen.set(false);
|
// title: TAPi18n.__('templates'),
|
||||||
},
|
title: title,
|
||||||
|
permission: 'private',
|
||||||
toggleVisibilityMenu() {
|
|
||||||
this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get());
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleAddTemplateContainer() {
|
|
||||||
$('#add-template-container').toggleClass('is-checked');
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmit(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const title = this.find('.js-new-board-title').value;
|
|
||||||
|
|
||||||
const addTemplateContainer = $('#add-template-container.is-checked').length > 0;
|
|
||||||
if (addTemplateContainer) {
|
|
||||||
//const templateContainerId = Meteor.call('setCreateTemplateContainer');
|
|
||||||
//Utils.goBoardId(templateContainerId);
|
|
||||||
//alert('niinku template ' + Meteor.call('setCreateTemplateContainer'));
|
|
||||||
|
|
||||||
this.boardId.set(
|
|
||||||
Boards.insert({
|
|
||||||
// title: TAPi18n.__('templates'),
|
|
||||||
title: title,
|
|
||||||
permission: 'private',
|
|
||||||
type: 'template-container',
|
|
||||||
migrationVersion: 1, // Latest version - no migration needed
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert the card templates swimlane
|
|
||||||
Swimlanes.insert({
|
|
||||||
// title: TAPi18n.__('card-templates-swimlane'),
|
|
||||||
title: 'Card Templates',
|
|
||||||
boardId: this.boardId.get(),
|
|
||||||
sort: 1,
|
|
||||||
type: 'template-container',
|
type: 'template-container',
|
||||||
}),
|
|
||||||
|
|
||||||
// Insert the list templates swimlane
|
|
||||||
Swimlanes.insert(
|
|
||||||
{
|
|
||||||
// title: TAPi18n.__('list-templates-swimlane'),
|
|
||||||
title: 'List Templates',
|
|
||||||
boardId: this.boardId.get(),
|
|
||||||
sort: 2,
|
|
||||||
type: 'template-container',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert the board templates swimlane
|
|
||||||
Swimlanes.insert(
|
|
||||||
{
|
|
||||||
//title: TAPi18n.__('board-templates-swimlane'),
|
|
||||||
title: 'Board Templates',
|
|
||||||
boardId: this.boardId.get(),
|
|
||||||
sort: 3,
|
|
||||||
type: 'template-container',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assign to space if one was selected
|
|
||||||
const spaceId = Session.get('createBoardInWorkspace');
|
|
||||||
if (spaceId) {
|
|
||||||
Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => {
|
|
||||||
if (err) console.error('Error assigning board to space:', err);
|
|
||||||
});
|
|
||||||
Session.set('createBoardInWorkspace', null); // Clear after use
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.goBoardId(this.boardId.get());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const visibility = this.visibility.get();
|
|
||||||
|
|
||||||
this.boardId.set(
|
|
||||||
Boards.insert({
|
|
||||||
title,
|
|
||||||
permission: visibility,
|
|
||||||
migrationVersion: 1, // Latest version - no migration needed
|
migrationVersion: 1, // Latest version - no migration needed
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
Swimlanes.insert({
|
// Insert the card templates swimlane
|
||||||
title: 'Default',
|
Swimlanes.insert({
|
||||||
boardId: this.boardId.get(),
|
// title: TAPi18n.__('card-templates-swimlane'),
|
||||||
});
|
title: 'Card Templates',
|
||||||
|
boardId: tpl.boardId.get(),
|
||||||
|
sort: 1,
|
||||||
|
type: 'template-container',
|
||||||
|
}),
|
||||||
|
|
||||||
// Assign to space if one was selected
|
// Insert the list templates swimlane
|
||||||
const spaceId = Session.get('createBoardInWorkspace');
|
Swimlanes.insert(
|
||||||
if (spaceId) {
|
|
||||||
Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => {
|
|
||||||
if (err) console.error('Error assigning board to space:', err);
|
|
||||||
});
|
|
||||||
Session.set('createBoardInWorkspace', null); // Clear after use
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.goBoardId(this.boardId.get());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
'click .js-select-visibility'() {
|
// title: TAPi18n.__('list-templates-swimlane'),
|
||||||
this.setVisibility(this.currentData());
|
title: 'List Templates',
|
||||||
},
|
boardId: tpl.boardId.get(),
|
||||||
'click .js-change-visibility': this.toggleVisibilityMenu,
|
sort: 2,
|
||||||
'click .js-import': Popup.open('boardImportBoard'),
|
type: 'template-container',
|
||||||
submit: this.onSubmit,
|
|
||||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
|
||||||
'click .js-board-template': Popup.open('searchElement'),
|
|
||||||
'click .js-toggle-add-template-container': this.toggleAddTemplateContainer,
|
|
||||||
},
|
},
|
||||||
];
|
);
|
||||||
|
|
||||||
|
// Insert the board templates swimlane
|
||||||
|
Swimlanes.insert(
|
||||||
|
{
|
||||||
|
//title: TAPi18n.__('board-templates-swimlane'),
|
||||||
|
title: 'Board Templates',
|
||||||
|
boardId: tpl.boardId.get(),
|
||||||
|
sort: 3,
|
||||||
|
type: 'template-container',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assign to space if one was selected
|
||||||
|
const spaceId = Session.get('createBoardInWorkspace');
|
||||||
|
if (spaceId) {
|
||||||
|
Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => {
|
||||||
|
if (err) console.error('Error assigning board to space:', err);
|
||||||
|
});
|
||||||
|
Session.set('createBoardInWorkspace', null); // Clear after use
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.goBoardId(tpl.boardId.get());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const visibility = tpl.visibility.get();
|
||||||
|
|
||||||
|
tpl.boardId.set(
|
||||||
|
Boards.insert({
|
||||||
|
title,
|
||||||
|
permission: visibility,
|
||||||
|
migrationVersion: 1, // Latest version - no migration needed
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Swimlanes.insert({
|
||||||
|
title: 'Default',
|
||||||
|
boardId: tpl.boardId.get(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign to space if one was selected
|
||||||
|
const spaceId = Session.get('createBoardInWorkspace');
|
||||||
|
if (spaceId) {
|
||||||
|
Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => {
|
||||||
|
if (err) console.error('Error assigning board to space:', err);
|
||||||
|
});
|
||||||
|
Session.set('createBoardInWorkspace', null); // Clear after use
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.goBoardId(tpl.boardId.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBoardEvents() {
|
||||||
|
return {
|
||||||
|
'click .js-select-visibility'(event, tpl) {
|
||||||
|
tpl.visibility.set(Template.currentData());
|
||||||
|
tpl.visibilityMenuIsOpen.set(false);
|
||||||
|
},
|
||||||
|
'click .js-change-visibility'(event, tpl) {
|
||||||
|
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
|
||||||
|
},
|
||||||
|
'click .js-import': Popup.open('boardImportBoard'),
|
||||||
|
'submit'(event, tpl) {
|
||||||
|
createBoardSubmit(tpl, event);
|
||||||
|
},
|
||||||
|
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||||
|
'click .js-board-template': Popup.open('searchElement'),
|
||||||
|
'click .js-toggle-add-template-container'() {
|
||||||
|
$('#add-template-container').toggleClass('is-checked');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// createBoard (non-popup version)
|
||||||
|
Template.createBoard.onCreated(function () {
|
||||||
|
setupCreateBoardState(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.createBoard.helpers(createBoardHelpers());
|
||||||
|
|
||||||
|
Template.createBoard.events(createBoardEvents());
|
||||||
|
|
||||||
|
// createBoardPopup
|
||||||
|
Template.createBoardPopup.onCreated(function () {
|
||||||
|
setupCreateBoardState(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.createBoardPopup.helpers(createBoardHelpers());
|
||||||
|
|
||||||
|
Template.createBoardPopup.events(createBoardEvents());
|
||||||
|
|
||||||
|
// createTemplateContainerPopup
|
||||||
|
Template.createTemplateContainerPopup.onCreated(function () {
|
||||||
|
setupCreateBoardState(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.createTemplateContainerPopup.onRendered(function () {
|
||||||
|
// Always pre-check the template container checkbox for this popup
|
||||||
|
$('#add-template-container').addClass('is-checked');
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.createTemplateContainerPopup.helpers(createBoardHelpers());
|
||||||
|
|
||||||
|
Template.createTemplateContainerPopup.events(createBoardEvents());
|
||||||
|
|
||||||
|
// headerBarCreateBoardPopup
|
||||||
|
Template.headerBarCreateBoardPopup.onCreated(function () {
|
||||||
|
setupCreateBoardState(this);
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.headerBarCreateBoardPopup.helpers(createBoardHelpers());
|
||||||
|
|
||||||
|
Template.headerBarCreateBoardPopup.events({
|
||||||
|
'click .js-select-visibility'(event, tpl) {
|
||||||
|
tpl.visibility.set(Template.currentData());
|
||||||
|
tpl.visibilityMenuIsOpen.set(false);
|
||||||
},
|
},
|
||||||
}).register('createBoardPopup');
|
'click .js-change-visibility'(event, tpl) {
|
||||||
|
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
|
||||||
|
},
|
||||||
|
'click .js-import': Popup.open('boardImportBoard'),
|
||||||
|
async submit(event, tpl) {
|
||||||
|
createBoardSubmit(tpl, event);
|
||||||
|
// Immediately star boards created with the headerbar popup.
|
||||||
|
await ReactiveCache.getCurrentUser().toggleBoardStar(tpl.boardId.get());
|
||||||
|
},
|
||||||
|
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||||
|
'click .js-board-template': Popup.open('searchElement'),
|
||||||
|
'click .js-toggle-add-template-container'() {
|
||||||
|
$('#add-template-container').toggleClass('is-checked');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
(class CreateTemplateContainerPopup extends CreateBoard {
|
Template.boardChangeVisibilityPopup.onCreated(function () {
|
||||||
onRendered() {
|
Meteor.subscribe('tableVisibilityModeSettings');
|
||||||
// Always pre-check the template container checkbox for this popup
|
});
|
||||||
$('#add-template-container').addClass('is-checked');
|
|
||||||
}
|
|
||||||
}).register('createTemplateContainerPopup');
|
|
||||||
|
|
||||||
(class HeaderBarCreateBoard extends CreateBoard {
|
Template.boardChangeVisibilityPopup.helpers({
|
||||||
async onSubmit(event) {
|
|
||||||
super.onSubmit(event);
|
|
||||||
// Immediately star boards crated with the headerbar popup.
|
|
||||||
await ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get());
|
|
||||||
}
|
|
||||||
}.register('headerBarCreateBoardPopup'));
|
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
notAllowPrivateVisibilityOnly(){
|
notAllowPrivateVisibilityOnly(){
|
||||||
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
|
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
|
||||||
},
|
},
|
||||||
visibilityCheck() {
|
visibilityCheck() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return this.currentData() === currentBoard.permission;
|
return this === currentBoard.permission;
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
selectBoardVisibility() {
|
Template.boardChangeVisibilityPopup.events({
|
||||||
|
'click .js-select-visibility'() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
const visibility = this.currentData();
|
const visibility = String(this);
|
||||||
currentBoard.setVisibility(visibility);
|
currentBoard.setVisibility(visibility);
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.boardChangeWatchPopup.helpers({
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-select-visibility': this.selectBoardVisibility,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('boardChangeVisibilityPopup');
|
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
|
||||||
watchLevel() {
|
watchLevel() {
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
return currentBoard.getWatchLevel(Meteor.userId());
|
return currentBoard.getWatchLevel(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
|
||||||
watchCheck() {
|
watchCheck() {
|
||||||
return this.currentData() === this.watchLevel();
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
|
return this === currentBoard.getWatchLevel(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.boardChangeWatchPopup.events({
|
||||||
return [
|
'click .js-select-watch'() {
|
||||||
{
|
const level = String(this);
|
||||||
'click .js-select-watch'() {
|
Meteor.call(
|
||||||
const level = this.currentData();
|
'watch',
|
||||||
Meteor.call(
|
'board',
|
||||||
'watch',
|
Session.get('currentBoard'),
|
||||||
'board',
|
level,
|
||||||
Session.get('currentBoard'),
|
(err, ret) => {
|
||||||
level,
|
if (!err && ret) Popup.back();
|
||||||
(err, ret) => {
|
|
||||||
if (!err && ret) Popup.back();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
);
|
||||||
},
|
},
|
||||||
}).register('boardChangeWatchPopup');
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
BlazeComponent.extendComponent({
|
// BlazeComponent.extendComponent was removed - this code is unused.
|
||||||
|
// Original listsortPopup component:
|
||||||
|
// {
|
||||||
onCreated() {
|
onCreated() {
|
||||||
//this.sortBy = new ReactiveVar();
|
//this.sortBy = new ReactiveVar();
|
||||||
////this.sortDirection = new ReactiveVar();
|
////this.sortDirection = new ReactiveVar();
|
||||||
|
|
@ -495,46 +507,40 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}).register('listsortPopup');
|
// }.register('listsortPopup');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.cardsSortPopup.events({
|
||||||
events() {
|
'click .js-sort-due'() {
|
||||||
return [
|
const sortBy = {
|
||||||
{
|
dueAt: 1,
|
||||||
'click .js-sort-due'() {
|
};
|
||||||
const sortBy = {
|
Session.set('sortBy', sortBy);
|
||||||
dueAt: 1,
|
sortCardsBy.set(TAPi18n.__('due-date'));
|
||||||
};
|
Popup.back();
|
||||||
Session.set('sortBy', sortBy);
|
|
||||||
sortCardsBy.set(TAPi18n.__('due-date'));
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
'click .js-sort-title'() {
|
|
||||||
const sortBy = {
|
|
||||||
title: 1,
|
|
||||||
};
|
|
||||||
Session.set('sortBy', sortBy);
|
|
||||||
sortCardsBy.set(TAPi18n.__('title'));
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
'click .js-sort-created-asc'() {
|
|
||||||
const sortBy = {
|
|
||||||
createdAt: 1,
|
|
||||||
};
|
|
||||||
Session.set('sortBy', sortBy);
|
|
||||||
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
'click .js-sort-created-desc'() {
|
|
||||||
const sortBy = {
|
|
||||||
createdAt: -1,
|
|
||||||
};
|
|
||||||
Session.set('sortBy', sortBy);
|
|
||||||
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
|
|
||||||
Popup.back();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
}).register('cardsSortPopup');
|
'click .js-sort-title'() {
|
||||||
|
const sortBy = {
|
||||||
|
title: 1,
|
||||||
|
};
|
||||||
|
Session.set('sortBy', sortBy);
|
||||||
|
sortCardsBy.set(TAPi18n.__('title'));
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
|
'click .js-sort-created-asc'() {
|
||||||
|
const sortBy = {
|
||||||
|
createdAt: 1,
|
||||||
|
};
|
||||||
|
Session.set('sortBy', sortBy);
|
||||||
|
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
|
'click .js-sort-created-desc'() {
|
||||||
|
const sortBy = {
|
||||||
|
createdAt: -1,
|
||||||
|
};
|
||||||
|
Session.set('sortBy', sortBy);
|
||||||
|
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
|
||||||
|
Popup.back();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,94 +1,73 @@
|
||||||
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
|
|
||||||
import { ReactiveVar } from 'meteor/reactive-var';
|
import { ReactiveVar } from 'meteor/reactive-var';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Template } from 'meteor/templating';
|
|
||||||
import './originalPositionsView.html';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display original positions for all entities on a board
|
* Component to display original positions for all entities on a board
|
||||||
*/
|
*/
|
||||||
class OriginalPositionsViewComponent extends BlazeComponent {
|
|
||||||
onCreated() {
|
|
||||||
super.onCreated();
|
|
||||||
this.showOriginalPositions = new ReactiveVar(false);
|
|
||||||
this.boardHistory = new ReactiveVar([]);
|
|
||||||
this.isLoading = new ReactiveVar(false);
|
|
||||||
this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card'
|
|
||||||
}
|
|
||||||
|
|
||||||
onRendered() {
|
Template.originalPositionsView.onCreated(function () {
|
||||||
super.onRendered();
|
this.showOriginalPositions = new ReactiveVar(false);
|
||||||
this.loadBoardHistory();
|
this.boardHistory = new ReactiveVar([]);
|
||||||
}
|
this.isLoading = new ReactiveVar(false);
|
||||||
|
this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card'
|
||||||
|
|
||||||
loadBoardHistory() {
|
const tpl = this;
|
||||||
|
|
||||||
|
this.loadBoardHistory = function () {
|
||||||
const boardId = Session.get('currentBoard');
|
const boardId = Session.get('currentBoard');
|
||||||
if (!boardId) return;
|
if (!boardId) return;
|
||||||
|
|
||||||
this.isLoading.set(true);
|
tpl.isLoading.set(true);
|
||||||
|
|
||||||
Meteor.call('positionHistory.getBoardHistory', boardId, (error, result) => {
|
Meteor.call('positionHistory.getBoardHistory', boardId, (error, result) => {
|
||||||
this.isLoading.set(false);
|
tpl.isLoading.set(false);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error loading board history:', error);
|
console.error('Error loading board history:', error);
|
||||||
this.boardHistory.set([]);
|
tpl.boardHistory.set([]);
|
||||||
} else {
|
} else {
|
||||||
this.boardHistory.set(result);
|
tpl.boardHistory.set(result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
});
|
||||||
|
|
||||||
toggleOriginalPositions() {
|
Template.originalPositionsView.onRendered(function () {
|
||||||
this.showOriginalPositions.set(!this.showOriginalPositions.get());
|
this.loadBoardHistory();
|
||||||
}
|
});
|
||||||
|
|
||||||
|
Template.originalPositionsView.helpers({
|
||||||
isShowingOriginalPositions() {
|
isShowingOriginalPositions() {
|
||||||
return this.showOriginalPositions.get();
|
return Template.instance().showOriginalPositions.get();
|
||||||
}
|
},
|
||||||
|
|
||||||
isLoading() {
|
isLoading() {
|
||||||
return this.isLoading.get();
|
return Template.instance().isLoading.get();
|
||||||
}
|
},
|
||||||
|
|
||||||
getBoardHistory() {
|
getBoardHistory() {
|
||||||
return this.boardHistory.get();
|
return Template.instance().boardHistory.get();
|
||||||
}
|
},
|
||||||
|
|
||||||
getFilteredHistory() {
|
getFilteredHistory() {
|
||||||
const history = this.getBoardHistory();
|
const tpl = Template.instance();
|
||||||
const filterType = this.filterType.get();
|
const history = tpl.boardHistory.get();
|
||||||
|
const filterType = tpl.filterType.get();
|
||||||
|
|
||||||
if (filterType === 'all') {
|
if (filterType === 'all') {
|
||||||
return history;
|
return history;
|
||||||
}
|
}
|
||||||
|
|
||||||
return history.filter(item => item.entityType === filterType);
|
return history.filter(item => item.entityType === filterType);
|
||||||
}
|
},
|
||||||
|
|
||||||
getSwimlanesHistory() {
|
isFilterType(type) {
|
||||||
return this.getBoardHistory().filter(item => item.entityType === 'swimlane');
|
return Template.instance().filterType.get() === type;
|
||||||
}
|
},
|
||||||
|
|
||||||
getListsHistory() {
|
|
||||||
return this.getBoardHistory().filter(item => item.entityType === 'list');
|
|
||||||
}
|
|
||||||
|
|
||||||
getCardsHistory() {
|
|
||||||
return this.getBoardHistory().filter(item => item.entityType === 'card');
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilterType(type) {
|
|
||||||
this.filterType.set(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilterType() {
|
|
||||||
return this.filterType.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
getEntityDisplayName(entity) {
|
getEntityDisplayName(entity) {
|
||||||
const position = entity.originalPosition || {};
|
const position = entity.originalPosition || {};
|
||||||
return position.title || `Entity ${entity.entityId}`;
|
return position.title || `Entity ${entity.entityId}`;
|
||||||
}
|
},
|
||||||
|
|
||||||
getEntityOriginalPositionDescription(entity) {
|
getEntityOriginalPositionDescription(entity) {
|
||||||
const position = entity.originalPosition || {};
|
const position = entity.originalPosition || {};
|
||||||
|
|
@ -106,7 +85,7 @@ class OriginalPositionsViewComponent extends BlazeComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return description;
|
return description;
|
||||||
}
|
},
|
||||||
|
|
||||||
getEntityTypeIcon(entityType) {
|
getEntityTypeIcon(entityType) {
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
|
|
@ -119,7 +98,7 @@ class OriginalPositionsViewComponent extends BlazeComponent {
|
||||||
default:
|
default:
|
||||||
return 'fa-question';
|
return 'fa-question';
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
getEntityTypeLabel(entityType) {
|
getEntityTypeLabel(entityType) {
|
||||||
switch (entityType) {
|
switch (entityType) {
|
||||||
|
|
@ -132,17 +111,24 @@ class OriginalPositionsViewComponent extends BlazeComponent {
|
||||||
default:
|
default:
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
formatDate(date) {
|
formatDate(date) {
|
||||||
return new Date(date).toLocaleString();
|
return new Date(date).toLocaleString();
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
refreshHistory() {
|
Template.originalPositionsView.events({
|
||||||
this.loadBoardHistory();
|
'click .js-toggle-original-positions'(evt, tpl) {
|
||||||
}
|
tpl.showOriginalPositions.set(!tpl.showOriginalPositions.get());
|
||||||
}
|
},
|
||||||
|
|
||||||
OriginalPositionsViewComponent.register('originalPositionsView');
|
'click .js-refresh-history'(evt, tpl) {
|
||||||
|
tpl.loadBoardHistory();
|
||||||
|
},
|
||||||
|
|
||||||
export default OriginalPositionsViewComponent;
|
'click .js-filter-type'(evt, tpl) {
|
||||||
|
const type = evt.currentTarget.dataset.filterType;
|
||||||
|
tpl.filterType.set(type);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,196 +4,202 @@ require('/client/lib/jquery-ui.js')
|
||||||
|
|
||||||
const { calculateIndex } = Utils;
|
const { calculateIndex } = Utils;
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.list.onCreated(function () {
|
||||||
// Proxy
|
this.newCardFormIsVisible = new ReactiveVar(true);
|
||||||
openForm(options) {
|
|
||||||
this.childComponents('listBody')[0].openForm(options);
|
|
||||||
},
|
|
||||||
|
|
||||||
onCreated() {
|
// Proxy - find the listBody child template instance via the DOM
|
||||||
this.newCardFormIsVisible = new ReactiveVar(true);
|
this.openForm = (options) => {
|
||||||
},
|
const listBodyEl = this.find('.list-body');
|
||||||
|
const view = listBodyEl && Blaze.getView(listBodyEl, 'Template.listBody');
|
||||||
|
const listBodyInstance = view?.templateInstance?.();
|
||||||
|
if (listBodyInstance) listBodyInstance.openForm(options);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// The jquery UI sortable library is the best solution I've found so far. I
|
// The jquery UI sortable library is the best solution I've found so far. I
|
||||||
// tried sortable and dragula but they were not powerful enough four our use
|
// tried sortable and dragula but they were not powerful enough four our use
|
||||||
// case. I also considered writing/forking a drag-and-drop + sortable library
|
// case. I also considered writing/forking a drag-and-drop + sortable library
|
||||||
// but it's probably too much work.
|
// but it's probably too much work.
|
||||||
// By calling asking the sortable library to cancel its move on the `stop`
|
// By calling asking the sortable library to cancel its move on the `stop`
|
||||||
// callback, we basically solve all issues related to reactive updates. A
|
// callback, we basically solve all issues related to reactive updates. A
|
||||||
// comment below provides further details.
|
// comment below provides further details.
|
||||||
onRendered() {
|
Template.list.onRendered(function () {
|
||||||
const boardComponent = this.parentComponent().parentComponent();
|
const boardBodyEl = this.firstNode?.parentElement?.closest?.('.board-body') ||
|
||||||
|
document.querySelector('.board-body');
|
||||||
|
const boardView = boardBodyEl && Blaze.getView(boardBodyEl, 'Template.boardBody');
|
||||||
|
const boardComponent = boardView?.templateInstance?.();
|
||||||
|
|
||||||
// Initialize list resize functionality immediately
|
// Initialize list resize functionality immediately
|
||||||
this.initializeListResize();
|
this.initializeListResize();
|
||||||
|
|
||||||
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
|
||||||
const $cards = this.$('.js-minicards');
|
const $cards = this.$('.js-minicards');
|
||||||
|
|
||||||
$cards.sortable({
|
$cards.sortable({
|
||||||
connectWith: '.js-minicards:not(.js-list-full)',
|
connectWith: '.js-minicards:not(.js-list-full)',
|
||||||
tolerance: 'pointer',
|
tolerance: 'pointer',
|
||||||
appendTo: '.board-canvas',
|
appendTo: '.board-canvas',
|
||||||
helper(evt, item) {
|
helper(evt, item) {
|
||||||
const helper = item.clone();
|
const helper = item.clone();
|
||||||
if (MultiSelection.isActive()) {
|
if (MultiSelection.isActive()) {
|
||||||
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
const andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
|
||||||
if (andNOthers > 0) {
|
if (andNOthers > 0) {
|
||||||
helper.append(
|
helper.append(
|
||||||
$(
|
$(
|
||||||
Blaze.toHTML(
|
Blaze.toHTML(
|
||||||
HTML.DIV(
|
HTML.DIV(
|
||||||
{ class: 'and-n-other' },
|
{ class: 'and-n-other' },
|
||||||
TAPi18n.__('and-n-other-card', { count: andNOthers }),
|
TAPi18n.__('and-n-other-card', { count: andNOthers }),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
return helper;
|
}
|
||||||
},
|
return helper;
|
||||||
distance: 7,
|
},
|
||||||
items: itemsSelector,
|
distance: 7,
|
||||||
placeholder: 'minicard-wrapper placeholder',
|
items: itemsSelector,
|
||||||
scrollSpeed: 10,
|
placeholder: 'minicard-wrapper placeholder',
|
||||||
start(evt, ui) {
|
scrollSpeed: 10,
|
||||||
ui.helper.css('z-index', 1000);
|
start(evt, ui) {
|
||||||
ui.placeholder.height(ui.helper.height());
|
ui.helper.css('z-index', 1000);
|
||||||
EscapeActions.executeUpTo('popup-close');
|
ui.placeholder.height(ui.helper.height());
|
||||||
boardComponent.setIsDragging(true);
|
EscapeActions.executeUpTo('popup-close');
|
||||||
},
|
if (boardComponent) boardComponent.setIsDragging(true);
|
||||||
stop(evt, ui) {
|
},
|
||||||
// To attribute the new index number, we need to get the DOM element
|
stop(evt, ui) {
|
||||||
// of the previous and the following card -- if any.
|
// To attribute the new index number, we need to get the DOM element
|
||||||
const prevCardDom = ui.item.prev('.js-minicard').get(0);
|
// of the previous and the following card -- if any.
|
||||||
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
const prevCardDom = ui.item.prev('.js-minicard').get(0);
|
||||||
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
const nextCardDom = ui.item.next('.js-minicard').get(0);
|
||||||
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
|
const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1;
|
||||||
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
|
const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards);
|
||||||
const currentBoard = Utils.getCurrentBoard();
|
const listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
|
||||||
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
|
const currentBoard = Utils.getCurrentBoard();
|
||||||
let targetSwimlaneId = null;
|
const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id;
|
||||||
|
let targetSwimlaneId = null;
|
||||||
|
|
||||||
// only set a new swimelane ID if the swimlanes view is active
|
// only set a new swimelane ID if the swimlanes view is active
|
||||||
if (
|
if (
|
||||||
Utils.boardView() === 'board-view-swimlanes' ||
|
Utils.boardView() === 'board-view-swimlanes' ||
|
||||||
currentBoard.isTemplatesBoard()
|
currentBoard.isTemplatesBoard()
|
||||||
)
|
)
|
||||||
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
|
targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0))
|
||||||
._id;
|
._id;
|
||||||
|
|
||||||
// Normally the jquery-ui sortable library moves the dragged DOM element
|
// Normally the jquery-ui sortable library moves the dragged DOM element
|
||||||
// to its new position, which disrupts Blaze reactive updates mechanism
|
// to its new position, which disrupts Blaze reactive updates mechanism
|
||||||
// (especially when we move the last card of a list, or when multiple
|
// (especially when we move the last card of a list, or when multiple
|
||||||
// users move some cards at the same time). To prevent these UX glitches
|
// users move some cards at the same time). To prevent these UX glitches
|
||||||
// we ask sortable to gracefully cancel the move, and to put back the
|
// we ask sortable to gracefully cancel the move, and to put back the
|
||||||
// DOM in its initial state. The card move is then handled reactively by
|
// DOM in its initial state. The card move is then handled reactively by
|
||||||
// Blaze with the below query.
|
// Blaze with the below query.
|
||||||
$cards.sortable('cancel');
|
$cards.sortable('cancel');
|
||||||
|
|
||||||
if (MultiSelection.isActive()) {
|
if (MultiSelection.isActive()) {
|
||||||
ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => {
|
ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => {
|
||||||
const newSwimlaneId = targetSwimlaneId
|
|
||||||
? targetSwimlaneId
|
|
||||||
: card.swimlaneId || defaultSwimlaneId;
|
|
||||||
card.move(
|
|
||||||
currentBoard._id,
|
|
||||||
newSwimlaneId,
|
|
||||||
listId,
|
|
||||||
sortIndex.base + i * sortIndex.increment,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const cardDomElement = ui.item.get(0);
|
|
||||||
const card = Blaze.getData(cardDomElement);
|
|
||||||
const newSwimlaneId = targetSwimlaneId
|
const newSwimlaneId = targetSwimlaneId
|
||||||
? targetSwimlaneId
|
? targetSwimlaneId
|
||||||
: card.swimlaneId || defaultSwimlaneId;
|
: card.swimlaneId || defaultSwimlaneId;
|
||||||
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
|
card.move(
|
||||||
}
|
currentBoard._id,
|
||||||
boardComponent.setIsDragging(false);
|
newSwimlaneId,
|
||||||
},
|
listId,
|
||||||
sort(event, ui) {
|
sortIndex.base + i * sortIndex.increment,
|
||||||
const $boardCanvas = $('.board-canvas');
|
);
|
||||||
const boardCanvas = $boardCanvas[0];
|
|
||||||
|
|
||||||
if (event.pageX < 10) { // scroll to the left
|
|
||||||
boardCanvas.scrollLeft -= 15;
|
|
||||||
ui.helper[0].offsetLeft -= 15;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
event.pageX > boardCanvas.offsetWidth - 10 &&
|
|
||||||
boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible
|
|
||||||
) { // scroll to the right
|
|
||||||
boardCanvas.scrollLeft += 15;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
event.pageY > boardCanvas.offsetHeight - 10 &&
|
|
||||||
event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible
|
|
||||||
) { // scroll to the bottom
|
|
||||||
boardCanvas.scrollTop += 15;
|
|
||||||
}
|
|
||||||
if (event.pageY < 10) { // scroll to the top
|
|
||||||
boardCanvas.scrollTop -= 15;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activate(event, ui) {
|
|
||||||
const $boardCanvas = $('.board-canvas');
|
|
||||||
const boardCanvas = $boardCanvas[0];
|
|
||||||
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
|
|
||||||
// https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/
|
|
||||||
$boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop);
|
|
||||||
// https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386
|
|
||||||
$boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.autorun(() => {
|
|
||||||
if ($cards.data('uiSortable') || $cards.data('sortable')) {
|
|
||||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
|
||||||
$cards.sortable('option', 'handle', '.handle');
|
|
||||||
} else {
|
|
||||||
$cards.sortable('option', 'handle', '.minicard');
|
|
||||||
}
|
|
||||||
|
|
||||||
$cards.sortable(
|
|
||||||
'option',
|
|
||||||
'disabled',
|
|
||||||
// Disable drag-dropping when user is not member
|
|
||||||
!Utils.canModifyBoard(),
|
|
||||||
// Not disable drag-dropping while in multi-selection mode
|
|
||||||
// MultiSelection.isActive() || !Utils.canModifyBoard(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// We want to re-run this function any time a card is added.
|
|
||||||
this.autorun(() => {
|
|
||||||
const currentBoardId = Tracker.nonreactive(() => {
|
|
||||||
return Session.get('currentBoard');
|
|
||||||
});
|
|
||||||
Tracker.afterFlush(() => {
|
|
||||||
$cards.find(itemsSelector).droppable({
|
|
||||||
hoverClass: 'draggable-hover-card',
|
|
||||||
accept: '.js-member,.js-label',
|
|
||||||
drop(event, ui) {
|
|
||||||
const cardId = Blaze.getData(this)._id;
|
|
||||||
const card = ReactiveCache.getCard(cardId);
|
|
||||||
|
|
||||||
if (ui.draggable.hasClass('js-member')) {
|
|
||||||
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
|
||||||
card.assignMember(memberId);
|
|
||||||
} else {
|
|
||||||
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
|
||||||
card.addLabel(labelId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
const cardDomElement = ui.item.get(0);
|
||||||
|
const card = Blaze.getData(cardDomElement);
|
||||||
|
const newSwimlaneId = targetSwimlaneId
|
||||||
|
? targetSwimlaneId
|
||||||
|
: card.swimlaneId || defaultSwimlaneId;
|
||||||
|
card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base);
|
||||||
|
}
|
||||||
|
if (boardComponent) boardComponent.setIsDragging(false);
|
||||||
|
},
|
||||||
|
sort(event, ui) {
|
||||||
|
const $boardCanvas = $('.board-canvas');
|
||||||
|
const boardCanvas = $boardCanvas[0];
|
||||||
|
|
||||||
|
if (event.pageX < 10) { // scroll to the left
|
||||||
|
boardCanvas.scrollLeft -= 15;
|
||||||
|
ui.helper[0].offsetLeft -= 15;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
event.pageX > boardCanvas.offsetWidth - 10 &&
|
||||||
|
boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible
|
||||||
|
) { // scroll to the right
|
||||||
|
boardCanvas.scrollLeft += 15;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
event.pageY > boardCanvas.offsetHeight - 10 &&
|
||||||
|
event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible
|
||||||
|
) { // scroll to the bottom
|
||||||
|
boardCanvas.scrollTop += 15;
|
||||||
|
}
|
||||||
|
if (event.pageY < 10) { // scroll to the top
|
||||||
|
boardCanvas.scrollTop -= 15;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activate(event, ui) {
|
||||||
|
const $boardCanvas = $('.board-canvas');
|
||||||
|
const boardCanvas = $boardCanvas[0];
|
||||||
|
// scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax)
|
||||||
|
// https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/
|
||||||
|
$boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop);
|
||||||
|
// https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386
|
||||||
|
$boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.autorun(() => {
|
||||||
|
if ($cards.data('uiSortable') || $cards.data('sortable')) {
|
||||||
|
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||||
|
$cards.sortable('option', 'handle', '.handle');
|
||||||
|
} else {
|
||||||
|
$cards.sortable('option', 'handle', '.minicard');
|
||||||
|
}
|
||||||
|
|
||||||
|
$cards.sortable(
|
||||||
|
'option',
|
||||||
|
'disabled',
|
||||||
|
// Disable drag-dropping when user is not member
|
||||||
|
!Utils.canModifyBoard(),
|
||||||
|
// Not disable drag-dropping while in multi-selection mode
|
||||||
|
// MultiSelection.isActive() || !Utils.canModifyBoard(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// We want to re-run this function any time a card is added.
|
||||||
|
this.autorun(() => {
|
||||||
|
const currentBoardId = Tracker.nonreactive(() => {
|
||||||
|
return Session.get('currentBoard');
|
||||||
|
});
|
||||||
|
Tracker.afterFlush(() => {
|
||||||
|
$cards.find(itemsSelector).droppable({
|
||||||
|
hoverClass: 'draggable-hover-card',
|
||||||
|
accept: '.js-member,.js-label',
|
||||||
|
drop(event, ui) {
|
||||||
|
const cardId = Blaze.getData(this)._id;
|
||||||
|
const card = ReactiveCache.getCard(cardId);
|
||||||
|
|
||||||
|
if (ui.draggable.hasClass('js-member')) {
|
||||||
|
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
||||||
|
card.assignMember(memberId);
|
||||||
|
} else {
|
||||||
|
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
||||||
|
card.addLabel(labelId);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.list.helpers({
|
||||||
listWidth() {
|
listWidth() {
|
||||||
const user = ReactiveCache.getCurrentUser();
|
const user = ReactiveCache.getCurrentUser();
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
|
|
@ -254,7 +260,16 @@ BlazeComponent.extendComponent({
|
||||||
return user.isAutoWidth(list.boardId);
|
return user.isAutoWidth(list.boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
initializeListResize() {
|
collapsed() {
|
||||||
|
return Utils.getListCollapseState(this);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// initializeListResize as a method on the template instance
|
||||||
|
Template.list.onCreated(function () {
|
||||||
|
const tpl = this;
|
||||||
|
|
||||||
|
tpl.initializeListResize = function () {
|
||||||
// Check if we're still in a valid template context
|
// Check if we're still in a valid template context
|
||||||
if (!Template.currentData()) {
|
if (!Template.currentData()) {
|
||||||
console.warn('No current template data available for list resize initialization');
|
console.warn('No current template data available for list resize initialization');
|
||||||
|
|
@ -262,23 +277,31 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
const $list = this.$('.js-list');
|
const $list = tpl.$('.js-list');
|
||||||
const $resizeHandle = this.$('.js-list-resize-handle');
|
const $resizeHandle = tpl.$('.js-list-resize-handle');
|
||||||
|
|
||||||
// Check if elements exist
|
// Check if elements exist
|
||||||
if (!$list.length || !$resizeHandle.length) {
|
if (!$list.length || !$resizeHandle.length) {
|
||||||
console.warn('List or resize handle not found, retrying in 100ms');
|
console.warn('List or resize handle not found, retrying in 100ms');
|
||||||
Meteor.setTimeout(() => {
|
Meteor.setTimeout(() => {
|
||||||
if (!this.isDestroyed) {
|
if (!tpl.isDestroyed) {
|
||||||
this.initializeListResize();
|
tpl.initializeListResize();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to get autoWidth state
|
||||||
|
const getAutoWidth = () => {
|
||||||
|
const user = ReactiveCache.getCurrentUser();
|
||||||
|
const listData = Template.currentData();
|
||||||
|
if (!user) return false;
|
||||||
|
return user.isAutoWidth(listData.boardId);
|
||||||
|
};
|
||||||
|
|
||||||
// Reactively show/hide resize handle based on collapse and auto-width state
|
// Reactively show/hide resize handle based on collapse and auto-width state
|
||||||
this.autorun(() => {
|
tpl.autorun(() => {
|
||||||
const isAutoWidth = this.autoWidth();
|
const isAutoWidth = getAutoWidth();
|
||||||
const isCollapsed = Utils.getListCollapseState(list);
|
const isCollapsed = Utils.getListCollapseState(list);
|
||||||
if (isCollapsed || isAutoWidth) {
|
if (isCollapsed || isAutoWidth) {
|
||||||
$resizeHandle.hide();
|
$resizeHandle.hide();
|
||||||
|
|
@ -291,20 +314,36 @@ BlazeComponent.extendComponent({
|
||||||
let startX = 0;
|
let startX = 0;
|
||||||
let startWidth = 0;
|
let startWidth = 0;
|
||||||
let minWidth = 270; // Minimum width matching system default
|
let minWidth = 270; // Minimum width matching system default
|
||||||
let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers
|
|
||||||
const component = this; // Store reference to component for use in event handlers
|
// Get listConstraint value
|
||||||
|
const getListConstraint = () => {
|
||||||
|
const user = ReactiveCache.getCurrentUser();
|
||||||
|
const listData = Template.currentData();
|
||||||
|
if (!listData) return 550;
|
||||||
|
if (user) {
|
||||||
|
return user.getListConstraintFromStorage(listData.boardId, listData._id);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem('wekan-list-constraints');
|
||||||
|
if (stored) {
|
||||||
|
const constraints = JSON.parse(stored);
|
||||||
|
if (constraints[listData.boardId] && constraints[listData.boardId][listData._id]) {
|
||||||
|
return constraints[listData.boardId][listData._id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return 550;
|
||||||
|
};
|
||||||
|
|
||||||
const startResize = (e) => {
|
const startResize = (e) => {
|
||||||
isResizing = true;
|
isResizing = true;
|
||||||
startX = e.pageX || e.originalEvent.touches[0].pageX;
|
startX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||||
startWidth = $list.outerWidth();
|
startWidth = $list.outerWidth();
|
||||||
|
|
||||||
|
|
||||||
// Add visual feedback
|
// Add visual feedback
|
||||||
$list.addClass('list-resizing');
|
$list.addClass('list-resizing');
|
||||||
$('body').addClass('list-resizing-active');
|
$('body').addClass('list-resizing-active');
|
||||||
|
|
||||||
|
|
||||||
// Prevent text selection during resize
|
// Prevent text selection during resize
|
||||||
$('body').css('user-select', 'none');
|
$('body').css('user-select', 'none');
|
||||||
|
|
||||||
|
|
@ -331,7 +370,6 @@ BlazeComponent.extendComponent({
|
||||||
$list[0].style.setProperty('flex-grow', '0');
|
$list[0].style.setProperty('flex-grow', '0');
|
||||||
$list[0].style.setProperty('flex-shrink', '0');
|
$list[0].style.setProperty('flex-shrink', '0');
|
||||||
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
@ -345,6 +383,7 @@ BlazeComponent.extendComponent({
|
||||||
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||||
const deltaX = currentX - startX;
|
const deltaX = currentX - startX;
|
||||||
const finalWidth = Math.max(minWidth, startWidth + deltaX);
|
const finalWidth = Math.max(minWidth, startWidth + deltaX);
|
||||||
|
const listConstraint = getListConstraint();
|
||||||
|
|
||||||
// Ensure the final width is applied
|
// Ensure the final width is applied
|
||||||
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
|
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
|
||||||
|
|
@ -361,14 +400,10 @@ BlazeComponent.extendComponent({
|
||||||
$('body').removeClass('list-resizing-active');
|
$('body').removeClass('list-resizing-active');
|
||||||
$('body').css('user-select', '');
|
$('body').css('user-select', '');
|
||||||
|
|
||||||
// Keep the CSS custom property for persistent width
|
|
||||||
// The CSS custom property will remain on the element to maintain the width
|
|
||||||
|
|
||||||
// Save the new width using the existing system
|
// Save the new width using the existing system
|
||||||
const boardId = list.boardId;
|
const boardId = list.boardId;
|
||||||
const listId = list._id;
|
const listId = list._id;
|
||||||
|
|
||||||
// Use the new storage method that handles both logged-in and non-logged-in users
|
|
||||||
if (process.env.DEBUG === 'true') {
|
if (process.env.DEBUG === 'true') {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -428,17 +463,15 @@ BlazeComponent.extendComponent({
|
||||||
$(document).on('touchmove', doResize, { passive: false });
|
$(document).on('touchmove', doResize, { passive: false });
|
||||||
$(document).on('touchend', stopResize, { passive: false });
|
$(document).on('touchend', stopResize, { passive: false });
|
||||||
|
|
||||||
|
|
||||||
// Prevent dragscroll interference
|
// Prevent dragscroll interference
|
||||||
$resizeHandle.on('mousedown', (e) => {
|
$resizeHandle.on('mousedown', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Reactively update resize handle visibility when auto-width or collapse changes
|
// Reactively update resize handle visibility when auto-width or collapse changes
|
||||||
component.autorun(() => {
|
tpl.autorun(() => {
|
||||||
const collapsed = Utils.getListCollapseState(list);
|
const collapsed = Utils.getListCollapseState(list);
|
||||||
if (component.autoWidth() || collapsed) {
|
if (getAutoWidth() || collapsed) {
|
||||||
$resizeHandle.hide();
|
$resizeHandle.hide();
|
||||||
} else {
|
} else {
|
||||||
$resizeHandle.show();
|
$resizeHandle.show();
|
||||||
|
|
@ -446,19 +479,13 @@ BlazeComponent.extendComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clean up on component destruction
|
// Clean up on component destruction
|
||||||
component.onDestroyed(() => {
|
tpl.view.onViewDestroyed(() => {
|
||||||
$(document).off('mousemove', doResize);
|
$(document).off('mousemove', doResize);
|
||||||
$(document).off('mouseup', stopResize);
|
$(document).off('mouseup', stopResize);
|
||||||
$(document).off('touchmove', doResize);
|
$(document).off('touchmove', doResize);
|
||||||
$(document).off('touchend', stopResize);
|
$(document).off('touchend', stopResize);
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
}).register('list');
|
|
||||||
|
|
||||||
Template.list.helpers({
|
|
||||||
collapsed() {
|
|
||||||
return Utils.getListCollapseState(this);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.miniList.events({
|
Template.miniList.events({
|
||||||
|
|
@ -468,15 +495,6 @@ Template.miniList.events({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enable drag-reorder for collapsed lists from .js-collapsed-list-drag area
|
// NOTE: Collapsed list drag-reorder was previously here but referenced
|
||||||
this.$('.js-collapsed-list-drag').draggable({
|
// boardComponent from an outer scope. If needed, this should be moved
|
||||||
axis: 'x',
|
// into Template.list.onRendered where boardComponent is available.
|
||||||
helper: 'clone',
|
|
||||||
revert: 'invalid',
|
|
||||||
start(evt, ui) {
|
|
||||||
boardComponent.setIsDragging(true);
|
|
||||||
},
|
|
||||||
stop(evt, ui) {
|
|
||||||
boardComponent.setIsDragging(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -8,13 +8,13 @@ Meteor.startup(() => {
|
||||||
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
|
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.listHeader.helpers({
|
||||||
canSeeAddCard() {
|
canSeeAddCard() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
return (
|
return (
|
||||||
(!list.getWipLimit('enabled') ||
|
(!list.getWipLimit('enabled') ||
|
||||||
list.getWipLimit('soft') ||
|
list.getWipLimit('soft') ||
|
||||||
!this.reachedWipLimit()) &&
|
!Template.instance().reachedWipLimit()) &&
|
||||||
!ReactiveCache.getCurrentUser().isWorker()
|
!ReactiveCache.getCurrentUser().isWorker()
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -22,42 +22,19 @@ BlazeComponent.extendComponent({
|
||||||
isBoardAdmin() {
|
isBoardAdmin() {
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||||
},
|
},
|
||||||
async starred(check = undefined) {
|
|
||||||
|
starred() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
const status = list.isStarred();
|
return list.isStarred();
|
||||||
if (check === undefined) {
|
|
||||||
// just check
|
|
||||||
return status;
|
|
||||||
} else {
|
|
||||||
await list.star(!status);
|
|
||||||
return !status;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
collapsed(check = undefined) {
|
|
||||||
|
collapsed() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
const status = Utils.getListCollapseState(list);
|
return Utils.getListCollapseState(list);
|
||||||
if (check === undefined) {
|
|
||||||
// just check
|
|
||||||
return status;
|
|
||||||
} else {
|
|
||||||
const next = typeof check === 'boolean' ? check : !status;
|
|
||||||
Utils.setListCollapseState(list, next);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async editTitle(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const newTitle = this.childComponents('inlinedForm')[0]
|
|
||||||
.getValue()
|
|
||||||
.trim();
|
|
||||||
const list = this.currentData();
|
|
||||||
if (newTitle) {
|
|
||||||
await list.rename(newTitle.trim());
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isWatching() {
|
isWatching() {
|
||||||
const list = this.currentData();
|
const list = Template.currentData();
|
||||||
return list.findWatcher(Meteor.userId());
|
return list.findWatcher(Meteor.userId());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -73,10 +50,9 @@ BlazeComponent.extendComponent({
|
||||||
cardsCount() {
|
cardsCount() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
let swimlaneId = '';
|
let swimlaneId = '';
|
||||||
if (Utils.boardView() === 'board-view-swimlanes')
|
if (Utils.boardView() === 'board-view-swimlanes') {
|
||||||
swimlaneId = this.parentComponent()
|
swimlaneId = list.swimlaneId || '';
|
||||||
.parentComponent()
|
}
|
||||||
.data()._id;
|
|
||||||
|
|
||||||
const ret = list.cards(swimlaneId).length;
|
const ret = list.cards(swimlaneId).length;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -99,7 +75,8 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
showCardsCountForList(count) {
|
showCardsCountForList(count) {
|
||||||
const limit = this.limitToShowCardsCount();
|
const currentUser = ReactiveCache.getCurrentUser();
|
||||||
|
const limit = currentUser ? currentUser.getLimitToShowCardsCount() : false;
|
||||||
return limit >= 0 && count >= limit;
|
return limit >= 0 && count >= limit;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -111,40 +88,6 @@ BlazeComponent.extendComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
events() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
'click .js-list-star'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.starred(!this.starred());
|
|
||||||
},
|
|
||||||
'click .js-collapse'(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.collapsed(!this.collapsed());
|
|
||||||
},
|
|
||||||
'click .js-open-list-menu': Popup.open('listAction'),
|
|
||||||
'click .js-add-card.list-header-plus-top'(event) {
|
|
||||||
const listDom = $(event.target).parents(
|
|
||||||
`#js-list-${this.currentData()._id}`,
|
|
||||||
)[0];
|
|
||||||
const listComponent = BlazeComponent.getComponentForElement(listDom);
|
|
||||||
listComponent.openForm({
|
|
||||||
position: 'top',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
'click .js-unselect-list'() {
|
|
||||||
Session.set('currentList', null);
|
|
||||||
},
|
|
||||||
submit: this.editTitle,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
}).register('listHeader');
|
|
||||||
|
|
||||||
Template.listHeader.helpers({
|
|
||||||
isBoardAdmin() {
|
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
|
||||||
},
|
|
||||||
numberFieldsSum() {
|
numberFieldsSum() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
if (!list) return 0;
|
if (!list) return 0;
|
||||||
|
|
@ -177,6 +120,7 @@ Template.listHeader.helpers({
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasNumberFieldsSum() {
|
hasNumberFieldsSum() {
|
||||||
const boardId = Session.get('currentBoard');
|
const boardId = Session.get('currentBoard');
|
||||||
const fields = ReactiveCache.getCustomFields({
|
const fields = ReactiveCache.getCustomFields({
|
||||||
|
|
@ -188,6 +132,56 @@ Template.listHeader.helpers({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper function on template instance for reachedWipLimit check
|
||||||
|
Template.listHeader.onCreated(function () {
|
||||||
|
this.reachedWipLimit = function () {
|
||||||
|
const list = Template.currentData();
|
||||||
|
return (
|
||||||
|
list.getWipLimit('enabled') &&
|
||||||
|
list.getWipLimit('value') <= list.cards().length
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.listHeader.events({
|
||||||
|
async 'click .js-list-star'(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const list = Template.currentData();
|
||||||
|
const status = list.isStarred();
|
||||||
|
await list.star(!status);
|
||||||
|
},
|
||||||
|
'click .js-collapse'(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const list = Template.currentData();
|
||||||
|
const status = Utils.getListCollapseState(list);
|
||||||
|
Utils.setListCollapseState(list, !status);
|
||||||
|
},
|
||||||
|
'click .js-open-list-menu': Popup.open('listAction'),
|
||||||
|
'click .js-add-card.list-header-plus-top'(event) {
|
||||||
|
const listDom = $(event.target).parents(
|
||||||
|
`#js-list-${Template.currentData()._id}`,
|
||||||
|
)[0];
|
||||||
|
const view = Blaze.getView(listDom, 'Template.listBody');
|
||||||
|
const listComponent = view?.templateInstance?.();
|
||||||
|
if (listComponent) {
|
||||||
|
listComponent.openForm({
|
||||||
|
position: 'top',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .js-unselect-list'() {
|
||||||
|
Session.set('currentList', null);
|
||||||
|
},
|
||||||
|
async 'submit'(event, tpl) {
|
||||||
|
event.preventDefault();
|
||||||
|
const newTitle = tpl.$('textarea,input[type=text]').val()?.trim();
|
||||||
|
const list = Template.currentData();
|
||||||
|
if (newTitle) {
|
||||||
|
await list.rename(newTitle.trim());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.listActionPopup.helpers({
|
Template.listActionPopup.helpers({
|
||||||
isBoardAdmin() {
|
isBoardAdmin() {
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||||
|
|
@ -206,7 +200,8 @@ Template.listActionPopup.events({
|
||||||
'click .js-list-subscribe'() {},
|
'click .js-list-subscribe'() {},
|
||||||
'click .js-add-card.list-header-plus-top'(event) {
|
'click .js-add-card.list-header-plus-top'(event) {
|
||||||
const listDom = $(`#js-list-${this._id}`)[0];
|
const listDom = $(`#js-list-${this._id}`)[0];
|
||||||
const listComponent = BlazeComponent.getComponentForElement(listDom);
|
const view = Blaze.getView(listDom, 'Template.listBody');
|
||||||
|
const listComponent = view?.templateInstance?.();
|
||||||
if (listComponent) {
|
if (listComponent) {
|
||||||
listComponent.openForm({
|
listComponent.openForm({
|
||||||
position: 'top',
|
position: 'top',
|
||||||
|
|
@ -216,7 +211,8 @@ Template.listActionPopup.events({
|
||||||
},
|
},
|
||||||
'click .js-add-card.list-header-plus-bottom'(event) {
|
'click .js-add-card.list-header-plus-bottom'(event) {
|
||||||
const listDom = $(`#js-list-${this._id}`)[0];
|
const listDom = $(`#js-list-${this._id}`)[0];
|
||||||
const listComponent = BlazeComponent.getComponentForElement(listDom);
|
const view = Blaze.getView(listDom, 'Template.listBody');
|
||||||
|
const listComponent = view?.templateInstance?.();
|
||||||
if (listComponent) {
|
if (listComponent) {
|
||||||
listComponent.openForm({
|
listComponent.openForm({
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
|
|
@ -248,50 +244,7 @@ Template.listActionPopup.events({
|
||||||
'click .js-more': Popup.open('listMore'),
|
'click .js-more': Popup.open('listMore'),
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.setWipLimitPopup.helpers({
|
||||||
applyWipLimit() {
|
|
||||||
const list = Template.currentData();
|
|
||||||
const limit = parseInt(
|
|
||||||
Template.instance()
|
|
||||||
.$('.wip-limit-value')
|
|
||||||
.val(),
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (limit < list.cards().length && !list.getWipLimit('soft')) {
|
|
||||||
Template.instance()
|
|
||||||
.$('.wip-limit-error')
|
|
||||||
.click();
|
|
||||||
} else {
|
|
||||||
Meteor.call('applyWipLimit', list._id, limit);
|
|
||||||
Popup.back();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async enableSoftLimit() {
|
|
||||||
const list = Template.currentData();
|
|
||||||
|
|
||||||
if (
|
|
||||||
list.getWipLimit('soft') &&
|
|
||||||
list.getWipLimit('value') < list.cards().length
|
|
||||||
) {
|
|
||||||
await list.setWipLimit(list.cards().length);
|
|
||||||
}
|
|
||||||
Meteor.call('enableSoftLimit', Template.currentData()._id);
|
|
||||||
},
|
|
||||||
|
|
||||||
async enableWipLimit() {
|
|
||||||
const list = Template.currentData();
|
|
||||||
// Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
|
|
||||||
if (
|
|
||||||
!list.getWipLimit('enabled') &&
|
|
||||||
list.getWipLimit('value') < list.cards().length
|
|
||||||
) {
|
|
||||||
await list.setWipLimit(list.cards().length);
|
|
||||||
}
|
|
||||||
Meteor.call('enableWipLimit', list._id);
|
|
||||||
},
|
|
||||||
|
|
||||||
isWipLimitSoft() {
|
isWipLimitSoft() {
|
||||||
return Template.currentData().getWipLimit('soft');
|
return Template.currentData().getWipLimit('soft');
|
||||||
},
|
},
|
||||||
|
|
@ -303,125 +256,114 @@ BlazeComponent.extendComponent({
|
||||||
wipLimitValue() {
|
wipLimitValue() {
|
||||||
return Template.currentData().getWipLimit('value');
|
return Template.currentData().getWipLimit('value');
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.setWipLimitPopup.events({
|
||||||
return [
|
async 'click .js-enable-wip-limit'() {
|
||||||
{
|
const list = Template.currentData();
|
||||||
'click .js-enable-wip-limit': this.enableWipLimit,
|
// Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
|
||||||
'click .wip-limit-apply': this.applyWipLimit,
|
if (
|
||||||
'click .wip-limit-error': Popup.open('wipLimitError'),
|
!list.getWipLimit('enabled') &&
|
||||||
'click .materialCheckBox': this.enableSoftLimit,
|
list.getWipLimit('value') < list.cards().length
|
||||||
},
|
) {
|
||||||
];
|
await list.setWipLimit(list.cards().length);
|
||||||
|
}
|
||||||
|
Meteor.call('enableWipLimit', list._id);
|
||||||
},
|
},
|
||||||
}).register('setWipLimitPopup');
|
'click .wip-limit-apply'(event, tpl) {
|
||||||
|
const list = Template.currentData();
|
||||||
|
const limit = parseInt(
|
||||||
|
tpl.$('.wip-limit-value').val(),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (limit < list.cards().length && !list.getWipLimit('soft')) {
|
||||||
|
tpl.$('.wip-limit-error').click();
|
||||||
|
} else {
|
||||||
|
Meteor.call('applyWipLimit', list._id, limit);
|
||||||
|
Popup.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .wip-limit-error': Popup.open('wipLimitError'),
|
||||||
|
async 'click .materialCheckBox'() {
|
||||||
|
const list = Template.currentData();
|
||||||
|
|
||||||
|
if (
|
||||||
|
list.getWipLimit('soft') &&
|
||||||
|
list.getWipLimit('value') < list.cards().length
|
||||||
|
) {
|
||||||
|
await list.setWipLimit(list.cards().length);
|
||||||
|
}
|
||||||
|
Meteor.call('enableSoftLimit', Template.currentData()._id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Template.listMorePopup.events({
|
Template.listMorePopup.events({
|
||||||
'click .js-delete': Popup.afterConfirm('listDelete', function() {
|
'click .js-delete': Popup.afterConfirm('listDelete', function() {
|
||||||
Popup.back();
|
Popup.back();
|
||||||
const allCards = this.allCards();
|
const list = Lists.findOne(this._id);
|
||||||
|
if (!list) return;
|
||||||
|
const allCards = list.allCards();
|
||||||
const allCardIds = _.pluck(allCards, '_id');
|
const allCardIds = _.pluck(allCards, '_id');
|
||||||
// it's okay if the linked cards are on the same list
|
// it's okay if the linked cards are on the same list
|
||||||
if (
|
if (
|
||||||
ReactiveCache.getCards({
|
ReactiveCache.getCards({
|
||||||
$and: [
|
$and: [
|
||||||
{ listId: { $ne: this._id } },
|
{ listId: { $ne: list._id } },
|
||||||
{ linkedId: { $in: allCardIds } },
|
{ linkedId: { $in: allCardIds } },
|
||||||
],
|
],
|
||||||
}).length === 0
|
}).length === 0
|
||||||
) {
|
) {
|
||||||
allCardIds.map(_id => Cards.remove(_id));
|
allCardIds.map(_id => Cards.remove(_id));
|
||||||
Lists.remove(this._id);
|
Lists.remove(list._id);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Figure out more informative message.
|
|
||||||
// Popup with a hint that the list cannot be deleted as there are
|
|
||||||
// linked cards. We can adapt the query above so we can list the linked
|
|
||||||
// cards.
|
|
||||||
// Related:
|
|
||||||
// client/components/cards/cardDetails.js about line 969
|
|
||||||
// https://github.com/wekan/wekan/issues/2785
|
|
||||||
const message = `${TAPi18n.__(
|
const message = `${TAPi18n.__(
|
||||||
'delete-linked-cards-before-this-list',
|
'delete-linked-cards-before-this-list',
|
||||||
)} linkedId: ${
|
)} linkedId: ${
|
||||||
this._id
|
list._id
|
||||||
} at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`;
|
} at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`;
|
||||||
alert(message);
|
alert(message);
|
||||||
}
|
}
|
||||||
Utils.goBoardId(this.boardId);
|
Utils.goBoardId(list.boardId);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.listHeader.helpers({
|
Template.setListColorPopup.onCreated(function () {
|
||||||
isBoardAdmin() {
|
const data = Template.currentData();
|
||||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
this.currentList = Lists.findOne(data._id) || data;
|
||||||
},
|
this.currentColor = new ReactiveVar(this.currentList.color);
|
||||||
});
|
});
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
Template.setListColorPopup.helpers({
|
||||||
onCreated() {
|
|
||||||
this.currentList = this.currentData();
|
|
||||||
this.currentColor = new ReactiveVar(this.currentList.color);
|
|
||||||
},
|
|
||||||
|
|
||||||
colors() {
|
colors() {
|
||||||
return listsColors.map(color => ({ color, name: '' }));
|
return listsColors.map(color => ({ color, name: '' }));
|
||||||
},
|
},
|
||||||
|
|
||||||
isSelected(color) {
|
isSelected(color) {
|
||||||
if (this.currentColor.get() === null) {
|
const tpl = Template.instance();
|
||||||
|
if (tpl.currentColor.get() === null) {
|
||||||
return color === 'white';
|
return color === 'white';
|
||||||
} else {
|
} else {
|
||||||
return this.currentColor.get() === color;
|
return tpl.currentColor.get() === color;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.setListColorPopup.events({
|
||||||
return [
|
'click .js-palette-color'(event, tpl) {
|
||||||
{
|
tpl.currentColor.set(Template.currentData().color);
|
||||||
'click .js-palette-color'() {
|
|
||||||
this.currentColor.set(this.currentData().color);
|
|
||||||
},
|
|
||||||
async 'click .js-submit'() {
|
|
||||||
await this.currentList.setColor(this.currentColor.get());
|
|
||||||
Popup.close();
|
|
||||||
},
|
|
||||||
async 'click .js-remove-color'() {
|
|
||||||
await this.currentList.setColor(null);
|
|
||||||
Popup.close();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
}).register('setListColorPopup');
|
async 'click .js-submit'(event, tpl) {
|
||||||
|
await tpl.currentList.setColor(tpl.currentColor.get());
|
||||||
BlazeComponent.extendComponent({
|
Popup.close();
|
||||||
applyListWidth() {
|
|
||||||
const list = Template.currentData();
|
|
||||||
const board = list.boardId;
|
|
||||||
const width = parseInt(
|
|
||||||
Template.instance()
|
|
||||||
.$('.list-width-value')
|
|
||||||
.val(),
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
const constraint = parseInt(
|
|
||||||
Template.instance()
|
|
||||||
.$('.list-constraint-value')
|
|
||||||
.val(),
|
|
||||||
10,
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME(mark-i-m): where do we put constants?
|
|
||||||
if (width < 270 || !width || constraint < 270 || !constraint) {
|
|
||||||
Template.instance()
|
|
||||||
.$('.list-width-error')
|
|
||||||
.click();
|
|
||||||
} else {
|
|
||||||
Meteor.call('applyListWidth', board, list._id, width, constraint);
|
|
||||||
Popup.back();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
async 'click .js-remove-color'(event, tpl) {
|
||||||
|
await tpl.currentList.setColor(null);
|
||||||
|
Popup.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.setListWidthPopup.helpers({
|
||||||
listWidthValue() {
|
listWidthValue() {
|
||||||
const list = Template.currentData();
|
const list = Template.currentData();
|
||||||
const board = list.boardId;
|
const board = list.boardId;
|
||||||
|
|
@ -439,122 +381,147 @@ BlazeComponent.extendComponent({
|
||||||
const user = ReactiveCache.getCurrentUser();
|
const user = ReactiveCache.getCurrentUser();
|
||||||
return user && user.isAutoWidth(boardId);
|
return user && user.isAutoWidth(boardId);
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.setListWidthPopup.events({
|
||||||
return [
|
'click .js-auto-width-board'() {
|
||||||
{
|
dragscroll.reset();
|
||||||
'click .js-auto-width-board'() {
|
ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId());
|
||||||
dragscroll.reset();
|
|
||||||
ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId());
|
|
||||||
},
|
|
||||||
'click .list-width-apply': this.applyListWidth,
|
|
||||||
'click .list-width-error': Popup.open('listWidthError'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
}).register('setListWidthPopup');
|
'click .list-width-apply'(event, tpl) {
|
||||||
|
const list = Template.currentData();
|
||||||
|
const board = list.boardId;
|
||||||
|
const width = parseInt(
|
||||||
|
tpl.$('.list-width-value').val(),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
const constraint = parseInt(
|
||||||
|
tpl.$('.list-constraint-value').val(),
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
|
||||||
BlazeComponent.extendComponent({
|
// FIXME(mark-i-m): where do we put constants?
|
||||||
onCreated() {
|
if (width < 270 || !width || constraint < 270 || !constraint) {
|
||||||
this.currentBoard = Utils.getCurrentBoard();
|
tpl.$('.list-width-error').click();
|
||||||
this.currentSwimlaneId = new ReactiveVar(null);
|
} else {
|
||||||
this.currentListId = new ReactiveVar(null);
|
Meteor.call('applyListWidth', board, list._id, width, constraint);
|
||||||
|
Popup.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .list-width-error': Popup.open('listWidthError'),
|
||||||
|
});
|
||||||
|
|
||||||
// Get the swimlane context from opener
|
Template.addListPopup.onCreated(function () {
|
||||||
const openerData = Popup.getOpenerComponent()?.data();
|
this.currentBoard = Utils.getCurrentBoard();
|
||||||
|
this.currentSwimlaneId = new ReactiveVar(null);
|
||||||
|
this.currentListId = new ReactiveVar(null);
|
||||||
|
|
||||||
// If opened from swimlane menu, openerData is the swimlane
|
// Get the swimlane context from opener
|
||||||
if (openerData?.type === 'swimlane' || openerData?.type === 'template-swimlane') {
|
const openerComponent = Popup.getOpenerComponent();
|
||||||
this.currentSwimlane = openerData;
|
const openerData = openerComponent?.data || Popup._getTopStack()?.dataContext;
|
||||||
this.currentSwimlaneId.set(openerData._id);
|
|
||||||
} else if (openerData?._id) {
|
// If opened from swimlane menu, openerData is the swimlane
|
||||||
// If opened from list menu, get swimlane from the list
|
if (openerData?.type === 'swimlane' || openerData?.type === 'template-swimlane') {
|
||||||
const list = ReactiveCache.getList({ _id: openerData._id });
|
this.currentSwimlane = openerData;
|
||||||
this.currentSwimlane = list?.swimlaneId ? ReactiveCache.getSwimlane({ _id: list.swimlaneId }) : null;
|
this.currentSwimlaneId.set(openerData._id);
|
||||||
|
} else if (openerData?._id) {
|
||||||
|
// If opened from list menu, get swimlane from the list
|
||||||
|
const list = ReactiveCache.getList({ _id: openerData._id });
|
||||||
|
if (list) {
|
||||||
|
this.currentSwimlane = list.swimlaneId
|
||||||
|
? ReactiveCache.getSwimlane({ _id: list.swimlaneId })
|
||||||
|
: null;
|
||||||
this.currentSwimlaneId.set(this.currentSwimlane?._id || null);
|
this.currentSwimlaneId.set(this.currentSwimlane?._id || null);
|
||||||
this.currentListId.set(openerData._id);
|
this.currentListId.set(openerData._id);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
|
if (!this.currentSwimlaneId.get()) {
|
||||||
|
const defaultSwimlane = this.currentBoard.getDefaultSwimline?.();
|
||||||
|
if (defaultSwimlane?._id) {
|
||||||
|
this.currentSwimlane = defaultSwimlane;
|
||||||
|
this.currentSwimlaneId.set(defaultSwimlane._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.addListPopup.helpers({
|
||||||
currentSwimlaneData() {
|
currentSwimlaneData() {
|
||||||
const swimlaneId = this.currentSwimlaneId.get();
|
const tpl = Template.instance();
|
||||||
|
const swimlaneId = tpl.currentSwimlaneId.get();
|
||||||
return swimlaneId ? ReactiveCache.getSwimlane({ _id: swimlaneId }) : null;
|
return swimlaneId ? ReactiveCache.getSwimlane({ _id: swimlaneId }) : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
currentListIdValue() {
|
currentListIdValue() {
|
||||||
return this.currentListId.get();
|
return Template.instance().currentListId.get();
|
||||||
},
|
},
|
||||||
|
|
||||||
swimlaneLists() {
|
swimlaneLists() {
|
||||||
const swimlaneId = this.currentSwimlaneId.get();
|
const tpl = Template.instance();
|
||||||
|
const swimlaneId = tpl.currentSwimlaneId.get();
|
||||||
if (swimlaneId) {
|
if (swimlaneId) {
|
||||||
return ReactiveCache.getLists({ swimlaneId, archived: false }).sort((a, b) => a.sort - b.sort);
|
return ReactiveCache.getLists({ swimlaneId, archived: false }).sort((a, b) => a.sort - b.sort);
|
||||||
}
|
}
|
||||||
return this.currentBoard.lists;
|
return tpl.currentBoard.lists;
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
events() {
|
Template.addListPopup.events({
|
||||||
return [
|
'submit .js-add-list-form'(evt, tpl) {
|
||||||
{
|
evt.preventDefault();
|
||||||
'submit .js-add-list-form'(evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
const titleInput = this.find('.list-name-input');
|
const titleInput = tpl.find('.list-name-input');
|
||||||
const title = titleInput?.value.trim();
|
const title = titleInput?.value.trim();
|
||||||
|
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
|
|
||||||
let sortIndex = 0;
|
let sortIndex = 0;
|
||||||
const boardId = Utils.getCurrentBoardId();
|
const boardId = Utils.getCurrentBoardId();
|
||||||
let swimlaneId = this.currentSwimlane?._id;
|
let swimlaneId = tpl.currentSwimlane?._id;
|
||||||
|
|
||||||
const positionInput = this.find('.list-position-input');
|
const positionInput = tpl.find('.list-position-input');
|
||||||
|
|
||||||
if (positionInput && positionInput.value) {
|
if (positionInput && positionInput.value) {
|
||||||
const positionId = positionInput.value.trim();
|
const positionId = positionInput.value.trim();
|
||||||
const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false });
|
const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false });
|
||||||
|
|
||||||
if (selectedList) {
|
if (selectedList) {
|
||||||
sortIndex = selectedList.sort + 1;
|
sortIndex = selectedList.sort + 1;
|
||||||
// Use the swimlane ID from the selected list to ensure the new list
|
// Use the swimlane ID from the selected list to ensure the new list
|
||||||
// is added to the same swimlane as the selected list
|
// is added to the same swimlane as the selected list
|
||||||
swimlaneId = selectedList.swimlaneId;
|
swimlaneId = selectedList.swimlaneId;
|
||||||
} else {
|
} else {
|
||||||
// No specific position, add at end of swimlane
|
// No specific position, add at end of swimlane
|
||||||
if (swimlaneId) {
|
if (swimlaneId) {
|
||||||
const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false });
|
const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false });
|
||||||
const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0];
|
const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0];
|
||||||
sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base;
|
sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base;
|
||||||
} else {
|
} else {
|
||||||
const lastList = this.currentBoard.getLastList();
|
const lastList = tpl.currentBoard.getLastList();
|
||||||
sortIndex = Utils.calculateIndexData(lastList, null).base;
|
sortIndex = Utils.calculateIndexData(lastList, null).base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No position input, add at end of swimlane
|
// No position input, add at end of swimlane
|
||||||
if (swimlaneId) {
|
if (swimlaneId) {
|
||||||
const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false });
|
const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false });
|
||||||
const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0];
|
const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0];
|
||||||
sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base;
|
sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base;
|
||||||
} else {
|
} else {
|
||||||
const lastList = this.currentBoard.getLastList();
|
const lastList = tpl.currentBoard.getLastList();
|
||||||
sortIndex = Utils.calculateIndexData(lastList, null).base;
|
sortIndex = Utils.calculateIndexData(lastList, null).base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Lists.insert({
|
Lists.insert({
|
||||||
title,
|
title,
|
||||||
boardId: Session.get('currentBoard'),
|
boardId: Session.get('currentBoard'),
|
||||||
sort: sortIndex,
|
sort: sortIndex,
|
||||||
type: 'list',
|
type: 'list',
|
||||||
swimlaneId: swimlaneId,
|
swimlaneId: swimlaneId,
|
||||||
});
|
});
|
||||||
|
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
|
||||||
'click .js-list-template': Popup.open('searchElement'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
}).register('addListPopup');
|
'click .js-list-template': Popup.open('searchElement'),
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue