Merge branch 'main' into autofocus-migration

This commit is contained in:
Harry Adel 2026-01-24 01:58:25 +02:00 committed by GitHub
commit 42968d4c15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 1002 additions and 1467 deletions

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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:

View file

@ -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

View file

@ -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');
}),
},

View file

@ -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'));

View file

@ -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);
}
},
});

View file

@ -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) {

View file

@ -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());

View file

@ -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() {

View file

@ -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'() {

View file

@ -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();
}
}),
}

View file

@ -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');

View file

@ -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;

View file

@ -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();
},
},

View file

@ -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);
}
},
});

View file

@ -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,

View file

@ -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();
},
),

View file

@ -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();
},
},

View file

@ -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();
}
};

View file

@ -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

View file

@ -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

View file

@ -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`.

View file

@ -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": "Ови списи су <strong>видљиви само сарадницима<strong>.",
@ -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": "Текући корак"
}

View file

@ -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),

View file

@ -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: {

View file

@ -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 },
});
},
});

View file

@ -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({

View file

@ -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);

View file

@ -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;
},
});

View file

@ -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'));
},

View file

@ -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);
},

View file

@ -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 } });
},
});

View file

@ -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);

View file

@ -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;
},

View file

@ -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(

View file

@ -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 },
});
},
});
};

View file

@ -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);

31
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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"),

View file

@ -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

View file

@ -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;
},
});

View file

@ -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;
}

View file

@ -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({