mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 15:30:13 +01:00
add a notification drawer like trello
This commit is contained in:
parent
29d62440a5
commit
9819c9f801
20 changed files with 701 additions and 257 deletions
|
|
@ -8,234 +8,201 @@ template(name="activities")
|
||||||
+cardActivities
|
+cardActivities
|
||||||
|
|
||||||
template(name="boardActivities")
|
template(name="boardActivities")
|
||||||
each currentBoard.activities
|
each activityData in currentBoard.activities
|
||||||
.activity
|
+activity(activity=activityData card=card mode=mode)
|
||||||
+userAvatar(userId=user._id)
|
|
||||||
p.activity-desc
|
|
||||||
+memberName(user=user)
|
|
||||||
|
|
||||||
if($eq activityType 'deleteAttachment')
|
|
||||||
| {{{_ 'activity-delete-attach' cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addAttachment')
|
|
||||||
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addBoardMember')
|
|
||||||
| {{{_ 'activity-added' memberLink boardLabel}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addComment')
|
|
||||||
| {{{_ 'activity-on' cardLink}}}
|
|
||||||
a.activity-comment(href="{{ card.absoluteUrl }}")
|
|
||||||
+viewer
|
|
||||||
= comment.text
|
|
||||||
|
|
||||||
if($eq activityType 'addChecklist')
|
|
||||||
| {{{_ 'activity-checklist-added' cardLink}}}.
|
|
||||||
.activity-checklist(href="{{ card.absoluteUrl }}")
|
|
||||||
+viewer
|
|
||||||
= checklist.title
|
|
||||||
if($eq activityType 'removeChecklist')
|
|
||||||
| {{{_ 'activity-checklist-removed' cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'checkedItem')
|
|
||||||
| {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'uncheckedItem')
|
|
||||||
| {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'checklistCompleted')
|
|
||||||
| {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'checklistUncompleted')
|
|
||||||
| {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addChecklistItem')
|
|
||||||
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
|
|
||||||
.activity-checklist(href="{{ card.absoluteUrl }}")
|
|
||||||
+viewer
|
|
||||||
= checklistItem.title
|
|
||||||
if($eq activityType 'removedChecklistItem')
|
|
||||||
| {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'archivedCard')
|
|
||||||
| {{{_ 'activity-archived' cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'archivedList')
|
|
||||||
| {{_ 'activity-archived' list.title}}.
|
|
||||||
|
|
||||||
if($eq activityType 'archivedSwimlane')
|
|
||||||
| {{_ 'activity-archived' swimlane.title}}.
|
|
||||||
|
|
||||||
if($eq activityType 'createBoard')
|
|
||||||
| {{_ 'activity-created' boardLabel}}.
|
|
||||||
|
|
||||||
if($eq activityType 'createCard')
|
|
||||||
| {{{_ 'activity-added' cardLink boardLabel}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'createCustomField')
|
|
||||||
| {{_ 'activity-customfield-created' customField}}.
|
|
||||||
|
|
||||||
if($eq activityType 'createList')
|
|
||||||
| {{_ 'activity-added' list.title boardLabel}}.
|
|
||||||
|
|
||||||
if($eq activityType 'createSwimlane')
|
|
||||||
| {{_ 'activity-added' swimlane.title boardLabel}}.
|
|
||||||
|
|
||||||
if($eq activityType 'removeList')
|
|
||||||
| {{_ 'activity-removed' title boardLabel}}.
|
|
||||||
|
|
||||||
if($eq activityType 'importBoard')
|
|
||||||
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'importCard')
|
|
||||||
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'importList')
|
|
||||||
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'joinMember')
|
|
||||||
if($eq user._id member._id)
|
|
||||||
| {{{_ 'activity-joined' cardLink}}}.
|
|
||||||
else
|
|
||||||
| {{{_ 'activity-added' memberLink cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'moveCardBoard')
|
|
||||||
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'moveCard')
|
|
||||||
| {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'removeBoardMember')
|
|
||||||
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'restoredCard')
|
|
||||||
| {{{_ 'activity-sent' cardLink boardLabel}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addedLabel')
|
|
||||||
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'removedLabel')
|
|
||||||
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'setCustomField')
|
|
||||||
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'unsetCustomField')
|
|
||||||
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
|
|
||||||
|
|
||||||
if($eq activityType 'unjoinMember')
|
|
||||||
if($eq user._id member._id)
|
|
||||||
| {{{_ 'activity-unjoined' cardLink}}}.
|
|
||||||
else
|
|
||||||
| {{{_ 'activity-removed' memberLink cardLink}}}.
|
|
||||||
|
|
||||||
span(title=createdAt).activity-meta {{ moment createdAt }}
|
|
||||||
|
|
||||||
template(name="cardActivities")
|
template(name="cardActivities")
|
||||||
each currentCard.activities
|
each activityData in currentCard.activities
|
||||||
.activity
|
+activity(activity=activityData card=card mode=mode)
|
||||||
+userAvatar(userId=user._id)
|
|
||||||
p.activity-desc
|
|
||||||
+memberName(user=user)
|
|
||||||
if($eq activityType 'createCard')
|
|
||||||
| {{_ 'activity-added' cardLabel listName}}.
|
|
||||||
if($eq activityType 'importCard')
|
|
||||||
| {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
|
|
||||||
if($eq activityType 'joinMember')
|
|
||||||
if($eq user._id member._id)
|
|
||||||
| {{_ 'activity-joined' cardLabel}}.
|
|
||||||
else
|
|
||||||
| {{{_ 'activity-added' memberLink cardLabel}}}.
|
|
||||||
if($eq activityType 'unjoinMember')
|
|
||||||
if($eq user._id member._id)
|
|
||||||
| {{_ 'activity-unjoined' cardLabel}}.
|
|
||||||
else
|
|
||||||
| {{{_ 'activity-removed' cardLabel memberLink}}}.
|
|
||||||
if($eq activityType 'archivedCard')
|
|
||||||
| {{_ 'activity-archived' cardLabel}}.
|
|
||||||
|
|
||||||
if($eq activityType 'addedLabel')
|
template(name="activity")
|
||||||
| {{{_ 'activity-added-label-card' lastLabel }}}.
|
.activity
|
||||||
|
+userAvatar(userId=activity.user._id)
|
||||||
|
p.activity-desc
|
||||||
|
+memberName(user=activity.user)
|
||||||
|
|
||||||
if($eq activityType 'removedLabel')
|
//- attachment activity -------------------------------------------------
|
||||||
| {{{_ 'activity-removed-label-card' lastLabel }}}.
|
if($eq activity.activityType 'deleteAttachment')
|
||||||
|
| {{{_ 'activity-delete-attach' cardLink}}}.
|
||||||
|
|
||||||
if($eq activityType 'removeChecklist')
|
if($eq activity.activityType 'addAttachment')
|
||||||
| {{{_ 'activity-checklist-removed' cardLabel}}}.
|
| {{{_ 'activity-attached' attachmentLink cardLink}}}.
|
||||||
|
if($neq mode 'board')
|
||||||
|
if activity.attachment.isImage
|
||||||
|
img.attachment-image-preview(src=activity.attachment.url)
|
||||||
|
|
||||||
if($eq activityType 'checkedItem')
|
//- board activity ------------------------------------------------------
|
||||||
| {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
|
if($eq mode 'board')
|
||||||
|
if($eq activity.activityType 'createBoard')
|
||||||
|
| {{_ 'activity-created' boardLabel}}.
|
||||||
|
|
||||||
if($eq activityType 'uncheckedItem')
|
if($eq activity.activityType 'importBoard')
|
||||||
| {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
|
| {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
|
||||||
|
|
||||||
if($eq activityType 'checklistCompleted')
|
if($eq activity.activityType 'addBoardMember')
|
||||||
| {{{_ 'activity-checklist-completed-card' checklist.title }}}.
|
| {{{_ 'activity-added' memberLink boardLabel}}}.
|
||||||
|
|
||||||
if($eq activityType 'checklistUncompleted')
|
if($eq activity.activityType 'removeBoardMember')
|
||||||
| {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
|
| {{{_ 'activity-excluded' memberLink boardLabel}}}.
|
||||||
|
|
||||||
if($eq activityType 'restoredCard')
|
//- card activity -------------------------------------------------------
|
||||||
| {{_ 'activity-sent' cardLabel boardLabel}}.
|
if($eq activity.activityType 'createCard')
|
||||||
if($eq activityType 'moveCard')
|
if($eq mode 'card')
|
||||||
| {{_ 'activity-moved' cardLabel oldList.title list.title}}.
|
| {{{_ 'activity-added' cardLabel activity.listName}}}.
|
||||||
|
else
|
||||||
|
| {{{_ 'activity-added' cardLabel boardLabel}}}.
|
||||||
|
|
||||||
if($eq activityType 'moveCardBoard')
|
if($eq activity.activityType 'importCard')
|
||||||
| {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
|
| {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
|
||||||
|
|
||||||
if($eq activityType 'addAttachment')
|
if($eq activity.activityType 'moveCard')
|
||||||
| {{{_ 'activity-attached' attachmentLink cardLabel}}}.
|
| {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}.
|
||||||
if attachment.isImage
|
|
||||||
img.attachment-image-preview(src=attachment.url)
|
if($eq activity.activityType 'moveCardBoard')
|
||||||
if($eq activityType 'deleteAttachment')
|
| {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}.
|
||||||
| {{{_ 'activity-delete-attach' cardLabel}}}.
|
|
||||||
if($eq activityType 'removedChecklist')
|
if($eq activity.activityType 'archivedCard')
|
||||||
| {{{_ 'activity-checklist-removed' cardLabel}}}.
|
| {{{_ 'activity-archived' cardLink}}}.
|
||||||
if($eq activityType 'addChecklist')
|
|
||||||
| {{{_ 'activity-checklist-added' cardLabel}}}.
|
if($eq activity.activityType 'restoredCard')
|
||||||
|
| {{{_ 'activity-sent' cardLink boardLabel}}}.
|
||||||
|
|
||||||
|
//- checklist activity --------------------------------------------------
|
||||||
|
if($eq activity.activityType 'addChecklist')
|
||||||
|
| {{{_ 'activity-checklist-added' cardLink}}}.
|
||||||
|
if($eq mode 'card')
|
||||||
.activity-checklist
|
.activity-checklist
|
||||||
+viewer
|
+viewer
|
||||||
= checklist.title
|
= activity.checklist.title
|
||||||
if($eq activityType 'addChecklistItem')
|
else
|
||||||
| {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
|
a.activity-checklist(href="{{ activity.card.absoluteUrl }}")
|
||||||
.activity-checklist(href="{{ card.absoluteUrl }}")
|
|
||||||
+viewer
|
+viewer
|
||||||
= checklistItem.title
|
= activity.checklist.title
|
||||||
|
|
||||||
if(currentData.timeKey)
|
if($eq activity.activityType 'removedChecklist')
|
||||||
| {{{_ activityType }}}
|
| {{{_ 'activity-checklist-removed' cardLink}}}.
|
||||||
= ' '
|
|
||||||
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
|
|
||||||
if (currentData.timeOldValue)
|
|
||||||
= ' '
|
|
||||||
| {{{_ "previous_as" }}}
|
|
||||||
= ' '
|
|
||||||
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
|
|
||||||
= ' @'
|
|
||||||
else if(currentData.timeValue)
|
|
||||||
| {{{_ activityType currentData.timeValue}}}
|
|
||||||
|
|
||||||
|
if($eq activity.activityType 'completeChecklist')
|
||||||
|
| {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}.
|
||||||
|
|
||||||
if($eq activityType 'deleteComment')
|
if($eq activity.activityType 'uncompleteChecklist')
|
||||||
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
|
| {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}.
|
||||||
if($eq activityType 'editComment')
|
|
||||||
| {{{_ 'activity-editComment' currentData.commentId}}}.
|
if($eq activity.activityType 'checkedItem')
|
||||||
if($eq activityType 'addComment')
|
| {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'uncheckedItem')
|
||||||
|
| {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'addChecklistItem')
|
||||||
|
| {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}.
|
||||||
|
.activity-checklist(href="{{ activity.card.absoluteUrl }}")
|
||||||
|
+viewer
|
||||||
|
= activity.checklistItem.title
|
||||||
|
|
||||||
|
if($eq activity.activityType 'removedChecklistItem')
|
||||||
|
| {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}.
|
||||||
|
|
||||||
|
//- comment activity ----------------------------------------------------
|
||||||
|
if($eq mode 'card')
|
||||||
|
//- if we are in card mode we display the comment in a way that it
|
||||||
|
//- can be edited by the owner
|
||||||
|
if($eq activity.activityType 'addComment')
|
||||||
+inlinedForm(classNames='js-edit-comment')
|
+inlinedForm(classNames='js-edit-comment')
|
||||||
+editor(autofocus=true)
|
+editor(autofocus=true)
|
||||||
= comment.text
|
= activity.comment.text
|
||||||
.edit-controls
|
.edit-controls
|
||||||
button.primary(type="submit") {{_ 'edit'}}
|
button.primary(type="submit") {{_ 'edit'}}
|
||||||
else
|
else
|
||||||
.activity-comment
|
.activity-comment
|
||||||
+viewer
|
+viewer
|
||||||
= comment.text
|
= activity.comment.text
|
||||||
span(title=createdAt).activity-meta {{ moment createdAt }}
|
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
|
||||||
if ($eq currentUser._id comment.userId)
|
if ($eq currentUser._id activity.comment.userId)
|
||||||
= ' - '
|
= ' - '
|
||||||
a.js-open-inlined-form {{_ "edit"}}
|
a.js-open-inlined-form {{_ "edit"}}
|
||||||
= ' - '
|
= ' - '
|
||||||
a.js-delete-comment {{_ "delete"}}
|
a.js-delete-comment {{_ "delete"}}
|
||||||
|
|
||||||
|
if($eq activity.activityType 'deleteComment')
|
||||||
|
| {{{_ 'activity-deleteComment' currentData.commentId}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'editComment')
|
||||||
|
| {{{_ 'activity-editComment' currentData.commentId}}}.
|
||||||
|
else
|
||||||
|
//- if we are not in card mode we only display a summary of the comment
|
||||||
|
if($eq activity.activityType 'addComment')
|
||||||
|
| {{{_ 'activity-on' cardLink}}}
|
||||||
|
a.activity-comment(href="{{ activity.card.absoluteUrl }}")
|
||||||
|
+viewer
|
||||||
|
= activity.comment.text
|
||||||
|
|
||||||
|
//- customField activity ------------------------------------------------
|
||||||
|
if($eq mode 'board')
|
||||||
|
if($eq activity.activityType 'createCustomField')
|
||||||
|
| {{_ 'activity-customfield-created' customField}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'setCustomField')
|
||||||
|
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'unsetCustomField')
|
||||||
|
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
|
||||||
|
|
||||||
|
//- label activity ------------------------------------------------------
|
||||||
|
if($eq activity.activityType 'addedLabel')
|
||||||
|
| {{{_ 'activity-added-label' lastLabel cardLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'removedLabel')
|
||||||
|
| {{{_ 'activity-removed-label' lastLabel cardLink}}}.
|
||||||
|
|
||||||
|
//- list activity -------------------------------------------------------
|
||||||
|
if($neq mode 'card')
|
||||||
|
if($eq activity.activityType 'createList')
|
||||||
|
| {{{_ 'activity-added' listLabel boardLabel}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'importList')
|
||||||
|
| {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'removeList')
|
||||||
|
| {{{_ 'activity-removed' activity.title boardLabel}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'archivedList')
|
||||||
|
| {{_ 'activity-archived' listLabel}}.
|
||||||
|
|
||||||
|
//- member activity ----------------------------------------------------
|
||||||
|
if($eq activity.activityType 'joinMember')
|
||||||
|
if($eq user._id activity.member._id)
|
||||||
|
| {{{_ 'activity-joined' cardLink}}}.
|
||||||
else
|
else
|
||||||
span(title=createdAt).activity-meta {{ moment createdAt }}
|
| {{{_ 'activity-added' memberLink cardLink}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'unjoinMember')
|
||||||
|
if($eq user._id activity.member._id)
|
||||||
|
| {{{_ 'activity-unjoined' cardLink}}}.
|
||||||
|
else
|
||||||
|
| {{{_ 'activity-removed' memberLink cardLink}}}.
|
||||||
|
|
||||||
|
//- swimlane activity --------------------------------------------------
|
||||||
|
if($neq mode 'card')
|
||||||
|
if($eq activity.activityType 'createSwimlane')
|
||||||
|
| {{{_ 'activity-added' activity.swimlane.title boardLabel}}}.
|
||||||
|
|
||||||
|
if($eq activity.activityType 'archivedSwimlane')
|
||||||
|
| {{_ 'activity-archived' activity.swimlane.title}}.
|
||||||
|
|
||||||
|
|
||||||
|
//- I don't understand this part ----------------------------------------
|
||||||
|
if(currentData.timeKey)
|
||||||
|
| {{{_ activity.activityType }}}
|
||||||
|
= ' '
|
||||||
|
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
|
||||||
|
if (currentData.timeOldValue)
|
||||||
|
= ' '
|
||||||
|
| {{{_ "previous_as" }}}
|
||||||
|
= ' '
|
||||||
|
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
|
||||||
|
= ' @'
|
||||||
|
else if(currentData.timeValue)
|
||||||
|
| {{{_ activity.activityType currentData.timeValue}}}
|
||||||
|
|
||||||
|
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ BlazeComponent.extendComponent({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
}).register('activities');
|
||||||
|
|
||||||
|
BlazeComponent.extendComponent({
|
||||||
loadNextPage() {
|
loadNextPage() {
|
||||||
if (this.loadNextPageLocked === false) {
|
if (this.loadNextPageLocked === false) {
|
||||||
this.page.set(this.page.get() + 1);
|
this.page.set(this.page.get() + 1);
|
||||||
|
|
@ -50,41 +52,37 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
checkItem() {
|
checkItem() {
|
||||||
const checkItemId = this.currentData().checklistItemId;
|
const checkItemId = this.currentData().activity.checklistItemId;
|
||||||
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
|
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
|
||||||
return checkItem.title;
|
return checkItem && checkItem.title;
|
||||||
},
|
},
|
||||||
|
|
||||||
boardLabel() {
|
boardLabel() {
|
||||||
|
const data = this.currentData();
|
||||||
|
if (data.mode !== 'board') {
|
||||||
|
return createBoardLink(data.activity.board(), data.activity.listName);
|
||||||
|
}
|
||||||
return TAPi18n.__('this-board');
|
return TAPi18n.__('this-board');
|
||||||
},
|
},
|
||||||
|
|
||||||
cardLabel() {
|
cardLabel() {
|
||||||
|
const data = this.currentData();
|
||||||
|
if (data.mode !== 'card') {
|
||||||
|
return createCardLink(this.currentData().activity.card());
|
||||||
|
}
|
||||||
return TAPi18n.__('this-card');
|
return TAPi18n.__('this-card');
|
||||||
},
|
},
|
||||||
|
|
||||||
cardLink() {
|
cardLink() {
|
||||||
const card = this.currentData().card();
|
return createCardLink(this.currentData().activity.card());
|
||||||
return (
|
|
||||||
card &&
|
|
||||||
Blaze.toHTML(
|
|
||||||
HTML.A(
|
|
||||||
{
|
|
||||||
href: card.absoluteUrl(),
|
|
||||||
class: 'action-card',
|
|
||||||
},
|
|
||||||
card.title,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
lastLabel() {
|
lastLabel() {
|
||||||
const lastLabelId = this.currentData().labelId;
|
const lastLabelId = this.currentData().activity.labelId;
|
||||||
if (!lastLabelId) return null;
|
if (!lastLabelId) return null;
|
||||||
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
|
const lastLabel = Boards.findOne(
|
||||||
lastLabelId,
|
this.currentData().activity.boardId,
|
||||||
);
|
).getLabelById(lastLabelId);
|
||||||
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
|
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
|
||||||
return lastLabel.color;
|
return lastLabel.color;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -94,7 +92,7 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
lastCustomField() {
|
lastCustomField() {
|
||||||
const lastCustomField = CustomFields.findOne(
|
const lastCustomField = CustomFields.findOne(
|
||||||
this.currentData().customFieldId,
|
this.currentData().activity.customFieldId,
|
||||||
);
|
);
|
||||||
if (!lastCustomField) return null;
|
if (!lastCustomField) return null;
|
||||||
return lastCustomField.name;
|
return lastCustomField.name;
|
||||||
|
|
@ -102,10 +100,10 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
lastCustomFieldValue() {
|
lastCustomFieldValue() {
|
||||||
const lastCustomField = CustomFields.findOne(
|
const lastCustomField = CustomFields.findOne(
|
||||||
this.currentData().customFieldId,
|
this.currentData().activity.customFieldId,
|
||||||
);
|
);
|
||||||
if (!lastCustomField) return null;
|
if (!lastCustomField) return null;
|
||||||
const value = this.currentData().value;
|
const value = this.currentData().activity.value;
|
||||||
if (
|
if (
|
||||||
lastCustomField.settings.dropdownItems &&
|
lastCustomField.settings.dropdownItems &&
|
||||||
lastCustomField.settings.dropdownItems.length > 0
|
lastCustomField.settings.dropdownItems.length > 0
|
||||||
|
|
@ -122,11 +120,13 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
listLabel() {
|
listLabel() {
|
||||||
return this.currentData().list().title;
|
const activity = this.currentData().activity;
|
||||||
|
const list = activity.list();
|
||||||
|
return (list && list.title) || activity.title;
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceLink() {
|
sourceLink() {
|
||||||
const source = this.currentData().source;
|
const source = this.currentData().activity.source;
|
||||||
if (source) {
|
if (source) {
|
||||||
if (source.url) {
|
if (source.url) {
|
||||||
return Blaze.toHTML(
|
return Blaze.toHTML(
|
||||||
|
|
@ -146,30 +146,31 @@ BlazeComponent.extendComponent({
|
||||||
|
|
||||||
memberLink() {
|
memberLink() {
|
||||||
return Blaze.toHTMLWithData(Template.memberName, {
|
return Blaze.toHTMLWithData(Template.memberName, {
|
||||||
user: this.currentData().member(),
|
user: this.currentData().activity.member(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
attachmentLink() {
|
attachmentLink() {
|
||||||
const attachment = this.currentData().attachment();
|
const attachment = this.currentData().activity.attachment();
|
||||||
// trying to display url before file is stored generates js errors
|
// trying to display url before file is stored generates js errors
|
||||||
return (
|
return (
|
||||||
attachment &&
|
(attachment &&
|
||||||
attachment.url({ download: true }) &&
|
attachment.url({ download: true }) &&
|
||||||
Blaze.toHTML(
|
Blaze.toHTML(
|
||||||
HTML.A(
|
HTML.A(
|
||||||
{
|
{
|
||||||
href: attachment.url({ download: true }),
|
href: attachment.url({ download: true }),
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
},
|
},
|
||||||
attachment.name(),
|
attachment.name(),
|
||||||
),
|
),
|
||||||
)
|
)) ||
|
||||||
|
this.currentData().activity.attachmentName
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
customField() {
|
customField() {
|
||||||
const customField = this.currentData().customField();
|
const customField = this.currentData().activity.customField();
|
||||||
if (!customField) return null;
|
if (!customField) return null;
|
||||||
return customField.name;
|
return customField.name;
|
||||||
},
|
},
|
||||||
|
|
@ -199,4 +200,36 @@ BlazeComponent.extendComponent({
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
}).register('activities');
|
}).register('activity');
|
||||||
|
|
||||||
|
function createCardLink(card) {
|
||||||
|
return (
|
||||||
|
card &&
|
||||||
|
Blaze.toHTML(
|
||||||
|
HTML.A(
|
||||||
|
{
|
||||||
|
href: card.absoluteUrl(),
|
||||||
|
class: 'action-card',
|
||||||
|
},
|
||||||
|
card.title,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBoardLink(board, list) {
|
||||||
|
let text = board.title;
|
||||||
|
if (list) text += `: ${list}`;
|
||||||
|
return (
|
||||||
|
board &&
|
||||||
|
Blaze.toHTML(
|
||||||
|
HTML.A(
|
||||||
|
{
|
||||||
|
href: board.absoluteUrl(),
|
||||||
|
class: 'action-board',
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ template(name="header")
|
||||||
a#header-new-board-icon.js-create-board
|
a#header-new-board-icon.js-create-board
|
||||||
i.fa.fa-plus(title="Create a new board")
|
i.fa.fa-plus(title="Create a new board")
|
||||||
|
|
||||||
|
+notifications
|
||||||
|
|
||||||
+headerUserBar
|
+headerUserBar
|
||||||
|
|
||||||
#header(class=currentBoard.colorClass)
|
#header(class=currentBoard.colorClass)
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
height: 28px
|
height: 28px
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
display: flex
|
display: flex
|
||||||
z-index: 17
|
z-index: 21
|
||||||
|
|
||||||
#header-user-bar,
|
#header-user-bar,
|
||||||
#header-new-board-icon,
|
#header-new-board-icon,
|
||||||
|
|
|
||||||
10
client/components/notifications/notification.jade
Normal file
10
client/components/notifications/notification.jade
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
template(name='notification')
|
||||||
|
li.notification(class="{{#if read}}read{{/if}}")
|
||||||
|
.read-status
|
||||||
|
.materialCheckBox(class="{{#if read}}is-checked{{/if}}")
|
||||||
|
+notificationIcon(activityData)
|
||||||
|
.details
|
||||||
|
+activity(activity=activityData mode='none')
|
||||||
|
if read
|
||||||
|
.remove
|
||||||
|
a.fa.fa-trash
|
||||||
28
client/components/notifications/notification.js
Normal file
28
client/components/notifications/notification.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
Template.notification.events({
|
||||||
|
'click .read-status .materialCheckBox'() {
|
||||||
|
const update = {};
|
||||||
|
update[`profile.notifications.${this.index}.read`] = this.read
|
||||||
|
? null
|
||||||
|
: Date.now();
|
||||||
|
Users.update(Meteor.userId(), { $set: update });
|
||||||
|
},
|
||||||
|
'click .remove a'() {
|
||||||
|
Meteor.user().removeNotification(this.activityData._id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.notification.helpers({
|
||||||
|
mode: 'board',
|
||||||
|
isOfActivityType(activityId, type) {
|
||||||
|
const activity = Activities.findOne(activityId);
|
||||||
|
return activity && activity.activityType === type;
|
||||||
|
},
|
||||||
|
activityType(activityId) {
|
||||||
|
const activity = Activities.findOne(activityId);
|
||||||
|
return activity ? activity.activityType : '';
|
||||||
|
},
|
||||||
|
activityUser(activityId) {
|
||||||
|
const activity = Activities.findOne(activityId);
|
||||||
|
return activity && activity.userId;
|
||||||
|
},
|
||||||
|
});
|
||||||
57
client/components/notifications/notification.styl
Normal file
57
client/components/notifications/notification.styl
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#notifications-drawer
|
||||||
|
&.show-read .notification.read
|
||||||
|
display: flex
|
||||||
|
|
||||||
|
.notification
|
||||||
|
display: flex
|
||||||
|
float: none
|
||||||
|
padding: 12px 8px 8px
|
||||||
|
color: black
|
||||||
|
border-bottom: 1px solid #dbdbdb
|
||||||
|
|
||||||
|
&.read
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.read-status
|
||||||
|
width: 30px
|
||||||
|
|
||||||
|
input
|
||||||
|
width: 24px
|
||||||
|
height: 24px
|
||||||
|
|
||||||
|
.activity-type
|
||||||
|
margin: 16px 0 0
|
||||||
|
width: 17px
|
||||||
|
height: 17px
|
||||||
|
font-size: 17px
|
||||||
|
display: block
|
||||||
|
color: #bbb
|
||||||
|
|
||||||
|
.details
|
||||||
|
width: calc(100% - 30px)
|
||||||
|
|
||||||
|
.activity
|
||||||
|
display: flex
|
||||||
|
|
||||||
|
.activity-desc
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.activity-comment
|
||||||
|
display: block
|
||||||
|
width: 100%
|
||||||
|
border-radius: 3px
|
||||||
|
background: #fff
|
||||||
|
text-decoration: none
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.2)
|
||||||
|
margin-top: 5px
|
||||||
|
padding: 5px
|
||||||
|
|
||||||
|
.activity-meta
|
||||||
|
display: block
|
||||||
|
font-size: 0.8em
|
||||||
|
color: #999
|
||||||
|
font-style: italic
|
||||||
|
|
||||||
|
.remove
|
||||||
|
a:hover
|
||||||
|
color #eb4646 !important
|
||||||
53
client/components/notifications/notificationIcon.jade
Normal file
53
client/components/notifications/notificationIcon.jade
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
template(name='notificationIcon')
|
||||||
|
if($in activityType 'deleteAttachment' 'addAttachment')
|
||||||
|
i.fa.fa-paperclip.activity-type(title="attachment")
|
||||||
|
else if($in activityType 'createBoard' 'importBoard')
|
||||||
|
i.fa.fa-chalkboard.activity-type(title="board")
|
||||||
|
|
||||||
|
else if($in activityType 'createCard' 'importCard' 'moveCard')
|
||||||
|
+cardNotificationIcon
|
||||||
|
else if($in activityType 'moveCardBoard' 'archivedCard' 'restoredCard')
|
||||||
|
+cardNotificationIcon
|
||||||
|
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
|
||||||
|
//- DRY and consistant
|
||||||
|
|
||||||
|
else if($in activityType 'addChecklist' 'removedChecklist' 'completeChecklist')
|
||||||
|
+checklistNotificationIcon
|
||||||
|
else if($in activityType 'uncompleteChecklist')
|
||||||
|
+checklistNotificationIcon
|
||||||
|
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
|
||||||
|
//- DRY and consistant
|
||||||
|
|
||||||
|
else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
|
||||||
|
i.fa.fa-check-square.activity-type(title="checklist item")
|
||||||
|
else if($in activityType 'addComment')
|
||||||
|
i.fa.fa-comment-o.activity-type(title="comment")
|
||||||
|
else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
|
||||||
|
i.fa.fa-code.activity-type(title="custom field")
|
||||||
|
else if($in activityType 'addedLabel' 'removedLabel')
|
||||||
|
i.fa.fa-tag.activity-type(title="label")
|
||||||
|
|
||||||
|
else if($in activityType 'createList' 'removeList' 'archivedList')
|
||||||
|
+listNotificationIcon
|
||||||
|
else if($in activityType 'importList')
|
||||||
|
+listNotificationIcon
|
||||||
|
//- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
|
||||||
|
//- DRY and consistant
|
||||||
|
|
||||||
|
//- elswhere in the app we use fa-trello to indicate lists...
|
||||||
|
//- i personally like fa-columns a bit better
|
||||||
|
else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
|
||||||
|
i.fa.fa-user.activity-type(title="member")
|
||||||
|
else if($in activityType 'createSwimlane' 'archivedSwimlane')
|
||||||
|
i.fa.fa-th-large.activity-type(title="swimlane")
|
||||||
|
else
|
||||||
|
i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
|
||||||
|
|
||||||
|
template(name='cardNotificationIcon')
|
||||||
|
i.fa.fa-clone.activity-type(title="card")
|
||||||
|
|
||||||
|
template(name='checklistNotificationIcon')
|
||||||
|
i.fa.fa-list.activity-type(title="checklist")
|
||||||
|
|
||||||
|
template(name='listNotificationIcon')
|
||||||
|
i.fa.fa-columns.activity-type(title="list")
|
||||||
5
client/components/notifications/notifications.jade
Normal file
5
client/components/notifications/notifications.jade
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
template(name='notifications')
|
||||||
|
#notifications.board-header-btns.right
|
||||||
|
a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}")
|
||||||
|
if $.Session.get 'showNotificationsDrawer'
|
||||||
|
+notificationsDrawer(unreadNotifications=unreadNotifications)
|
||||||
32
client/components/notifications/notifications.js
Normal file
32
client/components/notifications/notifications.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// this hides the notifications drawer if anyone clicks off of the panel
|
||||||
|
Template.body.events({
|
||||||
|
click(event) {
|
||||||
|
if (
|
||||||
|
!$(event.target).is('#notifications *') &&
|
||||||
|
Session.get('showNotificationsDrawer')
|
||||||
|
) {
|
||||||
|
toggleNotificationsDrawer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.notifications.helpers({
|
||||||
|
unreadNotifications() {
|
||||||
|
const notifications = Users.findOne(Meteor.userId()).notifications();
|
||||||
|
const unreadNotifications = _.filter(notifications, v => !v.read);
|
||||||
|
return unreadNotifications.length;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.notifications.events({
|
||||||
|
'click .notifications-drawer-toggle'() {
|
||||||
|
toggleNotificationsDrawer();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function toggleNotificationsDrawer() {
|
||||||
|
Session.set(
|
||||||
|
'showNotificationsDrawer',
|
||||||
|
!Session.get('showNotificationsDrawer'),
|
||||||
|
);
|
||||||
|
}
|
||||||
17
client/components/notifications/notifications.styl
Normal file
17
client/components/notifications/notifications.styl
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#notifications
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
.notifications-drawer-toggle
|
||||||
|
display: block
|
||||||
|
line-height: 28px
|
||||||
|
color: #f2f2f2
|
||||||
|
margin: 0 10px
|
||||||
|
width: 28px
|
||||||
|
height: 28px
|
||||||
|
text-align: center
|
||||||
|
border: 0
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
&.alert
|
||||||
|
background-color: #eb4646;
|
||||||
|
|
||||||
16
client/components/notifications/notificationsDrawer.jade
Normal file
16
client/components/notifications/notificationsDrawer.jade
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
template(name='notificationsDrawer')
|
||||||
|
section#notifications-drawer(class="{{#if $.Session.get 'showReadNotifications'}}show-read{{/if}}")
|
||||||
|
.header
|
||||||
|
if $.Session.get 'showReadNotifications'
|
||||||
|
a.toggle-read Filter by Unread
|
||||||
|
else
|
||||||
|
a.toggle-read View All
|
||||||
|
h5 Notifications
|
||||||
|
if($gt unreadNotifications 0)
|
||||||
|
|(#{unreadNotifications})
|
||||||
|
a.fa.fa-times-thin.close
|
||||||
|
ul.notifications
|
||||||
|
each transformedProfile.notifications
|
||||||
|
+notification(activityData=activity index=dbIndex read=read)
|
||||||
|
if($gt unreadNotifications 0)
|
||||||
|
a.all-read Mark all as read
|
||||||
38
client/components/notifications/notificationsDrawer.js
Normal file
38
client/components/notifications/notificationsDrawer.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { toggleNotificationsDrawer } from './notifications.js';
|
||||||
|
|
||||||
|
Template.notificationsDrawer.onCreated(function() {
|
||||||
|
Meteor.subscribe('notificationActivities');
|
||||||
|
Meteor.subscribe('notificationCards');
|
||||||
|
Meteor.subscribe('notificationUsers');
|
||||||
|
Meteor.subscribe('notificationsAttachments');
|
||||||
|
Meteor.subscribe('notificationChecklistItems');
|
||||||
|
Meteor.subscribe('notificationChecklists');
|
||||||
|
Meteor.subscribe('notificationComments');
|
||||||
|
Meteor.subscribe('notificationLists');
|
||||||
|
Meteor.subscribe('notificationSwimlanes');
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.notificationsDrawer.helpers({
|
||||||
|
transformedProfile() {
|
||||||
|
return Users.findOne(Meteor.userId());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.notificationsDrawer.events({
|
||||||
|
'click .all-read'() {
|
||||||
|
const notifications = Meteor.user().profile.notifications;
|
||||||
|
for (const index in notifications) {
|
||||||
|
if (notifications.hasOwnProperty(index) && !notifications[index].read) {
|
||||||
|
const update = {};
|
||||||
|
update[`profile.notifications.${index}.read`] = Date.now();
|
||||||
|
Users.update(Meteor.userId(), { $set: update });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'click .close'() {
|
||||||
|
toggleNotificationsDrawer();
|
||||||
|
},
|
||||||
|
'click .toggle-read'() {
|
||||||
|
Session.set('showReadNotifications', !Session.get('showReadNotifications'));
|
||||||
|
},
|
||||||
|
});
|
||||||
57
client/components/notifications/notificationsDrawer.styl
Normal file
57
client/components/notifications/notificationsDrawer.styl
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
belize = #2980b9
|
||||||
|
|
||||||
|
section#notifications-drawer
|
||||||
|
position: fixed
|
||||||
|
top: 28px
|
||||||
|
right: 0
|
||||||
|
width: 400px
|
||||||
|
background-color: #fafafa
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.15)
|
||||||
|
border-radius: 2px
|
||||||
|
max-height: calc(100vh - 28px - 36px)
|
||||||
|
color: black
|
||||||
|
padding-top 36px;
|
||||||
|
overflow: scroll
|
||||||
|
|
||||||
|
a:hover
|
||||||
|
color: belize !important
|
||||||
|
|
||||||
|
.header
|
||||||
|
position: fixed
|
||||||
|
top 28px
|
||||||
|
right 0
|
||||||
|
width calc(400px - 32px)
|
||||||
|
padding: 8px 16px
|
||||||
|
background: #ededed
|
||||||
|
border-bottom: 1px solid #dbdbdb
|
||||||
|
z-index 2
|
||||||
|
|
||||||
|
.toggle-read
|
||||||
|
position absolute
|
||||||
|
left 16px
|
||||||
|
top calc(50% - 8px)
|
||||||
|
color belize
|
||||||
|
|
||||||
|
h5
|
||||||
|
text-align: center
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
.close
|
||||||
|
position: absolute
|
||||||
|
top: calc(50% - 12px)
|
||||||
|
right: 12px
|
||||||
|
font-size: 24px
|
||||||
|
height: 24px
|
||||||
|
line-height: 24px
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.all-read
|
||||||
|
color belize
|
||||||
|
background-color: #fafafa
|
||||||
|
margin 8px 16px 12px
|
||||||
|
display inline-block
|
||||||
|
|
||||||
|
ul.notifications
|
||||||
|
display: block
|
||||||
|
padding: 0px 16px
|
||||||
|
margin: 0
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
"activity-unchecked-item": "unchecked %s in checklist %s of %s",
|
"activity-unchecked-item": "unchecked %s in checklist %s of %s",
|
||||||
"activity-checklist-added": "added checklist to %s",
|
"activity-checklist-added": "added checklist to %s",
|
||||||
"activity-checklist-removed": "removed a checklist from %s",
|
"activity-checklist-removed": "removed a checklist from %s",
|
||||||
"activity-checklist-completed": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
|
"activity-checklist-completed": "completed checklist %s of %s",
|
||||||
"activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
|
"activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
|
||||||
"activity-checklist-item-added": "added checklist item to '%s' in %s",
|
"activity-checklist-item-added": "added checklist item to '%s' in %s",
|
||||||
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
|
"activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@ if (Meteor.isServer) {
|
||||||
type: 'card',
|
type: 'card',
|
||||||
activityType: 'addAttachment',
|
activityType: 'addAttachment',
|
||||||
attachmentId: doc._id,
|
attachmentId: doc._id,
|
||||||
|
// this preserves the name so that notifications can be meaningful after
|
||||||
|
// this file is removed
|
||||||
|
attachmentName: doc.original.name,
|
||||||
boardId: doc.boardId,
|
boardId: doc.boardId,
|
||||||
cardId: doc.cardId,
|
cardId: doc.cardId,
|
||||||
listId: doc.listId,
|
listId: doc.listId,
|
||||||
|
|
@ -246,18 +249,15 @@ if (Meteor.isServer) {
|
||||||
type: 'card',
|
type: 'card',
|
||||||
activityType: 'deleteAttachment',
|
activityType: 'deleteAttachment',
|
||||||
attachmentId: doc._id,
|
attachmentId: doc._id,
|
||||||
|
// this preserves the name so that notifications can be meaningful after
|
||||||
|
// this file is removed
|
||||||
|
attachmentName: doc.original.name,
|
||||||
boardId: doc.boardId,
|
boardId: doc.boardId,
|
||||||
cardId: doc.cardId,
|
cardId: doc.cardId,
|
||||||
listId: doc.listId,
|
listId: doc.listId,
|
||||||
swimlaneId: doc.swimlaneId,
|
swimlaneId: doc.swimlaneId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Attachments.files.after.remove((userId, doc) => {
|
|
||||||
Activities.remove({
|
|
||||||
attachmentId: doc._id,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Attachments;
|
export default Attachments;
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,9 @@ if (Meteor.isServer) {
|
||||||
activityType: 'createList',
|
activityType: 'createList',
|
||||||
boardId: doc.boardId,
|
boardId: doc.boardId,
|
||||||
listId: doc._id,
|
listId: doc._id,
|
||||||
|
// this preserves the name so that the activity can be useful after the
|
||||||
|
// list is deleted
|
||||||
|
title: doc.title,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -397,6 +400,9 @@ if (Meteor.isServer) {
|
||||||
activityType: 'archivedList',
|
activityType: 'archivedList',
|
||||||
listId: doc._id,
|
listId: doc._id,
|
||||||
boardId: doc.boardId,
|
boardId: doc.boardId,
|
||||||
|
// this preserves the name so that the activity can be useful after the
|
||||||
|
// list is deleted
|
||||||
|
title: doc.title,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,20 @@ Users.attachSchema(
|
||||||
/**
|
/**
|
||||||
* enabled notifications for the user
|
* enabled notifications for the user
|
||||||
*/
|
*/
|
||||||
type: [String],
|
type: [Object],
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'profile.notifications.$.activity': {
|
||||||
|
/**
|
||||||
|
* The id of the activity this notification references
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
'profile.notifications.$.read': {
|
||||||
|
/**
|
||||||
|
* the date on which this notification was read
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
'profile.showCardsCountAt': {
|
'profile.showCardsCountAt': {
|
||||||
|
|
@ -429,6 +442,20 @@ Users.helpers({
|
||||||
return _.contains(notifications, activityId);
|
return _.contains(notifications, activityId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
notifications() {
|
||||||
|
const { notifications = [] } = this.profile || {};
|
||||||
|
for (const index in notifications) {
|
||||||
|
if (!notifications.hasOwnProperty(index)) continue;
|
||||||
|
const notification = notifications[index];
|
||||||
|
// this preserves their db sort order for editing
|
||||||
|
notification.dbIndex = index;
|
||||||
|
notification.activity = Activities.findOne(notification.activity);
|
||||||
|
}
|
||||||
|
// this sorts them newest to oldest to match Trello's behavior
|
||||||
|
notifications.reverse();
|
||||||
|
return notifications;
|
||||||
|
},
|
||||||
|
|
||||||
hasShowDesktopDragHandles() {
|
hasShowDesktopDragHandles() {
|
||||||
const profile = this.profile || {};
|
const profile = this.profile || {};
|
||||||
return profile.showDesktopDragHandles || false;
|
return profile.showDesktopDragHandles || false;
|
||||||
|
|
@ -573,7 +600,7 @@ Users.mutations({
|
||||||
addNotification(activityId) {
|
addNotification(activityId) {
|
||||||
return {
|
return {
|
||||||
$addToSet: {
|
$addToSet: {
|
||||||
'profile.notifications': activityId,
|
'profile.notifications': { activity: activityId },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -581,7 +608,7 @@ Users.mutations({
|
||||||
removeNotification(activityId) {
|
removeNotification(activityId) {
|
||||||
return {
|
return {
|
||||||
$pull: {
|
$pull: {
|
||||||
'profile.notifications': activityId,
|
'profile.notifications': { activity: activityId },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
// XXX: add activity id to profile.notifications,
|
Notifications.subscribe('profile', (user, title, description, params) => {
|
||||||
// it can be displayed and rendered on web or mobile UI
|
user.addNotification(params.activityId);
|
||||||
// will uncomment the following code once UI implemented
|
});
|
||||||
//
|
|
||||||
// Notifications.subscribe('profile', (user, title, description, params) => {
|
|
||||||
// user.addNotification(params.activityId);
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
100
server/publications/notifications.js
Normal file
100
server/publications/notifications.js
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// We use these when displaying notifications in the notificationsDrawer
|
||||||
|
|
||||||
|
// gets all activities associated with the current user
|
||||||
|
Meteor.publish('notificationActivities', () => {
|
||||||
|
return activities();
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all attachments associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationAttachments', function() {
|
||||||
|
return Attachments.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.attachmentId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all cards associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationCards', function() {
|
||||||
|
return Cards.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.cardId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all checklistItems associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationChecklistItems', function() {
|
||||||
|
return ChecklistItems.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.checklistItemId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all checklists associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationChecklists', function() {
|
||||||
|
return Checklists.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.checklistId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all comments associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationComments', function() {
|
||||||
|
return CardComments.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.commentId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all lists associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationLists', function() {
|
||||||
|
return Lists.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.listId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all swimlanes associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationSwimlanes', function() {
|
||||||
|
return Swimlanes.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.swimlaneId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// gets all users associated with activities associated with the current user
|
||||||
|
Meteor.publish('notificationUsers', function() {
|
||||||
|
return Users.find({
|
||||||
|
_id: {
|
||||||
|
$in: activities()
|
||||||
|
.map(v => v.userId)
|
||||||
|
.filter(v => !!v),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function activities() {
|
||||||
|
return Activities.find({
|
||||||
|
_id: { $in: Meteor.user().profile.notifications.map(v => v.activity) },
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue