From b5dabfe88695a8f8211b29fea0dc16131c9a1829 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Fri, 28 Aug 2015 02:21:42 +0200 Subject: More explicit file names --- client/components/boards/boardColors.styl | 66 ++++++++++++++ client/components/boards/boardList.jade | 16 ---- client/components/boards/boardList.js | 30 ------- client/components/boards/boardList.styl | 120 ------------------------- client/components/boards/boardsList.jade | 16 ++++ client/components/boards/boardsList.js | 30 +++++++ client/components/boards/boardsList.styl | 120 +++++++++++++++++++++++++ client/components/boards/colors.styl | 66 -------------- client/components/boards/helpers.js | 6 -- client/components/cards/cardDetails.jade | 120 +++++++++++++++++++++++++ client/components/cards/cardDetails.js | 123 +++++++++++++++++++++++++ client/components/cards/cardDetails.styl | 98 ++++++++++++++++++++ client/components/cards/details.jade | 120 ------------------------- client/components/cards/details.js | 123 ------------------------- client/components/cards/details.styl | 98 -------------------- client/components/lists/body.jade | 30 ------- client/components/lists/body.js | 143 ------------------------------ client/components/lists/header.jade | 16 ---- client/components/lists/header.js | 25 ------ client/components/lists/list.jade | 4 + client/components/lists/list.js | 126 ++++++++++++++++++++++++++ client/components/lists/list.styl | 110 +++++++++++++++++++++++ client/components/lists/listBody.jade | 30 +++++++ client/components/lists/listBody.js | 143 ++++++++++++++++++++++++++++++ client/components/lists/listHeader.jade | 16 ++++ client/components/lists/listHeader.js | 25 ++++++ client/components/lists/listMenu.jade | 29 ++++++ client/components/lists/listMenu.js | 52 +++++++++++ client/components/lists/main.jade | 4 - client/components/lists/main.js | 126 -------------------------- client/components/lists/main.styl | 110 ----------------------- client/components/lists/menu.jade | 29 ------ client/components/lists/menu.js | 52 ----------- client/config/blazeHelpers.js | 13 +++ client/config/helpers.js | 6 -- 35 files changed, 1121 insertions(+), 1120 deletions(-) create mode 100644 client/components/boards/boardColors.styl delete mode 100644 client/components/boards/boardList.jade delete mode 100644 client/components/boards/boardList.js delete mode 100644 client/components/boards/boardList.styl create mode 100644 client/components/boards/boardsList.jade create mode 100644 client/components/boards/boardsList.js create mode 100644 client/components/boards/boardsList.styl delete mode 100644 client/components/boards/colors.styl delete mode 100644 client/components/boards/helpers.js create mode 100644 client/components/cards/cardDetails.jade create mode 100644 client/components/cards/cardDetails.js create mode 100644 client/components/cards/cardDetails.styl delete mode 100644 client/components/cards/details.jade delete mode 100644 client/components/cards/details.js delete mode 100644 client/components/cards/details.styl delete mode 100644 client/components/lists/body.jade delete mode 100644 client/components/lists/body.js delete mode 100644 client/components/lists/header.jade delete mode 100644 client/components/lists/header.js create mode 100644 client/components/lists/list.jade create mode 100644 client/components/lists/list.js create mode 100644 client/components/lists/list.styl create mode 100644 client/components/lists/listBody.jade create mode 100644 client/components/lists/listBody.js create mode 100644 client/components/lists/listHeader.jade create mode 100644 client/components/lists/listHeader.js create mode 100644 client/components/lists/listMenu.jade create mode 100644 client/components/lists/listMenu.js delete mode 100644 client/components/lists/main.jade delete mode 100644 client/components/lists/main.js delete mode 100644 client/components/lists/main.styl delete mode 100644 client/components/lists/menu.jade delete mode 100644 client/components/lists/menu.js create mode 100644 client/config/blazeHelpers.js delete mode 100644 client/config/helpers.js (limited to 'client') diff --git a/client/components/boards/boardColors.styl b/client/components/boards/boardColors.styl new file mode 100644 index 00000000..d131701c --- /dev/null +++ b/client/components/boards/boardColors.styl @@ -0,0 +1,66 @@ +// We define a set of six board colors that we took from the FlatUI palette. +// http://flatuicolors.com +// +// XXX Centralizing all these properties in a single file just because their +// value is derived from the same color, doesn't make any sense. We should +// create a mixin/macro that would generate 6 versions of a given property and +// dispatch this list in the other stylus files. +setBoardColor(color) + &#header, + &.sk-spinner div, + .board-backgrounds-list &.background-box, + .board-list & a + background-color: color + + .is-selected .minicard + border-left: 3px solid color + + button[type=submit].primary, input[type=submit].primary + background-color: darken(color, 20%) + + &.pop-over .pop-over-list li a:hover, + .sidebar .sidebar-content .sidebar-btn:hover, + .sidebar-list li a:hover + background-color: lighten(color, 10%) + + &#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%) + + .materialCheckBox.is-checked + border-bottom: 2px solid color + border-right: 2px solid color + + .is-multiselection-active .multi-selection-checkbox + &.is-checked + .minicard + background: lighten(color, 90%) + + &:not(.is-checked) + .minicard:hover:not(.minicard-composer) + background: lighten(color, 97%) + +.board-color-nephritis + setBoardColor(#27AE60) + +.board-color-pomegranate + setBoardColor(#C0392B) + +.board-color-belize + setBoardColor(#2980B9) + +.board-color-wisteria + setBoardColor(#8E44AD) + +.board-color-midnight + setBoardColor(#2C3E50) + +.board-color-pumpkin + setBoardColor(#E67E22) diff --git a/client/components/boards/boardList.jade b/client/components/boards/boardList.jade deleted file mode 100644 index 4a73ed48..00000000 --- a/client/components/boards/boardList.jade +++ /dev/null @@ -1,16 +0,0 @@ -template(name="boardList") - .wrapper - if boards.count - ul.board-list.clearfix - each boards - li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) - a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name= title - i.fa.js-star-board( - class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" - title="{{_ 'star-board-title'}}") - else - ul.board-list.clearfix - li.js-add-board - a.label {{_ 'add-board'}} diff --git a/client/components/boards/boardList.js b/client/components/boards/boardList.js deleted file mode 100644 index 2311e7d0..00000000 --- a/client/components/boards/boardList.js +++ /dev/null @@ -1,30 +0,0 @@ -BlazeComponent.extendComponent({ - template: function() { - return 'boardList'; - }, - - boards: function() { - return Boards.find({ - archived: false, - 'members.userId': Meteor.userId() - }, { - sort: ['title'] - }); - }, - - isStarred: function() { - var user = Meteor.user(); - return user && user.hasStarred(this.currentData()._id); - }, - - events: function() { - return [{ - 'click .js-add-board': Popup.open('createBoard'), - 'click .js-star-board': function(evt) { - var boardId = this.currentData()._id; - Meteor.user().toggleBoardStar(boardId); - evt.preventDefault(); - } - }]; - } -}).register('boardList'); diff --git a/client/components/boards/boardList.styl b/client/components/boards/boardList.styl deleted file mode 100644 index 8e296028..00000000 --- a/client/components/boards/boardList.styl +++ /dev/null @@ -1,120 +0,0 @@ -$spaceBetweenTiles = 16px - -.board-list - margin: $spaceBetweenTiles ($spaceBetweenTiles/-2) 0 - - li - float: left - width: 25% - box-sizing: border-box - position: relative - - &.starred - .fa-star, - .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 ($spaceBetweenTiles/2) $spaceBetweenTiles - position: relative - text-decoration: none - - &.tile - background-size: auto - background-repeat: repeat - - .board-list-item-sub-name - color: rgba(255, 255, 255, .5) - display: block - font-size: 14px - font-weight: 400 - line-height: 22px - - .js-add-board - text-align:center - - .label - font-weight: normal - line-height:90px - - :hover - background-color:#939393 - - .fa-star, - .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: white - - li:hover a - color: #f6f6f6 - - .fa-star, - .fa-star-o - color: white - opacity: .75 - - &:hover - font-size: 18px - opacity: 1 - - &.is-star-active - opacity: 1 - -.board-backgrounds-list - - .board-background-select - box-sizing: border-box - display: block - float: left - width: 50% - padding-top: 12px - position: relative - z-index: 1 - - &:nth-child(-n + 2) - padding-top: 0 - - &:nth-child(2n) - padding-left: 6px - - &:nth-child(2n+1) - padding-right: 6px - - .background-box - border-radius: 3px - background-size: cover - display: block - height: 74px - position: relative - width: 100% - cursor: pointer - display: flex - align-items: center - justify-content: center - - i.fa-check - font-size: 25px - color: white - diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade new file mode 100644 index 00000000..4a73ed48 --- /dev/null +++ b/client/components/boards/boardsList.jade @@ -0,0 +1,16 @@ +template(name="boardList") + .wrapper + if boards.count + ul.board-list.clearfix + each boards + li(class="{{#if isStarred}}starred{{/if}}" class=colorClass) + a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name= title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + else + ul.board-list.clearfix + li.js-add-board + a.label {{_ 'add-board'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js new file mode 100644 index 00000000..2311e7d0 --- /dev/null +++ b/client/components/boards/boardsList.js @@ -0,0 +1,30 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'boardList'; + }, + + boards: function() { + return Boards.find({ + archived: false, + 'members.userId': Meteor.userId() + }, { + sort: ['title'] + }); + }, + + isStarred: function() { + var user = Meteor.user(); + return user && user.hasStarred(this.currentData()._id); + }, + + events: function() { + return [{ + 'click .js-add-board': Popup.open('createBoard'), + 'click .js-star-board': function(evt) { + var boardId = this.currentData()._id; + Meteor.user().toggleBoardStar(boardId); + evt.preventDefault(); + } + }]; + } +}).register('boardList'); diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl new file mode 100644 index 00000000..8e296028 --- /dev/null +++ b/client/components/boards/boardsList.styl @@ -0,0 +1,120 @@ +$spaceBetweenTiles = 16px + +.board-list + margin: $spaceBetweenTiles ($spaceBetweenTiles/-2) 0 + + li + float: left + width: 25% + box-sizing: border-box + position: relative + + &.starred + .fa-star, + .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 ($spaceBetweenTiles/2) $spaceBetweenTiles + position: relative + text-decoration: none + + &.tile + background-size: auto + background-repeat: repeat + + .board-list-item-sub-name + color: rgba(255, 255, 255, .5) + display: block + font-size: 14px + font-weight: 400 + line-height: 22px + + .js-add-board + text-align:center + + .label + font-weight: normal + line-height:90px + + :hover + background-color:#939393 + + .fa-star, + .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: white + + li:hover a + color: #f6f6f6 + + .fa-star, + .fa-star-o + color: white + opacity: .75 + + &:hover + font-size: 18px + opacity: 1 + + &.is-star-active + opacity: 1 + +.board-backgrounds-list + + .board-background-select + box-sizing: border-box + display: block + float: left + width: 50% + padding-top: 12px + position: relative + z-index: 1 + + &:nth-child(-n + 2) + padding-top: 0 + + &:nth-child(2n) + padding-left: 6px + + &:nth-child(2n+1) + padding-right: 6px + + .background-box + border-radius: 3px + background-size: cover + display: block + height: 74px + position: relative + width: 100% + cursor: pointer + display: flex + align-items: center + justify-content: center + + i.fa-check + font-size: 25px + color: white + diff --git a/client/components/boards/colors.styl b/client/components/boards/colors.styl deleted file mode 100644 index d131701c..00000000 --- a/client/components/boards/colors.styl +++ /dev/null @@ -1,66 +0,0 @@ -// We define a set of six board colors that we took from the FlatUI palette. -// http://flatuicolors.com -// -// XXX Centralizing all these properties in a single file just because their -// value is derived from the same color, doesn't make any sense. We should -// create a mixin/macro that would generate 6 versions of a given property and -// dispatch this list in the other stylus files. -setBoardColor(color) - &#header, - &.sk-spinner div, - .board-backgrounds-list &.background-box, - .board-list & a - background-color: color - - .is-selected .minicard - border-left: 3px solid color - - button[type=submit].primary, input[type=submit].primary - background-color: darken(color, 20%) - - &.pop-over .pop-over-list li a:hover, - .sidebar .sidebar-content .sidebar-btn:hover, - .sidebar-list li a:hover - background-color: lighten(color, 10%) - - &#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%) - - .materialCheckBox.is-checked - border-bottom: 2px solid color - border-right: 2px solid color - - .is-multiselection-active .multi-selection-checkbox - &.is-checked + .minicard - background: lighten(color, 90%) - - &:not(.is-checked) + .minicard:hover:not(.minicard-composer) - background: lighten(color, 97%) - -.board-color-nephritis - setBoardColor(#27AE60) - -.board-color-pomegranate - setBoardColor(#C0392B) - -.board-color-belize - setBoardColor(#2980B9) - -.board-color-wisteria - setBoardColor(#8E44AD) - -.board-color-midnight - setBoardColor(#2C3E50) - -.board-color-pumpkin - setBoardColor(#E67E22) diff --git a/client/components/boards/helpers.js b/client/components/boards/helpers.js deleted file mode 100644 index 969f8564..00000000 --- a/client/components/boards/helpers.js +++ /dev/null @@ -1,6 +0,0 @@ -Blaze.registerHelper('currentBoard', function() { - var boardId = Session.get('currentBoard'); - if (boardId) { - return Boards.findOne(boardId); - } -}); diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade new file mode 100644 index 00000000..56c32e84 --- /dev/null +++ b/client/components/cards/cardDetails.jade @@ -0,0 +1,120 @@ +template(name="cardDetails") + section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas + if cover + .card-details-cover(style="background-image: url({{ cover.url }})") + + .card-details-header + +inlinedForm(classNames="js-card-details-title") + input.full-line(type="text" value=title autofocus) + .edit-controls.clearfix + button.primary.confirm(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.fa.fa-angle-left.close-card-details.js-close-card-details + a.fa.fa-bars.card-details-menu.js-open-card-details-menu + h2.card-details-title.js-card-title( + class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") + = title + p.card-details-list + | {{_ 'in-list'}} + a.card-details-list-title( + class="{{#if currentUser.isBoardMember}}js-move-card is-editable{{/if}}") + = list.title + if currentUser.isBoardMember + i.fa.fa-chevron-down + + .card-details-items + .card-details-item.card-details-item-members + h3.card-details-item-title {{_ 'members'}} + each members + +userAvatar(userId=this cardId=../_id) + a.member.add-member.card-details-item-add-button.js-add-members + i.fa.fa-plus + + .card-details-item.card-details-item-labels + h3.card-details-item-title {{_ 'labels'}} + a.js-add-labels + each labels + span.card-label(class="card-label-{{color}}" title=name)= name + a.card-label.add-label.js-add-labels + i.fa.fa-plus + + //- XXX We should use "editable" to avoid repetiting ourselves + if currentUser.isBoardMember + h3.card-details-item-title Description + +inlinedForm(classNames="card-description js-card-description") + +editor(autofocus=true) + = description + .edit-controls.clearfix + button.primary(type="submit") {{_ 'edit'}} + a.fa.fa-times-thin.js-close-inlined-form + else + a.js-open-inlined-form + if description + +viewer + = description + else + | {{_ 'edit'}} + else if description + h3.card-details-item-title Description + +viewer + = description + if attachments.count + hr + h2 + i.fa.fa-paperclip + | {{_ 'attachments'}} + + +attachmentsGalery + + hr + h2 {{ _ 'activity'}} + if currentUser.isBoardMember + +commentForm + if isLoaded.get + +activities(card=this mode="card") + +template(name="cardDetailsActionsPopup") + if currentUser.isBoardMember + ul.pop-over-list + li: a.js-members Edit Members… + li: a.js-labels Edit Labels… + li: a.js-attachments Edit Attachments… + hr + ul.pop-over-list + li: a.js-copy Copy Card + unless archived + li: a.js-archive Archive Card + li: a.js-more More + +template(name="moveCardPopup") + +boardLists + +template(name="cardMembersPopup") + ul.pop-over-list.pop-over-member-list + each board.members + li.item(class="{{#if isCardMember}}active{{/if}}") + a.name.js-select-member(href="#") + +userAvatar(userId=user._id) + span.full-name + = user.profile.fullname + | ({{ user.username }}) + if isCardMember + i.fa.fa-check + +template(name="cardMorePopup") + p.quiet + span.clearfix + span {{_ 'link-card'}} + = ' ' + i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + input.inline-input(type="text" readonly value="{{ rootUrl }}") + | {{_ 'added'}} + span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} + a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} + +template(name="cardDeletePopup") + p {{_ "card-delete-pop"}} + unless archived + p {{_ "card-delete-suggest-archive"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js new file mode 100644 index 00000000..4fa90bf7 --- /dev/null +++ b/client/components/cards/cardDetails.js @@ -0,0 +1,123 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'cardDetails'; + }, + + mixins: function() { + return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; + }, + + calculateNextPeak: function() { + var altitude = this.find('.js-card-details').scrollHeight; + this.callFirstWith(this, 'setNextPeak', altitude); + }, + + reachNextPeak: function() { + var activitiesComponent = this.componentChildren('activities')[0]; + activitiesComponent.loadNextPage(); + }, + + onRendered: function() { + var bodyBoardComponent = this.componentParent(); + var additionalMargin = 550; + var $cardDetails = this.$(this.firstNode()); + var scollLeft = $cardDetails.offset().left + additionalMargin; + bodyBoardComponent.scrollLeft(scollLeft); + }, + + onDestroyed: function() { + this.componentParent().showOverlay.set(false); + }, + + updateCard: function(modifier) { + Cards.update(this.data()._id, { + $set: modifier + }); + }, + + onCreated: function() { + this.isLoaded = new ReactiveVar(false); + }, + + events: function() { + var events = { + [CSSEvents.animationend + ' .js-card-details']: function() { + this.isLoaded.set(true); + } + }; + + return [_.extend(events, { + 'click .js-close-card-details': function() { + Utils.goBoardId(this.data().boardId); + }, + 'click .js-move-card': Popup.open('moveCard'), + 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), + 'submit .js-card-description': function(evt) { + evt.preventDefault(); + var description = this.currentComponent().getValue(); + this.updateCard({ description: description }); + }, + 'submit .js-card-details-title': function(evt) { + evt.preventDefault(); + var title = this.currentComponent().getValue(); + if ($.trim(title)) { + this.updateCard({ title: title }); + } + }, + 'click .js-member': Popup.open('cardMember'), + 'click .js-add-members': Popup.open('cardMembers'), + 'click .js-add-labels': Popup.open('cardLabels'), + 'mouseenter .js-card-details': function() { + this.componentParent().showOverlay.set(true); + } + })]; + } +}).register('cardDetails'); + +Template.cardDetailsActionsPopup.events({ + 'click .js-members': Popup.open('cardMembers'), + 'click .js-labels': Popup.open('cardLabels'), + 'click .js-attachments': Popup.open('cardAttachments'), + // 'click .js-copy': Popup.open(), + 'click .js-archive': function(evt) { + evt.preventDefault(); + Cards.update(this._id, { + $set: { + archived: true + } + }); + Popup.close(); + }, + 'click .js-more': Popup.open('cardMore') +}); + +Template.moveCardPopup.events({ + 'click .js-select-list': function() { + // XXX We should *not* get the currentCard from the global state, but + // instead from a “component” state. + var cardId = Session.get('currentCard'); + var newListId = this._id; + Cards.update(cardId, { + $set: { + listId: newListId + } + }); + Popup.close(); + } +}); + +Template.cardMorePopup.events({ + 'click .js-delete': Popup.afterConfirm('cardDelete', function() { + Popup.close(); + Cards.remove(this._id); + Utils.goBoardId(this.board()._id); + }) +}); + +// Close the card details pane by pressing escape +EscapeActions.register('detailsPane', + function() { Utils.goBoardId(Session.get('currentBoard')); }, + function() { return ! Session.equals('currentCard', null); }, { + noClickEscapeOn: '.js-card-details,.board-sidebar,#header' + } +); diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl new file mode 100644 index 00000000..72e8c7c9 --- /dev/null +++ b/client/components/cards/cardDetails.styl @@ -0,0 +1,98 @@ +@import 'nib' + +.card-details + padding: 0 20px + height: 100% + flex-shrink: 0 + flex-basis: 470px + will-change: flex-basis + overflow: hidden + background: white + border-radius: 3px + z-index: 20 !important + animation: flexGrowIn 0.1s + box-shadow: 0 0 7px 0 darken(white, 30%) + transition: flex-basis 0.1s + margin-top: -9px + + .card-details-canvas + width: 470px + + .card-details-header + margin: 0 -20px 5px + padding 7px 20px 0 + background: #F7F7F7 + border-bottom: 1px solid darken(white, 10%) + min-height: 38px + position: relative + + .close-card-details + float: left + font-size: 24px + padding: 8px + padding-right: 11px + margin-left: -18px + + .card-details-menu + float: right + position: absolute + bottom: 6px + right: 15px + + .card-details-title + font-weight: bold + font-size: 1.33em + margin: 3px 0 + padding: 0 + + form.inlined-form + margin-top: 5px + margin-bottom: 10px + + .card-details-list + font-size: 0.85em + margin-bottom: 3px + + a.card-details-list-title + font-weight: bold + + &.is-editable + display: inline-block + background: darken(white, 10%) + border-radius: 3px + padding: 0px 5px + + .card-description textarea + min-height: 100px + + .card-details-items + display: flex + margin: 15px 0 + + .card-details-item + &.card-details-item-labels, + &.card-details-item-members + width: 50% + flex-shrink: 1 + + .card-details-item-title + font-size: 14px + color: darken(white, 45%) + + .card-label + padding-top: 5px + padding-bottom: 5px + + .activities + padding-top: 10px + +input[type="text"].attachment-add-link-input + float: left + margin: 0 0 8px + width: 80% + +input[type="submit"].attachment-add-link-submit + float: left + margin: 0 0 8px 4px + padding: 6px 12px + width: 18% diff --git a/client/components/cards/details.jade b/client/components/cards/details.jade deleted file mode 100644 index 56c32e84..00000000 --- a/client/components/cards/details.jade +++ /dev/null @@ -1,120 +0,0 @@ -template(name="cardDetails") - section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas - if cover - .card-details-cover(style="background-image: url({{ cover.url }})") - - .card-details-header - +inlinedForm(classNames="js-card-details-title") - input.full-line(type="text" value=title autofocus) - .edit-controls.clearfix - button.primary.confirm(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form - else - a.fa.fa-angle-left.close-card-details.js-close-card-details - a.fa.fa-bars.card-details-menu.js-open-card-details-menu - h2.card-details-title.js-card-title( - class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") - = title - p.card-details-list - | {{_ 'in-list'}} - a.card-details-list-title( - class="{{#if currentUser.isBoardMember}}js-move-card is-editable{{/if}}") - = list.title - if currentUser.isBoardMember - i.fa.fa-chevron-down - - .card-details-items - .card-details-item.card-details-item-members - h3.card-details-item-title {{_ 'members'}} - each members - +userAvatar(userId=this cardId=../_id) - a.member.add-member.card-details-item-add-button.js-add-members - i.fa.fa-plus - - .card-details-item.card-details-item-labels - h3.card-details-item-title {{_ 'labels'}} - a.js-add-labels - each labels - span.card-label(class="card-label-{{color}}" title=name)= name - a.card-label.add-label.js-add-labels - i.fa.fa-plus - - //- XXX We should use "editable" to avoid repetiting ourselves - if currentUser.isBoardMember - h3.card-details-item-title Description - +inlinedForm(classNames="card-description js-card-description") - +editor(autofocus=true) - = description - .edit-controls.clearfix - button.primary(type="submit") {{_ 'edit'}} - a.fa.fa-times-thin.js-close-inlined-form - else - a.js-open-inlined-form - if description - +viewer - = description - else - | {{_ 'edit'}} - else if description - h3.card-details-item-title Description - +viewer - = description - if attachments.count - hr - h2 - i.fa.fa-paperclip - | {{_ 'attachments'}} - - +attachmentsGalery - - hr - h2 {{ _ 'activity'}} - if currentUser.isBoardMember - +commentForm - if isLoaded.get - +activities(card=this mode="card") - -template(name="cardDetailsActionsPopup") - if currentUser.isBoardMember - ul.pop-over-list - li: a.js-members Edit Members… - li: a.js-labels Edit Labels… - li: a.js-attachments Edit Attachments… - hr - ul.pop-over-list - li: a.js-copy Copy Card - unless archived - li: a.js-archive Archive Card - li: a.js-more More - -template(name="moveCardPopup") - +boardLists - -template(name="cardMembersPopup") - ul.pop-over-list.pop-over-member-list - each board.members - li.item(class="{{#if isCardMember}}active{{/if}}") - a.name.js-select-member(href="#") - +userAvatar(userId=user._id) - span.full-name - = user.profile.fullname - | ({{ user.username }}) - if isCardMember - i.fa.fa-check - -template(name="cardMorePopup") - p.quiet - span.clearfix - span {{_ 'link-card'}} - = ' ' - i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}") - input.inline-input(type="text" readonly value="{{ rootUrl }}") - | {{_ 'added'}} - span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} - a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} - -template(name="cardDeletePopup") - p {{_ "card-delete-pop"}} - unless archived - p {{_ "card-delete-suggest-archive"}} - button.js-confirm.negate.full(type="submit") {{_ 'delete'}} diff --git a/client/components/cards/details.js b/client/components/cards/details.js deleted file mode 100644 index 4fa90bf7..00000000 --- a/client/components/cards/details.js +++ /dev/null @@ -1,123 +0,0 @@ -BlazeComponent.extendComponent({ - template: function() { - return 'cardDetails'; - }, - - mixins: function() { - return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar]; - }, - - calculateNextPeak: function() { - var altitude = this.find('.js-card-details').scrollHeight; - this.callFirstWith(this, 'setNextPeak', altitude); - }, - - reachNextPeak: function() { - var activitiesComponent = this.componentChildren('activities')[0]; - activitiesComponent.loadNextPage(); - }, - - onRendered: function() { - var bodyBoardComponent = this.componentParent(); - var additionalMargin = 550; - var $cardDetails = this.$(this.firstNode()); - var scollLeft = $cardDetails.offset().left + additionalMargin; - bodyBoardComponent.scrollLeft(scollLeft); - }, - - onDestroyed: function() { - this.componentParent().showOverlay.set(false); - }, - - updateCard: function(modifier) { - Cards.update(this.data()._id, { - $set: modifier - }); - }, - - onCreated: function() { - this.isLoaded = new ReactiveVar(false); - }, - - events: function() { - var events = { - [CSSEvents.animationend + ' .js-card-details']: function() { - this.isLoaded.set(true); - } - }; - - return [_.extend(events, { - 'click .js-close-card-details': function() { - Utils.goBoardId(this.data().boardId); - }, - 'click .js-move-card': Popup.open('moveCard'), - 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), - 'submit .js-card-description': function(evt) { - evt.preventDefault(); - var description = this.currentComponent().getValue(); - this.updateCard({ description: description }); - }, - 'submit .js-card-details-title': function(evt) { - evt.preventDefault(); - var title = this.currentComponent().getValue(); - if ($.trim(title)) { - this.updateCard({ title: title }); - } - }, - 'click .js-member': Popup.open('cardMember'), - 'click .js-add-members': Popup.open('cardMembers'), - 'click .js-add-labels': Popup.open('cardLabels'), - 'mouseenter .js-card-details': function() { - this.componentParent().showOverlay.set(true); - } - })]; - } -}).register('cardDetails'); - -Template.cardDetailsActionsPopup.events({ - 'click .js-members': Popup.open('cardMembers'), - 'click .js-labels': Popup.open('cardLabels'), - 'click .js-attachments': Popup.open('cardAttachments'), - // 'click .js-copy': Popup.open(), - 'click .js-archive': function(evt) { - evt.preventDefault(); - Cards.update(this._id, { - $set: { - archived: true - } - }); - Popup.close(); - }, - 'click .js-more': Popup.open('cardMore') -}); - -Template.moveCardPopup.events({ - 'click .js-select-list': function() { - // XXX We should *not* get the currentCard from the global state, but - // instead from a “component” state. - var cardId = Session.get('currentCard'); - var newListId = this._id; - Cards.update(cardId, { - $set: { - listId: newListId - } - }); - Popup.close(); - } -}); - -Template.cardMorePopup.events({ - 'click .js-delete': Popup.afterConfirm('cardDelete', function() { - Popup.close(); - Cards.remove(this._id); - Utils.goBoardId(this.board()._id); - }) -}); - -// Close the card details pane by pressing escape -EscapeActions.register('detailsPane', - function() { Utils.goBoardId(Session.get('currentBoard')); }, - function() { return ! Session.equals('currentCard', null); }, { - noClickEscapeOn: '.js-card-details,.board-sidebar,#header' - } -); diff --git a/client/components/cards/details.styl b/client/components/cards/details.styl deleted file mode 100644 index 72e8c7c9..00000000 --- a/client/components/cards/details.styl +++ /dev/null @@ -1,98 +0,0 @@ -@import 'nib' - -.card-details - padding: 0 20px - height: 100% - flex-shrink: 0 - flex-basis: 470px - will-change: flex-basis - overflow: hidden - background: white - border-radius: 3px - z-index: 20 !important - animation: flexGrowIn 0.1s - box-shadow: 0 0 7px 0 darken(white, 30%) - transition: flex-basis 0.1s - margin-top: -9px - - .card-details-canvas - width: 470px - - .card-details-header - margin: 0 -20px 5px - padding 7px 20px 0 - background: #F7F7F7 - border-bottom: 1px solid darken(white, 10%) - min-height: 38px - position: relative - - .close-card-details - float: left - font-size: 24px - padding: 8px - padding-right: 11px - margin-left: -18px - - .card-details-menu - float: right - position: absolute - bottom: 6px - right: 15px - - .card-details-title - font-weight: bold - font-size: 1.33em - margin: 3px 0 - padding: 0 - - form.inlined-form - margin-top: 5px - margin-bottom: 10px - - .card-details-list - font-size: 0.85em - margin-bottom: 3px - - a.card-details-list-title - font-weight: bold - - &.is-editable - display: inline-block - background: darken(white, 10%) - border-radius: 3px - padding: 0px 5px - - .card-description textarea - min-height: 100px - - .card-details-items - display: flex - margin: 15px 0 - - .card-details-item - &.card-details-item-labels, - &.card-details-item-members - width: 50% - flex-shrink: 1 - - .card-details-item-title - font-size: 14px - color: darken(white, 45%) - - .card-label - padding-top: 5px - padding-bottom: 5px - - .activities - padding-top: 10px - -input[type="text"].attachment-add-link-input - float: left - margin: 0 0 8px - width: 80% - -input[type="submit"].attachment-add-link-submit - float: left - margin: 0 0 8px 4px - padding: 6px 12px - width: 18% diff --git a/client/components/lists/body.jade b/client/components/lists/body.jade deleted file mode 100644 index b0a374ea..00000000 --- a/client/components/lists/body.jade +++ /dev/null @@ -1,30 +0,0 @@ -template(name="listBody") - .list-body.js-perfect-scrollbar - .minicards.clearfix.js-minicards - if cards.count - +inlinedForm(autoclose=false position="top") - +addCardForm(listId=_id position="top") - each cards - a.minicard-wrapper.js-minicard(href=absoluteUrl - class="{{#if cardIsSelected}}is-selected{{/if}}" - class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") - if MultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( - class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") - +minicard(this) - if currentUser.isBoardMember - +inlinedForm(autoclose=false position="bottom") - +addCardForm(listId=_id position="bottom") - else - a.open-minicard-composer.js-card-composer.js-open-inlined-form - i.fa.fa-plus - | {{_ 'add-card'}} - -template(name="addCardForm") - .minicard.minicard-composer.js-composer - .minicard-detailss.clearfix - textarea.minicard-composer-textarea.js-card-title(autofocus) - .minicard-members.js-minicard-composer-members - .add-controls.clearfix - button.primary.confirm(type="submit") {{_ 'add'}} - a.fa.fa-times-thin.js-close-inlined-form diff --git a/client/components/lists/body.js b/client/components/lists/body.js deleted file mode 100644 index 27864474..00000000 --- a/client/components/lists/body.js +++ /dev/null @@ -1,143 +0,0 @@ -BlazeComponent.extendComponent({ - template: function() { - return 'listBody'; - }, - - mixins: function() { - return [Mixins.PerfectScrollbar]; - }, - - openForm: function(options) { - options = options || {}; - options.position = options.position || 'top'; - - var forms = this.componentChildren('inlinedForm'); - var form = _.find(forms, function(component) { - return component.data().position === options.position; - }); - if (! form && forms.length > 0) { - form = forms[0]; - } - form.open(); - }, - - addCard: function(evt) { - evt.preventDefault(); - var textarea = $(evt.currentTarget).find('textarea'); - var title = textarea.val(); - var position = Blaze.getData(evt.currentTarget).position; - var sortIndex; - var firstCard = this.find('.js-minicard:first'); - var lastCard = this.find('.js-minicard:last'); - if (position === 'top') { - sortIndex = Utils.calculateIndex(null, firstCard).base; - } else if (position === 'bottom') { - sortIndex = Utils.calculateIndex(lastCard, null).base; - } - - if ($.trim(title)) { - var _id = Cards.insert({ - title: title, - listId: this.data()._id, - boardId: this.data().board()._id, - sort: sortIndex - }); - // In case the filter is active we need to add the newly inserted card in - // the list of exceptions -- cards that are not filtered. Otherwise the - // 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(); - if (position === 'bottom') { - this.scrollToBottom(); - } - } - }, - - scrollToBottom: function() { - var container = this.firstNode(); - $(container).animate({ - scrollTop: container.scrollHeight - }); - }, - - clickOnMiniCard: function(evt) { - if (MultiSelection.isActive() || evt.shiftKey) { - evt.stopImmediatePropagation(); - evt.preventDefault(); - var methodName = evt.shiftKey ? 'toogleRange' : 'toogle'; - MultiSelection[methodName](this.currentData()._id); - - // If the card is already selected, we want to de-select it. - // XXX We should probably modify the minicard href attribute instead of - // overwriting the event in case the card is already selected. - } else if (Session.equals('currentCard', this.currentData()._id)) { - evt.stopImmediatePropagation(); - evt.preventDefault(); - Utils.goBoardId(Session.get('currentBoard')); - } - }, - - cardIsSelected: function() { - return Session.equals('currentCard', this.currentData()._id); - }, - - toggleMultiSelection: function(evt) { - evt.stopPropagation(); - evt.preventDefault(); - MultiSelection.toogle(this.currentData()._id); - }, - - events: function() { - return [{ - 'click .js-minicard': this.clickOnMiniCard, - 'click .js-toggle-multi-selection': this.toggleMultiSelection, - 'click .open-minicard-composer': this.scrollToBottom, - submit: this.addCard - }]; - } -}).register('listBody'); - -BlazeComponent.extendComponent({ - template: function() { - return 'addCardForm'; - }, - - pressKey: function(evt) { - // Pressing Enter should submit the card - if (evt.keyCode === 13) { - evt.preventDefault(); - var $form = $(evt.currentTarget).closest('form'); - // XXX For some reason $form.submit() does not work (it's probably a bug - // of blaze-component related to the fact that the submit event is non- - // bubbling). This is why we click on the submit button instead -- which - // 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 - } else if (evt.keyCode === 9) { - evt.preventDefault(); - var isReverse = evt.shiftKey; - var list = $('#js-list-' + this.data().listId); - var listSelector = '.js-list:not(.js-list-composer)'; - var nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); - // If there is no next list, loop back to the beginning. - if (! nextList) { - nextList = $(listSelector + (isReverse ? ':last' : ':first')).get(0); - } - - BlazeComponent.getComponentForElement(nextList).openForm({ - position:this.data().position - }); - } - }, - - events: function() { - return [{ - keydown: this.pressKey - }]; - } -}).register('addCardForm'); diff --git a/client/components/lists/header.jade b/client/components/lists/header.jade deleted file mode 100644 index 288cfd57..00000000 --- a/client/components/lists/header.jade +++ /dev/null @@ -1,16 +0,0 @@ -template(name="listHeader") - .list-header.js-list-header - +inlinedForm - +editListTitleForm - else - h2.list-header-name( - class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") - = title - a.list-header-menu-icon.fa.fa-bars.js-open-list-menu - -template(name="editListTitleForm") - .list-composer - input.full-line(type="text" value=title autofocus) - .edit-controls.clearfix - button.primary.confirm(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form diff --git a/client/components/lists/header.js b/client/components/lists/header.js deleted file mode 100644 index 014cfd80..00000000 --- a/client/components/lists/header.js +++ /dev/null @@ -1,25 +0,0 @@ -BlazeComponent.extendComponent({ - template: function() { - return 'listHeader'; - }, - - editTitle: function(evt) { - evt.preventDefault(); - var form = this.componentChildren('inlinedForm')[0]; - var newTitle = form.getValue(); - if ($.trim(newTitle)) { - Lists.update(this.currentData()._id, { - $set: { - title: newTitle - } - }); - } - }, - - events: function() { - return [{ - 'click .js-open-list-menu': Popup.open('listAction'), - submit: this.editTitle - }]; - } -}).register('listHeader'); diff --git a/client/components/lists/list.jade b/client/components/lists/list.jade new file mode 100644 index 00000000..c959b87f --- /dev/null +++ b/client/components/lists/list.jade @@ -0,0 +1,4 @@ +template(name='list') + .list.js-list(id="js-list-{{_id}}") + +listHeader + +listBody diff --git a/client/components/lists/list.js b/client/components/lists/list.js new file mode 100644 index 00000000..3b602f43 --- /dev/null +++ b/client/components/lists/list.js @@ -0,0 +1,126 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'list'; + }, + + // Proxies + openForm: function(options) { + this.componentChildren('listBody')[0].openForm(options); + }, + + onCreated: function() { + this.newCardFormIsVisible = new ReactiveVar(true); + }, + + // The jquery UI sortable library is the best solution I've found so far. I + // tried sortable and dragula but they were not powerful enough four our use + // case. I also considered writing/forking a drag-and-drop + sortable library + // but it's probably too much work. + // By calling asking the sortable library to cancel its move on the `stop` + // callback, we basically solve all issues related to reactive updates. A + // comment below provides further details. + onRendered: function() { + var self = this; + if (! Meteor.user() || ! Meteor.user().isBoardMember()) + return; + + var boardComponent = self.componentParent(); + var itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; + var $cards = self.$('.js-minicards'); + $cards.sortable({ + connectWith: '.js-minicards', + tolerance: 'pointer', + appendTo: 'body', + helper: function(evt, item) { + var helper = item.clone(); + if (MultiSelection.isActive()) { + var andNOthers = $cards.find('.js-minicard.is-checked').length - 1; + if (andNOthers > 0) { + helper.append($(Blaze.toHTML(HTML.DIV( + // XXX Super bad class name + {'class': 'and-n-other'}, + // XXX Need to translate + 'and ' + andNOthers + ' other cards.' + )))); + } + } + return helper; + }, + distance: 7, + items: itemsSelector, + scroll: false, + placeholder: 'minicard-wrapper placeholder', + start: function(evt, ui) { + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup'); + boardComponent.setIsDragging(true); + }, + stop: function(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + var prevCardDom = ui.item.prev('.js-minicard').get(0); + var nextCardDom = ui.item.next('.js-minicard').get(0); + var nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; + var sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards); + var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; + + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $cards.sortable('cancel'); + + if (MultiSelection.isActive()) { + Cards.find(MultiSelection.getMongoSelector()).forEach(function(c, i) { + Cards.update(c._id, { + $set: { + listId: listId, + sort: sortIndex.base + i * sortIndex.increment + } + }); + }); + } else { + var cardDomElement = ui.item.get(0); + var cardId = Blaze.getData(cardDomElement)._id; + Cards.update(cardId, { + $set: { + listId: listId, + sort: sortIndex.base + } + }); + } + boardComponent.setIsDragging(false); + } + }); + + // We want to re-run this function any time a card is added. + self.autorun(function() { + var currentBoardId = Tracker.nonreactive(function() { + 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; + addToSet = { members: memberId }; + } else { + var labelId = Blaze.getData(ui.draggable.get(0))._id; + addToSet = { labelIds: labelId }; + } + Cards.update(cardId, { $addToSet: addToSet }); + } + }); + }); + }); + } +}).register('list'); diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl new file mode 100644 index 00000000..bfa0f348 --- /dev/null +++ b/client/components/lists/list.styl @@ -0,0 +1,110 @@ +@import 'nib' + +.list + box-sizing: border-box + display: flex + flex-direction: column + flex: 0 0 270px + position: relative + // Even if this background color is the same as the body we can't leave it + // transparent, because that won't work during a list drag. + background: darken(white, 13%) + height: 100% + border-left: 1px solid darken(white, 20%) + padding: 0 + + &:first-child + margin-left: 5px + border-left: none + + .card-details + & + border-left: none + + &.ui-sortable-helper + cursor: grabbing + box-shadow: -2px 2px 8px rgba(0, 0, 0, .3), + 0 0 1px rgba(0, 0, 0, .5) + transform: rotate(4deg) + + &.placeholder + background-color: rgba(0, 0, 0, .2) + border-color: transparent + box-shadow: none + height: 100px + + &.list-composer, & list-composer + padding: 17px + + form + margin-top: -5px + + .list-name-input + background: rgba(255, 255, 255, .4) + border-color: #aaa + display: block + margin: 0 + transition: margin 85ms ease-in, + background 85ms ease-in + width: 100% + + .edit-controls + height: 32px + transition: margin 85ms ease-in, + height 85ms ease-in + overflow: hidden + margin: 4px 0 0 + +.list-header + flex: 0 0 auto + margin: 20px 12px 4px + position: relative + min-height: 20px + + .list-header-name + display: inline + font-size: 16px + line-height: 17px + margin: 0 + font-weight: bold + min-height: 9px + min-width: 30px + overflow: hidden + text-overflow: ellipsis + word-wrap: break-word + + .list-header-menu-icon + position: absolute + top: 0 + right: 0 + +.list-body + flex: 1 + display: flex + overflow-y: auto + padding: 5px 11px + + .minicards + flex: 1 + + form + margin-bottom: 9px + + .ps-scrollbar-y-rail + transform: translateX(2px) + + .open-minicard-composer + border-radius: 2px + color: #8c8c8c + display: block + padding: 7px 10px + position: relative + text-decoration: none + animation: fadeIn 0.3s + + i.fa + margin-right: 7px + + &:hover + background: #fafafa + color: #222 + box-shadow: 0 1px 2px rgba(0,0,0,.2) diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade new file mode 100644 index 00000000..b0a374ea --- /dev/null +++ b/client/components/lists/listBody.jade @@ -0,0 +1,30 @@ +template(name="listBody") + .list-body.js-perfect-scrollbar + .minicards.clearfix.js-minicards + if cards.count + +inlinedForm(autoclose=false position="top") + +addCardForm(listId=_id position="top") + each cards + a.minicard-wrapper.js-minicard(href=absoluteUrl + class="{{#if cardIsSelected}}is-selected{{/if}}" + class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") + if MultiSelection.isActive + .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( + class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") + +minicard(this) + if currentUser.isBoardMember + +inlinedForm(autoclose=false position="bottom") + +addCardForm(listId=_id position="bottom") + else + a.open-minicard-composer.js-card-composer.js-open-inlined-form + i.fa.fa-plus + | {{_ 'add-card'}} + +template(name="addCardForm") + .minicard.minicard-composer.js-composer + .minicard-detailss.clearfix + textarea.minicard-composer-textarea.js-card-title(autofocus) + .minicard-members.js-minicard-composer-members + .add-controls.clearfix + button.primary.confirm(type="submit") {{_ 'add'}} + a.fa.fa-times-thin.js-close-inlined-form diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js new file mode 100644 index 00000000..27864474 --- /dev/null +++ b/client/components/lists/listBody.js @@ -0,0 +1,143 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'listBody'; + }, + + mixins: function() { + return [Mixins.PerfectScrollbar]; + }, + + openForm: function(options) { + options = options || {}; + options.position = options.position || 'top'; + + var forms = this.componentChildren('inlinedForm'); + var form = _.find(forms, function(component) { + return component.data().position === options.position; + }); + if (! form && forms.length > 0) { + form = forms[0]; + } + form.open(); + }, + + addCard: function(evt) { + evt.preventDefault(); + var textarea = $(evt.currentTarget).find('textarea'); + var title = textarea.val(); + var position = Blaze.getData(evt.currentTarget).position; + var sortIndex; + var firstCard = this.find('.js-minicard:first'); + var lastCard = this.find('.js-minicard:last'); + if (position === 'top') { + sortIndex = Utils.calculateIndex(null, firstCard).base; + } else if (position === 'bottom') { + sortIndex = Utils.calculateIndex(lastCard, null).base; + } + + if ($.trim(title)) { + var _id = Cards.insert({ + title: title, + listId: this.data()._id, + boardId: this.data().board()._id, + sort: sortIndex + }); + // In case the filter is active we need to add the newly inserted card in + // the list of exceptions -- cards that are not filtered. Otherwise the + // 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(); + if (position === 'bottom') { + this.scrollToBottom(); + } + } + }, + + scrollToBottom: function() { + var container = this.firstNode(); + $(container).animate({ + scrollTop: container.scrollHeight + }); + }, + + clickOnMiniCard: function(evt) { + if (MultiSelection.isActive() || evt.shiftKey) { + evt.stopImmediatePropagation(); + evt.preventDefault(); + var methodName = evt.shiftKey ? 'toogleRange' : 'toogle'; + MultiSelection[methodName](this.currentData()._id); + + // If the card is already selected, we want to de-select it. + // XXX We should probably modify the minicard href attribute instead of + // overwriting the event in case the card is already selected. + } else if (Session.equals('currentCard', this.currentData()._id)) { + evt.stopImmediatePropagation(); + evt.preventDefault(); + Utils.goBoardId(Session.get('currentBoard')); + } + }, + + cardIsSelected: function() { + return Session.equals('currentCard', this.currentData()._id); + }, + + toggleMultiSelection: function(evt) { + evt.stopPropagation(); + evt.preventDefault(); + MultiSelection.toogle(this.currentData()._id); + }, + + events: function() { + return [{ + 'click .js-minicard': this.clickOnMiniCard, + 'click .js-toggle-multi-selection': this.toggleMultiSelection, + 'click .open-minicard-composer': this.scrollToBottom, + submit: this.addCard + }]; + } +}).register('listBody'); + +BlazeComponent.extendComponent({ + template: function() { + return 'addCardForm'; + }, + + pressKey: function(evt) { + // Pressing Enter should submit the card + if (evt.keyCode === 13) { + evt.preventDefault(); + var $form = $(evt.currentTarget).closest('form'); + // XXX For some reason $form.submit() does not work (it's probably a bug + // of blaze-component related to the fact that the submit event is non- + // bubbling). This is why we click on the submit button instead -- which + // 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 + } else if (evt.keyCode === 9) { + evt.preventDefault(); + var isReverse = evt.shiftKey; + var list = $('#js-list-' + this.data().listId); + var listSelector = '.js-list:not(.js-list-composer)'; + var nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); + // If there is no next list, loop back to the beginning. + if (! nextList) { + nextList = $(listSelector + (isReverse ? ':last' : ':first')).get(0); + } + + BlazeComponent.getComponentForElement(nextList).openForm({ + position:this.data().position + }); + } + }, + + events: function() { + return [{ + keydown: this.pressKey + }]; + } +}).register('addCardForm'); diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade new file mode 100644 index 00000000..288cfd57 --- /dev/null +++ b/client/components/lists/listHeader.jade @@ -0,0 +1,16 @@ +template(name="listHeader") + .list-header.js-list-header + +inlinedForm + +editListTitleForm + else + h2.list-header-name( + class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") + = title + a.list-header-menu-icon.fa.fa-bars.js-open-list-menu + +template(name="editListTitleForm") + .list-composer + input.full-line(type="text" value=title autofocus) + .edit-controls.clearfix + button.primary.confirm(type="submit") {{_ 'save'}} + a.fa.fa-times-thin.js-close-inlined-form diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js new file mode 100644 index 00000000..014cfd80 --- /dev/null +++ b/client/components/lists/listHeader.js @@ -0,0 +1,25 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'listHeader'; + }, + + editTitle: function(evt) { + evt.preventDefault(); + var form = this.componentChildren('inlinedForm')[0]; + var newTitle = form.getValue(); + if ($.trim(newTitle)) { + Lists.update(this.currentData()._id, { + $set: { + title: newTitle + } + }); + } + }, + + events: function() { + return [{ + 'click .js-open-list-menu': Popup.open('listAction'), + submit: this.editTitle + }]; + } +}).register('listHeader'); diff --git a/client/components/lists/listMenu.jade b/client/components/lists/listMenu.jade new file mode 100644 index 00000000..052f064c --- /dev/null +++ b/client/components/lists/listMenu.jade @@ -0,0 +1,29 @@ +template(name="listActionPopup") + ul.pop-over-list + li: a.js-add-card {{_ 'add-card'}} + li: a.highlight-icon.js-list-subscribe {{_ 'subscribe'}} + if cards.count + hr + ul.pop-over-list + li: a.js-select-cards {{_ 'list-select-cards'}} + li: a.js-move-cards {{_ 'list-move-cards'}} + li: a.js-archive-cards {{_ 'list-archive-cards'}} + hr + ul.pop-over-list + li: a.js-close-list {{_ 'archive-list'}} + +template(name="listMoveCardsPopup") + +boardLists + +template(name="boardLists") + ul.pop-over-list + each currentBoard.lists + li + if($eq ../_id _id) + a.disabled {{title}} ({{_ 'current'}}) + else + a.js-select-list= title + +template(name="listArchiveCardsPopup") + p {{_ 'list-archive-cards-pop'}} + input.js-confirm.negate.full(type="submit" value="{{_ 'archive-all'}}") diff --git a/client/components/lists/listMenu.js b/client/components/lists/listMenu.js new file mode 100644 index 00000000..dda1270c --- /dev/null +++ b/client/components/lists/listMenu.js @@ -0,0 +1,52 @@ +Template.listActionPopup.events({ + 'click .js-add-card': function() { + var listDom = document.getElementById('js-list-' + this._id); + var listComponent = BlazeComponent.getComponentForElement(listDom); + listComponent.openForm({ position: 'top' }); + Popup.close(); + }, + 'click .js-list-subscribe': function() {}, + 'click .js-select-cards': function() { + var cardIds = Cards.find( + {listId: this._id}, + {fields: { _id: 1 }} + ).map(function(card) { return card._id; }); + MultiSelection.add(cardIds); + Popup.close(); + }, + 'click .js-move-cards': Popup.open('listMoveCards'), + 'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', function() { + Cards.find({listId: this._id}).forEach(function(card) { + Cards.update(card._id, { + $set: { + archived: true + } + }); + }); + Popup.close(); + }), + 'click .js-close-list': function(evt) { + evt.preventDefault(); + Lists.update(this._id, { + $set: { + archived: true + } + }); + Popup.close(); + } +}); + +Template.listMoveCardsPopup.events({ + 'click .js-select-list': function() { + var fromList = Template.parentData(2).data._id; + var toList = this._id; + Cards.find({listId: fromList}).forEach(function(card) { + Cards.update(card._id, { + $set: { + listId: toList + } + }); + }); + Popup.close(); + } +}); diff --git a/client/components/lists/main.jade b/client/components/lists/main.jade deleted file mode 100644 index c959b87f..00000000 --- a/client/components/lists/main.jade +++ /dev/null @@ -1,4 +0,0 @@ -template(name='list') - .list.js-list(id="js-list-{{_id}}") - +listHeader - +listBody diff --git a/client/components/lists/main.js b/client/components/lists/main.js deleted file mode 100644 index 3b602f43..00000000 --- a/client/components/lists/main.js +++ /dev/null @@ -1,126 +0,0 @@ -BlazeComponent.extendComponent({ - template: function() { - return 'list'; - }, - - // Proxies - openForm: function(options) { - this.componentChildren('listBody')[0].openForm(options); - }, - - onCreated: function() { - this.newCardFormIsVisible = new ReactiveVar(true); - }, - - // The jquery UI sortable library is the best solution I've found so far. I - // tried sortable and dragula but they were not powerful enough four our use - // case. I also considered writing/forking a drag-and-drop + sortable library - // but it's probably too much work. - // By calling asking the sortable library to cancel its move on the `stop` - // callback, we basically solve all issues related to reactive updates. A - // comment below provides further details. - onRendered: function() { - var self = this; - if (! Meteor.user() || ! Meteor.user().isBoardMember()) - return; - - var boardComponent = self.componentParent(); - var itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; - var $cards = self.$('.js-minicards'); - $cards.sortable({ - connectWith: '.js-minicards', - tolerance: 'pointer', - appendTo: 'body', - helper: function(evt, item) { - var helper = item.clone(); - if (MultiSelection.isActive()) { - var andNOthers = $cards.find('.js-minicard.is-checked').length - 1; - if (andNOthers > 0) { - helper.append($(Blaze.toHTML(HTML.DIV( - // XXX Super bad class name - {'class': 'and-n-other'}, - // XXX Need to translate - 'and ' + andNOthers + ' other cards.' - )))); - } - } - return helper; - }, - distance: 7, - items: itemsSelector, - scroll: false, - placeholder: 'minicard-wrapper placeholder', - start: function(evt, ui) { - ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup'); - boardComponent.setIsDragging(true); - }, - stop: function(evt, ui) { - // To attribute the new index number, we need to get the DOM element - // of the previous and the following card -- if any. - var prevCardDom = ui.item.prev('.js-minicard').get(0); - var nextCardDom = ui.item.next('.js-minicard').get(0); - var nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; - var sortIndex = Utils.calculateIndex(prevCardDom, nextCardDom, nCards); - var listId = Blaze.getData(ui.item.parents('.list').get(0))._id; - - // Normally the jquery-ui sortable library moves the dragged DOM element - // to its new position, which disrupts Blaze reactive updates mechanism - // (especially when we move the last card of a list, or when multiple - // users move some cards at the same time). To prevent these UX glitches - // we ask sortable to gracefully cancel the move, and to put back the - // DOM in its initial state. The card move is then handled reactively by - // Blaze with the below query. - $cards.sortable('cancel'); - - if (MultiSelection.isActive()) { - Cards.find(MultiSelection.getMongoSelector()).forEach(function(c, i) { - Cards.update(c._id, { - $set: { - listId: listId, - sort: sortIndex.base + i * sortIndex.increment - } - }); - }); - } else { - var cardDomElement = ui.item.get(0); - var cardId = Blaze.getData(cardDomElement)._id; - Cards.update(cardId, { - $set: { - listId: listId, - sort: sortIndex.base - } - }); - } - boardComponent.setIsDragging(false); - } - }); - - // We want to re-run this function any time a card is added. - self.autorun(function() { - var currentBoardId = Tracker.nonreactive(function() { - 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; - addToSet = { members: memberId }; - } else { - var labelId = Blaze.getData(ui.draggable.get(0))._id; - addToSet = { labelIds: labelId }; - } - Cards.update(cardId, { $addToSet: addToSet }); - } - }); - }); - }); - } -}).register('list'); diff --git a/client/components/lists/main.styl b/client/components/lists/main.styl deleted file mode 100644 index bfa0f348..00000000 --- a/client/components/lists/main.styl +++ /dev/null @@ -1,110 +0,0 @@ -@import 'nib' - -.list - box-sizing: border-box - display: flex - flex-direction: column - flex: 0 0 270px - position: relative - // Even if this background color is the same as the body we can't leave it - // transparent, because that won't work during a list drag. - background: darken(white, 13%) - height: 100% - border-left: 1px solid darken(white, 20%) - padding: 0 - - &:first-child - margin-left: 5px - border-left: none - - .card-details + & - border-left: none - - &.ui-sortable-helper - cursor: grabbing - box-shadow: -2px 2px 8px rgba(0, 0, 0, .3), - 0 0 1px rgba(0, 0, 0, .5) - transform: rotate(4deg) - - &.placeholder - background-color: rgba(0, 0, 0, .2) - border-color: transparent - box-shadow: none - height: 100px - - &.list-composer, & list-composer - padding: 17px - - form - margin-top: -5px - - .list-name-input - background: rgba(255, 255, 255, .4) - border-color: #aaa - display: block - margin: 0 - transition: margin 85ms ease-in, - background 85ms ease-in - width: 100% - - .edit-controls - height: 32px - transition: margin 85ms ease-in, - height 85ms ease-in - overflow: hidden - margin: 4px 0 0 - -.list-header - flex: 0 0 auto - margin: 20px 12px 4px - position: relative - min-height: 20px - - .list-header-name - display: inline - font-size: 16px - line-height: 17px - margin: 0 - font-weight: bold - min-height: 9px - min-width: 30px - overflow: hidden - text-overflow: ellipsis - word-wrap: break-word - - .list-header-menu-icon - position: absolute - top: 0 - right: 0 - -.list-body - flex: 1 - display: flex - overflow-y: auto - padding: 5px 11px - - .minicards - flex: 1 - - form - margin-bottom: 9px - - .ps-scrollbar-y-rail - transform: translateX(2px) - - .open-minicard-composer - border-radius: 2px - color: #8c8c8c - display: block - padding: 7px 10px - position: relative - text-decoration: none - animation: fadeIn 0.3s - - i.fa - margin-right: 7px - - &:hover - background: #fafafa - color: #222 - box-shadow: 0 1px 2px rgba(0,0,0,.2) diff --git a/client/components/lists/menu.jade b/client/components/lists/menu.jade deleted file mode 100644 index 052f064c..00000000 --- a/client/components/lists/menu.jade +++ /dev/null @@ -1,29 +0,0 @@ -template(name="listActionPopup") - ul.pop-over-list - li: a.js-add-card {{_ 'add-card'}} - li: a.highlight-icon.js-list-subscribe {{_ 'subscribe'}} - if cards.count - hr - ul.pop-over-list - li: a.js-select-cards {{_ 'list-select-cards'}} - li: a.js-move-cards {{_ 'list-move-cards'}} - li: a.js-archive-cards {{_ 'list-archive-cards'}} - hr - ul.pop-over-list - li: a.js-close-list {{_ 'archive-list'}} - -template(name="listMoveCardsPopup") - +boardLists - -template(name="boardLists") - ul.pop-over-list - each currentBoard.lists - li - if($eq ../_id _id) - a.disabled {{title}} ({{_ 'current'}}) - else - a.js-select-list= title - -template(name="listArchiveCardsPopup") - p {{_ 'list-archive-cards-pop'}} - input.js-confirm.negate.full(type="submit" value="{{_ 'archive-all'}}") diff --git a/client/components/lists/menu.js b/client/components/lists/menu.js deleted file mode 100644 index dda1270c..00000000 --- a/client/components/lists/menu.js +++ /dev/null @@ -1,52 +0,0 @@ -Template.listActionPopup.events({ - 'click .js-add-card': function() { - var listDom = document.getElementById('js-list-' + this._id); - var listComponent = BlazeComponent.getComponentForElement(listDom); - listComponent.openForm({ position: 'top' }); - Popup.close(); - }, - 'click .js-list-subscribe': function() {}, - 'click .js-select-cards': function() { - var cardIds = Cards.find( - {listId: this._id}, - {fields: { _id: 1 }} - ).map(function(card) { return card._id; }); - MultiSelection.add(cardIds); - Popup.close(); - }, - 'click .js-move-cards': Popup.open('listMoveCards'), - 'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', function() { - Cards.find({listId: this._id}).forEach(function(card) { - Cards.update(card._id, { - $set: { - archived: true - } - }); - }); - Popup.close(); - }), - 'click .js-close-list': function(evt) { - evt.preventDefault(); - Lists.update(this._id, { - $set: { - archived: true - } - }); - Popup.close(); - } -}); - -Template.listMoveCardsPopup.events({ - 'click .js-select-list': function() { - var fromList = Template.parentData(2).data._id; - var toList = this._id; - Cards.find({listId: fromList}).forEach(function(card) { - Cards.update(card._id, { - $set: { - listId: toList - } - }); - }); - Popup.close(); - } -}); diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js new file mode 100644 index 00000000..1be44728 --- /dev/null +++ b/client/config/blazeHelpers.js @@ -0,0 +1,13 @@ +Blaze.registerHelper('currentBoard', function() { + var boardId = Session.get('currentBoard'); + if (boardId) { + return Boards.findOne(boardId); + } +}); + +Blaze.registerHelper('currentCard', function() { + var cardId = Session.get('currentCard'); + if (cardId) { + return Cards.findOne(cardId); + } +}); diff --git a/client/config/helpers.js b/client/config/helpers.js deleted file mode 100644 index 01a28f9b..00000000 --- a/client/config/helpers.js +++ /dev/null @@ -1,6 +0,0 @@ -Blaze.registerHelper('currentCard', function() { - var cardId = Session.get('currentCard'); - if (cardId) { - return Cards.findOne(cardId); - } -}); -- cgit v1.2.3-1-g7c22