diff --git a/.meteor/packages b/.meteor/packages index 24e4c1bc6..555a6c4b9 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -20,7 +20,6 @@ reywood:publish-composite dburles:collection-helpers idmontie:migrations mongo@1.16.8 -mquandalle:collection-mutations # Account system accounts-password@2.4.0 @@ -43,7 +42,6 @@ session@1.2.1 tracker@1.3.3 underscore@1.0.13 audit-argument-checks@1.0.7 -ongoworks:speakingurl raix:handlebar-helpers http@2.0.0! # force new http package diff --git a/.meteor/versions b/.meteor/versions index d3106cd4f..65b8f3ead 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -78,7 +78,6 @@ mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 mongo-livedata@1.0.12 -mquandalle:collection-mutations@0.1.0 mquandalle:jade@0.4.9 mquandalle:jade-compiler@0.4.5 msavin:usercache@1.8.0 @@ -86,7 +85,6 @@ npm-mongo@4.17.2 oauth@2.2.1 oauth2@1.3.2 observe-sequence@1.0.21 -ongoworks:speakingurl@1.1.0 ordered-dict@1.1.0 ostrio:cookies@2.7.2 ostrio:cstorage@4.0.1 diff --git a/.vscode/settings.json b/.vscode/settings.json index d5e338938..860ffbbba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,8 @@ "TERM": "xterm-256color" }, "terminal.integrated.shell.linux": "/bin/bash", - "terminal.integrated.shellArgs.linux": ["-l"] + "terminal.integrated.shellArgs.linux": [ + "-l" + ], + "files.simpleDialog.enable": true } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b9be23f01..a4bec1b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,23 @@ Fixing other platforms In Progress. WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible. Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible. +# Upcoming WeKan ® release + +This release adds the following updates: + +- Secure Sandbox for VSCode at Debian 13 amd64. + [Part 1](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088), + [Part 1](https://github.com/wekan/wekan/commit/cc8b771eb448199fa23a87955cf9fa1a504ba8d2). + Thanks to xet7. +- [Updated build scripts and docs to Meteor 2.16](https://github.com/wekan/wekan/commit/1d374db0f3ed35a0463b5f89ca2d01078e245d11). + Thanks to xet7. +- [Replace mquandalle:collection-mutations with collection helpers](https://github.com/wekan/wekan/pull/6086). + Thanks to harryadel. +- [Replace ongoworks:speakingurl with limax](https://github.com/wekan/wekan/pull/6087). + Thanks to harryadel. + +Thanks to above GitHub users for their contributions and translators for their translations. + # v8.23 2026-01-21 WeKan ® release This release adds the following updates: diff --git a/Dockerfile b/Dockerfile index 4b02732b3..e553a01d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build- ENV \ DEBUG=false \ NODE_VERSION=v14.21.4 \ - METEOR_RELEASE=METEOR@2.14 \ + METEOR_RELEASE=METEOR@2.16 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=6.14.17 \ @@ -222,8 +222,8 @@ cd /home/wekan chown --recursive wekan:wekan /home/wekan echo "Starting meteor ${METEOR_RELEASE} installation... \n" #gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh -# Specify Meteor version 2.14 to be compatible: https://github.com/wekan/wekan/pull/5816/files -#gosu wekan:wekan npm -g install meteor@2.14 --unsafe-perm +# Specify Meteor version 2.16 to be compatible: https://github.com/wekan/wekan/pull/5816/files +#gosu wekan:wekan npm -g install meteor@2.16 --unsafe-perm #mv /root/.meteor /home/wekan/ #chown --recursive wekan:wekan /home/wekan/.meteor diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index c761bb69e..18ccf0b93 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -23,7 +23,7 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click .js-restore-board'() { + async 'click .js-restore-board'() { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && @@ -31,13 +31,13 @@ BlazeComponent.extendComponent({ Meteor.settings.public.sandstorm; if (isSandstorm && Utils.getCurrentBoardId()) { const currentBoard = Utils.getCurrentBoard(); - currentBoard.archive(); + await currentBoard.archive(); } const board = this.currentData(); - board.restore(); + await board.restore(); Utils.goBoardId(board._id); }, - 'click .js-delete-board': Popup.afterConfirm('boardDelete', function() { + 'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() { Popup.back(); const isSandstorm = Meteor.settings && @@ -45,9 +45,9 @@ BlazeComponent.extendComponent({ Meteor.settings.public.sandstorm; if (isSandstorm && Utils.getCurrentBoardId()) { const currentBoard = Utils.getCurrentBoard(); - Boards.remove(currentBoard._id); + await Boards.removeAsync(currentBoard._id); } - Boards.remove(this._id); + await Boards.removeAsync(this._id); FlowRouter.go('home'); }), }, diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index d9e1a99af..7317a7501 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -10,7 +10,7 @@ const UPCLS = 'fa-sort-up'; const sortCardsBy = new ReactiveVar(''); Template.boardChangeTitlePopup.events({ - submit(event, templateInstance) { + async submit(event, templateInstance) { const newTitle = templateInstance .$('.js-board-name') .val() @@ -20,8 +20,8 @@ Template.boardChangeTitlePopup.events({ .val() .trim(); if (newTitle) { - this.rename(newTitle); - this.setDescription(newDesc); + await this.rename(newTitle); + await this.setDescription(newDesc); Popup.back(); } event.preventDefault(); @@ -364,10 +364,10 @@ const CreateBoard = BlazeComponent.extendComponent({ }).register('createTemplateContainerPopup'); (class HeaderBarCreateBoard extends CreateBoard { - onSubmit(event) { + async onSubmit(event) { super.onSubmit(event); // Immediately star boards crated with the headerbar popup. - ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get()); + await ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get()); } }.register('headerBarCreateBoardPopup')); diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index d6a1b097e..381d37cde 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,6 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; +import getSlug from 'limax'; const subManager = new SubsManager(); @@ -143,7 +144,7 @@ BlazeComponent.extendComponent({ ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup-close'); }, - stop(evt, ui) { + async stop(evt, ui) { const prevBoardDom = ui.item.prev('.js-board').get(0); const nextBoardDom = ui.item.next('.js-board').get(0); const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1); @@ -153,7 +154,7 @@ BlazeComponent.extendComponent({ $boards.sortable('cancel'); const currentUser = ReactiveCache.getCurrentUser(); if (currentUser && typeof currentUser.setBoardSortIndex === 'function') { - currentUser.setBoardSortIndex(board._id, sortIndex.base); + await currentUser.setBoardSortIndex(board._id, sortIndex.base); } }, }); diff --git a/client/components/cards/cardDescription.js b/client/components/cards/cardDescription.js index c958c335e..8cf00f184 100644 --- a/client/components/cards/cardDescription.js +++ b/client/components/cards/cardDescription.js @@ -17,10 +17,10 @@ BlazeComponent.extendComponent({ events() { return [ { - 'submit .js-card-description'(event) { + async 'submit .js-card-description'(event) { event.preventDefault(); const description = this.currentComponent().getValue(); - this.data().setDescription(description); + await this.data().setDescription(description); }, // Pressing Ctrl+Enter should submit the form 'keydown form textarea'(evt) { diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 8c146369f..64708dd21 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -462,18 +462,18 @@ BlazeComponent.extendComponent({ const currentMode = Utils.getMobileMode(); Utils.setMobileMode(!currentMode); }, - 'submit .js-card-description'(event) { + async 'submit .js-card-description'(event) { event.preventDefault(); const description = this.currentComponent().getValue(); - this.data().setDescription(description); + await this.data().setDescription(description); }, - 'submit .js-card-details-title'(event) { + async 'submit .js-card-details-title'(event) { event.preventDefault(); const title = this.currentComponent().getValue().trim(); if (title) { - this.data().setTitle(title); + await this.data().setTitle(title); } else { - this.data().setTitle(''); + await this.data().setTitle(''); } }, 'submit .js-card-details-assigner'(event) { @@ -500,23 +500,23 @@ BlazeComponent.extendComponent({ this.find('button[type=submit]').click(); } }, - 'submit .js-card-details-sort'(event) { + async 'submit .js-card-details-sort'(event) { event.preventDefault(); const sort = parseFloat(this.currentComponent() .getValue() .trim()); if (!Number.isNaN(sort)) { let card = this.data(); - card.move(card.boardId, card.swimlaneId, card.listId, sort); + await card.move(card.boardId, card.swimlaneId, card.listId, sort); } }, - 'change .js-select-card-details-lists'(event) { + async 'change .js-select-card-details-lists'(event) { let card = this.data(); const listSelect = this.$('.js-select-card-details-lists')[0]; const listId = listSelect.options[listSelect.selectedIndex].value; const minOrder = card.getMinSort(listId, card.swimlaneId); - card.move(card.boardId, card.swimlaneId, listId, minOrder - 1); + await card.move(card.boardId, card.swimlaneId, listId, minOrder - 1); }, 'click .js-go-to-linked-card'() { Utils.goCardId(this.data().linkedId); @@ -554,8 +554,8 @@ BlazeComponent.extendComponent({ Session.set('cardDetailsIsDragging', false); Session.set('cardDetailsIsMouseDown', false); }, - 'click #toggleHideCheckedChecklistItems'() { - this.data().toggleHideCheckedChecklistItems(); + async 'click #toggleHideCheckedChecklistItems'() { + await this.data().toggleHideCheckedChecklistItems(); }, 'click #toggleCustomFieldsGridButton'() { Meteor.call('toggleCustomFieldsGrid'); @@ -862,21 +862,21 @@ Template.cardDetailsActionsPopup.events({ 'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'), 'click .js-copy-checklist-cards': Popup.open('copyManyCards'), 'click .js-set-card-color': Popup.open('setCardColor'), - 'click .js-move-card-to-top'(event) { + async 'click .js-move-card-to-top'(event) { event.preventDefault(); const minOrder = this.getMinSort(); - this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); + await this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); Popup.back(); }, - 'click .js-move-card-to-bottom'(event) { + async 'click .js-move-card-to-bottom'(event) { event.preventDefault(); const maxOrder = this.getMaxSort(); - this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); + await this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); Popup.back(); }, - 'click .js-archive': Popup.afterConfirm('cardArchive', function () { + 'click .js-archive': Popup.afterConfirm('cardArchive', async function () { Popup.close(); - this.archive(); + await this.archive(); Utils.goBoardId(this.boardId); }), 'click .js-more': Popup.open('cardMore'), @@ -1011,11 +1011,11 @@ Template.editCardAssignerForm.events({ const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); return ret; } - setDone(cardId, options) { + async setDone(cardId, options) { ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); const card = this.data(); let sortIndex = 0; - + if (cardId) { const targetCard = ReactiveCache.getCard(cardId); if (targetCard) { @@ -1030,8 +1030,8 @@ Template.editCardAssignerForm.events({ // If no card selected, move to end sortIndex = card.getMaxSort(options.listId, options.swimlaneId) + 1; } - - card.move(options.boardId, options.swimlaneId, options.listId, sortIndex); + + await card.move(options.boardId, options.swimlaneId, options.listId, sortIndex); } }).register('moveCardPopup'); @@ -1041,7 +1041,7 @@ Template.editCardAssignerForm.events({ const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); return ret; } - setDone(cardId, options) { + async setDone(cardId, options) { ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); const card = this.data(); @@ -1056,7 +1056,7 @@ Template.editCardAssignerForm.events({ if (newCardId) { const newCard = ReactiveCache.getCard(newCardId); let sortIndex = 0; - + if (cardId) { const targetCard = ReactiveCache.getCard(cardId); if (targetCard) { @@ -1071,8 +1071,8 @@ Template.editCardAssignerForm.events({ // If no card selected, copy to end sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1; } - - newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); + + await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); } // In case the filter is active we need to add the newly inserted card in @@ -1090,7 +1090,7 @@ Template.editCardAssignerForm.events({ const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); return ret; } - setDone(cardId, options) { + async setDone(cardId, options) { ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); const card = this.data(); @@ -1106,7 +1106,7 @@ Template.editCardAssignerForm.events({ sort: 0, }); const newCard = ReactiveCache.getCard(_id); - + let sortIndex = 0; if (cardId) { const targetCard = ReactiveCache.getCard(cardId); @@ -1121,8 +1121,8 @@ Template.editCardAssignerForm.events({ } else { sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1; } - - newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); + + await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); Filter.addException(_id); } @@ -1135,7 +1135,7 @@ Template.editCardAssignerForm.events({ const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); return ret; } - setDone(cardId, options) { + async setDone(cardId, options) { ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); const card = this.data(); @@ -1151,7 +1151,7 @@ Template.editCardAssignerForm.events({ if (newCardId) { const newCard = ReactiveCache.getCard(newCardId); let sortIndex = 0; - + if (cardId) { const targetCard = ReactiveCache.getCard(cardId); if (targetCard) { @@ -1165,8 +1165,8 @@ Template.editCardAssignerForm.events({ } else { sortIndex = newCard.getMaxSort(options.listId, options.swimlaneId) + 1; } - - newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); + + await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); } // In case the filter is active we need to add the newly inserted card in @@ -1202,14 +1202,14 @@ BlazeComponent.extendComponent({ 'click .js-palette-color'() { this.currentColor.set(this.currentData().color); }, - 'click .js-submit'(event) { + async 'click .js-submit'(event) { event.preventDefault(); - this.currentCard.setColor(this.currentColor.get()); + await this.currentCard.setColor(this.currentColor.get()); Popup.back(); }, - 'click .js-remove-color'(event) { + async 'click .js-remove-color'(event) { event.preventDefault(); - this.currentCard.setColor(null); + await this.currentCard.setColor(null); Popup.back(); }, }, @@ -1240,21 +1240,21 @@ BlazeComponent.extendComponent({ const color = colorClass ? colorClass.replace('card-details-', '') : null; this.currentColor.set(color); }, - 'click .js-submit'(event) { + async '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); - }); + for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) { + await card.setColor(color); + } Popup.back(); }, - 'click .js-remove-color'(event) { + async '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); - }); + for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) { + await card.setColor(null); + } Popup.back(); }, }, @@ -1866,7 +1866,7 @@ BlazeComponent.extendComponent({ // Close the card details pane by pressing escape EscapeActions.register( 'detailsPane', - () => { + async () => { // if card description diverges from database due to editing // ask user whether changes should be applied if (ReactiveCache.getCurrentUser()) { @@ -1874,7 +1874,7 @@ EscapeActions.register( currentDescription = document.getElementsByClassName("editor js-new-description-input").item(0) if (currentDescription?.value && !(currentDescription.value === Utils.getCurrentCard().getDescription())) { if (confirm(TAPi18n.__('rescue-card-description-dialogue'))) { - Utils.getCurrentCard().setDescription(document.getElementsByClassName("editor js-new-description-input").item(0).value); + await Utils.getCurrentCard().setDescription(document.getElementsByClassName("editor js-new-description-input").item(0).value); // Save it! console.log(document.getElementsByClassName("editor js-new-description-input").item(0).value); console.log("current description", Utils.getCurrentCard().getDescription()); diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index 15a9b9fa1..459fdeb0a 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -157,21 +157,21 @@ BlazeComponent.extendComponent({ textarea.focus(); }, - editChecklist(event) { + async editChecklist(event) { event.preventDefault(); const textarea = this.find('textarea.js-edit-checklist-item'); const title = textarea.value.trim(); const checklist = this.currentData().checklist; - checklist.setTitle(title); + await checklist.setTitle(title); }, - editChecklistItem(event) { + async editChecklistItem(event) { event.preventDefault(); const textarea = this.find('textarea.js-edit-checklist-item'); const title = textarea.value.trim(); const item = this.currentData().item; - item.setTitle(title); + await item.setTitle(title); }, pressKey(event) { @@ -321,20 +321,20 @@ BlazeComponent.extendComponent({ }, 'click .js-move-checklist': Popup.open('moveChecklist'), 'click .js-copy-checklist': Popup.open('copyChecklist'), - 'click .js-hide-checked-checklist-items'(event) { + async 'click .js-hide-checked-checklist-items'(event) { event.preventDefault(); - this.data().checklist.toggleHideCheckedChecklistItems(); + await this.data().checklist.toggleHideCheckedChecklistItems(); Popup.back(); }, - 'click .js-hide-all-checklist-items'(event) { + async 'click .js-hide-all-checklist-items'(event) { event.preventDefault(); - this.data().checklist.toggleHideAllChecklistItems(); + await this.data().checklist.toggleHideAllChecklistItems(); Popup.back(); }, - 'click .js-toggle-show-checklist-at-minicard'(event) { + async 'click .js-toggle-show-checklist-at-minicard'(event) { event.preventDefault(); const checklist = this.data().checklist; - checklist.toggleShowChecklistAtMinicard(); + await checklist.toggleShowChecklistAtMinicard(); Popup.back(); }, } @@ -365,11 +365,11 @@ Template.checklistItemDetail.helpers({ }); BlazeComponent.extendComponent({ - toggleItem() { + async toggleItem() { const checklist = this.currentData().checklist; const item = this.currentData().item; if (checklist && item && item._id) { - item.toggleItem(); + await item.toggleItem(); } }, events() { diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 194f37ee9..22ce35e62 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -91,10 +91,10 @@ BlazeComponent.extendComponent({ } }, - toggleChecklistItem() { + async toggleChecklistItem() { const item = this.currentData(); if (item && item._id) { - item.toggleItem(); + await item.toggleItem(); } }, @@ -319,15 +319,15 @@ Template.cardDetailsActionsPopup.events({ this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); Popup.back(); }, - 'click .js-move-card-to-bottom'(event) { + async 'click .js-move-card-to-bottom'(event) { event.preventDefault(); const maxOrder = this.getMaxSort(); - this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); + await this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); Popup.back(); }, - 'click .js-archive': Popup.afterConfirm('cardArchive', function () { + 'click .js-archive': Popup.afterConfirm('cardArchive', async function () { Popup.close(); - this.archive(); + await this.archive(); Utils.goBoardId(this.boardId); }), 'click .js-toggle-watch-card'() { diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js index 4396890e5..d0d19438c 100644 --- a/client/components/cards/subtasks.js +++ b/client/components/cards/subtasks.js @@ -62,10 +62,10 @@ BlazeComponent.extendComponent({ textarea.focus(); }, - deleteSubtask() { + async deleteSubtask() { const subtask = this.currentData().subtask; if (subtask && subtask._id) { - subtask.archive(); + await subtask.archive(); } }, @@ -73,12 +73,12 @@ BlazeComponent.extendComponent({ return ReactiveCache.getCurrentUser().isBoardAdmin(); }, - editSubtask(event) { + async editSubtask(event) { event.preventDefault(); const textarea = this.find('textarea.js-edit-subtask-item'); const title = textarea.value.trim(); const subtask = this.currentData().subtask; - subtask.setTitle(title); + await subtask.setTitle(title); }, pressKey(event) { @@ -105,10 +105,10 @@ BlazeComponent.extendComponent({ }).register('subtasks'); BlazeComponent.extendComponent({ - toggleItem() { + async toggleItem() { const item = this.currentData().item; if (item && item._id) { - item.toggleItem(); + await item.toggleItem(); } }, events() { @@ -138,11 +138,11 @@ BlazeComponent.extendComponent({ }); } }, - 'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', function () { + 'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', async function () { Popup.back(2); const subtask = this.subtask; if (subtask && subtask._id) { - subtask.archive(); + await subtask.archive(); } }), } diff --git a/client/components/import/import.js b/client/components/import/import.js index b8f7fe66b..b1e156b5e 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -3,6 +3,7 @@ import { trelloGetMembersToMap } from './trelloMembersMapper'; import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { wekanGetMembersToMap } from './wekanMembersMapper'; import { csvGetMembersToMap } from './csvMembersMapper'; +import getSlug from 'limax'; const Papa = require('papaparse'); diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index a9271ca6b..d85bcce51 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -2,6 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { Spinner } from '/client/lib/spinner'; +import getSlug from 'limax'; const subManager = new SubsManager(); const InfiniteScrollIter = 10; diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 2e57e694e..cf860877f 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -22,14 +22,14 @@ BlazeComponent.extendComponent({ isBoardAdmin() { return ReactiveCache.getCurrentUser().isBoardAdmin(); }, - starred(check = undefined) { + async starred(check = undefined) { const list = Template.currentData(); const status = list.isStarred(); if (check === undefined) { // just check return status; } else { - list.star(!status); + await list.star(!status); return !status; } }, @@ -45,14 +45,14 @@ BlazeComponent.extendComponent({ return next; } }, - editTitle(event) { + async editTitle(event) { event.preventDefault(); const newTitle = this.childComponents('inlinedForm')[0] .getValue() .trim(); const list = this.currentData(); if (newTitle) { - list.rename(newTitle.trim()); + await list.rename(newTitle.trim()); } }, @@ -226,9 +226,9 @@ Template.listActionPopup.events({ if (!err && ret) Popup.back(); }); }, - 'click .js-close-list'(event) { + async 'click .js-close-list'(event) { event.preventDefault(); - this.archive(); + await this.archive(); Popup.back(); }, 'click .js-set-wip-limit': Popup.open('setWipLimit'), @@ -255,26 +255,26 @@ BlazeComponent.extendComponent({ } }, - enableSoftLimit() { + async enableSoftLimit() { const list = Template.currentData(); if ( list.getWipLimit('soft') && list.getWipLimit('value') < list.cards().length ) { - list.setWipLimit(list.cards().length); + await list.setWipLimit(list.cards().length); } Meteor.call('enableSoftLimit', Template.currentData()._id); }, - enableWipLimit() { + 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 ) { - list.setWipLimit(list.cards().length); + await list.setWipLimit(list.cards().length); } Meteor.call('enableWipLimit', list._id); }, @@ -368,12 +368,12 @@ BlazeComponent.extendComponent({ 'click .js-palette-color'() { this.currentColor.set(this.currentData().color); }, - 'click .js-submit'() { - this.currentList.setColor(this.currentColor.get()); + async 'click .js-submit'() { + await this.currentList.setColor(this.currentColor.get()); Popup.close(); }, - 'click .js-remove-color'() { - this.currentList.setColor(null); + async 'click .js-remove-color'() { + await this.currentList.setColor(null); Popup.close(); }, }, diff --git a/client/components/main/bookmarks.js b/client/components/main/bookmarks.js index 6ec110593..2e0504960 100644 --- a/client/components/main/bookmarks.js +++ b/client/components/main/bookmarks.js @@ -15,12 +15,12 @@ Template.bookmarks.helpers({ }); Template.bookmarks.events({ - 'click .js-toggle-star'(e) { + async 'click .js-toggle-star'(e) { e.preventDefault(); const boardId = this._id; const user = ReactiveCache.getCurrentUser(); if (user && boardId) { - user.toggleBoardStar(boardId); + await user.toggleBoardStar(boardId); } }, }); @@ -42,12 +42,12 @@ Template.bookmarksPopup.helpers({ }); Template.bookmarksPopup.events({ - 'click .js-toggle-star'(e) { + async 'click .js-toggle-star'(e) { e.preventDefault(); const boardId = this._id; const user = ReactiveCache.getCurrentUser(); if (user && boardId) { - user.toggleBoardStar(boardId); + await user.toggleBoardStar(boardId); } }, }); diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index bda802102..f09f09c3f 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -327,9 +327,9 @@ Template.boardMenuPopup.events({ // You could add a toast notification here if available } }), - 'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() { + 'click .js-archive-board ': Popup.afterConfirm('archiveBoard', async function() { const currentBoard = Utils.getCurrentBoard(); - currentBoard.archive(); + await currentBoard.archive(); // XXX We should have some kind of notification on top of the page to // confirm that the board was successfully archived. FlowRouter.go('home'); @@ -382,7 +382,7 @@ Template.memberPopup.events({ Popup.back(); }, 'click .js-change-role': Popup.open('changePermissions'), - 'click .js-remove-member': Popup.afterConfirm('removeMember', function() { + 'click .js-remove-member': Popup.afterConfirm('removeMember', async function() { // This works from removing member from board, card members and assignees. const boardId = Session.get('currentBoard'); const memberId = this.userId; @@ -392,7 +392,7 @@ Template.memberPopup.events({ ReactiveCache.getCards({ boardId, assignees: memberId }).forEach(card => { card.unassignAssignee(memberId); }); - ReactiveCache.getBoard(boardId).removeMember(memberId); + await ReactiveCache.getBoard(boardId).removeMember(memberId); Popup.back(); }), 'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => { @@ -774,10 +774,10 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click .js-select-background'(evt) { + async 'click .js-select-background'(evt) { const currentBoard = Utils.getCurrentBoard(); const newColor = this.currentData().toString(); - currentBoard.setColor(newColor); + await currentBoard.setColor(newColor); evt.preventDefault(); }, }, @@ -789,10 +789,10 @@ BlazeComponent.extendComponent({ events() { return [ { - submit(event) { + async submit(event) { const currentBoard = Utils.getCurrentBoard(); const backgroundImageURL = this.find('.js-board-background-image-url').value.trim(); - currentBoard.setBackgroundImageURL(backgroundImageURL); + await currentBoard.setBackgroundImageURL(backgroundImageURL); Utils.setBackgroundImage(); Popup.back(); event.preventDefault(); @@ -1945,7 +1945,7 @@ Template.removeBoardTeamPopup.helpers({ }); Template.changePermissionsPopup.events({ - 'click .js-set-admin, click .js-set-normal, click .js-set-normal-assigned-only, click .js-set-no-comments, click .js-set-comment-only, click .js-set-comment-assigned-only, click .js-set-read-only, click .js-set-read-assigned-only, click .js-set-worker'( + async 'click .js-set-admin, click .js-set-normal, click .js-set-normal-assigned-only, click .js-set-no-comments, click .js-set-comment-only, click .js-set-comment-assigned-only, click .js-set-read-only, click .js-set-read-assigned-only, click .js-set-worker'( event, ) { const currentBoard = Utils.getCurrentBoard(); @@ -1964,7 +1964,7 @@ Template.changePermissionsPopup.events({ const isReadAssignedOnly = $(event.currentTarget).hasClass('js-set-read-assigned-only'); const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments'); const isWorker = $(event.currentTarget).hasClass('js-set-worker'); - currentBoard.setMemberPermission( + await currentBoard.setMemberPermission( memberId, isAdmin, isNoComments, diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js index f79e601ee..e7f4adbb8 100644 --- a/client/components/sidebar/sidebarArchives.js +++ b/client/components/sidebar/sidebarArchives.js @@ -81,76 +81,76 @@ BlazeComponent.extendComponent({ events() { return [ { - 'click .js-restore-card'() { + async 'click .js-restore-card'() { const card = this.currentData(); if (card.canBeRestored()) { - card.restore(); + await card.restore(); } }, - 'click .js-restore-all-cards'() { - this.archivedCards().forEach(card => { + async 'click .js-restore-all-cards'() { + for (const card of this.archivedCards()) { if (card.canBeRestored()) { - card.restore(); + await card.restore(); } - }); + } }, - 'click .js-delete-card': Popup.afterConfirm('cardDelete', function() { + 'click .js-delete-card': Popup.afterConfirm('cardDelete', async function() { const cardId = this._id; - Cards.remove(cardId); + await Cards.removeAsync(cardId); Popup.back(); }), - 'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => { - this.archivedCards().forEach(card => { - Cards.remove(card._id); - }); + 'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', async () => { + for (const card of this.archivedCards()) { + await Cards.removeAsync(card._id); + } Popup.back(); }), - 'click .js-restore-list'() { + async 'click .js-restore-list'() { const list = this.currentData(); - list.restore(); + await list.restore(); }, - 'click .js-restore-all-lists'() { - this.archivedLists().forEach(list => { - list.restore(); - }); + async 'click .js-restore-all-lists'() { + for (const list of this.archivedLists()) { + await list.restore(); + } }, - 'click .js-delete-list': Popup.afterConfirm('listDelete', function() { - this.remove(); + 'click .js-delete-list': Popup.afterConfirm('listDelete', async function() { + await this.remove(); Popup.back(); }), - 'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => { - this.archivedLists().forEach(list => { - list.remove(); - }); + 'click .js-delete-all-lists': Popup.afterConfirm('listDelete', async () => { + for (const list of this.archivedLists()) { + await list.remove(); + } Popup.back(); }), - 'click .js-restore-swimlane'() { + async 'click .js-restore-swimlane'() { const swimlane = this.currentData(); - swimlane.restore(); + await swimlane.restore(); }, - 'click .js-restore-all-swimlanes'() { - this.archivedSwimlanes().forEach(swimlane => { - swimlane.restore(); - }); + async 'click .js-restore-all-swimlanes'() { + for (const swimlane of this.archivedSwimlanes()) { + await swimlane.restore(); + } }, 'click .js-delete-swimlane': Popup.afterConfirm( 'swimlaneDelete', - function() { - this.remove(); + async function() { + await this.remove(); Popup.back(); }, ), 'click .js-delete-all-swimlanes': Popup.afterConfirm( 'swimlaneDelete', - () => { - this.archivedSwimlanes().forEach(swimlane => { - swimlane.remove(); - }); + async () => { + for (const swimlane of this.archivedSwimlanes()) { + await swimlane.remove(); + } Popup.back(); }, ), diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js index 1caeda34c..943e003cd 100644 --- a/client/components/swimlanes/swimlaneHeader.js +++ b/client/components/swimlanes/swimlaneHeader.js @@ -8,14 +8,14 @@ Meteor.startup(() => { }); BlazeComponent.extendComponent({ - editTitle(event) { + async editTitle(event) { event.preventDefault(); const newTitle = this.childComponents('inlinedForm')[0] .getValue() .trim(); const swimlane = this.currentData(); if (newTitle) { - swimlane.rename(newTitle.trim()); + await swimlane.rename(newTitle.trim()); } }, collapsed(check = undefined) { @@ -106,9 +106,9 @@ Template.editSwimlaneTitleForm.helpers({ Template.swimlaneActionPopup.events({ 'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'), 'click .js-set-swimlane-height': Popup.open('setSwimlaneHeight'), - 'click .js-close-swimlane'(event) { + async 'click .js-close-swimlane'(event) { event.preventDefault(); - this.archive(); + await this.archive(); Popup.back(); }, 'click .js-move-swimlane': Popup.open('moveSwimlane'), @@ -183,20 +183,20 @@ BlazeComponent.extendComponent({ events() { return [ { - 'submit form'(event) { + async 'submit form'(event) { event.preventDefault(); - this.currentSwimlane.setColor(this.currentColor.get()); + await this.currentSwimlane.setColor(this.currentColor.get()); Popup.back(); }, 'click .js-palette-color'() { this.currentColor.set(this.currentData().color); }, - 'click .js-submit'() { - this.currentSwimlane.setColor(this.currentColor.get()); + async 'click .js-submit'() { + await this.currentSwimlane.setColor(this.currentColor.get()); Popup.back(); }, - 'click .js-remove-color'() { - this.currentSwimlane.setColor(null); + async 'click .js-remove-color'() { + await this.currentSwimlane.setColor(null); Popup.back(); }, }, diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js index e06a6463e..7a72df472 100644 --- a/client/lib/keyboard.js +++ b/client/lib/keyboard.js @@ -268,7 +268,7 @@ hotkeys('space', (event) => { } }); -const archiveCard = (event) => { +const archiveCard = async (event) => { event.preventDefault(); const cardId = getSelectedCardId(); if (!cardId) { @@ -282,7 +282,7 @@ const archiveCard = (event) => { if (Utils.canModifyBoard()) { const card = Cards.findOne(cardId); - card.archive(); + await card.archive(); } }; diff --git a/client/lib/utils.js b/client/lib/utils.js index 52f4a0ac3..735e23025 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -2,14 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; Utils = { - setBackgroundImage(url) { + async setBackgroundImage(url) { const currentBoard = Utils.getCurrentBoard(); if (currentBoard.backgroundImageURL !== undefined) { $(".board-wrapper").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"}); $(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"}); $(".minicard").css({"opacity": "0.9"}); } else if (currentBoard["background-color"]) { - currentBoard.setColor(currentBoard["background-color"]); + await currentBoard.setColor(currentBoard["background-color"]); } }, /** returns the current board id diff --git a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md index c5835818c..2e81599f6 100644 --- a/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md +++ b/docs/Platforms/FOSS/FerretDB2-PostgreSQL.md @@ -16,7 +16,7 @@ sudo npm install -g n export N_NODE_MIRROR=https://github.com/wekan/node-v14-esm/releases/download sudo -E n 14.21.4 sudo npm -g install @mapbox/node-pre-gyp -sudo npm -g install meteor@2.14 --unsafe-perm +sudo npm -g install meteor@2.16 --unsafe-perm export PATH=$PATH:$HOME/.meteor meteor npm install production meteor build .build --directory --platforms=web.browser diff --git a/docs/Security/Sandboxes/vscode/README.md b/docs/Security/Sandboxes/vscode/README.md new file mode 100644 index 000000000..48d211018 --- /dev/null +++ b/docs/Security/Sandboxes/vscode/README.md @@ -0,0 +1,82 @@ +# Secure Sandbox: VSCode at Debian 13 amd64 + +Related files at this repo `.vscode` at [this commit](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088). + +## 1) Install Debian + +Install Debian with username `wekan`, so that WeKan repo here, only directory where VSCode will have access: +``` +/home/wekan/repos/wekan +``` + +## 2) Install Flatpak and VSCode + +``` +sudo apt install flatpak + +sudo apt install gnome-software-plugin-flatpak + +flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo + +sudo reboot + +flatpak install flathub com.visualstudio.code +``` + +## 3) Edit VSCode desktop icon + +``` +nano ~/.local/share/applications/wekan-vscode.desktop +``` +Content: +``` +[Desktop Entry] +Name=VS Code - Wekan +Comment=Open the Weka project with Flatpak +Exec=flatpak run com.visualstudio.code /home/wekan/repos/wekan +Icon=com.visualstudio.code +Terminal=false +Type=Application +Categories=Development;IDE; +StartupWMClass=code +``` + +## 4) Force VS Code to use the internal (isolated) browser + +This setting is also added as git commit to VSCode settings. + +This is the most important step. If this is "native", it will use the operating system window that sees everything. + +1. Open VS Code. +2. Press `Ctrl + ,` (options). +3. Type in search: **Dialogs: Custom** +4. Change the `Files: Simple Dialog` setting to **on** (check the box). +5. Restart VS Code. + +## 5) Set the strictest sandbox possible (in Terminal) + +Run these two commands (the first clears everything, the second sets limits): + +```bash +# Reset previous attempts +sudo flatpak override --reset com.visualstudio.code + +# Block EVERYTHING except the display and the wekan folder +sudo flatpak override com.visualstudio.code \ +--nofilesystem=home \ +--nofilesystem=host \ +--nofilesystem=xdg-run/gvfs \ +--nofilesystem=xdg-run/gvfsd \ +--filesystem=~/repos/wekan:rw \ +--device=all \ +--socket=wayland \ +--socket=x11 + +``` + +## 6) Test "File -> Open Folder" + +Now when you go to **File -> Open Folder**: + +1. You will no longer see the fancy system file window, but VS Code's own, simple list. +2. If you try to go to the parent folder or somewhere else, **the list is empty** or it only shows `~/repos/wekan`. diff --git a/imports/i18n/data/sr.i18n.json b/imports/i18n/data/sr.i18n.json index 9d591cd38..53dd58012 100644 --- a/imports/i18n/data/sr.i18n.json +++ b/imports/i18n/data/sr.i18n.json @@ -1,5 +1,5 @@ { - "accept": "Прихвати", + "accept": "Прихватам", "act-activity-notify": "Обавештење о последњим променама", "act-addAttachment": "унео предметну грађу __attachment__ у предмет __card__ у делу __list__ поступка __swimlane__ међу списе __board__", "act-deleteAttachment": "уклонио предметну грађу __attachment__ из предмета __card__ у делу __list__ поступка __swimlane__ из списа __board__", @@ -124,7 +124,7 @@ "addMemberPopup-title": "Прими сараднике", "memberPopup-title": "Избор сарадника", "admin": "Управник", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Има увид и пун приступ у све предмете из ових списа. У овим списима може да бира сараднике, поставља правила рада и чита записник.", "admin-announcement": "Јавни разглас", "admin-announcement-active": "Пусти на јавни разглас", "admin-announcement-title": "Јавно саопштење управника:", @@ -139,8 +139,8 @@ "archive-board": "Спакуј ове списе у архиву", "archive-board-confirm": "Да ли сте сигурни да желите да спакујете ове списе у архив?", "archive-card": "Спакуј предмет у архиву", - "archive-list": "Спакуј овај део поступка у архиву", - "archive-swimlane": "Спакуј овај ток поступка у архиву", + "archive-list": "Спакуј овај део у архиву", + "archive-swimlane": "Спакуј цео поступак у архиву", "archive-selection": "Спакуј изабрано у архиву", "archiveBoardPopup-title": "Архивираћете ове списе?", "archived-items": "Архива", @@ -172,10 +172,10 @@ "boardInfoOnMyBoards-title": "Списи у мојој надлежности", "show-card-counter-per-list": "Прикажи бројач предмета на сваком делу тока поступка", "show-board_members-avatar": "Прикажи слике сарадника на омоту списа", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", + "board_members": "Читава сарадничка мрежа", + "card_members": "Мрежа сарадника на овом предмету у овим списима", + "board_assignees": "Сви пуномоћници из свих предмета у овим списима", + "card_assignees": "Сви пуномоћници текућег предмета у овим списима", "board-nb-stars": "%s звездица", "board-not-found": "Спис није пронађен", "board-private-info": "Ови списи су видљиви само сарадницима.", @@ -285,8 +285,8 @@ "change-permissions": "Промени улогу", "change-settings": "Поставка предмета", "changeAvatarPopup-title": "Моја слика", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", + "delete-avatar-confirm": "Да ли сте сигурни да желите да уклоните ову слику?", + "deleteAvatarPopup-title": "Уклањате слику?", "changeLanguagePopup-title": "Избор језика", "changePasswordPopup-title": "Промена лозинке", "changePermissionsPopup-title": "Избор улоге", @@ -328,22 +328,22 @@ "color-slateblue": "загаситоплава", "color-white": "бела", "color-yellow": "жута", - "unset-color": "Обриши подешавање", + "unset-color": "Безбојно", "comments": "Ставови", "comment": "Изнеси мишљење", "comment-placeholder": "Место за расправу", "comment-only": "Стручни саветник", - "comment-only-desc": "Једино може да учествује у расправи око одређеног предмета.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", + "comment-only-desc": "Има право увида у све предмете и може да учествује у свим расправама.", + "comment-assigned-only": "Спољни стручни саветник", + "comment-assigned-only-desc": "Једино може да има право увида и узме учешће у расправама у додељеним му предметима.", "comment-delete": "Да ли сте сигурни да желите да повучете изнешено мишљење?", "deleteCommentPopup-title": "Повлачите мишљење?", "no-comments": "Посматрач", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Без права на расправу и права на увид у записник", + "read-only": "Читалац", + "read-only-desc": "Има право увида у све предмете.", + "read-assigned-only": "Спољни читалац", + "read-assigned-only-desc": "Има право увида само у додељене предмете.", "worker": "Приправник", "worker-desc": "Може да ради помоћне послове - да премешта предмете, бирa оне које ће пратити и да учествује у расправи. ", "computer": "Рачунар", @@ -351,7 +351,7 @@ "confirm-checklist-delete-popup": "Да ли сте сигурни да желите да избришете ову предметну радњу?", "subtaskDeletePopup-title": "Избрисаћете издвојени посао?", "checklistDeletePopup-title": "Избрисаћете предметну радњу?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", + "checklistItemDeletePopup-title": "Избрисаћете ставку из предметне радње?", "copy-card-link-to-clipboard": "Причувај везу на кратко", "copy-text-to-clipboard": "Причувај текст на кратко", "linkCardPopup-title": "Повежи предмет", @@ -382,11 +382,11 @@ "custom-field-text": "Текст", "custom-fields": "Придружене рубрике", "date": "Датум", - "date-format": "Начин записивање датума", + "date-format": "Запис", "date-format-yyyy-mm-dd": "година-месец-дан", "date-format-dd-mm-yyyy": "дан-месец-година", "date-format-mm-dd-yyyy": "месец-дан-година", - "decline": "Одбиј", + "decline": "Одбијам", "default-avatar": "иницијали уместо слике", "delete": "Уклони", "deleteCustomFieldPopup-title": "Обрисаћете ову придружену рубрику?", @@ -400,7 +400,7 @@ "edit": "Уреди", "edit-avatar": "Моја слика", "edit-profile": "Лични подаци", - "edit-wip-limit": "Затрпавање предметима", + "edit-wip-limit": "Затрпај се предметима", "soft-wip-limit": "Мека граница броја предмета", "editCardStartDatePopup-title": "Кад сте започели рад на предмету", "editCardDueDatePopup-title": "Крајњи рок за предмет", @@ -426,7 +426,7 @@ "email-verifyEmail-subject": "Потврдите Вашу адресу е-поште на страници __siteName__", "email-verifyEmail-text": "Добар дан __user__,\n\nДа би сте потврдили ваш налог за е-пошту, једноставно притисните на везу испод.\n\n__url__\n\nХвала.", "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Ограничи број предмета", + "enable-wip-limit": "Уведи ограничење", "error-board-doesNotExist": "Овакви списи не постоје", "error-board-notAdmin": "Да би то урадили, треба да будете управник ових списа", "error-board-notAMember": "Да би то урадили треба да будете сарадник на овим списима", @@ -525,7 +525,7 @@ "invalid-time": "Нетачно време", "invalid-user": "Непознат корисник", "joined": "придружен", - "just-invited": "Управо сте позвани да радите на овим списима", + "just-invited": "Управо сте позвани да узмете учешће у раду на овим списима", "keyboard-shortcuts": "Пречице на тастатури", "label-create": "Уведи нову налепницу", "label-default": "%s налепница (подразумевано)", @@ -542,7 +542,7 @@ "list-move-cards": "Премести све предмете из овог дела поступка", "list-select-cards": "Изабери све предмете", "set-color-list": "Обоји", - "listActionPopup-title": "Радње у делу поступка", + "listActionPopup-title": "Радње у овом делу поступка", "settingsUserPopup-title": "Рад са сарадницима", "settingsTeamPopup-title": "Рад са правним тимовима", "settingsOrgPopup-title": "Рад са странкама", @@ -560,7 +560,7 @@ "log-in": "Пријави се", "loginPopup-title": "Пријавница", "memberMenuPopup-title": "Сарадник", - "grey-icons": "Grey Icons", + "grey-icons": "Сиви дом", "members": "Сарадници", "menu": "Мени", "move-selection": "Премести изабрано", @@ -568,13 +568,13 @@ "moveCardToBottom-title": "Премести на дно", "moveCardToTop-title": "Премести на врх", "moveSelectionPopup-title": "Премести изабрано", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", + "copySelectionPopup-title": "Умножи изабрано", + "selection-color": "Боја за изабрано", "multi-selection": "Вишеструк избор", "multi-selection-label": "Залепите или одлепите налепнице на одабране предмете", "multi-selection-member": "Одаберите и сараднике", "multi-selection-on": "Постоји вишеструк избор", - "muted": "Не примај обававештења", + "muted": "Не примај обавештења", "muted-info": "Нећете чути обавештења кад наступе било какве промене у овим списима", "my-boards": "Списи у мојој надлежности", "name": "Задајте (нови) натпис", @@ -583,10 +583,10 @@ "no-archived-swimlanes": "Нема архивираних врсти поступака.", "no-results": "Нема резултата", "normal": "Виши сарадник", - "normal-desc": "Може да има и увид и пун приступ предметима. Не може да поставља правила рада на списима.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Позив још није прихваћен", + "normal-desc": "Може да има и увид и пун приступ у све предмете из ових списа. Не може да поставља правила рада у овим списима.", + "normal-assigned-only": "Спољни виши сарадник", + "normal-assigned-only-desc": "Има увид и пуна права само у оним предметима где има надлежност или је опуномоћен.", + "not-accepted-yet": "Позив још увек није прихваћен", "notify-participate": "Примајте допунске извештаје при било којој измени предмета које сте сами завели или где сте сарадник", "notify-watch": "Примајте допунске извештаје при било којој измени списа, делова поступака или предмета које пратите", "optional": "по избору", @@ -626,7 +626,7 @@ "select-color": "Изабери боју за овакав поступак", "select-board": "Изаберите списе", "set-wip-limit-value": "Поставите границу дозвољеног броја предмета у овом делу поступка", - "setWipLimitPopup-title": "Ограничење броја предмета", + "setWipLimitPopup-title": "Ограничи број предмета", "shortcut-add-self": "Доделите надлежност себи", "shortcut-assign-self": "Поверите пуномоћ себи", "shortcut-autocomplete-emoji": "Сам попуни emoji", @@ -776,7 +776,7 @@ "editCardReceivedDatePopup-title": "Датум и време запримања предмета", "editCardEndDatePopup-title": "Кад је задатак окончан", "setCardColorPopup-title": "Боја омота предмета", - "setSelectionColorPopup-title": "Set selection color", + "setSelectionColorPopup-title": "Боја за изабране ставке", "setCardActionsColorPopup-title": "Боја за подлогу предмета", "setSwimlaneColorPopup-title": "Боја за врсту поступка", "setListColorPopup-title": "Боја за део поступка", @@ -787,8 +787,8 @@ "delete-board-confirm-popup": "Сви делови поступка, предмети, налепнице и записници биће избачени и нећете моћи да повратите садржај списа. Опозив ове радње неће бити могућ.", "boardDeletePopup-title": "Избацићете ове списе?", "delete-board": "Избаци списе", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", + "delete-all-notifications": "Избриши сва обавештења", + "delete-all-notifications-confirm": "Да ли сте сигурни да желите да избришете сва обавештења? Опозив ове радње неће бити могућ.", "delete-duplicate-lists": "Уклоните истоимене делове поступка", "delete-duplicate-lists-confirm": "Да ли сте сигурни? Овом радњом ће бити избрисани сви истоимени делови поступка где нема предмета.", "default-subtasks-board": "Утврђени пријемни списи __board__", @@ -969,8 +969,8 @@ "a-endAt": "измењено време завршетка", "a-startAt": "измењено време почетка", "a-receivedAt": "измењено време пријема", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", + "above-selected-card": "Изнад изабраног предмета", + "below-selected-card": "Испод изабраног предмета", "almostdue": "приближава се крајњи рок %s", "pastdue": "крајњи рок %s је пробијен", "duenow": "крајњи рок %s је данас", @@ -979,7 +979,7 @@ "act-almostdue": "подсећам да се приближавамо последњем року (__timeValue__) за завршетак задатка __card__", "act-pastdue": "подсећам да је последњи рок (__timeValue__) задатка __card__ пробијен", "act-duenow": "подсећам да последњи рок (__timeValue__) задатка __card__ истиче данас", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "споменути сте у расправи у предмету __card__: __comment__ у делу __list__ поступка __swimlane__ у списима __board__", "delete-user-confirm-popup": "Да ли сте сигурни да желите да уклоните овај појединачни кориснички налог сарадника? Опозив ове радње неће бити могућ.", "delete-team-confirm-popup": "Да ли сте сигурни да желите да уклоните овај цео правни тим? Опозив ове радње неће бити могућ.", "delete-org-confirm-popup": "Да ли сте сигурни да желите да уклоните ову странку? Опозив ове радње неће бити могућ.", @@ -1003,7 +1003,7 @@ "view-all": "Прикажи сва", "filter-by-unread": "Издвој непрочитана", "mark-all-as-read": "Означи све као прочитано", - "mark-all-as-unread": "Mark all as unread", + "mark-all-as-unread": "Означи све као непрочитано", "remove-all-read": "Склони све прочитано", "allow-rename": "Дозволи преименовање", "allowRenamePopup-title": "Дозволи преименовање", @@ -1179,7 +1179,7 @@ "server-error-troubleshooting": "Молим да нам пошаљете извештај о грешци коју је изазвао сервер.\nАко је у питању snap инсталација, покрените: `sudo snap logs wekan.wekan`\nАко је у питању Docker инсталација, покрените: `sudo docker logs wekan-app`", "title-alphabetically": "По наслову абучним редом", "created-at-newest-first": "По најновијим запримљеним", - "created-at-oldest-first": "По најстарије запримљеним)", + "created-at-oldest-first": "По најстарије запримљеним", "links-heading": "Везе", "hide-activities-of-all-boards": "Don't show the board activities on all boards", "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", @@ -1327,11 +1327,11 @@ "hideAllChecklistItems": "Сакриј све помоћне предметне радње", "support": "Подршка", "supportPopup-title": "Подршка", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", + "support-page-enabled": "Неопходна ми је техничка подршка", + "support-info-not-added-yet": "Информације о техничкој подршци још увек нису додате", + "support-info-only-for-logged-in-users": "Информације о техничкој подршци се дају само пријављеним корисницима.", + "support-title": "Наслов", + "support-content": "Садржај", "accessibility": "Особе са посебним потешкоћама", "accessibility-page-enabled": "Омогући страницу за особе са посебним потешкоћама", "accessibility-info-not-added-yet": "Информације намењене особама са посебним потешкоћама, за сада, нису додате", @@ -1389,31 +1389,31 @@ "cron-job-deleted": "Успешно је уклоњен заказани посао", "cron-job-pause-failed": "Није успело паузирање заказаног посла", "cron-job-paused": "Заказани посао је успешно паузиран", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", + "cron-migration-errors": "Грешке приликом обнове списа", + "cron-migration-warnings": "Упозорења приликом обнове списа", + "cron-no-errors": "Нису се догодиле никакве грешке", + "cron-error-severity": "Учесталост", "cron-error-time": "Време", - "cron-error-message": "Error Message", + "cron-error-message": "Порука о грешци", "cron-error-details": "Појединости", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", + "cron-clear-errors": "Избриши све наведене грешке", + "cron-retry-failed": "Понови неуспешне обнове", + "cron-resume-paused": "Настави обнову након предаха", + "cron-errors-cleared": "Све грешке су успешно очишћене", + "cron-no-failed-migrations": "Нема понављања јер није ни било неуспешних обнова", + "cron-no-paused-migrations": "Нема настављања јер се није ни правио предах", + "cron-migrations-resumed": "Обнове су успешно настављене", + "cron-migrations-retried": "Неуспеле обнове су управо поново покренуте", "complete": "Посао је успешно обављен", "idle": "Стање мировања", "filesystem-path-description": "Почетак пута до складишта предметне грађе", "gridfs-enabled": "GridFS ради и можете га користити", "gridfs-enabled-description": "Можете користити MongoDB GridFS за складиште предметне грађе", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", + "all-migrations": "Све обнове", + "select-migration": "Изабрана обнова", "start": "Започет", - "pause": "Pause", - "stop": "Stop", + "pause": "Предах", + "stop": "Заустави", "migration-pause-failed": "Није било могуће направити предах у поступку обнове оштећених списа", "migration-paused": "Направљен је предах у поступку обнове оштећених списа", "migration-progress": "Напредак у току обнове", @@ -1511,9 +1511,9 @@ "migration-progress-status": "Стање", "migration-progress-details": "Појединости", "migration-progress-note": "Молимо да будете стрпљиви док траје препакивање Ваших списа...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", + "steps": "кораци", + "view": "Поглед", + "has-swimlanes": "има више поступака", "step-analyze-board-structure": "Изучавам везе у списима", "step-fix-orphaned-cards": "Поправљам одбачене предмете", @@ -1615,9 +1615,9 @@ "schedule": "Распоред", "search-boards-or-operations": "Претрага списа или радњи...", "show-list-on-minicard": "Прикажи део поступка на омоту", - "showChecklistAtMinicard": "Show Checklist at Minicard", + "showChecklistAtMinicard": "Прикажи предметну радњу на омоту", "showing": "Приказујем", - "start-test-operation": "Start Test Operation", + "start-test-operation": "Покрени симулацију", "start-time": "Покрени штоперицу", "step-progress": "Појединачни напредак", "stop-migration": "Заустави обнову", @@ -1629,5 +1629,5 @@ "unmigrated-boards": "Необновљени списи", "weight": "Оптерећење", "cron": "Периодични послови", - "current-step": "Current Step" + "current-step": "Текући корак" } diff --git a/models/boards.js b/models/boards.js index 56f21f37d..55328683d 100644 --- a/models/boards.js +++ b/models/boards.js @@ -12,6 +12,7 @@ import { import Users from "./users"; import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import TableVisibilityModeSettings from "./tableVisibilityModeSettings"; +import getSlug from 'limax'; // const escapeForRegex = require('escape-string-regexp'); @@ -1431,93 +1432,80 @@ Boards.helpers({ isTemplatesBoard() { return this.type === 'template-container'; }, -}); -Boards.mutations({ - archive() { - return { $set: { archived: true, archivedAt: new Date() } }; + async archive() { + return await Boards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); }, - restore() { - return { $set: { archived: false } }; + async restore() { + return await Boards.updateAsync(this._id, { $set: { archived: false } }); }, - rename(title) { - return { $set: { title } }; + async rename(title) { + return await Boards.updateAsync(this._id, { $set: { title } }); }, - setDescription(description) { - return { $set: { description } }; + async setDescription(description) { + return await Boards.updateAsync(this._id, { $set: { description } }); }, - setColor(color) { - return { $set: { color } }; + async setColor(color) { + return await Boards.updateAsync(this._id, { $set: { color } }); }, - setBackgroundImageURL(backgroundImageURL) { + async setBackgroundImageURL(backgroundImageURL) { const currentUser = ReactiveCache.getCurrentUser(); - if(currentUser.isBoardAdmin()) { - return { $set: { backgroundImageURL } }; - } else if (currentUser.isAdmin()) { - return { $set: { backgroundImageURL } }; - } else { - return false; + if (currentUser.isBoardAdmin() || currentUser.isAdmin()) { + return await Boards.updateAsync(this._id, { $set: { backgroundImageURL } }); } + return false; }, - setVisibility(visibility) { - return { $set: { permission: visibility } }; + async setVisibility(visibility) { + return await Boards.updateAsync(this._id, { $set: { permission: visibility } }); }, - addLabel(name, color) { - // If label with the same name and color already exists we don't want to - // create another one because they would be indistinguishable in the UI - // (they would still have different `_id` but that is not exposed to the - // user). + async addLabel(name, color) { if (!this.getLabel(name, color)) { const _id = Random.id(6); - return { $push: { labels: { _id, name, color } } }; + return await Boards.updateAsync(this._id, { $push: { labels: { _id, name, color } } }); } - return {}; + return null; }, - editLabel(labelId, name, color) { + async editLabel(labelId, name, color) { if (!this.getLabel(name, color)) { const labelIndex = this.labelIndex(labelId); - return { + return await Boards.updateAsync(this._id, { $set: { [`labels.${labelIndex}.name`]: name, [`labels.${labelIndex}.color`]: color, }, - }; + }); } - return {}; + return null; }, - removeLabel(labelId) { - return { $pull: { labels: { _id: labelId } } }; + async removeLabel(labelId) { + return await Boards.updateAsync(this._id, { $pull: { labels: { _id: labelId } } }); }, - changeOwnership(fromId, toId) { + async changeOwnership(fromId, toId) { const memberIndex = this.memberIndex(fromId); - return { - $set: { - [`members.${memberIndex}.userId`]: toId, - }, - }; + return await Boards.updateAsync(this._id, { + $set: { [`members.${memberIndex}.userId`]: toId }, + }); }, - addMember(memberId) { + async addMember(memberId) { const memberIndex = this.memberIndex(memberId); if (memberIndex >= 0) { - return { - $set: { - [`members.${memberIndex}.isActive`]: true, - }, - }; + return await Boards.updateAsync(this._id, { + $set: { [`members.${memberIndex}.isActive`]: true }, + }); } - return { + return await Boards.updateAsync(this._id, { $push: { members: { userId: memberId, @@ -1532,32 +1520,28 @@ Boards.mutations({ isReadAssignedOnly: false, }, }, - }; + }); }, - removeMember(memberId) { + async removeMember(memberId) { const memberIndex = this.memberIndex(memberId); - - // we do not allow the only one admin to be removed const allowRemove = !this.members[memberIndex].isAdmin || this.activeAdmins().length > 1; if (!allowRemove) { - return { - $set: { - [`members.${memberIndex}.isActive`]: true, - }, - }; + return await Boards.updateAsync(this._id, { + $set: { [`members.${memberIndex}.isActive`]: true }, + }); } - return { + return await Boards.updateAsync(this._id, { $set: { [`members.${memberIndex}.isActive`]: false, [`members.${memberIndex}.isAdmin`]: false, }, - }; + }); }, - setMemberPermission( + async setMemberPermission( memberId, isAdmin, isNoComments, @@ -1570,12 +1554,11 @@ Boards.mutations({ currentUserId = Meteor.userId(), ) { const memberIndex = this.memberIndex(memberId); - // do not allow change permission of self if (memberId === currentUserId) { isAdmin = this.members[memberIndex].isAdmin; } - return { + return await Boards.updateAsync(this._id, { $set: { [`members.${memberIndex}.isAdmin`]: isAdmin, [`members.${memberIndex}.isNoComments`]: isNoComments, @@ -1586,144 +1569,143 @@ Boards.mutations({ [`members.${memberIndex}.isReadOnly`]: isReadOnly, [`members.${memberIndex}.isReadAssignedOnly`]: isReadAssignedOnly, }, - }; + }); }, - setAllowsSubtasks(allowsSubtasks) { - return { $set: { allowsSubtasks } }; + async setAllowsSubtasks(allowsSubtasks) { + return await Boards.updateAsync(this._id, { $set: { allowsSubtasks } }); }, - setAllowsCreator(allowsCreator) { - return { $set: { allowsCreator } }; + async setAllowsCreator(allowsCreator) { + return await Boards.updateAsync(this._id, { $set: { allowsCreator } }); }, - setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) { - return { $set: { allowsCreatorOnMinicard } }; + async setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsCreatorOnMinicard } }); }, - setAllowsMembers(allowsMembers) { - return { $set: { allowsMembers } }; + async setAllowsMembers(allowsMembers) { + return await Boards.updateAsync(this._id, { $set: { allowsMembers } }); }, - setAllowsChecklists(allowsChecklists) { - return { $set: { allowsChecklists } }; + async setAllowsChecklists(allowsChecklists) { + return await Boards.updateAsync(this._id, { $set: { allowsChecklists } }); }, - setAllowsAssignee(allowsAssignee) { - return { $set: { allowsAssignee } }; + async setAllowsAssignee(allowsAssignee) { + return await Boards.updateAsync(this._id, { $set: { allowsAssignee } }); }, - setAllowsAssignedBy(allowsAssignedBy) { - return { $set: { allowsAssignedBy } }; + async setAllowsAssignedBy(allowsAssignedBy) { + return await Boards.updateAsync(this._id, { $set: { allowsAssignedBy } }); }, - setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) { - return { $set: { allowsShowListsOnMinicard } }; + async setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsShowListsOnMinicard } }); }, - setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) { - return { $set: { allowsChecklistAtMinicard } }; + async setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsChecklistAtMinicard } }); }, - setAllowsRequestedBy(allowsRequestedBy) { - return { $set: { allowsRequestedBy } }; + async setAllowsRequestedBy(allowsRequestedBy) { + return await Boards.updateAsync(this._id, { $set: { allowsRequestedBy } }); }, - setAllowsCardSortingByNumber(allowsCardSortingByNumber) { - return { $set: { allowsCardSortingByNumber } }; + async setAllowsCardSortingByNumber(allowsCardSortingByNumber) { + return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumber } }); }, - setAllowsShowLists(allowsShowLists) { - return { $set: { allowsShowLists } }; + async setAllowsShowLists(allowsShowLists) { + return await Boards.updateAsync(this._id, { $set: { allowsShowLists } }); }, - - setAllowsAttachments(allowsAttachments) { - return { $set: { allowsAttachments } }; + async setAllowsAttachments(allowsAttachments) { + return await Boards.updateAsync(this._id, { $set: { allowsAttachments } }); }, - setAllowsLabels(allowsLabels) { - return { $set: { allowsLabels } }; + async setAllowsLabels(allowsLabels) { + return await Boards.updateAsync(this._id, { $set: { allowsLabels } }); }, - setAllowsComments(allowsComments) { - return { $set: { allowsComments } }; + async setAllowsComments(allowsComments) { + return await Boards.updateAsync(this._id, { $set: { allowsComments } }); }, - setAllowsDescriptionTitle(allowsDescriptionTitle) { - return { $set: { allowsDescriptionTitle } }; + async setAllowsDescriptionTitle(allowsDescriptionTitle) { + return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTitle } }); }, - setAllowsCardNumber(allowsCardNumber) { - return { $set: { allowsCardNumber } }; + async setAllowsCardNumber(allowsCardNumber) { + return await Boards.updateAsync(this._id, { $set: { allowsCardNumber } }); }, - setAllowsDescriptionText(allowsDescriptionText) { - return { $set: { allowsDescriptionText } }; + async setAllowsDescriptionText(allowsDescriptionText) { + return await Boards.updateAsync(this._id, { $set: { allowsDescriptionText } }); }, - setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) { - return { $set: { allowsDescriptionTextOnMinicard } }; + async setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTextOnMinicard } }); }, - setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) { - return { $set: { allowsCoverAttachmentOnMinicard } }; + async setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsCoverAttachmentOnMinicard } }); }, - setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) { - return { $set: { allowsBadgeAttachmentOnMinicard } }; + async setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsBadgeAttachmentOnMinicard } }); }, - setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) { - return { $set: { allowsCardSortingByNumberOnMinicard } }; + async setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) { + return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumberOnMinicard } }); }, - setAllowsActivities(allowsActivities) { - return { $set: { allowsActivities } }; + async setAllowsActivities(allowsActivities) { + return await Boards.updateAsync(this._id, { $set: { allowsActivities } }); }, - setAllowsReceivedDate(allowsReceivedDate) { - return { $set: { allowsReceivedDate } }; + async setAllowsReceivedDate(allowsReceivedDate) { + return await Boards.updateAsync(this._id, { $set: { allowsReceivedDate } }); }, - setAllowsCardCounterList(allowsCardCounterList) { - return { $set: { allowsCardCounterList } }; + async setAllowsCardCounterList(allowsCardCounterList) { + return await Boards.updateAsync(this._id, { $set: { allowsCardCounterList } }); }, - setAllowsBoardMemberList(allowsBoardMemberList) { - return { $set: { allowsBoardMemberList } }; + async setAllowsBoardMemberList(allowsBoardMemberList) { + return await Boards.updateAsync(this._id, { $set: { allowsBoardMemberList } }); }, - setAllowsStartDate(allowsStartDate) { - return { $set: { allowsStartDate } }; + async setAllowsStartDate(allowsStartDate) { + return await Boards.updateAsync(this._id, { $set: { allowsStartDate } }); }, - setAllowsEndDate(allowsEndDate) { - return { $set: { allowsEndDate } }; + async setAllowsEndDate(allowsEndDate) { + return await Boards.updateAsync(this._id, { $set: { allowsEndDate } }); }, - setAllowsDueDate(allowsDueDate) { - return { $set: { allowsDueDate } }; + async setAllowsDueDate(allowsDueDate) { + return await Boards.updateAsync(this._id, { $set: { allowsDueDate } }); }, - setSubtasksDefaultBoardId(subtasksDefaultBoardId) { - return { $set: { subtasksDefaultBoardId } }; + async setSubtasksDefaultBoardId(subtasksDefaultBoardId) { + return await Boards.updateAsync(this._id, { $set: { subtasksDefaultBoardId } }); }, - setSubtasksDefaultListId(subtasksDefaultListId) { - return { $set: { subtasksDefaultListId } }; + async setSubtasksDefaultListId(subtasksDefaultListId) { + return await Boards.updateAsync(this._id, { $set: { subtasksDefaultListId } }); }, - setPresentParentTask(presentParentTask) { - return { $set: { presentParentTask } }; + async setPresentParentTask(presentParentTask) { + return await Boards.updateAsync(this._id, { $set: { presentParentTask } }); }, - move(sortIndex) { - return { $set: { sort: sortIndex } }; + async move(sortIndex) { + return await Boards.updateAsync(this._id, { $set: { sort: sortIndex } }); }, - toggleShowActivities() { - return { $set: { showActivities: !this.showActivities } }; + async toggleShowActivities() { + return await Boards.updateAsync(this._id, { $set: { showActivities: !this.showActivities } }); }, }); @@ -1909,14 +1891,14 @@ if (Meteor.isServer) { check(boardId, String); return ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 }); }, - quitBoard(boardId) { + async quitBoard(boardId) { check(boardId, String); const board = ReactiveCache.getBoard(boardId); if (board) { const userId = Meteor.userId(); const index = board.memberIndex(userId); if (index >= 0) { - board.removeMember(userId); + await board.removeMember(userId); return true; } else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-doesNotExist'); @@ -1993,14 +1975,14 @@ if (Meteor.isServer) { }); Meteor.methods({ - archiveBoard(boardId) { + async archiveBoard(boardId) { check(boardId, String); const board = ReactiveCache.getBoard(boardId); if (board) { const userId = Meteor.userId(); const index = board.memberIndex(userId); if (index >= 0) { - board.archive(); + await board.archive(); return true; } else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-doesNotExist'); @@ -2159,7 +2141,7 @@ if (Meteor.isServer) { } if (modifier.$set) { const boardId = doc._id; - foreachRemovedMember(doc, modifier.$set, memberId => { + foreachRemovedMember(doc, modifier.$set, async memberId => { Cards.update( { boardId }, { @@ -2182,7 +2164,7 @@ if (Meteor.isServer) { ); const board = Boards._transform(doc); - board.setWatcher(memberId, false); + await board.setWatcher(memberId, false); // Remove board from users starred list if (!board.isPublic()) { @@ -2589,7 +2571,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) { * @param {boolean} isReadOnly ReadOnly capability * @param {boolean} isReadAssignedOnly ReadAssignedOnly capability */ - JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function( + JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', async function( req, res, ) { @@ -2606,7 +2588,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) { return data; } } - const query = board.setMemberPermission( + const query = await board.setMemberPermission( memberId, isTrue(isAdmin), isTrue(isNoComments), diff --git a/models/cards.js b/models/cards.js index 66a3e3fac..f950f58cf 100644 --- a/models/cards.js +++ b/models/cards.js @@ -572,15 +572,17 @@ Cards.helpers({ }); }, - mapCustomFieldsToBoard(boardId) { + async mapCustomFieldsToBoard(boardId) { // Map custom fields to new board - return this.customFields.map(cf => { + const result = []; + for (const cf of this.customFields) { const oldCf = ReactiveCache.getCustomField(cf._id); // Check if oldCf is undefined or null if (!oldCf) { //console.error(`Custom field with ID ${cf._id} not found.`); - return cf; // Skip this field if oldCf is not found + result.push(cf); // Skip this field if oldCf is not found + continue; } const newCf = ReactiveCache.getCustomField({ @@ -592,11 +594,12 @@ Cards.helpers({ if (newCf) { cf._id = newCf._id; } else if (!_.contains(oldCf.boardIds, boardId)) { - oldCf.addBoard(boardId); + await oldCf.addBoard(boardId); } - return cf; - }); + result.push(cf); + } + return result; }, @@ -1203,11 +1206,11 @@ Cards.helpers({ } }, - assignMember(memberId) { + async assignMember(memberId) { let ret; if (this.isLinkedBoard()) { const board = ReactiveCache.getBoard(this.linkedId); - ret = board.addMember(memberId); + ret = await board.addMember(memberId); } else { ret = Cards.update( { _id: this.getRealId() }, @@ -1234,7 +1237,7 @@ Cards.helpers({ } }, - unassignMember(memberId) { + async unassignMember(memberId) { if (this.isLinkedCard()) { return Cards.update( { _id: this.linkedId }, @@ -1242,7 +1245,7 @@ Cards.helpers({ ); } else if (this.isLinkedBoard()) { const board = ReactiveCache.getBoard(this.linkedId); - return board.removeMember(memberId); + return await board.removeMember(memberId); } else { return Cards.update({ _id: this._id }, { $pull: { members: memberId } }); } @@ -1991,52 +1994,41 @@ Cards.helpers({ } return pokerWinnersListMap[0].pokerCard; }, -}); -Cards.mutations({ - applyToChildren(funct) { - ReactiveCache.getCards({ - parentId: this._id, - }).forEach(card => { - funct(card); + async applyToChildren(funct) { + const cards = ReactiveCache.getCards({ parentId: this._id }); + for (const card of cards) { + await funct(card); + } + }, + + async archive() { + await this.applyToChildren(async card => { + await card.archive(); + }); + return await Cards.updateAsync(this._id, { + $set: { archived: true, archivedAt: new Date() }, }); }, - archive() { - this.applyToChildren(card => { - return card.archive(); + async restore() { + await this.applyToChildren(async card => { + await card.restore(); + }); + return await Cards.updateAsync(this._id, { + $set: { archived: false }, }); - return { - $set: { - archived: true, - archivedAt: new Date(), - }, - }; }, - restore() { - this.applyToChildren(card => { - return card.restore(); - }); - return { - $set: { - archived: false, - }, - }; - }, - - moveToEndOfList({ listId, swimlaneId } = {}) { + async moveToEndOfList({ listId, swimlaneId } = {}) { swimlaneId = swimlaneId || this.swimlaneId; const boardId = this.boardId; let sortIndex = 0; - // This should never happen, but there was a bug that was fixed in commit - // ea0239538a68e225c867411a4f3e0d27c158383. if (!swimlaneId) { const board = ReactiveCache.getBoard(boardId); swimlaneId = board.getDefaultSwimline()._id; } - // Move the minicard to the end of the target list let parentElementDom = $(`#swimlane-${swimlaneId}`).get(0); if (!parentElementDom) parentElementDom = $(':root'); @@ -2045,7 +2037,7 @@ Cards.mutations({ .get(0); if (lastCardDom) sortIndex = Utils.calculateIndex(lastCardDom, null).base; - return this.moveOptionalArgs({ + return await this.moveOptionalArgs({ boardId, swimlaneId, listId, @@ -2053,22 +2045,19 @@ Cards.mutations({ }); }, - moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) { + async moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) { boardId = boardId || this.boardId; swimlaneId = swimlaneId || this.swimlaneId; - // This should never happen, but there was a bug that was fixed in commit - // ea0239538a68e225c867411a4f3e0d27c158383. if (!swimlaneId) { const board = ReactiveCache.getBoard(boardId); swimlaneId = board.getDefaultSwimline()._id; } listId = listId || this.listId; if (sort === undefined || sort === null) sort = this.sort; - return this.move(boardId, swimlaneId, listId, sort); + return await this.move(boardId, swimlaneId, listId, sort); }, - move(boardId, swimlaneId, listId, sort = null) { - // Capture previous state for history tracking + async move(boardId, swimlaneId, listId, sort = null) { const previousState = { boardId: this.boardId, swimlaneId: this.swimlaneId, @@ -2076,20 +2065,13 @@ Cards.mutations({ sort: this.sort, }; - const mutatedFields = { - boardId, - swimlaneId, - listId, - }; + const mutatedFields = { boardId, swimlaneId, listId }; if (sort !== null) { mutatedFields.sort = sort; } - // we must only copy the labels and custom fields if the target board - // differs from the source board if (this.boardId !== boardId) { - // Get label names const oldBoard = ReactiveCache.getBoard(this.boardId); const oldBoardLabels = oldBoard.labels; const oldCardLabels = _.pluck( @@ -2108,7 +2090,6 @@ Cards.mutations({ '_id', ); - // assign the new card number from the target board const newCardNumber = newBoard.getNextCardNumber(); Object.assign(mutatedFields, { @@ -2119,11 +2100,8 @@ Cards.mutations({ mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id); } - Cards.update(this._id, { - $set: mutatedFields, - }); + await Cards.updateAsync(this._id, { $set: mutatedFields }); - // Track position change in user history (server-side only) if (Meteor.isServer && Meteor.userId() && typeof UserPositionHistory !== 'undefined') { try { UserPositionHistory.trackChange({ @@ -2145,7 +2123,6 @@ Cards.mutations({ } } - // Ensure attachments follow the card to its new board/list/swimlane if (Meteor.isServer) { const updateMeta = {}; if (mutatedFields.boardId !== undefined) updateMeta['meta.boardId'] = mutatedFields.boardId; @@ -2154,293 +2131,159 @@ Cards.mutations({ if (Object.keys(updateMeta).length > 0) { try { - Attachments.collection.update( + await Attachments.collection.updateAsync( { 'meta.cardId': this._id }, { $set: updateMeta }, { multi: true }, ); } catch (err) { - // Do not block the move if attachment update fails - // eslint-disable-next-line no-console console.error('Failed to update attachments metadata after moving card', this._id, err); } } } }, - addLabel(labelId) { + async addLabel(labelId) { this.labelIds.push(labelId); - return { - $addToSet: { - labelIds: labelId, - }, - }; + return await Cards.updateAsync(this._id, { $addToSet: { labelIds: labelId } }); }, - removeLabel(labelId) { + async removeLabel(labelId) { this.labelIds = _.without(this.labelIds, labelId); - return { - $pull: { - labelIds: labelId, - }, - }; + return await Cards.updateAsync(this._id, { $pull: { labelIds: labelId } }); }, - toggleLabel(labelId) { + async toggleLabel(labelId) { if (this.labelIds && this.labelIds.indexOf(labelId) > -1) { - return this.removeLabel(labelId); + return await this.removeLabel(labelId); } else { - return this.addLabel(labelId); + return await this.addLabel(labelId); } }, - setColor(newColor) { + async setColor(newColor) { if (newColor === 'white') { newColor = null; } - return { - $set: { - color: newColor, - }, - }; + return await Cards.updateAsync(this._id, { $set: { color: newColor } }); }, - assignMember(memberId) { - return { - $addToSet: { - members: memberId, - }, - }; + async assignMember(memberId) { + return await Cards.updateAsync(this._id, { $addToSet: { members: memberId } }); }, - assignAssignee(assigneeId) { - // If there is not any assignee, allow one assignee, not more. - /* - if (this.getAssignees().length === 0) { - return { - $addToSet: { - assignees: assigneeId, - }, - }; - */ - // Allow more that one assignee: - // https://github.com/wekan/wekan/issues/3302 - return { - $addToSet: { - assignees: assigneeId, - }, - }; - //} else { - // return false, - //} + async assignAssignee(assigneeId) { + return await Cards.updateAsync(this._id, { $addToSet: { assignees: assigneeId } }); }, - unassignMember(memberId) { - return { - $pull: { - members: memberId, - }, - }; + async unassignMember(memberId) { + return await Cards.updateAsync(this._id, { $pull: { members: memberId } }); }, - unassignAssignee(assigneeId) { - return { - $pull: { - assignees: assigneeId, - }, - }; + async unassignAssignee(assigneeId) { + return await Cards.updateAsync(this._id, { $pull: { assignees: assigneeId } }); }, - toggleMember(memberId) { + async toggleMember(memberId) { if (this.members && this.members.indexOf(memberId) > -1) { - return this.unassignMember(memberId); + return await this.unassignMember(memberId); } else { - return this.assignMember(memberId); + return await this.assignMember(memberId); } }, - toggleAssignee(assigneeId) { + async toggleAssignee(assigneeId) { if (this.assignees && this.assignees.indexOf(assigneeId) > -1) { - return this.unassignAssignee(assigneeId); + return await this.unassignAssignee(assigneeId); } else { - return this.assignAssignee(assigneeId); + return await this.assignAssignee(assigneeId); } }, - assignCustomField(customFieldId) { - return { - $addToSet: { - customFields: { - _id: customFieldId, - value: null, - }, - }, - }; + async assignCustomField(customFieldId) { + return await Cards.updateAsync(this._id, { + $addToSet: { customFields: { _id: customFieldId, value: null } }, + }); }, - unassignCustomField(customFieldId) { - return { - $pull: { - customFields: { - _id: customFieldId, - }, - }, - }; + async unassignCustomField(customFieldId) { + return await Cards.updateAsync(this._id, { + $pull: { customFields: { _id: customFieldId } }, + }); }, - toggleCustomField(customFieldId) { + async toggleCustomField(customFieldId) { if (this.customFields && this.customFieldIndex(customFieldId) > -1) { - return this.unassignCustomField(customFieldId); + return await this.unassignCustomField(customFieldId); } else { - return this.assignCustomField(customFieldId); + return await this.assignCustomField(customFieldId); } }, - toggleShowActivities() { - return { - $set: { - showActivities: !this.showActivities, - } - }; + async toggleShowActivities() { + return await Cards.updateAsync(this._id, { + $set: { showActivities: !this.showActivities }, + }); }, - toggleShowChecklistAtMinicard() { - return { - $set: { - showChecklistAtMinicard: !this.showChecklistAtMinicard, - } - }; + async toggleShowChecklistAtMinicard() { + return await Cards.updateAsync(this._id, { + $set: { showChecklistAtMinicard: !this.showChecklistAtMinicard }, + }); }, - setCustomField(customFieldId, value) { - // todo + async setCustomField(customFieldId, value) { const index = this.customFieldIndex(customFieldId); if (index > -1) { - const update = { - $set: {}, - }; + const update = { $set: {} }; update.$set[`customFields.${index}.value`] = value; - return update; + return await Cards.updateAsync(this._id, update); } - // TODO - // Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct return null; }, - setCover(coverId) { - return { - $set: { - coverId, - }, - }; + async setCover(coverId) { + return await Cards.updateAsync(this._id, { $set: { coverId } }); }, - unsetCover() { - return { - $unset: { - coverId: '', - }, - }; + async unsetCover() { + return await Cards.updateAsync(this._id, { $unset: { coverId: '' } }); }, - //setReceived(receivedAt) { - // return { - // $set: { - // receivedAt, - // }, - // }; - //}, - - unsetReceived() { - return { - $unset: { - receivedAt: '', - }, - }; + async unsetReceived() { + return await Cards.updateAsync(this._id, { $unset: { receivedAt: '' } }); }, - //setStart(startAt) { - // return { - // $set: { - // startAt, - // }, - // }; - //}, - - unsetStart() { - return { - $unset: { - startAt: '', - }, - }; + async unsetStart() { + return await Cards.updateAsync(this._id, { $unset: { startAt: '' } }); }, - //setDue(dueAt) { - // return { - // $set: { - // dueAt, - // }, - // }; - //}, - - unsetDue() { - return { - $unset: { - dueAt: '', - }, - }; + async unsetDue() { + return await Cards.updateAsync(this._id, { $unset: { dueAt: '' } }); }, - //setEnd(endAt) { - // return { - // $set: { - // endAt, - // }, - // }; - //}, - - unsetEnd() { - return { - $unset: { - endAt: '', - }, - }; + async unsetEnd() { + return await Cards.updateAsync(this._id, { $unset: { endAt: '' } }); }, - setOvertime(isOvertime) { - return { - $set: { - isOvertime, - }, - }; + async setOvertime(isOvertime) { + return await Cards.updateAsync(this._id, { $set: { isOvertime } }); }, - setSpentTime(spentTime) { - return { - $set: { - spentTime, - }, - }; + async setSpentTime(spentTime) { + return await Cards.updateAsync(this._id, { $set: { spentTime } }); }, - unsetSpentTime() { - return { - $unset: { - spentTime: '', - isOvertime: false, - }, - }; + async unsetSpentTime() { + return await Cards.updateAsync(this._id, { $unset: { spentTime: '', isOvertime: false } }); }, - setParentId(parentId) { - return { - $set: { - parentId, - }, - }; + async setParentId(parentId) { + return await Cards.updateAsync(this._id, { $set: { parentId } }); }, - setVoteQuestion(question, publicVote, allowNonBoardMembers) { - return { + + async setVoteQuestion(question, publicVote, allowNonBoardMembers) { + return await Cards.updateAsync(this._id, { $set: { vote: { question, @@ -2450,61 +2293,42 @@ Cards.mutations({ negative: [], }, }, - }; + }); }, - unsetVote() { - return { - $unset: { - vote: '', - }, - }; + + async unsetVote() { + return await Cards.updateAsync(this._id, { $unset: { vote: '' } }); }, - setVoteEnd(end) { - return { - $set: { 'vote.end': end }, - }; + + async setVoteEnd(end) { + return await Cards.updateAsync(this._id, { $set: { 'vote.end': end } }); }, - unsetVoteEnd() { - return { - $unset: { 'vote.end': '' }, - }; + + async unsetVoteEnd() { + return await Cards.updateAsync(this._id, { $unset: { 'vote.end': '' } }); }, - setVote(userId, forIt) { + + async setVote(userId, forIt) { switch (forIt) { case true: - // vote for it - return { - $pull: { - 'vote.negative': userId, - }, - $addToSet: { - 'vote.positive': userId, - }, - }; + return await Cards.updateAsync(this._id, { + $pull: { 'vote.negative': userId }, + $addToSet: { 'vote.positive': userId }, + }); case false: - // vote against - return { - $pull: { - 'vote.positive': userId, - }, - $addToSet: { - 'vote.negative': userId, - }, - }; - + return await Cards.updateAsync(this._id, { + $pull: { 'vote.positive': userId }, + $addToSet: { 'vote.negative': userId }, + }); default: - // Remove votes - return { - $pull: { - 'vote.positive': userId, - 'vote.negative': userId, - }, - }; + return await Cards.updateAsync(this._id, { + $pull: { 'vote.positive': userId, 'vote.negative': userId }, + }); } }, - setPokerQuestion(question, allowNonBoardMembers) { - return { + async setPokerQuestion(question, allowNonBoardMembers) { + return await Cards.updateAsync(this._id, { $set: { poker: { question, @@ -2521,246 +2345,47 @@ Cards.mutations({ unsure: [], }, }, - }; + }); }, - setPokerEstimation(estimation) { - return { - $set: { 'poker.estimation': estimation }, - }; + + async setPokerEstimation(estimation) { + return await Cards.updateAsync(this._id, { $set: { 'poker.estimation': estimation } }); }, - unsetPokerEstimation() { - return { - $unset: { 'poker.estimation': '' }, - }; + + async unsetPokerEstimation() { + return await Cards.updateAsync(this._id, { $unset: { 'poker.estimation': '' } }); }, - unsetPoker() { - return { - $unset: { - poker: '', - }, - }; + + async unsetPoker() { + return await Cards.updateAsync(this._id, { $unset: { poker: '' } }); }, - setPokerEnd(end) { - return { - $set: { 'poker.end': end }, - }; + + async setPokerEnd(end) { + return await Cards.updateAsync(this._id, { $set: { 'poker.end': end } }); }, - unsetPokerEnd() { - return { - $unset: { 'poker.end': '' }, - }; + + async unsetPokerEnd() { + return await Cards.updateAsync(this._id, { $unset: { 'poker.end': '' } }); }, - setPoker(userId, state) { - switch (state) { - case 'one': - // poker one - return { - $pull: { - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.one': userId, - }, - }; - case 'two': - // poker two - return { - $pull: { - 'poker.one': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.two': userId, - }, - }; - case 'three': - // poker three - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.three': userId, - }, - }; + async setPoker(userId, state) { + const pokerFields = ['one', 'two', 'three', 'five', 'eight', 'thirteen', 'twenty', 'forty', 'oneHundred', 'unsure']; + const pullFields = {}; + pokerFields.forEach(f => { pullFields[`poker.${f}`] = userId; }); - case 'five': - // poker five - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.five': userId, - }, - }; - - case 'eight': - // poker eight - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.eight': userId, - }, - }; - - case 'thirteen': - // poker thirteen - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.thirteen': userId, - }, - }; - - case 'twenty': - // poker twenty - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.twenty': userId, - }, - }; - - case 'forty': - // poker forty - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.forty': userId, - }, - }; - - case 'oneHundred': - // poker one hundred - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.unsure': userId, - }, - $addToSet: { - 'poker.oneHundred': userId, - }, - }; - - case 'unsure': - // poker unsure - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - }, - $addToSet: { - 'poker.unsure': userId, - }, - }; - - default: - // Remove pokers - return { - $pull: { - 'poker.one': userId, - 'poker.two': userId, - 'poker.three': userId, - 'poker.five': userId, - 'poker.eight': userId, - 'poker.thirteen': userId, - 'poker.twenty': userId, - 'poker.forty': userId, - 'poker.oneHundred': userId, - 'poker.unsure': userId, - }, - }; + if (pokerFields.includes(state)) { + delete pullFields[`poker.${state}`]; + return await Cards.updateAsync(this._id, { + $pull: pullFields, + $addToSet: { [`poker.${state}`]: userId }, + }); + } else { + return await Cards.updateAsync(this._id, { $pull: pullFields }); } }, - replayPoker() { - return { + + async replayPoker() { + return await Cards.updateAsync(this._id, { $set: { 'poker.one': [], 'poker.two': [], @@ -2773,7 +2398,7 @@ Cards.mutations({ 'poker.oneHundred': [], 'poker.unsure': [], }, - }; + }); }, }); @@ -4544,7 +4169,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( JsonRoutes.add( 'POST', '/api/boards/:boardId/lists/:listId/cards/:cardId/archive', - function(req, res) { + async function(req, res) { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; const paramListId = req.params.listId; @@ -4558,7 +4183,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( if (!card) { throw new Meteor.Error(404, 'Card not found'); } - card.archive(); + await card.archive(); JsonRoutes.sendResult(res, { code: 200, data: { @@ -4583,7 +4208,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( JsonRoutes.add( 'POST', '/api/boards/:boardId/lists/:listId/cards/:cardId/unarchive', - function(req, res) { + async function(req, res) { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; const paramListId = req.params.listId; @@ -4597,7 +4222,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( if (!card) { throw new Meteor.Error(404, 'Card not found'); } - card.restore(); + await card.restore(); JsonRoutes.sendResult(res, { code: 200, data: { diff --git a/models/checklistItems.js b/models/checklistItems.js index db2aa55bd..0d4c23761 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -90,29 +90,24 @@ ChecklistItems.before.insert((userId, doc) => { } }); -// Mutations -ChecklistItems.mutations({ - setTitle(title) { - return { $set: { title } }; +ChecklistItems.helpers({ + async setTitle(title) { + return await ChecklistItems.updateAsync(this._id, { $set: { title } }); }, - check() { - return { $set: { isFinished: true } }; + async check() { + return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: true } }); }, - uncheck() { - return { $set: { isFinished: false } }; + async uncheck() { + return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: false } }); }, - toggleItem() { - return { $set: { isFinished: !this.isFinished } }; + async toggleItem() { + return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: !this.isFinished } }); }, - move(checklistId, sortIndex) { + async move(checklistId, sortIndex) { const cardId = ReactiveCache.getChecklist(checklistId).cardId; - const mutatedFields = { - cardId, - checklistId, - sort: sortIndex, - }; - - return { $set: mutatedFields }; + return await ChecklistItems.updateAsync(this._id, { + $set: { cardId, checklistId, sort: sortIndex }, + }); }, }); diff --git a/models/checklists.js b/models/checklists.js index 606e58f3f..e96ae6bcf 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -150,22 +150,49 @@ Checklists.helpers({ } return ret; }, - checkAllItems() { + async checkAllItems() { const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id }); - checkItems.forEach(function(item) { - item.check(); - }); + for (const item of checkItems) { + await item.check(); + } }, - uncheckAllItems() { + async uncheckAllItems() { const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id }); - checkItems.forEach(function(item) { - item.uncheck(); - }); + for (const item of checkItems) { + await item.uncheck(); + } }, itemIndex(itemId) { const items = ReactiveCache.getChecklist({ _id: this._id }).items; return _.pluck(items, '_id').indexOf(itemId); }, + + async setTitle(title) { + return await Checklists.updateAsync(this._id, { $set: { title } }); + }, + /** move the checklist to another card + * @param newCardId move the checklist to this cardId + */ + async move(newCardId) { + // Note: Activities and ChecklistItems updates are now handled server-side + // in the moveChecklist Meteor method to avoid client-side permission issues + return await Checklists.updateAsync(this._id, { $set: { cardId: newCardId } }); + }, + async toggleHideCheckedChecklistItems() { + return await Checklists.updateAsync(this._id, { + $set: { hideCheckedChecklistItems: !this.hideCheckedChecklistItems }, + }); + }, + async toggleHideAllChecklistItems() { + return await Checklists.updateAsync(this._id, { + $set: { hideAllChecklistItems: !this.hideAllChecklistItems }, + }); + }, + async toggleShowChecklistAtMinicard() { + return await Checklists.updateAsync(this._id, { + $set: { showChecklistAtMinicard: !this.showChecklistAtMinicard }, + }); + }, }); Checklists.allow({ @@ -191,46 +218,6 @@ Checklists.before.insert((userId, doc) => { } }); -Checklists.mutations({ - setTitle(title) { - return { $set: { title } }; - }, - /** move the checklist to another card - * @param newCardId move the checklist to this cardId - */ - move(newCardId) { - // Note: Activities and ChecklistItems updates are now handled server-side - // in the moveChecklist Meteor method to avoid client-side permission issues - - // update the checklist itself - return { - $set: { - cardId: newCardId, - }, - }; - }, - toggleHideCheckedChecklistItems() { - return { - $set: { - hideCheckedChecklistItems: !this.hideCheckedChecklistItems, - } - }; - }, - toggleHideAllChecklistItems() { - return { - $set: { - hideAllChecklistItems: !this.hideAllChecklistItems, - } - }; - }, - toggleShowChecklistAtMinicard() { - return { - $set: { - showChecklistAtMinicard: !this.showChecklistAtMinicard, - } - }; - }, -}); if (Meteor.isServer) { Meteor.methods({ diff --git a/models/csvCreator.js b/models/csvCreator.js index e7d83331b..fd3ee399a 100644 --- a/models/csvCreator.js +++ b/models/csvCreator.js @@ -382,14 +382,14 @@ export class CsvCreator { } } - create(board, currentBoardId) { + async create(board, currentBoardId) { const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { const currentBoard = ReactiveCache.getBoard(currentBoardId); - currentBoard.archive(); + await currentBoard.archive(); } this.mapHeadertoCardFieldIndex(board[0]); const boardId = this.createBoard(board); diff --git a/models/customFields.js b/models/customFields.js index 55d4cef98..db8aaa8c2 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -152,17 +152,14 @@ CustomFields.addToAllCards = cf => { ); }; -CustomFields.mutations({ - addBoard(boardId) { +CustomFields.helpers({ + async addBoard(boardId) { if (boardId) { - return { - $push: { - boardIds: boardId, - }, - }; - } else { - return null; + return await CustomFields.updateAsync(this._id, { + $push: { boardIds: boardId }, + }); } + return null; }, }); diff --git a/models/lists.js b/models/lists.js index 4eb4574f1..a4489f6d3 100644 --- a/models/lists.js +++ b/models/lists.js @@ -226,7 +226,7 @@ Lists.helpers({ }); }, - move(boardId, swimlaneId) { + async move(boardId, swimlaneId) { const boardList = ReactiveCache.getList({ boardId, title: this.title, @@ -235,9 +235,9 @@ Lists.helpers({ let listId; if (boardList) { listId = boardList._id; - this.cards().forEach(card => { - card.move(boardId, this._id, boardList._id); - }); + for (const card of this.cards()) { + await card.move(boardId, this._id, boardList._id); + } } else { console.log('list.title:', this.title); console.log('boardList:', boardList); @@ -251,9 +251,9 @@ Lists.helpers({ }); } - this.cards(swimlaneId).forEach(card => { - card.move(boardId, swimlaneId, listId); - }); + for (const card of this.cards(swimlaneId)) { + await card.move(boardId, swimlaneId, listId); + } }, cards(swimlaneId) { @@ -339,64 +339,58 @@ Lists.helpers({ const card = ReactiveCache.getCard({ listId: this._id }); return card && card.originRelativeUrl(); }, - remove() { - Lists.remove({ _id: this._id }); + async remove() { + return await Lists.removeAsync({ _id: this._id }); }, -}); -Lists.mutations({ - rename(title) { + async rename(title) { // Basic client-side validation - server will handle full sanitization if (typeof title === 'string') { // Basic length check to prevent abuse const sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title; - return { $set: { title: sanitizedTitle } }; + return await Lists.updateAsync(this._id, { $set: { title: sanitizedTitle } }); } - return { $set: { title } }; + return await Lists.updateAsync(this._id, { $set: { title } }); }, - star(enable = true) { - return { $set: { starred: !!enable } }; + async star(enable = true) { + return await Lists.updateAsync(this._id, { $set: { starred: !!enable } }); }, - collapse(enable = true) { - return { $set: { collapsed: !!enable } }; + async collapse(enable = true) { + return await Lists.updateAsync(this._id, { $set: { collapsed: !!enable } }); }, - archive() { + async archive() { if (this.isTemplateList()) { - this.cards().forEach(card => { - return card.archive(); - }); + for (const card of this.cards()) { + await card.archive(); + } } - return { $set: { archived: true, archivedAt: new Date() } }; + return await Lists.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); }, - restore() { + async restore() { if (this.isTemplateList()) { - this.allCards().forEach(card => { - return card.restore(); - }); + for (const card of this.allCards()) { + await card.restore(); + } } - return { $set: { archived: false } }; + return await Lists.updateAsync(this._id, { $set: { archived: false } }); }, - toggleSoftLimit(toggle) { - return { $set: { 'wipLimit.soft': toggle } }; + async toggleSoftLimit(toggle) { + return await Lists.updateAsync(this._id, { $set: { 'wipLimit.soft': toggle } }); }, - toggleWipLimit(toggle) { - return { $set: { 'wipLimit.enabled': toggle } }; + async toggleWipLimit(toggle) { + return await Lists.updateAsync(this._id, { $set: { 'wipLimit.enabled': toggle } }); }, - setWipLimit(limit) { - return { $set: { 'wipLimit.value': limit } }; + async setWipLimit(limit) { + return await Lists.updateAsync(this._id, { $set: { 'wipLimit.value': limit } }); }, - setColor(newColor) { - return { - $set: { - color: newColor, - }, - }; + async setColor(newColor) { + return await Lists.updateAsync(this._id, { $set: { color: newColor } }); }, }); @@ -422,49 +416,49 @@ Lists.archivedListIds = () => { }; Meteor.methods({ - applyWipLimit(listId, limit) { + async applyWipLimit(listId, limit) { check(listId, String); check(limit, Number); - + if (!this.userId) { throw new Meteor.Error('not-authorized', 'You must be logged in.'); } - + const list = ReactiveCache.getList(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - + const board = ReactiveCache.getBoard(list.boardId); if (!board || !board.hasAdmin(this.userId)) { throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.'); } - + if (limit === 0) { limit = 1; } - list.setWipLimit(limit); + await list.setWipLimit(limit); }, - enableWipLimit(listId) { + async enableWipLimit(listId) { check(listId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized', 'You must be logged in.'); } - + const list = ReactiveCache.getList(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - + const board = ReactiveCache.getBoard(list.boardId); if (!board || !board.hasAdmin(this.userId)) { throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.'); } - + if (list.getWipLimit('value') === 0) { - list.setWipLimit(1); + await list.setWipLimit(1); } list.toggleWipLimit(!list.getWipLimit('enabled')); }, diff --git a/models/rules.js b/models/rules.js index 38b3c87a5..55417ca49 100644 --- a/models/rules.js +++ b/models/rules.js @@ -50,13 +50,10 @@ Rules.attachSchema( }), ); -Rules.mutations({ - rename(description) { - return { $set: { description } }; - }, -}); - Rules.helpers({ + async rename(description) { + return await Rules.updateAsync(this._id, { $set: { description } }); + }, getAction() { return ReactiveCache.getAction(this.actionId); }, diff --git a/models/swimlanes.js b/models/swimlanes.js index 64dbfe529..13f95df09 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -171,8 +171,8 @@ Swimlanes.helpers({ }); }, - move(toBoardId) { - this.lists().forEach(list => { + async move(toBoardId) { + for (const list of this.lists()) { const toList = ReactiveCache.getList({ boardId: toBoardId, title: list.title, @@ -183,25 +183,26 @@ Swimlanes.helpers({ if (toList) { toListId = toList._id; } else { - toListId = Lists.insert({ + toListId = await Lists.insertAsync({ title: list.title, boardId: toBoardId, type: list.type, archived: false, wipLimit: list.wipLimit, - swimlaneId: toSwimlaneId, // Set the target swimlane for the copied list + swimlaneId: this._id, }); } - ReactiveCache.getCards({ + const cards = ReactiveCache.getCards({ listId: list._id, swimlaneId: this._id, - }).forEach(card => { - card.move(toBoardId, this._id, toListId); }); - }); + for (const card of cards) { + await card.move(toBoardId, this._id, toListId); + } + } - Swimlanes.update(this._id, { + await Swimlanes.updateAsync(this._id, { $set: { boardId: toBoardId, }, @@ -314,43 +315,37 @@ Swimlanes.helpers({ return (user.profile || {}).boardTemplatesSwimlaneId === this._id; }, - remove() { - Swimlanes.remove({ _id: this._id }); + async remove() { + return await Swimlanes.removeAsync({ _id: this._id }); }, -}); -Swimlanes.mutations({ - rename(title) { - return { $set: { title } }; + async rename(title) { + return await Swimlanes.updateAsync(this._id, { $set: { title } }); }, // NOTE: collapse() removed - collapsed state is per-user only // Use user.setCollapsedSwimlane(boardId, swimlaneId, collapsed) instead - archive() { + async archive() { if (this.isTemplateSwimlane()) { - this.myLists().forEach(list => { - return list.archive(); - }); + for (const list of this.myLists()) { + await list.archive(); + } } - return { $set: { archived: true, archivedAt: new Date() } }; + return await Swimlanes.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); }, - restore() { + async restore() { if (this.isTemplateSwimlane()) { - this.myLists().forEach(list => { - return list.restore(); - }); + for (const list of this.myLists()) { + await list.restore(); + } } - return { $set: { archived: false } }; + return await Swimlanes.updateAsync(this._id, { $set: { archived: false } }); }, - setColor(newColor) { - return { - $set: { - color: newColor, - }, - }; + async setColor(newColor) { + return await Swimlanes.updateAsync(this._id, { $set: { color: newColor } }); }, }); diff --git a/models/trelloCreator.js b/models/trelloCreator.js index c0400b443..a16a7d491 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -1,26 +1,27 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; import { CustomFields } from './customFields'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; +import getSlug from 'limax'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); @@ -767,7 +768,7 @@ export class TrelloCreator { } } - create(board, currentBoardId) { + async create(board, currentBoardId) { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && @@ -775,7 +776,7 @@ export class TrelloCreator { Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { const currentBoard = ReactiveCache.getBoard(currentBoardId); - currentBoard.archive(); + await currentBoard.archive(); } this.parseActions(board.actions); const boardId = this.createBoardAndLabels(board); diff --git a/models/triggers.js b/models/triggers.js index 6983955c6..d9c9390cc 100644 --- a/models/triggers.js +++ b/models/triggers.js @@ -3,16 +3,6 @@ import { Meteor } from 'meteor/meteor'; Triggers = new Mongo.Collection('triggers'); -Triggers.mutations({ - rename(description) { - return { - $set: { - description, - }, - }; - }, -}); - Triggers.before.insert((userId, doc) => { doc.createdAt = new Date(); doc.updatedAt = doc.createdAt; @@ -36,6 +26,12 @@ Triggers.allow({ }); Triggers.helpers({ + async rename(description) { + return await Triggers.updateAsync(this._id, { + $set: { description }, + }); + }, + description() { return this.desc; }, diff --git a/models/users.js b/models/users.js index f61bdb1c0..5c239408a 100644 --- a/models/users.js +++ b/models/users.js @@ -1593,376 +1593,206 @@ Users.helpers({ } return null; }, -}); -Users.mutations({ - /** set the confirmed board id/swimlane id/list id of a board - * @param boardId the current board id - * @param options an object with the confirmed field values - */ - setMoveAndCopyDialogOption(boardId, options) { + async setMoveAndCopyDialogOption(boardId, options) { let currentOptions = this.getMoveAndCopyDialogOptions(); currentOptions[boardId] = options; - return { - $set: { - 'profile.moveAndCopyDialog': currentOptions, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.moveAndCopyDialog': currentOptions } }); }, - /** set the confirmed board id/swimlane id/list id/card id of a board (move checklist) - * @param boardId the current board id - * @param options an object with the confirmed field values - */ - setMoveChecklistDialogOption(boardId, options) { + + async setMoveChecklistDialogOption(boardId, options) { let currentOptions = this.getMoveChecklistDialogOptions(); currentOptions[boardId] = options; - return { - $set: { - 'profile.moveChecklistDialog': currentOptions, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.moveChecklistDialog': currentOptions } }); }, - /** set the confirmed board id/swimlane id/list id/card id of a board (copy checklist) - * @param boardId the current board id - * @param options an object with the confirmed field values - */ - setCopyChecklistDialogOption(boardId, options) { + + async setCopyChecklistDialogOption(boardId, options) { let currentOptions = this.getCopyChecklistDialogOptions(); currentOptions[boardId] = options; - return { - $set: { - 'profile.copyChecklistDialog': currentOptions, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.copyChecklistDialog': currentOptions } }); }, - toggleBoardStar(boardId) { + + async toggleBoardStar(boardId) { const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet'; - return { - [queryKind]: { - 'profile.starredBoards': boardId, - }, - }; + return await Users.updateAsync(this._id, { [queryKind]: { 'profile.starredBoards': boardId } }); }, - /** - * Set per-user board sort index for a board - * Stored at profile.boardSortIndex[boardId] = sortIndex (Number) - */ - setBoardSortIndex(boardId, sortIndex) { + + async setBoardSortIndex(boardId, sortIndex) { const mapping = (this.profile && this.profile.boardSortIndex) || {}; mapping[boardId] = sortIndex; - return { - $set: { - 'profile.boardSortIndex': mapping, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.boardSortIndex': mapping } }); }, - toggleAutoWidth(boardId) { + + async toggleAutoWidth(boardId) { const { autoWidthBoards = {} } = this.profile || {}; autoWidthBoards[boardId] = !autoWidthBoards[boardId]; - return { - $set: { - 'profile.autoWidthBoards': autoWidthBoards, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.autoWidthBoards': autoWidthBoards } }); }, - toggleKeyboardShortcuts() { + + async toggleKeyboardShortcuts() { const { keyboardShortcuts = true } = this.profile || {}; - return { - $set: { - 'profile.keyboardShortcuts': !keyboardShortcuts, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.keyboardShortcuts': !keyboardShortcuts } }); }, - toggleVerticalScrollbars() { + + async toggleVerticalScrollbars() { const { verticalScrollbars = true } = this.profile || {}; - return { - $set: { - 'profile.verticalScrollbars': !verticalScrollbars, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.verticalScrollbars': !verticalScrollbars } }); }, - toggleShowWeekOfYear() { + + async toggleShowWeekOfYear() { const { showWeekOfYear = true } = this.profile || {}; - return { - $set: { - 'profile.showWeekOfYear': !showWeekOfYear, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.showWeekOfYear': !showWeekOfYear } }); }, - addInvite(boardId) { - return { - $addToSet: { - 'profile.invitedBoards': boardId, - }, - }; + async addInvite(boardId) { + return await Users.updateAsync(this._id, { $addToSet: { 'profile.invitedBoards': boardId } }); }, - removeInvite(boardId) { - return { - $pull: { - 'profile.invitedBoards': boardId, - }, - }; + async removeInvite(boardId) { + return await Users.updateAsync(this._id, { $pull: { 'profile.invitedBoards': boardId } }); }, - addTag(tag) { - return { - $addToSet: { - 'profile.tags': tag, - }, - }; + async addTag(tag) { + return await Users.updateAsync(this._id, { $addToSet: { 'profile.tags': tag } }); }, - removeTag(tag) { - return { - $pull: { - 'profile.tags': tag, - }, - }; + async removeTag(tag) { + return await Users.updateAsync(this._id, { $pull: { 'profile.tags': tag } }); }, - toggleTag(tag) { - if (this.hasTag(tag)) this.removeTag(tag); - else this.addTag(tag); + async toggleTag(tag) { + if (this.hasTag(tag)) { + return await this.removeTag(tag); + } else { + return await this.addTag(tag); + } }, - setListSortBy(value) { - return { - $set: { - 'profile.listSortBy': value, - }, - }; + async setListSortBy(value) { + return await Users.updateAsync(this._id, { $set: { 'profile.listSortBy': value } }); }, - setName(value) { - return { - $set: { - 'profile.fullname': value, - }, - }; + async setName(value) { + return await Users.updateAsync(this._id, { $set: { 'profile.fullname': value } }); }, - toggleDesktopHandles(value = false) { - return { - $set: { - 'profile.showDesktopDragHandles': !value, - }, - }; + async toggleDesktopHandles(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.showDesktopDragHandles': !value } }); }, - toggleFieldsGrid(value = false) { - return { - $set: { - 'profile.customFieldsGrid': !value, - }, - }; + async toggleFieldsGrid(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.customFieldsGrid': !value } }); }, - toggleCardMaximized(value = false) { - return { - $set: { - 'profile.cardMaximized': !value, - }, - }; + async toggleCardMaximized(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.cardMaximized': !value } }); }, - toggleCardCollapsed(value = false) { - return { - $set: { - 'profile.cardCollapsed': !value, - }, - }; + async toggleCardCollapsed(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.cardCollapsed': !value } }); }, - toggleShowActivities(value = false) { - return { - $set: { - 'profile.showActivities': !value, - }, - }; + async toggleShowActivities(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.showActivities': !value } }); }, - toggleLabelText(value = false) { - return { - $set: { - 'profile.hiddenMinicardLabelText': !value, - }, - }; - }, - toggleRescueCardDescription(value = false) { - return { - $set: { - 'profile.rescueCardDescription': !value, - }, - }; - }, - toggleGreyIcons(value = false) { - return { - $set: { - 'profile.GreyIcons': !value, - }, - }; + async toggleLabelText(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.hiddenMinicardLabelText': !value } }); }, - addNotification(activityId) { - return { - $addToSet: { - 'profile.notifications': { - activity: activityId, - read: null, - }, - }, - }; + async toggleRescueCardDescription(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.rescueCardDescription': !value } }); }, - removeNotification(activityId) { - return { - $pull: { - 'profile.notifications': { - activity: activityId, - }, - }, - }; + async toggleGreyIcons(value = false) { + return await Users.updateAsync(this._id, { $set: { 'profile.GreyIcons': !value } }); }, - addEmailBuffer(text) { - return { - $addToSet: { - 'profile.emailBuffer': text, - }, - }; + async addNotification(activityId) { + return await Users.updateAsync(this._id, { + $addToSet: { 'profile.notifications': { activity: activityId, read: null } }, + }); }, - clearEmailBuffer() { - return { - $set: { - 'profile.emailBuffer': [], - }, - }; + async removeNotification(activityId) { + return await Users.updateAsync(this._id, { + $pull: { 'profile.notifications': { activity: activityId } }, + }); }, - setAvatarUrl(avatarUrl) { - return { - $set: { - 'profile.avatarUrl': avatarUrl, - }, - }; + async addEmailBuffer(text) { + return await Users.updateAsync(this._id, { $addToSet: { 'profile.emailBuffer': text } }); }, - setShowCardsCountAt(limit) { - return { - $set: { - 'profile.showCardsCountAt': limit, - }, - }; + async clearEmailBuffer() { + return await Users.updateAsync(this._id, { $set: { 'profile.emailBuffer': [] } }); }, - setStartDayOfWeek(startDay) { - return { - $set: { - 'profile.startDayOfWeek': startDay, - }, - }; + async setAvatarUrl(avatarUrl) { + return await Users.updateAsync(this._id, { $set: { 'profile.avatarUrl': avatarUrl } }); }, - setDateFormat(dateFormat) { - return { - $set: { - 'profile.dateFormat': dateFormat, - }, - }; + async setShowCardsCountAt(limit) { + return await Users.updateAsync(this._id, { $set: { 'profile.showCardsCountAt': limit } }); }, - setBoardView(view) { - return { - $set: { - 'profile.boardView': view, - }, - }; + async setStartDayOfWeek(startDay) { + return await Users.updateAsync(this._id, { $set: { 'profile.startDayOfWeek': startDay } }); }, - setListWidth(boardId, listId, width) { + async setDateFormat(dateFormat) { + return await Users.updateAsync(this._id, { $set: { 'profile.dateFormat': dateFormat } }); + }, + + async setBoardView(view) { + return await Users.updateAsync(this._id, { $set: { 'profile.boardView': view } }); + }, + + async setListWidth(boardId, listId, width) { let currentWidths = this.getListWidths(); - if (!currentWidths[boardId]) { - currentWidths[boardId] = {}; - } + if (!currentWidths[boardId]) currentWidths[boardId] = {}; currentWidths[boardId][listId] = width; - return { - $set: { - 'profile.listWidths': currentWidths, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.listWidths': currentWidths } }); }, - setListConstraint(boardId, listId, constraint) { + async setListConstraint(boardId, listId, constraint) { let currentConstraints = this.getListConstraints(); - if (!currentConstraints[boardId]) { - currentConstraints[boardId] = {}; - } + if (!currentConstraints[boardId]) currentConstraints[boardId] = {}; currentConstraints[boardId][listId] = constraint; - return { - $set: { - 'profile.listConstraints': currentConstraints, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.listConstraints': currentConstraints } }); }, - setSwimlaneHeight(boardId, swimlaneId, height) { + async setSwimlaneHeight(boardId, swimlaneId, height) { let currentHeights = this.getSwimlaneHeights(); - if (!currentHeights[boardId]) { - currentHeights[boardId] = {}; - } + if (!currentHeights[boardId]) currentHeights[boardId] = {}; currentHeights[boardId][swimlaneId] = height; - return { - $set: { - 'profile.swimlaneHeights': currentHeights, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.swimlaneHeights': currentHeights } }); }, - setCollapsedList(boardId, listId, collapsed) { + + async setCollapsedList(boardId, listId, collapsed) { const current = (this.profile && this.profile.collapsedLists) || {}; if (!current[boardId]) current[boardId] = {}; current[boardId][listId] = !!collapsed; - return { - $set: { - 'profile.collapsedLists': current, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.collapsedLists': current } }); }, - setCollapsedSwimlane(boardId, swimlaneId, collapsed) { + + async setCollapsedSwimlane(boardId, swimlaneId, collapsed) { const current = (this.profile && this.profile.collapsedSwimlanes) || {}; if (!current[boardId]) current[boardId] = {}; current[boardId][swimlaneId] = !!collapsed; - return { - $set: { - 'profile.collapsedSwimlanes': current, - }, - }; + return await Users.updateAsync(this._id, { $set: { 'profile.collapsedSwimlanes': current } }); }, - setZoomLevel(level) { - return { - $set: { - 'profile.zoomLevel': level, - }, - }; + async setZoomLevel(level) { + return await Users.updateAsync(this._id, { $set: { 'profile.zoomLevel': level } }); }, - setMobileMode(enabled) { - return { - $set: { - 'profile.mobileMode': enabled, - }, - }; + async setMobileMode(enabled) { + return await Users.updateAsync(this._id, { $set: { 'profile.mobileMode': enabled } }); }, - setCardZoom(level) { - return { - $set: { - 'profile.cardZoom': level, - }, - }; + async setCardZoom(level) { + return await Users.updateAsync(this._id, { $set: { 'profile.cardZoom': level } }); }, }); @@ -3340,7 +3170,7 @@ if (Meteor.isServer) { * @return_type {_id: string, * title: string} */ - JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) { + JsonRoutes.add('PUT', '/api/users/:userId', async function (req, res) { try { Authentication.checkUserId(req.userId); const id = req.params.userId; @@ -3350,7 +3180,7 @@ if (Meteor.isServer) { }); if (data !== undefined) { if (action === 'takeOwnership') { - data = ReactiveCache.getBoards( + const boards = ReactiveCache.getBoards( { 'members.userId': id, 'members.isAdmin': true, @@ -3360,16 +3190,18 @@ if (Meteor.isServer) { sort: 1 /* boards default sorting */, }, }, - ).map(function (board) { + ); + data = []; + for (const board of boards) { if (board.hasMember(req.userId)) { - board.removeMember(req.userId); + await board.removeMember(req.userId); } board.changeOwnership(id, req.userId); - return { + data.push({ _id: board._id, title: board.title, - }; - }); + }); + } } else { if (action === 'disableLogin' && id !== req.userId) { Users.update( diff --git a/models/watchable.js b/models/watchable.js index 7dbacb594..73cb0564c 100644 --- a/models/watchable.js +++ b/models/watchable.js @@ -19,13 +19,13 @@ const simpleWatchable = collection => { findWatcher(userId) { return _.contains(this.watchers, userId); }, - }); - collection.mutations({ - setWatcher(userId, level) { + async setWatcher(userId, level) { // if level undefined or null or false, then remove - if (!level) return { $pull: { watchers: userId } }; - return { $addToSet: { watchers: userId } }; + if (!level) { + return await collection.updateAsync(this._id, { $pull: { watchers: userId } }); + } + return await collection.updateAsync(this._id, { $addToSet: { watchers: userId } }); }, }); }; @@ -66,20 +66,20 @@ const complexWatchable = collection => { const watcher = this.findWatcher(userId); return watcher ? watcher.level : complexWatchDefault; }, - }); - collection.mutations({ - setWatcher(userId, level) { + async setWatcher(userId, level) { // if level undefined or null or false, then remove if (level === complexWatchDefault) level = null; - if (!level) return { $pull: { watchers: { userId } } }; + if (!level) { + return await collection.updateAsync(this._id, { $pull: { watchers: { userId } } }); + } const index = this.watcherIndex(userId); - if (index < 0) return { $push: { watchers: { userId, level } } }; - return { - $set: { - [`watchers.${index}.level`]: level, - }, - }; + if (index < 0) { + return await collection.updateAsync(this._id, { $push: { watchers: { userId, level } } }); + } + return await collection.updateAsync(this._id, { + $set: { [`watchers.${index}.level`]: level }, + }); }, }); }; diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 503cba3fc..0580ca139 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,25 +1,26 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { CustomFields } from './customFields'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; +import getSlug from 'limax'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); @@ -970,7 +971,7 @@ export class WekanCreator { // } } - create(board, currentBoardId) { + async create(board, currentBoardId) { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && @@ -978,7 +979,7 @@ export class WekanCreator { Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { const currentBoard = ReactiveCache.getBoard(currentBoardId); - currentBoard.archive(); + await currentBoard.archive(); } this.parseActivities(board); const boardId = this.createBoardAndLabels(board); diff --git a/package-lock.json b/package-lock.json index 86e04bf83..3038c422a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -943,6 +943,11 @@ "function-bind": "^1.1.2" } }, + "hepburn": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/hepburn/-/hepburn-1.2.2.tgz", + "integrity": "sha512-DeykBc4XmfAWsnN+Y1Svi9uaQnnz21Q/ARuGWvIBxP1iUFeMIWL41DfVkgTh7tU23LFIbmIBO2Bk17BTPu0kVA==" + }, "hotkeys-js": { "version": "3.13.15", "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.15.tgz", @@ -1143,6 +1148,17 @@ "immediate": "~3.0.5" } }, + "limax": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/limax/-/limax-4.1.0.tgz", + "integrity": "sha512-vciK5Mx+y+GrJjcVjbEjItzZ6Pbt+LXCb9d3qo3B+HcnTLZYRFyuszD6Hbwk0PDVEmZzS+FA0nT5aBy1HlZgGg==", + "requires": { + "hepburn": "^1.2.0", + "lodash.deburr": "^4.1.0", + "pinyin-pro": "^3.14.0", + "speakingurl": "^14.0.1" + } + }, "linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -1156,6 +1172,11 @@ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" }, + "lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==" + }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -2526,6 +2547,11 @@ "is-reference": "^1.1.4" } }, + "pinyin-pro": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.28.0.tgz", + "integrity": "sha512-mMRty6RisoyYNphJrTo3pnvp3w8OMZBrXm9YSWkxhAfxKj1KZk2y8T2PDIZlDDRsvZ0No+Hz6FI4sZpA6Ey25g==" + }, "possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -2786,6 +2812,11 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, + "speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==" + }, "speech-rule-engine": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.2.tgz", diff --git a/package.json b/package.json index bed4fa107..b4de5757e 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "jquery-ui": "^1.13.3", "jszip": "^3.7.1", "ldapjs": "^2.3.3", + "limax": "4.1.0", "markdown-it": "^12.3.2", "markdown-it-emoji": "^2.0.0", "markdown-it-mathjax3": "^4.3.2", diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index 04a304290..a8cb0f2dd 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -319,7 +319,7 @@ Meteor.methods({ }); Meteor.methods({ - 'boardRoutineOnLogin': function(info, oidcUserId) + 'boardRoutineOnLogin': async function(info, oidcUserId) { check(info, Object); check(oidcUserId, String); @@ -333,8 +333,8 @@ Meteor.methods({ const memberIndex = _.pluck(board?.members, 'userId').indexOf(userId); if(!board || !userId || memberIndex > -1) return - board.addMember(userId) - board.setMemberPermission( + await board.addMember(userId) + await board.setMemberPermission( userId, defaultBoardParams.contains("isAdmin"), defaultBoardParams.contains("isNoComments"), diff --git a/rebuild-wekan.sh b/rebuild-wekan.sh index 6d3475e04..f27c2d0f6 100755 --- a/rebuild-wekan.sh +++ b/rebuild-wekan.sh @@ -47,7 +47,7 @@ do # Latest fibers for Meteor sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp sudo npm -g install fibers sudo npm -g install @mapbox/node-pre-gyp # Install Meteor, if it's not yet installed - sudo npm -g install meteor@2.14 --unsafe-perm + sudo npm -g install meteor@2.16 --unsafe-perm #sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor elif [[ "$OSTYPE" == "darwin"* ]]; then echo "macOS" @@ -89,7 +89,7 @@ do npm -g uninstall node-pre-gyp npm -g install @mapbox/node-pre-gyp npm -g install node-gyp - npm -g install meteor@2.14 + npm -g install meteor@2.16 export PATH=~/.meteor:$PATH exit; elif [[ "$OSTYPE" == "cygwin" ]]; then diff --git a/server/notifications/watch.js b/server/notifications/watch.js index b6e1a6092..3b4907220 100644 --- a/server/notifications/watch.js +++ b/server/notifications/watch.js @@ -1,7 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; Meteor.methods({ - watch(watchableType, id, level) { + async watch(watchableType, id, level) { check(watchableType, String); check(id, String); check(level, Match.OneOf(String, null)); @@ -29,7 +29,7 @@ Meteor.methods({ if (board.permission === 'private' && !board.hasMember(userId)) throw new Meteor.Error('error-board-notAMember'); - watchableObj.setWatcher(userId, level); + await watchableObj.setWatcher(userId, level); return true; }, }); diff --git a/server/publications/swimlanes.js b/server/publications/swimlanes.js index ecf45021c..fec6958c0 100644 --- a/server/publications/swimlanes.js +++ b/server/publications/swimlanes.js @@ -17,7 +17,7 @@ Meteor.methods({ return ret; }, - moveSwimlane(swimlaneId, toBoardId) { + async moveSwimlane(swimlaneId, toBoardId) { check(swimlaneId, String); check(toBoardId, String); @@ -26,7 +26,7 @@ Meteor.methods({ let ret = false; if (swimlane && toBoard) { - swimlane.move(toBoardId); + await swimlane.move(toBoardId); ret = true; } diff --git a/server/rulesHelper.js b/server/rulesHelper.js index d5efe1d3f..b783b407d 100644 --- a/server/rulesHelper.js +++ b/server/rulesHelper.js @@ -1,12 +1,12 @@ import { ReactiveCache } from '/imports/reactiveCache'; RulesHelper = { - executeRules(activity) { + async executeRules(activity) { const matchingRules = this.findMatchingRules(activity); for (let i = 0; i < matchingRules.length; i++) { const action = matchingRules[i].getAction(); if (action !== undefined) { - this.performAction(activity, action); + await this.performAction(activity, action); } } }, @@ -57,7 +57,7 @@ RulesHelper = { }); return matchingMap; }, - performAction(activity, action) { + async performAction(activity, action) { const card = ReactiveCache.getCard(activity.cardId); const boardId = activity.boardId; if ( @@ -112,12 +112,12 @@ RulesHelper = { const minOrder = _.min( list.cardsUnfiltered(swimlaneId).map(c => c.sort), ); - card.move(action.boardId, swimlaneId, listId, minOrder - 1); + await card.move(action.boardId, swimlaneId, listId, minOrder - 1); } else { const maxOrder = _.max( list.cardsUnfiltered(swimlaneId).map(c => c.sort), ); - card.move(action.boardId, swimlaneId, listId, maxOrder + 1); + await card.move(action.boardId, swimlaneId, listId, maxOrder + 1); } } if (action.actionType === 'sendEmail') { @@ -247,13 +247,13 @@ RulesHelper = { } } if (action.actionType === 'archive') { - card.archive(); + await card.archive(); } if (action.actionType === 'unarchive') { - card.restore(); + await card.restore(); } if (action.actionType === 'setColor') { - card.setColor(action.selectedColor); + await card.setColor(action.selectedColor); } if (action.actionType === 'addLabel') { card.addLabel(action.labelId); @@ -281,14 +281,14 @@ RulesHelper = { title: action.checklistName, cardId: card._id, }); - checkList.checkAllItems(); + await checkList.checkAllItems(); } if (action.actionType === 'uncheckAll') { const checkList = ReactiveCache.getChecklist({ title: action.checklistName, cardId: card._id, }); - checkList.uncheckAllItems(); + await checkList.uncheckAllItems(); } if (action.actionType === 'checkItem') { const checkList = ReactiveCache.getChecklist({ @@ -299,7 +299,7 @@ RulesHelper = { title: action.checkItemName, checkListId: checkList._id, }); - checkItem.check(); + await checkItem.check(); } if (action.actionType === 'uncheckItem') { const checkList = ReactiveCache.getChecklist({ @@ -310,7 +310,7 @@ RulesHelper = { title: action.checkItemName, checkListId: checkList._id, }); - checkItem.uncheck(); + await checkItem.uncheck(); } if (action.actionType === 'addChecklist') { Checklists.insert({