mirror of
https://github.com/wekan/wekan.git
synced 2026-02-19 22:48:07 +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
|
|
@ -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');
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue