diff options
Diffstat (limited to 'client')
40 files changed, 1066 insertions, 1139 deletions
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, + }; + }, }; |