diff options
60 files changed, 3074 insertions, 259 deletions
diff --git a/.DS_Store b/.DS_Store Binary files differnew file mode 100644 index 00000000..06b93641 --- /dev/null +++ b/.DS_Store diff --git a/.meteor/packages b/.meteor/packages index ebe35f81..d428111c 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -6,7 +6,7 @@ meteor-base@1.2.0 # Build system -ecmascript@0.9.0 +ecmascript stylus@2.513.13 standard-minifier-css@1.3.5 standard-minifier-js@2.2.0 diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index d3e3d5ba..bddc4dad 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -14,6 +14,9 @@ template(name="boardActivities") p.activity-desc +memberName(user=user) + if($eq activityType 'deleteAttachment') + | {{{_ 'activity-delete-attach' cardLink}}}. + if($eq activityType 'addAttachment') | {{{_ 'activity-attached' attachmentLink cardLink}}}. @@ -31,12 +34,28 @@ template(name="boardActivities") .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}}}. @@ -89,6 +108,12 @@ template(name="boardActivities") 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 'unjoinMember') if($eq user._id member._id) | {{{_ 'activity-unjoined' cardLink}}}. @@ -119,6 +144,28 @@ template(name="cardActivities") | {{{_ 'activity-removed' cardLabel memberLink}}}. if($eq activityType 'archivedCard') | {{_ 'activity-archived' cardLabel}}. + + if($eq activityType 'addedLabel') + | {{{_ 'activity-added-label-card' lastLabel }}}. + + if($eq activityType 'removedLabel') + | {{{_ 'activity-removed-label-card' lastLabel }}}. + + if($eq activityType 'removeChecklist') + | {{{_ 'activity-checklist-removed' cardLabel}}}. + + if($eq activityType 'checkedItem') + | {{{_ 'activity-checked-item-card' checkItem checklist.title }}}. + + if($eq activityType 'uncheckedItem') + | {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}. + + if($eq activityType 'checklistCompleted') + | {{{_ 'activity-checklist-completed-card' checklist.title }}}. + + if($eq activityType 'checklistUncompleted') + | {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}. + if($eq activityType 'restoredCard') | {{_ 'activity-sent' cardLabel boardLabel}}. if($eq activityType 'moveCard') @@ -127,6 +174,10 @@ template(name="cardActivities") | {{{_ 'activity-attached' attachmentLink cardLabel}}}. if attachment.isImage img.attachment-image-preview(src=attachment.url) + if($eq activityType 'deleteAttachment') + | {{{_ 'activity-delete-attach' cardLabel}}}. + if($eq activityType 'removedChecklist') + | {{{_ 'activity-checklist-removed' cardLabel}}}. if($eq activityType 'addChecklist') | {{{_ 'activity-checklist-added' cardLabel}}}. .activity-checklist diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 25e151fd..6633a91a 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -49,6 +49,12 @@ BlazeComponent.extendComponent({ this.loadNextPageLocked = true; } }, + + checkItem(){ + const checkItemId = this.currentData().checklistItemId; + const checkItem = ChecklistItems.findOne({_id:checkItemId}); + return checkItem.title; + }, boardLabel() { return TAPi18n.__('this-board'); @@ -66,6 +72,16 @@ BlazeComponent.extendComponent({ }, card.title)); }, + lastLabel(){ + const lastLabelId = this.currentData().labelId; + const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId); + if(lastLabel.name == undefined || lastLabel.name == ""){ + return lastLabel.color; + }else{ + return lastLabel.name; + } + }, + listLabel() { return this.currentData().list().title; }, diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 1c6c8f8c..dfd281de 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -88,6 +88,10 @@ template(name="boardHeaderBar") a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") i.fa.fa-times-thin + a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}") + i.fa.fa-magic + span {{_ 'rules'}} + a.board-header-btn.js-open-search-view(title="{{_ 'search'}}") i.fa.fa-search span {{_ 'search'}} @@ -290,6 +294,11 @@ template(name="boardChangeTitlePopup") textarea.js-board-desc= description input.primary.wide(type="submit" value="{{_ 'rename'}}") +template(name="boardCreateRulePopup") + p {{_ 'close-board-pop'}} + button.js-confirm.negate.full(type="submit") {{_ 'archive'}} + + template(name="archiveBoardPopup") p {{_ 'close-board-pop'}} button.js-confirm.negate.full(type="submit") {{_ 'archive'}} diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 2dfd58c1..89f686ab 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -108,6 +108,9 @@ BlazeComponent.extendComponent({ 'click .js-open-search-view'() { Sidebar.setView('search'); }, + 'click .js-open-rules-view'() { + Modal.openWide('rulesMain'); + }, 'click .js-multiselection-activate'() { const currentCard = Session.get('currentCard'); MultiSelection.activate(); diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 5be70b7a..892a6e74 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -1,5 +1,6 @@ @import 'nib' +select, textarea, input:not([type=file]), button diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index d99d9dc8..ce8396b9 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -98,6 +98,8 @@ BlazeComponent.extendComponent({ evt.preventDefault(); Utils.goBoardId(Session.get('currentBoard')); } + console.log(evt) + }, cardIsSelected() { diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade index b0024b33..ac7da3af 100644 --- a/client/components/main/layouts.jade +++ b/client/components/main/layouts.jade @@ -36,11 +36,18 @@ template(name="defaultLayout") if (Modal.isOpen) #modal .overlay - .modal-content - a.modal-close-btn.js-close-modal - i.fa.fa-times-thin - +Template.dynamic(template=Modal.getHeaderName) - +Template.dynamic(template=Modal.getTemplateName) + if (Modal.isWide) + .modal-content-wide.modal-container + a.modal-close-btn.js-close-modal + i.fa.fa-times-thin + +Template.dynamic(template=Modal.getHeaderName) + +Template.dynamic(template=Modal.getTemplateName) + else + .modal-content.modal-container + a.modal-close-btn.js-close-modal + i.fa.fa-times-thin + +Template.dynamic(template=Modal.getHeaderName) + +Template.dynamic(template=Modal.getTemplateName) template(name="notFound") +message(label='page-not-found') diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl index a79ff337..3457a028 100644 --- a/client/components/main/layouts.styl +++ b/client/components/main/layouts.styl @@ -61,6 +61,23 @@ body display: block float: right font-size: 24px + + .modal-content-wide + width: 800px + min-height: 0px + margin: 42px auto + padding: 12px + border-radius: 4px + background: darken(white, 13%) + z-index: 110 + + h2 + margin-bottom: 25px + + .modal-close-btn + display: block + float: right + font-size: 24px h1 font-size: 22px diff --git a/client/components/rules/.DS_Store b/client/components/rules/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/client/components/rules/.DS_Store diff --git a/client/components/rules/actions/boardActions.jade b/client/components/rules/actions/boardActions.jade new file mode 100644 index 00000000..dfeb3d84 --- /dev/null +++ b/client/components/rules/actions/boardActions.jade @@ -0,0 +1,46 @@ +template(name="boardActions") + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-move-card-to'}}} + div.trigger-dropdown + select(id="move-gen-action") + option(value="top") {{{_'r-top-of'}}} + option(value="bottom") {{{_'r-bottom-of'}}} + div.trigger-text + | {{{_'r-its-list'}}} + div.trigger-button.js-add-gen-move-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-move-card-to'}}} + div.trigger-dropdown + select(id="move-spec-action") + option(value="top") {{{_'r-top-of'}}} + option(value="bottom") {{{_'r-bottom-of'}}} + div.trigger-text + | {{{_'r-list'}}} + div.trigger-dropdown + input(id="listName",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-spec-move-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="arch-action") + option(value="archive") {{{_'r-archive'}}} + option(value="unarchive") {{{_'r-unarchive'}}} + div.trigger-text + | {{{_'r-card'}}} + div.trigger-button.js-add-arch-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js new file mode 100644 index 00000000..3eda039f --- /dev/null +++ b/client/components/rules/actions/boardActions.js @@ -0,0 +1,121 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-add-spec-move-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#move-spec-action').value; + const listTitle = this.find('#listName').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "top") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "moveCardToTop", + "listTitle": listTitle, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "bottom") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "moveCardToBottom", + "listTitle": listTitle, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + 'click .js-add-gen-move-action' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#move-gen-action').value; + if (actionSelected == "top") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "moveCardToTop", + "listTitle": "*", + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "bottom") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "moveCardToBottom", + "listTitle": "*", + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + 'click .js-add-arch-action' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#arch-action').value; + if (actionSelected == "archive") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "archive", + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "unarchive") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "unarchive", + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + }]; + }, + +}).register('boardActions');
\ No newline at end of file diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade new file mode 100644 index 00000000..74ad9ab5 --- /dev/null +++ b/client/components/rules/actions/cardActions.jade @@ -0,0 +1,43 @@ +template(name="cardActions") + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="label-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-label'}}} + div.trigger-dropdown + select(id="label-id") + each labels + option(value="#{_id}") + = name + div.trigger-button.js-add-label-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="member-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-member'}}} + div.trigger-dropdown + input(id="member-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-member-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-remove-all'}}} + div.trigger-button.js-add-removeall-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js new file mode 100644 index 00000000..a6e74fe9 --- /dev/null +++ b/client/components/rules/actions/cardActions.js @@ -0,0 +1,119 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + + labels() { + const labels = Boards.findOne(Session.get('currentBoard')).labels; + for (let i = 0; i < labels.length; i++) { + if (labels[i].name == "" || labels[i].name == undefined) { + labels[i].name = labels[i].color.toUpperCase(); + } + } + console.log(labels); + return labels; + }, + + events() { + return [{ + 'click .js-add-label-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#label-action').value; + const labelId = this.find('#label-id').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "add") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "addLabel", + "labelId": labelId, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "remove") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "removeLabel", + "labelId": labelId, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + + }, + 'click .js-add-member-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#member-action').value; + const memberName = this.find('#member-name').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "add") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "addMember", + "memberName": memberName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId, + "desc": desc + }); + } + if (actionSelected == "remove") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "removeMember", + "memberName": memberName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + 'click .js-add-removeall-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const triggerId = Triggers.insert(trigger); + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const actionId = Actions.insert({ + actionType: "removeMember", + "memberName": "*", + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + }, + }]; + }, + +}).register('cardActions');
\ No newline at end of file diff --git a/client/components/rules/actions/checklistActions.jade b/client/components/rules/actions/checklistActions.jade new file mode 100644 index 00000000..8414a1a5 --- /dev/null +++ b/client/components/rules/actions/checklistActions.jade @@ -0,0 +1,51 @@ +template(name="checklistActions") + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="check-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-checklist'}}} + div.trigger-dropdown + input(id="checklist-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-checklist-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="checkall-action") + option(value="check") {{{_'r-check-all'}}} + option(value="uncheck") {{{_'r-uncheck-all'}}} + div.trigger-text + | {{{_'r-items-check'}}} + div.trigger-dropdown + input(id="checklist-name2",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-checkall-action.js-goto-rules + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="check-item-action") + option(value="check") {{{_'r-check'}}} + option(value="uncheck") {{{_'r-uncheck'}}} + div.trigger-text + | {{{_'r-item'}}} + div.trigger-dropdown + input(id="checkitem-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-of-checklist'}}} + div.trigger-dropdown + input(id="checklist-name3",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-check-item-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/checklistActions.js b/client/components/rules/actions/checklistActions.js new file mode 100644 index 00000000..0b4c2167 --- /dev/null +++ b/client/components/rules/actions/checklistActions.js @@ -0,0 +1,128 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + events() { + return [{ + 'click .js-add-checklist-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#check-action').value; + const checklistName = this.find('#checklist-name').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "add") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "addChecklist", + "checklistName": checklistName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "remove") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "removeChecklist", + "checklistName": checklistName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + + }, + 'click .js-add-checkall-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#checkall-action').value; + const checklistName = this.find('#checklist-name2').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "check") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "checkAll", + "checklistName": checklistName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "uncheck") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "uncheckAll", + "checklistName": checklistName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + 'click .js-add-check-item-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const checkItemName = this.find("#checkitem-name"); + const checklistName = this.find("#checklist-name3"); + const actionSelected = this.find('#check-item-action').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected == "check") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "checkItem", + "checklistName": checklistName, + "checkItemName": checkItemName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + if (actionSelected == "uncheck") { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: "uncheckItem", + "checklistName": checklistName, + "checkItemName": checkItemName, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + } + }, + }]; + }, + +}).register('checklistActions');
\ No newline at end of file diff --git a/client/components/rules/actions/mailActions.jade b/client/components/rules/actions/mailActions.jade new file mode 100644 index 00000000..c10fb384 --- /dev/null +++ b/client/components/rules/actions/mailActions.jade @@ -0,0 +1,11 @@ +template(name="mailActions") + div.trigger-item.trigger-item-mail + div.trigger-content.trigger-content-mail + div.trigger-text.trigger-text-email + | {{{_'r-send-email'}}} + div.trigger-dropdown-mail + input(id="email-to",type=text,placeholder="{{{_'r-to'}}}") + input(id="email-subject",type=text,placeholder="{{{_'r-subject'}}}") + textarea(id="email-msg") + div.trigger-button.trigger-button-email.js-mail-action.js-goto-rules + i.fa.fa-plus
\ No newline at end of file diff --git a/client/components/rules/actions/mailActions.js b/client/components/rules/actions/mailActions.js new file mode 100644 index 00000000..dae7d08d --- /dev/null +++ b/client/components/rules/actions/mailActions.js @@ -0,0 +1,35 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-mail-action' (event) { + const emailTo = this.find('#email-to').value; + const emailSubject = this.find('#email-subject').value; + const emailMsg = this.find('#email-msg').value; + const trigger = this.data().triggerVar.get(); + const ruleName = this.data().ruleName.get(); + const triggerId = Triggers.insert(trigger); + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + const actionId = Actions.insert({ + actionType: "sendEmail", + "emailTo": emailTo, + "emailSubject": emailSubject, + "emailMsg": emailMsg, + "boardId": boardId, + "desc": desc + }); + Rules.insert({ + title: ruleName, + triggerId: triggerId, + actionId: actionId, + "boardId": boardId + }); + }, + }]; + }, + +}).register('mailActions');
\ No newline at end of file diff --git a/client/components/rules/ruleDetails.jade b/client/components/rules/ruleDetails.jade new file mode 100644 index 00000000..b9a1351c --- /dev/null +++ b/client/components/rules/ruleDetails.jade @@ -0,0 +1,18 @@ +template(name="ruleDetails") + .rules + h2 + i.fa.fa-magic + | {{{_ 'r-rule-details' }}} + .triggers-content + .triggers-body + .triggers-main-body + div.trigger-item + div.trigger-content + div.trigger-text + = trigger + div.trigger-item + div.trigger-content + div.trigger-text + = action + +
\ No newline at end of file diff --git a/client/components/rules/ruleDetails.js b/client/components/rules/ruleDetails.js new file mode 100644 index 00000000..385b0bae --- /dev/null +++ b/client/components/rules/ruleDetails.js @@ -0,0 +1,36 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + this.subscribe('allTriggers'); + this.subscribe('allActions'); + + }, + + trigger() { + const ruleId = this.data().ruleId; + const rule = Rules.findOne({ + _id: ruleId.get() + }); + const trigger = Triggers.findOne({ + _id: rule.triggerId + }); + console.log(trigger); + return trigger.description(); + }, + action() { + const ruleId = this.data().ruleId; + const rule = Rules.findOne({ + _id: ruleId.get() + }); + const action = Actions.findOne({ + _id: rule.actionId + }); + console.log(action); + return action.description(); + }, + + events() { + return [{}]; + }, + +}).register('ruleDetails');
\ No newline at end of file diff --git a/client/components/rules/rules.styl b/client/components/rules/rules.styl new file mode 100644 index 00000000..68d74d32 --- /dev/null +++ b/client/components/rules/rules.styl @@ -0,0 +1,156 @@ +.rules-list + overflow:hidden + overflow-y:scroll + max-height: 400px +.rules-lists-item + display: block + position: relative + overflow: auto + p + display: inline-block + float: left + margin: revert + +.rules-btns-group + position: absolute + right: 0 + top: 50% + transform: translateY(-50%) + button + margin: auto +.rules-add + display: block + overflow: auto + margin-top: 15px + margin-bottom: 5px + input + display: inline-block + float: right + margin: auto + margin-right: 10px + button + display: inline-block + float: right + margin: auto +.flex + display: -webkit-box + display: -moz-box + display: -webkit-flex + display: -moz-flex + display: -ms-flexbox + display: flex + + + +.triggers-content + color: #727479 + background: #dedede + .triggers-body + display flex + padding-top 15px + height 100% + + .triggers-side-menu + background-color: #f7f7f7 + border: 1px solid #f0f0f0 + border-radius: 4px + height: intrinsic + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05) + + ul + + li + margin: 0.1rem 0.2rem; + width:50px + height:50px + text-align:center + font-size: 25px + position: relative + + i + position: absolute; + top: 50%; + left: 50%; + box-shadow: none + transform: translate(-50%,-50%); + + + &.active + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + + &:hover + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + a + @extends .flex + padding: 1rem 0 1rem 1rem + width: 100% - 5rem + + + span + font-size: 13px + .triggers-main-body + padding: 0.1em 1em + width:100% + .trigger-item + overflow:auto + padding:10px + height:40px + margin-bottom:5px + border-radius: 3px + position: relative + background-color: white + .trigger-content + position:absolute + top:50% + transform: translateY(-50%) + left:10px + .trigger-text + font-size: 16px + display:inline-block + .trigger-text.trigger-text-email + margin-left: 5px; + margin-top: 10px; + margin-bottom: 10px; + .trigger-dropdown + display:inline-block + select + width:100px + height:30px + margin:0px + margin-left:5px + input + display: inline-block + width: 80px; + margin: 0; + .trigger-content-mail + left:20px + right:100px + .trigger-button + position:absolute + top:50% + transform: translateY(-50%) + width:30px + height:30px + border: 1px solid #eee + border-radius: 4px + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05) + text-align:center + font-size: 20px + right:10px + i + position: absolute + top: 50% + left: 50% + box-shadow: none + transform: translate(-50%,-50%) + &:hover, &.is-active + box-shadow: 0 0 0 2px darken(white, 60%) inset + .trigger-button.trigger-button-email + top:30px + .trigger-item.trigger-item-mail + height:300px + + + diff --git a/client/components/rules/rulesActions.jade b/client/components/rules/rulesActions.jade new file mode 100644 index 00000000..8dfceeeb --- /dev/null +++ b/client/components/rules/rulesActions.jade @@ -0,0 +1,25 @@ +template(name="rulesActions") + h2 + i.fa.fa-magic + | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-action'}}} + .triggers-content + .triggers-body + .triggers-side-menu + ul + li.active.js-set-board-actions + i.fa.fa-columns + li.js-set-card-actions + i.fa.fa-sticky-note + li.js-set-checklist-actions + i.fa.fa-check + li.js-set-mail-actions + i.fa.fa-at + .triggers-main-body + if ($eq currentActions.get 'board') + +boardActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'card') + +cardActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'checklist') + +checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'mail') + +mailActions(ruleName=data.ruleName triggerVar=data.triggerVar)
\ No newline at end of file diff --git a/client/components/rules/rulesActions.js b/client/components/rules/rulesActions.js new file mode 100644 index 00000000..d492cbd5 --- /dev/null +++ b/client/components/rules/rulesActions.js @@ -0,0 +1,58 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.currentActions = new ReactiveVar("board"); + }, + + setBoardActions() { + this.currentActions.set("board"); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').addClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setCardActions() { + this.currentActions.set("card"); + $('.js-set-card-actions').addClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setChecklistActions() { + this.currentActions.set("checklist"); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').addClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setMailActions() { + this.currentActions.set("mail"); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').addClass('active'); + }, + + rules() { + return Rules.find({}); + }, + + name() { + console.log(this.data()); + }, + events() { + return [{ + 'click .js-set-board-actions' (event) { + this.setBoardActions(); + }, + 'click .js-set-card-actions' (event) { + this.setCardActions(); + }, + 'click .js-set-mail-actions' (event) { + this.setMailActions(); + }, + 'click .js-set-checklist-actions' (event) { + this.setChecklistActions(); + }, + }]; + }, +}).register('rulesActions');
\ No newline at end of file diff --git a/client/components/rules/rulesList.jade b/client/components/rules/rulesList.jade new file mode 100644 index 00000000..c2676aa7 --- /dev/null +++ b/client/components/rules/rulesList.jade @@ -0,0 +1,27 @@ +template(name="rulesList") + .rules + h2 + i.fa.fa-magic + | {{{_ 'r-board-rules' }}} + + ul.rules-list + each rules + li.rules-lists-item + p + = title + div.rules-btns-group + button.js-goto-details + i.fa.fa-eye + | {{{_ 'r-view-rule'}}} + if currentUser.isAdmin + button.js-delete-rule + i.fa.fa-trash-o + | {{{_ 'r-delete-rule'}}} + else + li.no-items-message {{{_ 'r-no-rules' }}} + if currentUser.isAdmin + div.rules-add + button.js-goto-trigger + i.fa.fa-plus + | {{{_ 'r-add-rule'}}} + input(type=text,placeholder="{{{_ 'r-new-rule-name' }}}",id="ruleTitle")
\ No newline at end of file diff --git a/client/components/rules/rulesList.js b/client/components/rules/rulesList.js new file mode 100644 index 00000000..e7b4660a --- /dev/null +++ b/client/components/rules/rulesList.js @@ -0,0 +1,15 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + + rules() { + const boardId = Session.get('currentBoard'); + return Rules.find({ + "boardId": boardId + }); + }, + events() { + return [{}]; + }, +}).register('rulesList');
\ No newline at end of file diff --git a/client/components/rules/rulesMain.jade b/client/components/rules/rulesMain.jade new file mode 100644 index 00000000..dc33ee4e --- /dev/null +++ b/client/components/rules/rulesMain.jade @@ -0,0 +1,9 @@ +template(name="rulesMain") + if($eq rulesCurrentTab.get 'rulesList') + +rulesList + if($eq rulesCurrentTab.get 'trigger') + +rulesTriggers(ruleName=ruleName triggerVar=triggerVar) + if($eq rulesCurrentTab.get 'action') + +rulesActions(ruleName=ruleName triggerVar=triggerVar) + if($eq rulesCurrentTab.get 'ruleDetails') + +ruleDetails(ruleId=ruleId)
\ No newline at end of file diff --git a/client/components/rules/rulesMain.js b/client/components/rules/rulesMain.js new file mode 100644 index 00000000..e4cac03d --- /dev/null +++ b/client/components/rules/rulesMain.js @@ -0,0 +1,58 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.rulesCurrentTab = new ReactiveVar("rulesList") + this.ruleName = new ReactiveVar(""); + this.triggerVar = new ReactiveVar(); + this.ruleId = new ReactiveVar(); + }, + + setTrigger() { + this.rulesCurrentTab.set("trigger") + }, + + setRulesList() { + this.rulesCurrentTab.set("rulesList") + }, + + setAction() { + this.rulesCurrentTab.set("action") + }, + setRuleDetails() { + this.rulesCurrentTab.set("ruleDetails") + }, + + events() { + return [{ + 'click .js-delete-rule' (event) { + const rule = this.currentData(); + Rules.remove(rule._id); + Actions.remove(rule.actionId); + Triggers.remove(rule.triggerId); + + }, + 'click .js-goto-trigger' (event) { + event.preventDefault(); + const ruleTitle = this.find('#ruleTitle').value; + this.find('#ruleTitle').value = ""; + this.ruleName.set(ruleTitle) + this.setTrigger(); + }, + 'click .js-goto-action' (event) { + event.preventDefault(); + this.setAction(); + }, + 'click .js-goto-rules' (event) { + event.preventDefault(); + this.setRulesList(); + }, + 'click .js-goto-details' (event) { + event.preventDefault(); + const rule = this.currentData(); + this.ruleId.set(rule._id) + this.setRuleDetails(); + }, + + }]; + }, + +}).register('rulesMain');
\ No newline at end of file diff --git a/client/components/rules/rulesTriggers.jade b/client/components/rules/rulesTriggers.jade new file mode 100644 index 00000000..0ef5edfa --- /dev/null +++ b/client/components/rules/rulesTriggers.jade @@ -0,0 +1,21 @@ +template(name="rulesTriggers") + h2 + i.fa.fa-magic + | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-trigger'}}} + .triggers-content + .triggers-body + .triggers-side-menu + ul + li.active.js-set-board-triggers + i.fa.fa-columns + li.js-set-card-triggers + i.fa.fa-sticky-note + li.js-set-checklist-triggers + i.fa.fa-check + .triggers-main-body + if showBoardTrigger.get + +boardTriggers + else if showCardTrigger.get + +cardTriggers + else if showChecklistTrigger.get + +checklistTriggers
\ No newline at end of file diff --git a/client/components/rules/rulesTriggers.js b/client/components/rules/rulesTriggers.js new file mode 100644 index 00000000..f9dd4ecc --- /dev/null +++ b/client/components/rules/rulesTriggers.js @@ -0,0 +1,53 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.showBoardTrigger = new ReactiveVar(true); + this.showCardTrigger = new ReactiveVar(false); + this.showChecklistTrigger = new ReactiveVar(false); + }, + + setBoardTriggers() { + this.showBoardTrigger.set(true); + this.showCardTrigger.set(false); + this.showChecklistTrigger.set(false); + $('.js-set-card-triggers').removeClass('active'); + $('.js-set-board-triggers').addClass('active'); + $('.js-set-checklist-triggers').removeClass('active'); + }, + setCardTriggers() { + this.showBoardTrigger.set(false); + this.showCardTrigger.set(true); + this.showChecklistTrigger.set(false); + $('.js-set-card-triggers').addClass('active'); + $('.js-set-board-triggers').removeClass('active'); + $('.js-set-checklist-triggers').removeClass('active'); + }, + setChecklistTriggers() { + this.showBoardTrigger.set(false); + this.showCardTrigger.set(false); + this.showChecklistTrigger.set(true); + $('.js-set-card-triggers').removeClass('active'); + $('.js-set-board-triggers').removeClass('active'); + $('.js-set-checklist-triggers').addClass('active'); + }, + + rules() { + return Rules.find({}); + }, + + name() { + console.log(this.data()); + }, + events() { + return [{ + 'click .js-set-board-triggers' (event) { + this.setBoardTriggers(); + }, + 'click .js-set-card-triggers' (event) { + this.setCardTriggers(); + }, + 'click .js-set-checklist-triggers' (event) { + this.setChecklistTriggers(); + }, + }]; + }, +}).register('rulesTriggers');
\ No newline at end of file diff --git a/client/components/rules/triggers/boardTriggers.jade b/client/components/rules/triggers/boardTriggers.jade new file mode 100644 index 00000000..b5e08c8c --- /dev/null +++ b/client/components/rules/triggers/boardTriggers.jade @@ -0,0 +1,61 @@ +template(name="boardTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-card-is'}}} + div.trigger-dropdown + select(id="gen-action") + option(value="created") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-the-board'}}} + div.trigger-button.js-add-gen-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-card-is'}}} + div.trigger-dropdown + select(id="create-action") + option(value="created") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-list'}}} + div.trigger-dropdown + input(id="create-list-name",type=text,placeholder="{{{_'r-list-name'}}}") + div.trigger-button.js-add-create-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-card-is'}}} + div.trigger-dropdown + select(id="move-action") + option(value="moved-to") {{{_'r-moved-to'}}} + option(value="moved-from") {{{_'r-moved-from'}}} + div.trigger-text + | {{{_'r-list'}}} + div.trigger-dropdown + input(id="move-list-name",type=text,placeholder="{{{_'r-list-name'}}}") + div.trigger-button.js-add-moved-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-card-is'}}} + div.trigger-dropdown + select(id="arch-action") + option(value="archived") {{{_'r-archived'}}} + option(value="unarchived") {{{_'r-unarchived'}}} + div.trigger-button.js-add-arch-trigger.js-goto-action + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/triggers/boardTriggers.js b/client/components/rules/triggers/boardTriggers.js new file mode 100644 index 00000000..95c10a5b --- /dev/null +++ b/client/components/rules/triggers/boardTriggers.js @@ -0,0 +1,103 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-add-gen-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#gen-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "created") { + datas.triggerVar.set({ + activityType: "createCard", + "boardId": boardId, + "listName": "*", + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removeCard", + "boardId": boardId, + "desc": desc + }); + } + + }, + 'click .js-add-create-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#create-action').value; + const listName = this.find('#create-list-name').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "created") { + datas.triggerVar.set({ + activityType: "createCard", + "boardId": boardId, + "listName": listName, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removeCard", + "boardId": boardId, + "listName": listName, + "desc": desc + }); + } + }, + 'click .js-add-moved-trigger' (event) { + let datas = this.data(); + const desc = Utils.getTriggerActionDesc(event, this); + + const actionSelected = this.find('#move-action').value; + const listName = this.find('#move-list-name').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "moved-to") { + datas.triggerVar.set({ + activityType: "moveCard", + "boardId": boardId, + "listName": listName, + "oldListName": "*", + "desc": desc + }); + } + if (actionSelected == "moved-from") { + datas.triggerVar.set({ + activityType: "moveCard", + "boardId": boardId, + "listName": "*", + "oldListName": listName, + "desc": desc + }); + } + }, + 'click .js-add-arc-trigger' (event) { + let datas = this.data(); + const desc = Utils.getTriggerActionDesc(event, this); + const actionSelected = this.find('#arch-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "archived") { + datas.triggerVar.set({ + activityType: "archivedCard", + "boardId": boardId, + "desc": desc + }); + } + if (actionSelected == "unarchived") { + datas.triggerVar.set({ + activityType: "restoredCard", + "boardId": boardId, + "desc": desc + }); + } + } + + }]; + }, + +}).register('boardTriggers');
\ No newline at end of file diff --git a/client/components/rules/triggers/cardTriggers.jade b/client/components/rules/triggers/cardTriggers.jade new file mode 100644 index 00000000..dd02413c --- /dev/null +++ b/client/components/rules/triggers/cardTriggers.jade @@ -0,0 +1,79 @@ +template(name="cardTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-label-is'}}} + div.trigger-dropdown + select(id="label-action") + option(value="added") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-gen-label-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-the-label-is'}}} + div.trigger-dropdown + select(id="spec-label") + each labels + option(value="#{_id}") + = name + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="spec-label-action") + option(value="added") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-spec-label-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-member'}}} + div.trigger-dropdown + select(id="gen-member-action") + option(value="added") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-gen-member-trigger.js-goto-action + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-the-member'}}} + div.trigger-dropdown + input(id="spec-member",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="spec-member-action") + option(value="added") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-spec-member-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-attach'}}} + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="attach-action") + option(value="added") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-attachment-trigger.js-goto-action + i.fa.fa-plus
\ No newline at end of file diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js new file mode 100644 index 00000000..c0a5ec1a --- /dev/null +++ b/client/components/rules/triggers/cardTriggers.js @@ -0,0 +1,130 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + labels() { + const labels = Boards.findOne(Session.get('currentBoard')).labels; + console.log(labels); + for (let i = 0; i < labels.length; i++) { + if (labels[i].name == "" || labels[i].name == undefined) { + labels[i].name = labels[i].color.toUpperCase(); + } + } + console.log(labels); + return labels; + }, + events() { + return [{ + 'click .js-add-gen-label-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#label-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "addedLabel", + "boardId": boardId, + "labelId": "*", + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removedLabel", + "boardId": boardId, + "labelId": "*", + "desc": desc + }); + } + }, + 'click .js-add-spec-label-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#spec-label-action').value; + const labelId = this.find('#spec-label').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "addedLabel", + "boardId": boardId, + "labelId": labelId, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removedLabel", + "boardId": boardId, + "labelId": labelId, + "desc": desc + }); + } + }, + 'click .js-add-gen-member-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#gen-member-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "joinMember", + "boardId": boardId, + "memberId": "*", + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "unjoinMember", + "boardId": boardId, + "memberId": "*", + "desc": desc + }); + } + }, + 'click .js-add-spec-member-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#spec-member-action').value; + const memberId = this.find('#spec-member').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "joinMember", + "boardId": boardId, + "memberId": memberId, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "unjoinMember", + "boardId": boardId, + "memberId": memberId, + "desc": desc + }); + } + }, + 'click .js-add-attachment-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#attach-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "addAttachment", + "boardId": boardId, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "deleteAttachment", + "boardId": boardId, + "desc": desc + }); + } + }, + }]; + }, +}).register('cardTriggers');
\ No newline at end of file diff --git a/client/components/rules/triggers/checklistTriggers.jade b/client/components/rules/triggers/checklistTriggers.jade new file mode 100644 index 00000000..465713c8 --- /dev/null +++ b/client/components/rules/triggers/checklistTriggers.jade @@ -0,0 +1,83 @@ +template(name="checklistTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-checklist'}}} + div.trigger-dropdown + select(id="gen-check-action") + option(value="created") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-gen-check-trigger.js-goto-action + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-the-checklist'}}} + div.trigger-dropdown + input(id="check-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="spec-check-action") + option(value="created") {{{_'r-added-to'}}} + option(value="removed") {{{_'r-removed-from'}}} + div.trigger-text + | {{{_'r-a-card'}}} + div.trigger-button.js-add-spec-check-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-checklist'}}} + div.trigger-dropdown + select(id="gen-comp-check-action") + option(value="completed") {{{_'r-completed'}}} + option(value="uncompleted") {{{_'r-made-incomplete'}}} + div.trigger-button.js-add-gen-comp-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-the-checklist'}}} + div.trigger-dropdown + input(id="spec-comp-check-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="spec-comp-check-action") + option(value="completed") {{{_'r-completed'}}} + option(value="uncompleted") {{{_'r-made-incomplete'}}} + div.trigger-button.js-add-spec-comp-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-a-item'}}} + div.trigger-dropdown + select(id="check-item-gen-action") + option(value="checked") {{{_'r-checked'}}} + option(value="unchecked") {{{_'r-unchecked'}}} + div.trigger-button.js-add-gen-check-item-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-when-the-item'}}} + div.trigger-dropdown + input(id="check-item-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-is'}}} + div.trigger-dropdown + select(id="check-item-spec-action") + option(value="checked") {{{_'r-checked'}}} + option(value="unchecked") {{{_'r-unchecked'}}} + div.trigger-button.js-add-spec-check-item-trigger.js-goto-action + i.fa.fa-plus
\ No newline at end of file diff --git a/client/components/rules/triggers/checklistTriggers.js b/client/components/rules/triggers/checklistTriggers.js new file mode 100644 index 00000000..6e7b3445 --- /dev/null +++ b/client/components/rules/triggers/checklistTriggers.js @@ -0,0 +1,146 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + events() { + return [{ + 'click .js-add-gen-check-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#gen-check-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "created") { + datas.triggerVar.set({ + activityType: "addChecklist", + "boardId": boardId, + "checklistName": "*", + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removeChecklist", + "boardId": boardId, + "checklistName": "*", + "desc": desc + }); + } + }, + 'click .js-add-spec-check-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#spec-check-action').value; + const checklistId = this.find('#check-name').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "created") { + datas.triggerVar.set({ + activityType: "addChecklist", + "boardId": boardId, + "checklistName": checklistId, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "removeChecklist", + "boardId": boardId, + "checklistName": checklistId, + "desc": desc + }); + } + }, + 'click .js-add-gen-comp-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + + let datas = this.data(); + const actionSelected = this.find('#gen-comp-check-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "completed") { + datas.triggerVar.set({ + activityType: "completeChecklist", + "boardId": boardId, + "checklistName": "*", + "desc": desc + }); + } + if (actionSelected == "uncompleted") { + datas.triggerVar.set({ + activityType: "uncompleteChecklist", + "boardId": boardId, + "checklistName": "*", + "desc": desc + }); + } + }, + 'click .js-add-spec-comp-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#spec-comp-check-action').value; + const checklistId = this.find('#spec-comp-check-name').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "added") { + datas.triggerVar.set({ + activityType: "completeChecklist", + "boardId": boardId, + "checklistName": checklistId, + "desc": desc + }); + } + if (actionSelected == "removed") { + datas.triggerVar.set({ + activityType: "uncompleteChecklist", + "boardId": boardId, + "checklistName": checklistId, + "desc": desc + }); + } + }, + 'click .js-add-gen-check-item-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#check-item-gen-action').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "checked") { + datas.triggerVar.set({ + activityType: "checkedItem", + "boardId": boardId, + "checklistItemName": "*", + "desc": desc + }); + } + if (actionSelected == "unchecked") { + datas.triggerVar.set({ + activityType: "uncheckedItem", + "boardId": boardId, + "checklistItemName": "*", + "desc": desc + }); + } + }, + 'click .js-add-spec-check-item-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + let datas = this.data(); + const actionSelected = this.find('#check-item-spec-action').value; + const checklistItemId = this.find('#check-item-name').value; + const boardId = Session.get('currentBoard') + if (actionSelected == "checked") { + datas.triggerVar.set({ + activityType: "checkedItem", + "boardId": boardId, + "checklistItemName": checklistItemId, + "desc": desc + }); + } + if (actionSelected == "unchecked") { + datas.triggerVar.set({ + activityType: "uncheckedItem", + "boardId": boardId, + "checklistItemName": checklistItemId, + "desc": desc + }); + } + }, + }]; + }, + +}).register('checklistTriggers');
\ No newline at end of file diff --git a/client/lib/modal.js b/client/lib/modal.js index d5350264..3c27a179 100644 --- a/client/lib/modal.js +++ b/client/lib/modal.js @@ -4,6 +4,7 @@ window.Modal = new class { constructor() { this._currentModal = new ReactiveVar(closedValue); this._onCloseGoTo = ''; + this._isWideModal = false; } getHeaderName() { @@ -20,6 +21,10 @@ window.Modal = new class { return this.getTemplateName() !== closedValue; } + isWide(){ + return this._isWideModal; + } + close() { this._currentModal.set(closedValue); if (this._onCloseGoTo) { @@ -27,9 +32,16 @@ window.Modal = new class { } } + openWide(modalName, { header = '', onCloseGoTo = ''} = {}) { + this._currentModal.set({ header, modalName }); + this._onCloseGoTo = onCloseGoTo; + this._isWideModal = true; + } + open(modalName, { header = '', onCloseGoTo = ''} = {}) { this._currentModal.set({ header, modalName }); this._onCloseGoTo = onCloseGoTo; + } }(); @@ -38,5 +50,5 @@ Blaze.registerHelper('Modal', Modal); EscapeActions.register('modalWindow', () => Modal.close(), () => Modal.isOpen(), - { noClickEscapeOn: '.modal-content' } + { noClickEscapeOn: '.modal-container' } ); diff --git a/client/lib/popup.js b/client/lib/popup.js index 0a700f82..cb56858f 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -83,6 +83,7 @@ window.Popup = new class { // our internal dependency, and since we just changed the top element of // our internal stack, the popup will be updated with the new data. if (!self.isOpen()) { + console.log(self.template) self.current = Blaze.renderWithData(self.template, () => { self._dep.depend(); return { ...self._getTopStack(), stack: self._stack }; diff --git a/client/lib/utils.js b/client/lib/utils.js index 5349e500..a15dac39 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -39,11 +39,11 @@ Utils = { if (!prevData && !nextData) { base = 0; increment = 1; - // If we drop the card in the first position + // If we drop the card in the first position } else if (!prevData) { base = nextData.sort - 1; increment = -1; - // If we drop the card in the last position + // If we drop the card in the last position } else if (!nextData) { base = prevData.sort + 1; increment = 1; @@ -71,11 +71,11 @@ Utils = { if (!prevCardDomElement && !nextCardDomElement) { base = 0; increment = 1; - // If we drop the card in the first position + // If we drop the card in the first position } else if (!prevCardDomElement) { base = Blaze.getData(nextCardDomElement).sort - 1; increment = -1; - // If we drop the card in the last position + // If we drop the card in the last position } else if (!nextCardDomElement) { base = Blaze.getData(prevCardDomElement).sort + 1; increment = 1; @@ -145,6 +145,7 @@ Utils = { }); }, +<<<<<<< HEAD setMatomo(data){ window._paq = window._paq || []; window._paq.push(['setDoNotTrack', data.doNotTrack]); @@ -188,6 +189,26 @@ Utils = { } else if (matomo) { window._paq.push(['trackPageView']); } + + getTriggerActionDesc(event, tempInstance) { + const jqueryEl = tempInstance.$(event.currentTarget.parentNode); + const triggerEls = jqueryEl.find(".trigger-content").children(); + let finalString = ""; + for (let i = 0; i < triggerEls.length; i++) { + const element = tempInstance.$(triggerEls[i]); + if (element.hasClass("trigger-text")) { + finalString += element.text().toLowerCase(); + } else if (element.find("select").length > 0) { + finalString += element.find("select option:selected").text().toLowerCase(); + } else if (element.find("input").length > 0) { + finalString += element.find("input").val(); + } + // Add space + if (i != length - 1) { + finalString += " "; + } + } + return finalString; }, }; diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 689ed42d..395940c6 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -43,9 +43,19 @@ "activity-sent": "sent %s to %s", "activity-unjoined": "unjoined %s", "activity-subtask-added": "added subtask to %s", + "activity-checked-item": "checked %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-removed": "removed a checklist from %s", + "activity-checklist-completed": "completed 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-removed": "removed a checklist item from '%s' in %s", "add": "Add", + "activity-checked-item-card": "checked %s in checklist %s", + "activity-unchecked-item-card": "unchecked %s in checklist %s", + "activity-checklist-completed-card": "completed the checklist %s", + "activity-checklist-uncompleted-card": "uncompleted the checklist %s", "add-attachment": "Add Attachment", "add-board": "Add Board", "add-card": "Add Card", @@ -371,6 +381,7 @@ "restore": "Restore", "save": "Save", "search": "Search", + "rules": "Rules", "search-cards": "Search from card titles and descriptions on this board", "search-example": "Text to search for?", "select-color": "Select Color", @@ -507,6 +518,107 @@ "change-card-parent": "Change card's parent", "parent-card": "Parent card", "source-board": "Source board", - "no-parent": "Don't show parent" + "no-parent": "Don't show parent", + "activity-added-label": "added label '%s' to %s", + "activity-removed-label": "removed label '%s' from %s", + "activity-delete-attach": "deleted an attachment from %s", + "activity-added-label-card": "added label '%s'", + "activity-removed-label-card": "removed label '%s'", + "activity-delete-attach-card": "deleted an attachment", + "r-rule": "Rule", + "r-add-trigger": "Add trigger", + "r-add-action": "Add action", + "r-board-rules": "Board rules", + "r-add-rule": "Add rule", + "r-view-rule": "View rule", + "r-delete-rule": "Delete rule", + "r-new-rule-name": "Add new rule", + "r-no-rules": "No rules", + "r-when-a-card-is": "When a card is", + "r-added-to": "Added to", + "r-removed-from": "Removed from", + "r-the-board": "the board", + "r-list": "list", + "r-moved-to": "Moved to", + "r-moved-from": "Moved from", + "r-archived": "Archived", + "r-unarchived": "Unarchived", + "r-a-card": "a card", + "r-when-a-label-is": "When a label is", + "r-when-the-label-is": "When the label is", + "r-list-name": "List name", + "r-when-a-member": "When a member is", + "r-when-the-member": "When the member is", + "r-name": "name", + "r-is": "is", + "r-when-a-attach": "When an attachment", + "r-when-a-checklist": "When a checklist is", + "r-when-the-checklist": "When the checklist", + "r-completed": "Completed", + "r-made-incomplete": "Made incomplete", + "r-when-a-item": "When a checklist item is", + "r-when-the-item": "When the checklist item", + "r-checked": "Checked", + "r-unchecked": "Unchecked", + "r-move-card-to": "Move card to", + "r-top-of": "Top of", + "r-bottom-of": "Bottom of", + "r-its-list": "its list", + "r-list": "list", + "r-archive": "Archive", + "r-unarchive": "Unarchive", + "r-card": "card", + "r-add": "Add", + "r-remove": "Remove", + "r-label": "label", + "r-member": "member", + "r-remove-all": "Remove all members from the card", + "r-checklist": "checklist", + "r-check-all": "Check all", + "r-uncheck-all": "Uncheck all", + "r-item-check": "Items of checklist", + "r-check": "Check", + "r-uncheck": "Uncheck", + "r-item": "item", + "r-of-checklist": "of checlist", + "r-send-email": "Send an email", + "r-to": "to", + "r-subject": "subject", + "r-rule-details": "Rule details", + "r-d-move-to-top-gen": "Move card to top of its list", + "r-d-move-to-top-spec": "Move card to top of list", + "r-d-move-to-bottom-gen": "Move card to bottom of its list", + "r-d-move-to-bottom-spec": "Move card to bottom of list", + "r-d-send-email": "Send email", + "r-d-send-email-to": "to", + "r-d-send-email-subject": "subject", + "r-d-send-email-message": "message", + "r-d-archive": "Archive the card", + "r-d-unarchive": "Unarchive the card", + "r-d-add-label": "Add label", + "r-d-remove-label": "Remove label", + "r-d-add-member": "Add member", + "r-d-remove-member": "Remove member", + "r-d-remove-all-member": "Remove all member", + "r-d-check-all": "Check all item of list", + "r-d-uncheck-all": "Uncheck all item of list", + "r-d-check-one": "Check item", + "r-d-uncheck-one": "Uncheck item", + "r-d-check-of-list": "of checklist", + "r-d-add-checklist": "Add checklist", + "r-d-remove-checklist": "Remove checklist" + + + + + + + + + + + + + } diff --git a/models/.DS_Store b/models/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/models/.DS_Store diff --git a/models/actions.js b/models/actions.js new file mode 100644 index 00000000..82ab0d19 --- /dev/null +++ b/models/actions.js @@ -0,0 +1,19 @@ +Actions = new Mongo.Collection('actions'); + +Actions.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + } +}); + +Actions.helpers({ + description() { + return this.desc; + } +});
\ No newline at end of file diff --git a/models/activities.js b/models/activities.js index 2228f66e..c14760c2 100644 --- a/models/activities.js +++ b/models/activities.js @@ -56,6 +56,17 @@ Activities.before.insert((userId, doc) => { doc.createdAt = new Date(); }); + + +Activities.after.insert((userId, doc) => { + const activity = Activities._transform(doc); + RulesHelper.executeRules(activity); + +}); + + + + if (Meteor.isServer) { // For efficiency create indexes on the date of creation, and on the date of // creation in conjunction with the card or board id, as corresponding views diff --git a/models/attachments.js b/models/attachments.js index 91dd0dbc..d769de34 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -86,5 +86,12 @@ if (Meteor.isServer) { Activities.remove({ attachmentId: doc._id, }); + Activities.insert({ + userId, + type: 'card', + activityType: 'deleteAttachment', + boardId: doc.boardId, + cardId: doc.cardId, + }); }); } diff --git a/models/boards.js b/models/boards.js index 2a21d6da..641ecdb9 100644 --- a/models/boards.js +++ b/models/boards.js @@ -280,6 +280,10 @@ Boards.helpers({ return _.findWhere(this.labels, { name, color }); }, + getLabelById(labelId){ + return _.findWhere(this.labels, { _id: labelId }); + }, + labelIndex(labelId) { return _.pluck(this.labels, '_id').indexOf(labelId); }, diff --git a/models/cards.js b/models/cards.js index 73b9a023..2595e934 100644 --- a/models/cards.js +++ b/models/cards.js @@ -276,14 +276,22 @@ Cards.helpers({ return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1 + } + }); }, allSubtasks() { return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1 + } + }); }, subtasksCount() { @@ -296,7 +304,8 @@ Cards.helpers({ subtasksFinishedCount() { return Cards.find({ parentId: this._id, - archived: true}).count(); + archived: true + }).count(); }, subtasksFinished() { @@ -328,12 +337,9 @@ Cards.helpers({ }); //search for "True Value" which is for DropDowns other then the Value (which is the id) let trueValue = customField.value; - if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) - { - for (let i = 0; i < definition.settings.dropdownItems.length; i++) - { - if (definition.settings.dropdownItems[i]._id === customField.value) - { + if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) { + for (let i = 0; i < definition.settings.dropdownItems.length; i++) { + if (definition.settings.dropdownItems[i]._id === customField.value) { trueValue = definition.settings.dropdownItems[i].name; } } @@ -358,8 +364,10 @@ Cards.helpers({ }, canBeRestored() { - const list = Lists.findOne({_id: this.listId}); - if(!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){ + const list = Lists.findOne({ + _id: this.listId + }); + if (!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()) { return false; } return true; @@ -424,7 +432,7 @@ Cards.helpers({ }, parentString(sep) { - return this.parentList().map(function(elem){ + return this.parentList().map(function(elem) { return elem.title; }).join(sep); }, @@ -826,19 +834,65 @@ Cards.helpers({ Cards.mutations({ applyToChildren(funct) { - Cards.find({ parentId: this._id }).forEach((card) => { + Cards.find({ + parentId: this._id + }).forEach((card) => { funct(card); }); }, archive() { - this.applyToChildren((card) => { return card.archive(); }); - return {$set: {archived: true}}; + this.applyToChildren((card) => { + return card.archive(); + }); + return { + $set: { + archived: true + } + }; }, restore() { - this.applyToChildren((card) => { return card.restore(); }); - return {$set: {archived: false}}; + this.applyToChildren((card) => { + return card.restore(); + }); + return { + $set: { + archived: false + } + }; + }, + + setTitle(title) { + return { + $set: { + title + } + }; + }, + + setDescription(description) { + return { + $set: { + description + } + }; + }, + + setRequestedBy(requestedBy) { + return { + $set: { + requestedBy + } + }; + }, + + setAssignedBy(assignedBy) { + return { + $set: { + assignedBy + } + }; }, move(swimlaneId, listId, sortIndex) { @@ -850,15 +904,25 @@ Cards.mutations({ sort: sortIndex, }; - return {$set: mutatedFields}; + return { + $set: mutatedFields + }; }, addLabel(labelId) { - return {$addToSet: {labelIds: labelId}}; + return { + $addToSet: { + labelIds: labelId + } + }; }, removeLabel(labelId) { - return {$pull: {labelIds: labelId}}; + return { + $pull: { + labelIds: labelId + } + }; }, toggleLabel(labelId) { @@ -869,12 +933,52 @@ Cards.mutations({ } }, +<<<<<<< HEAD +======= + 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); + } + }, + +>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b assignCustomField(customFieldId) { - return {$addToSet: {customFields: {_id: customFieldId, value: null}}}; + return { + $addToSet: { + customFields: { + _id: customFieldId, + value: null + } + } + }; }, unassignCustomField(customFieldId) { - return {$pull: {customFields: {_id: customFieldId}}}; + return { + $pull: { + customFields: { + _id: customFieldId + } + } + }; }, toggleCustomField(customFieldId) { @@ -889,7 +993,9 @@ Cards.mutations({ // todo const index = this.customFieldIndex(customFieldId); if (index > -1) { - const update = {$set: {}}; + const update = { + $set: {} + }; update.$set[`customFields.${index}.value`] = value; return update; } @@ -899,19 +1005,122 @@ Cards.mutations({ }, setCover(coverId) { - return {$set: {coverId}}; + return { + $set: { + coverId + } + }; }, unsetCover() { - return {$unset: {coverId: ''}}; + return { + $unset: { + coverId: '' + } + }; + }, + +<<<<<<< HEAD +======= + setReceived(receivedAt) { + return { + $set: { + receivedAt + } + }; + }, + + unsetReceived() { + return { + $unset: { + receivedAt: '' + } + }; + }, + + setStart(startAt) { + return { + $set: { + startAt + } + }; + }, + + unsetStart() { + return { + $unset: { + startAt: '' + } + }; + }, + + setDue(dueAt) { + return { + $set: { + dueAt + } + }; }, + unsetDue() { + return { + $unset: { + dueAt: '' + } + }; + }, + + setEnd(endAt) { + return { + $set: { + endAt + } + }; + }, + + unsetEnd() { + return { + $unset: { + endAt: '' + } + }; + }, + + setOvertime(isOvertime) { + return { + $set: { + isOvertime + } + }; + }, + + setSpentTime(spentTime) { + return { + $set: { + spentTime + } + }; + }, + + unsetSpentTime() { + return { + $unset: { + spentTime: '', + isOvertime: false + } + }; + }, + +>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b setParentId(parentId) { - return {$set: {parentId}}; + return { + $set: { + parentId + } + }; }, }); - //FUNCTIONS FOR creation of Activities function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { @@ -921,6 +1130,7 @@ function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { userId, oldListId, activityType: 'moveCard', + listName: Lists.findOne(doc.listId).title, listId: doc.listId, boardId: doc.boardId, cardId: doc._id, @@ -936,6 +1146,7 @@ function cardState(userId, doc, fieldNames) { Activities.insert({ userId, activityType: 'archivedCard', + listName: Lists.findOne(doc.listId).title, boardId: doc.boardId, listId: doc.listId, cardId: doc._id, @@ -945,6 +1156,7 @@ function cardState(userId, doc, fieldNames) { userId, activityType: 'restoredCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, }); @@ -986,11 +1198,47 @@ function cardMembers(userId, doc, fieldNames, modifier) { } } +function cardLabels(userId, doc, fieldNames, modifier) { + if (!_.contains(fieldNames, 'labelIds')) + return; + let labelId; + // Say hello to the new label + if (modifier.$addToSet && modifier.$addToSet.labelIds) { + labelId = modifier.$addToSet.labelIds; + if (!_.contains(doc.labelIds, labelId)) { + const act = { + userId, + labelId, + activityType: 'addedLabel', + boardId: doc.boardId, + cardId: doc._id, + } + Activities.insert(act); + } + } + + // Say goodbye to the label + if (modifier.$pull && modifier.$pull.labelIds) { + labelId = modifier.$pull.labelIds; + // Check that the former member is member of the card + if (_.contains(doc.labelIds, labelId)) { + Activities.insert({ + userId, + labelId, + activityType: 'removedLabel', + boardId: doc.boardId, + cardId: doc._id, + }); + } + } +} + function cardCreation(userId, doc) { Activities.insert({ userId, activityType: 'createCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, swimlaneId: doc.swimlaneId, @@ -1015,7 +1263,6 @@ function cardRemover(userId, doc) { }); } - if (Meteor.isServer) { // Cards are often fetched within a board, so we create an index to make these // queries more efficient. @@ -1039,7 +1286,7 @@ if (Meteor.isServer) { }); //New activity for card moves - Cards.after.update(function (userId, doc, fieldNames) { + Cards.after.update(function(userId, doc, fieldNames) { const oldListId = this.previous.listId; const oldSwimlaneId = this.previous.swimlaneId; cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId); @@ -1050,6 +1297,11 @@ if (Meteor.isServer) { cardMembers(userId, doc, fieldNames, modifier); }); + // Add a new activity if we add or remove a label to the card + Cards.before.update((userId, doc, fieldNames, modifier) => { + cardLabels(userId, doc, fieldNames, modifier); + }); + // Remove all activities associated with a card if we remove the card // Remove also card_comments / checklists / attachments Cards.after.remove((userId, doc) => { @@ -1058,13 +1310,17 @@ if (Meteor.isServer) { } //LISTS REST API if (Meteor.isServer) { - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: Cards.find({boardId: paramBoardId, listId: paramListId, archived: false}).map(function (doc) { + data: Cards.find({ + boardId: paramBoardId, + listId: paramListId, + archived: false + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -1074,24 +1330,31 @@ if (Meteor.isServer) { }); }); - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: Cards.findOne({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}), + data: Cards.findOne({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false + }), }); }); - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; - const check = Users.findOne({_id: req.body.authorId}); + const check = Users.findOne({ + _id: req.body.authorId + }); const members = req.body.members || [req.body.authorId]; - if (typeof check !== 'undefined') { + if (typeof check !== 'undefined') { const id = Cards.direct.insert({ title: req.body.title, boardId: paramBoardId, @@ -1109,7 +1372,9 @@ if (Meteor.isServer) { }, }); - const card = Cards.findOne({_id:id}); + const card = Cards.findOne({ + _id: id + }); cardCreation(req.body.authorId, card); } else { @@ -1119,7 +1384,7 @@ if (Meteor.isServer) { } }); - JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -1127,27 +1392,63 @@ if (Meteor.isServer) { if (req.body.hasOwnProperty('title')) { const newTitle = req.body.title; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {title: newTitle}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false + }, { + $set: { + title: newTitle + } + }); } if (req.body.hasOwnProperty('listId')) { const newParamListId = req.body.listId; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {listId: newParamListId}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false + }, { + $set: { + listId: newParamListId + } + }); - const card = Cards.findOne({_id: paramCardId} ); - cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId); + const card = Cards.findOne({ + _id: paramCardId + }); + cardMove(req.body.authorId, card, { + fieldName: 'listId' + }, paramListId); } if (req.body.hasOwnProperty('description')) { const newDescription = req.body.description; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {description: newDescription}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false + }, { + $set: { + description: newDescription + } + }); } if (req.body.hasOwnProperty('labelIds')) { const newlabelIds = req.body.labelIds; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {labelIds: newlabelIds}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false + }, { + $set: { + labelIds: newlabelIds + } + }); } if (req.body.hasOwnProperty('requestedBy')) { const newrequestedBy = req.body.requestedBy; @@ -1202,15 +1503,20 @@ if (Meteor.isServer) { }); }); - - JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; - Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId}); - const card = Cards.find({_id: paramCardId} ); + Cards.direct.remove({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId + }); + const card = Cards.find({ + _id: paramCardId + }); cardRemover(req.body.authorId, card); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/checklistItems.js b/models/checklistItems.js index e075eda2..e24f0cbd 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -44,6 +44,12 @@ ChecklistItems.mutations({ setTitle(title) { return { $set: { title } }; }, + check(){ + return { $set: { isFinished: true } }; + }, + uncheck(){ + return { $set: { isFinished: false } }; + }, toggleItem() { return { $set: { isFinished: !this.isFinished } }; }, @@ -70,21 +76,102 @@ function itemCreation(userId, doc) { boardId, checklistId: doc.checklistId, checklistItemId: doc._id, + checklistItemName:doc.title }); } function itemRemover(userId, doc) { + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + Activities.insert({ + userId, + activityType: 'removedChecklistItem', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title + }); Activities.remove({ checklistItemId: doc._id, }); } +function publishCheckActivity(userId,doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + let activityType; + if(doc.isFinished){ + activityType = "checkedItem"; + }else{ + activityType = "uncheckedItem"; + } + let act = { + userId, + activityType: activityType, + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title + } + console.log(act); + Activities.insert(act); +} + +function publishChekListCompleted(userId,doc,fieldNames,modifier){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + let act = { + userId, + activityType: "checklistCompleted", + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName:doc.title + } + Activities.insert(act); + } +} + +function publishChekListUncompleted(userId,doc,fieldNames,modifier){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + let act = { + userId, + activityType: "checklistUncompleted", + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName:doc.title + } + Activities.insert(act); + } +} + // Activities if (Meteor.isServer) { Meteor.startup(() => { ChecklistItems._collection._ensureIndex({ checklistId: 1 }); }); + ChecklistItems.after.update((userId, doc, fieldNames, modifier) => { + publishCheckActivity(userId,doc); + publishChekListCompleted(userId,doc,fieldNames,modifier) + }); + + ChecklistItems.before.update((userId, doc, fieldNames, modifier) => { + publishChekListUncompleted(userId,doc,fieldNames,modifier) + }); + + + ChecklistItems.after.insert((userId, doc) => { itemCreation(userId, doc); }); diff --git a/models/checklists.js b/models/checklists.js index c58453ef..26429092 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -47,6 +47,18 @@ Checklists.helpers({ isFinished() { return 0 !== this.itemCount() && this.itemCount() === this.finishedCount(); }, + checkAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.check(); + }); + }, + uncheckAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.uncheck(); + }); + }, itemIndex(itemId) { const items = self.findOne({_id : this._id}).items; return _.pluck(items, '_id').indexOf(itemId); @@ -91,6 +103,7 @@ if (Meteor.isServer) { cardId: doc.cardId, boardId: Cards.findOne(doc.cardId).boardId, checklistId: doc._id, + checklistName:doc.title }); }); @@ -101,6 +114,16 @@ if (Meteor.isServer) { Activities.remove(activity._id); }); } + Activities.insert({ + userId, + activityType: 'removeChecklist', + cardId: doc.cardId, + boardId: Cards.findOne(doc.cardId).boardId, + checklistId: doc._id, + checklistName:doc.title + }); + + }); } diff --git a/models/export.js b/models/export.js index 6c0b43fd..c65ebf52 100644 --- a/models/export.js +++ b/models/export.js @@ -14,7 +14,7 @@ if (Meteor.isServer) { * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * for detailed explanations */ - JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) { + JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; let user = null; // todo XXX for real API, first look for token in Authentication: header @@ -28,8 +28,11 @@ if (Meteor.isServer) { } const exporter = new Exporter(boardId); - if(exporter.canExport(user)) { - JsonRoutes.sendResult(res, { code: 200, data: exporter.build() }); + if (exporter.canExport(user)) { + JsonRoutes.sendResult(res, { + code: 200, + data: exporter.build() + }); } else { // we could send an explicit error message, but on the other hand the only // way to get there is by hacking the UI so let's keep it raw. @@ -47,24 +50,49 @@ class Exporter { const byBoard = { boardId: this._boardId }; const byBoardNoLinked = { boardId: this._boardId, linkedId: '' }; // we do not want to retrieve boardId in related elements - const noBoardId = { fields: { boardId: 0 } }; + const noBoardId = { + fields: { + boardId: 0 + } + }; const result = { _format: 'wekan-board-1.0.0', }; - _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } })); + _.extend(result, Boards.findOne(this._boardId, { + fields: { + stars: 0 + } + })); result.lists = Lists.find(byBoard, noBoardId).fetch(); result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); result.customFields = CustomFields.find(byBoard, noBoardId).fetch(); result.comments = CardComments.find(byBoard, noBoardId).fetch(); result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); result.checklists = []; result.checklistItems = []; result.subtaskItems = []; + result.triggers = []; + result.actions = []; result.cards.forEach((card) => { - result.checklists.push(...Checklists.find({ cardId: card._id }).fetch()); - result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch()); - result.subtaskItems.push(...Cards.find({ parentid: card._id }).fetch()); + result.checklists.push(...Checklists.find({ + cardId: card._id + }).fetch()); + result.checklistItems.push(...ChecklistItems.find({ + cardId: card._id + }).fetch()); + result.subtaskItems.push(...Cards.find({ + parentid: card._id + }).fetch()); + }); + result.rules.forEach((rule) => { + result.triggers.push(...Triggers.find({ + _id: rule.triggerId + }, noBoardId).fetch()); + result.actions.push(...Actions.find({ + _id: rule.actionId + }, noBoardId).fetch()); }); // [Old] for attachments we only export IDs and absolute url to original doc @@ -101,18 +129,34 @@ class Exporter { // 1- only exports users that are linked somehow to that board // 2- do not export any sensitive information const users = {}; - result.members.forEach((member) => { users[member.userId] = true; }); - result.lists.forEach((list) => { users[list.userId] = true; }); + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); result.cards.forEach((card) => { users[card.userId] = true; if (card.members) { - card.members.forEach((memberId) => { users[memberId] = true; }); + card.members.forEach((memberId) => { + users[memberId] = true; + }); } }); - result.comments.forEach((comment) => { users[comment.userId] = true; }); - result.activities.forEach((activity) => { users[activity.userId] = true; }); - result.checklists.forEach((checklist) => { users[checklist.userId] = true; }); - const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } }; + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users) + } + }; // we use whitelist to be sure we do not expose inadvertently // some secret fields that gets added to User later. const userFields = { diff --git a/models/lists.js b/models/lists.js index 9bcb9ba1..bf5aae3c 100644 --- a/models/lists.js +++ b/models/lists.js @@ -82,7 +82,7 @@ Lists.helpers({ }; if (swimlaneId) selector.swimlaneId = swimlaneId; - return Cards.find(Filter.mongoSelector(selector), + return Cards.find(selector, { sort: ['sort'] }); }, diff --git a/models/rules.js b/models/rules.js new file mode 100644 index 00000000..fe6b04cb --- /dev/null +++ b/models/rules.js @@ -0,0 +1,49 @@ +Rules = new Mongo.Collection('rules'); + +Rules.attachSchema(new SimpleSchema({ + title: { + type: String, + optional: false, + }, + triggerId: { + type: String, + optional: false, + }, + actionId: { + type: String, + optional: false, + }, + boardId: { + type: String, + optional: false, + }, +})); + +Rules.mutations({ + rename(description) { + return { $set: { description } }; + }, +}); + +Rules.helpers({ + getAction(){ + return Actions.findOne({_id:this.actionId}); + }, + getTrigger(){ + return Triggers.findOne({_id:this.triggerId}); + } +}); + + + +Rules.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + } +}); diff --git a/models/triggers.js b/models/triggers.js new file mode 100644 index 00000000..c8e4cc75 --- /dev/null +++ b/models/triggers.js @@ -0,0 +1,58 @@ +Triggers = new Mongo.Collection('triggers'); + +Triggers.mutations({ + rename(description) { + return { + $set: { + description + } + }; + }, +}); + +Triggers.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + } +}); + +Triggers.helpers({ + + description() { + return this.desc; + }, + + getRule() { + return Rules.findOne({ + triggerId: this._id + }); + }, + + fromList() { + return Lists.findOne(this.fromId); + }, + + toList() { + return Lists.findOne(this.toId); + }, + + findList(title) { + return Lists.findOne({ + title: title + }); + }, + + labels() { + const boardLabels = this.board().labels; + const cardLabels = _.filter(boardLabels, (label) => { + return _.contains(this.labelIds, label._id); + }); + return cardLabels; + } +});
\ No newline at end of file diff --git a/models/wekanCreator.js b/models/wekanCreator.js index d144821f..6841a6ae 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,4 +1,4 @@ -const DateString = Match.Where(function (dateAsString) { +const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); return moment(dateAsString, moment.ISO_8601).isValid(); }); @@ -42,6 +42,10 @@ export class WekanCreator { this.comments = {}; // the members, indexed by Wekan member id => Wekan user ID this.members = data.membersMapping ? data.membersMapping : {}; + // Map of triggers Wekan ID => Wekan ID + this.triggers = {}; + // Map of actions Wekan ID => Wekan ID + this.actions = {}; // maps a wekanCardId to an array of wekanAttachments this.attachments = {}; @@ -57,10 +61,10 @@ export class WekanCreator { * @param {String} dateString a properly formatted Date */ _now(dateString) { - if(dateString) { + if (dateString) { return new Date(dateString); } - if(!this._nowDate) { + if (!this._nowDate) { this._nowDate = new Date(); } return this._nowDate; @@ -72,9 +76,9 @@ export class WekanCreator { * Otherwise return current logged user. * @param wekanUserId * @private - */ + */ _user(wekanUserId) { - if(wekanUserId && this.members[wekanUserId]) { + if (wekanUserId && this.members[wekanUserId]) { return this.members[wekanUserId]; } return Meteor.userId(); @@ -96,7 +100,7 @@ export class WekanCreator { // allowed values (is it worth the maintenance?) color: String, permission: Match.Where((value) => { - return ['private', 'public'].indexOf(value)>= 0; + return ['private', 'public'].indexOf(value) >= 0; }), })); } @@ -147,6 +151,30 @@ export class WekanCreator { })]); } + checkRules(wekanRules) { + check(wekanRules, [Match.ObjectIncluding({ + triggerId: String, + actionId: String, + title: String, + })]); + } + + checkTriggers(wekanTriggers) { + // XXX More check based on trigger type + check(wekanTriggers, [Match.ObjectIncluding({ + activityType: String, + desc: String, + })]); + } + + checkActions(wekanActions) { + // XXX More check based on action type + check(wekanActions, [Match.ObjectIncluding({ + actionType: String, + desc: String, + })]); + } + // You must call parseActions before calling this one. createBoardAndLabels(boardToImport) { const boardToCreate = { @@ -172,12 +200,12 @@ export class WekanCreator { title: boardToImport.title, }; // now add other members - if(boardToImport.members) { + if (boardToImport.members) { boardToImport.members.forEach((wekanMember) => { // do we already have it in our list? - if(!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) + if (!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) boardToCreate.members.push({ - ... wekanMember, + ...wekanMember, userId: wekanMember.wekanId, }); }); @@ -194,7 +222,11 @@ export class WekanCreator { boardToCreate.labels.push(labelToCreate); }); const boardId = Boards.direct.insert(boardToCreate); - Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}}); + Boards.direct.update(boardId, { + $set: { + modifiedAt: this._now() + } + }); // log activity Activities.direct.insert({ activityType: 'importBoard', @@ -246,21 +278,21 @@ export class WekanCreator { }); } // add members { - if(card.members) { + if (card.members) { const wekanMembers = []; // we can't just map, as some members may not have been mapped card.members.forEach((sourceMemberId) => { - if(this.members[sourceMemberId]) { + if (this.members[sourceMemberId]) { const wekanId = this.members[sourceMemberId]; // we may map multiple Wekan members to the same wekan user // in which case we risk adding the same user multiple times - if(!wekanMembers.find((wId) => wId === wekanId)){ + if (!wekanMembers.find((wId) => wId === wekanId)) { wekanMembers.push(wekanId); } } return true; }); - if(wekanMembers.length>0) { + if (wekanMembers.length > 0) { cardToCreate.members = wekanMembers; } } @@ -321,9 +353,9 @@ export class WekanCreator { // - the template then tries to display the url to the attachment which causes other errors // so we make it server only, and let UI catch up once it is done, forget about latency comp. const self = this; - if(Meteor.isServer) { + if (Meteor.isServer) { if (att.url) { - file.attachData(att.url, function (error) { + file.attachData(att.url, function(error) { file.boardId = boardId; file.cardId = cardId; file.userId = self._user(att.userId); @@ -331,20 +363,26 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); self.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id + } + }); } } }); } else if (att.file) { - file.attachData(new Buffer(att.file, 'base64'), {type: att.type}, (error) => { + file.attachData(new Buffer(att.file, 'base64'), { + type: att.type + }, (error) => { file.name(att.name); file.boardId = boardId; file.cardId = cardId; @@ -353,15 +391,19 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); this.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id + } + }); } } }); @@ -404,7 +446,11 @@ export class WekanCreator { sort: list.sort ? list.sort : listIndex, }; const listId = Lists.direct.insert(listToCreate); - Lists.direct.update(listId, {$set: {'updatedAt': this._now()}}); + Lists.direct.update(listId, { + $set: { + 'updatedAt': this._now() + } + }); this.lists[list._id] = listId; // // log activity // Activities.direct.insert({ @@ -437,7 +483,11 @@ export class WekanCreator { sort: swimlane.sort ? swimlane.sort : swimlaneIndex, }; const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate); - Swimlanes.direct.update(swimlaneId, {$set: {'updatedAt': this._now()}}); + Swimlanes.direct.update(swimlaneId, { + $set: { + 'updatedAt': this._now() + } + }); this.swimlanes[swimlane._id] = swimlaneId; }); } @@ -459,6 +509,47 @@ export class WekanCreator { return result; } + createTriggers(wekanTriggers, boardId) { + wekanTriggers.forEach((trigger, ruleIndex) => { + if (trigger.hasOwnProperty('labelId')) { + trigger['labelId'] = this.labels[trigger['labelId']] + } + if (trigger.hasOwnProperty('memberId')) { + trigger['memberId'] = this.members[trigger['memberId']] + } + trigger['boardId'] = boardId; + const oldId = trigger['_id']; + delete trigger._id; + this.triggers[oldId] = Triggers.direct.insert(trigger); + }); + } + + createActions(wekanActions, boardId) { + wekanActions.forEach((action, ruleIndex) => { + if (action.hasOwnProperty('labelId')) { + action['labelId'] = this.labels[action['labelId']] + } + if (action.hasOwnProperty('memberId')) { + action['memberId'] = this.members[action['memberId']] + } + action['boardId'] = boardId; + const oldId = action['_id']; + delete action._id; + this.actions[oldId] = Actions.direct.insert(action); + }); + } + + createRules(wekanRules, boardId) { + wekanRules.forEach((rule, ruleIndex) => { + // Create the rule + rule['boardId'] = boardId; + rule['triggerId'] = this.triggers[rule['triggerId']]; + rule['actionId'] = this.actions[rule['actionId']]; + delete rule._id; + Rules.direct.insert(rule); + }); + } + createChecklistItems(wekanChecklistItems) { wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => { // Create the checklistItem @@ -477,166 +568,182 @@ export class WekanCreator { parseActivities(wekanBoard) { wekanBoard.activities.forEach((activity) => { switch (activity.activityType) { - case 'addAttachment': { - // We have to be cautious, because the attachment could have been removed later. - // In that case Wekan still reports its addition, but removes its 'url' field. - // So we test for that - const wekanAttachment = wekanBoard.attachments.filter((attachment) => { - return attachment._id === activity.attachmentId; - })[0]; + case 'addAttachment': + { + // We have to be cautious, because the attachment could have been removed later. + // In that case Wekan still reports its addition, but removes its 'url' field. + // So we test for that + const wekanAttachment = wekanBoard.attachments.filter((attachment) => { + return attachment._id === activity.attachmentId; + })[0]; - if ( typeof wekanAttachment !== 'undefined' && wekanAttachment ) { - if(wekanAttachment.url || wekanAttachment.file) { - // we cannot actually create the Wekan attachment, because we don't yet - // have the cards to attach it to, so we store it in the instance variable. - const wekanCardId = activity.cardId; - if(!this.attachments[wekanCardId]) { - this.attachments[wekanCardId] = []; + if (typeof wekanAttachment !== 'undefined' && wekanAttachment) { + if (wekanAttachment.url || wekanAttachment.file) { + // we cannot actually create the Wekan attachment, because we don't yet + // have the cards to attach it to, so we store it in the instance variable. + const wekanCardId = activity.cardId; + if (!this.attachments[wekanCardId]) { + this.attachments[wekanCardId] = []; + } + this.attachments[wekanCardId].push(wekanAttachment); + } } - this.attachments[wekanCardId].push(wekanAttachment); + break; + } + case 'addComment': + { + const wekanComment = wekanBoard.comments.filter((comment) => { + return comment._id === activity.commentId; + })[0]; + const id = activity.cardId; + if (!this.comments[id]) { + this.comments[id] = []; + } + this.comments[id].push(wekanComment); + break; + } + case 'createBoard': + { + this.createdAt.board = activity.createdAt; + break; + } + case 'createCard': + { + const cardId = activity.cardId; + this.createdAt.cards[cardId] = activity.createdAt; + this.createdBy.cards[cardId] = activity.userId; + break; + } + case 'createList': + { + const listId = activity.listId; + this.createdAt.lists[listId] = activity.createdAt; + break; + } + case 'createSwimlane': + { + const swimlaneId = activity.swimlaneId; + this.createdAt.swimlanes[swimlaneId] = activity.createdAt; + break; } - } - break; - } - case 'addComment': { - const wekanComment = wekanBoard.comments.filter((comment) => { - return comment._id === activity.commentId; - })[0]; - const id = activity.cardId; - if (!this.comments[id]) { - this.comments[id] = []; - } - this.comments[id].push(wekanComment); - break; - } - case 'createBoard': { - this.createdAt.board = activity.createdAt; - break; - } - case 'createCard': { - const cardId = activity.cardId; - this.createdAt.cards[cardId] = activity.createdAt; - this.createdBy.cards[cardId] = activity.userId; - break; - } - case 'createList': { - const listId = activity.listId; - this.createdAt.lists[listId] = activity.createdAt; - break; } - case 'createSwimlane': { - const swimlaneId = activity.swimlaneId; - this.createdAt.swimlanes[swimlaneId] = activity.createdAt; - break; - }} }); } importActivities(activities, boardId) { activities.forEach((activity) => { switch (activity.activityType) { - // Board related activities - // TODO: addBoardMember, removeBoardMember - case 'createBoard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'board', - activityTypeId: boardId, - activityType: activity.activityType, - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // List related activities - // TODO: removeList, archivedList - case 'createList': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'list', - activityType: activity.activityType, - listId: this.lists[activity.listId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Card related activities - // TODO: archivedCard, restoredCard, joinMember, unjoinMember - case 'createCard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - listId: this.lists[activity.listId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - case 'moveCard': { - Activities.direct.insert({ - userId: this._user(activity.userId), - oldListId: this.lists[activity.oldListId], - activityType: activity.activityType, - listId: this.lists[activity.listId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Comment related activities - case 'addComment': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - commentId: this.commentIds[activity.commentId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Attachment related activities - case 'addAttachment': { - Activities.direct.insert({ - userId: this._user(activity.userId), - type: 'card', - activityType: activity.activityType, - attachmentId: this.attachmentIds[activity.attachmentId], - cardId: this.cards[activity.cardId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - } - // Checklist related activities - case 'addChecklist': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - checklistId: this.checklists[activity.checklistId], - boardId, - createdAt: this._now(activity.createdAt), - }); - break; + // Board related activities + // TODO: addBoardMember, removeBoardMember + case 'createBoard': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + type: 'board', + activityTypeId: boardId, + activityType: activity.activityType, + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + // List related activities + // TODO: removeList, archivedList + case 'createList': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + type: 'list', + activityType: activity.activityType, + listId: this.lists[activity.listId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + // Card related activities + // TODO: archivedCard, restoredCard, joinMember, unjoinMember + case 'createCard': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + activityType: activity.activityType, + listId: this.lists[activity.listId], + cardId: this.cards[activity.cardId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + case 'moveCard': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + oldListId: this.lists[activity.oldListId], + activityType: activity.activityType, + listId: this.lists[activity.listId], + cardId: this.cards[activity.cardId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + // Comment related activities + case 'addComment': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + activityType: activity.activityType, + cardId: this.cards[activity.cardId], + commentId: this.commentIds[activity.commentId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + // Attachment related activities + case 'addAttachment': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + type: 'card', + activityType: activity.activityType, + attachmentId: this.attachmentIds[activity.attachmentId], + cardId: this.cards[activity.cardId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + // Checklist related activities + case 'addChecklist': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + activityType: activity.activityType, + cardId: this.cards[activity.cardId], + checklistId: this.checklists[activity.checklistId], + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } + case 'addChecklistItem': + { + Activities.direct.insert({ + userId: this._user(activity.userId), + activityType: activity.activityType, + cardId: this.cards[activity.cardId], + checklistId: this.checklists[activity.checklistId], + checklistItemId: activity.checklistItemId.replace( + activity.checklistId, + this.checklists[activity.checklistId]), + boardId, + createdAt: this._now(activity.createdAt), + }); + break; + } } - case 'addChecklistItem': { - Activities.direct.insert({ - userId: this._user(activity.userId), - activityType: activity.activityType, - cardId: this.cards[activity.cardId], - checklistId: this.checklists[activity.checklistId], - checklistItemId: activity.checklistItemId.replace( - activity.checklistId, - this.checklists[activity.checklistId]), - boardId, - createdAt: this._now(activity.createdAt), - }); - break; - }} }); } @@ -652,6 +759,9 @@ export class WekanCreator { this.checkSwimlanes(board.swimlanes); this.checkCards(board.cards); this.checkChecklists(board.checklists); + this.checkRules(board.rules); + this.checkActions(board.actions); + this.checkTriggers(board.triggers); this.checkChecklistItems(board.checklistItems); } catch (e) { throw new Meteor.Error('error-json-schema'); @@ -674,7 +784,10 @@ export class WekanCreator { this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId); + this.createTriggers(board.triggers, boardId); + this.createActions(board.actions, boardId); + this.createRules(board.rules, boardId); // XXX add members return boardId; } -} +}
\ No newline at end of file diff --git a/server/.DS_Store b/server/.DS_Store Binary files differnew file mode 100644 index 00000000..75d47436 --- /dev/null +++ b/server/.DS_Store diff --git a/server/lib/.DS_Store b/server/lib/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/server/lib/.DS_Store diff --git a/server/lib/utils.js b/server/lib/utils.js index ee925847..c155cda5 100644 --- a/server/lib/utils.js +++ b/server/lib/utils.js @@ -1,9 +1,9 @@ allowIsBoardAdmin = function(userId, board) { - return board && board.hasAdmin(userId); + return board && board.hasAdmin(userId); }; allowIsBoardMember = function(userId, board) { - return board && board.hasMember(userId); + return board && board.hasMember(userId); }; allowIsBoardMemberCommentOnly = function(userId, board) { @@ -15,6 +15,6 @@ allowIsBoardMemberNoComments = function(userId, board) { }; allowIsBoardMemberByCard = function(userId, card) { - const board = card.board(); - return board && board.hasMember(userId); + const board = card.board(); + return board && board.hasMember(userId); }; diff --git a/server/notifications/email.js b/server/notifications/email.js index 2af6381e..b2b7fab8 100644 --- a/server/notifications/email.js +++ b/server/notifications/email.js @@ -39,3 +39,5 @@ Meteor.startup(() => { }, 30000); }); }); + + diff --git a/server/publications/rules.js b/server/publications/rules.js new file mode 100644 index 00000000..29be2e78 --- /dev/null +++ b/server/publications/rules.js @@ -0,0 +1,18 @@ +Meteor.publish('rules', (ruleId) => { + check(ruleId, String); + return Rules.find({ + _id: ruleId + }); +}); + +Meteor.publish('allRules', () => { + return Rules.find({}); +}); + +Meteor.publish('allTriggers', () => { + return Triggers.find({}); +}); + +Meteor.publish('allActions', () => { + return Actions.find({}); +});
\ No newline at end of file diff --git a/server/rulesHelper.js b/server/rulesHelper.js new file mode 100644 index 00000000..d56b70aa --- /dev/null +++ b/server/rulesHelper.js @@ -0,0 +1,131 @@ +RulesHelper = { + executeRules(activity){ + const matchingRules = this.findMatchingRules(activity); + for(let i = 0;i< matchingRules.length;i++){ + const action = matchingRules[i].getAction(); + this.performAction(activity,action); + } + }, + findMatchingRules(activity){ + const activityType = activity.activityType; + if(TriggersDef[activityType] == undefined){ + return []; + } + const matchingFields = TriggersDef[activityType].matchingFields; + const matchingMap = this.buildMatchingFieldsMap(activity,matchingFields); + let matchingTriggers = Triggers.find(matchingMap); + let matchingRules = []; + matchingTriggers.forEach(function(trigger){ + matchingRules.push(trigger.getRule()); + }); + return matchingRules; + }, + buildMatchingFieldsMap(activity, matchingFields){ + let matchingMap = {"activityType":activity.activityType}; + for(let i = 0;i< matchingFields.length;i++){ + // Creating a matching map with the actual field of the activity + // and with the wildcard (for example: trigger when a card is added + // in any [*] board + matchingMap[matchingFields[i]] = { $in: [activity[matchingFields[i]],"*"]}; + } + return matchingMap; + }, + performAction(activity,action){ + const card = Cards.findOne({_id:activity.cardId}); + const boardId = activity.boardId; + if(action.actionType == "moveCardToTop"){ + let listId; + let list; + if(activity.listTitle == "*"){ + listId = card.swimlaneId; + list = card.list(); + }else{ + list = Lists.findOne({title: action.listTitle, boardId:boardId });; + listId = list._id; + } + const minOrder = _.min(list.cards(card.swimlaneId).map((c) => c.sort)); + card.move(card.swimlaneId, listId, minOrder - 1); + } + if(action.actionType == "moveCardToBottom"){ + let listId; + let list; + if(activity.listTitle == "*"){ + listId = card.swimlaneId; + list = card.list(); + }else{ + list = Lists.findOne({title: action.listTitle, boardId:boardId}); + listId = list._id; + } + const maxOrder = _.max(list.cards(card.swimlaneId).map((c) => c.sort)); + card.move(card.swimlaneId, listId, maxOrder + 1); + } + if(action.actionType == "sendEmail"){ + const emailTo = action.emailTo; + const emailMsg = action.emailMsg; + const emailSubject = action.emailSubject; + try { + Email.send({ + to: to, + from: Accounts.emailTemplates.from, + subject: subject, + text, + }); + } catch (e) { + return; + } + } + if(action.actionType == "archive"){ + card.archive(); + } + if(action.actionType == "unarchive"){ + card.restore(); + } + if(action.actionType == "addLabel"){ + card.addLabel(action.labelId); + } + if(action.actionType == "removeLabel"){ + card.removeLabel(action.labelId); + } + if(action.actionType == "addMember"){ + const memberId = Users.findOne({username:action.memberName})._id; + card.assignMember(memberId); + } + if(action.actionType == "removeMember"){ + if(action.memberName == "*"){ + const members = card.members; + for(let i = 0;i< members.length;i++){ + card.unassignMember(members[i]); + } + }else{ + const memberId = Users.findOne({username:action.memberName})._id; + card.unassignMember(memberId); + } + } + if(action.actionType == "checkAll"){ + const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id}); + checkList.checkAllItems(); + } + if(action.actionType == "uncheckAll"){ + const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id}); + checkList.uncheckAllItems(); + } + if(action.actionType == "checkItem"){ + const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id}); + const checkItem = ChecklistItems.findOne({"title":action.checkItemName,"checkListId":checkList._id}) + checkItem.check(); + } + if(action.actionType == "uncheckItem"){ + const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id}); + const checkItem = ChecklistItems.findOne({"title":action.checkItemName,"checkListId":checkList._id}) + checkItem.uncheck(); + } + if(action.actionType == "addChecklist"){ + Checklists.insert({"title":action.checklistName,"cardId":card._id,"sort":0}); + } + if(action.actionType == "removeChecklist"){ + Checklists.remove({"title":action.checklistName,"cardId":card._id,"sort":0}); + } + + }, + +}
\ No newline at end of file diff --git a/server/triggersDef.js b/server/triggersDef.js new file mode 100644 index 00000000..8c52051b --- /dev/null +++ b/server/triggersDef.js @@ -0,0 +1,59 @@ +TriggersDef = { + createCard:{ + matchingFields: ["boardId", "listName"] + }, + moveCard:{ + matchingFields: ["boardId", "listName", "oldListName"] + }, + archivedCard:{ + matchingFields: ["boardId"] + }, + restoredCard:{ + matchingFields: ["boardId"] + }, + joinMember:{ + matchingFields: ["boardId","memberId"] + }, + unjoinMember:{ + matchingFields: ["boardId","memberId"] + }, + addChecklist:{ + matchingFields: ["boardId","checklistName"] + }, + removeChecklist:{ + matchingFields: ["boardId","checklistName"] + }, + completeChecklist:{ + matchingFields: ["boardId","checklistName"] + }, + uncompleteChecklist:{ + matchingFields: ["boardId","checklistName"] + }, + addedChecklistItem:{ + matchingFields: ["boardId","checklistItemName"] + }, + removedChecklistItem:{ + matchingFields: ["boardId","checklistItemName"] + }, + checkedItem:{ + matchingFields: ["boardId","checklistItemName"] + }, + uncheckedItem:{ + matchingFields: ["boardId","checklistItemName"] + }, + addAttachment:{ + matchingFields: ["boardId"] + }, + deleteAttachment:{ + matchingFields: ["boardId"] + }, + addedLabel:{ + matchingFields: ["boardId","labelId"] + }, + removedLabel:{ + matchingFields: ["boardId","labelId"] + } +} + + + |