mirror of
https://github.com/wekan/wekan.git
synced 2026-01-24 18:26:10 +01:00
Merge branch 'main' into autofocus-migration
This commit is contained in:
commit
42968d4c15
46 changed files with 1002 additions and 1467 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -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
|
||||
}
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -22,6 +22,23 @@ Fixing other platforms In Progress.
|
|||
WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible.
|
||||
Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible.
|
||||
|
||||
# Upcoming WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
||||
- Secure Sandbox for VSCode at Debian 13 amd64.
|
||||
[Part 1](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088),
|
||||
[Part 1](https://github.com/wekan/wekan/commit/cc8b771eb448199fa23a87955cf9fa1a504ba8d2).
|
||||
Thanks to xet7.
|
||||
- [Updated build scripts and docs to Meteor 2.16](https://github.com/wekan/wekan/commit/1d374db0f3ed35a0463b5f89ca2d01078e245d11).
|
||||
Thanks to xet7.
|
||||
- [Replace mquandalle:collection-mutations with collection helpers](https://github.com/wekan/wekan/pull/6086).
|
||||
Thanks to harryadel.
|
||||
- [Replace ongoworks:speakingurl with limax](https://github.com/wekan/wekan/pull/6087).
|
||||
Thanks to harryadel.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v8.23 2026-01-21 WeKan ® release
|
||||
|
||||
This release adds the following updates:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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'() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
82
docs/Security/Sandboxes/vscode/README.md
Normal file
82
docs/Security/Sandboxes/vscode/README.md
Normal 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`.
|
||||
|
|
@ -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": "Текући корак"
|
||||
}
|
||||
|
|
|
|||
260
models/boards.js
260
models/boards.js
|
|
@ -12,6 +12,7 @@ import {
|
|||
import Users from "./users";
|
||||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
import TableVisibilityModeSettings from "./tableVisibilityModeSettings";
|
||||
import getSlug from 'limax';
|
||||
|
||||
// const escapeForRegex = require('escape-string-regexp');
|
||||
|
||||
|
|
@ -1431,93 +1432,80 @@ Boards.helpers({
|
|||
isTemplatesBoard() {
|
||||
return this.type === 'template-container';
|
||||
},
|
||||
});
|
||||
|
||||
Boards.mutations({
|
||||
archive() {
|
||||
return { $set: { archived: true, archivedAt: new Date() } };
|
||||
async archive() {
|
||||
return await Boards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } });
|
||||
},
|
||||
|
||||
restore() {
|
||||
return { $set: { archived: false } };
|
||||
async restore() {
|
||||
return await Boards.updateAsync(this._id, { $set: { archived: false } });
|
||||
},
|
||||
|
||||
rename(title) {
|
||||
return { $set: { title } };
|
||||
async rename(title) {
|
||||
return await Boards.updateAsync(this._id, { $set: { title } });
|
||||
},
|
||||
|
||||
setDescription(description) {
|
||||
return { $set: { description } };
|
||||
async setDescription(description) {
|
||||
return await Boards.updateAsync(this._id, { $set: { description } });
|
||||
},
|
||||
|
||||
setColor(color) {
|
||||
return { $set: { color } };
|
||||
async setColor(color) {
|
||||
return await Boards.updateAsync(this._id, { $set: { color } });
|
||||
},
|
||||
|
||||
setBackgroundImageURL(backgroundImageURL) {
|
||||
async setBackgroundImageURL(backgroundImageURL) {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if(currentUser.isBoardAdmin()) {
|
||||
return { $set: { backgroundImageURL } };
|
||||
} else if (currentUser.isAdmin()) {
|
||||
return { $set: { backgroundImageURL } };
|
||||
} else {
|
||||
return false;
|
||||
if (currentUser.isBoardAdmin() || currentUser.isAdmin()) {
|
||||
return await Boards.updateAsync(this._id, { $set: { backgroundImageURL } });
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
setVisibility(visibility) {
|
||||
return { $set: { permission: visibility } };
|
||||
async setVisibility(visibility) {
|
||||
return await Boards.updateAsync(this._id, { $set: { permission: visibility } });
|
||||
},
|
||||
|
||||
addLabel(name, color) {
|
||||
// If label with the same name and color already exists we don't want to
|
||||
// create another one because they would be indistinguishable in the UI
|
||||
// (they would still have different `_id` but that is not exposed to the
|
||||
// user).
|
||||
async addLabel(name, color) {
|
||||
if (!this.getLabel(name, color)) {
|
||||
const _id = Random.id(6);
|
||||
return { $push: { labels: { _id, name, color } } };
|
||||
return await Boards.updateAsync(this._id, { $push: { labels: { _id, name, color } } });
|
||||
}
|
||||
return {};
|
||||
return null;
|
||||
},
|
||||
|
||||
editLabel(labelId, name, color) {
|
||||
async editLabel(labelId, name, color) {
|
||||
if (!this.getLabel(name, color)) {
|
||||
const labelIndex = this.labelIndex(labelId);
|
||||
return {
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: {
|
||||
[`labels.${labelIndex}.name`]: name,
|
||||
[`labels.${labelIndex}.color`]: color,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
return {};
|
||||
return null;
|
||||
},
|
||||
|
||||
removeLabel(labelId) {
|
||||
return { $pull: { labels: { _id: labelId } } };
|
||||
async removeLabel(labelId) {
|
||||
return await Boards.updateAsync(this._id, { $pull: { labels: { _id: labelId } } });
|
||||
},
|
||||
|
||||
changeOwnership(fromId, toId) {
|
||||
async changeOwnership(fromId, toId) {
|
||||
const memberIndex = this.memberIndex(fromId);
|
||||
return {
|
||||
$set: {
|
||||
[`members.${memberIndex}.userId`]: toId,
|
||||
},
|
||||
};
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: { [`members.${memberIndex}.userId`]: toId },
|
||||
});
|
||||
},
|
||||
|
||||
addMember(memberId) {
|
||||
async addMember(memberId) {
|
||||
const memberIndex = this.memberIndex(memberId);
|
||||
if (memberIndex >= 0) {
|
||||
return {
|
||||
$set: {
|
||||
[`members.${memberIndex}.isActive`]: true,
|
||||
},
|
||||
};
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: { [`members.${memberIndex}.isActive`]: true },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$push: {
|
||||
members: {
|
||||
userId: memberId,
|
||||
|
|
@ -1532,32 +1520,28 @@ Boards.mutations({
|
|||
isReadAssignedOnly: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
removeMember(memberId) {
|
||||
async removeMember(memberId) {
|
||||
const memberIndex = this.memberIndex(memberId);
|
||||
|
||||
// we do not allow the only one admin to be removed
|
||||
const allowRemove =
|
||||
!this.members[memberIndex].isAdmin || this.activeAdmins().length > 1;
|
||||
if (!allowRemove) {
|
||||
return {
|
||||
$set: {
|
||||
[`members.${memberIndex}.isActive`]: true,
|
||||
},
|
||||
};
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: { [`members.${memberIndex}.isActive`]: true },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: {
|
||||
[`members.${memberIndex}.isActive`]: false,
|
||||
[`members.${memberIndex}.isAdmin`]: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setMemberPermission(
|
||||
async setMemberPermission(
|
||||
memberId,
|
||||
isAdmin,
|
||||
isNoComments,
|
||||
|
|
@ -1570,12 +1554,11 @@ Boards.mutations({
|
|||
currentUserId = Meteor.userId(),
|
||||
) {
|
||||
const memberIndex = this.memberIndex(memberId);
|
||||
// do not allow change permission of self
|
||||
if (memberId === currentUserId) {
|
||||
isAdmin = this.members[memberIndex].isAdmin;
|
||||
}
|
||||
|
||||
return {
|
||||
return await Boards.updateAsync(this._id, {
|
||||
$set: {
|
||||
[`members.${memberIndex}.isAdmin`]: isAdmin,
|
||||
[`members.${memberIndex}.isNoComments`]: isNoComments,
|
||||
|
|
@ -1586,144 +1569,143 @@ Boards.mutations({
|
|||
[`members.${memberIndex}.isReadOnly`]: isReadOnly,
|
||||
[`members.${memberIndex}.isReadAssignedOnly`]: isReadAssignedOnly,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setAllowsSubtasks(allowsSubtasks) {
|
||||
return { $set: { allowsSubtasks } };
|
||||
async setAllowsSubtasks(allowsSubtasks) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsSubtasks } });
|
||||
},
|
||||
|
||||
setAllowsCreator(allowsCreator) {
|
||||
return { $set: { allowsCreator } };
|
||||
async setAllowsCreator(allowsCreator) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCreator } });
|
||||
},
|
||||
|
||||
setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) {
|
||||
return { $set: { allowsCreatorOnMinicard } };
|
||||
async setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCreatorOnMinicard } });
|
||||
},
|
||||
|
||||
setAllowsMembers(allowsMembers) {
|
||||
return { $set: { allowsMembers } };
|
||||
async setAllowsMembers(allowsMembers) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsMembers } });
|
||||
},
|
||||
|
||||
setAllowsChecklists(allowsChecklists) {
|
||||
return { $set: { allowsChecklists } };
|
||||
async setAllowsChecklists(allowsChecklists) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsChecklists } });
|
||||
},
|
||||
|
||||
setAllowsAssignee(allowsAssignee) {
|
||||
return { $set: { allowsAssignee } };
|
||||
async setAllowsAssignee(allowsAssignee) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsAssignee } });
|
||||
},
|
||||
|
||||
setAllowsAssignedBy(allowsAssignedBy) {
|
||||
return { $set: { allowsAssignedBy } };
|
||||
async setAllowsAssignedBy(allowsAssignedBy) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsAssignedBy } });
|
||||
},
|
||||
|
||||
setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) {
|
||||
return { $set: { allowsShowListsOnMinicard } };
|
||||
async setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsShowListsOnMinicard } });
|
||||
},
|
||||
|
||||
setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) {
|
||||
return { $set: { allowsChecklistAtMinicard } };
|
||||
async setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsChecklistAtMinicard } });
|
||||
},
|
||||
|
||||
setAllowsRequestedBy(allowsRequestedBy) {
|
||||
return { $set: { allowsRequestedBy } };
|
||||
async setAllowsRequestedBy(allowsRequestedBy) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsRequestedBy } });
|
||||
},
|
||||
|
||||
setAllowsCardSortingByNumber(allowsCardSortingByNumber) {
|
||||
return { $set: { allowsCardSortingByNumber } };
|
||||
async setAllowsCardSortingByNumber(allowsCardSortingByNumber) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumber } });
|
||||
},
|
||||
|
||||
setAllowsShowLists(allowsShowLists) {
|
||||
return { $set: { allowsShowLists } };
|
||||
async setAllowsShowLists(allowsShowLists) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsShowLists } });
|
||||
},
|
||||
|
||||
|
||||
setAllowsAttachments(allowsAttachments) {
|
||||
return { $set: { allowsAttachments } };
|
||||
async setAllowsAttachments(allowsAttachments) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsAttachments } });
|
||||
},
|
||||
|
||||
setAllowsLabels(allowsLabels) {
|
||||
return { $set: { allowsLabels } };
|
||||
async setAllowsLabels(allowsLabels) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsLabels } });
|
||||
},
|
||||
|
||||
setAllowsComments(allowsComments) {
|
||||
return { $set: { allowsComments } };
|
||||
async setAllowsComments(allowsComments) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsComments } });
|
||||
},
|
||||
|
||||
setAllowsDescriptionTitle(allowsDescriptionTitle) {
|
||||
return { $set: { allowsDescriptionTitle } };
|
||||
async setAllowsDescriptionTitle(allowsDescriptionTitle) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTitle } });
|
||||
},
|
||||
|
||||
setAllowsCardNumber(allowsCardNumber) {
|
||||
return { $set: { allowsCardNumber } };
|
||||
async setAllowsCardNumber(allowsCardNumber) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCardNumber } });
|
||||
},
|
||||
|
||||
setAllowsDescriptionText(allowsDescriptionText) {
|
||||
return { $set: { allowsDescriptionText } };
|
||||
async setAllowsDescriptionText(allowsDescriptionText) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionText } });
|
||||
},
|
||||
|
||||
setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
|
||||
return { $set: { allowsDescriptionTextOnMinicard } };
|
||||
async setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTextOnMinicard } });
|
||||
},
|
||||
|
||||
setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
|
||||
return { $set: { allowsCoverAttachmentOnMinicard } };
|
||||
async setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCoverAttachmentOnMinicard } });
|
||||
},
|
||||
|
||||
setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
|
||||
return { $set: { allowsBadgeAttachmentOnMinicard } };
|
||||
async setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsBadgeAttachmentOnMinicard } });
|
||||
},
|
||||
|
||||
setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
|
||||
return { $set: { allowsCardSortingByNumberOnMinicard } };
|
||||
async setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumberOnMinicard } });
|
||||
},
|
||||
|
||||
setAllowsActivities(allowsActivities) {
|
||||
return { $set: { allowsActivities } };
|
||||
async setAllowsActivities(allowsActivities) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsActivities } });
|
||||
},
|
||||
|
||||
setAllowsReceivedDate(allowsReceivedDate) {
|
||||
return { $set: { allowsReceivedDate } };
|
||||
async setAllowsReceivedDate(allowsReceivedDate) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsReceivedDate } });
|
||||
},
|
||||
|
||||
setAllowsCardCounterList(allowsCardCounterList) {
|
||||
return { $set: { allowsCardCounterList } };
|
||||
async setAllowsCardCounterList(allowsCardCounterList) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsCardCounterList } });
|
||||
},
|
||||
|
||||
setAllowsBoardMemberList(allowsBoardMemberList) {
|
||||
return { $set: { allowsBoardMemberList } };
|
||||
async setAllowsBoardMemberList(allowsBoardMemberList) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsBoardMemberList } });
|
||||
},
|
||||
|
||||
setAllowsStartDate(allowsStartDate) {
|
||||
return { $set: { allowsStartDate } };
|
||||
async setAllowsStartDate(allowsStartDate) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsStartDate } });
|
||||
},
|
||||
|
||||
setAllowsEndDate(allowsEndDate) {
|
||||
return { $set: { allowsEndDate } };
|
||||
async setAllowsEndDate(allowsEndDate) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsEndDate } });
|
||||
},
|
||||
|
||||
setAllowsDueDate(allowsDueDate) {
|
||||
return { $set: { allowsDueDate } };
|
||||
async setAllowsDueDate(allowsDueDate) {
|
||||
return await Boards.updateAsync(this._id, { $set: { allowsDueDate } });
|
||||
},
|
||||
|
||||
setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
|
||||
return { $set: { subtasksDefaultBoardId } };
|
||||
async setSubtasksDefaultBoardId(subtasksDefaultBoardId) {
|
||||
return await Boards.updateAsync(this._id, { $set: { subtasksDefaultBoardId } });
|
||||
},
|
||||
|
||||
setSubtasksDefaultListId(subtasksDefaultListId) {
|
||||
return { $set: { subtasksDefaultListId } };
|
||||
async setSubtasksDefaultListId(subtasksDefaultListId) {
|
||||
return await Boards.updateAsync(this._id, { $set: { subtasksDefaultListId } });
|
||||
},
|
||||
|
||||
setPresentParentTask(presentParentTask) {
|
||||
return { $set: { presentParentTask } };
|
||||
async setPresentParentTask(presentParentTask) {
|
||||
return await Boards.updateAsync(this._id, { $set: { presentParentTask } });
|
||||
},
|
||||
|
||||
move(sortIndex) {
|
||||
return { $set: { sort: sortIndex } };
|
||||
async move(sortIndex) {
|
||||
return await Boards.updateAsync(this._id, { $set: { sort: sortIndex } });
|
||||
},
|
||||
|
||||
toggleShowActivities() {
|
||||
return { $set: { showActivities: !this.showActivities } };
|
||||
async toggleShowActivities() {
|
||||
return await Boards.updateAsync(this._id, { $set: { showActivities: !this.showActivities } });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1909,14 +1891,14 @@ if (Meteor.isServer) {
|
|||
check(boardId, String);
|
||||
return ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 });
|
||||
},
|
||||
quitBoard(boardId) {
|
||||
async quitBoard(boardId) {
|
||||
check(boardId, String);
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
if (board) {
|
||||
const userId = Meteor.userId();
|
||||
const index = board.memberIndex(userId);
|
||||
if (index >= 0) {
|
||||
board.removeMember(userId);
|
||||
await board.removeMember(userId);
|
||||
return true;
|
||||
} else throw new Meteor.Error('error-board-notAMember');
|
||||
} else throw new Meteor.Error('error-board-doesNotExist');
|
||||
|
|
@ -1993,14 +1975,14 @@ if (Meteor.isServer) {
|
|||
});
|
||||
|
||||
Meteor.methods({
|
||||
archiveBoard(boardId) {
|
||||
async archiveBoard(boardId) {
|
||||
check(boardId, String);
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
if (board) {
|
||||
const userId = Meteor.userId();
|
||||
const index = board.memberIndex(userId);
|
||||
if (index >= 0) {
|
||||
board.archive();
|
||||
await board.archive();
|
||||
return true;
|
||||
} else throw new Meteor.Error('error-board-notAMember');
|
||||
} else throw new Meteor.Error('error-board-doesNotExist');
|
||||
|
|
@ -2159,7 +2141,7 @@ if (Meteor.isServer) {
|
|||
}
|
||||
if (modifier.$set) {
|
||||
const boardId = doc._id;
|
||||
foreachRemovedMember(doc, modifier.$set, memberId => {
|
||||
foreachRemovedMember(doc, modifier.$set, async memberId => {
|
||||
Cards.update(
|
||||
{ boardId },
|
||||
{
|
||||
|
|
@ -2182,7 +2164,7 @@ if (Meteor.isServer) {
|
|||
);
|
||||
|
||||
const board = Boards._transform(doc);
|
||||
board.setWatcher(memberId, false);
|
||||
await board.setWatcher(memberId, false);
|
||||
|
||||
// Remove board from users starred list
|
||||
if (!board.isPublic()) {
|
||||
|
|
@ -2589,7 +2571,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
|
|||
* @param {boolean} isReadOnly ReadOnly capability
|
||||
* @param {boolean} isReadAssignedOnly ReadAssignedOnly capability
|
||||
*/
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', async function(
|
||||
req,
|
||||
res,
|
||||
) {
|
||||
|
|
@ -2606,7 +2588,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
|
|||
return data;
|
||||
}
|
||||
}
|
||||
const query = board.setMemberPermission(
|
||||
const query = await board.setMemberPermission(
|
||||
memberId,
|
||||
isTrue(isAdmin),
|
||||
isTrue(isNoComments),
|
||||
|
|
|
|||
713
models/cards.js
713
models/cards.js
|
|
@ -572,15 +572,17 @@ Cards.helpers({
|
|||
});
|
||||
},
|
||||
|
||||
mapCustomFieldsToBoard(boardId) {
|
||||
async mapCustomFieldsToBoard(boardId) {
|
||||
// Map custom fields to new board
|
||||
return this.customFields.map(cf => {
|
||||
const result = [];
|
||||
for (const cf of this.customFields) {
|
||||
const oldCf = ReactiveCache.getCustomField(cf._id);
|
||||
|
||||
// Check if oldCf is undefined or null
|
||||
if (!oldCf) {
|
||||
//console.error(`Custom field with ID ${cf._id} not found.`);
|
||||
return cf; // Skip this field if oldCf is not found
|
||||
result.push(cf); // Skip this field if oldCf is not found
|
||||
continue;
|
||||
}
|
||||
|
||||
const newCf = ReactiveCache.getCustomField({
|
||||
|
|
@ -592,11 +594,12 @@ Cards.helpers({
|
|||
if (newCf) {
|
||||
cf._id = newCf._id;
|
||||
} else if (!_.contains(oldCf.boardIds, boardId)) {
|
||||
oldCf.addBoard(boardId);
|
||||
await oldCf.addBoard(boardId);
|
||||
}
|
||||
|
||||
return cf;
|
||||
});
|
||||
result.push(cf);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
|
|
@ -1203,11 +1206,11 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
assignMember(memberId) {
|
||||
async assignMember(memberId) {
|
||||
let ret;
|
||||
if (this.isLinkedBoard()) {
|
||||
const board = ReactiveCache.getBoard(this.linkedId);
|
||||
ret = board.addMember(memberId);
|
||||
ret = await board.addMember(memberId);
|
||||
} else {
|
||||
ret = Cards.update(
|
||||
{ _id: this.getRealId() },
|
||||
|
|
@ -1234,7 +1237,7 @@ Cards.helpers({
|
|||
}
|
||||
},
|
||||
|
||||
unassignMember(memberId) {
|
||||
async unassignMember(memberId) {
|
||||
if (this.isLinkedCard()) {
|
||||
return Cards.update(
|
||||
{ _id: this.linkedId },
|
||||
|
|
@ -1242,7 +1245,7 @@ Cards.helpers({
|
|||
);
|
||||
} else if (this.isLinkedBoard()) {
|
||||
const board = ReactiveCache.getBoard(this.linkedId);
|
||||
return board.removeMember(memberId);
|
||||
return await board.removeMember(memberId);
|
||||
} else {
|
||||
return Cards.update({ _id: this._id }, { $pull: { members: memberId } });
|
||||
}
|
||||
|
|
@ -1991,52 +1994,41 @@ Cards.helpers({
|
|||
}
|
||||
return pokerWinnersListMap[0].pokerCard;
|
||||
},
|
||||
});
|
||||
|
||||
Cards.mutations({
|
||||
applyToChildren(funct) {
|
||||
ReactiveCache.getCards({
|
||||
parentId: this._id,
|
||||
}).forEach(card => {
|
||||
funct(card);
|
||||
async applyToChildren(funct) {
|
||||
const cards = ReactiveCache.getCards({ parentId: this._id });
|
||||
for (const card of cards) {
|
||||
await funct(card);
|
||||
}
|
||||
},
|
||||
|
||||
async archive() {
|
||||
await this.applyToChildren(async card => {
|
||||
await card.archive();
|
||||
});
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: { archived: true, archivedAt: new Date() },
|
||||
});
|
||||
},
|
||||
|
||||
archive() {
|
||||
this.applyToChildren(card => {
|
||||
return card.archive();
|
||||
async restore() {
|
||||
await this.applyToChildren(async card => {
|
||||
await card.restore();
|
||||
});
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: { archived: false },
|
||||
});
|
||||
return {
|
||||
$set: {
|
||||
archived: true,
|
||||
archivedAt: new Date(),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
restore() {
|
||||
this.applyToChildren(card => {
|
||||
return card.restore();
|
||||
});
|
||||
return {
|
||||
$set: {
|
||||
archived: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
moveToEndOfList({ listId, swimlaneId } = {}) {
|
||||
async moveToEndOfList({ listId, swimlaneId } = {}) {
|
||||
swimlaneId = swimlaneId || this.swimlaneId;
|
||||
const boardId = this.boardId;
|
||||
let sortIndex = 0;
|
||||
|
||||
// This should never happen, but there was a bug that was fixed in commit
|
||||
// ea0239538a68e225c867411a4f3e0d27c158383.
|
||||
if (!swimlaneId) {
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
swimlaneId = board.getDefaultSwimline()._id;
|
||||
}
|
||||
// Move the minicard to the end of the target list
|
||||
let parentElementDom = $(`#swimlane-${swimlaneId}`).get(0);
|
||||
if (!parentElementDom) parentElementDom = $(':root');
|
||||
|
||||
|
|
@ -2045,7 +2037,7 @@ Cards.mutations({
|
|||
.get(0);
|
||||
if (lastCardDom) sortIndex = Utils.calculateIndex(lastCardDom, null).base;
|
||||
|
||||
return this.moveOptionalArgs({
|
||||
return await this.moveOptionalArgs({
|
||||
boardId,
|
||||
swimlaneId,
|
||||
listId,
|
||||
|
|
@ -2053,22 +2045,19 @@ Cards.mutations({
|
|||
});
|
||||
},
|
||||
|
||||
moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) {
|
||||
async moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) {
|
||||
boardId = boardId || this.boardId;
|
||||
swimlaneId = swimlaneId || this.swimlaneId;
|
||||
// This should never happen, but there was a bug that was fixed in commit
|
||||
// ea0239538a68e225c867411a4f3e0d27c158383.
|
||||
if (!swimlaneId) {
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
swimlaneId = board.getDefaultSwimline()._id;
|
||||
}
|
||||
listId = listId || this.listId;
|
||||
if (sort === undefined || sort === null) sort = this.sort;
|
||||
return this.move(boardId, swimlaneId, listId, sort);
|
||||
return await this.move(boardId, swimlaneId, listId, sort);
|
||||
},
|
||||
|
||||
move(boardId, swimlaneId, listId, sort = null) {
|
||||
// Capture previous state for history tracking
|
||||
async move(boardId, swimlaneId, listId, sort = null) {
|
||||
const previousState = {
|
||||
boardId: this.boardId,
|
||||
swimlaneId: this.swimlaneId,
|
||||
|
|
@ -2076,20 +2065,13 @@ Cards.mutations({
|
|||
sort: this.sort,
|
||||
};
|
||||
|
||||
const mutatedFields = {
|
||||
boardId,
|
||||
swimlaneId,
|
||||
listId,
|
||||
};
|
||||
const mutatedFields = { boardId, swimlaneId, listId };
|
||||
|
||||
if (sort !== null) {
|
||||
mutatedFields.sort = sort;
|
||||
}
|
||||
|
||||
// we must only copy the labels and custom fields if the target board
|
||||
// differs from the source board
|
||||
if (this.boardId !== boardId) {
|
||||
// Get label names
|
||||
const oldBoard = ReactiveCache.getBoard(this.boardId);
|
||||
const oldBoardLabels = oldBoard.labels;
|
||||
const oldCardLabels = _.pluck(
|
||||
|
|
@ -2108,7 +2090,6 @@ Cards.mutations({
|
|||
'_id',
|
||||
);
|
||||
|
||||
// assign the new card number from the target board
|
||||
const newCardNumber = newBoard.getNextCardNumber();
|
||||
|
||||
Object.assign(mutatedFields, {
|
||||
|
|
@ -2119,11 +2100,8 @@ Cards.mutations({
|
|||
mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id);
|
||||
}
|
||||
|
||||
Cards.update(this._id, {
|
||||
$set: mutatedFields,
|
||||
});
|
||||
await Cards.updateAsync(this._id, { $set: mutatedFields });
|
||||
|
||||
// Track position change in user history (server-side only)
|
||||
if (Meteor.isServer && Meteor.userId() && typeof UserPositionHistory !== 'undefined') {
|
||||
try {
|
||||
UserPositionHistory.trackChange({
|
||||
|
|
@ -2145,7 +2123,6 @@ Cards.mutations({
|
|||
}
|
||||
}
|
||||
|
||||
// Ensure attachments follow the card to its new board/list/swimlane
|
||||
if (Meteor.isServer) {
|
||||
const updateMeta = {};
|
||||
if (mutatedFields.boardId !== undefined) updateMeta['meta.boardId'] = mutatedFields.boardId;
|
||||
|
|
@ -2154,293 +2131,159 @@ Cards.mutations({
|
|||
|
||||
if (Object.keys(updateMeta).length > 0) {
|
||||
try {
|
||||
Attachments.collection.update(
|
||||
await Attachments.collection.updateAsync(
|
||||
{ 'meta.cardId': this._id },
|
||||
{ $set: updateMeta },
|
||||
{ multi: true },
|
||||
);
|
||||
} catch (err) {
|
||||
// Do not block the move if attachment update fails
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to update attachments metadata after moving card', this._id, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addLabel(labelId) {
|
||||
async addLabel(labelId) {
|
||||
this.labelIds.push(labelId);
|
||||
return {
|
||||
$addToSet: {
|
||||
labelIds: labelId,
|
||||
},
|
||||
};
|
||||
return await Cards.updateAsync(this._id, { $addToSet: { labelIds: labelId } });
|
||||
},
|
||||
|
||||
removeLabel(labelId) {
|
||||
async removeLabel(labelId) {
|
||||
this.labelIds = _.without(this.labelIds, labelId);
|
||||
return {
|
||||
$pull: {
|
||||
labelIds: labelId,
|
||||
},
|
||||
};
|
||||
return await Cards.updateAsync(this._id, { $pull: { labelIds: labelId } });
|
||||
},
|
||||
|
||||
toggleLabel(labelId) {
|
||||
async toggleLabel(labelId) {
|
||||
if (this.labelIds && this.labelIds.indexOf(labelId) > -1) {
|
||||
return this.removeLabel(labelId);
|
||||
return await this.removeLabel(labelId);
|
||||
} else {
|
||||
return this.addLabel(labelId);
|
||||
return await this.addLabel(labelId);
|
||||
}
|
||||
},
|
||||
|
||||
setColor(newColor) {
|
||||
async setColor(newColor) {
|
||||
if (newColor === 'white') {
|
||||
newColor = null;
|
||||
}
|
||||
return {
|
||||
$set: {
|
||||
color: newColor,
|
||||
},
|
||||
};
|
||||
return await Cards.updateAsync(this._id, { $set: { color: newColor } });
|
||||
},
|
||||
|
||||
assignMember(memberId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
members: memberId,
|
||||
},
|
||||
};
|
||||
async assignMember(memberId) {
|
||||
return await Cards.updateAsync(this._id, { $addToSet: { members: memberId } });
|
||||
},
|
||||
|
||||
assignAssignee(assigneeId) {
|
||||
// If there is not any assignee, allow one assignee, not more.
|
||||
/*
|
||||
if (this.getAssignees().length === 0) {
|
||||
return {
|
||||
$addToSet: {
|
||||
assignees: assigneeId,
|
||||
},
|
||||
};
|
||||
*/
|
||||
// Allow more that one assignee:
|
||||
// https://github.com/wekan/wekan/issues/3302
|
||||
return {
|
||||
$addToSet: {
|
||||
assignees: assigneeId,
|
||||
},
|
||||
};
|
||||
//} else {
|
||||
// return false,
|
||||
//}
|
||||
async assignAssignee(assigneeId) {
|
||||
return await Cards.updateAsync(this._id, { $addToSet: { assignees: assigneeId } });
|
||||
},
|
||||
|
||||
unassignMember(memberId) {
|
||||
return {
|
||||
$pull: {
|
||||
members: memberId,
|
||||
},
|
||||
};
|
||||
async unassignMember(memberId) {
|
||||
return await Cards.updateAsync(this._id, { $pull: { members: memberId } });
|
||||
},
|
||||
|
||||
unassignAssignee(assigneeId) {
|
||||
return {
|
||||
$pull: {
|
||||
assignees: assigneeId,
|
||||
},
|
||||
};
|
||||
async unassignAssignee(assigneeId) {
|
||||
return await Cards.updateAsync(this._id, { $pull: { assignees: assigneeId } });
|
||||
},
|
||||
|
||||
toggleMember(memberId) {
|
||||
async toggleMember(memberId) {
|
||||
if (this.members && this.members.indexOf(memberId) > -1) {
|
||||
return this.unassignMember(memberId);
|
||||
return await this.unassignMember(memberId);
|
||||
} else {
|
||||
return this.assignMember(memberId);
|
||||
return await this.assignMember(memberId);
|
||||
}
|
||||
},
|
||||
|
||||
toggleAssignee(assigneeId) {
|
||||
async toggleAssignee(assigneeId) {
|
||||
if (this.assignees && this.assignees.indexOf(assigneeId) > -1) {
|
||||
return this.unassignAssignee(assigneeId);
|
||||
return await this.unassignAssignee(assigneeId);
|
||||
} else {
|
||||
return this.assignAssignee(assigneeId);
|
||||
return await this.assignAssignee(assigneeId);
|
||||
}
|
||||
},
|
||||
|
||||
assignCustomField(customFieldId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
customFields: {
|
||||
_id: customFieldId,
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
async assignCustomField(customFieldId) {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$addToSet: { customFields: { _id: customFieldId, value: null } },
|
||||
});
|
||||
},
|
||||
|
||||
unassignCustomField(customFieldId) {
|
||||
return {
|
||||
$pull: {
|
||||
customFields: {
|
||||
_id: customFieldId,
|
||||
},
|
||||
},
|
||||
};
|
||||
async unassignCustomField(customFieldId) {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$pull: { customFields: { _id: customFieldId } },
|
||||
});
|
||||
},
|
||||
|
||||
toggleCustomField(customFieldId) {
|
||||
async toggleCustomField(customFieldId) {
|
||||
if (this.customFields && this.customFieldIndex(customFieldId) > -1) {
|
||||
return this.unassignCustomField(customFieldId);
|
||||
return await this.unassignCustomField(customFieldId);
|
||||
} else {
|
||||
return this.assignCustomField(customFieldId);
|
||||
return await this.assignCustomField(customFieldId);
|
||||
}
|
||||
},
|
||||
|
||||
toggleShowActivities() {
|
||||
return {
|
||||
$set: {
|
||||
showActivities: !this.showActivities,
|
||||
}
|
||||
};
|
||||
async toggleShowActivities() {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: { showActivities: !this.showActivities },
|
||||
});
|
||||
},
|
||||
|
||||
toggleShowChecklistAtMinicard() {
|
||||
return {
|
||||
$set: {
|
||||
showChecklistAtMinicard: !this.showChecklistAtMinicard,
|
||||
}
|
||||
};
|
||||
async toggleShowChecklistAtMinicard() {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: { showChecklistAtMinicard: !this.showChecklistAtMinicard },
|
||||
});
|
||||
},
|
||||
|
||||
setCustomField(customFieldId, value) {
|
||||
// todo
|
||||
async setCustomField(customFieldId, value) {
|
||||
const index = this.customFieldIndex(customFieldId);
|
||||
if (index > -1) {
|
||||
const update = {
|
||||
$set: {},
|
||||
};
|
||||
const update = { $set: {} };
|
||||
update.$set[`customFields.${index}.value`] = value;
|
||||
return update;
|
||||
return await Cards.updateAsync(this._id, update);
|
||||
}
|
||||
// TODO
|
||||
// Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct
|
||||
return null;
|
||||
},
|
||||
|
||||
setCover(coverId) {
|
||||
return {
|
||||
$set: {
|
||||
coverId,
|
||||
},
|
||||
};
|
||||
async setCover(coverId) {
|
||||
return await Cards.updateAsync(this._id, { $set: { coverId } });
|
||||
},
|
||||
|
||||
unsetCover() {
|
||||
return {
|
||||
$unset: {
|
||||
coverId: '',
|
||||
},
|
||||
};
|
||||
async unsetCover() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { coverId: '' } });
|
||||
},
|
||||
|
||||
//setReceived(receivedAt) {
|
||||
// return {
|
||||
// $set: {
|
||||
// receivedAt,
|
||||
// },
|
||||
// };
|
||||
//},
|
||||
|
||||
unsetReceived() {
|
||||
return {
|
||||
$unset: {
|
||||
receivedAt: '',
|
||||
},
|
||||
};
|
||||
async unsetReceived() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { receivedAt: '' } });
|
||||
},
|
||||
|
||||
//setStart(startAt) {
|
||||
// return {
|
||||
// $set: {
|
||||
// startAt,
|
||||
// },
|
||||
// };
|
||||
//},
|
||||
|
||||
unsetStart() {
|
||||
return {
|
||||
$unset: {
|
||||
startAt: '',
|
||||
},
|
||||
};
|
||||
async unsetStart() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { startAt: '' } });
|
||||
},
|
||||
|
||||
//setDue(dueAt) {
|
||||
// return {
|
||||
// $set: {
|
||||
// dueAt,
|
||||
// },
|
||||
// };
|
||||
//},
|
||||
|
||||
unsetDue() {
|
||||
return {
|
||||
$unset: {
|
||||
dueAt: '',
|
||||
},
|
||||
};
|
||||
async unsetDue() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { dueAt: '' } });
|
||||
},
|
||||
|
||||
//setEnd(endAt) {
|
||||
// return {
|
||||
// $set: {
|
||||
// endAt,
|
||||
// },
|
||||
// };
|
||||
//},
|
||||
|
||||
unsetEnd() {
|
||||
return {
|
||||
$unset: {
|
||||
endAt: '',
|
||||
},
|
||||
};
|
||||
async unsetEnd() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { endAt: '' } });
|
||||
},
|
||||
|
||||
setOvertime(isOvertime) {
|
||||
return {
|
||||
$set: {
|
||||
isOvertime,
|
||||
},
|
||||
};
|
||||
async setOvertime(isOvertime) {
|
||||
return await Cards.updateAsync(this._id, { $set: { isOvertime } });
|
||||
},
|
||||
|
||||
setSpentTime(spentTime) {
|
||||
return {
|
||||
$set: {
|
||||
spentTime,
|
||||
},
|
||||
};
|
||||
async setSpentTime(spentTime) {
|
||||
return await Cards.updateAsync(this._id, { $set: { spentTime } });
|
||||
},
|
||||
|
||||
unsetSpentTime() {
|
||||
return {
|
||||
$unset: {
|
||||
spentTime: '',
|
||||
isOvertime: false,
|
||||
},
|
||||
};
|
||||
async unsetSpentTime() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { spentTime: '', isOvertime: false } });
|
||||
},
|
||||
|
||||
setParentId(parentId) {
|
||||
return {
|
||||
$set: {
|
||||
parentId,
|
||||
},
|
||||
};
|
||||
async setParentId(parentId) {
|
||||
return await Cards.updateAsync(this._id, { $set: { parentId } });
|
||||
},
|
||||
setVoteQuestion(question, publicVote, allowNonBoardMembers) {
|
||||
return {
|
||||
|
||||
async setVoteQuestion(question, publicVote, allowNonBoardMembers) {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: {
|
||||
vote: {
|
||||
question,
|
||||
|
|
@ -2450,61 +2293,42 @@ Cards.mutations({
|
|||
negative: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
unsetVote() {
|
||||
return {
|
||||
$unset: {
|
||||
vote: '',
|
||||
},
|
||||
};
|
||||
|
||||
async unsetVote() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { vote: '' } });
|
||||
},
|
||||
setVoteEnd(end) {
|
||||
return {
|
||||
$set: { 'vote.end': end },
|
||||
};
|
||||
|
||||
async setVoteEnd(end) {
|
||||
return await Cards.updateAsync(this._id, { $set: { 'vote.end': end } });
|
||||
},
|
||||
unsetVoteEnd() {
|
||||
return {
|
||||
$unset: { 'vote.end': '' },
|
||||
};
|
||||
|
||||
async unsetVoteEnd() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { 'vote.end': '' } });
|
||||
},
|
||||
setVote(userId, forIt) {
|
||||
|
||||
async setVote(userId, forIt) {
|
||||
switch (forIt) {
|
||||
case true:
|
||||
// vote for it
|
||||
return {
|
||||
$pull: {
|
||||
'vote.negative': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'vote.positive': userId,
|
||||
},
|
||||
};
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$pull: { 'vote.negative': userId },
|
||||
$addToSet: { 'vote.positive': userId },
|
||||
});
|
||||
case false:
|
||||
// vote against
|
||||
return {
|
||||
$pull: {
|
||||
'vote.positive': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'vote.negative': userId,
|
||||
},
|
||||
};
|
||||
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$pull: { 'vote.positive': userId },
|
||||
$addToSet: { 'vote.negative': userId },
|
||||
});
|
||||
default:
|
||||
// Remove votes
|
||||
return {
|
||||
$pull: {
|
||||
'vote.positive': userId,
|
||||
'vote.negative': userId,
|
||||
},
|
||||
};
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$pull: { 'vote.positive': userId, 'vote.negative': userId },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setPokerQuestion(question, allowNonBoardMembers) {
|
||||
return {
|
||||
async setPokerQuestion(question, allowNonBoardMembers) {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: {
|
||||
poker: {
|
||||
question,
|
||||
|
|
@ -2521,246 +2345,47 @@ Cards.mutations({
|
|||
unsure: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
setPokerEstimation(estimation) {
|
||||
return {
|
||||
$set: { 'poker.estimation': estimation },
|
||||
};
|
||||
|
||||
async setPokerEstimation(estimation) {
|
||||
return await Cards.updateAsync(this._id, { $set: { 'poker.estimation': estimation } });
|
||||
},
|
||||
unsetPokerEstimation() {
|
||||
return {
|
||||
$unset: { 'poker.estimation': '' },
|
||||
};
|
||||
|
||||
async unsetPokerEstimation() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { 'poker.estimation': '' } });
|
||||
},
|
||||
unsetPoker() {
|
||||
return {
|
||||
$unset: {
|
||||
poker: '',
|
||||
},
|
||||
};
|
||||
|
||||
async unsetPoker() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { poker: '' } });
|
||||
},
|
||||
setPokerEnd(end) {
|
||||
return {
|
||||
$set: { 'poker.end': end },
|
||||
};
|
||||
|
||||
async setPokerEnd(end) {
|
||||
return await Cards.updateAsync(this._id, { $set: { 'poker.end': end } });
|
||||
},
|
||||
unsetPokerEnd() {
|
||||
return {
|
||||
$unset: { 'poker.end': '' },
|
||||
};
|
||||
|
||||
async unsetPokerEnd() {
|
||||
return await Cards.updateAsync(this._id, { $unset: { 'poker.end': '' } });
|
||||
},
|
||||
setPoker(userId, state) {
|
||||
switch (state) {
|
||||
case 'one':
|
||||
// poker one
|
||||
return {
|
||||
$pull: {
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.one': userId,
|
||||
},
|
||||
};
|
||||
case 'two':
|
||||
// poker two
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.two': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'three':
|
||||
// poker three
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.three': userId,
|
||||
},
|
||||
};
|
||||
async setPoker(userId, state) {
|
||||
const pokerFields = ['one', 'two', 'three', 'five', 'eight', 'thirteen', 'twenty', 'forty', 'oneHundred', 'unsure'];
|
||||
const pullFields = {};
|
||||
pokerFields.forEach(f => { pullFields[`poker.${f}`] = userId; });
|
||||
|
||||
case 'five':
|
||||
// poker five
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.five': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'eight':
|
||||
// poker eight
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.eight': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'thirteen':
|
||||
// poker thirteen
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.thirteen': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'twenty':
|
||||
// poker twenty
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.twenty': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'forty':
|
||||
// poker forty
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.forty': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'oneHundred':
|
||||
// poker one hundred
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.oneHundred': userId,
|
||||
},
|
||||
};
|
||||
|
||||
case 'unsure':
|
||||
// poker unsure
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
},
|
||||
$addToSet: {
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
// Remove pokers
|
||||
return {
|
||||
$pull: {
|
||||
'poker.one': userId,
|
||||
'poker.two': userId,
|
||||
'poker.three': userId,
|
||||
'poker.five': userId,
|
||||
'poker.eight': userId,
|
||||
'poker.thirteen': userId,
|
||||
'poker.twenty': userId,
|
||||
'poker.forty': userId,
|
||||
'poker.oneHundred': userId,
|
||||
'poker.unsure': userId,
|
||||
},
|
||||
};
|
||||
if (pokerFields.includes(state)) {
|
||||
delete pullFields[`poker.${state}`];
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$pull: pullFields,
|
||||
$addToSet: { [`poker.${state}`]: userId },
|
||||
});
|
||||
} else {
|
||||
return await Cards.updateAsync(this._id, { $pull: pullFields });
|
||||
}
|
||||
},
|
||||
replayPoker() {
|
||||
return {
|
||||
|
||||
async replayPoker() {
|
||||
return await Cards.updateAsync(this._id, {
|
||||
$set: {
|
||||
'poker.one': [],
|
||||
'poker.two': [],
|
||||
|
|
@ -2773,7 +2398,7 @@ Cards.mutations({
|
|||
'poker.oneHundred': [],
|
||||
'poker.unsure': [],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -4544,7 +4169,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
|
|||
JsonRoutes.add(
|
||||
'POST',
|
||||
'/api/boards/:boardId/lists/:listId/cards/:cardId/archive',
|
||||
function(req, res) {
|
||||
async function(req, res) {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramCardId = req.params.cardId;
|
||||
const paramListId = req.params.listId;
|
||||
|
|
@ -4558,7 +4183,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
|
|||
if (!card) {
|
||||
throw new Meteor.Error(404, 'Card not found');
|
||||
}
|
||||
card.archive();
|
||||
await card.archive();
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
|
|
@ -4583,7 +4208,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
|
|||
JsonRoutes.add(
|
||||
'POST',
|
||||
'/api/boards/:boardId/lists/:listId/cards/:cardId/unarchive',
|
||||
function(req, res) {
|
||||
async function(req, res) {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramCardId = req.params.cardId;
|
||||
const paramListId = req.params.listId;
|
||||
|
|
@ -4597,7 +4222,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function(
|
|||
if (!card) {
|
||||
throw new Meteor.Error(404, 'Card not found');
|
||||
}
|
||||
card.restore();
|
||||
await card.restore();
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 } });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
400
models/users.js
400
models/users.js
|
|
@ -1593,376 +1593,206 @@ Users.helpers({
|
|||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
Users.mutations({
|
||||
/** set the confirmed board id/swimlane id/list id of a board
|
||||
* @param boardId the current board id
|
||||
* @param options an object with the confirmed field values
|
||||
*/
|
||||
setMoveAndCopyDialogOption(boardId, options) {
|
||||
async setMoveAndCopyDialogOption(boardId, options) {
|
||||
let currentOptions = this.getMoveAndCopyDialogOptions();
|
||||
currentOptions[boardId] = options;
|
||||
return {
|
||||
$set: {
|
||||
'profile.moveAndCopyDialog': currentOptions,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.moveAndCopyDialog': currentOptions } });
|
||||
},
|
||||
/** set the confirmed board id/swimlane id/list id/card id of a board (move checklist)
|
||||
* @param boardId the current board id
|
||||
* @param options an object with the confirmed field values
|
||||
*/
|
||||
setMoveChecklistDialogOption(boardId, options) {
|
||||
|
||||
async setMoveChecklistDialogOption(boardId, options) {
|
||||
let currentOptions = this.getMoveChecklistDialogOptions();
|
||||
currentOptions[boardId] = options;
|
||||
return {
|
||||
$set: {
|
||||
'profile.moveChecklistDialog': currentOptions,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.moveChecklistDialog': currentOptions } });
|
||||
},
|
||||
/** set the confirmed board id/swimlane id/list id/card id of a board (copy checklist)
|
||||
* @param boardId the current board id
|
||||
* @param options an object with the confirmed field values
|
||||
*/
|
||||
setCopyChecklistDialogOption(boardId, options) {
|
||||
|
||||
async setCopyChecklistDialogOption(boardId, options) {
|
||||
let currentOptions = this.getCopyChecklistDialogOptions();
|
||||
currentOptions[boardId] = options;
|
||||
return {
|
||||
$set: {
|
||||
'profile.copyChecklistDialog': currentOptions,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.copyChecklistDialog': currentOptions } });
|
||||
},
|
||||
toggleBoardStar(boardId) {
|
||||
|
||||
async toggleBoardStar(boardId) {
|
||||
const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';
|
||||
return {
|
||||
[queryKind]: {
|
||||
'profile.starredBoards': boardId,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { [queryKind]: { 'profile.starredBoards': boardId } });
|
||||
},
|
||||
/**
|
||||
* Set per-user board sort index for a board
|
||||
* Stored at profile.boardSortIndex[boardId] = sortIndex (Number)
|
||||
*/
|
||||
setBoardSortIndex(boardId, sortIndex) {
|
||||
|
||||
async setBoardSortIndex(boardId, sortIndex) {
|
||||
const mapping = (this.profile && this.profile.boardSortIndex) || {};
|
||||
mapping[boardId] = sortIndex;
|
||||
return {
|
||||
$set: {
|
||||
'profile.boardSortIndex': mapping,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.boardSortIndex': mapping } });
|
||||
},
|
||||
toggleAutoWidth(boardId) {
|
||||
|
||||
async toggleAutoWidth(boardId) {
|
||||
const { autoWidthBoards = {} } = this.profile || {};
|
||||
autoWidthBoards[boardId] = !autoWidthBoards[boardId];
|
||||
return {
|
||||
$set: {
|
||||
'profile.autoWidthBoards': autoWidthBoards,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.autoWidthBoards': autoWidthBoards } });
|
||||
},
|
||||
toggleKeyboardShortcuts() {
|
||||
|
||||
async toggleKeyboardShortcuts() {
|
||||
const { keyboardShortcuts = true } = this.profile || {};
|
||||
return {
|
||||
$set: {
|
||||
'profile.keyboardShortcuts': !keyboardShortcuts,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.keyboardShortcuts': !keyboardShortcuts } });
|
||||
},
|
||||
toggleVerticalScrollbars() {
|
||||
|
||||
async toggleVerticalScrollbars() {
|
||||
const { verticalScrollbars = true } = this.profile || {};
|
||||
return {
|
||||
$set: {
|
||||
'profile.verticalScrollbars': !verticalScrollbars,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.verticalScrollbars': !verticalScrollbars } });
|
||||
},
|
||||
toggleShowWeekOfYear() {
|
||||
|
||||
async toggleShowWeekOfYear() {
|
||||
const { showWeekOfYear = true } = this.profile || {};
|
||||
return {
|
||||
$set: {
|
||||
'profile.showWeekOfYear': !showWeekOfYear,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.showWeekOfYear': !showWeekOfYear } });
|
||||
},
|
||||
|
||||
addInvite(boardId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
'profile.invitedBoards': boardId,
|
||||
},
|
||||
};
|
||||
async addInvite(boardId) {
|
||||
return await Users.updateAsync(this._id, { $addToSet: { 'profile.invitedBoards': boardId } });
|
||||
},
|
||||
|
||||
removeInvite(boardId) {
|
||||
return {
|
||||
$pull: {
|
||||
'profile.invitedBoards': boardId,
|
||||
},
|
||||
};
|
||||
async removeInvite(boardId) {
|
||||
return await Users.updateAsync(this._id, { $pull: { 'profile.invitedBoards': boardId } });
|
||||
},
|
||||
|
||||
addTag(tag) {
|
||||
return {
|
||||
$addToSet: {
|
||||
'profile.tags': tag,
|
||||
},
|
||||
};
|
||||
async addTag(tag) {
|
||||
return await Users.updateAsync(this._id, { $addToSet: { 'profile.tags': tag } });
|
||||
},
|
||||
|
||||
removeTag(tag) {
|
||||
return {
|
||||
$pull: {
|
||||
'profile.tags': tag,
|
||||
},
|
||||
};
|
||||
async removeTag(tag) {
|
||||
return await Users.updateAsync(this._id, { $pull: { 'profile.tags': tag } });
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
if (this.hasTag(tag)) this.removeTag(tag);
|
||||
else this.addTag(tag);
|
||||
async toggleTag(tag) {
|
||||
if (this.hasTag(tag)) {
|
||||
return await this.removeTag(tag);
|
||||
} else {
|
||||
return await this.addTag(tag);
|
||||
}
|
||||
},
|
||||
|
||||
setListSortBy(value) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.listSortBy': value,
|
||||
},
|
||||
};
|
||||
async setListSortBy(value) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.listSortBy': value } });
|
||||
},
|
||||
|
||||
setName(value) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.fullname': value,
|
||||
},
|
||||
};
|
||||
async setName(value) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.fullname': value } });
|
||||
},
|
||||
|
||||
toggleDesktopHandles(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.showDesktopDragHandles': !value,
|
||||
},
|
||||
};
|
||||
async toggleDesktopHandles(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.showDesktopDragHandles': !value } });
|
||||
},
|
||||
|
||||
toggleFieldsGrid(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.customFieldsGrid': !value,
|
||||
},
|
||||
};
|
||||
async toggleFieldsGrid(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.customFieldsGrid': !value } });
|
||||
},
|
||||
|
||||
toggleCardMaximized(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.cardMaximized': !value,
|
||||
},
|
||||
};
|
||||
async toggleCardMaximized(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.cardMaximized': !value } });
|
||||
},
|
||||
|
||||
toggleCardCollapsed(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.cardCollapsed': !value,
|
||||
},
|
||||
};
|
||||
async toggleCardCollapsed(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.cardCollapsed': !value } });
|
||||
},
|
||||
|
||||
toggleShowActivities(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.showActivities': !value,
|
||||
},
|
||||
};
|
||||
async toggleShowActivities(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.showActivities': !value } });
|
||||
},
|
||||
|
||||
toggleLabelText(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.hiddenMinicardLabelText': !value,
|
||||
},
|
||||
};
|
||||
},
|
||||
toggleRescueCardDescription(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.rescueCardDescription': !value,
|
||||
},
|
||||
};
|
||||
},
|
||||
toggleGreyIcons(value = false) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.GreyIcons': !value,
|
||||
},
|
||||
};
|
||||
async toggleLabelText(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.hiddenMinicardLabelText': !value } });
|
||||
},
|
||||
|
||||
addNotification(activityId) {
|
||||
return {
|
||||
$addToSet: {
|
||||
'profile.notifications': {
|
||||
activity: activityId,
|
||||
read: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
async toggleRescueCardDescription(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.rescueCardDescription': !value } });
|
||||
},
|
||||
|
||||
removeNotification(activityId) {
|
||||
return {
|
||||
$pull: {
|
||||
'profile.notifications': {
|
||||
activity: activityId,
|
||||
},
|
||||
},
|
||||
};
|
||||
async toggleGreyIcons(value = false) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.GreyIcons': !value } });
|
||||
},
|
||||
|
||||
addEmailBuffer(text) {
|
||||
return {
|
||||
$addToSet: {
|
||||
'profile.emailBuffer': text,
|
||||
},
|
||||
};
|
||||
async addNotification(activityId) {
|
||||
return await Users.updateAsync(this._id, {
|
||||
$addToSet: { 'profile.notifications': { activity: activityId, read: null } },
|
||||
});
|
||||
},
|
||||
|
||||
clearEmailBuffer() {
|
||||
return {
|
||||
$set: {
|
||||
'profile.emailBuffer': [],
|
||||
},
|
||||
};
|
||||
async removeNotification(activityId) {
|
||||
return await Users.updateAsync(this._id, {
|
||||
$pull: { 'profile.notifications': { activity: activityId } },
|
||||
});
|
||||
},
|
||||
|
||||
setAvatarUrl(avatarUrl) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.avatarUrl': avatarUrl,
|
||||
},
|
||||
};
|
||||
async addEmailBuffer(text) {
|
||||
return await Users.updateAsync(this._id, { $addToSet: { 'profile.emailBuffer': text } });
|
||||
},
|
||||
|
||||
setShowCardsCountAt(limit) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.showCardsCountAt': limit,
|
||||
},
|
||||
};
|
||||
async clearEmailBuffer() {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.emailBuffer': [] } });
|
||||
},
|
||||
|
||||
setStartDayOfWeek(startDay) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.startDayOfWeek': startDay,
|
||||
},
|
||||
};
|
||||
async setAvatarUrl(avatarUrl) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.avatarUrl': avatarUrl } });
|
||||
},
|
||||
|
||||
setDateFormat(dateFormat) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.dateFormat': dateFormat,
|
||||
},
|
||||
};
|
||||
async setShowCardsCountAt(limit) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.showCardsCountAt': limit } });
|
||||
},
|
||||
|
||||
setBoardView(view) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.boardView': view,
|
||||
},
|
||||
};
|
||||
async setStartDayOfWeek(startDay) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.startDayOfWeek': startDay } });
|
||||
},
|
||||
|
||||
setListWidth(boardId, listId, width) {
|
||||
async setDateFormat(dateFormat) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.dateFormat': dateFormat } });
|
||||
},
|
||||
|
||||
async setBoardView(view) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.boardView': view } });
|
||||
},
|
||||
|
||||
async setListWidth(boardId, listId, width) {
|
||||
let currentWidths = this.getListWidths();
|
||||
if (!currentWidths[boardId]) {
|
||||
currentWidths[boardId] = {};
|
||||
}
|
||||
if (!currentWidths[boardId]) currentWidths[boardId] = {};
|
||||
currentWidths[boardId][listId] = width;
|
||||
return {
|
||||
$set: {
|
||||
'profile.listWidths': currentWidths,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.listWidths': currentWidths } });
|
||||
},
|
||||
|
||||
setListConstraint(boardId, listId, constraint) {
|
||||
async setListConstraint(boardId, listId, constraint) {
|
||||
let currentConstraints = this.getListConstraints();
|
||||
if (!currentConstraints[boardId]) {
|
||||
currentConstraints[boardId] = {};
|
||||
}
|
||||
if (!currentConstraints[boardId]) currentConstraints[boardId] = {};
|
||||
currentConstraints[boardId][listId] = constraint;
|
||||
return {
|
||||
$set: {
|
||||
'profile.listConstraints': currentConstraints,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.listConstraints': currentConstraints } });
|
||||
},
|
||||
|
||||
setSwimlaneHeight(boardId, swimlaneId, height) {
|
||||
async setSwimlaneHeight(boardId, swimlaneId, height) {
|
||||
let currentHeights = this.getSwimlaneHeights();
|
||||
if (!currentHeights[boardId]) {
|
||||
currentHeights[boardId] = {};
|
||||
}
|
||||
if (!currentHeights[boardId]) currentHeights[boardId] = {};
|
||||
currentHeights[boardId][swimlaneId] = height;
|
||||
return {
|
||||
$set: {
|
||||
'profile.swimlaneHeights': currentHeights,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.swimlaneHeights': currentHeights } });
|
||||
},
|
||||
setCollapsedList(boardId, listId, collapsed) {
|
||||
|
||||
async setCollapsedList(boardId, listId, collapsed) {
|
||||
const current = (this.profile && this.profile.collapsedLists) || {};
|
||||
if (!current[boardId]) current[boardId] = {};
|
||||
current[boardId][listId] = !!collapsed;
|
||||
return {
|
||||
$set: {
|
||||
'profile.collapsedLists': current,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.collapsedLists': current } });
|
||||
},
|
||||
setCollapsedSwimlane(boardId, swimlaneId, collapsed) {
|
||||
|
||||
async setCollapsedSwimlane(boardId, swimlaneId, collapsed) {
|
||||
const current = (this.profile && this.profile.collapsedSwimlanes) || {};
|
||||
if (!current[boardId]) current[boardId] = {};
|
||||
current[boardId][swimlaneId] = !!collapsed;
|
||||
return {
|
||||
$set: {
|
||||
'profile.collapsedSwimlanes': current,
|
||||
},
|
||||
};
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.collapsedSwimlanes': current } });
|
||||
},
|
||||
|
||||
setZoomLevel(level) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.zoomLevel': level,
|
||||
},
|
||||
};
|
||||
async setZoomLevel(level) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.zoomLevel': level } });
|
||||
},
|
||||
|
||||
setMobileMode(enabled) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.mobileMode': enabled,
|
||||
},
|
||||
};
|
||||
async setMobileMode(enabled) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.mobileMode': enabled } });
|
||||
},
|
||||
|
||||
setCardZoom(level) {
|
||||
return {
|
||||
$set: {
|
||||
'profile.cardZoom': level,
|
||||
},
|
||||
};
|
||||
async setCardZoom(level) {
|
||||
return await Users.updateAsync(this._id, { $set: { 'profile.cardZoom': level } });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -3340,7 +3170,7 @@ if (Meteor.isServer) {
|
|||
* @return_type {_id: string,
|
||||
* title: string}
|
||||
*/
|
||||
JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) {
|
||||
JsonRoutes.add('PUT', '/api/users/:userId', async function (req, res) {
|
||||
try {
|
||||
Authentication.checkUserId(req.userId);
|
||||
const id = req.params.userId;
|
||||
|
|
@ -3350,7 +3180,7 @@ if (Meteor.isServer) {
|
|||
});
|
||||
if (data !== undefined) {
|
||||
if (action === 'takeOwnership') {
|
||||
data = ReactiveCache.getBoards(
|
||||
const boards = ReactiveCache.getBoards(
|
||||
{
|
||||
'members.userId': id,
|
||||
'members.isAdmin': true,
|
||||
|
|
@ -3360,16 +3190,18 @@ if (Meteor.isServer) {
|
|||
sort: 1 /* boards default sorting */,
|
||||
},
|
||||
},
|
||||
).map(function (board) {
|
||||
);
|
||||
data = [];
|
||||
for (const board of boards) {
|
||||
if (board.hasMember(req.userId)) {
|
||||
board.removeMember(req.userId);
|
||||
await board.removeMember(req.userId);
|
||||
}
|
||||
board.changeOwnership(id, req.userId);
|
||||
return {
|
||||
data.push({
|
||||
_id: board._id,
|
||||
title: board.title,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (action === 'disableLogin' && id !== req.userId) {
|
||||
Users.update(
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
31
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue