mirror of
https://github.com/wekan/wekan.git
synced 2025-04-24 22:17:16 -04:00
Centralize all mutations at the model level
This commit uses a new package that I need to document. It tries to solve the long-standing debate in the Meteor community about allow/deny rules versus methods (RPC). This approach gives us both the centralized security rules of allow/deny and the white-list of allowed mutations similarly to Meteor methods. The idea to have static mutation descriptions is also inspired by Facebook's Relay/GraphQL. This will allow the development of a REST API using the high-level methods instead of the MongoDB queries to do the mapping between the HTTP requests and our collections.
This commit is contained in:
parent
c04341f1ea
commit
45b662a1dd
26 changed files with 395 additions and 377 deletions
|
@ -117,6 +117,7 @@ globals:
|
||||||
presences: true
|
presences: true
|
||||||
Ps: true
|
Ps: true
|
||||||
ReactiveTabs: false
|
ReactiveTabs: false
|
||||||
|
Restivus: false
|
||||||
SimpleSchema: false
|
SimpleSchema: false
|
||||||
SubsManager: false
|
SubsManager: false
|
||||||
T9n: false
|
T9n: false
|
||||||
|
|
|
@ -18,7 +18,6 @@ mquandalle:stylus
|
||||||
es5-shim
|
es5-shim
|
||||||
|
|
||||||
# Collections
|
# Collections
|
||||||
mongo
|
|
||||||
aldeed:collection2
|
aldeed:collection2
|
||||||
cfs:gridfs
|
cfs:gridfs
|
||||||
cfs:standard-packages
|
cfs:standard-packages
|
||||||
|
@ -26,6 +25,8 @@ dburles:collection-helpers
|
||||||
idmontie:migrations
|
idmontie:migrations
|
||||||
matb33:collection-hooks
|
matb33:collection-hooks
|
||||||
matteodem:easy-search
|
matteodem:easy-search
|
||||||
|
mongo
|
||||||
|
mquandalle:collection-mutations
|
||||||
reywood:publish-composite
|
reywood:publish-composite
|
||||||
|
|
||||||
# Account system
|
# Account system
|
||||||
|
|
|
@ -86,6 +86,7 @@ mongo-id@1.0.1-rc.0
|
||||||
mongo-livedata@1.0.9-rc.0
|
mongo-livedata@1.0.9-rc.0
|
||||||
mousetrap:mousetrap@1.4.6_1
|
mousetrap:mousetrap@1.4.6_1
|
||||||
mquandalle:autofocus@1.0.0
|
mquandalle:autofocus@1.0.0
|
||||||
|
mquandalle:collection-mutations@0.1.0
|
||||||
mquandalle:jade@0.4.3_1
|
mquandalle:jade@0.4.3_1
|
||||||
mquandalle:jade-compiler@0.4.3
|
mquandalle:jade-compiler@0.4.3
|
||||||
mquandalle:jquery-textcomplete@0.3.9_1
|
mquandalle:jquery-textcomplete@0.3.9_1
|
||||||
|
|
|
@ -22,13 +22,9 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [{
|
return [{
|
||||||
'click .js-restore-board'() {
|
'click .js-restore-board'() {
|
||||||
const boardId = this.currentData()._id;
|
const board = this.currentData();
|
||||||
Boards.update(boardId, {
|
board.restore();
|
||||||
$set: {
|
Utils.goBoardId(board._id);
|
||||||
archived: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Utils.goBoardId(boardId);
|
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,8 +7,8 @@ Template.boardMenuPopup.events({
|
||||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||||
'click .js-change-language': Popup.open('changeLanguage'),
|
'click .js-change-language': Popup.open('changeLanguage'),
|
||||||
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', () => {
|
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', () => {
|
||||||
const boardId = Session.get('currentBoard');
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
Boards.update(boardId, { $set: { archived: true }});
|
currentBoard.archive();
|
||||||
// XXX We should have some kind of notification on top of the page to
|
// XXX We should have some kind of notification on top of the page to
|
||||||
// confirm that the board was successfully archived.
|
// confirm that the board was successfully archived.
|
||||||
FlowRouter.go('home');
|
FlowRouter.go('home');
|
||||||
|
@ -17,13 +17,9 @@ Template.boardMenuPopup.events({
|
||||||
|
|
||||||
Template.boardChangeTitlePopup.events({
|
Template.boardChangeTitlePopup.events({
|
||||||
submit(evt, tpl) {
|
submit(evt, tpl) {
|
||||||
const title = tpl.$('.js-board-name').val().trim();
|
const newTitle = tpl.$('.js-board-name').val().trim();
|
||||||
if (title) {
|
if (newTitle) {
|
||||||
Boards.update(this._id, {
|
this.rename(newTitle);
|
||||||
$set: {
|
|
||||||
title,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
}
|
}
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -95,12 +91,9 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [{
|
return [{
|
||||||
'click .js-select-background'(evt) {
|
'click .js-select-background'(evt) {
|
||||||
const currentBoardId = Session.get('currentBoard');
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
Boards.update(currentBoardId, {
|
const newColor = this.currentData().toString();
|
||||||
$set: {
|
currentBoard.setColor(newColor);
|
||||||
color: this.currentData().toString(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
@ -168,11 +161,9 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
selectBoardVisibility() {
|
selectBoardVisibility() {
|
||||||
Boards.update(Session.get('currentBoard'), {
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
$set: {
|
const visibility = this.currentData();
|
||||||
permission: this.currentData(),
|
currentBoard.setVisibility(visibility);
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ Template.attachmentsGalery.events({
|
||||||
// XXX Not implemented!
|
// XXX Not implemented!
|
||||||
},
|
},
|
||||||
'click .js-add-cover'() {
|
'click .js-add-cover'() {
|
||||||
Cards.update(this.cardId, { $set: { coverId: this._id } });
|
Cards.findOne(this.cardId).setCover(this._id);
|
||||||
},
|
},
|
||||||
'click .js-remove-cover'() {
|
'click .js-remove-cover'() {
|
||||||
Cards.update(this.cardId, { $unset: { coverId: '' } });
|
Cards.findOne(this.cardId).unsetCover();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,6 @@ BlazeComponent.extendComponent({
|
||||||
this.componentParent().showOverlay.set(false);
|
this.componentParent().showOverlay.set(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCard(modifier) {
|
|
||||||
Cards.update(this.data()._id, {
|
|
||||||
$set: modifier,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
const events = {
|
const events = {
|
||||||
[`${CSSEvents.animationend} .js-card-details`]() {
|
[`${CSSEvents.animationend} .js-card-details`]() {
|
||||||
|
@ -76,13 +70,13 @@ BlazeComponent.extendComponent({
|
||||||
'submit .js-card-description'(evt) {
|
'submit .js-card-description'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const description = this.currentComponent().getValue();
|
const description = this.currentComponent().getValue();
|
||||||
this.updateCard({ description });
|
this.data().setDescription(description);
|
||||||
},
|
},
|
||||||
'submit .js-card-details-title'(evt) {
|
'submit .js-card-details-title'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const title = this.currentComponent().getValue();
|
const title = this.currentComponent().getValue();
|
||||||
if ($.trim(title)) {
|
if ($.trim(title)) {
|
||||||
this.updateCard({ title });
|
this.data().setTitle(title);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'click .js-member': Popup.open('cardMember'),
|
'click .js-member': Popup.open('cardMember'),
|
||||||
|
@ -135,14 +129,9 @@ Template.cardDetailsActionsPopup.events({
|
||||||
'click .js-labels': Popup.open('cardLabels'),
|
'click .js-labels': Popup.open('cardLabels'),
|
||||||
'click .js-attachments': Popup.open('cardAttachments'),
|
'click .js-attachments': Popup.open('cardAttachments'),
|
||||||
'click .js-move-card': Popup.open('moveCard'),
|
'click .js-move-card': Popup.open('moveCard'),
|
||||||
// 'click .js-copy': Popup.open(),
|
|
||||||
'click .js-archive'(evt) {
|
'click .js-archive'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
Cards.update(this._id, {
|
this.archive();
|
||||||
$set: {
|
|
||||||
archived: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
'click .js-more': Popup.open('cardMore'),
|
'click .js-more': Popup.open('cardMore'),
|
||||||
|
@ -152,13 +141,9 @@ Template.moveCardPopup.events({
|
||||||
'click .js-select-list'() {
|
'click .js-select-list'() {
|
||||||
// XXX We should *not* get the currentCard from the global state, but
|
// XXX We should *not* get the currentCard from the global state, but
|
||||||
// instead from a “component” state.
|
// instead from a “component” state.
|
||||||
const cardId = Session.get('currentCard');
|
const card = Cards.findOne(Session.get('currentCard'));
|
||||||
const newListId = this._id;
|
const newListId = this._id;
|
||||||
Cards.update(cardId, {
|
card.move(newListId);
|
||||||
$set: {
|
|
||||||
listId: newListId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,19 +45,9 @@ Template.createLabelPopup.helpers({
|
||||||
|
|
||||||
Template.cardLabelsPopup.events({
|
Template.cardLabelsPopup.events({
|
||||||
'click .js-select-label'(evt) {
|
'click .js-select-label'(evt) {
|
||||||
const cardId = Template.parentData(2).data._id;
|
const card = Cards.findOne(Session.get('currentCard'));
|
||||||
const labelId = this._id;
|
const labelId = this._id;
|
||||||
let operation;
|
card.toggleLabel(labelId);
|
||||||
if (Cards.find({ _id: cardId, labelIds: labelId}).count() === 0)
|
|
||||||
operation = '$addToSet';
|
|
||||||
else
|
|
||||||
operation = '$pull';
|
|
||||||
|
|
||||||
Cards.update(cardId, {
|
|
||||||
[operation]: {
|
|
||||||
labelIds: labelId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
},
|
},
|
||||||
'click .js-edit-label': Popup.open('editLabel'),
|
'click .js-edit-label': Popup.open('editLabel'),
|
||||||
|
@ -79,20 +69,10 @@ Template.formLabel.events({
|
||||||
Template.createLabelPopup.events({
|
Template.createLabelPopup.events({
|
||||||
// Create the new label
|
// Create the new label
|
||||||
'submit .create-label'(evt, tpl) {
|
'submit .create-label'(evt, tpl) {
|
||||||
|
const board = Boards.findOne(Session.get('currentBoard'));
|
||||||
const name = tpl.$('#labelName').val().trim();
|
const name = tpl.$('#labelName').val().trim();
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
const color = Blaze.getData(tpl.find('.fa-check')).color;
|
const color = Blaze.getData(tpl.find('.fa-check')).color;
|
||||||
|
board.addLabel(name, color);
|
||||||
Boards.update(boardId, {
|
|
||||||
$push: {
|
|
||||||
labels: {
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
_id: Random.id(6),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Popup.back();
|
Popup.back();
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
},
|
},
|
||||||
|
@ -100,31 +80,16 @@ Template.createLabelPopup.events({
|
||||||
|
|
||||||
Template.editLabelPopup.events({
|
Template.editLabelPopup.events({
|
||||||
'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() {
|
'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() {
|
||||||
const boardId = Session.get('currentBoard');
|
const board = Boards.findOne(Session.get('currentBoard'));
|
||||||
Boards.update(boardId, {
|
board.removeLabel(this._id);
|
||||||
$pull: {
|
|
||||||
labels: {
|
|
||||||
_id: this._id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Popup.back(2);
|
Popup.back(2);
|
||||||
}),
|
}),
|
||||||
'submit .edit-label'(evt, tpl) {
|
'submit .edit-label'(evt, tpl) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
const board = Boards.findOne(Session.get('currentBoard'));
|
||||||
const name = tpl.$('#labelName').val().trim();
|
const name = tpl.$('#labelName').val().trim();
|
||||||
const boardId = Session.get('currentBoard');
|
|
||||||
const getLabel = Utils.getLabelIndex(boardId, this._id);
|
|
||||||
const color = Blaze.getData(tpl.find('.fa-check')).color;
|
const color = Blaze.getData(tpl.find('.fa-check')).color;
|
||||||
|
board.editLabel(this._id, name, color);
|
||||||
Boards.update(boardId, {
|
|
||||||
$set: {
|
|
||||||
[getLabel.key('name')]: name,
|
|
||||||
[getLabel.key('color')]: color,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Popup.back();
|
Popup.back();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -73,23 +73,13 @@ BlazeComponent.extendComponent({
|
||||||
$cards.sortable('cancel');
|
$cards.sortable('cancel');
|
||||||
|
|
||||||
if (MultiSelection.isActive()) {
|
if (MultiSelection.isActive()) {
|
||||||
Cards.find(MultiSelection.getMongoSelector()).forEach((c, i) => {
|
Cards.find(MultiSelection.getMongoSelector()).forEach((card, i) => {
|
||||||
Cards.update(c._id, {
|
card.move(listId, sortIndex.base + i * sortIndex.increment);
|
||||||
$set: {
|
|
||||||
listId,
|
|
||||||
sort: sortIndex.base + i * sortIndex.increment,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const cardDomElement = ui.item.get(0);
|
const cardDomElement = ui.item.get(0);
|
||||||
const cardId = Blaze.getData(cardDomElement)._id;
|
const card = Blaze.getData(cardDomElement);
|
||||||
Cards.update(cardId, {
|
card.move(listId, sortIndex.base);
|
||||||
$set: {
|
|
||||||
listId,
|
|
||||||
sort: sortIndex.base,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
boardComponent.setIsDragging(false);
|
boardComponent.setIsDragging(false);
|
||||||
},
|
},
|
||||||
|
@ -107,16 +97,15 @@ BlazeComponent.extendComponent({
|
||||||
accept: '.js-member,.js-label',
|
accept: '.js-member,.js-label',
|
||||||
drop(event, ui) {
|
drop(event, ui) {
|
||||||
const cardId = Blaze.getData(this)._id;
|
const cardId = Blaze.getData(this)._id;
|
||||||
let addToSet;
|
const card = Cards.findOne(cardId);
|
||||||
|
|
||||||
if (ui.draggable.hasClass('js-member')) {
|
if (ui.draggable.hasClass('js-member')) {
|
||||||
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
const memberId = Blaze.getData(ui.draggable.get(0)).userId;
|
||||||
addToSet = { members: memberId };
|
card.assignMember(memberId);
|
||||||
} else {
|
} else {
|
||||||
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
const labelId = Blaze.getData(ui.draggable.get(0))._id;
|
||||||
addToSet = { labelIds: labelId };
|
card.addLabel(labelId);
|
||||||
}
|
}
|
||||||
Cards.update(cardId, { $addToSet: addToSet });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,14 +5,10 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
editTitle(evt) {
|
editTitle(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const form = this.componentChildren('inlinedForm')[0];
|
const newTitle = this.componentChildren('inlinedForm')[0].getValue();
|
||||||
const newTitle = form.getValue();
|
const list = this.currentData();
|
||||||
if ($.trim(newTitle)) {
|
if ($.trim(newTitle)) {
|
||||||
Lists.update(this.currentData()._id, {
|
list.rename(newTitle);
|
||||||
$set: {
|
|
||||||
title: newTitle,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -33,45 +29,30 @@ Template.listActionPopup.events({
|
||||||
},
|
},
|
||||||
'click .js-list-subscribe'() {},
|
'click .js-list-subscribe'() {},
|
||||||
'click .js-select-cards'() {
|
'click .js-select-cards'() {
|
||||||
const cardIds = Cards.find(
|
const cardIds = this.allCards().map((card) => card._id);
|
||||||
{listId: this._id},
|
|
||||||
{fields: { _id: 1 }}
|
|
||||||
).map((card) => card._id);
|
|
||||||
MultiSelection.add(cardIds);
|
MultiSelection.add(cardIds);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
'click .js-move-cards': Popup.open('listMoveCards'),
|
'click .js-move-cards': Popup.open('listMoveCards'),
|
||||||
'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', () => {
|
'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', () => {
|
||||||
Cards.find({listId: this._id}).forEach((card) => {
|
this.allCards().forEach((card) => {
|
||||||
Cards.update(card._id, {
|
card.archive();
|
||||||
$set: {
|
|
||||||
archived: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
Popup.close();
|
Popup.close();
|
||||||
}),
|
}),
|
||||||
'click .js-close-list'(evt) {
|
'click .js-close-list'(evt) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
Lists.update(this._id, {
|
this.archive();
|
||||||
$set: {
|
|
||||||
archived: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.listMoveCardsPopup.events({
|
Template.listMoveCardsPopup.events({
|
||||||
'click .js-select-list'() {
|
'click .js-select-list'() {
|
||||||
const fromList = Template.parentData(2).data._id;
|
const fromList = Template.parentData(2).data;
|
||||||
const toList = this._id;
|
const toList = this._id;
|
||||||
Cards.find({ listId: fromList }).forEach((card) => {
|
fromList.allCards().forEach((card) => {
|
||||||
Cards.update(card._id, {
|
card.move(toList);
|
||||||
$set: {
|
|
||||||
listId: toList,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
|
|
|
@ -109,14 +109,6 @@ EscapeActions.register('sidebarView',
|
||||||
() => { return Sidebar && Sidebar.getView() !== defaultView; }
|
() => { return Sidebar && Sidebar.getView() !== defaultView; }
|
||||||
);
|
);
|
||||||
|
|
||||||
function getMemberIndex(board, searchId) {
|
|
||||||
for (let i = 0; i < board.members.length; i++) {
|
|
||||||
if (board.members[i].userId === searchId)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
throw new Meteor.Error('Member not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
Template.memberPopup.helpers({
|
Template.memberPopup.helpers({
|
||||||
user() {
|
user() {
|
||||||
return Users.findOne(this.userId);
|
return Users.findOne(this.userId);
|
||||||
|
@ -135,13 +127,8 @@ Template.memberPopup.events({
|
||||||
'click .js-change-role': Popup.open('changePermissions'),
|
'click .js-change-role': Popup.open('changePermissions'),
|
||||||
'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
|
'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
|
||||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
const memberIndex = getMemberIndex(currentBoard, this.userId);
|
const memberId = this.userId;
|
||||||
|
currentBoard.removeMember(memberId);
|
||||||
Boards.update(currentBoard._id, {
|
|
||||||
$set: {
|
|
||||||
[`members.${memberIndex}.isActive`]: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
}),
|
}),
|
||||||
'click .js-leave-member'() {
|
'click .js-leave-member'() {
|
||||||
|
@ -209,26 +196,7 @@ Template.addMemberPopup.events({
|
||||||
'click .js-select-member'() {
|
'click .js-select-member'() {
|
||||||
const userId = this._id;
|
const userId = this._id;
|
||||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
const currentMembersIds = _.pluck(currentBoard.members, 'userId');
|
currentBoard.addMember(userId);
|
||||||
if (currentMembersIds.indexOf(userId) === -1) {
|
|
||||||
Boards.update(currentBoard._id, {
|
|
||||||
$push: {
|
|
||||||
members: {
|
|
||||||
userId,
|
|
||||||
isAdmin: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const memberIndex = getMemberIndex(currentBoard, userId);
|
|
||||||
|
|
||||||
Boards.update(currentBoard._id, {
|
|
||||||
$set: {
|
|
||||||
[`members.${memberIndex}.isActive`]: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -240,14 +208,9 @@ Template.addMemberPopup.onRendered(function() {
|
||||||
Template.changePermissionsPopup.events({
|
Template.changePermissionsPopup.events({
|
||||||
'click .js-set-admin, click .js-set-normal'(event) {
|
'click .js-set-admin, click .js-set-normal'(event) {
|
||||||
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
const currentBoard = Boards.findOne(Session.get('currentBoard'));
|
||||||
const memberIndex = getMemberIndex(currentBoard, this.userId);
|
const memberId = this.userId;
|
||||||
const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
|
const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
|
||||||
|
currentBoard.setMemberPermission(memberId, isAdmin);
|
||||||
Boards.update(currentBoard._id, {
|
|
||||||
$set: {
|
|
||||||
[`members.${memberIndex}.isAdmin`]: isAdmin,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Popup.back(1);
|
Popup.back(1);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,8 +29,8 @@ BlazeComponent.extendComponent({
|
||||||
events() {
|
events() {
|
||||||
return [{
|
return [{
|
||||||
'click .js-restore-card'() {
|
'click .js-restore-card'() {
|
||||||
const cardId = this.currentData()._id;
|
const card = this.currentData();
|
||||||
Cards.update(cardId, {$set: {archived: false}});
|
card.restore();
|
||||||
},
|
},
|
||||||
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
|
'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
|
||||||
const cardId = this._id;
|
const cardId = this._id;
|
||||||
|
@ -38,8 +38,8 @@ BlazeComponent.extendComponent({
|
||||||
Popup.close();
|
Popup.close();
|
||||||
}),
|
}),
|
||||||
'click .js-restore-list'() {
|
'click .js-restore-list'() {
|
||||||
const listId = this.currentData()._id;
|
const list = this.currentData();
|
||||||
Lists.update(listId, {$set: {archived: false}});
|
list.restore();
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,9 +30,9 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
}).register('filterSidebar');
|
}).register('filterSidebar');
|
||||||
|
|
||||||
function updateSelectedCards(query) {
|
function mutateSelectedCards(mutationName, ...args) {
|
||||||
Cards.find(MultiSelection.getMongoSelector()).forEach((card) => {
|
Cards.find(MultiSelection.getMongoSelector()).forEach((card) => {
|
||||||
Cards.update(card._id, query);
|
card[mutationName](...args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,47 +67,34 @@ BlazeComponent.extendComponent({
|
||||||
'click .js-toggle-label-multiselection'(evt) {
|
'click .js-toggle-label-multiselection'(evt) {
|
||||||
const labelId = this.currentData()._id;
|
const labelId = this.currentData()._id;
|
||||||
const mappedSelection = this.mapSelection('label', labelId);
|
const mappedSelection = this.mapSelection('label', labelId);
|
||||||
let operation;
|
|
||||||
if (_.every(mappedSelection))
|
if (_.every(mappedSelection)) {
|
||||||
operation = '$pull';
|
mutateSelectedCards('addLabel', labelId);
|
||||||
else if (_.every(mappedSelection, (bool) => !bool))
|
} else if (_.every(mappedSelection, (bool) => !bool)) {
|
||||||
operation = '$addToSet';
|
mutateSelectedCards('removeLabel', labelId);
|
||||||
else {
|
} else {
|
||||||
const popup = Popup.open('disambiguateMultiLabel');
|
const popup = Popup.open('disambiguateMultiLabel');
|
||||||
// XXX We need to have a better integration between the popup and the
|
// XXX We need to have a better integration between the popup and the
|
||||||
// UI components systems.
|
// UI components systems.
|
||||||
return popup.call(this.currentData(), evt);
|
return popup.call(this.currentData(), evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedCards({
|
|
||||||
[operation]: {
|
|
||||||
labelIds: labelId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'click .js-toggle-member-multiselection'(evt) {
|
'click .js-toggle-member-multiselection'(evt) {
|
||||||
const memberId = this.currentData()._id;
|
const memberId = this.currentData()._id;
|
||||||
const mappedSelection = this.mapSelection('member', memberId);
|
const mappedSelection = this.mapSelection('member', memberId);
|
||||||
let operation;
|
if (_.every(mappedSelection)) {
|
||||||
if (_.every(mappedSelection))
|
mutateSelectedCards('assignMember', memberId);
|
||||||
operation = '$pull';
|
} else if (_.every(mappedSelection, (bool) => !bool)) {
|
||||||
else if (_.every(mappedSelection, (bool) => !bool))
|
mutateSelectedCards('unassignMember', memberId);
|
||||||
operation = '$addToSet';
|
} else {
|
||||||
else {
|
|
||||||
const popup = Popup.open('disambiguateMultiMember');
|
const popup = Popup.open('disambiguateMultiMember');
|
||||||
// XXX We need to have a better integration between the popup and the
|
// XXX We need to have a better integration between the popup and the
|
||||||
// UI components systems.
|
// UI components systems.
|
||||||
return popup.call(this.currentData(), evt);
|
return popup.call(this.currentData(), evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedCards({
|
|
||||||
[operation]: {
|
|
||||||
members: memberId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
'click .js-archive-selection'() {
|
'click .js-archive-selection'() {
|
||||||
updateSelectedCards({$set: {archived: true}});
|
mutateSelectedCards('archive');
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
@ -115,22 +102,22 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
Template.disambiguateMultiLabelPopup.events({
|
Template.disambiguateMultiLabelPopup.events({
|
||||||
'click .js-remove-label'() {
|
'click .js-remove-label'() {
|
||||||
updateSelectedCards({$pull: {labelIds: this._id}});
|
mutateSelectedCards('removeLabel', this._id);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
'click .js-add-label'() {
|
'click .js-add-label'() {
|
||||||
updateSelectedCards({$addToSet: {labelIds: this._id}});
|
mutateSelectedCards('addLabel', this._id);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.disambiguateMultiMemberPopup.events({
|
Template.disambiguateMultiMemberPopup.events({
|
||||||
'click .js-unassign-member'() {
|
'click .js-unassign-member'() {
|
||||||
updateSelectedCards({$pull: {members: this._id}});
|
mutateSelectedCards('assignMember', this._id);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
'click .js-assign-member'() {
|
'click .js-assign-member'() {
|
||||||
updateSelectedCards({$addToSet: {members: this._id}});
|
mutateSelectedCards('unassignMember', this._id);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,11 +82,7 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
setAvatar(avatarUrl) {
|
setAvatar(avatarUrl) {
|
||||||
Meteor.users.update(Meteor.userId(), {
|
Meteor.user().setAvatarUrl(avatarUrl);
|
||||||
$set: {
|
|
||||||
'profile.avatarUrl': avatarUrl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setError(error) {
|
setError(error) {
|
||||||
|
@ -151,19 +147,9 @@ Template.cardMembersPopup.helpers({
|
||||||
|
|
||||||
Template.cardMembersPopup.events({
|
Template.cardMembersPopup.events({
|
||||||
'click .js-select-member'(evt) {
|
'click .js-select-member'(evt) {
|
||||||
const cardId = Template.parentData(2).data._id;
|
const card = Cards.findOne(Session.get('currentCard'));
|
||||||
const memberId = this.userId;
|
const memberId = this.userId;
|
||||||
let operation;
|
card.toggleMember(memberId);
|
||||||
if (Cards.find({ _id: cardId, members: memberId}).count() === 0)
|
|
||||||
operation = '$addToSet';
|
|
||||||
else
|
|
||||||
operation = '$pull';
|
|
||||||
|
|
||||||
Cards.update(cardId, {
|
|
||||||
[operation]: {
|
|
||||||
members: memberId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -176,7 +162,7 @@ Template.cardMemberPopup.helpers({
|
||||||
|
|
||||||
Template.cardMemberPopup.events({
|
Template.cardMemberPopup.events({
|
||||||
'click .js-remove-member'() {
|
'click .js-remove-member'() {
|
||||||
Cards.update(this.cardId, {$pull: {members: this.userId}});
|
Cards.findOne(this.cardId).unassignMember(this.userId);
|
||||||
Popup.close();
|
Popup.close();
|
||||||
},
|
},
|
||||||
'click .js-edit-profile': Popup.open('editProfile'),
|
'click .js-edit-profile': Popup.open('editProfile'),
|
||||||
|
|
|
@ -65,7 +65,7 @@ UnsavedEdits = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Blaze.registerHelper('getUnsavedValue', (fieldName, docId, defaultTo) => {
|
Blaze.registerHelper('getUnsavedValue', (fieldName, docId, defaultTo) => {
|
||||||
// Workaround some blaze feature that ass a list of keywords arguments as the
|
// Workaround some blaze feature that pass a list of keywords arguments as the
|
||||||
// last parameter (even if the caller didn't specify any).
|
// last parameter (even if the caller didn't specify any).
|
||||||
if (!_.isString(defaultTo)) {
|
if (!_.isString(defaultTo)) {
|
||||||
defaultTo = '';
|
defaultTo = '';
|
||||||
|
|
|
@ -22,20 +22,6 @@ Utils = {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLabelIndex(boardId, labelId) {
|
|
||||||
const board = Boards.findOne(boardId);
|
|
||||||
const labels = {};
|
|
||||||
_.each(board.labels, (a, b) => {
|
|
||||||
labels[a._id] = b;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
index: labels[labelId],
|
|
||||||
key(key) {
|
|
||||||
return `labels.${labels[labelId]}.${key}`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Determine the new sort index
|
// Determine the new sort index
|
||||||
calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) {
|
calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) {
|
||||||
let base, increment;
|
let base, increment;
|
||||||
|
|
|
@ -73,6 +73,125 @@ Boards.attachSchema(new SimpleSchema({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
Boards.helpers({
|
||||||
|
isPublic() {
|
||||||
|
return this.permission === 'public';
|
||||||
|
},
|
||||||
|
|
||||||
|
lists() {
|
||||||
|
return Lists.find({ boardId: this._id, archived: false },
|
||||||
|
{ sort: { sort: 1 }});
|
||||||
|
},
|
||||||
|
|
||||||
|
activities() {
|
||||||
|
return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }});
|
||||||
|
},
|
||||||
|
|
||||||
|
activeMembers() {
|
||||||
|
return _.where(this.members, {isActive: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
labelIndex(labelId) {
|
||||||
|
return _.indexOf(_.pluck(this.labels, '_id'), labelId);
|
||||||
|
},
|
||||||
|
|
||||||
|
memberIndex(memberId) {
|
||||||
|
return _.indexOf(_.pluck(this.members, 'userId'), memberId);
|
||||||
|
},
|
||||||
|
|
||||||
|
absoluteUrl() {
|
||||||
|
return FlowRouter.path('board', { id: this._id, slug: this.slug });
|
||||||
|
},
|
||||||
|
|
||||||
|
colorClass() {
|
||||||
|
return `board-color-${this.color}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Boards.mutations({
|
||||||
|
archive() {
|
||||||
|
return { $set: { archived: true }};
|
||||||
|
},
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
return { $set: { archived: false }};
|
||||||
|
},
|
||||||
|
|
||||||
|
rename(title) {
|
||||||
|
return { $set: { title }};
|
||||||
|
},
|
||||||
|
|
||||||
|
setColor(color) {
|
||||||
|
return { $set: { color }};
|
||||||
|
},
|
||||||
|
|
||||||
|
setVisibility(visibility) {
|
||||||
|
return { $set: { permission: visibility }};
|
||||||
|
},
|
||||||
|
|
||||||
|
addLabel(name, color) {
|
||||||
|
const _id = Random.id(6);
|
||||||
|
return { $push: {labels: { _id, name, color }}};
|
||||||
|
},
|
||||||
|
|
||||||
|
editLabel(labelId, name, color) {
|
||||||
|
const labelIndex = this.labelIndex(labelId);
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
[`labels.${labelIndex}.name`]: name,
|
||||||
|
[`labels.${labelIndex}.color`]: color,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
removeLabel(labelId) {
|
||||||
|
return { $pull: { labels: { _id: labelId }}};
|
||||||
|
},
|
||||||
|
|
||||||
|
addMember(memberId) {
|
||||||
|
const memberIndex = this.memberIndex(memberId);
|
||||||
|
if (memberIndex === -1) {
|
||||||
|
return {
|
||||||
|
$push: {
|
||||||
|
members: {
|
||||||
|
userId: memberId,
|
||||||
|
isAdmin: false,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
[`members.${memberIndex}.isActive`]: true,
|
||||||
|
[`members.${memberIndex}.isAdmin`]: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeMember(memberId) {
|
||||||
|
const memberIndex = this.memberIndex(memberId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
[`members.${memberIndex}.isActive`]: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setMemberPermission(memberId, isAdmin) {
|
||||||
|
const memberIndex = this.memberIndex(memberId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
$set: {
|
||||||
|
[`members.${memberIndex}.isAdmin`]: isAdmin,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Boards.allow({
|
Boards.allow({
|
||||||
insert: Meteor.userId,
|
insert: Meteor.userId,
|
||||||
|
@ -119,33 +238,6 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Boards.helpers({
|
|
||||||
isPublic() {
|
|
||||||
return this.permission === 'public';
|
|
||||||
},
|
|
||||||
|
|
||||||
lists() {
|
|
||||||
return Lists.find({ boardId: this._id, archived: false },
|
|
||||||
{ sort: { sort: 1 }});
|
|
||||||
},
|
|
||||||
|
|
||||||
activities() {
|
|
||||||
return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }});
|
|
||||||
},
|
|
||||||
|
|
||||||
activeMembers() {
|
|
||||||
return _.where(this.members, {isActive: true});
|
|
||||||
},
|
|
||||||
|
|
||||||
absoluteUrl() {
|
|
||||||
return FlowRouter.path('board', { id: this._id, slug: this.slug });
|
|
||||||
},
|
|
||||||
|
|
||||||
colorClass() {
|
|
||||||
return `board-color-${this.color}`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Boards.before.insert((userId, doc) => {
|
Boards.before.insert((userId, doc) => {
|
||||||
// XXX We need to improve slug management. Only the id should be necessary
|
// XXX We need to improve slug management. Only the id should be necessary
|
||||||
// to identify a board in the code.
|
// to identify a board in the code.
|
69
models/cardComments.js
Normal file
69
models/cardComments.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
CardComments = new Mongo.Collection('card_comments');
|
||||||
|
|
||||||
|
CardComments.attachSchema(new SimpleSchema({
|
||||||
|
boardId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
cardId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
// XXX Rename in `content`? `text` is a bit vague...
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
// XXX We probably don't need this information here, since we already have it
|
||||||
|
// in the associated comment creation activity
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
},
|
||||||
|
// XXX Should probably be called `authorId`
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
CardComments.allow({
|
||||||
|
insert(userId, doc) {
|
||||||
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
|
},
|
||||||
|
update(userId, doc) {
|
||||||
|
return userId === doc.userId;
|
||||||
|
},
|
||||||
|
remove(userId, doc) {
|
||||||
|
return userId === doc.userId;
|
||||||
|
},
|
||||||
|
fetch: ['userId', 'boardId'],
|
||||||
|
});
|
||||||
|
|
||||||
|
CardComments.helpers({
|
||||||
|
user() {
|
||||||
|
return Users.findOne(this.userId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CardComments.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
|
CardComments.before.insert((userId, doc) => {
|
||||||
|
doc.createdAt = new Date();
|
||||||
|
doc.userId = userId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
CardComments.after.insert((userId, doc) => {
|
||||||
|
Activities.insert({
|
||||||
|
userId,
|
||||||
|
activityType: 'addComment',
|
||||||
|
boardId: doc.boardId,
|
||||||
|
cardId: doc.cardId,
|
||||||
|
commentId: doc._id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
CardComments.after.remove((userId, doc) => {
|
||||||
|
const activity = Activities.findOne({ commentId: doc._id });
|
||||||
|
if (activity) {
|
||||||
|
Activities.remove(activity._id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
Cards = new Mongo.Collection('cards');
|
Cards = new Mongo.Collection('cards');
|
||||||
CardComments = new Mongo.Collection('card_comments');
|
|
||||||
|
|
||||||
// XXX To improve pub/sub performances a card document should include a
|
// XXX To improve pub/sub performances a card document should include a
|
||||||
// de-normalized number of comments so we don't have to publish the whole list
|
// de-normalized number of comments so we don't have to publish the whole list
|
||||||
|
@ -54,64 +53,28 @@ Cards.attachSchema(new SimpleSchema({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
CardComments.attachSchema(new SimpleSchema({
|
Cards.allow({
|
||||||
boardId: {
|
insert(userId, doc) {
|
||||||
type: String,
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
},
|
},
|
||||||
cardId: {
|
update(userId, doc) {
|
||||||
type: String,
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
},
|
},
|
||||||
// XXX Rename in `content`? `text` is a bit vague...
|
remove(userId, doc) {
|
||||||
text: {
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
type: String,
|
|
||||||
},
|
},
|
||||||
// XXX We probably don't need this information here, since we already have it
|
fetch: ['boardId'],
|
||||||
// in the associated comment creation activity
|
});
|
||||||
createdAt: {
|
|
||||||
type: Date,
|
|
||||||
denyUpdate: false,
|
|
||||||
},
|
|
||||||
// XXX Should probably be called `authorId`
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
|
||||||
Cards.allow({
|
|
||||||
insert(userId, doc) {
|
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
|
||||||
},
|
|
||||||
update(userId, doc) {
|
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
|
||||||
},
|
|
||||||
remove(userId, doc) {
|
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
|
||||||
},
|
|
||||||
fetch: ['boardId'],
|
|
||||||
});
|
|
||||||
|
|
||||||
CardComments.allow({
|
|
||||||
insert(userId, doc) {
|
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
|
||||||
},
|
|
||||||
update(userId, doc) {
|
|
||||||
return userId === doc.userId;
|
|
||||||
},
|
|
||||||
remove(userId, doc) {
|
|
||||||
return userId === doc.userId;
|
|
||||||
},
|
|
||||||
fetch: ['userId', 'boardId'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Cards.helpers({
|
Cards.helpers({
|
||||||
list() {
|
list() {
|
||||||
return Lists.findOne(this.listId);
|
return Lists.findOne(this.listId);
|
||||||
},
|
},
|
||||||
|
|
||||||
board() {
|
board() {
|
||||||
return Boards.findOne(this.boardId);
|
return Boards.findOne(this.boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
labels() {
|
labels() {
|
||||||
const boardLabels = this.board().labels;
|
const boardLabels = this.board().labels;
|
||||||
const cardLabels = _.filter(boardLabels, (label) => {
|
const cardLabels = _.filter(boardLabels, (label) => {
|
||||||
|
@ -119,27 +82,35 @@ Cards.helpers({
|
||||||
});
|
});
|
||||||
return cardLabels;
|
return cardLabels;
|
||||||
},
|
},
|
||||||
|
|
||||||
hasLabel(labelId) {
|
hasLabel(labelId) {
|
||||||
return _.contains(this.labelIds, labelId);
|
return _.contains(this.labelIds, labelId);
|
||||||
},
|
},
|
||||||
|
|
||||||
user() {
|
user() {
|
||||||
return Users.findOne(this.userId);
|
return Users.findOne(this.userId);
|
||||||
},
|
},
|
||||||
|
|
||||||
isAssigned(memberId) {
|
isAssigned(memberId) {
|
||||||
return _.contains(this.members, memberId);
|
return _.contains(this.members, memberId);
|
||||||
},
|
},
|
||||||
|
|
||||||
activities() {
|
activities() {
|
||||||
return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }});
|
return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }});
|
||||||
},
|
},
|
||||||
|
|
||||||
comments() {
|
comments() {
|
||||||
return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
|
return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }});
|
||||||
},
|
},
|
||||||
|
|
||||||
attachments() {
|
attachments() {
|
||||||
return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }});
|
return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }});
|
||||||
},
|
},
|
||||||
|
|
||||||
cover() {
|
cover() {
|
||||||
return Attachments.findOne(this.coverId);
|
return Attachments.findOne(this.coverId);
|
||||||
},
|
},
|
||||||
|
|
||||||
absoluteUrl() {
|
absoluteUrl() {
|
||||||
const board = this.board();
|
const board = this.board();
|
||||||
return FlowRouter.path('card', {
|
return FlowRouter.path('card', {
|
||||||
|
@ -148,33 +119,86 @@ Cards.helpers({
|
||||||
cardId: this._id,
|
cardId: this._id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
rootUrl() {
|
rootUrl() {
|
||||||
return Meteor.absoluteUrl(this.absoluteUrl().replace('/', ''));
|
return Meteor.absoluteUrl(this.absoluteUrl().replace('/', ''));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
CardComments.helpers({
|
Cards.mutations({
|
||||||
user() {
|
archive() {
|
||||||
return Users.findOne(this.userId);
|
return { $set: { archived: true }};
|
||||||
|
},
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
return { $set: { archived: false }};
|
||||||
|
},
|
||||||
|
|
||||||
|
setTitle(title) {
|
||||||
|
return { $set: { title }};
|
||||||
|
},
|
||||||
|
|
||||||
|
setDescription(description) {
|
||||||
|
return { $set: { description }};
|
||||||
|
},
|
||||||
|
|
||||||
|
move(listId, sortIndex) {
|
||||||
|
const mutatedFields = { listId };
|
||||||
|
if (sortIndex) {
|
||||||
|
mutatedFields.sort = sortIndex;
|
||||||
|
}
|
||||||
|
return { $set: mutatedFields };
|
||||||
|
},
|
||||||
|
|
||||||
|
addLabel(labelId) {
|
||||||
|
return { $addToSet: { labelIds: labelId }};
|
||||||
|
},
|
||||||
|
|
||||||
|
removeLabel(labelId) {
|
||||||
|
return { $pull: { labelIds: labelId }};
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleLabel(labelId) {
|
||||||
|
if (this.labelIds && this.labelIds.indexOf(labelId) > -1) {
|
||||||
|
return this.removeLabel(labelId);
|
||||||
|
} else {
|
||||||
|
return this.addLabel(labelId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
assignMember(memberId) {
|
||||||
|
return { $addToSet: { members: memberId }};
|
||||||
|
},
|
||||||
|
|
||||||
|
unassignMember(memberId) {
|
||||||
|
return { $pull: { members: memberId }};
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleMember(memberId) {
|
||||||
|
if (this.members && this.members.indexOf(memberId) > -1) {
|
||||||
|
return this.unassignMember(memberId);
|
||||||
|
} else {
|
||||||
|
return this.assignMember(memberId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setCover(coverId) {
|
||||||
|
return { $set: { coverId }};
|
||||||
|
},
|
||||||
|
|
||||||
|
unsetCover() {
|
||||||
|
return { $unset: { coverId: '' }};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
CardComments.hookOptions.after.update = { fetchPrevious: false };
|
|
||||||
Cards.before.insert((userId, doc) => {
|
Cards.before.insert((userId, doc) => {
|
||||||
doc.createdAt = new Date();
|
doc.createdAt = new Date();
|
||||||
doc.dateLastActivity = new Date();
|
doc.dateLastActivity = new Date();
|
||||||
|
|
||||||
// defaults
|
|
||||||
doc.archived = false;
|
doc.archived = false;
|
||||||
|
|
||||||
// userId native set.
|
if (!doc.userId) {
|
||||||
if (!doc.userId)
|
|
||||||
doc.userId = userId;
|
doc.userId = userId;
|
||||||
});
|
}
|
||||||
|
|
||||||
CardComments.before.insert((userId, doc) => {
|
|
||||||
doc.createdAt = new Date();
|
|
||||||
doc.userId = userId;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
|
@ -264,21 +288,4 @@ if (Meteor.isServer) {
|
||||||
cardId: doc._id,
|
cardId: doc._id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
CardComments.after.insert((userId, doc) => {
|
|
||||||
Activities.insert({
|
|
||||||
userId,
|
|
||||||
activityType: 'addComment',
|
|
||||||
boardId: doc.boardId,
|
|
||||||
cardId: doc.cardId,
|
|
||||||
commentId: doc._id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
CardComments.after.remove((userId, doc) => {
|
|
||||||
const activity = Activities.findOne({ commentId: doc._id });
|
|
||||||
if (activity) {
|
|
||||||
Activities.remove(activity._id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
|
@ -27,20 +27,18 @@ Lists.attachSchema(new SimpleSchema({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
Lists.allow({
|
||||||
Lists.allow({
|
insert(userId, doc) {
|
||||||
insert(userId, doc) {
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
},
|
||||||
},
|
update(userId, doc) {
|
||||||
update(userId, doc) {
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
},
|
||||||
},
|
remove(userId, doc) {
|
||||||
remove(userId, doc) {
|
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
||||||
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
|
},
|
||||||
},
|
fetch: ['boardId'],
|
||||||
fetch: ['boardId'],
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Lists.helpers({
|
Lists.helpers({
|
||||||
cards() {
|
cards() {
|
||||||
|
@ -49,12 +47,30 @@ Lists.helpers({
|
||||||
archived: false,
|
archived: false,
|
||||||
}), { sort: ['sort'] });
|
}), { sort: ['sort'] });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
allCards() {
|
||||||
|
return Cards.find({ listId: this._id });
|
||||||
|
},
|
||||||
|
|
||||||
board() {
|
board() {
|
||||||
return Boards.findOne(this.boardId);
|
return Boards.findOne(this.boardId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// HOOKS
|
Lists.mutations({
|
||||||
|
rename(title) {
|
||||||
|
return { $set: { title }};
|
||||||
|
},
|
||||||
|
|
||||||
|
archive() {
|
||||||
|
return { $set: { archived: true }};
|
||||||
|
},
|
||||||
|
|
||||||
|
restore() {
|
||||||
|
return { $set: { archived: false }};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Lists.hookOptions.after.update = { fetchPrevious: false };
|
Lists.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
Lists.before.insert((userId, doc) => {
|
Lists.before.insert((userId, doc) => {
|
|
@ -49,14 +49,20 @@ Users.helpers({
|
||||||
return this.username[0].toUpperCase();
|
return this.username[0].toUpperCase();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Users.mutations({
|
||||||
toggleBoardStar(boardId) {
|
toggleBoardStar(boardId) {
|
||||||
const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';
|
const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet';
|
||||||
Meteor.users.update(this._id, {
|
return {
|
||||||
[queryKind]: {
|
[queryKind]: {
|
||||||
'profile.starredBoards': boardId,
|
'profile.starredBoards': boardId,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setAvatarUrl(avatarUrl) {
|
||||||
|
return { $set: { 'profile.avatarUrl': avatarUrl }};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,11 +25,7 @@ if (isSandstorm && Meteor.isServer) {
|
||||||
// apparently meteor-core misses an API to handle that cleanly, cf.
|
// apparently meteor-core misses an API to handle that cleanly, cf.
|
||||||
// https://github.com/meteor/meteor/blob/ff783e9a12ffa04af6fd163843a563c9f4bbe8c1/packages/accounts-base/accounts_server.js#L1143
|
// https://github.com/meteor/meteor/blob/ff783e9a12ffa04af6fd163843a563c9f4bbe8c1/packages/accounts-base/accounts_server.js#L1143
|
||||||
function updateUserAvatar(userId, avatarUrl) {
|
function updateUserAvatar(userId, avatarUrl) {
|
||||||
Users.update(userId, {
|
Users.findOne(userId).setAvatarUrl(avatarUrl);
|
||||||
$set: {
|
|
||||||
'profile.avatarUrl': avatarUrl,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUserPermissions(userId, permissions) {
|
function updateUserPermissions(userId, permissions) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue