diff options
author | Maxime Quandalle <maxime@quandalle.com> | 2015-05-27 17:17:00 +0200 |
---|---|---|
committer | Maxime Quandalle <maxime@quandalle.com> | 2015-05-27 17:23:25 +0200 |
commit | dcc64f44f9f81d32c8071c6bdac86546eaeb57a0 (patch) | |
tree | b8977727227a3ddbb2874ea3f86f1e26e03c8835 /client/components | |
parent | 42f6dc686f313ba294e3cbcfb0ebde50678580fe (diff) | |
download | wekan-dcc64f44f9f81d32c8071c6bdac86546eaeb57a0.tar.gz wekan-dcc64f44f9f81d32c8071c6bdac86546eaeb57a0.tar.bz2 wekan-dcc64f44f9f81d32c8071c6bdac86546eaeb57a0.zip |
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.
Diffstat (limited to 'client/components')
29 files changed, 555 insertions, 856 deletions
diff --git a/client/components/boards/body.jade b/client/components/boards/boardBody.jade index b157b742..672a3860 100644 --- a/client/components/boards/body.jade +++ b/client/components/boards/boardBody.jade @@ -27,7 +27,7 @@ template(name="addListForm") 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 + a.fa.fa-times-thin.js-close-inlined-form else .js-open-inlined-form i.fa.fa-plus diff --git a/client/components/boards/body.js b/client/components/boards/boardBody.js index cf32f764..cf32f764 100644 --- a/client/components/boards/body.js +++ b/client/components/boards/boardBody.js diff --git a/client/components/boards/body.styl b/client/components/boards/boardBody.styl index de4963ab..de4963ab 100644 --- a/client/components/boards/body.styl +++ b/client/components/boards/boardBody.styl 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/list.jade b/client/components/boards/boardList.jade index 3a8fecd2..e133f73c 100644 --- a/client/components/boards/list.jade +++ b/client/components/boards/boardList.jade @@ -1,4 +1,11 @@ +//- + 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 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/list.styl b/client/components/boards/boardList.styl index c068dbb0..c068dbb0 100644 --- a/client/components/boards/list.styl +++ b/client/components/boards/boardList.styl 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/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 |