From 0a4e472601dbceb1008715c451e6966ff7f11e7a Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Sun, 3 Jul 2022 13:18:44 +0200 Subject: [PATCH 1/3] Move card dialog remembers now the last selected board --- client/components/cards/cardDetails.jade | 25 ++- client/components/cards/cardDetails.js | 214 ++++++++++++++++++++--- 2 files changed, 214 insertions(+), 25 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 2628ed7cf..990cbb181 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -712,7 +712,7 @@ template(name="exportCardPopup") | {{_ 'export-card-pdf'}} template(name="moveCardPopup") - +boardsAndLists + +copyAndMoveCard template(name="copyCardPopup") label(for='copy-card-title') {{_ 'title'}}: @@ -749,6 +749,29 @@ template(name="boardsAndLists") .edit-controls.clearfix button.primary.confirm.js-done {{_ 'done'}} +template(name="copyAndMoveCard") + unless currentUser.isWorker + label {{_ 'boards'}}: + select.js-select-boards(autofocus) + each boards + if $eq _id currentBoard._id + option(value="{{_id}}" selected) {{_ 'current'}} + else + option(value="{{_id}}" selected="{{#if isCardDialogOptionBoardId _id}}selected{{/if}}") {{title}} + + label {{_ 'swimlanes'}}: + select.js-select-swimlanes + each swimlanes + option(value="{{_id}}" selected="{{#if isCardDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}} + + label {{_ 'lists'}}: + select.js-select-lists + each lists + option(value="{{_id}}" selected="{{#if isCardDialogOptionListId _id}}selected{{/if}}") {{title}} + + .edit-controls.clearfix + button.primary.confirm.js-done {{_ 'done'}} + template(name="cardMembersPopup") input.card-members-filter(type="text" placeholder="{{_ 'search'}}") ul.pop-over-list.js-card-member-list diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index ad1b29cc8..b74886ae4 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -826,34 +826,200 @@ Template.editCardAssignerForm.events({ }, }); -Template.moveCardPopup.events({ - 'click .js-done'() { - const card = Cards.findOne(this._id); - const bSelect = $('.js-select-boards')[0]; - let boardId; - // if we are a worker, we won't have a board select so we just use the - // current boardId of the card. - if (bSelect) { - boardId = bSelect.options[bSelect.selectedIndex].value; - } else { - boardId = card.boardId; - } - const lSelect = $('.js-select-lists')[0]; - const listId = lSelect.options[lSelect.selectedIndex].value; - const slSelect = $('.js-select-swimlanes')[0]; - const swimlaneId = slSelect.options[slSelect.selectedIndex].value; +class DialogWithBoardSwimlaneList extends BlazeComponent { + /** returns the card dialog options + * @return Object with properties { boardId, swimlaneId, listId } + */ + getCardDialogOptions() { + } + /** list is done + * @param listId the selected list id + * @param options the selected options (Object with properties { boardId, swimlaneId, listId }) + */ + setDone(listId, options) { + } + + onCreated() { + this.currentBoardId = Utils.getCurrentBoardId(); + this.selectedBoardId = new ReactiveVar(this.currentBoardId); + this.selectedSwimlaneId = new ReactiveVar(''); + this.selectedListId = new ReactiveVar(''); + this.setCardDialogOption(this.currentBoardId); + } + + /** set the last confirmed dialog field values + * @param boardId the current board id + */ + setCardDialogOption(boardId) { + this.cardDialogOption = { + 'boardId' : "", + 'swimlaneId' : "", + 'listId' : "", + } + + let currentOptions = this.getCardDialogOptions(); + if (currentOptions && boardId && currentOptions[boardId]) { + this.cardDialogOption = currentOptions[boardId]; + if (this.cardDialogOption.boardId && + this.cardDialogOption.swimlaneId && + this.cardDialogOption.listId + ) + { + this.selectedBoardId.set(this.cardDialogOption.boardId) + this.selectedSwimlaneId.set(this.cardDialogOption.swimlaneId); + this.selectedListId.set(this.cardDialogOption.listId); + } + } + this.getBoardData(this.selectedBoardId.get()); + if (!this.selectedSwimlaneId.get() || !Swimlanes.findOne({_id: this.selectedSwimlaneId.get(), boardId: this.selectedBoardId.get()})) { + this.setFirstSwimlaneId(); + } + if (!this.selectedListId.get() || !Lists.findOne({_id: this.selectedListId.get(), boardId: this.selectedBoardId.get()})) { + this.setFirstListId(); + } + } + /** sets the first swimlane id */ + setFirstSwimlaneId() { + try { + const board = Boards.findOne(this.selectedBoardId.get()); + const swimlaneId = board.swimlanes().fetch()[0]._id; + this.selectedSwimlaneId.set(swimlaneId); + } catch (e) {} + } + /** sets the first list id */ + setFirstListId() { + try { + const board = Boards.findOne(this.selectedBoardId.get()); + const listId = board.lists().fetch()[0]._id; + this.selectedListId.set(listId); + } catch (e) {} + } + + /** returns if the board id was the last confirmed one + * @param boardId check this board id + * @return if the board id was the last confirmed one + */ + isCardDialogOptionBoardId(boardId) { + let ret = this.cardDialogOption.boardId == boardId; + return ret; + } + + /** returns if the swimlane id was the last confirmed one + * @param swimlaneId check this swimlane id + * @return if the swimlane id was the last confirmed one + */ + isCardDialogOptionSwimlaneId(swimlaneId) { + let ret = this.cardDialogOption.swimlaneId == swimlaneId; + return ret; + } + + /** returns if the list id was the last confirmed one + * @param listId check this list id + * @return if the list id was the last confirmed one + */ + isCardDialogOptionListId(listId) { + let ret = this.cardDialogOption.listId == listId; + return ret; + } + + /** returns all available board */ + boards() { + const ret = Boards.find( + { + archived: false, + 'members.userId': Meteor.userId(), + _id: { $ne: Meteor.user().getTemplatesBoardId() }, + }, + { + sort: { sort: 1 }, + }, + ); + return ret; + } + + /** returns all available swimlanes of the current board */ + swimlanes() { + const board = Boards.findOne(this.selectedBoardId.get()); + const ret = board.swimlanes(); + return ret; + } + + /** returns all available lists of the current board */ + lists() { + const board = Boards.findOne(this.selectedBoardId.get()); + const ret = board.lists(); + return ret; + } + + /** get the board data from the server + * @param boardId get the board data of this board id + */ + getBoardData(boardId) { + const self = this; + Meteor.subscribe('board', boardId, false, { + onReady() { + const sameBoardId = self.selectedBoardId.get() == boardId; + self.selectedBoardId.set(boardId); + + if (!sameBoardId) { + // reset swimlane id (for selection in cards()) + self.setFirstSwimlaneId(); + + // reset list id (for selection in cards()) + self.setFirstListId(); + } + }, + }); + } + + events() { + return [ + { + 'click .js-done'() { + const boardSelect = this.$('.js-select-boards')[0]; + const boardId = boardSelect.options[boardSelect.selectedIndex].value; + + const listSelect = this.$('.js-select-lists')[0]; + const listId = listSelect.options[listSelect.selectedIndex].value; + + const swimlaneSelect = this.$('.js-select-swimlanes')[0]; + const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value; + + const options = { + 'boardId' : boardId, + 'swimlaneId' : swimlaneId, + 'listId' : listId, + } + this.setDone(boardId, swimlaneId, listId, options); + Popup.back(2); + }, + 'change .js-select-boards'(event) { + const boardId = $(event.currentTarget).val(); + this.getBoardData(boardId); + }, + 'change .js-select-swimlanes'(event) { + this.selectedSwimlaneId.set($(event.currentTarget).val()); + }, + }, + ]; + } +} + +/** Move Card Dialog */ +(class extends DialogWithBoardSwimlaneList { + getCardDialogOptions() { + const ret = Meteor.user().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + Meteor.user().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); const minOrder = card.getMinSort(listId, swimlaneId); card.move(boardId, swimlaneId, listId, minOrder - 1); + } +}).register('moveCardPopup'); - // set new id's to card object in case the card is moved to top by the comment "moveCard" after this command (.js-move-card) - this.boardId = boardId; - this.swimlaneId = swimlaneId; - this.listId = listId; - - Popup.back(2); - }, -}); BlazeComponent.extendComponent({ onCreated() { this.currentBoardId = Utils.getCurrentBoardId(); From a06d1806df4b09c7c85d82f38b976ba549778d5f Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Sun, 3 Jul 2022 14:41:12 +0200 Subject: [PATCH 2/3] Copy card dialog remembers now the last selected board --- client/components/cards/cardDetails.jade | 2 +- client/components/cards/cardDetails.js | 68 +++++++++++++----------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 990cbb181..b8a082f2f 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -718,7 +718,7 @@ template(name="copyCardPopup") label(for='copy-card-title') {{_ 'title'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) = getTitle - +boardsAndLists + +copyAndMoveCard template(name="copyChecklistToManyCardsPopup") label(for='copy-checklist-cards-title') {{_ 'copyChecklistToManyCardsPopup-instructions'}}: diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b74886ae4..6fc2fbf1e 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1020,6 +1020,43 @@ class DialogWithBoardSwimlaneList extends BlazeComponent { } }).register('moveCardPopup'); +/** Copy Card Dialog */ +(class extends DialogWithBoardSwimlaneList { + getCardDialogOptions() { + const ret = Meteor.user().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + Meteor.user().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); + + // const textarea = $('#copy-card-title'); + const textarea = this.$('#copy-card-title'); + const title = textarea.val().trim(); + + if (title) { + const oldTitle = card.title; + card.title = title; + card.coverId = ''; + + // insert new card to the top of new list + const minOrder = card.getMinSort(listId, swimlaneId); + card.sort = minOrder - 1; + + const newCardId = card.copy(boardId, swimlaneId, listId); + + // restore the old card title, otherwise the card title would change in the current view (only temporary) + card.title = oldTitle; + + // 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 + // card will disappear instantly. + // See https://github.com/wekan/wekan/issues/80 + Filter.addException(newCardId); + } + } +}).register('copyCardPopup'); + BlazeComponent.extendComponent({ onCreated() { this.currentBoardId = Utils.getCurrentBoardId(); @@ -1127,37 +1164,6 @@ BlazeComponent.extendComponent({ }, }).register('boardsAndLists'); -Template.copyCardPopup.events({ - 'click .js-done'() { - const card = Utils.getCurrentCard(); - const lSelect = $('.js-select-lists')[0]; - const listId = lSelect.options[lSelect.selectedIndex].value; - const slSelect = $('.js-select-swimlanes')[0]; - const swimlaneId = slSelect.options[slSelect.selectedIndex].value; - const bSelect = $('.js-select-boards')[0]; - const boardId = bSelect.options[bSelect.selectedIndex].value; - const textarea = $('#copy-card-title'); - const title = textarea.val().trim(); - - // insert new card to the top of new list - const minOrder = card.getMinSort(listId, swimlaneId); - card.sort = minOrder - 1; - - if (title) { - card.title = title; - card.coverId = ''; - const _id = card.copy(boardId, swimlaneId, listId); - // 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 - // card will disappear instantly. - // See https://github.com/wekan/wekan/issues/80 - Filter.addException(_id); - - Popup.back(2); - } - }, -}); - Template.convertChecklistItemToCardPopup.events({ 'click .js-done'() { const card = Utils.getCurrentCard(); From 03deeb67299f77b7cabd154be5d8bd86994d2f51 Mon Sep 17 00:00:00 2001 From: Martin Filser Date: Sun, 3 Jul 2022 15:22:41 +0200 Subject: [PATCH 3/3] Copy many card dialog remembers now the last selected board --- client/components/cards/cardDetails.jade | 25 +--- client/components/cards/cardDetails.js | 178 ++++------------------- models/cards.js | 2 + 3 files changed, 32 insertions(+), 173 deletions(-) diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index b8a082f2f..862630e9b 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -724,30 +724,7 @@ template(name="copyChecklistToManyCardsPopup") label(for='copy-checklist-cards-title') {{_ 'copyChecklistToManyCardsPopup-instructions'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) | {{_ 'copyChecklistToManyCardsPopup-format'}} - +boardsAndLists - -template(name="boardsAndLists") - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each boards - if $eq _id currentBoard._id - option(value="{{_id}}" selected) {{_ 'current'}} - else - option(value="{{_id}}" selected="{{#if isMoveAndCopyDialogOptionBoardId _id}}selected{{/if}}") {{title}} - - label {{_ 'swimlanes'}}: - select.js-select-swimlanes - each swimlanes - option(value="{{_id}}" selected="{{#if isMoveAndCopyDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}} - - label {{_ 'lists'}}: - select.js-select-lists - each aBoardLists - option(value="{{_id}}" selected="{{#if isMoveAndCopyDialogOptionListId _id}}selected{{/if}}") {{title}} - - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + +copyAndMoveCard template(name="copyAndMoveCard") unless currentUser.isWorker diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 6fc2fbf1e..a5af18272 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1057,113 +1057,6 @@ class DialogWithBoardSwimlaneList extends BlazeComponent { } }).register('copyCardPopup'); -BlazeComponent.extendComponent({ - onCreated() { - this.currentBoardId = Utils.getCurrentBoardId(); - this.selectedBoardId = new ReactiveVar(this.currentBoardId); - this.setMoveAndCopyDialogOption(this.currentBoardId); - }, - - /** set the last confirmed dialog field values - * @param boardId the current board id - */ - setMoveAndCopyDialogOption(boardId) { - this.moveAndCopyDialogOption = { - 'boardId': "", - 'swimlaneId': "", - 'listId': "", - } - - let currentOptions = Meteor.user().getMoveAndCopyDialogOptions(); - if (currentOptions && boardId && currentOptions[boardId]) { - this.moveAndCopyDialogOption = currentOptions[boardId]; - if (this.moveAndCopyDialogOption.boardId) { - this.selectedBoardId.set(this.moveAndCopyDialogOption.boardId) - } - } - subManager.subscribe('board', this.selectedBoardId.get(), false); - }, - - /** returns if the board id was the last confirmed one - * @param boardId check this board id - * @return if the board id was the last confirmed one - */ - isMoveAndCopyDialogOptionBoardId(boardId) { - let ret = this.moveAndCopyDialogOption.boardId == boardId; - return ret; - }, - - /** returns if the swimlane id was the last confirmed one - * @param swimlaneId check this swimlane id - * @return if the swimlane id was the last confirmed one - */ - isMoveAndCopyDialogOptionSwimlaneId(swimlaneId) { - let ret = this.moveAndCopyDialogOption.swimlaneId == swimlaneId; - return ret; - }, - - /** returns if the list id was the last confirmed one - * @param listId check this list id - * @return if the list id was the last confirmed one - */ - isMoveAndCopyDialogOptionListId(listId) { - let ret = this.moveAndCopyDialogOption.listId == listId; - return ret; - }, - - boards() { - return Boards.find( - { - archived: false, - 'members.userId': Meteor.userId(), - _id: { $ne: Meteor.user().getTemplatesBoardId() }, - }, - { - sort: { sort: 1 /* boards default sorting */ }, - }, - ); - }, - - swimlanes() { - const board = Boards.findOne(this.selectedBoardId.get()); - return board.swimlanes(); - }, - - aBoardLists() { - const board = Boards.findOne(this.selectedBoardId.get()); - return board.lists(); - }, - - events() { - return [ - { - 'click .js-done'() { - const boardSelect = this.$('.js-select-boards')[0]; - const boardId = boardSelect.options[boardSelect.selectedIndex].value; - - const listSelect = this.$('.js-select-lists')[0]; - const listId = listSelect.options[listSelect.selectedIndex].value; - - const swimlaneSelect = this.$('.js-select-swimlanes')[0]; - const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value; - - const options = { - 'boardId': boardId, - 'swimlaneId': swimlaneId, - 'listId': listId, - } - Meteor.user().setMoveAndCopyDialogOption(this.currentBoardId, options); - }, - 'change .js-select-boards'(event) { - const boardId = $(event.currentTarget).val(); - this.selectedBoardId.set(boardId); - subManager.subscribe('board', boardId, false); - }, - }, - ]; - }, -}).register('boardsAndLists'); - Template.convertChecklistItemToCardPopup.events({ 'click .js-done'() { const card = Utils.getCurrentCard(); @@ -1192,60 +1085,47 @@ Template.convertChecklistItemToCardPopup.events({ }, }); -Template.copyChecklistToManyCardsPopup.events({ - 'click .js-done'() { - const card = Utils.getCurrentCard(); - const oldId = card._id; - card._id = null; - const lSelect = $('.js-select-lists')[0]; - card.listId = lSelect.options[lSelect.selectedIndex].value; - const slSelect = $('.js-select-swimlanes')[0]; - card.swimlaneId = slSelect.options[slSelect.selectedIndex].value; - const bSelect = $('.js-select-boards')[0]; - card.boardId = bSelect.options[bSelect.selectedIndex].value; - const textarea = $('#copy-card-title'); - const titleEntry = textarea.val().trim(); - // insert new card to the bottom of new list - card.sort = Lists.findOne(card.listId).cards().count(); +/** Copy many cards dialog */ +(class extends DialogWithBoardSwimlaneList { + getCardDialogOptions() { + const ret = Meteor.user().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + Meteor.user().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); - if (titleEntry) { - const titleList = JSON.parse(titleEntry); + const textarea = this.$('#copy-card-title'); + const title = textarea.val().trim(); + + if (title) { + const oldTitle = card.title; + + const titleList = JSON.parse(title); for (let i = 0; i < titleList.length; i++) { const obj = titleList[i]; card.title = obj.title; card.description = obj.description; card.coverId = ''; - const _id = Cards.insert(card); + + // insert new card to the top of new list + const maxOrder = card.getMaxSort(listId, swimlaneId); + card.sort = maxOrder + 1; + + const newCardId = card.copy(boardId, swimlaneId, listId); + // 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 // card will disappear instantly. // See https://github.com/wekan/wekan/issues/80 - Filter.addException(_id); - - // copy checklists - Checklists.find({ cardId: oldId }).forEach((ch) => { - ch.copy(_id); - }); - - // copy subtasks - const cursor = Cards.find({ parentId: oldId }); - cursor.forEach(function () { - 'use strict'; - const subtask = arguments[0]; - subtask.parentId = _id; - subtask._id = null; - /* const newSubtaskId = */ Cards.insert(subtask); - }); - - // copy card comments - CardComments.find({ cardId: oldId }).forEach((cmt) => { - cmt.copy(_id); - }); + Filter.addException(newCardId); } - Popup.back(); + + // restore the old card title, otherwise the card title would change in the current view (only temporary) + card.title = oldTitle; } - }, -}); + } +}).register('copyChecklistToManyCardsPopup'); BlazeComponent.extendComponent({ onCreated() { diff --git a/models/cards.js b/models/cards.js index ef4b2003e..4a3497c6a 100644 --- a/models/cards.js +++ b/models/cards.js @@ -609,6 +609,8 @@ Cards.helpers({ CardComments.find({ cardId: oldId }).forEach(cmt => { cmt.copy(_id); }); + // restore the id, otherwise new copies will fail + this._id = oldId; return _id; },