mirror of
https://github.com/wekan/wekan.git
synced 2025-12-30 22:28:49 +01:00
Fix copy move card at board and MultiSelect to have numbered target of board, card above or below. Added MultiSelect change color.
Thanks to mimZD and xet7 ! Fixes #6045
This commit is contained in:
parent
db4b04d837
commit
74f1dfde72
9 changed files with 510 additions and 30 deletions
|
|
@ -828,17 +828,29 @@ template(name="copyAndMoveCard")
|
|||
label {{_ 'boards'}}:
|
||||
select.js-select-boards(autofocus)
|
||||
each boards
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}}
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'swimlanes'}}:
|
||||
select.js-select-swimlanes
|
||||
each swimlanes
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}}
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'lists'}}:
|
||||
select.js-select-lists
|
||||
each lists
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}}
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'cards'}}:
|
||||
select.js-select-cards
|
||||
each cards
|
||||
option(value="{{_id}}") {{add @index 1}}. {{title}}
|
||||
|
||||
div
|
||||
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
|
||||
label(for="position-above") {{_ 'above-selected-card'}}
|
||||
div
|
||||
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
|
||||
label(for="position-below") {{_ 'below-selected-card'}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-done {{_ 'done'}}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import CardComments from '/models/cardComments';
|
|||
import { ALLOWED_COLORS } from '/config/const';
|
||||
import { UserAvatar } from '../users/userAvatar';
|
||||
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
|
||||
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
|
||||
import { handleFileUpload } from './attachments';
|
||||
import uploadProgressManager from '../../lib/uploadProgressManager';
|
||||
|
||||
|
|
@ -973,26 +974,42 @@ Template.editCardAssignerForm.events({
|
|||
});
|
||||
|
||||
/** Move Card Dialog */
|
||||
(class extends DialogWithBoardSwimlaneList {
|
||||
(class extends DialogWithBoardSwimlaneListCard {
|
||||
getDialogOptions() {
|
||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||
return ret;
|
||||
}
|
||||
setDone(boardId, swimlaneId, listId, options) {
|
||||
setDone(cardId, options) {
|
||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||
const card = this.data();
|
||||
const minOrder = card.getMinSort(listId, swimlaneId);
|
||||
card.move(boardId, swimlaneId, listId, minOrder - 1);
|
||||
let sortIndex = 0;
|
||||
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
const position = this.$('input[name="position"]:checked').val();
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no card selected, move to end
|
||||
sortIndex = card.getMaxSort(options.listId, options.swimlaneId) + 1;
|
||||
}
|
||||
|
||||
card.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
||||
}
|
||||
}).register('moveCardPopup');
|
||||
|
||||
/** Copy Card Dialog */
|
||||
(class extends DialogWithBoardSwimlaneList {
|
||||
(class extends DialogWithBoardSwimlaneListCard {
|
||||
getDialogOptions() {
|
||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||
return ret;
|
||||
}
|
||||
setDone(boardId, swimlaneId, listId, options) {
|
||||
setDone(cardId, options) {
|
||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||
const card = this.data();
|
||||
|
||||
|
|
@ -1001,8 +1018,30 @@ Template.editCardAssignerForm.events({
|
|||
const title = textarea.val().trim();
|
||||
|
||||
if (title) {
|
||||
// insert new card to the top of new list
|
||||
const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, true, {title: title});
|
||||
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, true, {title: title});
|
||||
|
||||
// Position the copied card
|
||||
if (newCardId) {
|
||||
const newCard = ReactiveCache.getCard(newCardId);
|
||||
let sortIndex = 0;
|
||||
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
const position = this.$('input[name="position"]:checked').val();
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no card selected, copy to end
|
||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
||||
}
|
||||
|
||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
||||
}
|
||||
|
||||
// In case the filter is active we need to add the newly inserted card in
|
||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||
|
|
@ -1014,12 +1053,12 @@ Template.editCardAssignerForm.events({
|
|||
}).register('copyCardPopup');
|
||||
|
||||
/** Convert Checklist-Item to card dialog */
|
||||
(class extends DialogWithBoardSwimlaneList {
|
||||
(class extends DialogWithBoardSwimlaneListCard {
|
||||
getDialogOptions() {
|
||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||
return ret;
|
||||
}
|
||||
setDone(boardId, swimlaneId, listId, options) {
|
||||
setDone(cardId, options) {
|
||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||
const card = this.data();
|
||||
|
||||
|
|
@ -1029,14 +1068,29 @@ Template.editCardAssignerForm.events({
|
|||
if (title) {
|
||||
const _id = Cards.insert({
|
||||
title: title,
|
||||
listId: listId,
|
||||
boardId: boardId,
|
||||
swimlaneId: swimlaneId,
|
||||
listId: options.listId,
|
||||
boardId: options.boardId,
|
||||
swimlaneId: options.swimlaneId,
|
||||
sort: 0,
|
||||
});
|
||||
const card = ReactiveCache.getCard(_id);
|
||||
const minOrder = card.getMinSort();
|
||||
card.move(card.boardId, card.swimlaneId, card.listId, minOrder - 1);
|
||||
const newCard = ReactiveCache.getCard(_id);
|
||||
|
||||
let sortIndex = 0;
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
const position = this.$('input[name="position"]:checked').val();
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
||||
}
|
||||
|
||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
||||
|
||||
Filter.addException(_id);
|
||||
}
|
||||
|
|
@ -1044,12 +1098,12 @@ Template.editCardAssignerForm.events({
|
|||
}).register('convertChecklistItemToCardPopup');
|
||||
|
||||
/** Copy many cards dialog */
|
||||
(class extends DialogWithBoardSwimlaneList {
|
||||
(class extends DialogWithBoardSwimlaneListCard {
|
||||
getDialogOptions() {
|
||||
const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions();
|
||||
return ret;
|
||||
}
|
||||
setDone(boardId, swimlaneId, listId, options) {
|
||||
setDone(cardId, options) {
|
||||
ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options);
|
||||
const card = this.data();
|
||||
|
||||
|
|
@ -1059,7 +1113,29 @@ Template.editCardAssignerForm.events({
|
|||
if (title) {
|
||||
const titleList = JSON.parse(title);
|
||||
for (const obj of titleList) {
|
||||
const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, false, {title: obj.title, description: obj.description});
|
||||
const newCardId = Meteor.call('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, false, {title: obj.title, description: obj.description});
|
||||
|
||||
// Position the copied card
|
||||
if (newCardId) {
|
||||
const newCard = ReactiveCache.getCard(newCardId);
|
||||
let sortIndex = 0;
|
||||
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
const position = this.$('input[name="position"]:checked').val();
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1;
|
||||
}
|
||||
|
||||
newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
|
||||
}
|
||||
|
||||
// In case the filter is active we need to add the newly inserted card in
|
||||
// the list of exceptions -- cards that are not filtered. Otherwise the
|
||||
|
|
@ -1109,6 +1185,51 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
}).register('setCardColorPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentColor = new ReactiveVar(null);
|
||||
},
|
||||
|
||||
colors() {
|
||||
return ALLOWED_COLORS.map((color) => ({ color, name: '' }));
|
||||
},
|
||||
|
||||
isSelected(color) {
|
||||
return this.currentColor.get() === color;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-palette-color'(event) {
|
||||
// Extract color from class name like "card-details-red"
|
||||
const classes = $(event.currentTarget).attr('class').split(' ');
|
||||
const colorClass = classes.find(cls => cls.startsWith('card-details-'));
|
||||
const color = colorClass ? colorClass.replace('card-details-', '') : null;
|
||||
this.currentColor.set(color);
|
||||
},
|
||||
'click .js-submit'(event) {
|
||||
event.preventDefault();
|
||||
const color = this.currentColor.get();
|
||||
// Use MultiSelection to get selected cards and set color on each
|
||||
ReactiveCache.getCards(MultiSelection.getMongoSelector()).forEach(card => {
|
||||
card.setColor(color);
|
||||
});
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-remove-color'(event) {
|
||||
event.preventDefault();
|
||||
// Use MultiSelection to get selected cards and remove color from each
|
||||
ReactiveCache.getCards(MultiSelection.getMongoSelector()).forEach(card => {
|
||||
card.setColor(null);
|
||||
});
|
||||
Popup.back();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('setSelectionColorPopup');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.currentCard = this.currentData();
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
|
|||
border-radius: 3px;
|
||||
background: #e6e6e6;
|
||||
}
|
||||
.sidebar .sidebar-content .sidebar-btn * {
|
||||
color: #fff;
|
||||
}
|
||||
.sidebar .sidebar-content .sidebar-btn:hover * {
|
||||
color: #fff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,12 @@ template(name="multiselectionSidebar")
|
|||
| ⋯
|
||||
if currentUser.isBoardAdmin
|
||||
hr
|
||||
a.sidebar-btn.js-selection-color
|
||||
| 🎨
|
||||
span {{_ 'selection-color'}}
|
||||
a.sidebar-btn.js-copy-selection
|
||||
| 📋
|
||||
span {{_ 'copy-selection'}}
|
||||
a.sidebar-btn.js-move-selection
|
||||
| 📤
|
||||
span {{_ 'move-selection'}}
|
||||
|
|
@ -224,4 +230,76 @@ template(name="disambiguateMultiMemberPopup")
|
|||
button.wide.js-assign-member {{_ 'assign-member'}}
|
||||
|
||||
template(name="moveSelectionPopup")
|
||||
+boardLists
|
||||
h3 {{_ 'moveSelectionPopup-title'}}
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards
|
||||
each boards
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'swimlanes'}}:
|
||||
select.js-select-swimlanes
|
||||
each swimlanes
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'lists'}}:
|
||||
select.js-select-lists
|
||||
each lists
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'cards'}}:
|
||||
select.js-select-cards
|
||||
each cards
|
||||
option(value="{{_id}}") {{add @index 1}}. {{title}}
|
||||
|
||||
div
|
||||
input(type="radio" name="position" value="above" checked id="position-above-move" style="display: inline")
|
||||
label(for="position-above-move") {{_ 'above-selected-card'}}
|
||||
div
|
||||
input(type="radio" name="position" value="below" id="position-below-move" style="display: inline")
|
||||
label(for="position-below-move") {{_ 'below-selected-card'}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-done {{_ 'done'}}
|
||||
|
||||
template(name="copySelectionPopup")
|
||||
h3 {{_ 'copySelectionPopup-title'}}
|
||||
label {{_ 'boards'}}:
|
||||
select.js-select-boards
|
||||
each boards
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'swimlanes'}}:
|
||||
select.js-select-swimlanes
|
||||
each swimlanes
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'lists'}}:
|
||||
select.js-select-lists
|
||||
each lists
|
||||
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
|
||||
|
||||
label {{_ 'cards'}}:
|
||||
select.js-select-cards
|
||||
each cards
|
||||
option(value="{{_id}}") {{add @index 1}}. {{title}}
|
||||
|
||||
div
|
||||
input(type="radio" name="position" value="above" checked id="position-above-copy" style="display: inline")
|
||||
label(for="position-above-copy") {{_ 'above-selected-card'}}
|
||||
div
|
||||
input(type="radio" name="position" value="below" id="position-below-copy" style="display: inline")
|
||||
label(for="position-below-copy") {{_ 'below-selected-card'}}
|
||||
|
||||
.edit-controls.clearfix
|
||||
button.primary.confirm.js-done {{_ 'done'}}
|
||||
|
||||
template(name="setSelectionColorPopup")
|
||||
h3 {{_ 'setSelectionColorPopup-title'}}
|
||||
form.edit-label
|
||||
.palette-colors: each colors
|
||||
unless $eq color 'white'
|
||||
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
|
||||
if(isSelected color)
|
||||
| ✅
|
||||
button.primary.confirm.js-submit {{_ 'save'}}
|
||||
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,8 @@ BlazeComponent.extendComponent({
|
|||
}
|
||||
},
|
||||
'click .js-move-selection': Popup.open('moveSelection'),
|
||||
'click .js-copy-selection': Popup.open('copySelection'),
|
||||
'click .js-selection-color': Popup.open('setSelectionColor'),
|
||||
'click .js-archive-selection'() {
|
||||
mutateSelectedCards('archive');
|
||||
EscapeActions.executeUpTo('multiselection');
|
||||
|
|
@ -202,10 +204,267 @@ Template.disambiguateMultiMemberPopup.events({
|
|||
},
|
||||
});
|
||||
|
||||
Template.moveSelectionPopup.onCreated(function() {
|
||||
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
||||
this.selectedSwimlaneId = new ReactiveVar('');
|
||||
this.selectedListId = new ReactiveVar('');
|
||||
this.selectedCardId = new ReactiveVar('');
|
||||
this.position = new ReactiveVar('above');
|
||||
|
||||
this.getBoardData = function(boardId) {
|
||||
const self = this;
|
||||
Meteor.subscribe('board', boardId, false, {
|
||||
onReady() {
|
||||
const sameBoardId = self.selectedBoardId.get() === boardId;
|
||||
self.selectedBoardId.set(boardId);
|
||||
|
||||
if (!sameBoardId) {
|
||||
self.setFirstSwimlaneId();
|
||||
self.setFirstListId();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
this.setFirstSwimlaneId = function() {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const swimlaneId = board.swimlanes()[0]._id;
|
||||
this.selectedSwimlaneId.set(swimlaneId);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
this.setFirstListId = function() {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const listId = board.lists()[0]._id;
|
||||
this.selectedListId.set(listId);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
this.getBoardData(Session.get('currentBoard'));
|
||||
this.setFirstSwimlaneId();
|
||||
this.setFirstListId();
|
||||
});
|
||||
|
||||
Template.moveSelectionPopup.helpers({
|
||||
boards() {
|
||||
return ReactiveCache.getBoards(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
|
||||
},
|
||||
{
|
||||
sort: { sort: 1 },
|
||||
},
|
||||
);
|
||||
},
|
||||
swimlanes() {
|
||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
||||
return board ? board.swimlanes() : [];
|
||||
},
|
||||
lists() {
|
||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
||||
return board ? board.lists() : [];
|
||||
},
|
||||
cards() {
|
||||
const instance = Template.instance();
|
||||
const list = ReactiveCache.getList(instance.selectedListId.get());
|
||||
if (!list) return [];
|
||||
return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
|
||||
},
|
||||
isDialogOptionBoardId(boardId) {
|
||||
return Template.instance().selectedBoardId.get() === boardId;
|
||||
},
|
||||
isDialogOptionSwimlaneId(swimlaneId) {
|
||||
return Template.instance().selectedSwimlaneId.get() === swimlaneId;
|
||||
},
|
||||
isDialogOptionListId(listId) {
|
||||
return Template.instance().selectedListId.get() === listId;
|
||||
},
|
||||
});
|
||||
|
||||
Template.moveSelectionPopup.events({
|
||||
'click .js-select-list'() {
|
||||
// Move the minicard to the end of the target list
|
||||
mutateSelectedCards('moveToEndOfList', { listId: this._id });
|
||||
'change .js-select-boards'(event) {
|
||||
const boardId = $(event.currentTarget).val();
|
||||
Template.instance().getBoardData(boardId);
|
||||
},
|
||||
'change .js-select-swimlanes'(event) {
|
||||
Template.instance().selectedSwimlaneId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change .js-select-lists'(event) {
|
||||
Template.instance().selectedListId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change .js-select-cards'(event) {
|
||||
Template.instance().selectedCardId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change input[name="position"]'(event) {
|
||||
Template.instance().position.set($(event.currentTarget).val());
|
||||
},
|
||||
'click .js-done'() {
|
||||
const instance = Template.instance();
|
||||
const boardId = instance.selectedBoardId.get();
|
||||
const swimlaneId = instance.selectedSwimlaneId.get();
|
||||
const listId = instance.selectedListId.get();
|
||||
const cardId = instance.selectedCardId.get();
|
||||
const position = instance.position.get();
|
||||
|
||||
// Calculate sortIndex
|
||||
let sortIndex = 0;
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no card selected, move to end
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
const cards = board.cards({ swimlaneId, listId }).sort('sort');
|
||||
if (cards.length > 0) {
|
||||
sortIndex = cards[cards.length - 1].sort + 1;
|
||||
}
|
||||
}
|
||||
|
||||
mutateSelectedCards('move', boardId, swimlaneId, listId, sortIndex);
|
||||
EscapeActions.executeUpTo('multiselection');
|
||||
},
|
||||
});
|
||||
|
||||
Template.copySelectionPopup.onCreated(function() {
|
||||
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
||||
this.selectedSwimlaneId = new ReactiveVar('');
|
||||
this.selectedListId = new ReactiveVar('');
|
||||
this.selectedCardId = new ReactiveVar('');
|
||||
this.position = new ReactiveVar('above');
|
||||
|
||||
this.getBoardData = function(boardId) {
|
||||
const self = this;
|
||||
Meteor.subscribe('board', boardId, false, {
|
||||
onReady() {
|
||||
const sameBoardId = self.selectedBoardId.get() === boardId;
|
||||
self.selectedBoardId.set(boardId);
|
||||
|
||||
if (!sameBoardId) {
|
||||
self.setFirstSwimlaneId();
|
||||
self.setFirstListId();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
this.setFirstSwimlaneId = function() {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const swimlaneId = board.swimlanes()[0]._id;
|
||||
this.selectedSwimlaneId.set(swimlaneId);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
this.setFirstListId = function() {
|
||||
try {
|
||||
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
|
||||
const listId = board.lists()[0]._id;
|
||||
this.selectedListId.set(listId);
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
this.getBoardData(Session.get('currentBoard'));
|
||||
this.setFirstSwimlaneId();
|
||||
this.setFirstListId();
|
||||
});
|
||||
|
||||
Template.copySelectionPopup.helpers({
|
||||
boards() {
|
||||
return ReactiveCache.getBoards(
|
||||
{
|
||||
archived: false,
|
||||
'members.userId': Meteor.userId(),
|
||||
_id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
|
||||
},
|
||||
{
|
||||
sort: { sort: 1 },
|
||||
},
|
||||
);
|
||||
},
|
||||
swimlanes() {
|
||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
||||
return board ? board.swimlanes() : [];
|
||||
},
|
||||
lists() {
|
||||
const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
|
||||
return board ? board.lists() : [];
|
||||
},
|
||||
cards() {
|
||||
const instance = Template.instance();
|
||||
const list = ReactiveCache.getList(instance.selectedListId.get());
|
||||
if (!list) return [];
|
||||
return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
|
||||
},
|
||||
isDialogOptionBoardId(boardId) {
|
||||
return Template.instance().selectedBoardId.get() === boardId;
|
||||
},
|
||||
isDialogOptionSwimlaneId(swimlaneId) {
|
||||
return Template.instance().selectedSwimlaneId.get() === swimlaneId;
|
||||
},
|
||||
isDialogOptionListId(listId) {
|
||||
return Template.instance().selectedListId.get() === listId;
|
||||
},
|
||||
});
|
||||
|
||||
Template.copySelectionPopup.events({
|
||||
'change .js-select-boards'(event) {
|
||||
const boardId = $(event.currentTarget).val();
|
||||
Template.instance().getBoardData(boardId);
|
||||
},
|
||||
'change .js-select-swimlanes'(event) {
|
||||
Template.instance().selectedSwimlaneId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change .js-select-lists'(event) {
|
||||
Template.instance().selectedListId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change .js-select-cards'(event) {
|
||||
Template.instance().selectedCardId.set($(event.currentTarget).val());
|
||||
},
|
||||
'change input[name="position"]'(event) {
|
||||
Template.instance().position.set($(event.currentTarget).val());
|
||||
},
|
||||
'click .js-done'() {
|
||||
const instance = Template.instance();
|
||||
const boardId = instance.selectedBoardId.get();
|
||||
const swimlaneId = instance.selectedSwimlaneId.get();
|
||||
const listId = instance.selectedListId.get();
|
||||
const cardId = instance.selectedCardId.get();
|
||||
const position = instance.position.get();
|
||||
|
||||
mutateSelectedCards((card) => {
|
||||
const newCard = card.copy(boardId, swimlaneId, listId);
|
||||
if (newCard) {
|
||||
let sortIndex = 0;
|
||||
if (cardId) {
|
||||
const targetCard = ReactiveCache.getCard(cardId);
|
||||
if (targetCard) {
|
||||
if (position === 'above') {
|
||||
sortIndex = targetCard.sort - 0.5;
|
||||
} else {
|
||||
sortIndex = targetCard.sort + 0.5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// To end
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
const cards = board.cards({ swimlaneId, listId }).sort('sort');
|
||||
if (cards.length > 0) {
|
||||
sortIndex = cards[cards.length - 1].sort + 1;
|
||||
}
|
||||
}
|
||||
newCard.setSort(sortIndex);
|
||||
}
|
||||
});
|
||||
EscapeActions.executeUpTo('multiselection');
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -80,3 +80,5 @@ Blaze.registerHelper('canMoveCard', () =>
|
|||
Blaze.registerHelper('canModifyBoard', () =>
|
||||
Utils.canModifyBoard(),
|
||||
);
|
||||
|
||||
Blaze.registerHelper('add', (a, b) => a + b);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export class DialogWithBoardSwimlaneListCard extends DialogWithBoardSwimlaneList
|
|||
const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value;
|
||||
|
||||
const cardSelect = this.$('.js-select-cards')[0];
|
||||
const cardId = cardSelect.options[cardSelect.selectedIndex].value;
|
||||
const cardId = cardSelect.options.length > 0 ? cardSelect.options[cardSelect.selectedIndex].value : null;
|
||||
|
||||
const options = {
|
||||
'boardId' : boardId,
|
||||
|
|
|
|||
|
|
@ -561,6 +561,8 @@
|
|||
"moveCardToBottom-title": "Move to Bottom",
|
||||
"moveCardToTop-title": "Move to Top",
|
||||
"moveSelectionPopup-title": "Move selection",
|
||||
"copySelectionPopup-title": "Copy selection",
|
||||
"selection-color": "Selection Color",
|
||||
"multi-selection": "Multi-Selection",
|
||||
"multi-selection-label": "Set label for selection",
|
||||
"multi-selection-member": "Set member for selection",
|
||||
|
|
@ -767,6 +769,7 @@
|
|||
"editCardReceivedDatePopup-title": "Change received date",
|
||||
"editCardEndDatePopup-title": "Change end date",
|
||||
"setCardColorPopup-title": "Set color",
|
||||
"setSelectionColorPopup-title": "Set selection color",
|
||||
"setCardActionsColorPopup-title": "Choose a color",
|
||||
"setSwimlaneColorPopup-title": "Choose a color",
|
||||
"setListColorPopup-title": "Choose a color",
|
||||
|
|
@ -957,6 +960,8 @@
|
|||
"a-endAt": "modified ending time to be",
|
||||
"a-startAt": "modified starting time to be",
|
||||
"a-receivedAt": "modified received time to be",
|
||||
"above-selected-card": "Above selected card",
|
||||
"below-selected-card": "Below selected card",
|
||||
"almostdue": "current due time %s is approaching",
|
||||
"pastdue": "current due time %s is past",
|
||||
"duenow": "current due time %s is today",
|
||||
|
|
|
|||
|
|
@ -2018,8 +2018,8 @@ Cards.mutations({
|
|||
};
|
||||
},
|
||||
|
||||
moveToEndOfList({ listId } = {}) {
|
||||
let swimlaneId = this.swimlaneId;
|
||||
moveToEndOfList({ listId, swimlaneId } = {}) {
|
||||
swimlaneId = swimlaneId || this.swimlaneId;
|
||||
const boardId = this.boardId;
|
||||
let sortIndex = 0;
|
||||
|
||||
|
|
@ -2030,7 +2030,7 @@ Cards.mutations({
|
|||
swimlaneId = board.getDefaultSwimline()._id;
|
||||
}
|
||||
// Move the minicard to the end of the target list
|
||||
let parentElementDom = $(`#swimlane-${this.swimlaneId}`).get(0);
|
||||
let parentElementDom = $(`#swimlane-${swimlaneId}`).get(0);
|
||||
if (!parentElementDom) parentElementDom = $(':root');
|
||||
|
||||
const lastCardDom = $(parentElementDom)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue