diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 145f67892..14174938a 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -124,9 +124,13 @@ BlazeComponent.extendComponent({ }, 'click .js-clone-board'(evt) { Meteor.call( - 'cloneBoard', + 'copyBoard', this.currentData()._id, - Session.get('fromBoard'), + { + sort: Boards.find({ archived: false }).count(), + type: 'board', + title: Boards.findOne(this.currentData()._id).copyTitle(), + }, (err, res) => { if (err) { this.setError(err.error); diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index c08f82673..f607219fd 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -675,12 +675,18 @@ BlazeComponent.extendComponent({ element.type = 'swimlane'; _id = element.copy(this.boardId); } else if (this.isBoardTemplateSearch) { - board = Boards.findOne(element.linkedId); - board.sort = Boards.find({ archived: false }).count(); - board.type = 'board'; - board.title = element.title; - delete board.slug; - _id = board.copy(); + Meteor.call( + 'copyBoard', + element.linkedId, + { + sort: Boards.find({ archived: false }).count(), + type: 'board', + title: element.title, + }, + (err, data) => { + _id = data; + }, + ); } Popup.close(); }, diff --git a/models/boards.js b/models/boards.js index e0b791fa8..38594ca51 100644 --- a/models/boards.js +++ b/models/boards.js @@ -508,6 +508,7 @@ Boards.helpers({ copy() { const oldId = this._id; delete this._id; + delete this.slug; const _id = Boards.insert(this); // Copy all swimlanes in board @@ -537,7 +538,52 @@ Boards.helpers({ }, }); }); + + // copy rules, actions, and triggers + const actionsMap = {}; + Actions.find({ boardId: oldId }).forEach(action => { + const id = action._id; + delete action._id; + action.boardId = _id; + actionsMap[id] = Actions.insert(action); + }); + const triggersMap = {}; + Triggers.find({ boardId: oldId }).forEach(trigger => { + const id = trigger._id; + delete trigger._id; + trigger.boardId = _id; + triggersMap[id] = Triggers.insert(trigger); + }); + Rules.find({ boardId: oldId }).forEach(rule => { + delete rule._id; + rule.boardId = _id; + rule.actionId = actionsMap[rule.actionId]; + rule.triggerId = triggersMap[rule.triggerId]; + Rules.insert(rule); + }); }, + /** + * Return a unique title based on the current title + * + * @returns {string|null} + */ + copyTitle() { + const m = this.title.match(/^(?.*?)\s*(\[(?<num>\d+)]\s*$|\s*$)/); + const title = m.groups.title; + let num = 0; + Boards.find({ title: new RegExp(`^${title}\\s*\\[\\d+]\\s*$`) }).forEach( + board => { + const m = board.title.match(/^(?<title>.*?)\s*\[(?<num>\d+)]\s*$/); + if (m) { + const n = parseInt(m.groups.num, 10); + num = num < n ? n : num; + } + }, + ); + + return `${title} [${num + 1}]`; + }, + /** * Is supplied user authorized to view this board? */ diff --git a/server/publications/boards.js b/server/publications/boards.js index 1ceadd021..8aab2e281 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -209,3 +209,19 @@ Meteor.publishRelations('board', function(boardId, isArchived) { return this.ready(); }); + +Meteor.methods({ + copyBoard(boardId, properties) { + check(boardId, String); + check(properties, Object); + + const board = Boards.findOne(boardId); + if (board) { + for (const key in properties) { + board[key] = properties[key]; + } + return board.copy(); + } + return null; + }, +});