From dcc64f44f9f81d32c8071c6bdac86546eaeb57a0 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Wed, 27 May 2015 17:17:00 +0200 Subject: UI improvements * Implement visibility choice on board creation; * Rework the board header bar. Remove links to un-implemented features; * Implement a board star counter (visible if the board have >2 stars); * Define a new icon (a thin cross) to close elements; * Remove $(document).on('mouseover') event handlers that were basically fired hundreds of times for nothing, we now define a proper Tracker dependency to execute jquery-ui plugin initialization only when something has changed; * Bug fixes related to list scrolling. --- client/components/boards/boardBody.jade | 34 ++++ client/components/boards/boardBody.js | 141 ++++++++++++++ client/components/boards/boardBody.styl | 34 ++++ client/components/boards/boardHeader.jade | 94 ++++++++++ client/components/boards/boardHeader.js | 157 ++++++++++++++++ client/components/boards/boardList.jade | 21 +++ client/components/boards/boardList.js | 34 ++++ client/components/boards/boardList.styl | 85 +++++++++ client/components/boards/body.jade | 34 ---- client/components/boards/body.js | 141 -------------- client/components/boards/body.styl | 34 ---- client/components/boards/colors.styl | 15 +- client/components/boards/events.js | 96 ---------- client/components/boards/header.jade | 87 --------- client/components/boards/header.js | 7 - client/components/boards/header.styl | 137 -------------- client/components/boards/helpers.js | 39 ---- client/components/boards/list.jade | 14 -- client/components/boards/list.styl | 85 --------- client/components/cards/details.jade | 4 +- client/components/cards/events.js | 61 ------- client/components/cards/minicard.jade | 27 +++ client/components/cards/minicard.js | 14 ++ client/components/forms/forms.styl | 23 +-- client/components/forms/inlinedform.js | 7 +- client/components/lists/body.jade | 29 +-- client/components/lists/body.js | 34 ++-- client/components/lists/main.js | 87 +++++---- client/components/main/header.styl | 284 ++++++++--------------------- client/components/main/popup.js | 26 ++- client/components/main/popup.styl | 79 +------- client/components/main/popup.tpl.jade | 5 +- client/components/sidebar/sidebar.js | 36 +++- client/components/users/headerButtons.jade | 20 +- 34 files changed, 862 insertions(+), 1163 deletions(-) create mode 100644 client/components/boards/boardBody.jade create mode 100644 client/components/boards/boardBody.js create mode 100644 client/components/boards/boardBody.styl create mode 100644 client/components/boards/boardHeader.jade create mode 100644 client/components/boards/boardHeader.js create mode 100644 client/components/boards/boardList.jade create mode 100644 client/components/boards/boardList.js create mode 100644 client/components/boards/boardList.styl delete mode 100644 client/components/boards/body.jade delete mode 100644 client/components/boards/body.js delete mode 100644 client/components/boards/body.styl delete mode 100644 client/components/boards/events.js delete mode 100644 client/components/boards/header.jade delete mode 100644 client/components/boards/header.js delete mode 100644 client/components/boards/header.styl delete mode 100644 client/components/boards/list.jade delete mode 100644 client/components/boards/list.styl create mode 100644 client/components/cards/minicard.jade create mode 100644 client/components/cards/minicard.js (limited to 'client/components') diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade new file mode 100644 index 00000000..672a3860 --- /dev/null +++ b/client/components/boards/boardBody.jade @@ -0,0 +1,34 @@ +//- + XXX This template can't be transformed into a component because it is + included by iron-router. That's a bug. + See https://github.com/peerlibrary/meteor-blaze-components/issues/44 +template(name="board") + +boardComponent + +template(name="boardComponent") + if this + .board-wrapper(class=colorClass) + .board-canvas(class=sidebarSize) + .lists.js-lists + each lists + +list(this) + if currentCardIsInThisList + +cardDetails(currentCard) + if currentUser.isBoardMember + +addListForm + +sidebar + else + +message(label="board-no-found") + +template(name="addListForm") + .list.js-list.list-composer.js-list-composer + +inlinedForm(autoclose=false) + input.list-name-input(type="text" placeholder="{{_ 'add-list'}}" + autocomplete="off" autofocus value=getCache) + div.edit-controls.clearfix + button.primary.confirm.js-save-edit(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + .js-open-inlined-form + i.fa.fa-plus + | {{_ 'add-list'}} diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js new file mode 100644 index 00000000..cf32f764 --- /dev/null +++ b/client/components/boards/boardBody.js @@ -0,0 +1,141 @@ +// XXX This event list must be abstracted somewhere else. +var endTransitionEvents = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend' +].join(' '); + +BlazeComponent.extendComponent({ + template: function() { + return 'boardComponent'; + }, + + openNewListForm: function() { + this.componentChildren('addListForm')[0].open(); + }, + + showNewCardForms: function(value) { + _.each(this.componentChildren('list'), function(listComponent) { + listComponent.showNewCardForm(value); + }); + }, + + scrollLeft: function(position) { + position = position || 0; + var $container = $(this.find('.js-lists')); + var containerWidth = $container.width(); + var currentScrollPosition = $container.scrollLeft(); + if (position < currentScrollPosition) { + $container.animate({ + scrollLeft: position + }); + } else if (position > currentScrollPosition + containerWidth) { + $container.animate({ + scrollLeft: Math.max(0, position - containerWidth) + }); + } + }, + + currentCardIsInThisList: function() { + var currentCard = Cards.findOne(Session.get('currentCard')); + var listId = this.currentData()._id; + return currentCard && currentCard.listId === listId; + }, + + onRendered: function() { + var self = this; + + self.scrollLeft(); + + var lists = this.find('.js-lists'); + + // We want to animate the card details window closing. We rely on CSS + // transition for the actual animation. + lists._uihooks = { + removeElement: function(node) { + var removeNode = _.once(function() { + node.parentNode.removeChild(node); + }); + if ($(node).hasClass('js-card-detail')) { + $(node).css({ + flex: '0 0 0', + padding: 0 + }); + $(lists).one(endTransitionEvents, removeNode); + } else { + removeNode(); + } + } + }; + + if (! Meteor.userId() || ! Meteor.user().isBoardMember()) + return; + + self.$(lists).sortable({ + tolerance: 'pointer', + appendTo: '.js-lists', + helper: 'clone', + items: '.js-list:not(.js-list-composer)', + placeholder: 'list placeholder', + start: function(event, ui) { + $('.list.placeholder').height(ui.item.height()); + Popup.close(); + }, + stop: function() { + self.$('.js-lists').find('.js-list:not(.js-list-composer)').each( + function(i, list) { + var data = Blaze.getData(list); + Lists.update(data._id, { + $set: { + sort: i + } + }); + } + ); + } + }); + + // If there is no data in the board (ie, no lists) we autofocus the list + // creation form by clicking on the corresponding element. + if (self.data().lists().count() === 0) { + this.openNewListForm(); + } + }, + + sidebarSize: function() { + var sidebar = this.componentChildren('sidebar')[0]; + if (sidebar && sidebar.isOpen()) + return 'next-sidebar'; + } +}).register('boardComponent'); + +BlazeComponent.extendComponent({ + template: function() { + return 'addListForm'; + }, + + // Proxy + open: function() { + this.componentChildren('inlinedForm')[0].open(); + }, + + events: function() { + return [{ + submit: function(evt) { + evt.preventDefault(); + var title = this.find('.list-name-input'); + if ($.trim(title.value)) { + Lists.insert({ + title: title.value, + boardId: Session.get('currentBoard'), + sort: $('.list').length + }); + + title.value = ''; + } + } + }]; + } +}).register('addListForm'); diff --git a/client/components/boards/boardBody.styl b/client/components/boards/boardBody.styl new file mode 100644 index 00000000..de4963ab --- /dev/null +++ b/client/components/boards/boardBody.styl @@ -0,0 +1,34 @@ +@import 'nib' + +.board-wrapper + left: 0 + top: 0 + bottom: 0 + right: 0 + position: absolute + overflow: hidden + + .board-canvas + position: absolute + left: 0 + right: 0 + top: 0 + bottom: 0 + transition: margin .1s + + &.next-sidebar + margin-right: 248px + +.lists + align-items: flex-start + display: flex + flex-direction: row + margin-bottom: 10px + overflow-x: auto + overflow-y: hidden + padding-bottom: 10px + position: absolute + top: 0 + right: 0 + bottom: 0 + left: 0 diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade new file mode 100644 index 00000000..0ea359fc --- /dev/null +++ b/client/components/boards/boardHeader.jade @@ -0,0 +1,94 @@ +template(name="headerBoard") + h1.header-board-menu( + class="{{#if currentUser.isBoardMember}}is-clickable js-edit-board-title{{/if}}") + = title + + .board-header-btns.left + unless isSandstorm + if currentUser + a.board-header-btn.js-star-board(class="{{#if isStarred}}is-hovered{{/if}}" + title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") + i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") + if showStarCounter + span {{_ 'board-nb-stars' stars}} + + a.board-header-btn.js-change-visibility(class="{{#unless currentUser.isBoardAdmin}}no-edit{{/unless}}") + i.fa(class="{{#if isPublic}}fa-globe{{else}}fa-lock{{/if}}") + span {{_ permission}} + + .board-header-btns.right + a.board-header-btn.js-open-filter-view( + title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{/if}}" + class="{{#if Filter.isActive}}emphasis{{/if}}") + i.fa.fa-filter + if Filter.isActive + span {{_ 'filter-on'}} + a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") + i.fa.fa-times-thin + else + span {{_ 'filter'}} + .separator + a.board-header-btn.js-open-board-menu + i.board-header-btn-icon.fa.fa-cog + +template(name="boardMenuPopup") + ul.pop-over-list + li: a.js-change-board-color Change color + li: a Copy this board + li: a Permissions + +template(name="boardVisibilityList") + ul.pop-over-list + li + with "private" + a.js-select-visibility + i.fa.fa-lock.colorful + | {{_ 'private'}} + if visibilityCheck + i.fa.fa-check + span.sub-name {{_ 'private-desc'}} + li + with "public" + a.js-select-visibility + i.fa.fa-globe.colorful + | {{_ 'public'}} + if visibilityCheck + i.fa.fa-check + span.sub-name {{_ 'public-desc'}} + +template(name="boardChangeVisibilityPopup") + +boardVisibilityList + +template(name="boardChangeColorPopup") + .board-backgrounds-list.clearfix + each backgroundColors + .board-background-select.js-select-background + span.background-box(class="board-color-{{this}}") + if isSelected + i.fa.fa-check + +template(name="createBoardPopup") + form + label + | {{_ 'title'}} + input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) + if visibilityMenuIsOpen.get + +boardVisibilityList + else + p.quiet + if $eq visibility.get 'public' + span.fa.fa-globe.colorful + | {{{_ 'board-public-info'}}} + else + span.fa.fa-lock.colorful + | {{{_ 'board-private-info'}}} + a.js-change-visibility Change. + input.primary.wide(type="submit" value="{{_ 'create'}}") + + +template(name="boardChangeTitlePopup") + form + label + | {{_ 'title'}} + input.js-board-name(type="text" value="{{title}}" autofocus) + input.primary.wide(type="submit" value="{{_ 'rename'}}") diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js new file mode 100644 index 00000000..a78012ca --- /dev/null +++ b/client/components/boards/boardHeader.js @@ -0,0 +1,157 @@ +Template.boardMenuPopup.events({ + 'click .js-rename-board': Popup.open('boardChangeTitle'), + 'click .js-change-board-color': Popup.open('boardChangeColor') +}); + +Template.boardChangeTitlePopup.events({ + submit: function(evt, t) { + var title = t.$('.js-board-name').val().trim(); + if (title) { + Boards.update(this._id, { + $set: { + title: title + } + }); + Popup.close(); + } + evt.preventDefault(); + } +}); + +BlazeComponent.extendComponent({ + template: function() { + return 'headerBoard'; + }, + + isStarred: function() { + var boardId = this.currentData()._id; + var user = Meteor.user(); + return boardId && user && user.hasStarred(boardId); + }, + + // Only show the star counter if the number of star is greater than 2 + showStarCounter: function() { + return this.currentData().stars > 2; + }, + + events: function() { + return [{ + 'click .js-edit-board-title': Popup.open('boardChangeTitle'), + 'click .js-star-board': function() { + 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() { + Sidebar.setView('filter'); + }, + 'click .js-filter-reset': function(evt) { + evt.stopPropagation(); + Sidebar.setView(); + Filter.reset(); + } + }]; + } +}).register('headerBoard'); + +BlazeComponent.extendComponent({ + template: function() { + return 'boardChangeColorPopup'; + }, + + backgroundColors: function() { + return Boards.simpleSchema()._schema.color.allowedValues; + }, + + isSelected: function() { + var currentBoard = Boards.findOne(Session.get('currentBoard')); + return currentBoard.color === this.currentData().toString(); + }, + + events: function() { + return [{ + 'click .js-select-background': function(evt) { + var currentBoardId = Session.get('currentBoard'); + Boards.update(currentBoardId, { + $set: { + color: this.currentData().toString() + } + }); + evt.preventDefault(); + } + }]; + } +}).register('boardChangeColorPopup'); + +BlazeComponent.extendComponent({ + template: function() { + return 'createBoardPopup'; + }, + + onCreated: function() { + this.visibilityMenuIsOpen = new ReactiveVar(false); + this.visibility = new ReactiveVar('private'); + }, + + visibilityCheck: function() { + return this.currentData() === this.visibility.get(); + }, + + setVisibility: function(visibility) { + this.visibility.set(visibility); + this.visibilityMenuIsOpen.set(false); + }, + + toogleVisibilityMenu: function() { + this.visibilityMenuIsOpen.set(! this.visibilityMenuIsOpen.get()); + }, + + onSubmit: function(evt) { + evt.preventDefault(); + var title = this.find('.js-new-board-title').value; + var visibility = this.visibility.get(); + + var boardId = Boards.insert({ + title: title, + permission: visibility + }); + + Utils.goBoardId(boardId); + }, + + events: function() { + return [{ + 'click .js-select-visibility': function() { + this.setVisibility(this.currentData()); + }, + 'click .js-change-visibility': this.toogleVisibilityMenu, + submit: this.onSubmit + }]; + } +}).register('createBoardPopup'); + +BlazeComponent.extendComponent({ + template: function() { + return 'boardChangeVisibilityPopup'; + }, + + visibilityCheck: function() { + var currentBoard = Boards.findOne(Session.get('currentBoard')); + return this.currentData() === currentBoard.permission; + }, + + selectBoardVisibility: function() { + Boards.update(Session.get('currentBoard'), { + $set: { + permission: this.currentData() + } + }); + Popup.close(); + }, + + events: function() { + return [{ + 'click .js-select-visibility': this.selectBoardVisibility + }]; + } +}).register('boardChangeVisibilityPopup'); diff --git a/client/components/boards/boardList.jade b/client/components/boards/boardList.jade new file mode 100644 index 00000000..e133f73c --- /dev/null +++ b/client/components/boards/boardList.jade @@ -0,0 +1,21 @@ +//- + XXX This template can't be transformed into a component because it is + included by iron-router. That's a bug. + See https://github.com/peerlibrary/meteor-blaze-components/issues/44 +template(name="boards") + +boardList + +template(name="boardList") + if boards + ul.board-list.clearfix + each boards + li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) + a.js-open-board(href="{{ pathFor route='Board' boardId=_id }}") + span.details + span.board-list-item-name= title + i.fa.fa-star-o.js-star-board( + class="{{#if isStarred}}is-star-active{{/if}}" + title="{{_ 'star-board-title'}}") + else + p.quiet {{_ 'no-boards'}} + button.js-add-board {{_ 'add-board'}} diff --git a/client/components/boards/boardList.js b/client/components/boards/boardList.js new file mode 100644 index 00000000..8dc70ee9 --- /dev/null +++ b/client/components/boards/boardList.js @@ -0,0 +1,34 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'boardList'; + }, + + boards: function() { + return Boards.find({}, { + sort: ['title'] + }); + }, + + starredBoards: function() { + var cursor = Boards.find({ + _id: { $in: Meteor.user().profile.starredBoards || [] } + }, { + sort: ['title'] + }); + return cursor.count() === 0 ? null : cursor; + }, + + isStarred: function() { + var user = Meteor.user(); + return user && user.hasStarred(this._id); + }, + + events: function() { + return [{ + 'click .js-star-board': function(evt) { + Meteor.user().toggleBoardStar(this._id); + evt.preventDefault(); + } + }]; + } +}).register('boardList'); diff --git a/client/components/boards/boardList.styl b/client/components/boards/boardList.styl new file mode 100644 index 00000000..c068dbb0 --- /dev/null +++ b/client/components/boards/boardList.styl @@ -0,0 +1,85 @@ +.board-list + margin: 25px auto + width: 1200px + + li + float: left + width: 25% + box-sizing: border-box + position: relative + + &.starred .fa-star-o + opacity: 1 + + a + background-color: #999 + color: #f6f6f6 + height: 90px + font-size: 16px + line-height: 22px + border-radius: 3px + display: block + font-weight: 700 + min-height: 18px + padding: 8px 12px 8px 12px + margin: 0 16px 16px 0 + position: relative + text-decoration: none + + &.tile + background-size: auto + background-repeat: repeat + + .details + height: 84px + padding-right: 36px + bottom: 0 + left: 0 + overflow: hidden + padding: 9px 12px + position: absolute + right: 0 + top: 0 + + .board-list-item-sub-name + color: rgba(255, 255, 255, .5) + display: block + font-size: 14px + font-weight: 400 + line-height: 22px + + .fa-star-o + bottom: 0 + font-size: 14px + height: 18px + line-height: 18px + opacity: 0 + padding: 9px 9px + position: absolute + right: 0 + top: 0 + transition-duration: .15s + transition-property: color, font-size, background + + .is-star-active + color: #e6bf00 + + li:hover a + color: #f6f6f6 + + .fa-star-o + color: #fff + opacity: .75 + + &:hover + font-size: 18px + opacity: 1 + + &.is-star-active + color: #e6bf00 + opacity: 1 + + &:hover + color: #ffd91a + font-size: 16px + opacity: 1 diff --git a/client/components/boards/body.jade b/client/components/boards/body.jade deleted file mode 100644 index b157b742..00000000 --- a/client/components/boards/body.jade +++ /dev/null @@ -1,34 +0,0 @@ -//- - XXX This template can't be transformed into a component because it is - included by iron-router. That's a bug. - See https://github.com/peerlibrary/meteor-blaze-components/issues/44 -template(name="board") - +boardComponent - -template(name="boardComponent") - if this - .board-wrapper(class=colorClass) - .board-canvas(class=sidebarSize) - .lists.js-lists - each lists - +list(this) - if currentCardIsInThisList - +cardDetails(currentCard) - if currentUser.isBoardMember - +addListForm - +sidebar - else - +message(label="board-no-found") - -template(name="addListForm") - .list.js-list.list-composer.js-list-composer - +inlinedForm(autoclose=false) - input.list-name-input(type="text" placeholder="{{_ 'add-list'}}" - autocomplete="off" autofocus value=getCache) - div.edit-controls.clearfix - button.primary.confirm.js-save-edit(type="submit") {{_ 'save'}} - a.fa.fa-times.dark-hover.cancel.js-close-inlined-form - else - .js-open-inlined-form - i.fa.fa-plus - | {{_ 'add-list'}} diff --git a/client/components/boards/body.js b/client/components/boards/body.js deleted file mode 100644 index cf32f764..00000000 --- a/client/components/boards/body.js +++ /dev/null @@ -1,141 +0,0 @@ -// XXX This event list must be abstracted somewhere else. -var endTransitionEvents = [ - 'webkitTransitionEnd', - 'otransitionend', - 'oTransitionEnd', - 'msTransitionEnd', - 'transitionend' -].join(' '); - -BlazeComponent.extendComponent({ - template: function() { - return 'boardComponent'; - }, - - openNewListForm: function() { - this.componentChildren('addListForm')[0].open(); - }, - - showNewCardForms: function(value) { - _.each(this.componentChildren('list'), function(listComponent) { - listComponent.showNewCardForm(value); - }); - }, - - scrollLeft: function(position) { - position = position || 0; - var $container = $(this.find('.js-lists')); - var containerWidth = $container.width(); - var currentScrollPosition = $container.scrollLeft(); - if (position < currentScrollPosition) { - $container.animate({ - scrollLeft: position - }); - } else if (position > currentScrollPosition + containerWidth) { - $container.animate({ - scrollLeft: Math.max(0, position - containerWidth) - }); - } - }, - - currentCardIsInThisList: function() { - var currentCard = Cards.findOne(Session.get('currentCard')); - var listId = this.currentData()._id; - return currentCard && currentCard.listId === listId; - }, - - onRendered: function() { - var self = this; - - self.scrollLeft(); - - var lists = this.find('.js-lists'); - - // We want to animate the card details window closing. We rely on CSS - // transition for the actual animation. - lists._uihooks = { - removeElement: function(node) { - var removeNode = _.once(function() { - node.parentNode.removeChild(node); - }); - if ($(node).hasClass('js-card-detail')) { - $(node).css({ - flex: '0 0 0', - padding: 0 - }); - $(lists).one(endTransitionEvents, removeNode); - } else { - removeNode(); - } - } - }; - - if (! Meteor.userId() || ! Meteor.user().isBoardMember()) - return; - - self.$(lists).sortable({ - tolerance: 'pointer', - appendTo: '.js-lists', - helper: 'clone', - items: '.js-list:not(.js-list-composer)', - placeholder: 'list placeholder', - start: function(event, ui) { - $('.list.placeholder').height(ui.item.height()); - Popup.close(); - }, - stop: function() { - self.$('.js-lists').find('.js-list:not(.js-list-composer)').each( - function(i, list) { - var data = Blaze.getData(list); - Lists.update(data._id, { - $set: { - sort: i - } - }); - } - ); - } - }); - - // If there is no data in the board (ie, no lists) we autofocus the list - // creation form by clicking on the corresponding element. - if (self.data().lists().count() === 0) { - this.openNewListForm(); - } - }, - - sidebarSize: function() { - var sidebar = this.componentChildren('sidebar')[0]; - if (sidebar && sidebar.isOpen()) - return 'next-sidebar'; - } -}).register('boardComponent'); - -BlazeComponent.extendComponent({ - template: function() { - return 'addListForm'; - }, - - // Proxy - open: function() { - this.componentChildren('inlinedForm')[0].open(); - }, - - events: function() { - return [{ - submit: function(evt) { - evt.preventDefault(); - var title = this.find('.list-name-input'); - if ($.trim(title.value)) { - Lists.insert({ - title: title.value, - boardId: Session.get('currentBoard'), - sort: $('.list').length - }); - - title.value = ''; - } - } - }]; - } -}).register('addListForm'); diff --git a/client/components/boards/body.styl b/client/components/boards/body.styl deleted file mode 100644 index de4963ab..00000000 --- a/client/components/boards/body.styl +++ /dev/null @@ -1,34 +0,0 @@ -@import 'nib' - -.board-wrapper - left: 0 - top: 0 - bottom: 0 - right: 0 - position: absolute - overflow: hidden - - .board-canvas - position: absolute - left: 0 - right: 0 - top: 0 - bottom: 0 - transition: margin .1s - - &.next-sidebar - margin-right: 248px - -.lists - align-items: flex-start - display: flex - flex-direction: row - margin-bottom: 10px - overflow-x: auto - overflow-y: hidden - padding-bottom: 10px - position: absolute - top: 0 - right: 0 - bottom: 0 - left: 0 diff --git a/client/components/boards/colors.styl b/client/components/boards/colors.styl index a3f74ab8..2b60dde3 100644 --- a/client/components/boards/colors.styl +++ b/client/components/boards/colors.styl @@ -5,16 +5,29 @@ setBoardColor(color) &#header, &.sk-spinner div, .board-backgrounds-list &.background-box, - &.pop-over .pop-over-list li a:hover, .board-list & a background-color: color & .minicard.is-selected .minicard-details border-left: 3px solid color + &.pop-over .pop-over-list li a:hover, button[type=submit].primary, input[type=submit].primary background-color: darken(color, 20%) + &#header #header-quick-access ul li.current + border-bottom: 2px solid lighten(color, 10%) + + &#header #header-main-bar .board-header-btn.emphasis + background: complement(color) + + &:hover, + .board-header-btn-close + background: darken(complement(color), 10%) + + &:hover .board-header-btn-close + background: darken(complement(color), 20%) + .board-color-nephritis setBoardColor(#27AE60) diff --git a/client/components/boards/events.js b/client/components/boards/events.js deleted file mode 100644 index 6f9d7fc6..00000000 --- a/client/components/boards/events.js +++ /dev/null @@ -1,96 +0,0 @@ -var toggleBoardStar = function(boardId) { - var queryType = Meteor.user().hasStarred(boardId) ? '$pull' : '$addToSet'; - var query = {}; - query[queryType] = { - 'profile.starredBoards': boardId - }; - Meteor.users.update(Meteor.userId(), query); -}; - -Template.boards.events({ - 'click .js-star-board': function(evt) { - toggleBoardStar(this._id); - evt.preventDefault(); - } -}); - -Template.headerBoard.events({ - 'click .js-star-board': function() { - toggleBoardStar(this._id); - }, - 'click .js-open-board-menu': Popup.open('boardMenu'), - 'click #permission-level:not(.no-edit)': Popup.open('boardChangePermission'), - 'click .js-filter-cards-indicator': function(evt) { - Session.set('currentWidget', 'filter'); - evt.preventDefault(); - }, - 'click .js-filter-card-clear': function(evt) { - Filter.reset(); - evt.stopPropagation(); - } -}); - -Template.boardMenuPopup.events({ - 'click .js-rename-board': Popup.open('boardChangeTitle'), - 'click .js-change-board-color': Popup.open('boardChangeColor') -}); - -Template.createBoardPopup.events({ - 'submit #CreateBoardForm': function(evt, t) { - var title = t.$('#boardNewTitle'); - - // trim value title - if ($.trim(title.val())) { - // İnsert Board title - var boardId = Boards.insert({ - title: title.val(), - permission: 'public' - }); - - // Go to Board _id - Utils.goBoardId(boardId); - } - evt.preventDefault(); - } -}); - -Template.boardChangeTitlePopup.events({ - 'submit #ChangeBoardTitleForm': function(evt, t) { - var title = t.$('.js-board-name').val().trim(); - if (title) { - Boards.update(this._id, { - $set: { - title: title - } - }); - Popup.close(); - } - evt.preventDefault(); - } -}); - -Template.boardChangePermissionPopup.events({ - 'click .js-select': function(evt) { - var $this = $(evt.currentTarget); - var permission = $this.attr('name'); - - Boards.update(this._id, { - $set: { - permission: permission - } - }); - Popup.close(); - } -}); - -Template.boardChangeColorPopup.events({ - 'click .js-select-background': function(evt) { - var currentBoardId = Session.get('currentBoard'); - Boards.update(currentBoardId, { - $set: { - color: this.toString() - } - }); - evt.preventDefault(); - } -}); diff --git a/client/components/boards/header.jade b/client/components/boards/header.jade deleted file mode 100644 index 189cdac4..00000000 --- a/client/components/boards/header.jade +++ /dev/null @@ -1,87 +0,0 @@ -template(name="headerBoard") - h1.header-board-menu.js-open-board-menu - = title - span.fa.fa-angle-down - - .board-header-btns.left - unless isSandstorm - a.board-header-btn.js-star-board(class="{{#if isStarred}}board-header-starred{{/if}}" - title="{{# if isStarred }}{{_ 'click-to-unstar'}}{{ else }}{{_ 'click-to-star'}}{{/ if }} {{_ 'starred-boards-description'}}") - span.board-header-btn-icon.icon-sm.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") - //- XXX To implement - span.board-header-btn-text Starred - //- - XXX Normally we would disable this field for sandstorm, but we keep it - until sandstorm implements sharing capabilities - - a.board-header-btn.perms-btn.js-change-vis(class="{{#unless currentUser.isBoardAdmin}}no-edit{{/ unless}}" id="permission-level") - span.board-header-btn-icon.icon-sm.fa(class="{{#if isPublic}}fa-globe{{else}}fa-lock{{/if}}") - span.board-header-btn-text {{_ permission}} - - a.board-header-btn.js-search - span.board-header-btn-icon.icon-sm.fa.fa-tag - span.board-header-btn-text Labels - - //- XXX Clicking here should open a search field - a.board-header-btn.js-search - span.board-header-btn-icon.icon-sm.fa.fa-search - span.board-header-btn-text {{_ 'search'}} - - //- +boardMembersHeader - -template(name="boardMembersHeader") - .board-header-members - each currentBoard.members - +userAvatar(userId=userId draggable=true showBadges=true) - unless isSandstorm - if currentUser.isBoardAdmin - a.member.add-board-member.js-open-manage-board-members - i.fa.fa-plus - -template(name="boardMenuPopup") - ul.pop-over-list - li: a.js-rename-board {{_ 'rename-board'}} - li: a.js-change-board-color Change color - li: a Copy this board - li: a Rules - -template(name="boardChangeTitlePopup") - form#ChangeBoardTitleForm - label {{_ 'name'}} - input.js-board-name(type="text" value="{{ title }}" autofocus) - input.primary.wide.js-rename-board(type="submit" value="{{_ 'rename'}}") - -template(name="boardChangePermissionPopup") - ul.pop-over-list - li - a.js-select.light-hover(name="private") - span.icon-sm.fa.fa-lock.vis-icon - | {{_ 'private'}} - if check 'private' - span.icon-sm.fa.fa-check - span.sub-name {{_ 'private-desc'}} - li - a.js-select.light-hover(name="public") - span.icon-sm.fa.fa-globe.vis-icon - | {{_ 'public'}} - if check 'public' - span.icon-sm.fa.fa-check - span.sub-name {{_ 'public-desc'}} - -template(name="boardChangeColorPopup") - .board-backgrounds-list.clearfix - each backgroundColors - .board-background-select.js-select-background - span.background-box(class="board-color-{{this}}") - if isSelected - i.fa.fa-check - -template(name="createBoardPopup") - .content.clearfix - form#CreateBoardForm - label(for="boardNewTitle") {{_ 'title'}} - input#boardNewTitle.non-empty(type="text" name="name" placeholder="{{_ 'bucket-example'}}" autofocus) - p.quiet - span.icon-sm.fa.fa-globe - | {{{_ 'board-public-info'}}} - input.primary.wide(type="submit" value="{{_ 'create'}}") diff --git a/client/components/boards/header.js b/client/components/boards/header.js deleted file mode 100644 index 7d02df48..00000000 --- a/client/components/boards/header.js +++ /dev/null @@ -1,7 +0,0 @@ -Template.headerBoard.helpers({ - isStarred: function() { - var boardId = Session.get('currentBoard'); - var user = Meteor.user(); - return boardId && user && user.hasStarred(boardId); - } -}); diff --git a/client/components/boards/header.styl b/client/components/boards/header.styl deleted file mode 100644 index 44c38a4b..00000000 --- a/client/components/boards/header.styl +++ /dev/null @@ -1,137 +0,0 @@ -@import 'nib' - -.board-header { - height: auto; - overflow: hidden; - padding: 10px 30px 10px 8px; - position: relative; - transition: padding .15s ease-in; -} - -.board-header-btns { - position: relative; - display: block; -} - -.board-header-btn { - border-radius: 3px; - color: #f6f6f6; - cursor: default; - float: left; - font-size: 12px; - height: 30px; - line-height: 32px; - margin: 2px 4px 0 0; - overflow: hidden; - padding-left: 30px; - position: relative; - text-decoration: none; -} - -.board-header-btn:empty { - display: none; -} - -.board-header-btn-without-icon { - padding-left: 8px; -} - -.board-header-btn-icon { - background-clip: content-box; - background-origin: content-box; - color: #f6f6f6 !important; - padding: 6px; - position: absolute; - top: 0; - left: 0; -} - -.board-header-btn-text { - padding-right: 8px; -} - -.board-header-btn:not(.no-edit) .text { - text-decoration: underline; -} - -.board-header-btn:not(.no-edit):hover { - background: rgba(0, 0, 0, .12); - cursor: pointer; -} - -.board-header-btn:hover { - color: #f6f6f6; -} - -.board-header-btn.board-header-btn-enabled { - background-color: rgba(0, 0, 0, .1); - - &:hover { - background-color: rgba(0, 0, 0, .3); - } - - .board-header-btn-icon.icon-star { - color: #e6bf00 !important; - } -} - -.board-header-btn-name { - cursor: default; - font-size: 18px; - font-weight: 700; - line-height: 30px; - padding-left: 4px; - text-decoration: none; - - .board-header-btn-text { - padding-left: 6px; - } -} - -.board-header-btn-name-org-logo { - border-radius: 3px; - height: 30px; - left: 0; - position: absolute; - top: 0; - width: 30px; - - .board-header-btn-text { - padding-left: 32px; - } -} - -.board-header-btn-org-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 400px; -} - -.board-header-btn-filter-indicator { - background: #3d990f; - padding-right: 30px; - color: #fff; - text-shadow: 0; - - &:hover { - background: #43a711 !important; - } - - .board-header-btn-icon-close { - background: #43a711; - border-top-left-radius: 0; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - border-bottom-left-radius: 0; - color: #fff; - padding: 6px; - position: absolute; - right: 0; - top: 0; - - &:hover { - background: #48b512; - } - } -} diff --git a/client/components/boards/helpers.js b/client/components/boards/helpers.js index 05be987d..969f8564 100644 --- a/client/components/boards/helpers.js +++ b/client/components/boards/helpers.js @@ -1,42 +1,3 @@ -Template.boards.helpers({ - boards: function() { - return Boards.find({}, { - sort: ['title'] - }); - }, - - starredBoards: function() { - var cursor = Boards.find({ - _id: { $in: Meteor.user().profile.starredBoards || [] } - }, { - sort: ['title'] - }); - return cursor.count() === 0 ? null : cursor; - }, - - isStarred: function() { - var user = Meteor.user(); - return user && user.hasStarred(this._id); - } -}); - -Template.boardChangePermissionPopup.helpers({ - check: function(perm) { - return this.permission === perm; - } -}); - -Template.boardChangeColorPopup.helpers({ - backgroundColors: function() { - return Boards.simpleSchema()._schema.color.allowedValues; - }, - - isSelected: function() { - var currentBoard = Boards.findOne(Session.get('currentBoard')); - return currentBoard.color === this.toString(); - } -}); - Blaze.registerHelper('currentBoard', function() { var boardId = Session.get('currentBoard'); if (boardId) { diff --git a/client/components/boards/list.jade b/client/components/boards/list.jade deleted file mode 100644 index 3a8fecd2..00000000 --- a/client/components/boards/list.jade +++ /dev/null @@ -1,14 +0,0 @@ -template(name="boards") - if boards - ul.board-list.clearfix - each boards - li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) - a.js-open-board(href="{{ pathFor route='Board' boardId=_id }}") - span.details - span.board-list-item-name= title - i.fa.fa-star-o.js-star-board( - class="{{#if isStarred}}is-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - else - p.quiet {{_ 'no-boards'}} - button.js-add-board {{_ 'add-board'}} diff --git a/client/components/boards/list.styl b/client/components/boards/list.styl deleted file mode 100644 index c068dbb0..00000000 --- a/client/components/boards/list.styl +++ /dev/null @@ -1,85 +0,0 @@ -.board-list - margin: 25px auto - width: 1200px - - li - float: left - width: 25% - box-sizing: border-box - position: relative - - &.starred .fa-star-o - opacity: 1 - - a - background-color: #999 - color: #f6f6f6 - height: 90px - font-size: 16px - line-height: 22px - border-radius: 3px - display: block - font-weight: 700 - min-height: 18px - padding: 8px 12px 8px 12px - margin: 0 16px 16px 0 - position: relative - text-decoration: none - - &.tile - background-size: auto - background-repeat: repeat - - .details - height: 84px - padding-right: 36px - bottom: 0 - left: 0 - overflow: hidden - padding: 9px 12px - position: absolute - right: 0 - top: 0 - - .board-list-item-sub-name - color: rgba(255, 255, 255, .5) - display: block - font-size: 14px - font-weight: 400 - line-height: 22px - - .fa-star-o - bottom: 0 - font-size: 14px - height: 18px - line-height: 18px - opacity: 0 - padding: 9px 9px - position: absolute - right: 0 - top: 0 - transition-duration: .15s - transition-property: color, font-size, background - - .is-star-active - color: #e6bf00 - - li:hover a - color: #f6f6f6 - - .fa-star-o - color: #fff - opacity: .75 - - &:hover - font-size: 18px - opacity: 1 - - &.is-star-active - color: #e6bf00 - opacity: 1 - - &:hover - color: #ffd91a - font-size: 16px - opacity: 1 diff --git a/client/components/cards/details.jade b/client/components/cards/details.jade index b77c3961..4e8fd8c0 100644 --- a/client/components/cards/details.jade +++ b/client/components/cards/details.jade @@ -4,7 +4,7 @@ template(name="cardDetails") .card-detail-cover(style="background-image: url({{ card.cover.url }})") .card-detail-header(class="{{#if currentUser.isBoardMember}}editable{{/if}}") a.js-close-card-detail - i.fa.fa-times + i.fa.fa-times-thin h2.card-detail-title.js-card-title= title p.card-detail-list.js-move-card | {{_ 'in-list'}} @@ -25,7 +25,7 @@ template(name="cardDetails") if currentUser.isBoardMember h3 Description +inlinedForm(classNames="js-card-description") - i.fa.fa-times.js-close-inlined-form + a.fa.fa-times-thin.js-close-inlined-form +editor(autofocus=true) = description button(type="submit") {{_ 'edit'}} diff --git a/client/components/cards/events.js b/client/components/cards/events.js index 9c270e8d..21d628df 100644 --- a/client/components/cards/events.js +++ b/client/components/cards/events.js @@ -1,64 +1,3 @@ -// Template.cards.events({ -// // 'click .js-cancel': function(event, t) { -// // var composer = t.$('.card-composer'); - -// // // Keep the old value in memory to display it again next time -// // var inputCacheKey = "addCard-" + this.listId; -// // var oldValue = composer.find('.js-card-title').val(); -// // InputsCache.set(inputCacheKey, oldValue); - -// // // add composer hide class -// // composer.addClass('hide'); -// // composer.find('.js-card-title').val(''); - -// // // remove hide open link class -// // $('.js-open-card-composer').removeClass('hide'); -// // }, -// 'submit': function(evt, tpl) { -// evt.preventDefault(); -// var textarea = $(evt.currentTarget).find('textarea'); -// var title = textarea.val(); -// var lastCard = tpl.find('.js-minicard:last-child'); -// var sort; -// if (lastCard === null) { -// sort = 0; -// } else { -// sort = Blaze.getData(lastCard).sort + 1; -// } -// // debugger - -// // Clear the form in-memory cache -// // var inputCacheKey = "addCard-" + this.listId; -// // InputsCache.set(inputCacheKey, ''); - -// // title trim if not empty then -// if ($.trim(title)) { -// Cards.insert({ -// title: title, -// listId: Template.currentData().listId, -// boardId: Template.currentData().board._id, -// sort: sort -// }, function(err, _id) { -// // 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 card will disappear instantly. -// // See https://github.com/libreboard/libreboard/issues/80 -// Filter.addException(_id); -// }); - -// // empty and focus. -// textarea.val('').focus(); - -// // focus complete then scroll top -// Utils.Scroll(tpl.find('.js-minicards')).top(1000, true); -// } -// } -// }); - -// Template.cards.events({ -// 'click .member': Popup.open('cardMember') -// }); - Template.cardMemberPopup.events({ 'click .js-remove-member': function() { Cards.update(this.cardId, {$pull: {members: this.userId}}); diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade new file mode 100644 index 00000000..e1176264 --- /dev/null +++ b/client/components/cards/minicard.jade @@ -0,0 +1,27 @@ +template(name="minicard") + .minicard.card.js-minicard( + class="{{#if isSelected}}is-selected{{/if}}") + a.minicard-details.clearfix.show(href=absoluteUrl) + if cover + .minicard-cover.js-card-cover(style="background-image: url({{cover.url}});") + if labels + .minicard-labels + each labels + .minicard-label(class="card-label-{{color}}" title="{{name}}") + .minicard-title= title + if members + .minicard-members.js-minicard-members + each members + +userAvatar(userId=this size="small" cardId="{{../_id}}") + .badges + if comments.count + .badge(title="{{_ 'card-comments-title' comments.count }}") + span.badge-icon.icon-sm.fa.fa-comment-o + .badge-text= comments.count + if description + .badge.badge-state-image-only(title=description) + span.badge-icon.icon-sm.fa.fa-align-left + if attachments.count + .badge + span.badge-icon.icon-sm.fa.fa-paperclip + span.badge-text= attachments.count diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js new file mode 100644 index 00000000..b339580b --- /dev/null +++ b/client/components/cards/minicard.js @@ -0,0 +1,14 @@ +// Template.cards.events({ +// 'click .member': Popup.open('cardMember') +// }); + + +BlazeComponent.extendComponent({ + template: function() { + return 'minicard'; + }, + + isSelected: function() { + return Session.equals('currentCard', this.currentData()._id); + } +}).register('minicard'); diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 1084a4a6..c572863d 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -229,26 +229,9 @@ textarea padding-top: 5px padding-bottom: 5px - i.fa.fa-times - font-size: 20px - - .option - border-color: transparent - border-radius: 3px - color: #8c8c8c - display: block - float: right - height: 30px - line-height: 30px - padding: 0 8px - margin: 0 2px - - &:hover - background-color: #dbdbdb - color: #4d4d4d - - &:active - background-color: #ccc + .fa-times-thin + font-size: 26px + margin: 3px 4px .button-link background: #fff diff --git a/client/components/forms/inlinedform.js b/client/components/forms/inlinedform.js index b84952d2..f2774084 100644 --- a/client/components/forms/inlinedform.js +++ b/client/components/forms/inlinedform.js @@ -30,11 +30,12 @@ BlazeComponent.extendComponent({ this.isOpen = new ReactiveVar(false); }, + onDestroyed: function() { + currentlyOpenedForm.set(null); + }, + open: function() { // Close currently opened form, if any - // if (currentlyOpenedForm.get() !== null) { - // currentlyOpenedForm.get().close(); - // } EscapeActions.executeLowerThan('inlinedForm'); this.isOpen.set(true); currentlyOpenedForm.set(this); diff --git a/client/components/lists/body.jade b/client/components/lists/body.jade index 9d4a903d..3e769206 100644 --- a/client/components/lists/body.jade +++ b/client/components/lists/body.jade @@ -5,32 +5,7 @@ template(name="listBody") +inlinedForm(autoclose=false position="top") +addCardForm(listId=_id position="top") each cards - .minicard.card.js-minicard( - class="{{#if isSelected}}is-selected{{/if}}") - a.minicard-details.clearfix.show(href=absoluteUrl) - if cover - .minicard-cover.js-card-cover(style="background-image: url({{cover.url}});") - if labels - .minicard-labels - each labels - .minicard-label(class="card-label-{{color}}" title="{{name}}") - .minicard-title= title - if members - .minicard-members.js-minicard-members - each members - +userAvatar(userId=this size="small" cardId="{{../_id}}") - .badges - if comments.count - .badge(title="{{_ 'card-comments-title' comments.count }}") - span.badge-icon.icon-sm.fa.fa-comment-o - .badge-text= comments.count - if description - .badge.badge-state-image-only(title=description) - span.badge-icon.icon-sm.fa.fa-align-left - if attachments.count - .badge - span.badge-icon.icon-sm.fa.fa-paperclip - span.badge-text= attachments.count + +minicard(this) if currentUser.isBoardMember +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") @@ -49,4 +24,4 @@ template(name="addCardForm") .minicard-members.js-minicard-composer-members .add-controls.clearfix button.primary.confirm(type="submit") {{_ 'add'}} - a.fa.fa-times.dark-hover.cancel.js-close-inlined-form + a.fa.fa-times-thin.js-close-inlined-form diff --git a/client/components/lists/body.js b/client/components/lists/body.js index d8238c9a..8400af96 100644 --- a/client/components/lists/body.js +++ b/client/components/lists/body.js @@ -7,10 +7,6 @@ BlazeComponent.extendComponent({ return [Mixins.PerfectScrollbar]; }, - isSelected: function() { - return Session.equals('currentCard', this.currentData()._id); - }, - openForm: function(options) { options = options || {}; options.position = options.position || 'top'; @@ -37,11 +33,6 @@ BlazeComponent.extendComponent({ sortIndex = Utils.getSortIndex(this.find('.js-minicard:last'), null); } - // Clear the form in-memory cache - // var inputCacheKey = "addCard-" + this.listId; - // InputsCache.set(inputCacheKey, ''); - - // title trim if not empty then if ($.trim(title)) { Cards.insert({ title: title, @@ -49,16 +40,18 @@ BlazeComponent.extendComponent({ boardId: this.data().board()._id, sort: sortIndex }, function(err, _id) { - // 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 card will disappear instantly. + // 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 card will disappear instantly. // See https://github.com/libreboard/libreboard/issues/80 Filter.addException(_id); }); // We keep the form opened, empty it, and scroll to it. textarea.val('').focus(); - Utils.Scroll(this.find('.js-minicards')).top(1000, true); + if (position === 'bottom') { + this.scrollToBottom(); + } } }, @@ -67,9 +60,9 @@ BlazeComponent.extendComponent({ }, scrollToBottom: function() { - var $container = $(this.firstNode()); - $container.animate({ - scrollTop: $container.height() + var container = this.firstNode(); + $(container).animate({ + scrollTop: container.scrollHeight }); }, @@ -94,7 +87,12 @@ BlazeComponent.extendComponent({ // Pressing Enter should submit the card if (evt.keyCode === 13) { evt.preventDefault(); - $(evt.currentTarget).parents('form:first').submit(); + var $form = $(evt.currentTarget).parents('form:first'); + // 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 + // work. + $form.find('button[type=submit]').click(); // Pressing Tab should open the form of the next column, and Maj+Tab go // in the reverse order @@ -102,7 +100,7 @@ BlazeComponent.extendComponent({ evt.preventDefault(); var isReverse = evt.shiftKey; var list = $('#js-list-' + this.data().listId); - var listSelector = '.js-list:not(.js-add-list)'; + var listSelector = '.js-list:not(.js-list-composer)'; var nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); // If there isn't no next list, loop back to the beginning. if (! nextList) { diff --git a/client/components/lists/main.js b/client/components/lists/main.js index 3464865a..8f86183a 100644 --- a/client/components/lists/main.js +++ b/client/components/lists/main.js @@ -24,58 +24,69 @@ BlazeComponent.extendComponent({ // powerful enough for our use casesShould we “simply” write the drag&drop // code ourselves? onRendered: function() { - if (Meteor.user().isBoardMember()) { - var boardComponent = this.componentParent(); - var itemsSelector = '.js-minicard:not(.placeholder, .hide, .js-composer)'; - var $cards = this.$('.js-minicards'); - $cards.sortable({ - connectWith: '.js-minicards', - tolerance: 'pointer', - appendTo: '.js-lists', - helper: 'clone', - items: itemsSelector, - placeholder: 'minicard placeholder', - start: function(event, ui) { - $('.minicard.placeholder').height(ui.item.height()); - Popup.close(); - boardComponent.showNewCardForms(false); - }, - stop: function(event, ui) { - // To attribute the new index number, we need to get the dom element - // of the previous and the following card -- if any. - var cardDomElement = ui.item.get(0); - var prevCardDomElement = ui.item.prev('.js-minicard').get(0); - var nextCardDomElement = ui.item.next('.js-minicard').get(0); - var sort = Utils.getSortIndex(prevCardDomElement, nextCardDomElement); - var cardId = Blaze.getData(cardDomElement)._id; - var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; - Cards.update(cardId, { - $set: { - listId: listId, - sort: sort - } - }); - boardComponent.showNewCardForms(true); - } - }).disableSelection(); + var self = this; + if (! Meteor.userId() || ! Meteor.user().isBoardMember()) + return; - $(document).on('mouseover', function() { + var boardComponent = self.componentParent(); + var itemsSelector = '.js-minicard:not(.placeholder, .hide, .js-composer)'; + var $cards = self.$('.js-minicards'); + $cards.sortable({ + connectWith: '.js-minicards', + tolerance: 'pointer', + appendTo: '.js-lists', + helper: 'clone', + items: itemsSelector, + placeholder: 'minicard placeholder', + start: function(event, ui) { + $('.minicard.placeholder').height(ui.item.height()); + Popup.close(); + boardComponent.showNewCardForms(false); + }, + stop: function(event, ui) { + // To attribute the new index number, we need to get the dom element + // of the previous and the following card -- if any. + var cardDomElement = ui.item.get(0); + var prevCardDomElement = ui.item.prev('.js-minicard').get(0); + var nextCardDomElement = ui.item.next('.js-minicard').get(0); + var sort = Utils.getSortIndex(prevCardDomElement, nextCardDomElement); + var cardId = Blaze.getData(cardDomElement)._id; + var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; + Cards.update(cardId, { + $set: { + listId: listId, + sort: sort + } + }); + boardComponent.showNewCardForms(true); + } + }); + + // We want to re-run this function any time a card is added. + self.autorun(function() { + var currentBoardId = Tracker.nonreactive(function() { + return Session.get('currentBoard'); + }); + Cards.find({ boardId: currentBoardId }).fetch(); + Tracker.afterFlush(function() { $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; if (ui.draggable.hasClass('js-member')) { var memberId = Blaze.getData(ui.draggable.get(0)).userId; - Cards.update(cardId, {$addToSet: {members: memberId}}); + addToSet = { members: memberId }; } else { var labelId = Blaze.getData(ui.draggable.get(0))._id; - Cards.update(cardId, {$addToSet: {labelIds: labelId}}); + addToSet = { labelIds: labelId }; } + Cards.update(cardId, { $addToSet: addToSet }); } }); }); - } + }); } }).register('list'); diff --git a/client/components/main/header.styl b/client/components/main/header.styl index 1177d930..248e2851 100644 --- a/client/components/main/header.styl +++ b/client/components/main/header.styl @@ -1,7 +1,5 @@ @import 'nib' -global-reset() - #header color: white transition: background-color 0.4s @@ -9,15 +7,16 @@ global-reset() #header-quick-access background-color: rgba(0, 0, 0, 0.2) - padding: 4px 10px - height: 16px + padding: 0px 10px + height: 28px font-size: 12px display: flex - ul li, #header-user-bar + #header-user-bar + ul li color: darken(white, 17%) - a + a, .fa color: inherit text-decoration: none @@ -27,240 +26,101 @@ global-reset() ul flex: 1 transition: opacity 0.2s - margin-left: 5px + margin: 4px 0 0 5px li display: block float: left width: auto color: darken(white, 15%) - padding: 0 4px 1px 4px - - &.separator - padding: 0 2px 1px 2px + padding: 2px 5px 0 &.current - font-style: italic + color: darken(white, 5%) &:first-child .fa-home margin-right: 5px - #header-main-bar - height: 30px - padding: 8px - - h1 - font-size: 19px - line-height: 1.7em - margin: 0 20px 0 10px - float: left - - &.header-board-menu - cursor: pointer - - .fa-angle-down - font-size: 0.8em - // line-height: 1.1em + a.js-create-board margin-left: 5px - .board-header-starred .fa - color: yellow - - .board-header-members - float: right + #header-user-bar + margin: 2px 0 .member - display: block - width: 32px + width: 24px height: @width + float: left + margin: 0 + margin-top: 1px - .add-board-member - color: white - display: flex - align-items: center - justify-content: center - border: 1px solid white - height: 32px - 2px - width: @height - - i.fa-plus - margin-top: 2px - - .header-btn:last-child - margin-right: 0 - - - -// #header { -// background: #138871; -// height: 30px; -// overflow: hidden; -// padding: 5px; -// position: relative; -// z-index: 10; -// } - -// .header-logo { -// bottom: 0; -// display: block; -// height: 25px; -// left: 50%; -// position: absolute; -// top: 8px; -// width: 80px; -// margin-left: - @width/2; -// text-align: center; -// z-index: 2; -// opacity: .5; -// transition: opacity ease-in 85ms; -// color: white; -// font-size: 22px; -// text-decoration: none; -// background-image: url('/logos/white_logo.png'); - -// &:hover { -// opacity: .8; -// color: white; -// } -// } - -// .header-btn.header-btn-feedback { -// background: rgba(255, 255, 255, .1); -// background: linear-gradient(to bottom, rgba(255, 255, 255, .1) 0, rgba(255, 255, 255, .05) 100%); -// padding-left: 22px; -// margin-right: 16px; - -// .header-btn-icon { -// top: 1px; -// } -// } - -.header-btn { - border-radius: 3px; - user-select: none; - background: rgba(255, 255, 255, .3); - background: linear-gradient(to bottom, rgba(255, 255, 255, .3) 0, rgba(255, 255, 255, .2) 100%); - color: #f3f3f3; - display: block; - float: left; - font-weight: 700; - height: 30px; - line-height: 30px; - padding: 0 10px; - position: relative; - margin-right: 8px; - min-width: 30px; - text-decoration: none; - cursor: pointer; - - .header-btn-icon { - font-size: 16px; - line-height: 28px; - position: absolute; - top: 0; - left: 0; - } - - &.new-notifications { - background: #ba1212; - - &:hover { - background: #d11515; - } - } - - &.header-member .member { - margin: 0; - border-top-left-radius: 3px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 3px; - - &:hover .member-avatar { - opacity: 1; - } - } - - &:hover { - background: rgba(255, 255, 255, .4); - background: linear-gradient(to bottom, rgba(255, 255, 255, .4) 0, rgba(255, 255, 255, .3) 100%); - color: #fff; - - .header-btn-count { - background: #d11515; - } - } - - &:active { - background: rgba(255, 255, 255, .4); - background: linear-gradient(to bottom, rgba(255, 255, 255, .4) 0, rgba(255, 255, 255, .3) 100%); - } - - &.upgrade { - margin-right: 16px; + .header-user-bar-name + margin: 4px 8px 0 0 + float: left - .icon-sm { - padding: 6px 2px 6px 4px; - } - } + #header-main-bar + height: 28px * 1.618034 - 6px + padding: 7px 10px 0 - &.upgrade, - &.header-boards { - padding-left: 4px; - } + h1 + font-size: 20px + line-height: 1.7em + padding: 0 10px + margin: 0 + margin-right: 10px + float: left + border-radius: 3px - &.header-boards { - padding-right: 4px; - } + &.is-clickable + cursor: pointer - &.header-login, - &.header-signup { - padding: 0 12px; - } + .board-header-btns + display: block + margin-top: 3px + width: auto - &.header-signup { - background: #48b512; - background: linear-gradient(to bottom, #48b512 0, #3d990f 100%); + // XXX Use a flexbox instead of floats? + &.left + float: left - &:hover { - background: #3d990f; - background: linear-gradient(to bottom, #3d990f 0, #327d0c 100%); - } + &.right + float: right - &:active { - background: #327d0c; - } - } + .board-header-btn + border-radius: 3px + color: darken(white, 5%) + padding: 0 + height: 28px + font-size: 13px + float: left + overflow: hidden + line-height: @height + margin: 0 2px - &.header-go-to-boards { - padding: 0 8px 0 38px; - } + i.fa + float: left + display: block + line-height: 28px + color: darken(white, 5%) + margin: 0 10px - &.header-go-to-boards .member { - border-top-left-radius: 3px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-bottom-left-radius: 3px; - position: absolute; - left: 0; - } -} + + span + margin-right: 10px -// .header-btn-text { -// padding: 0 8px; -// } + .board-header-btn-close + float: right -// .header-notification-list ul { -// margin-top: 8px; -// } + i.fa + margin: 0 6px -// .header-notification-list .action-comment { -// max-height: 250px; -// overflow-y: auto; -// } + .board-header-btn, + h1.is-clickable + &.is-hovered, + &:hover + background: rgba(0, 0, 0, .15) -// .header-user { -// position: absolute; -// top: 5px; -// right: 0; -// } + .separator + margin: 2px 4px + border-left: 1px solid rgba(255, 255, 255, .3) + height: 24px + float: left diff --git a/client/components/main/popup.js b/client/components/main/popup.js index 8abe1697..48847525 100644 --- a/client/components/main/popup.js +++ b/client/components/main/popup.js @@ -1,11 +1,21 @@ // XXX This event list must be abstracted somewhere else. -var endTransitionEvents = [ - 'webkitTransitionEnd', - 'otransitionend', - 'oTransitionEnd', - 'msTransitionEnd', - 'transitionend' -].join(' '); +function whichTransitionEvent() { + var t; + var el = document.createElement('fakeelement'); + var transitions = { + transition:'transitionend', + OTransition:'oTransitionEnd', + MozTransition:'transitionend', + WebkitTransition:'webkitTransitionEnd' + }; + + for (t in transitions) { + if (el.style[t] !== undefined) { + return transitions[t]; + } + } +} +var transitionEvent = whichTransitionEvent(); Popup.template.events({ click: function(evt) { @@ -32,7 +42,7 @@ Popup.template.onRendered(function() { container._uihooks = { removeElement: function(node) { $(node).addClass('no-height'); - $(container).one(endTransitionEvents, function() { + $(container).one(transitionEvent, function() { node.parentNode.removeChild(node); }); } diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl index 9faad876..141f4261 100644 --- a/client/components/main/popup.styl +++ b/client/components/main/popup.styl @@ -91,6 +91,10 @@ top: 0 right: 0 + + &.no-title .header + background: none + .content-wrapper width: 100% @@ -527,78 +531,3 @@ &.limited li.exceeds-limit display: none - -.pop-over-emoji-list li > a - padding: 2px 4px - - .emoji - margin: 0 6px - -.pop-over-card-list li > a - padding: 2px 4px - -.login-signup-popover - padding: 15px - - .form-tabs - display: none - - h1 - margin-bottom: 15px - - p - margin: 8px 0 - - .form-parts-container - position: relative - - .active-box - position: absolute - top: 0 - background: #e2e2e2 - border: 1px solid #c9c9c9 - border-radius: 3px - z-index: 1 - height: 100% - width: 49% - transition-property: all - transition-duration: .4s - opacity: 1 - - &.start - opacity: 0 - left: 25% - - .signup-form, - .login-form - position: relative - box-sizing: border-box - padding: 20px - width: 50% - z-index: 2 - opacity: .3 - transition-property: opacity - transition-duration: .2s - - .active - opacity: 1 - - - .js-signup-form-pos - left: 0 - - .login-form - position: absolute - top: 0 - - .login-form .icon-google - position: absolute - left: 5px - top: 3px - - .login-form .button.google - padding-left: 40px - margin: 0 0 15px 0 - - .js-login-form-pos - left: 50% diff --git a/client/components/main/popup.tpl.jade b/client/components/main/popup.tpl.jade index fa54c713..dade8449 100644 --- a/client/components/main/popup.tpl.jade +++ b/client/components/main/popup.tpl.jade @@ -1,13 +1,14 @@ .pop-over.clearfix( class="{{#unless title}}miniprofile{{/unless}}" class=currentBoard.colorClass + class="{{#unless title}}no-title{{/unless}}" style="display:block; left:{{offset.left}}px; top:{{offset.top}}px;") - .header.clearfix + .header a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}") i.fa.fa-chevron-left span.header-title= title a.close-btn.js-close-pop-over - i.fa.fa-times + i.fa.fa-times-thin .content-wrapper //- We display the all stack of popup content next to each other and move diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index ce7925ea..b737e9de 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -68,23 +68,39 @@ BlazeComponent.extendComponent({ return this.getView() + 'Sidebar'; }, + // Board members can assign people or labels by drag-dropping elements from + // the sidebar to the cards on the board. In order to re-initialize the + // jquery-ui 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. onRendered: function() { var self = this; if (! Meteor.userId() || ! Meteor.user().isBoardMember()) return; - $(document).on('mouseover', function() { - self.$('.js-member,.js-label').draggable({ - appendTo: 'body', - helper: 'clone', - revert: 'invalid', - revertDuration: 150, - snap: false, - snapMode: 'both', - start: function() { - Popup.close(); + self.autorun(function() { + var currentBoardId = Tracker.nonreactive(function() { + return Session.get('currentBoard'); + }); + Boards.findOne(currentBoardId, { + fields: { + members: 1, + labels: 1 } }); + Tracker.afterFlush(function() { + self.$('.js-member,.js-label').draggable({ + appendTo: 'body', + helper: 'clone', + revert: 'invalid', + revertDuration: 150, + snap: false, + snapMode: 'both', + start: function() { + EscapeActions.executeLowerThan('popup'); + } + }); + }); }); }, diff --git a/client/components/users/headerButtons.jade b/client/components/users/headerButtons.jade index 74c24ad5..0a2f64cf 100644 --- a/client/components/users/headerButtons.jade +++ b/client/components/users/headerButtons.jade @@ -1,16 +1,12 @@ template(name="headerUserBar") - #header-user-bar - if currentUser - a.js-open-header-member-menu - if currentUser.profile.name - = currentUser.profile.name - else - = currentUser.username - i.fa.fa-chevron-down - else - a(href="{{pathFor route='signUp'}}") Sign in - span.separator - - a(href="{{pathFor route='signIn'}}") Log in + a#header-user-bar.js-open-header-member-menu + .header-user-bar-name + i.fa.fa-chevron-down + if currentUser.profile.name + = currentUser.profile.name + else + = currentUser.username + +userAvatar(user=currentUser) template(name="memberHeader") a.header-member.js-open-header-member-menu -- cgit v1.2.3-1-g7c22