diff options
60 files changed, 1604 insertions, 1692 deletions
diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..030c0422 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,157 @@ +ecmaFeatures: + experimentalObjectRestSpread: true +rules: + indent: + - 2 + - 2 + semi: + - 2 + - always + comma-dangle: + - 2 + - always-multiline + no-inner-declarations: + - 0 + dot-notation: + - 2 + eqeqeq: + - 2 + no-eval: + - 2 + radix: + - 2 + + # Stylistic Issues + camelcase: + - 2 + comma-spacing: + - 2 + comma-style: + - 2 + new-parens: + - 2 + no-lonely-if: + - 2 + no-multiple-empty-lines: + - 2 + no-nested-ternary: + - 2 + linebreak-style: + - 2 + - unix + quotes: + - 2 + - single + semi-spacing: + - 2 + spaced-comment: + - 2 + - always + - markers: + - '/' + space-unary-ops: + - 2 + + # ECMAScript 6 + arrow-parens: + - 2 + arrow-spacing: + - 2 + no-class-assign: + - 2 + no-dupe-class-members: + - 2 + no-var: + - 2 + object-shorthand: + - 2 + prefer-const: + - 2 + prefer-template: + - 2 + prefer-spread: + - 2 +globals: + # Meteor globals + Meteor: false + DDP: false + Mongo: false + Session: false + Accounts: false + Template: false + Blaze: false + UI: false + Match: false + check: false + Tracker: false + Deps: false + ReactiveVar: false + EJSON: false + HTTP: false + Email: false + Assets: false + Handlebars: false + Package: false + App: false + Npm: false + Tinytest: false + Random: false + HTML: false + + # Exported by packages we use + '$': false + _: false + autosize: false + Avatar: true + Avatars: true + BlazeComponent: false + BlazeLayout: false + FlowRouter: false + FS: false + getSlug: false + Migrations: false + Mousetrap: false + Picker: false + Presence: true + Presences: true + Ps: true + ReactiveTabs: false + SimpleSchema: false + SubsManager: false + T9n: false + TAPi18n: false + + # Our collections + AccountsTemplates: true + Activities: true + Attachments: true + Boards: true + CardComments: true + Cards: true + Lists: true + UnsavedEditCollection: true + Users: true + + # Our objects + CSSEvents: true + EscapeActions: true + Filter: true + Filter: true + Mixins: true + Modal: true + MultiSelection: true + Popup: true + Sidebar: true + Utils: true + InlinedForm: true + UnsavedEdits: true + + # XXX Temp, we should remove these + allowIsBoardAdmin: true + allowIsBoardMember: true + Emoji: true +env: + es6: true + node: true + browser: true +extends: 'eslint:recommended' diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index b795ce4a..00000000 --- a/.jscsrc +++ /dev/null @@ -1,73 +0,0 @@ -{ - "disallowSpacesInNamedFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInFunctionDeclaration": { - "beforeOpeningRoundBrace": true - }, - "disallowEmptyBlocks": true, - "disallowSpacesInsideArrayBrackets": true, - "disallowSpacesInsideParentheses": true, - "disallowQuotedKeysInObjects": "allButReserved", - "disallowSpaceAfterObjectKeys": true, - "disallowSpaceAfterPrefixUnaryOperators": [ - "++", - "--", - "+", - "-", - "~" - ], - "disallowSpaceBeforePostfixUnaryOperators": true, - "disallowSpaceBeforeBinaryOperators": [ - "," - ], - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "disallowTrailingComma": true, - "disallowYodaConditions": true, - "disallowKeywords": [ "with" ], - "disallowMultipleLineBreaks": true, - "disallowMultipleVarDecl": "exceptUndefined", - "requireSpaceBeforeBlockStatements": true, - "requireParenthesesAroundIIFE": true, - "requireSpacesInConditionalExpression": true, - "requireBlocksOnNewline": 1, - "requireCommaBeforeLineBreak": true, - "requireSpaceAfterPrefixUnaryOperators": [ - "!" - ], - "requireSpaceBeforeBinaryOperators": true, - "requireSpaceAfterBinaryOperators": true, - "requireCamelCaseOrUpperCaseIdentifiers": true, - "requireLineFeedAtFileEnd": true, - "requireCapitalizedConstructors": true, - "requireDotNotation": true, - "requireSpacesInForStatement": true, - "requireSpaceBetweenArguments": true, - "requireCurlyBraces": [ - "do" - ], - "requireSpaceAfterKeywords": [ - "if", - "else", - "for", - "while", - "do", - "switch", - "case", - "return", - "try", - "catch", - "typeof" - ], - "validateLineBreaks": "LF", - "validateQuoteMarks": "'", - "validateIndentation": 2, - "maximumLineLength": 80 -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 0d5cb077..00000000 --- a/.jshintrc +++ /dev/null @@ -1,91 +0,0 @@ -{ - // JSHint options: http://jshint.com/docs/options/ - "maxerr": 50, - - // Enforcing - "camelcase": true, - "eqeqeq": true, - "undef": true, - "unused": true, - - // Environments - "browser": true, - "devel": true, - - // Authorized globals - "globals": { - // Meteor globals - "Meteor": false, - "DDP": false, - "Mongo": false, - "Session": false, - "Accounts": false, - "Template": false, - "Blaze": false, - "UI": false, - "Match": false, - "check": false, - "Tracker": false, - "Deps": false, - "ReactiveVar": false, - "EJSON": false, - "HTTP": false, - "Email": false, - "Assets": false, - "Handlebars": false, - "Package": false, - "App": false, - "Npm": false, - "Tinytest": false, - "Random": false, - "HTML": false, - - // Exported by packages we use - "_": false, - "$": false, - "autosize": false, - "Router": false, - "SimpleSchema": false, - "getSlug": false, - "Migrations": false, - "FS": false, - "BlazeComponent": false, - "TAPi18n": false, - "T9n": false, - "SubsManager": false, - "Mousetrap": false, - "Avatar": true, - "Avatars": true, - "Ps": true, - "Presence": true, - "Presences": true, - - // Our collections - "Boards": true, - "Lists": true, - "Cards": true, - "CardComments": true, - "Activities": true, - "Attachments": true, - "Users": true, - "AccountsTemplates": true, - - // Our objects - "CSSEvents": true, - "EscapeActions": true, - "Filter": true, - "Filter": true, - "Mixins": true, - "Modal": true, - "MultiSelection": true, - "Popup": true, - "Sidebar": true, - "Utils": true, - "InlinedForm": true, - - // XXX Temp, we should remove these - "allowIsBoardAdmin": true, - "allowIsBoardMember": true, - "Emoji": true - } -} diff --git a/.meteor/packages b/.meteor/packages index 7dde3983..43019d91 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -9,12 +9,14 @@ meteor-base # Build system -es5-shim ecmascript standard-minifiers mquandalle:jade mquandalle:stylus +# Polyfills +es5-shim + # Collections mongo aldeed:collection2 @@ -63,6 +65,7 @@ fortawesome:fontawesome mousetrap:mousetrap mquandalle:jquery-textcomplete mquandalle:jquery-ui-drag-drop-sort +mquandalle:mousetrap-bindglobal mquandalle:perfect-scrollbar peerlibrary:blaze-components perak:markdown diff --git a/.meteor/versions b/.meteor/versions index d7cd3438..a63ea39f 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -90,6 +90,7 @@ mquandalle:jade-compiler@0.4.3 mquandalle:jquery-textcomplete@0.3.9_1 mquandalle:jquery-ui-drag-drop-sort@0.1.0 mquandalle:moment@1.0.0 +mquandalle:mousetrap-bindglobal@0.0.1 mquandalle:perfect-scrollbar@0.6.5_2 mquandalle:stylus@1.1.1 npm-bcrypt@0.7.8_2 diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 400e3848..d6a0cf7f 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -1,99 +1,98 @@ -var activitiesPerPage = 20; +const activitiesPerPage = 20; BlazeComponent.extendComponent({ - template: function() { + template() { return 'activities'; }, - onCreated: function() { - var self = this; + onCreated() { // XXX Should we use ReactiveNumber? - self.page = new ReactiveVar(1); - self.loadNextPageLocked = false; - var sidebar = self.componentParent(); // XXX for some reason not working + this.page = new ReactiveVar(1); + this.loadNextPageLocked = false; + const sidebar = this.componentParent(); // XXX for some reason not working sidebar.callFirstWith(null, 'resetNextPeak'); - self.autorun(function() { - var mode = self.data().mode; - var capitalizedMode = Utils.capitalize(mode); - var id = Session.get('current' + capitalizedMode); - var limit = self.page.get() * activitiesPerPage; + this.autorun(() => { + const mode = this.data().mode; + const capitalizedMode = Utils.capitalize(mode); + const id = Session.get(`current${capitalizedMode}`); + const limit = this.page.get() * activitiesPerPage; if (id === null) return; - self.subscribe('activities', mode, id, limit, function() { - self.loadNextPageLocked = false; + this.subscribe('activities', mode, id, limit, () => { + this.loadNextPageLocked = false; // If the sibear peak hasn't increased, that mean that there are no more // activities, and we can stop calling new subscriptions. // XXX This is hacky! We need to know excatly and reactively how many // activities there are, we probably want to denormalize this number // dirrectly into card and board documents. - var a = sidebar.callFirstWith(null, 'getNextPeak'); + const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak'); sidebar.calculateNextPeak(); - var b = sidebar.callFirstWith(null, 'getNextPeak'); - if (a === b) { + const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak'); + if (nextPeakBefore === nextPeakAfter) { sidebar.callFirstWith(null, 'resetNextPeak'); } }); }); }, - loadNextPage: function() { + loadNextPage() { if (this.loadNextPageLocked === false) { this.page.set(this.page.get() + 1); this.loadNextPageLocked = true; } }, - boardLabel: function() { + boardLabel() { return TAPi18n.__('this-board'); }, - cardLabel: function() { + cardLabel() { return TAPi18n.__('this-card'); }, - cardLink: function() { - var card = this.currentData().card(); + cardLink() { + const card = this.currentData().card(); return card && Blaze.toHTML(HTML.A({ href: card.absoluteUrl(), - 'class': 'action-card' + 'class': 'action-card', }, card.title)); }, - memberLink: function() { + memberLink() { return Blaze.toHTMLWithData(Template.memberName, { - user: this.currentData().member() + user: this.currentData().member(), }); }, - attachmentLink: function() { - var attachment = this.currentData().attachment(); + attachmentLink() { + const attachment = this.currentData().attachment(); return attachment && Blaze.toHTML(HTML.A({ href: attachment.url(), - 'class': 'js-open-attachment-viewer' + 'class': 'js-open-attachment-viewer', }, attachment.name())); }, - events: function() { + events() { return [{ // XXX We should use Popup.afterConfirmation here - 'click .js-delete-comment': function() { - var commentId = this.currentData().commentId; + 'click .js-delete-comment'() { + const commentId = this.currentData().commentId; CardComments.remove(commentId); }, - 'submit .js-edit-comment': function(evt) { + 'submit .js-edit-comment'(evt) { evt.preventDefault(); - var commentText = this.currentComponent().getValue(); - var commentId = Template.parentData().commentId; + const commentText = this.currentComponent().getValue(); + const commentId = Template.parentData().commentId; if ($.trim(commentText)) { CardComments.update(commentId, { $set: { - text: commentText - } + text: commentText, + }, }); } - } + }, }]; - } + }, }).register('activities'); diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index e3a82c4b..08401caa 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -1,4 +1,4 @@ -let commentFormIsOpen = new ReactiveVar(false); +const commentFormIsOpen = new ReactiveVar(false); BlazeComponent.extendComponent({ template() { @@ -19,16 +19,16 @@ BlazeComponent.extendComponent({ events() { return [{ - 'click .js-new-comment:not(.focus)': function() { + 'click .js-new-comment:not(.focus)'() { commentFormIsOpen.set(true); }, - 'submit .js-new-comment-form': function(evt) { - let input = this.getInput(); + 'submit .js-new-comment-form'(evt) { + const input = this.getInput(); if ($.trim(input.val())) { CardComments.insert({ boardId: this.currentData().boardId, cardId: this.currentData()._id, - text: input.val() + text: input.val(), }); resetCommentInput(input); Tracker.flush(); @@ -37,13 +37,13 @@ BlazeComponent.extendComponent({ evt.preventDefault(); }, // Pressing Ctrl+Enter should submit the form - 'keydown form textarea': function(evt) { + 'keydown form textarea'(evt) { if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { this.find('button[type=submit]').click(); } - } + }, }]; - } + }, }).register('commentForm'); // XXX This should be a static method of the `commentForm` component @@ -63,15 +63,15 @@ Tracker.autorun(() => { Tracker.afterFlush(() => { autosize.update($('.js-new-comment-input')); }); -}) +}); EscapeActions.register('inlinedForm', - function() { + () => { const draftKey = { fieldName: 'cardComment', - docId: Session.get('currentCard') + docId: Session.get('currentCard'), }; - let commentInput = $('.js-new-comment-input'); + const commentInput = $('.js-new-comment-input'); if ($.trim(commentInput.val())) { UnsavedEdits.set(draftKey, commentInput.val()); } else { @@ -79,7 +79,7 @@ EscapeActions.register('inlinedForm', } resetCommentInput(commentInput); }, - function() { return commentFormIsOpen.get(); }, { - noClickEscapeOn: '.js-new-comment' + () => { return commentFormIsOpen.get(); }, { + noClickEscapeOn: '.js-new-comment', } ); diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index 1ce1a593..9d7ca7f2 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -1,8 +1,8 @@ Template.headerTitle.events({ - 'click .js-open-archived-board': function() { - Modal.open('archivedBoards') - } -}) + 'click .js-open-archived-board'() { + Modal.open('archivedBoards'); + }, +}); BlazeComponent.extendComponent({ template() { @@ -10,26 +10,26 @@ BlazeComponent.extendComponent({ }, onCreated() { - this.subscribe('archivedBoards') + this.subscribe('archivedBoards'); }, archivedBoards() { return Boards.find({ archived: true }, { - sort: ['title'] - }) + sort: ['title'], + }); }, events() { return [{ - 'click .js-restore-board': function() { - let boardId = this.currentData()._id + 'click .js-restore-board'() { + const boardId = this.currentData()._id; Boards.update(boardId, { $set: { - archived: false - } - }) - Utils.goBoardId(boardId) - } - }] + archived: false, + }, + }); + Utils.goBoardId(boardId); + }, + }]; }, -}).register('archivedBoards') +}).register('archivedBoards'); diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index fd9ee6bc..980a9015 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,11 +1,11 @@ -var subManager = new SubsManager(); +const subManager = new SubsManager(); BlazeComponent.extendComponent({ - template: function() { + template() { return 'board'; }, - onCreated: function() { + onCreated() { this.draggingActive = new ReactiveVar(false); this.showOverlay = new ReactiveVar(false); this.isBoardReady = new ReactiveVar(false); @@ -15,15 +15,15 @@ BlazeComponent.extendComponent({ // XXX The boardId should be readed from some sort the component "props", // unfortunatly, Blaze doesn't have this notion. this.autorun(() => { - let currentBoardId = Session.get('currentBoard'); - if (! currentBoardId) + const currentBoardId = Session.get('currentBoard'); + if (!currentBoardId) return; - var handle = subManager.subscribe('board', currentBoardId); + const handle = subManager.subscribe('board', currentBoardId); Tracker.nonreactive(() => { Tracker.autorun(() => { this.isBoardReady.set(handle.ready()); - }) - }) + }); + }); }); this._isDragging = false; @@ -33,52 +33,52 @@ BlazeComponent.extendComponent({ this.mouseHasEnterCardDetails = false; }, - openNewListForm: function() { + openNewListForm() { this.componentChildren('addListForm')[0].open(); }, // XXX Flow components allow us to avoid creating these two setter methods by // exposing a public API to modify the component state. We need to investigate // best practices here. - setIsDragging: function(bool) { + setIsDragging(bool) { this.draggingActive.set(bool); }, - scrollLeft: function(position = 0) { + scrollLeft(position = 0) { this.$('.js-lists').animate({ - scrollLeft: position + scrollLeft: position, }); }, - currentCardIsInThisList: function() { - var currentCard = Cards.findOne(Session.get('currentCard')); - var listId = this.currentData()._id; + currentCardIsInThisList() { + const currentCard = Cards.findOne(Session.get('currentCard')); + const listId = this.currentData()._id; return currentCard && currentCard.listId === listId; }, - events: function() { + events() { return [{ // XXX The board-overlay div should probably be moved to the parent // component. - 'mouseenter .board-overlay': function() { + 'mouseenter .board-overlay'() { if (this.mouseHasEnterCardDetails) { this.showOverlay.set(false); } }, // Click-and-drag action - 'mousedown .board-canvas': function(evt) { + 'mousedown .board-canvas'(evt) { if ($(evt.target).closest('a,.js-list-header').length === 0) { this._isDragging = true; this._lastDragPositionX = evt.clientX; } }, - 'mouseup': function(evt) { + 'mouseup'() { if (this._isDragging) { this._isDragging = false; } }, - 'mousemove': function(evt) { + 'mousemove'(evt) { if (this._isDragging) { // Update the canvas position this.listsDom.scrollLeft -= evt.clientX - this._lastDragPositionX; @@ -91,40 +91,40 @@ BlazeComponent.extendComponent({ EscapeActions.executeUpTo('popup-close'); EscapeActions.preventNextClick(); } - } + }, }]; - } + }, }).register('board'); Template.boardBody.onRendered(function() { - var self = BlazeComponent.getComponentForElement(this.firstNode); + const self = BlazeComponent.getComponentForElement(this.firstNode); self.listsDom = this.find('.js-lists'); - if (! Session.get('currentCard')) { + if (!Session.get('currentCard')) { self.scrollLeft(); } // We want to animate the card details window closing. We rely on CSS // transition for the actual animation. self.listsDom._uihooks = { - removeElement: function(node) { - var removeNode = _.once(function() { + removeElement(node) { + const removeNode = _.once(() => { node.parentNode.removeChild(node); }); if ($(node).hasClass('js-card-details')) { $(node).css({ flexBasis: 0, - padding: 0 + padding: 0, }); $(self.listsDom).one(CSSEvents.transitionend, removeNode); } else { removeNode(); } - } + }, }; - if (! Meteor.user() || ! Meteor.user().isBoardMember()) + if (!Meteor.user() || !Meteor.user().isBoardMember()) return; self.$(self.listsDom).sortable({ @@ -134,63 +134,63 @@ Template.boardBody.onRendered(function() { items: '.js-list:not(.js-list-composer)', placeholder: 'list placeholder', distance: 7, - start: function(evt, ui) { + start(evt, ui) { ui.placeholder.height(ui.helper.height()); Popup.close(); }, - stop: function() { + stop() { self.$('.js-lists').find('.js-list:not(.js-list-composer)').each( - function(i, list) { - var data = Blaze.getData(list); + (i, list) => { + const data = Blaze.getData(list); Lists.update(data._id, { $set: { - sort: i - } + sort: i, + }, }); } ); - } + }, }); // Disable drag-dropping while in multi-selection mode - self.autorun(function() { + self.autorun(() => { self.$(self.listsDom).sortable('option', 'disabled', MultiSelection.isActive()); }); // If there is no data in the board (ie, no lists) we autofocus the list // creation form by clicking on the corresponding element. - var currentBoard = Boards.findOne(Session.get('currentBoard')); + const currentBoard = Boards.findOne(Session.get('currentBoard')); if (currentBoard.lists().count() === 0) { self.openNewListForm(); } }); BlazeComponent.extendComponent({ - template: function() { + template() { return 'addListForm'; }, // Proxy - open: function() { + open() { this.componentChildren('inlinedForm')[0].open(); }, - events: function() { + events() { return [{ - submit: function(evt) { + submit(evt) { evt.preventDefault(); - var title = this.find('.list-name-input'); + const title = this.find('.list-name-input'); if ($.trim(title.value)) { Lists.insert({ title: title.value, boardId: Session.get('currentBoard'), - sort: $('.list').length + sort: $('.list').length, }); title.value = ''; } - } + }, }]; - } + }, }).register('addListForm'); diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index d23dd5f2..5d13fa34 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,143 +1,143 @@ Template.boardMenuPopup.events({ 'click .js-rename-board': Popup.open('boardChangeTitle'), - 'click .js-open-archives': function() { + 'click .js-open-archives'() { Sidebar.setView('archives'); Popup.close(); }, 'click .js-change-board-color': Popup.open('boardChangeColor'), 'click .js-change-language': Popup.open('changeLanguage'), - 'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() { - var boardId = Session.get('currentBoard'); + 'click .js-archive-board ': Popup.afterConfirm('archiveBoard', () => { + const boardId = Session.get('currentBoard'); Boards.update(boardId, { $set: { archived: true }}); // XXX We should have some kind of notification on top of the page to // confirm that the board was successfully archived. FlowRouter.go('home'); - }) + }), }); Template.boardChangeTitlePopup.events({ - submit: function(evt, t) { - var title = t.$('.js-board-name').val().trim(); + submit(evt, tpl) { + const title = tpl.$('.js-board-name').val().trim(); if (title) { Boards.update(this._id, { $set: { - title: title - } + title, + }, }); Popup.close(); } evt.preventDefault(); - } + }, }); BlazeComponent.extendComponent({ - template: function() { + template() { return 'headerBoard'; }, - isStarred: function() { - var boardId = Session.get('currentBoard'); - var user = Meteor.user(); + isStarred() { + const boardId = Session.get('currentBoard'); + const user = Meteor.user(); return user && user.hasStarred(boardId); }, // Only show the star counter if the number of star is greater than 2 - showStarCounter: function() { - var currentBoard = this.currentData(); + showStarCounter() { + const currentBoard = this.currentData(); return currentBoard && currentBoard.stars >= 2; }, - events: function() { + events() { return [{ 'click .js-edit-board-title': Popup.open('boardChangeTitle'), - 'click .js-star-board': function() { + 'click .js-star-board'() { Meteor.user().toggleBoardStar(Session.get('currentBoard')); }, 'click .js-open-board-menu': Popup.open('boardMenu'), 'click .js-change-visibility': Popup.open('boardChangeVisibility'), - 'click .js-open-filter-view': function() { + 'click .js-open-filter-view'() { Sidebar.setView('filter'); }, - 'click .js-filter-reset': function(evt) { + 'click .js-filter-reset'(evt) { evt.stopPropagation(); Sidebar.setView(); Filter.reset(); }, - 'click .js-multiselection-activate': function() { - var currentCard = Session.get('currentCard'); + 'click .js-multiselection-activate'() { + const currentCard = Session.get('currentCard'); MultiSelection.activate(); if (currentCard) { MultiSelection.add(currentCard); } }, - 'click .js-multiselection-reset': function(evt) { + 'click .js-multiselection-reset'(evt) { evt.stopPropagation(); MultiSelection.disable(); - } + }, }]; - } + }, }).register('headerBoard'); BlazeComponent.extendComponent({ - template: function() { + template() { return 'boardChangeColorPopup'; }, - backgroundColors: function() { + backgroundColors() { return Boards.simpleSchema()._schema.color.allowedValues; }, - isSelected: function() { - var currentBoard = Boards.findOne(Session.get('currentBoard')); + isSelected() { + const currentBoard = Boards.findOne(Session.get('currentBoard')); return currentBoard.color === this.currentData().toString(); }, - events: function() { + events() { return [{ - 'click .js-select-background': function(evt) { - var currentBoardId = Session.get('currentBoard'); + 'click .js-select-background'(evt) { + const currentBoardId = Session.get('currentBoard'); Boards.update(currentBoardId, { $set: { - color: this.currentData().toString() - } + color: this.currentData().toString(), + }, }); evt.preventDefault(); - } + }, }]; - } + }, }).register('boardChangeColorPopup'); BlazeComponent.extendComponent({ - template: function() { + template() { return 'createBoardPopup'; }, - onCreated: function() { + onCreated() { this.visibilityMenuIsOpen = new ReactiveVar(false); this.visibility = new ReactiveVar('private'); }, - visibilityCheck: function() { + visibilityCheck() { return this.currentData() === this.visibility.get(); }, - setVisibility: function(visibility) { + setVisibility(visibility) { this.visibility.set(visibility); this.visibilityMenuIsOpen.set(false); }, - toogleVisibilityMenu: function() { - this.visibilityMenuIsOpen.set(! this.visibilityMenuIsOpen.get()); + toogleVisibilityMenu() { + this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get()); }, - onSubmit: function(evt) { + onSubmit(evt) { evt.preventDefault(); - var title = this.find('.js-new-board-title').value; - var visibility = this.visibility.get(); + const title = this.find('.js-new-board-title').value; + const visibility = this.visibility.get(); - var boardId = Boards.insert({ - title: title, - permission: visibility + const boardId = Boards.insert({ + title, + permission: visibility, }); Utils.goBoardId(boardId); @@ -146,39 +146,39 @@ BlazeComponent.extendComponent({ Meteor.user().toggleBoardStar(boardId); }, - events: function() { + events() { return [{ - 'click .js-select-visibility': function() { + 'click .js-select-visibility'() { this.setVisibility(this.currentData()); }, 'click .js-change-visibility': this.toogleVisibilityMenu, - submit: this.onSubmit + submit: this.onSubmit, }]; - } + }, }).register('createBoardPopup'); BlazeComponent.extendComponent({ - template: function() { + template() { return 'boardChangeVisibilityPopup'; }, - visibilityCheck: function() { - var currentBoard = Boards.findOne(Session.get('currentBoard')); + visibilityCheck() { + const currentBoard = Boards.findOne(Session.get('currentBoard')); return this.currentData() === currentBoard.permission; }, - selectBoardVisibility: function() { + selectBoardVisibility() { Boards.update(Session.get('currentBoard'), { $set: { - permission: this.currentData() - } + permission: this.currentData(), + }, }); Popup.close(); }, - events: function() { + events() { return [{ - 'click .js-select-visibility': this.selectBoardVisibility + 'click .js-select-visibility': this.selectBoardVisibility, }]; - } + }, }).register('boardChangeVisibilityPopup'); diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 2311e7d0..1a2d3c9a 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,30 +1,30 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'boardList'; }, - boards: function() { + boards() { return Boards.find({ archived: false, - 'members.userId': Meteor.userId() + 'members.userId': Meteor.userId(), }, { - sort: ['title'] + sort: ['title'], }); }, - isStarred: function() { - var user = Meteor.user(); + isStarred() { + const user = Meteor.user(); return user && user.hasStarred(this.currentData()._id); }, - events: function() { + events() { return [{ 'click .js-add-board': Popup.open('createBoard'), - 'click .js-star-board': function(evt) { - var boardId = this.currentData()._id; + 'click .js-star-board'(evt) { + const boardId = this.currentData()._id; Meteor.user().toggleBoardStar(boardId); evt.preventDefault(); - } + }, }]; - } + }, }).register('boardList'); diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 9e299cc0..ba56aa1a 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -1,32 +1,32 @@ Template.attachmentsGalery.events({ 'click .js-add-attachment': Popup.open('cardAttachments'), 'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', - function() { + () => { Attachments.remove(this._id); Popup.close(); } ), // If we let this event bubble, FlowRouter will handle it and empty the page // content, see #101. - 'click .js-download': function(event) { + 'click .js-download'(event) { event.stopPropagation(); }, - 'click .js-open-viewer': function() { + 'click .js-open-viewer'() { // XXX Not implemented! }, - 'click .js-add-cover': function() { + 'click .js-add-cover'() { Cards.update(this.cardId, { $set: { coverId: this._id } }); }, - 'click .js-remove-cover': function() { + 'click .js-remove-cover'() { Cards.update(this.cardId, { $unset: { coverId: '' } }); - } + }, }); Template.cardAttachmentsPopup.events({ - 'change .js-attach-file': function(evt) { - var card = this; - FS.Utility.eachFile(evt, function(f) { - var file = new FS.File(f); + 'change .js-attach-file'(evt) { + const card = this; + FS.Utility.eachFile(evt, (f) => { + const file = new FS.File(f); file.boardId = card.boardId; file.cardId = card._id; @@ -34,8 +34,8 @@ Template.cardAttachmentsPopup.events({ Popup.close(); }); }, - 'click .js-computer-upload': function(evt, tpl) { + 'click .js-computer-upload'(evt, tpl) { tpl.find('.js-attach-file').click(); evt.preventDefault(); - } + }, }); diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index aba7254a..69e0cfdd 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,39 +1,39 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'cardDetails'; }, - mixins: function() { + mixins() { return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; }, - calculateNextPeak: function() { - var altitude = this.find('.js-card-details').scrollHeight; + calculateNextPeak() { + const altitude = this.find('.js-card-details').scrollHeight; this.callFirstWith(this, 'setNextPeak', altitude); }, - reachNextPeak: function() { - var activitiesComponent = this.componentChildren('activities')[0]; + reachNextPeak() { + const activitiesComponent = this.componentChildren('activities')[0]; activitiesComponent.loadNextPage(); }, - onCreated: function() { + onCreated() { this.isLoaded = new ReactiveVar(false); this.componentParent().showOverlay.set(true); this.componentParent().mouseHasEnterCardDetails = false; }, - scrollParentContainer: function() { + scrollParentContainer() { const cardPanelWidth = 510; - let bodyBoardComponent = this.componentParent(); + const bodyBoardComponent = this.componentParent(); - let $cardContainer = bodyBoardComponent.$('.js-lists'); - let $cardView = this.$(this.firstNode()); - let cardContainerScroll = $cardContainer.scrollLeft(); - let cardContainerWidth = $cardContainer.width(); + const $cardContainer = bodyBoardComponent.$('.js-lists'); + const $cardView = this.$(this.firstNode()); + const cardContainerScroll = $cardContainer.scrollLeft(); + const cardContainerWidth = $cardContainer.width(); - let cardViewStart = $cardView.offset().left; - let cardViewEnd = cardViewStart + cardPanelWidth; + const cardViewStart = $cardView.offset().left; + const cardViewEnd = cardViewStart + cardPanelWidth; let offset = false; if (cardViewStart < 0) { @@ -47,53 +47,53 @@ BlazeComponent.extendComponent({ } }, - onRendered: function() { + onRendered() { this.scrollParentContainer(); }, - onDestroyed: function() { + onDestroyed() { this.componentParent().showOverlay.set(false); }, - updateCard: function(modifier) { + updateCard(modifier) { Cards.update(this.data()._id, { - $set: modifier + $set: modifier, }); }, - events: function() { - var events = { - [CSSEvents.animationend + ' .js-card-details']: function() { + events() { + const events = { + [`${CSSEvents.animationend} .js-card-details`]() { this.isLoaded.set(true); - } + }, }; return [_.extend(events, { - 'click .js-close-card-details': function() { + 'click .js-close-card-details'() { Utils.goBoardId(this.data().boardId); }, 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), - 'submit .js-card-description': function(evt) { + 'submit .js-card-description'(evt) { evt.preventDefault(); - var description = this.currentComponent().getValue(); - this.updateCard({ description: description }); + const description = this.currentComponent().getValue(); + this.updateCard({ description }); }, - 'submit .js-card-details-title': function(evt) { + 'submit .js-card-details-title'(evt) { evt.preventDefault(); - var title = this.currentComponent().getValue(); + const title = this.currentComponent().getValue(); if ($.trim(title)) { - this.updateCard({ title: title }); + this.updateCard({ title }); } }, 'click .js-member': Popup.open('cardMember'), 'click .js-add-members': Popup.open('cardMembers'), 'click .js-add-labels': Popup.open('cardLabels'), - 'mouseenter .js-card-details': function() { + 'mouseenter .js-card-details'() { this.componentParent().showOverlay.set(true); this.componentParent().mouseHasEnterCardDetails = true; - } + }, })]; - } + }, }).register('cardDetails'); // We extends the normal InlinedForm component to support UnsavedEdits draft @@ -103,12 +103,12 @@ BlazeComponent.extendComponent({ return { fieldName: 'cardDescription', docId: Session.get('currentCard'), - } + }; } close(isReset = false) { - if (this.isOpen.get() && ! isReset) { - let draft = $.trim(this.getValue()); + if (this.isOpen.get() && !isReset) { + const draft = $.trim(this.getValue()); if (draft !== Cards.findOne(Session.get('currentCard')).description) { UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue()); } @@ -136,45 +136,45 @@ Template.cardDetailsActionsPopup.events({ 'click .js-attachments': Popup.open('cardAttachments'), 'click .js-move-card': Popup.open('moveCard'), // 'click .js-copy': Popup.open(), - 'click .js-archive': function(evt) { + 'click .js-archive'(evt) { evt.preventDefault(); Cards.update(this._id, { $set: { - archived: true - } + archived: true, + }, }); Popup.close(); }, - 'click .js-more': Popup.open('cardMore') + 'click .js-more': Popup.open('cardMore'), }); Template.moveCardPopup.events({ - 'click .js-select-list': function() { + 'click .js-select-list'() { // XXX We should *not* get the currentCard from the global state, but // instead from a “component” state. - var cardId = Session.get('currentCard'); - var newListId = this._id; + const cardId = Session.get('currentCard'); + const newListId = this._id; Cards.update(cardId, { $set: { - listId: newListId - } + listId: newListId, + }, }); Popup.close(); - } + }, }); Template.cardMorePopup.events({ - 'click .js-delete': Popup.afterConfirm('cardDelete', function() { + 'click .js-delete': Popup.afterConfirm('cardDelete', () => { Popup.close(); Cards.remove(this._id); Utils.goBoardId(this.board()._id); - }) + }), }); // Close the card details pane by pressing escape EscapeActions.register('detailsPane', - function() { Utils.goBoardId(Session.get('currentBoard')); }, - function() { return ! Session.equals('currentCard', null); }, { - noClickEscapeOn: '.js-card-details,.board-sidebar,#header' + () => { Utils.goBoardId(Session.get('currentBoard')); }, + () => { return !Session.equals('currentCard', null); }, { + noClickEscapeOn: '.js-card-details,.board-sidebar,#header', } ); diff --git a/client/components/cards/labels.js b/client/components/cards/labels.js index 4e6ceb3a..2da3b80b 100644 --- a/client/components/cards/labels.js +++ b/client/components/cards/labels.js @@ -1,136 +1,136 @@ - -var labelColors; -Meteor.startup(function() { +let labelColors; +Meteor.startup(() => { labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; }); BlazeComponent.extendComponent({ - template: function() { + template() { return 'formLabel'; }, - onCreated: function() { + onCreated() { this.currentColor = new ReactiveVar(this.data().color); }, - labels: function() { - return _.map(labelColors, function(color) { - return { color: color, name: '' }; + labels() { + return _.map(labelColors, (color) => { + return { color, name: '' }; }); }, - isSelected: function(color) { + isSelected(color) { return this.currentColor.get() === color; }, - events: function() { + events() { return [{ - 'click .js-palette-color': function() { + 'click .js-palette-color'() { this.currentColor.set(this.currentData().color); - } + }, }]; - } + }, }).register('formLabel'); Template.createLabelPopup.helpers({ // This is the default color for a new label. We search the first color that // is not already used in the board (although it's not a problem if two // labels have the same color). - defaultColor: function() { - var labels = Boards.findOne(Session.get('currentBoard')).labels; - var usedColors = _.pluck(labels, 'color'); - var availableColors = _.difference(labelColors, usedColors); + defaultColor() { + const labels = Boards.findOne(Session.get('currentBoard')).labels; + const usedColors = _.pluck(labels, 'color'); + const availableColors = _.difference(labelColors, usedColors); return availableColors.length > 1 ? availableColors[0] : labelColors[0]; - } + }, }); Template.cardLabelsPopup.events({ - 'click .js-select-label': function(evt) { - var cardId = Template.parentData(2).data._id; - var labelId = this._id; - var operation; + 'click .js-select-label'(evt) { + const cardId = Template.parentData(2).data._id; + const labelId = this._id; + let operation; if (Cards.find({ _id: cardId, labelIds: labelId}).count() === 0) operation = '$addToSet'; else operation = '$pull'; - var query = {}; - query[operation] = { - labelIds: labelId - }; - Cards.update(cardId, query); + Cards.update(cardId, { + [operation]: { + labelIds: labelId, + }, + }); evt.preventDefault(); }, 'click .js-edit-label': Popup.open('editLabel'), - 'click .js-add-label': Popup.open('createLabel') + 'click .js-add-label': Popup.open('createLabel'), }); Template.formLabel.events({ - 'click .js-palette-color': function(evt) { - var $this = $(evt.currentTarget); + 'click .js-palette-color'(evt) { + const $this = $(evt.currentTarget); // hide selected ll colors $('.js-palette-select').addClass('hide'); // show select color $this.find('.js-palette-select').removeClass('hide'); - } + }, }); Template.createLabelPopup.events({ // Create the new label - 'submit .create-label': function(evt, tpl) { - var name = tpl.$('#labelName').val().trim(); - var boardId = Session.get('currentBoard'); - var color = Blaze.getData(tpl.find('.fa-check')).color; + 'submit .create-label'(evt, tpl) { + const name = tpl.$('#labelName').val().trim(); + const boardId = Session.get('currentBoard'); + const color = Blaze.getData(tpl.find('.fa-check')).color; Boards.update(boardId, { $push: { labels: { + name, + color, _id: Random.id(6), - name: name, - color: color - } - } + }, + }, }); Popup.back(); evt.preventDefault(); - } + }, }); Template.editLabelPopup.events({ 'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() { - var boardId = Session.get('currentBoard'); + const boardId = Session.get('currentBoard'); Boards.update(boardId, { $pull: { labels: { - _id: this._id - } - } + _id: this._id, + }, + }, }); Popup.back(2); }), - 'submit .edit-label': function(evt, tpl) { + 'submit .edit-label'(evt, tpl) { evt.preventDefault(); - var name = tpl.$('#labelName').val().trim(); - var boardId = Session.get('currentBoard'); - var getLabel = Utils.getLabelIndex(boardId, this._id); - var color = Blaze.getData(tpl.find('.fa-check')).color; + const name = tpl.$('#labelName').val().trim(); + const boardId = Session.get('currentBoard'); + const getLabel = Utils.getLabelIndex(boardId, this._id); + const color = Blaze.getData(tpl.find('.fa-check')).color; - var $set = {}; - $set[getLabel.key('name')] = name; - $set[getLabel.key('color')] = color; - - Boards.update(boardId, { $set: $set }); + Boards.update(boardId, { + $set: { + [getLabel.key('name')]: name, + [getLabel.key('color')]: color, + }, + }); Popup.back(); - } + }, }); Template.cardLabelsPopup.helpers({ - isLabelSelected: function(cardId) { + isLabelSelected(cardId) { return _.contains(Cards.findOne(cardId).labelIds, this._id); - } + }, }); diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 70751152..a98b5730 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -3,7 +3,7 @@ // }); BlazeComponent.extendComponent({ - template: function() { + template() { return 'minicard'; - } + }, }).register('minicard'); diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 3b602f43..1b54741c 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -1,14 +1,16 @@ +const { calculateIndex } = Utils; + BlazeComponent.extendComponent({ - template: function() { + template() { return 'list'; }, - // Proxies - openForm: function(options) { + // Proxy + openForm(options) { this.componentChildren('listBody')[0].openForm(options); }, - onCreated: function() { + onCreated() { this.newCardFormIsVisible = new ReactiveVar(true); }, @@ -19,28 +21,27 @@ BlazeComponent.extendComponent({ // By calling asking the sortable library to cancel its move on the `stop` // callback, we basically solve all issues related to reactive updates. A // comment below provides further details. - onRendered: function() { - var self = this; - if (! Meteor.user() || ! Meteor.user().isBoardMember()) + onRendered() { + if (!Meteor.user() || !Meteor.user().isBoardMember()) return; - var boardComponent = self.componentParent(); - var itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; - var $cards = self.$('.js-minicards'); + const boardComponent = this.componentParent(); + const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; + const $cards = this.$('.js-minicards'); $cards.sortable({ connectWith: '.js-minicards', tolerance: 'pointer', appendTo: 'body', - helper: function(evt, item) { - var helper = item.clone(); + helper(evt, item) { + const helper = item.clone(); if (MultiSelection.isActive()) { - var andNOthers = $cards.find('.js-minicard.is-checked').length - 1; + const andNOthers = $cards.find('.js-minicard.is-checked').length - 1; if (andNOthers > 0) { helper.append($(Blaze.toHTML(HTML.DIV( // XXX Super bad class name {'class': 'and-n-other'}, // XXX Need to translate - 'and ' + andNOthers + ' other cards.' + `and ${andNOthers} other cards.` )))); } } @@ -50,19 +51,19 @@ BlazeComponent.extendComponent({ items: itemsSelector, scroll: false, placeholder: 'minicard-wrapper placeholder', - start: function(evt, ui) { + start(evt, ui) { ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup'); boardComponent.setIsDragging(true); }, - stop: function(evt, ui) { + stop(evt, ui) { // To attribute the new index number, we need to get the DOM element // of the previous and the following card -- if any. - var prevCardDom = ui.item.prev('.js-minicard').get(0); - var nextCardDom = ui.item.next('.js-minicard').get(0); - var nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; - var sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards); - var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; + const prevCardDom = ui.item.prev('.js-minicard').get(0); + const nextCardDom = ui.item.next('.js-minicard').get(0); + const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; + const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards); + const listId = Blaze.getData(ui.item.parents('.list').get(0))._id; // Normally the jquery-ui sortable library moves the dragged DOM element // to its new position, which disrupts Blaze reactive updates mechanism @@ -74,53 +75,53 @@ BlazeComponent.extendComponent({ $cards.sortable('cancel'); if (MultiSelection.isActive()) { - Cards.find(MultiSelection.getMongoSelector()).forEach(function(c, i) { + Cards.find(MultiSelection.getMongoSelector()).forEach((c, i) => { Cards.update(c._id, { $set: { - listId: listId, - sort: sortIndex.base + i * sortIndex.increment - } + listId, + sort: sortIndex.base + i * sortIndex.increment, + }, }); }); } else { - var cardDomElement = ui.item.get(0); - var cardId = Blaze.getData(cardDomElement)._id; + const cardDomElement = ui.item.get(0); + const cardId = Blaze.getData(cardDomElement)._id; Cards.update(cardId, { $set: { - listId: listId, - sort: sortIndex.base - } + listId, + sort: sortIndex.base, + }, }); } boardComponent.setIsDragging(false); - } + }, }); // We want to re-run this function any time a card is added. - self.autorun(function() { - var currentBoardId = Tracker.nonreactive(function() { + this.autorun(() => { + const currentBoardId = Tracker.nonreactive(() => { return Session.get('currentBoard'); }); Cards.find({ boardId: currentBoardId }).fetch(); - Tracker.afterFlush(function() { + Tracker.afterFlush(() => { $cards.find(itemsSelector).droppable({ hoverClass: 'draggable-hover-card', accept: '.js-member,.js-label', - drop: function(event, ui) { - var cardId = Blaze.getData(this)._id; - var addToSet; + drop(event, ui) { + const cardId = Blaze.getData(this)._id; + let addToSet; if (ui.draggable.hasClass('js-member')) { - var memberId = Blaze.getData(ui.draggable.get(0)).userId; + const memberId = Blaze.getData(ui.draggable.get(0)).userId; addToSet = { members: memberId }; } else { - var labelId = Blaze.getData(ui.draggable.get(0))._id; + const labelId = Blaze.getData(ui.draggable.get(0))._id; addToSet = { labelIds: labelId }; } Cards.update(cardId, { $addToSet: addToSet }); - } + }, }); }); }); - } + }, }).register('list'); diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 2b561ae3..cc8c94cc 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,46 +1,46 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'listBody'; }, - mixins: function() { + mixins() { return [Mixins.PerfectScrollbar]; }, - openForm: function(options) { + openForm(options) { options = options || {}; options.position = options.position || 'top'; - var forms = this.componentChildren('inlinedForm'); - var form = _.find(forms, function(component) { + const forms = this.componentChildren('inlinedForm'); + let form = _.find(forms, (component) => { return component.data().position === options.position; }); - if (! form && forms.length > 0) { + if (!form && forms.length > 0) { form = forms[0]; } form.open(); }, - addCard: function(evt) { + addCard(evt) { evt.preventDefault(); - var textarea = $(evt.currentTarget).find('textarea'); - var title = textarea.val(); - var position = Blaze.getData(evt.currentTarget).position; - var sortIndex; - var firstCard = this.find('.js-minicard:first'); - var lastCard = this.find('.js-minicard:last'); + const firstCardDom = this.find('.js-minicard:first'); + const lastCardDom = this.find('.js-minicard:last'); + const textarea = $(evt.currentTarget).find('textarea'); + const title = textarea.val(); + const position = Blaze.getData(evt.currentTarget).position; + let sortIndex; if (position === 'top') { - sortIndex = Utils.calculateIndex(null, firstCard).base; + sortIndex = Utils.calculateIndex(null, firstCardDom).base; } else if (position === 'bottom') { - sortIndex = Utils.calculateIndex(lastCard, null).base; + sortIndex = Utils.calculateIndex(lastCardDom, null).base; } if ($.trim(title)) { - var _id = Cards.insert({ - title: title, + const _id = Cards.insert({ + title, listId: this.data()._id, boardId: this.data().board()._id, - sort: sortIndex + sort: sortIndex, }); // In case the filter is active we need to add the newly inserted card in // the list of exceptions -- cards that are not filtered. Otherwise the @@ -56,18 +56,18 @@ BlazeComponent.extendComponent({ } }, - scrollToBottom: function() { - var container = this.firstNode(); + scrollToBottom() { + const container = this.firstNode(); $(container).animate({ - scrollTop: container.scrollHeight + scrollTop: container.scrollHeight, }); }, - clickOnMiniCard: function(evt) { + clickOnMiniCard(evt) { if (MultiSelection.isActive() || evt.shiftKey) { evt.stopImmediatePropagation(); evt.preventDefault(); - var methodName = evt.shiftKey ? 'toogleRange' : 'toogle'; + const methodName = evt.shiftKey ? 'toogleRange' : 'toogle'; MultiSelection[methodName](this.currentData()._id); // If the card is already selected, we want to de-select it. @@ -80,36 +80,36 @@ BlazeComponent.extendComponent({ } }, - cardIsSelected: function() { + cardIsSelected() { return Session.equals('currentCard', this.currentData()._id); }, - toggleMultiSelection: function(evt) { + toggleMultiSelection(evt) { evt.stopPropagation(); evt.preventDefault(); MultiSelection.toogle(this.currentData()._id); }, - events: function() { + events() { return [{ 'click .js-minicard': this.clickOnMiniCard, 'click .js-toggle-multi-selection': this.toggleMultiSelection, 'click .open-minicard-composer': this.scrollToBottom, - submit: this.addCard + submit: this.addCard, }]; - } + }, }).register('listBody'); BlazeComponent.extendComponent({ - template: function() { + template() { return 'addCardForm'; }, - pressKey: function(evt) { + pressKey(evt) { // Pressing Enter should submit the card if (evt.keyCode === 13) { evt.preventDefault(); - var $form = $(evt.currentTarget).closest('form'); + const $form = $(evt.currentTarget).closest('form'); // XXX For some reason $form.submit() does not work (it's probably a bug // of blaze-component related to the fact that the submit event is non- // bubbling). This is why we click on the submit button instead -- which @@ -120,24 +120,24 @@ BlazeComponent.extendComponent({ // in the reverse order } else if (evt.keyCode === 9) { evt.preventDefault(); - var isReverse = evt.shiftKey; - var list = $('#js-list-' + this.data().listId); - var listSelector = '.js-list:not(.js-list-composer)'; - var nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); + const isReverse = evt.shiftKey; + const list = $(`#js-list-${this.data().listId}`); + const listSelector = '.js-list:not(.js-list-composer)'; + let nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); // If there is no next list, loop back to the beginning. - if (! nextList) { + if (!nextList) { nextList = $(listSelector + (isReverse ? ':last' : ':first')).get(0); } BlazeComponent.getComponentForElement(nextList).openForm({ - position:this.data().position + position:this.data().position, }); } }, - events: function() { + events() { return [{ - keydown: this.pressKey + keydown: this.pressKey, }]; - } + }, }).register('addCardForm'); diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index cd4df276..9431b461 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -1,78 +1,78 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'listHeader'; }, - editTitle: function(evt) { + editTitle(evt) { evt.preventDefault(); - var form = this.componentChildren('inlinedForm')[0]; - var newTitle = form.getValue(); + const form = this.componentChildren('inlinedForm')[0]; + const newTitle = form.getValue(); if ($.trim(newTitle)) { Lists.update(this.currentData()._id, { $set: { - title: newTitle - } + title: newTitle, + }, }); } }, - events: function() { + events() { return [{ 'click .js-open-list-menu': Popup.open('listAction'), - submit: this.editTitle + submit: this.editTitle, }]; - } + }, }).register('listHeader'); Template.listActionPopup.events({ - 'click .js-add-card': function() { - var listDom = document.getElementById('js-list-' + this._id); - var listComponent = BlazeComponent.getComponentForElement(listDom); + 'click .js-add-card'() { + const listDom = document.getElementById(`js-list-${this._id}`); + const listComponent = BlazeComponent.getComponentForElement(listDom); listComponent.openForm({ position: 'top' }); Popup.close(); }, - 'click .js-list-subscribe': function() {}, - 'click .js-select-cards': function() { - var cardIds = Cards.find( + 'click .js-list-subscribe'() {}, + 'click .js-select-cards'() { + const cardIds = Cards.find( {listId: this._id}, {fields: { _id: 1 }} - ).map(function(card) { return card._id; }); + ).map((card) => card._id); MultiSelection.add(cardIds); Popup.close(); }, 'click .js-move-cards': Popup.open('listMoveCards'), - 'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', function() { - Cards.find({listId: this._id}).forEach(function(card) { + 'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', () => { + Cards.find({listId: this._id}).forEach((card) => { Cards.update(card._id, { $set: { - archived: true - } + archived: true, + }, }); }); Popup.close(); }), - 'click .js-close-list': function(evt) { + 'click .js-close-list'(evt) { evt.preventDefault(); Lists.update(this._id, { $set: { - archived: true - } + archived: true, + }, }); Popup.close(); - } + }, }); Template.listMoveCardsPopup.events({ - 'click .js-select-list': function() { - var fromList = Template.parentData(2).data._id; - var toList = this._id; - Cards.find({listId: fromList}).forEach(function(card) { + 'click .js-select-list'() { + const fromList = Template.parentData(2).data._id; + const toList = this._id; + Cards.find({ listId: fromList }).forEach((card) => { Cards.update(card._id, { $set: { - listId: toList - } + listId: toList, + }, }); }); Popup.close(); - } + }, }); diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 7966ff60..c34539b3 100644 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -1,7 +1,7 @@ -var dropdownMenuIsOpened = false; +let dropdownMenuIsOpened = false; -Template.editor.onRendered(function() { - var $textarea = this.$('textarea'); +Template.editor.onRendered(() => { + const $textarea = this.$('textarea'); autosize($textarea); @@ -9,39 +9,40 @@ Template.editor.onRendered(function() { // Emojies { match: /\B:([\-+\w]*)$/, - search: function(term, callback) { - callback($.map(Emoji.values, function(emoji) { + search(term, callback) { + callback($.map(Emoji.values, (emoji) => { return emoji.indexOf(term) === 0 ? emoji : null; })); }, - template: function(value) { - var image = '<img src="' + Emoji.baseImagePath + value + '.png"></img>'; + template(value) { + const imgSrc = Emoji.baseImagePath + value; + const image = `<img src="${imgSrc}.png" />`; return image + value; }, - replace: function(value) { - return ':' + value + ':'; + replace(value) { + return `:${value}:`; }, - index: 1 + index: 1, }, // User mentions { match: /\B@(\w*)$/, - search: function(term, callback) { - var currentBoard = Boards.findOne(Session.get('currentBoard')); - callback($.map(currentBoard.members, function(member) { - var username = Users.findOne(member.userId).username; + search(term, callback) { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + callback($.map(currentBoard.members, (member) => { + const username = Users.findOne(member.userId).username; return username.indexOf(term) === 0 ? username : null; })); }, - template: function(value) { + template(value) { return value; }, - replace: function(username) { - return '@' + username + ' '; + replace(username) { + return `@${username} `; }, - index: 1 - } + index: 1, + }, ]); // Since commit d474017 jquery-textComplete automatically closes a potential @@ -51,27 +52,27 @@ Template.editor.onRendered(function() { // 'open' and 'hide' events, and create a ghost escapeAction when the dropdown // is opened (and rely on textComplete to execute the actual action). $textarea.on({ - 'textComplete:show': function() { + 'textComplete:show'() { dropdownMenuIsOpened = true; }, - 'textComplete:hide': function() { - Tracker.afterFlush(function() { + 'textComplete:hide'() { + Tracker.afterFlush(() => { dropdownMenuIsOpened = false; }); - } + }, }); }); EscapeActions.register('textcomplete', - function() {}, - function() { return dropdownMenuIsOpened; } + () => {}, + () => dropdownMenuIsOpened ); Template.viewer.events({ // Viewer sometimes have click-able wrapper around them (for instance to edit // the corresponding text). Clicking a link shouldn't fire these actions, stop // we stop these event at the viewer component level. - 'click a': function(evt) { + 'click a'(evt) { evt.stopPropagation(); // XXX We hijack the build-in browser action because we currently don't have @@ -79,7 +80,7 @@ Template.viewer.events({ // handled by a third party package that we can't configure easily. Fix that // by using directly `_blank` attribute in the rendered HTML. evt.preventDefault(); - let href = evt.currentTarget.href; + const href = evt.currentTarget.href; window.open(href, '_blank'); - } + }, }); diff --git a/client/components/main/header.js b/client/components/main/header.js index 7b7e431f..30cb2d8f 100644 --- a/client/components/main/header.js +++ b/client/components/main/header.js @@ -1,14 +1,14 @@ Template.header.helpers({ // Reactively set the color of the page from the color of the current board. - headerTemplate: function() { + headerTemplate() { return 'headerBoard'; }, - wrappedHeader: function() { - return ! Session.get('currentBoard'); - } + wrappedHeader() { + return !Session.get('currentBoard'); + }, }); Template.header.events({ - 'click .js-create-board': Popup.open('createBoard') + 'click .js-create-board': Popup.open('createBoard'), }); diff --git a/client/components/main/helpers.js b/client/components/main/helpers.js deleted file mode 100644 index 91205351..00000000 --- a/client/components/main/helpers.js +++ /dev/null @@ -1,65 +0,0 @@ -var Helpers = { - error: function() { - return Session.get('error'); - }, - - toLowerCase: function(text) { - return text && text.toLowerCase(); - }, - - toUpperCase: function(text) { - return text && text.toUpperCase(); - }, - - firstChar: function(text) { - return text && text[0].toUpperCase(); - }, - - session: function(prop) { - return Session.get(prop); - }, - - getUser: function(userId) { - return Users.findOne(userId); - } -}; - -// Register all Helpers -_.each(Helpers, function(fn, name) { Blaze.registerHelper(name, fn); }); - -// XXX I believe we should compute a HTML rendered field on the server that -// would handle markdown, emojies and user mentions. We can simply have two -// fields, one source, and one compiled version (in HTML) and send only the -// compiled version to most users -- who don't need to edit. -// In the meantime, all the transformation are done on the client using the -// Blaze API. -var at = HTML.CharRef({html: '@', str: '@'}); -Blaze.Template.registerHelper('mentions', new Template('mentions', function() { - var view = this; - var content = Blaze.toHTML(view.templateContentBlock); - var currentBoard = Session.get('currentBoard'); - var knowedUsers = _.map(currentBoard.members, function(member) { - member.username = Users.findOne(member.userId).username; - return member; - }); - - var mentionRegex = /\B@(\w*)/gi; - var currentMention, knowedUser, href, linkClass, linkValue, link; - while (!! (currentMention = mentionRegex.exec(content))) { - - knowedUser = _.findWhere(knowedUsers, { username: currentMention[1] }); - if (! knowedUser) - continue; - - linkValue = [' ', at, knowedUser.username]; - href = Router.url('Profile', { username: knowedUser.username }); - linkClass = 'atMention'; - if (knowedUser.userId === Meteor.userId()) - linkClass += ' me'; - link = HTML.A({ href: href, 'class': linkClass }, linkValue); - - content = content.replace(currentMention[0], Blaze.toHTML(link)); - } - - return HTML.Raw(content); -})); diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index 11a43313..ab62e76a 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -1,13 +1,13 @@ -Meteor.subscribe('boards') +Meteor.subscribe('boards'); -BlazeLayout.setRoot('body') +BlazeLayout.setRoot('body'); -Template.userFormsLayout.onRendered(function() { - EscapeActions.executeAll() -}) +Template.userFormsLayout.onRendered(() => { + EscapeActions.executeAll(); +}); Template.defaultLayout.events({ 'click .js-close-modal': () => { - Modal.close() - } -}) + Modal.close(); + }, +}); diff --git a/client/components/main/popup.js b/client/components/main/popup.js index 5fc4e979..ba20a6d3 100644 --- a/client/components/main/popup.js +++ b/client/components/main/popup.js @@ -1,11 +1,11 @@ Popup.template.events({ - 'click .js-back-view': function() { + 'click .js-back-view'() { Popup.back(); }, - 'click .js-close-pop-over': function() { + 'click .js-close-pop-over'() { Popup.close(); }, - 'click .js-confirm': function() { + 'click .js-confirm'() { this.__afterConfirmAction.call(this); }, // This handler intends to solve a pretty tricky bug with our popup @@ -18,22 +18,22 @@ Popup.template.events({ // in moving the whole popup container outside of the popup wrapper. To // disable this behavior we have to manually reset the scrollLeft position // whenever it is modified. - 'scroll .content-wrapper': function(evt) { + 'scroll .content-wrapper'(evt) { evt.currentTarget.scrollLeft = 0; - } + }, }); // When a popup content is removed (ie, when the user press the "back" button), // we need to wait for the container translation to end before removing the // actual DOM element. For that purpose we use the undocumented `_uihooks` API. -Popup.template.onRendered(function() { - var container = this.find('.content-container'); +Popup.template.onRendered(() => { + const container = this.find('.content-container'); container._uihooks = { - removeElement: function(node) { + removeElement(node) { $(node).addClass('no-height'); - $(container).one(CSSEvents.transitionend, function() { + $(container).one(CSSEvents.transitionend, () => { node.parentNode.removeChild(node); }); - } + }, }; }); diff --git a/client/components/mixins/infiniteScrolling.js b/client/components/mixins/infiniteScrolling.js index df3b8901..0afb33f9 100644 --- a/client/components/mixins/infiniteScrolling.js +++ b/client/components/mixins/infiniteScrolling.js @@ -1,37 +1,37 @@ -var peakAnticipation = 200; +const peakAnticipation = 200; Mixins.InfiniteScrolling = BlazeComponent.extendComponent({ - onCreated: function() { + onCreated() { this._nextPeak = Infinity; }, - setNextPeak: function(v) { + setNextPeak(v) { this._nextPeak = v; }, - getNextPeak: function() { + getNextPeak() { return this._nextPeak; }, - resetNextPeak: function() { + resetNextPeak() { this._nextPeak = Infinity; }, // To be overwritten by consumers of this mixin - reachNextPeak: function() { + reachNextPeak() { }, - events: function() { + events() { return [{ - scroll: function(evt) { - var domElement = evt.currentTarget; - var altitude = domElement.scrollTop + domElement.offsetHeight; + scroll(evt) { + const domElement = evt.currentTarget; + let altitude = domElement.scrollTop + domElement.offsetHeight; altitude += peakAnticipation; if (altitude >= this.callFirstWith(null, 'getNextPeak')) { this.callFirstWith(null, 'reachNextPeak'); } - } + }, }]; - } + }, }); diff --git a/client/components/mixins/perfectScrollbar.js b/client/components/mixins/perfectScrollbar.js index 510a4563..f652f043 100644 --- a/client/components/mixins/perfectScrollbar.js +++ b/client/components/mixins/perfectScrollbar.js @@ -1,14 +1,12 @@ Mixins.PerfectScrollbar = BlazeComponent.extendComponent({ - onRendered: function() { - var component = this.mixinParent(); - var domElement = component.find('.js-perfect-scrollbar'); + onRendered() { + const component = this.mixinParent(); + const domElement = component.find('.js-perfect-scrollbar'); Ps.initialize(domElement); // XXX We should create an event map to be consistent with other components // but since BlazeComponent doesn't merge Mixins events transparently I // prefered to use a jQuery event (which is what an event map ends up doing) - component.$(domElement).on('mouseenter', function() { - Ps.update(domElement); - }); - } + component.$(domElement).on('mouseenter', () => Ps.update(domElement)); + }, }); diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 97b6f2de..51f3e0e4 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,76 +1,76 @@ Sidebar = null; -var defaultView = 'home'; +const defaultView = 'home'; -var viewTitles = { +const viewTitles = { filter: 'filter-cards', multiselection: 'multi-selection', - archives: 'archives' + archives: 'archives', }; BlazeComponent.extendComponent({ - template: function() { + template() { return 'sidebar'; }, - mixins: function() { + mixins() { return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; }, - onCreated: function() { - this._isOpen = new ReactiveVar(! Session.get('currentCard')); + onCreated() { + this._isOpen = new ReactiveVar(!Session.get('currentCard')); this._view = new ReactiveVar(defaultView); Sidebar = this; }, - onDestroyed: function() { + onDestroyed() { Sidebar = null; }, - isOpen: function() { + isOpen() { return this._isOpen.get(); }, - open: function() { - if (! this._isOpen.get()) { + open() { + if (!this._isOpen.get()) { this._isOpen.set(true); EscapeActions.executeUpTo('detailsPane'); } }, - hide: function() { + hide() { if (this._isOpen.get()) { this._isOpen.set(false); } }, - toogle: function() { - this._isOpen.set(! this._isOpen.get()); + toogle() { + this._isOpen.set(!this._isOpen.get()); }, - calculateNextPeak: function() { - var altitude = this.find('.js-board-sidebar-content').scrollHeight; + calculateNextPeak() { + const altitude = this.find('.js-board-sidebar-content').scrollHeight; this.callFirstWith(this, 'setNextPeak', altitude); }, - reachNextPeak: function() { - var activitiesComponent = this.componentChildren('activities')[0]; + reachNextPeak() { + const activitiesComponent = this.componentChildren('activities')[0]; activitiesComponent.loadNextPage(); }, - isTongueHidden: function() { + isTongueHidden() { return this.isOpen() && this.getView() !== defaultView; }, - scrollTop: function() { + scrollTop() { this.$('.js-board-sidebar-content').scrollTop(0); }, - getView: function() { + getView() { return this._view.get(); }, - setView: function(view) { + setView(view) { view = _.isString(view) ? view : defaultView; if (this._view.get() !== view) { this._view.set(view); @@ -80,83 +80,84 @@ BlazeComponent.extendComponent({ this.open(); }, - isDefaultView: function() { + isDefaultView() { return this.getView() === defaultView; }, - getViewTemplate: function() { - return this.getView() + 'Sidebar'; + getViewTemplate() { + return `${this.getView()}Sidebar`; }, - getViewTitle: function() { + getViewTitle() { return TAPi18n.__(viewTitles[this.getView()]); }, - events: function() { + events() { // XXX Hacky, we need some kind of `super` - var mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events(); + const mixinEvents = this.getMixin(Mixins.InfiniteScrolling).events(); return mixinEvents.concat([{ 'click .js-toogle-sidebar': this.toogle, - 'click .js-back-home': this.setView + 'click .js-back-home': this.setView, }]); - } + }, }).register('sidebar'); -Blaze.registerHelper('Sidebar', function() { - return Sidebar; -}); +Blaze.registerHelper('Sidebar', () => Sidebar); EscapeActions.register('sidebarView', - function() { Sidebar.setView(defaultView); }, - function() { return Sidebar && Sidebar.getView() !== defaultView; } + () => { Sidebar.setView(defaultView); }, + () => { return Sidebar && Sidebar.getView() !== defaultView; } ); -var getMemberIndex = function(board, searchId) { - for (var i = 0; i < board.members.length; i++) { +function getMemberIndex(board, searchId) { + for (let i = 0; i < board.members.length; i++) { if (board.members[i].userId === searchId) return i; } throw new Meteor.Error('Member not found'); -}; +} Template.memberPopup.helpers({ - user: function() { + user() { return Users.findOne(this.userId); }, - memberType: function() { - var type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal'; + memberType() { + const type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal'; return TAPi18n.__(type).toLowerCase(); - } + }, }); Template.memberPopup.events({ - 'click .js-filter-member': function() { + 'click .js-filter-member'() { Filter.members.toogle(this.userId); Popup.close(); }, 'click .js-change-role': Popup.open('changePermissions'), 'click .js-remove-member': Popup.afterConfirm('removeMember', function() { - var currentBoard = Boards.findOne(Session.get('currentBoard')); - var memberIndex = getMemberIndex(currentBoard, this.userId); - var setQuery = {}; - setQuery[['members', memberIndex, 'isActive'].join('.')] = false; - Boards.update(currentBoard._id, { $set: setQuery }); + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const memberIndex = getMemberIndex(currentBoard, this.userId); + + Boards.update(currentBoard._id, { + $set: { + [`members.${memberIndex}.isActive`]: false, + }, + }); Popup.close(); }), - 'click .js-leave-member': function() { + 'click .js-leave-member'() { // XXX Not implemented Popup.close(); - } + }, }); Template.membersWidget.events({ 'click .js-member': Popup.open('member'), - 'click .js-manage-board-members': Popup.open('addMember') + 'click .js-manage-board-members': Popup.open('addMember'), }); Template.labelsWidget.events({ 'click .js-label': Popup.open('editLabel'), - 'click .js-add-label': Popup.open('createLabel') + 'click .js-add-label': Popup.open('createLabel'), }); // Board members can assign people or labels by drag-dropping elements from the @@ -164,99 +165,102 @@ Template.labelsWidget.events({ // plugin any time a draggable member or label is modified or removed we use a // autorun function and register a dependency on the both members and labels // fields of the current board document. -var draggableMembersLabelsWidgets = function() { - var self = this; - if (! Meteor.user() || ! Meteor.user().isBoardMember()) +function draggableMembersLabelsWidgets() { + if (!Meteor.user() || !Meteor.user().isBoardMember()) return; - self.autorun(function() { - var currentBoardId = Tracker.nonreactive(function() { + this.autorun(() => { + const currentBoardId = Tracker.nonreactive(() => { return Session.get('currentBoard'); }); Boards.findOne(currentBoardId, { fields: { members: 1, - labels: 1 - } + labels: 1, + }, }); - Tracker.afterFlush(function() { - self.$('.js-member,.js-label').draggable({ + Tracker.afterFlush(() => { + this.$('.js-member,.js-label').draggable({ appendTo: 'body', helper: 'clone', revert: 'invalid', revertDuration: 150, snap: false, snapMode: 'both', - start: function() { + start() { EscapeActions.executeUpTo('popup-back'); - } + }, }); }); }); -}; +} Template.membersWidget.onRendered(draggableMembersLabelsWidgets); Template.labelsWidget.onRendered(draggableMembersLabelsWidgets); Template.addMemberPopup.helpers({ - isBoardMember: function() { - var user = Users.findOne(this._id); + isBoardMember() { + const user = Users.findOne(this._id); return user && user.isBoardMember(); - } + }, }); Template.addMemberPopup.events({ - 'click .pop-over-member-list li:not(.disabled)': function() { - var userId = this._id; - var currentBoard = Boards.findOne(Session.get('currentBoard')); - var currentMembersIds = _.pluck(currentBoard.members, 'userId'); + 'click .pop-over-member-list li:not(.disabled)'() { + const userId = this._id; + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const currentMembersIds = _.pluck(currentBoard.members, 'userId'); if (currentMembersIds.indexOf(userId) === -1) { Boards.update(currentBoard._id, { $push: { members: { - userId: userId, + userId, isAdmin: false, - isActive: true - } - } + isActive: true, + }, + }, }); } else { - var memberIndex = getMemberIndex(currentBoard, userId); - var setQuery = {}; - setQuery[['members', memberIndex, 'isActive'].join('.')] = true; - Boards.update(currentBoard._id, { $set: setQuery }); + const memberIndex = getMemberIndex(currentBoard, userId); + + Boards.update(currentBoard._id, { + $set: { + [`members.${memberIndex}.isActive`]: true, + }, + }); } Popup.close(); - } + }, }); -Template.addMemberPopup.onRendered(function() { +Template.addMemberPopup.onRendered(() => { this.find('.js-search-member input').focus(); }); Template.changePermissionsPopup.events({ - 'click .js-set-admin, click .js-set-normal': function(event) { - var currentBoard = Boards.findOne(Session.get('currentBoard')); - var memberIndex = getMemberIndex(currentBoard, this.user._id); - var isAdmin = $(event.currentTarget).hasClass('js-set-admin'); - var setQuery = {}; - setQuery[['members', memberIndex, 'isAdmin'].join('.')] = isAdmin; + 'click .js-set-admin, click .js-set-normal'(event) { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const memberIndex = getMemberIndex(currentBoard, this.user._id); + const isAdmin = $(event.currentTarget).hasClass('js-set-admin'); + Boards.update(currentBoard._id, { - $set: setQuery + $set: { + [`members.${memberIndex}.isAdmin`]: isAdmin, + }, }); Popup.back(1); - } + }, }); Template.changePermissionsPopup.helpers({ - isAdmin: function() { + isAdmin() { return this.user.isBoardAdmin(); }, - isLastAdmin: function() { - if (! this.user.isBoardAdmin()) + isLastAdmin() { + if (!this.user.isBoardAdmin()) return false; - var currentBoard = Boards.findOne(Session.get('currentBoard')); - var nbAdmins = _.where(currentBoard.members, { isAdmin: true }).length; + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const nbAdmins = _.where(currentBoard.members, { isAdmin: true }).length; return nbAdmins === 1; - } + }, }); diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js index 4e44f3f4..f2597c3c 100644 --- a/client/components/sidebar/sidebarArchives.js +++ b/client/components/sidebar/sidebarArchives.js @@ -1,46 +1,46 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'archivesSidebar'; }, - tabs: function() { + tabs() { return [ { name: TAPi18n.__('cards'), slug: 'cards' }, - { name: TAPi18n.__('lists'), slug: 'lists' } - ] + { name: TAPi18n.__('lists'), slug: 'lists' }, + ]; }, - archivedCards: function() { + archivedCards() { return Cards.find({ archived: true }); }, - archivedLists: function() { + archivedLists() { return Lists.find({ archived: true }); }, - cardIsInArchivedList: function() { + cardIsInArchivedList() { return this.currentData().list().archived; }, - onRendered: function() { - //XXX We should support dragging a card from the sidebar to the board + onRendered() { + // XXX We should support dragging a card from the sidebar to the board }, - events: function() { + events() { return [{ - 'click .js-restore-card': function() { - var cardId = this.currentData()._id; + 'click .js-restore-card'() { + const cardId = this.currentData()._id; Cards.update(cardId, {$set: {archived: false}}); }, 'click .js-delete-card': Popup.afterConfirm('cardDelete', function() { - var cardId = this._id; + const cardId = this._id; Cards.remove(cardId); Popup.close(); }), - 'click .js-restore-list': function() { - var listId = this.currentData()._id; + 'click .js-restore-list'() { + const listId = this.currentData()._id; Lists.update(listId, {$set: {archived: false}}); - } + }, }]; - } + }, }).register('archivesSidebar'); diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js index c7b28443..7fce7286 100644 --- a/client/components/sidebar/sidebarFilters.js +++ b/client/components/sidebar/sidebarFilters.js @@ -1,136 +1,136 @@ BlazeComponent.extendComponent({ - template: function() { + template() { return 'filterSidebar'; }, - events: function() { + events() { return [{ - 'click .js-toggle-label-filter': function(evt) { + 'click .js-toggle-label-filter'(evt) { evt.preventDefault(); Filter.labelIds.toogle(this.currentData()._id); Filter.resetExceptions(); }, - 'click .js-toogle-member-filter': function(evt) { + 'click .js-toogle-member-filter'(evt) { evt.preventDefault(); Filter.members.toogle(this.currentData()._id); Filter.resetExceptions(); }, - 'click .js-clear-all': function(evt) { + 'click .js-clear-all'(evt) { evt.preventDefault(); Filter.reset(); }, - 'click .js-filter-to-selection': function(evt) { + 'click .js-filter-to-selection'(evt) { evt.preventDefault(); - var selectedCards = Cards.find(Filter.mongoSelector()).map(function(c) { + const selectedCards = Cards.find(Filter.mongoSelector()).map((c) => { return c._id; }); MultiSelection.add(selectedCards); - } + }, }]; - } + }, }).register('filterSidebar'); -var updateSelectedCards = function(query) { - Cards.find(MultiSelection.getMongoSelector()).forEach(function(card) { +function updateSelectedCards(query) { + Cards.find(MultiSelection.getMongoSelector()).forEach((card) => { Cards.update(card._id, query); }); -}; +} BlazeComponent.extendComponent({ - template: function() { + template() { return 'multiselectionSidebar'; }, - mapSelection: function(kind, _id) { - return Cards.find(MultiSelection.getMongoSelector()).map(function(card) { - var methodName = kind === 'label' ? 'hasLabel' : 'isAssigned'; + mapSelection(kind, _id) { + return Cards.find(MultiSelection.getMongoSelector()).map((card) => { + const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned'; return card[methodName](_id); }); }, - allSelectedElementHave: function(kind, _id) { + allSelectedElementHave(kind, _id) { if (MultiSelection.isEmpty()) return false; else return _.every(this.mapSelection(kind, _id)); }, - someSelectedElementHave: function(kind, _id) { + someSelectedElementHave(kind, _id) { if (MultiSelection.isEmpty()) return false; else return _.some(this.mapSelection(kind, _id)); }, - events: function() { + events() { return [{ - 'click .js-toggle-label-multiselection': function(evt) { - var labelId = this.currentData()._id; - var mappedSelection = this.mapSelection('label', labelId); - var operation; + 'click .js-toggle-label-multiselection'(evt) { + const labelId = this.currentData()._id; + const mappedSelection = this.mapSelection('label', labelId); + let operation; if (_.every(mappedSelection)) operation = '$pull'; - else if (_.every(mappedSelection, function(bool) { return ! bool; })) + else if (_.every(mappedSelection, (bool) => !bool)) operation = '$addToSet'; else { - var popup = Popup.open('disambiguateMultiLabel'); + const popup = Popup.open('disambiguateMultiLabel'); // XXX We need to have a better integration between the popup and the // UI components systems. return popup.call(this.currentData(), evt); } - var query = {}; - query[operation] = { - labelIds: labelId - }; - updateSelectedCards(query); + updateSelectedCards({ + [operation]: { + labelIds: labelId, + }, + }); }, - 'click .js-toogle-member-multiselection': function(evt) { - var memberId = this.currentData()._id; - var mappedSelection = this.mapSelection('member', memberId); - var operation; + 'click .js-toogle-member-multiselection'(evt) { + const memberId = this.currentData()._id; + const mappedSelection = this.mapSelection('member', memberId); + let operation; if (_.every(mappedSelection)) operation = '$pull'; - else if (_.every(mappedSelection, function(bool) { return ! bool; })) + else if (_.every(mappedSelection, (bool) => !bool)) operation = '$addToSet'; else { - var popup = Popup.open('disambiguateMultiMember'); + const popup = Popup.open('disambiguateMultiMember'); // XXX We need to have a better integration between the popup and the // UI components systems. return popup.call(this.currentData(), evt); } - var query = {}; - query[operation] = { - members: memberId - }; - updateSelectedCards(query); + updateSelectedCards({ + [operation]: { + members: memberId, + }, + }); }, - 'click .js-archive-selection': function() { + 'click .js-archive-selection'() { updateSelectedCards({$set: {archived: true}}); - } + }, }]; - } + }, }).register('multiselectionSidebar'); Template.disambiguateMultiLabelPopup.events({ - 'click .js-remove-label': function() { + 'click .js-remove-label'() { updateSelectedCards({$pull: {labelIds: this._id}}); Popup.close(); }, - 'click .js-add-label': function() { + 'click .js-add-label'() { updateSelectedCards({$addToSet: {labelIds: this._id}}); Popup.close(); - } + }, }); Template.disambiguateMultiMemberPopup.events({ - 'click .js-unassign-member': function() { + 'click .js-unassign-member'() { updateSelectedCards({$pull: {members: this._id}}); Popup.close(); }, - 'click .js-assign-member': function() { + 'click .js-assign-member'() { updateSelectedCards({$addToSet: {members: this._id}}); Popup.close(); - } + }, }); diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js index a35415e2..b0702468 100644 --- a/client/components/users/userAvatar.js +++ b/client/components/users/userAvatar.js @@ -1,98 +1,98 @@ Meteor.subscribe('my-avatars'); Template.userAvatar.helpers({ - userData: function() { + userData() { return Users.findOne(this.userId, { fields: { profile: 1, - username: 1 - } + username: 1, + }, }); }, - memberType: function() { - var user = Users.findOne(this.userId); + memberType() { + const user = Users.findOne(this.userId); return user && user.isBoardAdmin() ? 'admin' : 'normal'; }, - presenceStatusClassName: function() { - var userPresence = Presences.findOne({ userId: this.userId }); - if (! userPresence) + presenceStatusClassName() { + const userPresence = Presences.findOne({ userId: this.userId }); + if (!userPresence) return 'disconnected'; else if (Session.equals('currentBoard', userPresence.state.currentBoardId)) return 'active'; else return 'idle'; - } + }, }); Template.userAvatar.events({ - 'click .js-change-avatar': Popup.open('changeAvatar') + 'click .js-change-avatar': Popup.open('changeAvatar'), }); Template.userAvatarInitials.helpers({ - initials: function() { - var user = Users.findOne(this.userId); + initials() { + const user = Users.findOne(this.userId); return user && user.getInitials(); }, - viewPortWidth: function() { - var user = Users.findOne(this.userId); + viewPortWidth() { + const user = Users.findOne(this.userId); return (user && user.getInitials().length || 1) * 12; - } + }, }); BlazeComponent.extendComponent({ - template: function() { + template() { return 'changeAvatarPopup'; }, - onCreated: function() { + onCreated() { this.error = new ReactiveVar(''); }, - avatarUrlOptions: function() { + avatarUrlOptions() { return { auth: false, - brokenIsFine: true + brokenIsFine: true, }; }, - uploadedAvatars: function() { + uploadedAvatars() { return Avatars.find({userId: Meteor.userId()}); }, - isSelected: function() { - var userProfile = Meteor.user().profile; - var avatarUrl = userProfile && userProfile.avatarUrl; - var currentAvatarUrl = this.currentData().url(this.avatarUrlOptions()); + isSelected() { + const userProfile = Meteor.user().profile; + const avatarUrl = userProfile && userProfile.avatarUrl; + const currentAvatarUrl = this.currentData().url(this.avatarUrlOptions()); return avatarUrl === currentAvatarUrl; }, - noAvatarUrl: function() { - var userProfile = Meteor.user().profile; - var avatarUrl = userProfile && userProfile.avatarUrl; - return ! avatarUrl; + noAvatarUrl() { + const userProfile = Meteor.user().profile; + const avatarUrl = userProfile && userProfile.avatarUrl; + return !avatarUrl; }, - setAvatar: function(avatarUrl) { + setAvatar(avatarUrl) { Meteor.users.update(Meteor.userId(), { $set: { - 'profile.avatarUrl': avatarUrl - } + 'profile.avatarUrl': avatarUrl, + }, }); }, - setError: function(error) { + setError(error) { this.error.set(error); }, - events: function() { + events() { return [{ - 'click .js-upload-avatar': function() { + 'click .js-upload-avatar'() { this.$('.js-upload-avatar-input').click(); }, - 'change .js-upload-avatar-input': function(evt) { + 'change .js-upload-avatar-input'(evt) { let file, fileUrl; FS.Utility.eachFile(evt, (f) => { @@ -106,71 +106,71 @@ BlazeComponent.extendComponent({ if (fileUrl) { this.setError(''); - let fetchAvatarInterval = window.setInterval(() => { + const fetchAvatarInterval = window.setInterval(() => { $.ajax({ url: fileUrl, success: () => { this.setAvatar(file.url(this.avatarUrlOptions())); window.clearInterval(fetchAvatarInterval); - } + }, }); }, 100); } }, - 'click .js-select-avatar': function() { - var avatarUrl = this.currentData().url(this.avatarUrlOptions()); + 'click .js-select-avatar'() { + const avatarUrl = this.currentData().url(this.avatarUrlOptions()); this.setAvatar(avatarUrl); }, - 'click .js-select-initials': function() { + 'click .js-select-initials'() { this.setAvatar(''); }, - 'click .js-delete-avatar': function() { + 'click .js-delete-avatar'() { Avatars.remove(this.currentData()._id); - } + }, }]; - } + }, }).register('changeAvatarPopup'); Template.cardMembersPopup.helpers({ - isCardMember: function() { - var cardId = Template.parentData()._id; - var cardMembers = Cards.findOne(cardId).members || []; + isCardMember() { + const cardId = Template.parentData()._id; + const cardMembers = Cards.findOne(cardId).members || []; return _.contains(cardMembers, this.userId); }, - user: function() { + user() { return Users.findOne(this.userId); - } + }, }); Template.cardMembersPopup.events({ - 'click .js-select-member': function(evt) { - var cardId = Template.parentData(2).data._id; - var memberId = this.userId; - var operation; + 'click .js-select-member'(evt) { + const cardId = Template.parentData(2).data._id; + const memberId = this.userId; + let operation; if (Cards.find({ _id: cardId, members: memberId}).count() === 0) operation = '$addToSet'; else operation = '$pull'; - var query = {}; - query[operation] = { - members: memberId - }; - Cards.update(cardId, query); + Cards.update(cardId, { + [operation]: { + members: memberId, + }, + }); evt.preventDefault(); - } + }, }); Template.cardMemberPopup.helpers({ - user: function() { + user() { return Users.findOne(this.userId); - } + }, }); Template.cardMemberPopup.events({ - 'click .js-remove-member': function() { + 'click .js-remove-member'() { Cards.update(this.cardId, {$pull: {members: this.userId}}); Popup.close(); }, - 'click .js-edit-profile': Popup.open('editProfile') + 'click .js-edit-profile': Popup.open('editProfile'), }); diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 2110faa6..d8b1ee48 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -1,6 +1,6 @@ Template.headerUserBar.events({ 'click .js-open-header-member-menu': Popup.open('memberMenu'), - 'click .js-change-avatar': Popup.open('changeAvatar') + 'click .js-change-avatar': Popup.open('changeAvatar'), }); Template.memberMenuPopup.events({ @@ -8,58 +8,57 @@ Template.memberMenuPopup.events({ 'click .js-change-avatar': Popup.open('changeAvatar'), 'click .js-change-password': Popup.open('changePassword'), 'click .js-change-language': Popup.open('changeLanguage'), - 'click .js-logout': function(evt) { + 'click .js-logout'(evt) { evt.preventDefault(); AccountsTemplates.logout(); - } + }, }); Template.editProfilePopup.events({ - submit: function(evt, tpl) { + submit(evt, tpl) { evt.preventDefault(); - var fullname = $.trim(tpl.find('.js-profile-fullname').value); - var username = $.trim(tpl.find('.js-profile-username').value); - var initials = $.trim(tpl.find('.js-profile-initials').value); + const fullname = $.trim(tpl.find('.js-profile-fullname').value); + const username = $.trim(tpl.find('.js-profile-username').value); + const initials = $.trim(tpl.find('.js-profile-initials').value); Users.update(Meteor.userId(), {$set: { 'profile.fullname': fullname, - 'profile.initials': initials + 'profile.initials': initials, }}); // XXX We should report the error to the user. if (username !== Meteor.user().username) { Meteor.call('setUsername', username); } Popup.back(); - } + }, }); // XXX For some reason the useraccounts autofocus isnt working in this case. // See https://github.com/meteor-useraccounts/core/issues/384 -Template.changePasswordPopup.onRendered(function() { +Template.changePasswordPopup.onRendered(() => { this.find('#at-field-current_password').focus(); }); Template.changeLanguagePopup.helpers({ - languages: function() { - return _.map(TAPi18n.getLanguages(), function(lang, tag) { - return { - tag: tag, - name: lang.name - }; + languages() { + return _.map(TAPi18n.getLanguages(), (lang, tag) => { + const name = lang.name; + return { tag, name }; }); }, - isCurrentLanguage: function() { + + isCurrentLanguage() { return this.tag === TAPi18n.getLanguage(); - } + }, }); Template.changeLanguagePopup.events({ - 'click .js-set-language': function(evt) { + 'click .js-set-language'(evt) { Users.update(Meteor.userId(), { $set: { - 'profile.language': this.tag - } + 'profile.language': this.tag, + }, }); evt.preventDefault(); - } + }, }); diff --git a/client/config/accounts.js b/client/config/accounts.js index a448d612..df0935f7 100644 --- a/client/config/accounts.js +++ b/client/config/accounts.js @@ -1,11 +1,11 @@ -var passwordField = AccountsTemplates.removeField('password'); -var emailField = AccountsTemplates.removeField('email'); +const passwordField = AccountsTemplates.removeField('password'); +const emailField = AccountsTemplates.removeField('email'); AccountsTemplates.addFields([{ _id: 'username', type: 'text', displayName: 'username', required: true, - minLength: 2 + minLength: 2, }, emailField, passwordField]); AccountsTemplates.configure({ @@ -15,36 +15,34 @@ AccountsTemplates.configure({ enablePasswordChange: true, sendVerificationEmail: true, showForgotPasswordLink: true, - onLogoutHook: function() { - var homePage = 'home'; + onLogoutHook() { + const homePage = 'home'; if (FlowRouter.getRouteName() === homePage) { FlowRouter.reload(); } else { FlowRouter.go(homePage); } - } + }, }); _.each(['signIn', 'signUp', 'resetPwd', 'forgotPwd', 'enrollAccount'], - function(routeName) { - AccountsTemplates.configureRoute(routeName); -}); + (routeName) => AccountsTemplates.configureRoute(routeName)); // We display the form to change the password in a popup window that already // have a title, so we unset the title automatically displayed by useraccounts. AccountsTemplates.configure({ texts: { title: { - changePwd: '' - } - } + changePwd: '', + }, + }, }); AccountsTemplates.configureRoute('changePwd', { - redirect: function() { + redirect() { // XXX We should emit a notification once we have a notification system. // Currently the user has no indication that his modification has been // applied. Popup.back(); - } + }, }); diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js index 1be44728..2c55bc40 100644 --- a/client/config/blazeHelpers.js +++ b/client/config/blazeHelpers.js @@ -1,13 +1,53 @@ -Blaze.registerHelper('currentBoard', function() { - var boardId = Session.get('currentBoard'); +Blaze.registerHelper('currentBoard', () => { + const boardId = Session.get('currentBoard'); if (boardId) { return Boards.findOne(boardId); } }); -Blaze.registerHelper('currentCard', function() { - var cardId = Session.get('currentCard'); +Blaze.registerHelper('currentCard', () => { + const cardId = Session.get('currentCard'); if (cardId) { return Cards.findOne(cardId); } }); + +Blaze.registerHelper('getUser', (userId) => Users.findOne(userId)); + +// XXX I believe we should compute a HTML rendered field on the server that +// would handle markdown, emojies and user mentions. We can simply have two +// fields, one source, and one compiled version (in HTML) and send only the +// compiled version to most users -- who don't need to edit. +// In the meantime, all the transformation are done on the client using the +// Blaze API. +const at = HTML.CharRef({html: '@', str: '@'}); +Blaze.Template.registerHelper('mentions', new Template('mentions', function() { + const view = this; + const currentBoard = Session.get('currentBoard'); + const knowedUsers = _.map(currentBoard.members, (member) => { + member.username = Users.findOne(member.userId).username; + return member; + }); + const mentionRegex = /\B@(\w*)/gi; + let content = Blaze.toHTML(view.templateContentBlock); + + let currentMention, knowedUser, href, linkClass, linkValue, link; + while (Boolean(currentMention = mentionRegex.exec(content))) { + + knowedUser = _.findWhere(knowedUsers, { username: currentMention[1] }); + if (!knowedUser) + continue; + + linkValue = [' ', at, knowedUser.username]; + // XXX We need to convert to flow router + href = Router.url('Profile', { username: knowedUser.username }); + linkClass = 'atMention'; + if (knowedUser.userId === Meteor.userId()) + linkClass += ' me'; + link = HTML.A({ href, 'class': linkClass }, linkValue); + + content = content.replace(currentMention[0], Blaze.toHTML(link)); + } + + return HTML.Raw(content); +})); diff --git a/client/config/reactiveTabs.js b/client/config/reactiveTabs.js index 937057f7..5365c6d1 100644 --- a/client/config/reactiveTabs.js +++ b/client/config/reactiveTabs.js @@ -1,5 +1,4 @@ // XXX Since Blaze doesn't have a clean high component API, component API are // also tweaky to use. I guess React would be a solution. -ReactiveTabs.createInterface({ - template: 'basicTabs' -}); +const template = 'basicTabs'; +ReactiveTabs.createInterface({ template }); diff --git a/client/config/router.js b/client/config/router.js index 77b17b63..5260e03f 100644 --- a/client/config/router.js +++ b/client/config/router.js @@ -6,7 +6,7 @@ FlowRouter.triggers.exit([({path}) => { FlowRouter.route('/', { name: 'home', triggersEnter: [AccountsTemplates.ensureSignedIn], - action: function() { + action() { Session.set('currentBoard', null); Session.set('currentCard', null); @@ -14,14 +14,14 @@ FlowRouter.route('/', { EscapeActions.executeAll(); BlazeLayout.render('defaultLayout', { content: 'boardList' }); - } + }, }); FlowRouter.route('/b/:id/:slug', { name: 'board', - action: function(params) { - let currentBoard = params.id; - let previousBoard = Session.get('currentBoard'); + action(params) { + const currentBoard = params.id; + const previousBoard = Session.get('currentBoard'); Session.set('currentBoard', currentBoard); Session.set('currentCard', null); @@ -32,57 +32,57 @@ FlowRouter.route('/b/:id/:slug', { } BlazeLayout.render('defaultLayout', { content: 'board' }); - } + }, }); FlowRouter.route('/b/:boardId/:slug/:cardId', { name: 'card', - action: function(params) { + action(params) { Session.set('currentBoard', params.boardId); Session.set('currentCard', params.cardId); EscapeActions.executeUpTo('inlinedForm'); BlazeLayout.render('defaultLayout', { content: 'board' }); - } + }, }); FlowRouter.route('/shortcuts', { name: 'shortcuts', - action: function(params) { + action() { const shortcutsTemplate = 'keyboardShortcuts'; EscapeActions.executeUpTo('popup-close'); if (previousPath) { Modal.open(shortcutsTemplate, { - onCloseGoTo: previousPath + onCloseGoTo: previousPath, }); } else { // XXX There is currently no way to escape this page on Sandstorm BlazeLayout.render('defaultLayout', { content: shortcutsTemplate }); } - } + }, }); FlowRouter.notFound = { - action: function() { + action() { BlazeLayout.render('defaultLayout', { content: 'notFound' }); - } -} + }, +}; // We maintain a list of redirections to ensure that we don't break old URLs // when we change our routing scheme. -var redirections = { +const redirections = { '/boards': '/', '/boards/:id/:slug': '/b/:id/:slug', - '/boards/:id/:slug/:cardId': '/b/:id/:slug/:cardId' + '/boards/:id/:slug/:cardId': '/b/:id/:slug/:cardId', }; -_.each(redirections, function(newPath, oldPath) { +_.each(redirections, (newPath, oldPath) => { FlowRouter.route(oldPath, { - triggersEnter: [function(context, redirect) { + triggersEnter: [(context, redirect) => { redirect(FlowRouter.path(newPath, context.params)); - }] + }], }); }); diff --git a/client/lib/cssEvents.js b/client/lib/cssEvents.js index 487ba69b..39c3fb90 100644 --- a/client/lib/cssEvents.js +++ b/client/lib/cssEvents.js @@ -1,42 +1,40 @@ // XXX Should we use something like Moderniz instead of our custom detector? -var whichTransitionEvent = function() { - var t; - var el = document.createElement('fakeelement'); - var transitions = { +function whichTransitionEvent() { + const el = document.createElement('fakeelement'); + const transitions = { transition:'transitionend', OTransition:'oTransitionEnd', MSTransition:'msTransitionEnd', MozTransition:'transitionend', - WebkitTransition:'webkitTransitionEnd' + WebkitTransition:'webkitTransitionEnd', }; - for (t in transitions) { + for (const t in transitions) { if (el.style[t] !== undefined) { return transitions[t]; } } -}; +} -var whichAnimationEvent = function() { - var t; - var el = document.createElement('fakeelement'); - var transitions = { +function whichAnimationEvent() { + const el = document.createElement('fakeelement'); + const transitions = { animation:'animationend', OAnimation:'oAnimationEnd', MSTransition:'msAnimationEnd', MozAnimation:'animationend', - WebkitAnimation:'webkitAnimationEnd' + WebkitAnimation:'webkitAnimationEnd', }; - for (t in transitions) { + for (const t in transitions) { if (el.style[t] !== undefined) { return transitions[t]; } } -}; +} CSSEvents = { transitionend: whichTransitionEvent(), - animationend: whichAnimationEvent() + animationend: whichAnimationEvent(), }; diff --git a/client/lib/escapeActions.js b/client/lib/escapeActions.js index ff793b1d..f2dc3dcb 100644 --- a/client/lib/escapeActions.js +++ b/client/lib/escapeActions.js @@ -31,7 +31,7 @@ EscapeActions = { enabledOnClick = true; } - let noClickEscapeOn = options.noClickEscapeOn; + const noClickEscapeOn = options.noClickEscapeOn; this._actions = _.sortBy([...this._actions, { priority, @@ -44,20 +44,20 @@ EscapeActions = { executeLowest() { return this._execute({ - multipleAction: false + multipleAction: false, }); }, executeAll() { return this._execute({ - multipleActions: true + multipleActions: true, }); }, executeUpTo(maxLabel) { return this._execute({ - maxLabel: maxLabel, - multipleActions: true + maxLabel, + multipleActions: true, }); }, @@ -66,10 +66,10 @@ EscapeActions = { this._nextclickPrevented = false; } else { return this._execute({ - maxLabel: maxLabel, + maxLabel, multipleActions: false, isClick: true, - clickTarget: target + clickTarget: target, }); } }, @@ -79,7 +79,7 @@ EscapeActions = { }, _stopClick(action, clickTarget) { - if (! _.isString(action.noClickEscapeOn)) + if (!_.isString(action.noClickEscapeOn)) return false; else return $(clickTarget).closest(action.noClickEscapeOn).length > 0; @@ -88,86 +88,46 @@ EscapeActions = { _execute(options) { const maxLabel = options.maxLabel; const multipleActions = options.multipleActions; - const isClick = !! options.isClick; + const isClick = Boolean(options.isClick); const clickTarget = options.clickTarget; let executedAtLeastOne = false; let maxPriority; - if (! maxLabel) + if (!maxLabel) maxPriority = Infinity; else maxPriority = this.hierarchy.indexOf(maxLabel); - for (let currentAction of this._actions) { + for (const currentAction of this._actions) { if (currentAction.priority > maxPriority) return executedAtLeastOne; if (isClick && this._stopClick(currentAction, clickTarget)) return executedAtLeastOne; - let isEnabled = currentAction.enabledOnClick || ! isClick; + const isEnabled = currentAction.enabledOnClick || !isClick; if (isEnabled && currentAction.condition()) { currentAction.action(); executedAtLeastOne = true; - if (! multipleActions) + if (!multipleActions) return executedAtLeastOne; } } return executedAtLeastOne; - } -}; - -// MouseTrap plugin bindGlobal plugin. Adds a bindGlobal method to Mousetrap -// that allows you to bind specific keyboard shortcuts that will still work -// inside a text input field. -// -// usage: -// Mousetrap.bindGlobal('ctrl+s', _saveChanges); -// -// source: -// https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind -var _globalCallbacks = {}; -var _originalStopCallback = Mousetrap.stopCallback; - -Mousetrap.stopCallback = function(e, element, combo, sequence) { - var self = this; - - if (self.paused) { - return true; - } - - if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { - return false; - } - - return _originalStopCallback.call(self, e, element, combo); -}; - -Mousetrap.bindGlobal = function(keys, callback, action) { - var self = this; - self.bind(keys, callback, action); - - if (keys instanceof Array) { - for (var i = 0; i < keys.length; i++) { - _globalCallbacks[keys[i]] = true; - } - return; - } - - _globalCallbacks[keys] = true; + }, }; // Pressing escape to execute one escape action. We use `bindGloabal` vecause // the shortcut sould work on textarea and inputs as well. -Mousetrap.bindGlobal('esc', function() { +Mousetrap.bindGlobal('esc', () => { EscapeActions.executeLowest(); }); // On a left click on the document, we try to exectute one escape action (eg, // close the popup). We don't execute any action if the user has clicked on a // link or a button. -$(document).on('click', function(evt) { +$(document).on('click', (evt) => { if (evt.button === 0 && $(evt.target).closest('a,button,.is-editable').length === 0) { EscapeActions.clickExecute(evt.target, 'multiselection'); diff --git a/client/lib/filter.js b/client/lib/filter.js index 359b65d3..532ef236 100644 --- a/client/lib/filter.js +++ b/client/lib/filter.js @@ -4,66 +4,66 @@ // goal is to filter complete documents by using the local filters for each // fields. -var showFilterSidebar = function() { +function showFilterSidebar() { Sidebar.setView('filter'); -}; +} // Use a "set" filter for a field that is a set of documents uniquely // identified. For instance `{ labels: ['labelA', 'labelC', 'labelD'] }`. -var SetFilter = function() { - this._dep = new Tracker.Dependency(); - this._selectedElements = []; -}; +class SetFilter { + constructor() { + this._dep = new Tracker.Dependency(); + this._selectedElements = []; + } -_.extend(SetFilter.prototype, { - isSelected: function(val) { + isSelected(val) { this._dep.depend(); return this._selectedElements.indexOf(val) > -1; - }, + } - add: function(val) { + add(val) { if (this._indexOfVal(val) === -1) { this._selectedElements.push(val); this._dep.changed(); showFilterSidebar(); } - }, + } - remove: function(val) { - var indexOfVal = this._indexOfVal(val); + remove(val) { + const indexOfVal = this._indexOfVal(val); if (this._indexOfVal(val) !== -1) { this._selectedElements.splice(indexOfVal, 1); this._dep.changed(); } - }, + } - toogle: function(val) { + toogle(val) { if (this._indexOfVal(val) === -1) { this.add(val); } else { this.remove(val); } - }, + } - reset: function() { + reset() { this._selectedElements = []; this._dep.changed(); - }, + } - _indexOfVal: function(val) { + _indexOfVal(val) { return this._selectedElements.indexOf(val); - }, + } - _isActive: function() { + _isActive() { this._dep.depend(); return this._selectedElements.length !== 0; - }, + } - _getMongoSelector: function() { + _getMongoSelector() { this._dep.depend(); return { $in: this._selectedElements }; } -}); +} // The global Filter object. // XXX It would be possible to re-write this object more elegantly, and removing @@ -84,50 +84,46 @@ Filter = { _exceptions: [], _exceptionsDep: new Tracker.Dependency(), - isActive: function() { - var self = this; - return _.any(self._fields, function(fieldName) { - return self[fieldName]._isActive(); + isActive() { + return _.any(this._fields, (fieldName) => { + return this[fieldName]._isActive(); }); }, - _getMongoSelector: function() { - var self = this; - - if (! self.isActive()) + _getMongoSelector() { + if (!this.isActive()) return {}; - var filterSelector = {}; - _.forEach(self._fields, function(fieldName) { - var filter = self[fieldName]; + const filterSelector = {}; + _.forEach(this._fields, (fieldName) => { + const filter = this[fieldName]; if (filter._isActive()) filterSelector[fieldName] = filter._getMongoSelector(); }); - var exceptionsSelector = {_id: {$in: this._exceptions}}; + const exceptionsSelector = {_id: {$in: this._exceptions}}; this._exceptionsDep.depend(); return {$or: [filterSelector, exceptionsSelector]}; }, - mongoSelector: function(additionalSelector) { - var filterSelector = this._getMongoSelector(); + mongoSelector(additionalSelector) { + const filterSelector = this._getMongoSelector(); if (_.isUndefined(additionalSelector)) return filterSelector; else return {$and: [filterSelector, additionalSelector]}; }, - reset: function() { - var self = this; - _.forEach(self._fields, function(fieldName) { - var filter = self[fieldName]; + reset() { + _.forEach(this._fields, (fieldName) => { + const filter = this[fieldName]; filter.reset(); }); - self.resetExceptions(); + this.resetExceptions(); }, - addException: function(_id) { + addException(_id) { if (this.isActive()) { this._exceptions.push(_id); this._exceptionsDep.changed(); @@ -135,10 +131,10 @@ Filter = { } }, - resetExceptions: function() { + resetExceptions() { this._exceptions = []; this._exceptionsDep.changed(); - } + }, }; Blaze.registerHelper('Filter', Filter); diff --git a/client/lib/i18n.js b/client/lib/i18n.js index 7d7e3ebb..a03fb398 100644 --- a/client/lib/i18n.js +++ b/client/lib/i18n.js @@ -2,9 +2,9 @@ // the language reactively. If the user is not connected we use the language // information provided by the browser, and default to english. -Tracker.autorun(function() { - var language; - var currentUser = Meteor.user(); +Tracker.autorun(() => { + const currentUser = Meteor.user(); + let language; if (currentUser) { language = currentUser.profile && currentUser.profile.language; } else { @@ -12,11 +12,10 @@ Tracker.autorun(function() { } if (language) { - TAPi18n.setLanguage(language); // XXX - var shortLanguage = language.split('-')[0]; + const shortLanguage = language.split('-')[0]; T9n.setLanguage(shortLanguage); } }); diff --git a/client/lib/inlinedform.js b/client/lib/inlinedform.js index 15074f40..2732a2e3 100644 --- a/client/lib/inlinedform.js +++ b/client/lib/inlinedform.js @@ -13,66 +13,66 @@ // // the content when the form is close (optional) // We can only have one inlined form element opened at a time -currentlyOpenedForm = new ReactiveVar(null); +const currentlyOpenedForm = new ReactiveVar(null); InlinedForm = BlazeComponent.extendComponent({ - template: function() { + template() { return 'inlinedForm'; }, - onCreated: function() { + onCreated() { this.isOpen = new ReactiveVar(false); }, - onDestroyed: function() { + onDestroyed() { currentlyOpenedForm.set(null); }, - open: function() { + open() { // Close currently opened form, if any EscapeActions.executeUpTo('inlinedForm'); this.isOpen.set(true); currentlyOpenedForm.set(this); }, - close: function() { + close() { this.isOpen.set(false); currentlyOpenedForm.set(null); }, - getValue: function() { - var input = this.find('textarea,input[type=text]'); + getValue() { + const input = this.find('textarea,input[type=text]'); return this.isOpen.get() && input && input.value; }, - events: function() { + events() { return [{ 'click .js-close-inlined-form': this.close, 'click .js-open-inlined-form': this.open, // Pressing Ctrl+Enter should submit the form - 'keydown form textarea': function(evt) { + 'keydown form textarea'(evt) { if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { this.find('button[type=submit]').click(); } }, // Close the inlined form when after its submission - submit: function() { + submit() { if (this.currentData().autoclose !== false) { Tracker.afterFlush(() => { this.close(); }); } - } + }, }]; - } + }, }).register('inlinedForm'); // Press escape to close the currently opened inlinedForm EscapeActions.register('inlinedForm', - function() { currentlyOpenedForm.get().close(); }, - function() { return currentlyOpenedForm.get() !== null; }, { - noClickEscapeOn: '.js-inlined-form' + () => { currentlyOpenedForm.get().close(); }, + () => { return currentlyOpenedForm.get() !== null; }, { + noClickEscapeOn: '.js-inlined-form', } ); diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js index 2b327b33..0bb9c380 100644 --- a/client/lib/keyboard.js +++ b/client/lib/keyboard.js @@ -24,7 +24,7 @@ Mousetrap.bind('x', () => { }); Mousetrap.bind(['down', 'up'], (evt, key) => { - if (! Session.get('currentCard')) { + if (!Session.get('currentCard')) { return; } @@ -39,24 +39,24 @@ Mousetrap.bind(['down', 'up'], (evt, key) => { Template.keyboardShortcuts.helpers({ mapping: [{ keys: ['W'], - action: 'shortcut-toogle-sidebar' + action: 'shortcut-toogle-sidebar', }, { keys: ['Q'], - action: 'shortcut-filter-my-cards' + action: 'shortcut-filter-my-cards', }, { keys: ['X'], - action: 'shortcut-clear-filters' + action: 'shortcut-clear-filters', }, { keys: ['?'], - action: 'shortcut-show-shortcuts' + action: 'shortcut-show-shortcuts', }, { keys: ['ESC'], - action: 'shortcut-close-dialog' + action: 'shortcut-close-dialog', }, { keys: ['@'], - action: 'shortcut-autocomplete-members' + action: 'shortcut-autocomplete-members', }, { keys: [':'], - action: 'shortcut-autocomplete-emojies' - }] + action: 'shortcut-autocomplete-emojies', + }], }); diff --git a/client/lib/modal.js b/client/lib/modal.js index 9b204bb4..5b3392b2 100644 --- a/client/lib/modal.js +++ b/client/lib/modal.js @@ -1,4 +1,4 @@ -const closedValue = null +const closedValue = null; window.Modal = new class { constructor() { diff --git a/client/lib/multiSelection.js b/client/lib/multiSelection.js index e6db42cd..77f351a4 100644 --- a/client/lib/multiSelection.js +++ b/client/lib/multiSelection.js @@ -1,53 +1,53 @@ -var getCardsBetween = function(idA, idB) { +function getCardsBetween(idA, idB) { - var pluckId = function(doc) { + function pluckId(doc) { return doc._id; - }; + } - var getListsStrictlyBetween = function(id1, id2) { + function getListsStrictlyBetween(id1, id2) { return Lists.find({ $and: [ { sort: { $gt: Lists.findOne(id1).sort } }, - { sort: { $lt: Lists.findOne(id2).sort } } + { sort: { $lt: Lists.findOne(id2).sort } }, ], - archived: false + archived: false, }).map(pluckId); - }; + } - var cards = _.sortBy([Cards.findOne(idA), Cards.findOne(idB)], function(c) { + const cards = _.sortBy([Cards.findOne(idA), Cards.findOne(idB)], (c) => { return c.sort; }); - var selector; + let selector; if (cards[0].listId === cards[1].listId) { selector = { listId: cards[0].listId, sort: { $gte: cards[0].sort, - $lte: cards[1].sort + $lte: cards[1].sort, }, - archived: false + archived: false, }; } else { selector = { $or: [{ listId: cards[0].listId, - sort: { $lte: cards[0].sort } + sort: { $lte: cards[0].sort }, }, { listId: { - $in: getListsStrictlyBetween(cards[0].listId, cards[1].listId) - } + $in: getListsStrictlyBetween(cards[0].listId, cards[1].listId), + }, }, { listId: cards[1].listId, - sort: { $gte: cards[1].sort } + sort: { $gte: cards[1].sort }, }], - archived: false + archived: false, }; } return Cards.find(Filter.mongoSelector(selector)).map(pluckId); -}; +} MultiSelection = { sidebarView: 'multiselection', @@ -58,30 +58,30 @@ MultiSelection = { startRangeCardId: null, - reset: function() { + reset() { this._selectedCards.set([]); }, - getMongoSelector: function() { + getMongoSelector() { return Filter.mongoSelector({ - _id: { $in: this._selectedCards.get() } + _id: { $in: this._selectedCards.get() }, }); }, - isActive: function() { + isActive() { return this._isActive.get(); }, - count: function() { + count() { return Cards.find(this.getMongoSelector()).count(); }, - isEmpty: function() { + isEmpty() { return this.count() === 0; }, - activate: function() { - if (! this.isActive()) { + activate() { + if (!this.isActive()) { EscapeActions.executeUpTo('detailsPane'); this._isActive.set(true); Tracker.flush(); @@ -89,7 +89,7 @@ MultiSelection = { Sidebar.setView(this.sidebarView); }, - disable: function() { + disable() { if (this.isActive()) { this._isActive.set(false); if (Sidebar && Sidebar.getView() === this.sidebarView) { @@ -99,19 +99,19 @@ MultiSelection = { } }, - add: function(cardIds) { + add(cardIds) { return this.toogle(cardIds, { add: true, remove: false }); }, - remove: function(cardIds) { + remove(cardIds) { return this.toogle(cardIds, { add: false, remove: true }); }, - toogleRange: function(cardId) { - var selectedCards = this._selectedCards.get(); - var startRange; + toogleRange(cardId) { + const selectedCards = this._selectedCards.get(); + let startRange; this.reset(); - if (! this.isActive() || selectedCards.length === 0) { + if (!this.isActive() || selectedCards.length === 0) { this.toogle(cardId); } else { startRange = selectedCards[selectedCards.length - 1]; @@ -119,23 +119,22 @@ MultiSelection = { } }, - toogle: function(cardIds, options) { - var self = this; + toogle(cardIds, options) { cardIds = _.isString(cardIds) ? [cardIds] : cardIds; options = _.extend({ add: true, - remove: true + remove: true, }, options || {}); - if (! self.isActive()) { - self.reset(); - self.activate(); + if (!this.isActive()) { + this.reset(); + this.activate(); } - var selectedCards = self._selectedCards.get(); + const selectedCards = this._selectedCards.get(); - _.each(cardIds, function(cardId) { - var indexOfCard = selectedCards.indexOf(cardId); + _.each(cardIds, (cardId) => { + const indexOfCard = selectedCards.indexOf(cardId); if (options.remove && indexOfCard > -1) selectedCards.splice(indexOfCard, 1); @@ -144,19 +143,19 @@ MultiSelection = { selectedCards.push(cardId); }); - self._selectedCards.set(selectedCards); + this._selectedCards.set(selectedCards); }, - isSelected: function(cardId) { + isSelected(cardId) { return this._selectedCards.get().indexOf(cardId) > -1; - } + }, }; Blaze.registerHelper('MultiSelection', MultiSelection); EscapeActions.register('multiselection', - function() { MultiSelection.disable(); }, - function() { return MultiSelection.isActive(); }, { - noClickEscapeOn: '.js-minicard,.js-board-sidebar-content' + () => { MultiSelection.disable(); }, + () => { return MultiSelection.isActive(); }, { + noClickEscapeOn: '.js-minicard,.js-board-sidebar-content', } ); diff --git a/client/lib/popup.js b/client/lib/popup.js index b2340e04..3c39af29 100644 --- a/client/lib/popup.js +++ b/client/lib/popup.js @@ -1,55 +1,53 @@ // A simple tracker dependency that we invalidate every time the window is // resized. This is used to reactively re-calculate the popup position in case // of a window resize. This is the equivalent of a "Signal" in some other -// programming environments. -let windowResizeDep = new Tracker.Dependency() -$(window).on('resize', () => windowResizeDep.changed()) +// programming environments (eg, elm). +const windowResizeDep = new Tracker.Dependency(); +$(window).on('resize', () => windowResizeDep.changed()); window.Popup = new class { constructor() { // The template we use to render popups - this.template = Template.popup + this.template = Template.popup; // We only want to display one popup at a time and we keep the view object // in this `Popup._current` variable. If there is no popup currently opened // the value is `null`. - this._current = null + this._current = null; // It's possible to open a sub-popup B from a popup A. In that case we keep // the data of popup A so we can return back to it. Every time we open a new // popup the stack grows, every time we go back the stack decrease, and if // we close the popup the stack is reseted to the empty stack []. - this._stack = [] + this._stack = []; // We invalidate this internal dependency every time the top of the stack // has changed and we want to re-render a popup with the new top-stack data. - this._dep = new Tracker.Dependency() + this._dep = new Tracker.Dependency(); } /// This function returns a callback that can be used in an event map: - /// /// Template.tplName.events({ - /// 'click .elementClass': Popup.open("popupName") - /// }) - /// + /// 'click .elementClass': Popup.open("popupName"), + /// }); /// The popup inherit the data context of its parent. open(name) { - let self = this - const popupName = `${name}Popup` + const self = this; + const popupName = `${name}Popup`; function clickFromPopup(evt) { - return $(evt.target).closest('.js-pop-over').length !== 0 + return $(evt.target).closest('.js-pop-over').length !== 0; } return function(evt) { // If a popup is already opened, clicking again on the opener element // should close it -- and interrupt the current `open` function. if (self.isOpen()) { - let previousOpenerElement = self._getTopStack().openerElement + const previousOpenerElement = self._getTopStack().openerElement; if (previousOpenerElement === evt.currentTarget) { - return self.close() + return self.close(); } else { - $(previousOpenerElement).removeClass('is-active') + $(previousOpenerElement).removeClass('is-active'); } } @@ -58,16 +56,16 @@ window.Popup = new class { // if the popup has no parent, or from the parent `openerElement` if it // has one. This allows us to position a sub-popup exactly at the same // position than its parent. - let openerElement + let openerElement; if (clickFromPopup(evt)) { - openerElement = self._getTopStack().openerElement + openerElement = self._getTopStack().openerElement; } else { - self._stack = [] - openerElement = evt.currentTarget + self._stack = []; + openerElement = evt.currentTarget; } - $(openerElement).addClass('is-active') - evt.preventDefault() + $(openerElement).addClass('is-active'); + evt.preventDefault(); // We push our popup data to the stack. The top of the stack is always // used as the data source for our current popup. @@ -79,7 +77,7 @@ window.Popup = new class { depth: self._stack.length, offset: self._getOffset(openerElement), dataContext: this.currentData && this.currentData() || this, - }) + }); // If there are no popup currently opened we use the Blaze API to render // one into the DOM. We use a reactive function as the data parameter that @@ -90,39 +88,38 @@ window.Popup = new class { // Otherwise if there is already a popup open we just need to invalidate // 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()) { + if (!self.isOpen()) { self.current = Blaze.renderWithData(self.template, () => { - self._dep.depend() - return _.extend(self._getTopStack(), { stack: self._stack }) - }, document.body) + self._dep.depend(); + return _.extend(self._getTopStack(), { stack: self._stack }); + }, document.body); } else { - self._dep.changed() + self._dep.changed(); } - } + }; } /// This function returns a callback that can be used in an event map: - /// /// Template.tplName.events({ /// 'click .elementClass': Popup.afterConfirm("popupName", function() { /// // What to do after the user has confirmed the action - /// }) - /// }) + /// }), + /// }); afterConfirm(name, action) { - let self = this + const self = this; return function(evt, tpl) { - let context = this.currentData && this.currentData() || this - context.__afterConfirmAction = action - self.open(name).call(context, evt, tpl) - } + const context = this.currentData && this.currentData() || this; + context.__afterConfirmAction = action; + self.open(name).call(context, evt, tpl); + }; } /// The public reactive state of the popup. isOpen() { - this._dep.changed() - return !! this.current + this._dep.changed(); + return Boolean(this.current); } /// In case the popup was opened from a parent popup we can get back to it @@ -132,45 +129,45 @@ window.Popup = new class { /// steps back is greater than the popup stack size, the popup will be closed. back(n = 1) { if (this._stack.length > n) { - _.times(n, () => this._stack.pop()) - this._dep.changed() + _.times(n, () => this._stack.pop()); + this._dep.changed(); } else { - this.close() + this.close(); } } /// Close the current opened popup. close() { if (this.isOpen()) { - Blaze.remove(this.current) - this.current = null + Blaze.remove(this.current); + this.current = null; - let openerElement = this._getTopStack().openerElement - $(openerElement).removeClass('is-active') + const openerElement = this._getTopStack().openerElement; + $(openerElement).removeClass('is-active'); - this._stack = [] + this._stack = []; } } // An utility fonction that returns the top element of the internal stack _getTopStack() { - return this._stack[this._stack.length - 1] + return this._stack[this._stack.length - 1]; } // We automatically calculate the popup offset from the reference element // position and dimensions. We also reactively use the window dimensions to // ensure that the popup is always visible on the screen. _getOffset(element) { - let $element = $(element) + const $element = $(element); return () => { - windowResizeDep.depend() - const offset = $element.offset() - const popupWidth = 300 + 15 + windowResizeDep.depend(); + const offset = $element.offset(); + const popupWidth = 300 + 15; return { left: Math.min(offset.left, $(window).width() - popupWidth), top: offset.top + $element.outerHeight(), - } - } + }; + }; } // We get the title from the translation files. Instead of returning the @@ -178,22 +175,22 @@ window.Popup = new class { // is a reactive data source, the title will be changed reactively. _getTitle(popupName) { return () => { - const translationKey = `${popupName}-title` + const translationKey = `${popupName}-title`; // XXX There is no public API to check if there is an available // translation for a given key. So we try to translate the key and if the // translation output equals the key input we deduce that no translation // was available and returns `false`. There is a (small) risk a false // positives. - const title = TAPi18n.__(translationKey) - return title !== translationKey ? title : false - } + const title = TAPi18n.__(translationKey); + return title !== translationKey ? title : false; + }; } -} +}; // We close a potential opened popup on any left click on the document, or go // one step back by pressing escape. -const escapeActions = ['back', 'close'] +const escapeActions = ['back', 'close']; _.each(escapeActions, (actionName) => { EscapeActions.register(`popup-${actionName}`, () => Popup[actionName](), @@ -202,6 +199,6 @@ _.each(escapeActions, (actionName) => { noClickEscapeOn: '.js-pop-over', enabledOnClick: actionName === 'close', } - ) -}) + ); +}); diff --git a/client/lib/unsavedEdits.js b/client/lib/unsavedEdits.js index 55ea2529..dc267bfb 100644 --- a/client/lib/unsavedEdits.js +++ b/client/lib/unsavedEdits.js @@ -27,9 +27,9 @@ UnsavedEdits = { // _collection: UnsavedEditCollection, get({ fieldName, docId }, defaultTo = '') { - let unsavedValue = this._getCollectionDocument(fieldName, docId); + const unsavedValue = this._getCollectionDocument(fieldName, docId); if (unsavedValue) { - return unsavedValue.value + return unsavedValue.value; } else { return defaultTo; } @@ -40,13 +40,9 @@ UnsavedEdits = { }, set({ fieldName, docId }, value) { - let currentDoc = this._getCollectionDocument(fieldName, docId); + const currentDoc = this._getCollectionDocument(fieldName, docId); if (currentDoc) { - UnsavedEditCollection.update(currentDoc._id, { - $set: { - value: value - } - }); + UnsavedEditCollection.update(currentDoc._id, { $set: { value }}); } else { UnsavedEditCollection.insert({ fieldName, @@ -57,7 +53,7 @@ UnsavedEdits = { }, reset({ fieldName, docId }) { - let currentDoc = this._getCollectionDocument(fieldName, docId); + const currentDoc = this._getCollectionDocument(fieldName, docId); if (currentDoc) { UnsavedEditCollection.remove(currentDoc._id); } @@ -65,13 +61,13 @@ UnsavedEdits = { _getCollectionDocument(fieldName, docId) { return UnsavedEditCollection.findOne({fieldName, docId}); - } -} + }, +}; Blaze.registerHelper('getUnsavedValue', (fieldName, docId, defaultTo) => { // Workaround some blaze feature that ass a list of keywords arguments as the // last parameter (even if the caller didn't specify any). - if (! _.isString(defaultTo)) { + if (!_.isString(defaultTo)) { defaultTo = ''; } return UnsavedEdits.get({ fieldName, docId }, defaultTo); diff --git a/client/lib/utils.js b/client/lib/utils.js index c6a9adc5..0cd93419 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -1,62 +1,70 @@ Utils = { // XXX We should remove these two methods - goBoardId: function(_id) { - var board = Boards.findOne(_id); + goBoardId(_id) { + const board = Boards.findOne(_id); return board && FlowRouter.go('board', { id: board._id, - slug: board.slug + slug: board.slug, }); }, - goCardId: function(_id) { - var card = Cards.findOne(_id); - var board = Boards.findOne(card.boardId); + goCardId(_id) { + const card = Cards.findOne(_id); + const board = Boards.findOne(card.boardId); return board && FlowRouter.go('card', { cardId: card._id, boardId: board._id, - slug: board.slug + slug: board.slug, }); }, - capitalize: function(string) { + capitalize(string) { return string.charAt(0).toUpperCase() + string.slice(1); }, - getLabelIndex: function(boardId, labelId) { - var board = Boards.findOne(boardId); - var labels = {}; - _.each(board.labels, function(a, b) { + getLabelIndex(boardId, labelId) { + const board = Boards.findOne(boardId); + const labels = {}; + _.each(board.labels, (a, b) => { labels[a._id] = b; }); return { index: labels[labelId], - key: function(key) { - return 'labels.' + labels[labelId] + '.' + key; - } + key(key) { + return `labels.${labels[labelId]}.${key}`; + }, }; }, // Determine the new sort index - calculateIndex: function(prevCardDomElement, nextCardDomElement, nCards) { - nCards = nCards || 1; - + calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) { + let base, increment; // If we drop the card to an empty column - if (! prevCardDomElement && ! nextCardDomElement) { - return {base: 0, increment: 1}; + if (!prevCardDomElement && !nextCardDomElement) { + base = 0; + increment = 1; // If we drop the card in the first position - } else if (! prevCardDomElement) { - return {base: Blaze.getData(nextCardDomElement).sort - 1, increment: -1}; + } else if (!prevCardDomElement) { + base = Blaze.getData(nextCardDomElement).sort - 1; + increment = -1; // If we drop the card in the last position - } else if (! nextCardDomElement) { - return {base: Blaze.getData(prevCardDomElement).sort + 1, increment: 1}; + } else if (!nextCardDomElement) { + base = Blaze.getData(prevCardDomElement).sort + 1; + increment = 1; } // In the general case take the average of the previous and next element // sort indexes. else { - var prevSortIndex = Blaze.getData(prevCardDomElement).sort; - var nextSortIndex = Blaze.getData(nextCardDomElement).sort; - var increment = (nextSortIndex - prevSortIndex) / (nCards + 1); - return {base: prevSortIndex + increment, increment: increment}; + const prevSortIndex = Blaze.getData(prevCardDomElement).sort; + const nextSortIndex = Blaze.getData(nextCardDomElement).sort; + increment = (nextSortIndex - prevSortIndex) / (nCards + 1); + base = prevSortIndex + increment; } - } + // XXX Return a generator that yield values instead of a base with a + // increment number. + return { + base, + increment, + }; + }, }; diff --git a/collections/activities.js b/collections/activities.js index 1e24cf7c..5de07ee5 100644 --- a/collections/activities.js +++ b/collections/activities.js @@ -11,41 +11,41 @@ Activities = new Mongo.Collection('activities'); Activities.helpers({ - board: function() { + board() { return Boards.findOne(this.boardId); }, - user: function() { + user() { return Users.findOne(this.userId); }, - member: function() { + member() { return Users.findOne(this.memberId); }, - list: function() { + list() { return Lists.findOne(this.listId); }, - oldList: function() { + oldList() { return Lists.findOne(this.oldListId); }, - card: function() { + card() { return Cards.findOne(this.cardId); }, - comment: function() { + comment() { return CardComments.findOne(this.commentId); }, - attachment: function() { + attachment() { return Attachments.findOne(this.attachmentId); - } + }, }); -Activities.before.insert(function(userId, doc) { +Activities.before.insert((userId, doc) => { doc.createdAt = new Date(); }); // For efficiency create an index on the date of creation. if (Meteor.isServer) { - Meteor.startup(function() { + Meteor.startup(() => { Activities._collection._ensureIndex({ - createdAt: -1 + createdAt: -1, }); }); } diff --git a/collections/attachments.js b/collections/attachments.js index c8fe6b18..8ef0fef0 100644 --- a/collections/attachments.js +++ b/collections/attachments.js @@ -3,19 +3,19 @@ Attachments = new FS.Collection('attachments', { // XXX Add a new store for cover thumbnails so we don't load big images in // the general board view - new FS.Store.GridFS('attachments') - ] + new FS.Store.GridFS('attachments'), + ], }); if (Meteor.isServer) { Attachments.allow({ - insert: function(userId, doc) { + insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update: function(userId, doc) { + update(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - remove: function(userId, doc) { + remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, // We authorize the attachment download either: @@ -26,24 +26,24 @@ if (Meteor.isServer) { // // https://github.com/CollectionFS/Meteor-CollectionFS/issues/449 // - download: function(userId, doc) { - var query = { + download(userId, doc) { + const query = { $or: [ { 'members.userId': userId }, - { permission: 'public' } - ] + { permission: 'public' }, + ], }; - return !! Boards.findOne(doc.boardId, query); + return Boolean(Boards.findOne(doc.boardId, query)); }, - fetch: ['boardId'] + fetch: ['boardId'], }); } // XXX Enforce a schema for the Attachments CollectionFS -Attachments.files.before.insert(function(userId, doc) { - var file = new FS.File(doc); +Attachments.files.before.insert((userId, doc) => { + const file = new FS.File(doc); doc.userId = userId; // If the uploaded document is not an image we need to enforce browser @@ -54,26 +54,26 @@ Attachments.files.before.insert(function(userId, doc) { // See https://github.com/libreboard/libreboard/issues/99 // XXX Should we use `beforeWrite` option of CollectionFS instead of // collection-hooks? - if (! file.isImage()) { + if (!file.isImage()) { file.original.type = 'application/octet-stream'; } }); if (Meteor.isServer) { - Attachments.files.after.insert(function(userId, doc) { + Attachments.files.after.insert((userId, doc) => { Activities.insert({ + userId, type: 'card', activityType: 'addAttachment', attachmentId: doc._id, boardId: doc.boardId, cardId: doc.cardId, - userId: userId }); }); - Attachments.files.after.remove(function(userId, doc) { + Attachments.files.after.remove((userId, doc) => { Activities.remove({ - attachmentId: doc._id + attachmentId: doc._id, }); }); } diff --git a/collections/avatars.js b/collections/avatars.js index 87af7120..53924ffb 100644 --- a/collections/avatars.js +++ b/collections/avatars.js @@ -1,27 +1,27 @@ Avatars = new FS.Collection('avatars', { stores: [ - new FS.Store.GridFS('avatars') + new FS.Store.GridFS('avatars'), ], filter: { maxSize: 72000, allow: { - contentTypes: ['image/*'] - } - } + contentTypes: ['image/*'], + }, + }, }); -var isOwner = function(userId, file) { +function isOwner(userId, file) { return userId && userId === file.userId; -}; +} Avatars.allow({ insert: isOwner, update: isOwner, remove: isOwner, - download: function() { return true; }, - fetch: ['userId'] + download() { return true; }, + fetch: ['userId'], }); -Avatars.files.before.insert(function(userId, doc) { +Avatars.files.before.insert((userId, doc) => { doc.userId = userId; }); diff --git a/collections/boards.js b/collections/boards.js index ade70466..8260fc3d 100644 --- a/collections/boards.js +++ b/collections/boards.js @@ -2,27 +2,27 @@ Boards = new Mongo.Collection('boards'); Boards.attachSchema(new SimpleSchema({ title: { - type: String + type: String, }, slug: { - type: String + type: String, }, archived: { - type: Boolean + type: Boolean, }, createdAt: { type: Date, - denyUpdate: true + denyUpdate: true, }, // XXX Inconsistent field naming modifiedAt: { type: Date, denyInsert: true, - optional: true + optional: true, }, // De-normalized number of users that have starred this board stars: { - type: Number + type: Number, }, // De-normalized label system 'labels.$._id': { @@ -31,46 +31,46 @@ Boards.attachSchema(new SimpleSchema({ // always set on the server. // XXX Actually if we create a new label, the `_id` is set on the client // without being overwritten by the server, could it be a problem? - type: String + type: String, }, 'labels.$.name': { type: String, - optional: true + optional: true, }, 'labels.$.color': { type: String, allowedValues: [ 'green', 'yellow', 'orange', 'red', 'purple', - 'blue', 'sky', 'lime', 'pink', 'black' - ] + 'blue', 'sky', 'lime', 'pink', 'black', + ], }, // XXX We might want to maintain more informations under the member sub- // documents like de-normalized meta-data (the date the member joined the // board, the number of contributions, etc.). 'members.$.userId': { - type: String + type: String, }, 'members.$.isAdmin': { - type: Boolean + type: Boolean, }, 'members.$.isActive': { - type: Boolean + type: Boolean, }, permission: { type: String, - allowedValues: ['public', 'private'] + allowedValues: ['public', 'private'], }, color: { type: String, allowedValues: [ - 'belize', - 'nephritis', - 'pomegranate', - 'pumpkin', - 'wisteria', - 'midnight', - ] - } + 'belize', + 'nephritis', + 'pomegranate', + 'pumpkin', + 'wisteria', + 'midnight', + ], + }, })); if (Meteor.isServer) { @@ -78,30 +78,30 @@ if (Meteor.isServer) { insert: Meteor.userId, update: allowIsBoardAdmin, remove: allowIsBoardAdmin, - fetch: ['members'] + fetch: ['members'], }); // The number of users that have starred this board is managed by trusted code // and the user is not allowed to update it Boards.deny({ - update: function(userId, board, fieldNames) { + update(userId, board, fieldNames) { return _.contains(fieldNames, 'stars'); }, - fetch: [] + fetch: [], }); // We can't remove a member if it is the last administrator Boards.deny({ - update: function(userId, doc, fieldNames, modifier) { - if (! _.contains(fieldNames, 'members')) + update(userId, doc, fieldNames, modifier) { + if (!_.contains(fieldNames, 'members')) return false; // We only care in case of a $pull operation, ie remove a member - if (! _.isObject(modifier.$pull && modifier.$pull.members)) + if (!_.isObject(modifier.$pull && modifier.$pull.members)) return false; // If there is more than one admin, it's ok to remove anyone - var nbAdmins = _.filter(doc.members, function(member) { + const nbAdmins = _.filter(doc.members, (member) => { return member.isAdmin; }).length; if (nbAdmins > 1) @@ -109,36 +109,36 @@ if (Meteor.isServer) { // If all the previous conditions were verified, we can't remove // a user if it's an admin - var removedMemberId = modifier.$pull.members.userId; - return !! _.findWhere(doc.members, { + const removedMemberId = modifier.$pull.members.userId; + return Boolean(_.findWhere(doc.members, { userId: removedMemberId, - isAdmin: true - }); + isAdmin: true, + })); }, - fetch: ['members'] + fetch: ['members'], }); } Boards.helpers({ - isPublic: function() { + isPublic() { return this.permission === 'public'; }, - lists: function() { + lists() { return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 }}); }, - activities: function() { + activities() { return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 }}); }, - absoluteUrl: function() { + absoluteUrl() { return FlowRouter.path('board', { id: this._id, slug: this.slug }); }, - colorClass: function() { - return 'board-color-' + this.color; - } + colorClass() { + return `board-color-${this.color}`; + }, }); -Boards.before.insert(function(userId, doc) { +Boards.before.insert((userId, doc) => { // XXX We need to improve slug management. Only the id should be necessary // to identify a board in the code. // XXX If the board title is updated, the slug should also be updated. @@ -149,87 +149,87 @@ Boards.before.insert(function(userId, doc) { doc.createdAt = new Date(); doc.archived = false; doc.members = [{ - userId: userId, + userId, isAdmin: true, - isActive: true + isActive: true, }]; doc.stars = 0; doc.color = Boards.simpleSchema()._schema.color.allowedValues[0]; // Handle labels - var colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; - var defaultLabelsColors = _.clone(colors).splice(0, 6); - doc.labels = _.map(defaultLabelsColors, function(val) { + const colors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; + const defaultLabelsColors = _.clone(colors).splice(0, 6); + doc.labels = _.map(defaultLabelsColors, (color) => { return { + color, _id: Random.id(6), name: '', - color: val }; }); }); -Boards.before.update(function(userId, doc, fieldNames, modifier) { +Boards.before.update((userId, doc, fieldNames, modifier) => { modifier.$set = modifier.$set || {}; modifier.$set.modifiedAt = new Date(); }); if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board - Meteor.startup(function() { + Meteor.startup(() => { Boards._collection._ensureIndex({ _id: 1, - 'members.userId': 1 + 'members.userId': 1, }, { unique: true }); }); // Genesis: the first activity of the newly created board - Boards.after.insert(function(userId, doc) { + Boards.after.insert((userId, doc) => { Activities.insert({ + userId, type: 'board', activityTypeId: doc._id, activityType: 'createBoard', boardId: doc._id, - userId: userId }); }); // If the user remove one label from a board, we cant to remove reference of // this label in any card of this board. - Boards.after.update(function(userId, doc, fieldNames, modifier) { - if (! _.contains(fieldNames, 'labels') || - ! modifier.$pull || - ! modifier.$pull.labels || - ! modifier.$pull.labels._id) + Boards.after.update((userId, doc, fieldNames, modifier) => { + if (!_.contains(fieldNames, 'labels') || + !modifier.$pull || + !modifier.$pull.labels || + !modifier.$pull.labels._id) return; - var removedLabelId = modifier.$pull.labels._id; + const removedLabelId = modifier.$pull.labels._id; Cards.update( { boardId: doc._id }, { $pull: { - labels: removedLabelId - } + labels: removedLabelId, + }, }, { multi: true } ); }); // Add a new activity if we add or remove a member to the board - Boards.after.update(function(userId, doc, fieldNames, modifier) { - if (! _.contains(fieldNames, 'members')) + Boards.after.update((userId, doc, fieldNames, modifier) => { + if (!_.contains(fieldNames, 'members')) return; - var memberId; + let memberId; // Say hello to the new member if (modifier.$push && modifier.$push.members) { memberId = modifier.$push.members.userId; Activities.insert({ + userId, + memberId, type: 'member', activityType: 'addBoardMember', boardId: doc._id, - userId: userId, - memberId: memberId }); } @@ -237,11 +237,11 @@ if (Meteor.isServer) { if (modifier.$pull && modifier.$pull.members) { memberId = modifier.$pull.members.userId; Activities.insert({ + userId, + memberId, type: 'member', activityType: 'removeBoardMember', boardId: doc._id, - userId: userId, - memberId: memberId }); } }); diff --git a/collections/cards.js b/collections/cards.js index d9f1bf6e..97ba4e3c 100644 --- a/collections/cards.js +++ b/collections/cards.js @@ -6,162 +6,161 @@ CardComments = new Mongo.Collection('card_comments'); // of comments just to display the number of them in the board view. Cards.attachSchema(new SimpleSchema({ title: { - type: String + type: String, }, archived: { - type: Boolean + type: Boolean, }, listId: { - type: String + type: String, }, // The system could work without this `boardId` information (we could deduce // the board identifier from the card), but it would make the system more // difficult to manage and less efficient. boardId: { - type: String + type: String, }, coverId: { type: String, - optional: true + optional: true, }, createdAt: { type: Date, - denyUpdate: true + denyUpdate: true, }, dateLastActivity: { - type: Date + type: Date, }, description: { type: String, - optional: true + optional: true, }, labelIds: { type: [String], - optional: true + optional: true, }, members: { type: [String], - optional: true + optional: true, }, // XXX Should probably be called `authorId`. Is it even needed since we have // the `members` field? userId: { - type: String + type: String, }, sort: { type: Number, - decimal: true - } + decimal: true, + }, })); CardComments.attachSchema(new SimpleSchema({ boardId: { - type: String + type: String, }, cardId: { - type: String + type: String, }, // XXX Rename in `content`? `text` is a bit vague... text: { - type: String + type: String, }, // XXX We probably don't need this information here, since we already have it // in the associated comment creation activity createdAt: { type: Date, - denyUpdate: false + denyUpdate: false, }, // XXX Should probably be called `authorId` userId: { - type: String - } + type: String, + }, })); if (Meteor.isServer) { Cards.allow({ - insert: function(userId, doc) { + insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update: function(userId, doc) { + update(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - remove: function(userId, doc) { + remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - fetch: ['boardId'] + fetch: ['boardId'], }); CardComments.allow({ - insert: function(userId, doc) { + insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update: function(userId, doc) { + update(userId, doc) { return userId === doc.userId; }, - remove: function(userId, doc) { + remove(userId, doc) { return userId === doc.userId; }, - fetch: ['userId', 'boardId'] + fetch: ['userId', 'boardId'], }); } Cards.helpers({ - list: function() { + list() { return Lists.findOne(this.listId); }, - board: function() { + board() { return Boards.findOne(this.boardId); }, - labels: function() { - var self = this; - var boardLabels = self.board().labels; - var cardLabels = _.filter(boardLabels, function(label) { - return _.contains(self.labelIds, label._id); + labels() { + const boardLabels = this.board().labels; + const cardLabels = _.filter(boardLabels, (label) => { + return _.contains(this.labelIds, label._id); }); return cardLabels; }, - hasLabel: function(labelId) { + hasLabel(labelId) { return _.contains(this.labelIds, labelId); }, - user: function() { + user() { return Users.findOne(this.userId); }, - isAssigned: function(memberId) { + isAssigned(memberId) { return _.contains(this.members, memberId); }, - activities: function() { + activities() { return Activities.find({ cardId: this._id }, { sort: { createdAt: -1 }}); }, - comments: function() { + comments() { return CardComments.find({ cardId: this._id }, { sort: { createdAt: -1 }}); }, - attachments: function() { + attachments() { return Attachments.find({ cardId: this._id }, { sort: { uploadedAt: -1 }}); }, - cover: function() { + cover() { return Attachments.findOne(this.coverId); }, - absoluteUrl: function() { - var board = this.board(); + absoluteUrl() { + const board = this.board(); return FlowRouter.path('card', { boardId: board._id, slug: board.slug, - cardId: this._id + cardId: this._id, }); }, - rootUrl: function() { + rootUrl() { return Meteor.absoluteUrl(this.absoluteUrl().replace('/', '')); - } + }, }); CardComments.helpers({ - user: function() { + user() { return Users.findOne(this.userId); - } + }, }); CardComments.hookOptions.after.update = { fetchPrevious: false }; -Cards.before.insert(function(userId, doc) { +Cards.before.insert((userId, doc) => { doc.createdAt = new Date(); doc.dateLastActivity = new Date(); @@ -169,44 +168,44 @@ Cards.before.insert(function(userId, doc) { doc.archived = false; // userId native set. - if (! doc.userId) + if (!doc.userId) doc.userId = userId; }); -CardComments.before.insert(function(userId, doc) { +CardComments.before.insert((userId, doc) => { doc.createdAt = new Date(); doc.userId = userId; }); if (Meteor.isServer) { - Cards.after.insert(function(userId, doc) { + Cards.after.insert((userId, doc) => { Activities.insert({ + userId, activityType: 'createCard', boardId: doc.boardId, listId: doc.listId, cardId: doc._id, - userId: userId }); }); // New activity for card (un)archivage - Cards.after.update(function(userId, doc, fieldNames) { + Cards.after.update((userId, doc, fieldNames) => { if (_.contains(fieldNames, 'archived')) { if (doc.archived) { Activities.insert({ + userId, activityType: 'archivedCard', boardId: doc.boardId, listId: doc.listId, cardId: doc._id, - userId: userId }); } else { Activities.insert({ + userId, activityType: 'restoredCard', boardId: doc.boardId, listId: doc.listId, cardId: doc._id, - userId: userId }); } } @@ -214,34 +213,34 @@ if (Meteor.isServer) { // New activity for card moves Cards.after.update(function(userId, doc, fieldNames) { - var oldListId = this.previous.listId; + const oldListId = this.previous.listId; if (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) { Activities.insert({ + userId, + oldListId, activityType: 'moveCard', listId: doc.listId, - oldListId: oldListId, boardId: doc.boardId, cardId: doc._id, - userId: userId }); } }); // Add a new activity if we add or remove a member to the card - Cards.before.update(function(userId, doc, fieldNames, modifier) { - if (! _.contains(fieldNames, 'members')) + Cards.before.update((userId, doc, fieldNames, modifier) => { + if (!_.contains(fieldNames, 'members')) return; - var memberId; + let memberId; // Say hello to the new member if (modifier.$addToSet && modifier.$addToSet.members) { memberId = modifier.$addToSet.members; - if (! _.contains(doc.members, memberId)) { + if (!_.contains(doc.members, memberId)) { Activities.insert({ + userId, + memberId, activityType: 'joinMember', boardId: doc.boardId, cardId: doc._id, - userId: userId, - memberId: memberId }); } } @@ -250,34 +249,34 @@ if (Meteor.isServer) { if (modifier.$pull && modifier.$pull.members) { memberId = modifier.$pull.members; Activities.insert({ + userId, + memberId, activityType: 'unjoinMember', boardId: doc.boardId, cardId: doc._id, - userId: userId, - memberId: memberId }); } }); // Remove all activities associated with a card if we remove the card - Cards.after.remove(function(userId, doc) { + Cards.after.remove((userId, doc) => { Activities.remove({ - cardId: doc._id + cardId: doc._id, }); }); - CardComments.after.insert(function(userId, doc) { + CardComments.after.insert((userId, doc) => { Activities.insert({ + userId, activityType: 'addComment', boardId: doc.boardId, cardId: doc.cardId, commentId: doc._id, - userId: userId }); }); - CardComments.after.remove(function(userId, doc) { - var activity = Activities.findOne({ commentId: doc._id }); + CardComments.after.remove((userId, doc) => { + const activity = Activities.findOne({ commentId: doc._id }); if (activity) { Activities.remove(activity._id); } diff --git a/collections/lists.js b/collections/lists.js index 1a30dbba..0c6ba407 100644 --- a/collections/lists.js +++ b/collections/lists.js @@ -2,92 +2,92 @@ Lists = new Mongo.Collection('lists'); Lists.attachSchema(new SimpleSchema({ title: { - type: String + type: String, }, archived: { - type: Boolean + type: Boolean, }, boardId: { - type: String + type: String, }, createdAt: { type: Date, - denyUpdate: true + denyUpdate: true, }, sort: { type: Number, decimal: true, // XXX We should probably provide a default - optional: true + optional: true, }, updatedAt: { type: Date, denyInsert: true, - optional: true - } + optional: true, + }, })); if (Meteor.isServer) { Lists.allow({ - insert: function(userId, doc) { + insert(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - update: function(userId, doc) { + update(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - remove: function(userId, doc) { + remove(userId, doc) { return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); }, - fetch: ['boardId'] + fetch: ['boardId'], }); } Lists.helpers({ - cards: function() { + cards() { return Cards.find(Filter.mongoSelector({ listId: this._id, - archived: false + archived: false, }), { sort: ['sort'] }); }, - board: function() { + board() { return Boards.findOne(this.boardId); - } + }, }); // HOOKS Lists.hookOptions.after.update = { fetchPrevious: false }; -Lists.before.insert(function(userId, doc) { +Lists.before.insert((userId, doc) => { doc.createdAt = new Date(); doc.archived = false; - if (! doc.userId) + if (!doc.userId) doc.userId = userId; }); -Lists.before.update(function(userId, doc, fieldNames, modifier) { +Lists.before.update((userId, doc, fieldNames, modifier) => { modifier.$set = modifier.$set || {}; modifier.$set.modifiedAt = new Date(); }); if (Meteor.isServer) { - Lists.after.insert(function(userId, doc) { + Lists.after.insert((userId, doc) => { Activities.insert({ + userId, type: 'list', activityType: 'createList', boardId: doc.boardId, listId: doc._id, - userId: userId }); }); - Lists.after.update(function(userId, doc) { + Lists.after.update((userId, doc) => { if (doc.archived) { Activities.insert({ + userId, type: 'list', activityType: 'archivedList', listId: doc._id, boardId: doc.boardId, - userId: userId }); } }); diff --git a/collections/unsavedEdits.js b/collections/unsavedEdits.js index e9105daf..87a70e22 100644 --- a/collections/unsavedEdits.js +++ b/collections/unsavedEdits.js @@ -4,16 +4,16 @@ UnsavedEditCollection = new Mongo.Collection('unsaved-edits'); UnsavedEditCollection.attachSchema(new SimpleSchema({ fieldName: { - type: String + type: String, }, docId: { - type: String + type: String, }, value: { - type: String + type: String, }, userId: { - type: String + type: String, }, })); @@ -25,10 +25,10 @@ if (Meteor.isServer) { insert: isAuthor, update: isAuthor, remove: isAuthor, - fetch: ['userId'] + fetch: ['userId'], }); } -UnsavedEditCollection.before.insert(function(userId, doc) { +UnsavedEditCollection.before.insert((userId, doc) => { doc.userId = userId; }); diff --git a/collections/users.js b/collections/users.js index 28b63ba7..b30b7805 100644 --- a/collections/users.js +++ b/collections/users.js @@ -2,42 +2,42 @@ Users = Meteor.users; // Search a user in the complete server database by its name or username. This // is used for instance to add a new user to a board. -var searchInFields = ['username', 'profile.name']; +const searchInFields = ['username', 'profile.name']; Users.initEasySearch(searchInFields, { use: 'mongo-db', - returnFields: searchInFields + returnFields: searchInFields, }); Users.helpers({ - boards: function() { + boards() { return Boards.find({ userId: this._id }); }, - starredBoards: function() { - var starredBoardIds = this.profile.starredBoards || []; + starredBoards() { + const starredBoardIds = this.profile.starredBoards || []; return Boards.find({archived: false, _id: {$in: starredBoardIds}}); }, - hasStarred: function(boardId) { - var starredBoardIds = this.profile.starredBoards || []; + hasStarred(boardId) { + const starredBoardIds = this.profile.starredBoards || []; return _.contains(starredBoardIds, boardId); }, - isBoardMember: function() { - var board = Boards.findOne(Session.get('currentBoard')); + isBoardMember() { + const board = Boards.findOne(Session.get('currentBoard')); return board && _.contains(_.pluck(board.members, 'userId'), this._id) && _.where(board.members, {userId: this._id})[0].isActive; }, - isBoardAdmin: function() { - var board = Boards.findOne(Session.get('currentBoard')); + isBoardAdmin() { + const board = Boards.findOne(Session.get('currentBoard')); if (this.isBoardMember(board)) return _.where(board.members, {userId: this._id})[0].isAdmin; }, - getInitials: function() { - var profile = this.profile || {}; + getInitials() { + const profile = this.profile || {}; if (profile.initials) return profile.initials; else if (profile.fullname) { - return _.reduce(profile.fullname.split(/\s+/), function(memo, word) { + return _.reduce(profile.fullname.split(/\s+/), (memo, word) => { return memo + word[0]; }, '').toUpperCase(); @@ -46,43 +46,41 @@ Users.helpers({ } }, - toggleBoardStar: function(boardId) { - var queryType = this.hasStarred(boardId) ? '$pull' : '$addToSet'; - var query = {}; - query[queryType] = { - 'profile.starredBoards': boardId - }; - Meteor.users.update(this._id, query); - } + toggleBoardStar(boardId) { + const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet'; + Meteor.users.update(this._id, { + [queryKind]: { + 'profile.starredBoards': boardId, + }, + }); + }, }); Meteor.methods({ - setUsername: function(username) { + setUsername(username) { check(username, String); - var nUsersWithUsername = Users.find({username: username}).count(); + const nUsersWithUsername = Users.find({ username }).count(); if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else { - Users.update(this.userId, {$set: { - username: username - }}); + Users.update(this.userId, {$set: { username }}); } - } + }, }); -Users.before.insert(function(userId, doc) { +Users.before.insert((userId, doc) => { doc.profile = doc.profile || {}; - if (! doc.username && doc.profile.name) { + if (!doc.username && doc.profile.name) { doc.username = doc.profile.name.toLowerCase().replace(/\s/g, ''); } }); if (Meteor.isServer) { // Let mongoDB ensure username unicity - Meteor.startup(function() { + Meteor.startup(() => { Users._collection._ensureIndex({ - username: 1 + username: 1, }, { unique: true }); }); @@ -94,44 +92,44 @@ if (Meteor.isServer) { Users.after.update(function(userId, user, fieldNames) { // The `starredBoards` list is hosted on the `profile` field. If this // field hasn't been modificated we don't need to run this hook. - if (! _.contains(fieldNames, 'profile')) + if (!_.contains(fieldNames, 'profile')) return; // To calculate a diff of board starred ids, we get both the previous // and the newly board ids list - var getStarredBoardsIds = function(doc) { + function getStarredBoardsIds(doc) { return doc.profile && doc.profile.starredBoards; - }; - var oldIds = getStarredBoardsIds(this.previous); - var newIds = getStarredBoardsIds(user); + } + const oldIds = getStarredBoardsIds(this.previous); + const newIds = getStarredBoardsIds(user); // The _.difference(a, b) method returns the values from a that are not in // b. We use it to find deleted and newly inserted ids by using it in one // direction and then in the other. - var incrementBoards = function(boardsIds, inc) { - _.forEach(boardsIds, function(boardId) { + function incrementBoards(boardsIds, inc) { + _.forEach(boardsIds, (boardId) => { Boards.update(boardId, {$inc: {stars: inc}}); }); - }; + } incrementBoards(_.difference(oldIds, newIds), -1); incrementBoards(_.difference(newIds, oldIds), +1); }); // XXX i18n - Users.after.insert(function(userId, doc) { - var ExampleBoard = { + Users.after.insert((userId, doc) => { + const ExampleBoard = { title: 'Welcome Board', userId: doc._id, - permission: 'private' + permission: 'private', }; // Insert the Welcome Board - Boards.insert(ExampleBoard, function(err, boardId) { + Boards.insert(ExampleBoard, (err, boardId) => { - _.forEach(['Basics', 'Advanced'], function(title) { - var list = { - title: title, - boardId: boardId, + _.forEach(['Basics', 'Advanced'], (title) => { + const list = { + title, + boardId, userId: ExampleBoard.userId, // XXX Not certain this is a bug, but we except these fields get @@ -139,7 +137,7 @@ if (Meteor.isServer) { // hook is not called in this case, we have to dublicate the logic and // set them here. archived: false, - createdAt: new Date() + createdAt: new Date(), }; Lists.insert(list); @@ -150,9 +148,7 @@ if (Meteor.isServer) { // Presence indicator if (Meteor.isClient) { - Presence.state = function() { - return { - currentBoardId: Session.get('currentBoard') - }; + Presence.state = () => { + return { currentBoardId: Session.get('currentBoard') }; }; } diff --git a/sandstorm.js b/sandstorm.js index c410a7f8..8ff5bad3 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -1,12 +1,12 @@ // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. -var isSandstorm = Meteor.settings && Meteor.settings.public && - Meteor.settings.public.sandstorm; +const isSandstorm = Meteor.settings && Meteor.settings.public && + Meteor.settings.public.sandstorm; // In sandstorm we only have one board per sandstorm instance. Since we want to // keep most of our code unchanged, we simply hard-code a board `_id` and // redirect the user to this particular board. -var sandstormBoard = { +const sandstormBoard = { _id: 'sandstorm', // XXX Should be shared with the grain instance name. @@ -16,15 +16,15 @@ var sandstormBoard = { // Board access security is handled by sandstorm, so in our point of view we // can alway assume that the board is public (unauthorized users won’t be able // to access it anyway). - permission: 'public' + permission: 'public', }; // The list of permissions a user have is provided by sandstorm accounts // package. -var userHasPermission = function(user, permission) { - var userPermissions = user.services.sandstorm.permissions; +function userHasPermission(user, permission) { + const userPermissions = user.services.sandstorm.permissions; return userPermissions.indexOf(permission) > -1; -}; +} if (isSandstorm && Meteor.isServer) { // Redirect the user to the hard-coded board. On the first launch the user @@ -35,15 +35,15 @@ if (isSandstorm && Meteor.isServer) { // browser, a server-side redirection solves both of these issues. // // XXX Maybe sandstorm manifest could provide some kind of "home url"? - Picker.route('/', function(params, request, response) { - var base = request.headers['x-sandstorm-base-path']; + Picker.route('/', (params, request, response) => { + const base = request.headers['x-sandstorm-base-path']; // XXX If this routing scheme changes, this will break. We should generation // the location url using the router, but at the time of writting, the // router is only accessible on the client. - var path = '/boards/' + sandstormBoard._id + '/' + sandstormBoard.slug; + const path = `/boards/${sandstormBoard._id}/${sandstormBoard.slug}`; response.writeHead(301, { - Location: base + path + Location: base + path, }); response.end(); }); @@ -53,8 +53,8 @@ if (isSandstorm && Meteor.isServer) { // unique board document. Note that when the `Users.after.insert` hook is // called, the user is inserted into the database but not connected. So // despite the appearances `userId` is null in this block. - Users.after.insert(function(userId, doc) { - if (! Boards.findOne(sandstormBoard._id)) { + Users.after.insert((userId, doc) => { + if (!Boards.findOne(sandstormBoard._id)) { Boards.insert(sandstormBoard, {validate: false}); Boards.update(sandstormBoard._id, { $set: { @@ -62,14 +62,14 @@ if (isSandstorm && Meteor.isServer) { 'members.0': { userId: doc._id, isActive: true, - isAdmin: true - } - } + isAdmin: true, + }, + }, }); Activities.update( - { activityTypeId: sandstormBoard._id }, { - $set: { userId: doc._id } - }); + { activityTypeId: sandstormBoard._id }, + { $set: { userId: doc._id }} + ); } // If the hard-coded board already exists and we are inserting a new user, @@ -77,15 +77,15 @@ if (isSandstorm && Meteor.isServer) { else if (userHasPermission(doc, 'participate')) { Boards.update({ _id: sandstormBoard._id, - permission: 'public' + permission: 'public', }, { $push: { members: { userId: doc._id, isActive: true, - isAdmin: userHasPermission(doc, 'configure') - } - } + isAdmin: userHasPermission(doc, 'configure'), + }, + }, }); } }); @@ -96,10 +96,10 @@ if (isSandstorm && Meteor.isClient) { // session has a different URL whereas Meteor computes absoluteUrl based on // the ROOT_URL environment variable. So we overwrite this function on a // sandstorm client to return relative paths instead of absolutes. - var _absoluteUrl = Meteor.absoluteUrl; - var _defaultOptions = Meteor.absoluteUrl.defaultOptions; - Meteor.absoluteUrl = function(path, options) { - var url = _absoluteUrl(path, options); + const _absoluteUrl = Meteor.absoluteUrl; + const _defaultOptions = Meteor.absoluteUrl.defaultOptions; + Meteor.absoluteUrl = (path, options) => { + const url = _absoluteUrl(path, options); return url.replace(/^https?:\/\/127\.0\.0\.1:[0-9]{2,5}/, ''); }; Meteor.absoluteUrl.defaultOptions = _defaultOptions; @@ -108,6 +108,4 @@ if (isSandstorm && Meteor.isClient) { // We use this blaze helper in the UI to hide some templates that does not make // sense in the context of sandstorm, like board staring, board archiving, user // name edition, etc. -Blaze.registerHelper('isSandstorm', function() { - return isSandstorm; -}); +Blaze.registerHelper('isSandstorm', () => isSandstorm); diff --git a/server/lib/utils.js b/server/lib/utils.js index 56c87c0b..3e141f06 100644 --- a/server/lib/utils.js +++ b/server/lib/utils.js @@ -1,5 +1,5 @@ allowIsBoardAdmin = function(userId, board) { - var admins = _.pluck(_.where(board.members, {isAdmin: true}), 'userId'); + const admins = _.pluck(_.where(board.members, {isAdmin: true}), 'userId'); return _.contains(admins, userId); }; diff --git a/server/migrations.js b/server/migrations.js index e9b60ed9..05f5ff7d 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -11,35 +11,35 @@ // // To prevent this bug we always have to disable the schema validation and // argument transformations. We generally use the shorthandlers defined below. -var noValidate = { +const noValidate = { validate: false, filter: false, autoConvert: false, removeEmptyStrings: false, - getAutoValues: false + getAutoValues: false, }; -var noValidateMulti = _.extend(noValidate, { multi: true }); +const noValidateMulti = { ...noValidate, multi: true }; -Migrations.add('board-background-color', function() { - var defaultColor = '#16A085'; +Migrations.add('board-background-color', () => { + const defaultColor = '#16A085'; Boards.update({ background: { - $exists: false - } + $exists: false, + }, }, { $set: { background: { type: 'color', - color: defaultColor - } - } + color: defaultColor, + }, + }, }, noValidateMulti); }); -Migrations.add('lowercase-board-permission', function() { - _.forEach(['Public', 'Private'], function(permission) { +Migrations.add('lowercase-board-permission', () => { + _.forEach(['Public', 'Private'], (permission) => { Boards.update( - { permission: permission }, + { permission }, { $set: { permission: permission.toLowerCase() } }, noValidateMulti ); @@ -47,23 +47,23 @@ Migrations.add('lowercase-board-permission', function() { }); // Security migration: see https://github.com/wekan/wekan/issues/99 -Migrations.add('change-attachments-type-for-non-images', function() { - var newTypeForNonImage = 'application/octet-stream'; - Attachments.find().forEach(function(file) { - if (! file.isImage()) { +Migrations.add('change-attachments-type-for-non-images', () => { + const newTypeForNonImage = 'application/octet-stream'; + Attachments.find().forEach((file) => { + if (!file.isImage()) { Attachments.update(file._id, { $set: { 'original.type': newTypeForNonImage, - 'copies.attachments.type': newTypeForNonImage - } + 'copies.attachments.type': newTypeForNonImage, + }, }, noValidate); } }); }); -Migrations.add('card-covers', function() { - Cards.find().forEach(function(card) { - var cover = Attachments.findOne({ cardId: card._id, cover: true }); +Migrations.add('card-covers', () => { + Cards.find().forEach((card) => { + const cover = Attachments.findOne({ cardId: card._id, cover: true }); if (cover) { Cards.update(card._id, {$set: {coverId: cover._id}}, noValidate); } @@ -71,54 +71,54 @@ Migrations.add('card-covers', function() { Attachments.update({}, {$unset: {cover: ''}}, noValidateMulti); }); -Migrations.add('use-css-class-for-boards-colors', function() { - var associationTable = { +Migrations.add('use-css-class-for-boards-colors', () => { + const associationTable = { '#27AE60': 'nephritis', '#C0392B': 'pomegranate', '#2980B9': 'belize', '#8E44AD': 'wisteria', '#2C3E50': 'midnight', - '#E67E22': 'pumpkin' + '#E67E22': 'pumpkin', }; - Boards.find().forEach(function(board) { - var oldBoardColor = board.background.color; - var newBoardColor = associationTable[oldBoardColor]; + Boards.find().forEach((board) => { + const oldBoardColor = board.background.color; + const newBoardColor = associationTable[oldBoardColor]; Boards.update(board._id, { $set: { color: newBoardColor }, - $unset: { background: '' } + $unset: { background: '' }, }, noValidate); }); }); -Migrations.add('denormalize-star-number-per-board', function() { - Boards.find().forEach(function(board) { - var nStars = Users.find({'profile.starredBoards': board._id}).count(); +Migrations.add('denormalize-star-number-per-board', () => { + Boards.find().forEach((board) => { + const nStars = Users.find({'profile.starredBoards': board._id}).count(); Boards.update(board._id, {$set: {stars: nStars}}, noValidate); }); }); // We want to keep a trace of former members so we can efficiently publish their // infos in the general board publication. -Migrations.add('add-member-isactive-field', function() { - Boards.find({}, {fields: {members: 1}}).forEach(function(board) { - var allUsersWithSomeActivity = _.chain( - Activities.find({boardId: board._id}, {fields:{userId:1}}).fetch()) +Migrations.add('add-member-isactive-field', () => { + Boards.find({}, {fields: {members: 1}}).forEach((board) => { + const allUsersWithSomeActivity = _.chain( + Activities.find({ boardId: board._id }, { fields:{ userId:1 }}).fetch()) .pluck('userId') .uniq() .value(); - var currentUsers = _.pluck(board.members, 'userId'); - var formerUsers = _.difference(allUsersWithSomeActivity, currentUsers); + const currentUsers = _.pluck(board.members, 'userId'); + const formerUsers = _.difference(allUsersWithSomeActivity, currentUsers); - var newMemberSet = []; - _.forEach(board.members, function(member) { + const newMemberSet = []; + _.forEach(board.members, (member) => { member.isActive = true; newMemberSet.push(member); }); - _.forEach(formerUsers, function(userId) { + _.forEach(formerUsers, (userId) => { newMemberSet.push({ - userId: userId, + userId, isAdmin: false, - isActive: false + isActive: false, }); }); Boards.update(board._id, {$set: {members: newMemberSet}}, noValidate); diff --git a/server/publications/activities.js b/server/publications/activities.js index 5277206c..38c61ebf 100644 --- a/server/publications/activities.js +++ b/server/publications/activities.js @@ -1,24 +1,19 @@ -// We use activities fields at three different places: -// 1. The home page that contains -// 2. The board -// 3. -// We use publish paginate for these three publications. +// We use activities fields at two different places: +// 1. The board sidebar +// 2. The card activity tab +// We use this publication to paginate for these two publications. -Meteor.publish('activities', function(mode, id, limit) { - check(mode, Match.Where(function(x) { +Meteor.publish('activities', (kind, id, limit) => { + check(kind, Match.Where((x) => { return ['board', 'card'].indexOf(x) !== -1; })); check(id, String); check(limit, Number); - var selector = {}; - if (mode === 'board') - selector.boardId = id; - else if (mode === 'card') - selector.cardId = id; - - return Activities.find(selector, { + return Activities.find({ + [`${kind}Id`]: id, + }, { + limit, sort: {createdAt: -1}, - limit: limit }); }); diff --git a/server/publications/boards.js b/server/publications/boards.js index ae30357f..7f6a544b 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -5,20 +5,20 @@ Meteor.publish('boards', function() { // Ensure that the user is connected. If it is not, we need to return an empty // array to tell the client to remove the previously published docs. - if (! Match.test(this.userId, String)) + if (!Match.test(this.userId, String)) return []; // Defensive programming to verify that starredBoards has the expected // format -- since the field is in the `profile` a user can modify it. - var starredBoards = Users.findOne(this.userId).profile.starredBoards || []; + const starredBoards = Users.findOne(this.userId).profile.starredBoards || []; check(starredBoards, [String]); return Boards.find({ archived: false, $or: [ { 'members.userId': this.userId }, - { _id: { $in: starredBoards } } - ] + { _id: { $in: starredBoards } }, + ], }, { fields: { _id: 1, @@ -27,13 +27,13 @@ Meteor.publish('boards', function() { title: 1, color: 1, members: 1, - permission: 1 - } + permission: 1, + }, }); }); Meteor.publish('archivedBoards', function() { - if (! Match.test(this.userId, String)) + if (!Match.test(this.userId, String)) return []; return Boards.find({ @@ -41,23 +41,23 @@ Meteor.publish('archivedBoards', function() { members: { $elemMatch: { userId: this.userId, - isAdmin: true - } - } + isAdmin: true, + }, + }, }, { fields: { _id: 1, archived: 1, slug: 1, - title: 1 - } - }) + title: 1, + }, + }); }); Meteor.publishComposite('board', function(boardId) { check(boardId, String); return { - find: function() { + find() { return Boards.find({ _id: boardId, archived: false, @@ -65,18 +65,18 @@ Meteor.publishComposite('board', function(boardId) { // it. $or: [ { permission: 'public' }, - { 'members.userId': this.userId } - ] + { 'members.userId': this.userId }, + ], }, { limit: 1 }); }, children: [ // Lists { - find: function(board) { + find(board) { return Lists.find({ - boardId: board._id + boardId: board._id, }); - } + }, }, // Cards and cards comments @@ -103,48 +103,48 @@ Meteor.publishComposite('board', function(boardId) { // And in the meantime our code below works pretty well -- it's not even a // hack! { - find: function(board) { + find(board) { return Cards.find({ - boardId: board._id + boardId: board._id, }); }, children: [ // comments { - find: function(card) { + find(card) { return CardComments.find({ - cardId: card._id + cardId: card._id, }); - } + }, }, // Attachments { - find: function(card) { + find(card) { return Attachments.find({ - cardId: card._id + cardId: card._id, }); - } - } - ] + }, + }, + ], }, // Board members. This publication also includes former board members that // aren't members anymore but may have some activities attached to them in // the history. { - find: function(board) { + find(board) { return Users.find({ - _id: { $in: _.pluck(board.members, 'userId') } + _id: { $in: _.pluck(board.members, 'userId') }, }); }, // Presence indicators children: [{ - find: function(user) { + find(user) { return Presences.find({userId: user._id}); - } - }] - } - ] + }, + }], + }, + ], }; }); diff --git a/server/publications/cards.js b/server/publications/cards.js index a9664095..c9abc421 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -1,4 +1,4 @@ -Meteor.publish('card', function(cardId) { +Meteor.publish('card', (cardId) => { check(cardId, String); return Cards.find({ _id: cardId }); }); diff --git a/server/publications/unsavedEdits.js b/server/publications/unsavedEdits.js index ecc24646..264eba74 100644 --- a/server/publications/unsavedEdits.js +++ b/server/publications/unsavedEdits.js @@ -1,5 +1,5 @@ Meteor.publish('unsaved-edits', function() { return UnsavedEditCollection.find({ - userId: this.userId + userId: this.userId, }); }); |