diff options
author | Maxime Quandalle <maxime@quandalle.com> | 2015-05-12 19:20:58 +0200 |
---|---|---|
committer | Maxime Quandalle <maxime@quandalle.com> | 2015-05-12 19:33:50 +0200 |
commit | 2dbea30842ec63a68055245fe26633bb7913daf3 (patch) | |
tree | e9143893a3d3bf4ad34dd3a97d6f3466561c8756 /client/components/cards | |
download | wekan-2dbea30842ec63a68055245fe26633bb7913daf3.tar.gz wekan-2dbea30842ec63a68055245fe26633bb7913daf3.tar.bz2 wekan-2dbea30842ec63a68055245fe26633bb7913daf3.zip |
Renaissance
_,,ad8888888888bba,_
,ad88888I888888888888888ba,
,88888888I88888888888888888888a,
,d888888888I8888888888888888888888b,
d88888PP"""" ""YY88888888888888888888b,
,d88"'__,,--------,,,,.;ZZZY8888888888888,
,8IIl'" ;;l"ZZZIII8888888888,
,I88l;' ;lZZZZZ888III8888888,
,II88Zl;. ;llZZZZZ888888I888888,
,II888Zl;. .;;;;;lllZZZ888888I8888b
,II8888Z;; `;;;;;''llZZ8888888I8888,
II88888Z;' .;lZZZ8888888I888b
II88888Z; _,aaa, .,aaaaa,__.l;llZZZ88888888I888
II88888IZZZZZZZZZ, .ZZZZZZZZZZZZZZ;llZZ88888888I888,
II88888IZZ<'(@@>Z| |ZZZ<'(@@>ZZZZ;;llZZ888888888I88I
,II88888; `""" ;| |ZZ; `""" ;;llZ8888888888I888
II888888l `;; .;llZZ8888888888I888,
,II888888Z; ;;; .;;llZZZ8888888888I888I
III888888Zl; .., `;; ,;;lllZZZ88888888888I888
II88888888Z;;...;(_ _) ,;;;llZZZZ88888888888I888,
II88888888Zl;;;;;' `--'Z;. .,;;;;llZZZZ88888888888I888b
]I888888888Z;;;;' ";llllll;..;;;lllZZZZ88888888888I8888,
II888888888Zl.;;"Y88bd888P";;,..;lllZZZZZ88888888888I8888I
II8888888888Zl;.; `"PPP";;;,..;lllZZZZZZZ88888888888I88888
II888888888888Zl;;. `;;;l;;;;lllZZZZZZZZW88888888888I88888
`II8888888888888Zl;. ,;;lllZZZZZZZZWMZ88888888888I88888
II8888888888888888ZbaalllZZZZZZZZZWWMZZZ8888888888I888888,
`II88888888888888888b"WWZZZZZWWWMMZZZZZZI888888888I888888b
`II88888888888888888;ZZMMMMMMZZZZZZZZllI888888888I8888888
`II8888888888888888 `;lZZZZZZZZZZZlllll888888888I8888888,
II8888888888888888, `;lllZZZZllllll;;.Y88888888I8888888b,
,II8888888888888888b .;;lllllll;;;.;..88888888I88888888b,
II888888888888888PZI;. .`;;;.;;;..; ...88888888I8888888888,
II888888888888PZ;;';;. ;. .;. .;. .. Y8888888I88888888888b,
,II888888888PZ;;' `8888888I8888888888888b,
II888888888' 888888I8888888888888888
,II888888888 ,888888I8888888888888888
,d88888888888 d888888I8888888888ZZZZZZ
,ad888888888888I 8888888I8888ZZZZZZZZZZZZ
888888888888888' 888888IZZZZZZZZZZZZZZZZZ
8888888888P'8P' Y888ZZZZZZZZZZZZZZZZZZZZ
888888888, " ,ZZZZZZZZZZZZZZZZZZZZZZZ
8888888888, ,ZZZZZZZZZZZZZZZZZZZZZZZZZZ
888888888888a, _ ,ZZZZZZZZZZZZZZZZZZZZ88888888
888888888888888ba,_d' ,ZZZZZZZZZZZZZZZZZ8888888888888
8888888888888888888888bbbaaa,,,______,ZZZZZZZZZZZZZZZ88888888888888888
88888888888888888888888888888888888ZZZZZZZZZZZZZZZ88888888888888888888
8888888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888
888888888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888888888
8888888888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888
88888888888888888888888888888ZZZZZZZZZZZZZZ888888888888888888888888888
8888888888888888888888888888ZZZZZZZZZZZZZZ88888888888888888 Normand 8
88888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888 Veilleux 8
8888888888888888888888888ZZZZZZZZZZZZZZ8888888888888888888888888888888
Diffstat (limited to 'client/components/cards')
-rw-r--r-- | client/components/cards/details.jade | 47 | ||||
-rw-r--r-- | client/components/cards/details.js | 103 | ||||
-rw-r--r-- | client/components/cards/details.styl | 161 | ||||
-rw-r--r-- | client/components/cards/events.js | 285 | ||||
-rw-r--r-- | client/components/cards/helpers.js | 48 | ||||
-rw-r--r-- | client/components/cards/labels.styl | 183 | ||||
-rw-r--r-- | client/components/cards/minicard.styl | 136 | ||||
-rw-r--r-- | client/components/cards/popups.jade | 12 | ||||
-rw-r--r-- | client/components/cards/router.js | 15 | ||||
-rw-r--r-- | client/components/cards/templates.html | 336 |
10 files changed, 1326 insertions, 0 deletions
diff --git a/client/components/cards/details.jade b/client/components/cards/details.jade new file mode 100644 index 00000000..0de59297 --- /dev/null +++ b/client/components/cards/details.jade @@ -0,0 +1,47 @@ +template(name="cardSidebar") + .card-sidebar.sidebar + .card-detail.sidebar-content.js-card-sidebar-content + if cover + .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 + h2.card-detail-title.js-card-title= title + p.card-detail-list.js-move-card + | {{_ 'in-list'}} + a.card-detail-list-title( + class="{{#if currentUser.isBoardMember}}js-open-move-from-header is-editable{{/if}}") + = list.title + hr + //- if card.members + .card-detail-item.card-detail-item-members.clearfix.js-card-detail-members + h3.card-detail-item-header {{_ 'members'}} + .js-card-detail-members-list.clearfix + each members + +userAvatar(userId=this size="small" cardId=../_id) + a.card-detail-item-add-button.dark-hover.js-details-edit-members + i.fa.fa-plus + //- We should use "editable" to avoide repetiting ourselves + .clearfix + if currentUser.isBoardMember + h3 Description + +inlinedForm(classNames="js-card-description") + i.fa.fa-times.js-close-inlined-form + textarea(autofocus)= description + button(type="submit") {{_ 'edit'}} + else + .js-open-inlined-form + a {{_ 'edit'}} + +viewer + = description + else if description + h3 Description + +viewer + = description + hr + if attachments.count + +WindowAttachmentsModule(card=this) + +WindowActivityModule(card=this) + +template(name="moveCardPopup") + +boardLists diff --git a/client/components/cards/details.js b/client/components/cards/details.js new file mode 100644 index 00000000..a4fe89a3 --- /dev/null +++ b/client/components/cards/details.js @@ -0,0 +1,103 @@ +BlazeComponent.extendComponent({ + template: function() { + return 'cardSidebar'; + }, + + mixins: function() { + return [Mixins.InfiniteScrolling]; + }, + + calculateNextPeak: function() { + var altitude = this.find('.js-card-sidebar-content').scrollHeight; + this.callFirstWith(this, 'setNextPeak', altitude); + }, + + reachNextPeak: function() { + var activitiesComponent = this.componentChildren('activities')[0]; + activitiesComponent.loadNextPage(); + }, + + events: function() { + return [{ + 'click .js-move-card': Popup.open('moveCard'), + 'submit .js-card-description': function(evt) { + evt.preventDefault(); + var cardId = Session.get('currentCard'); + var form = this.componentChildren('inlinedForm')[0]; + var newDescription = form.getValue(); + Cards.update(cardId, { + $set: { + description: newDescription + } + }); + form.close(); + }, + 'click .js-close-card-detail': function() { + Utils.goBoardId(Session.get('currentBoard')); + }, + 'click .editable .js-card-title': function(event, t) { + var editable = t.$('.card-detail-title'); + + // add class editing and focus + $('.editing').removeClass('editing'); + editable.addClass('editing'); + editable.find('#title').focus(); + }, + 'click .js-edit-desc': function(event, t) { + var editable = t.$('.card-detail-item'); + + // editing remove based and add current editing. + $('.editing').removeClass('editing'); + editable.addClass('editing'); + editable.find('#desc').focus(); + + event.preventDefault(); + }, + 'click .js-cancel-edit': function(event, t) { + // remove editing hide. + $('.editing').removeClass('editing'); + }, + 'submit #WindowTitleEdit': function(event, t) { + var title = t.find('#title').value; + if ($.trim(title)) { + Cards.update(this.card._id, { + $set: { + title: title + } + }, function (err, res) { + if (!err) $('.editing').removeClass('editing'); + }); + } + + event.preventDefault(); + }, + 'submit #WindowDescEdit': function(event, t) { + Cards.update(this.card._id, { + $set: { + description: t.find('#desc').value + } + }, function(err) { + if (!err) $('.editing').removeClass('editing'); + }); + event.preventDefault(); + }, + 'click .member': Popup.open('cardMember'), + 'click .js-details-edit-members': Popup.open('cardMembers'), + 'click .js-details-edit-labels': Popup.open('cardLabels') + }]; + } +}).register('cardSidebar'); + +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 + } + }); + } +}); diff --git a/client/components/cards/details.styl b/client/components/cards/details.styl new file mode 100644 index 00000000..faf15d79 --- /dev/null +++ b/client/components/cards/details.styl @@ -0,0 +1,161 @@ +@import 'nib' + +.card-detail.sidebar-content + width: 496px - 2 * 20px + top: -46px !important + z-index: 20 !important + // XXX Animate apparition + + .card-detail-header + background: #F7F7F7 + border-bottom: 1px solid darken(white, 10%) + position: absolute + min-height: 38px + top: 0 + left: 0 + right: 0 + padding 7px 20px 0 + + i.fa + float: right + font-size: 1.3em + color: darken(white, 35%) + margin-top: 7px + + .card-detail-title + font-weight: bold + font-size: 1.7em + margin: 3px 0 0 + padding: 0 + + .card-detail-list + font-size: 0.85em + margin-bottom: 3px + + a.card-detail-list-title + font-weight: bold + + &.is-editable + display: inline-block + background: darken(white, 10%) + border-radius: 3px + padding: 0px 5px + +.new-comment + position: relative + margin: 0 0 20px 38px + + .member + opacity: .7 + position: absolute + top: 1px + left: -38px + + .helper + bottom: 0 + display: none + position: absolute + right: 9px + + &.focus + + .member + opacity: 1 + + .helper + display: inline-block + + .new-comment-input + min-height: 108px + color: #4d4d4d + cursor: auto + overflow: hidden + word-wrap: break-word + + .too-long + margin-top: 8px + +.new-comment-input + background-color: #fff + border: 0 + box-shadow: 0 1px 2px rgba(0, 0, 0, .23) + color: #8c8c8c + height: 36px + margin: 4px 4px 6px 0 + padding: 9px 11px + width: 100% + + &:hover, + &:focus + background-color: #fff + box-shadow: 0 1px 3px rgba(0, 0, 0, .33) + border: 0 + cursor: pointer + + &:focus + cursor: auto + +.list-voters.compact .voter + position: relative + min-height: 36px + + .member + left: 0 + position: absolute + top: 0 + + .title + display: block + line-height: 30px + left: 0 + overflow: hidden + padding-left: 38px + position: absolute + text-overflow: ellipsis + top: 0 + white-space: nowrap + width: 230px + +.list-voters .title + display: none + +.card-composer + padding-bottom: 8px + +.cc-controls + margin-top: 1px + + input[type="submit"] + float: left + margin-top: 0 + padding: 5px 18px + + .icon-lg + float: left + + .cc-opt + float: right + +.minicard-placeholder, +.minicard.placeholder + background: silver + border: none + min-height: 18px + + .hook + height: 18px + position: absolute + right: 0 + top: 0 + width: 18px + +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/events.js b/client/components/cards/events.js new file mode 100644 index 00000000..9c270e8d --- /dev/null +++ b/client/components/cards/events.js @@ -0,0 +1,285 @@ +// 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}}); + Popup.close(); + } +}); + +Template.WindowActivityModule.events({ + 'click .js-new-comment:not(.focus)': function(evt) { + var $this = $(evt.currentTarget); + $this.addClass('focus'); + }, + 'submit #CommentForm': function(evt, t) { + var text = t.$('.js-new-comment-input'); + if ($.trim(text.val())) { + CardComments.insert({ + boardId: this.card.boardId, + cardId: this.card._id, + text: text.val() + }); + text.val(''); + $('.focus').removeClass('focus'); + } + evt.preventDefault(); + } +}); + +Template.WindowSidebarModule.events({ + 'click .js-change-card-members': Popup.open('cardMembers'), + 'click .js-edit-labels': Popup.open('cardLabels'), + 'click .js-archive-card': function(evt) { + // Update + Cards.update(this.card._id, { + $set: { + archived: true + } + }); + evt.preventDefault(); + }, + 'click .js-unarchive-card': function(evt) { + Cards.update(this.card._id, { + $set: { + archived: false + } + }); + evt.preventDefault(); + }, + 'click .js-delete-card': Popup.afterConfirm('cardDelete', function() { + Cards.remove(this.card._id); + + // redirect board + Utils.goBoardId(this.card.board()._id); + Popup.close(); + }), + 'click .js-more-menu': Popup.open('cardMore'), + 'click .js-attach': Popup.open('cardAttachments') +}); + +Template.WindowAttachmentsModule.events({ + 'click .js-attach': Popup.open('cardAttachments'), + 'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', + function() { + Attachments.remove(this._id); + Popup.close(); + } + ), + // If we let this event bubble, Iron-Router will handle it and empty the + // page content, see #101. + 'click .js-open-viewer, click .js-download': function(event) { + event.stopPropagation(); + }, + 'click .js-add-cover': function() { + Cards.update(this.cardId, { $set: { coverId: this._id } }); + }, + 'click .js-remove-cover': function() { + Cards.update(this.cardId, { $unset: { coverId: '' } }); + } +}); + +Template.cardMembersPopup.events({ + 'click .js-select-member': function(evt) { + var cardId = Template.parentData(2).data._id; + var memberId = this.userId; + var operation; + if (Cards.find({ _id: cardId, members: memberId}).count() === 0) + operation = '$addToSet'; + else + operation = '$pull'; + + var query = {}; + query[operation] = { + members: memberId + }; + Cards.update(cardId, query); + evt.preventDefault(); + } +}); + +Template.cardLabelsPopup.events({ + 'click .js-select-label': function(evt) { + var cardId = Template.parentData(2).data._id; + var labelId = this._id; + var operation; + if (Cards.find({ _id: cardId, labelIds: labelId}).count() === 0) + operation = '$addToSet'; + else + operation = '$pull'; + + var query = {}; + query[operation] = { + labelIds: labelId + }; + Cards.update(cardId, query); + evt.preventDefault(); + }, + 'click .js-edit-label': Popup.open('editLabel'), + 'click .js-add-label': Popup.open('createLabel') +}); + +Template.formLabel.events({ + 'click .js-palette-color': function(evt) { + var $this = $(evt.currentTarget); + + // hide selected ll colors + $('.js-palette-select').addClass('hide'); + + // show select color + $this.find('.js-palette-select').removeClass('hide'); + } +}); + +Template.createLabelPopup.events({ + // Create the new label + 'submit .create-label': function(evt, tpl) { + var name = tpl.$('#labelName').val().trim(); + var boardId = Session.get('currentBoard'); + var selectLabelDom = tpl.$('.js-palette-select:not(.hide)').get(0); + var selectLabel = Blaze.getData(selectLabelDom); + Boards.update(boardId, { + $push: { + labels: { + _id: Random.id(6), + name: name, + color: selectLabel.color + } + } + }); + Popup.back(); + evt.preventDefault(); + } +}); + +Template.editLabelPopup.events({ + 'click .js-delete-label': Popup.afterConfirm('deleteLabel', function() { + var boardId = Session.get('currentBoard'); + Boards.update(boardId, { + $pull: { + labels: { + _id: this._id + } + } + }); + Popup.back(2); + }), + 'submit .edit-label': function(evt, tpl) { + var name = tpl.$('#labelName').val().trim(); + var boardId = Session.get('currentBoard'); + var getLabel = Utils.getLabelIndex(boardId, this._id); + var selectLabelDom = tpl.$('.js-palette-select:not(.hide)').get(0); + var selectLabel = Blaze.getData(selectLabelDom); + var $set = {}; + + // set label index + $set[getLabel.key('name')] = name; + + // set color + $set[getLabel.key('color')] = selectLabel.color; + + // update + Boards.update(boardId, { $set: $set }); + + // return to the previous popup view trigger + Popup.back(); + + evt.preventDefault(); + }, + 'click .js-select-label': function() { + Cards.remove(this.cardId); + + // redirect board + Utils.goBoardId(this.boardId); + } +}); + +Template.cardMorePopup.events({ + 'click .js-delete': Popup.afterConfirm('cardDelete', function() { + Cards.remove(this.card._id); + + // redirect board + Utils.goBoardId(this.card.board()._id); + }) +}); + +Template.cardAttachmentsPopup.events({ + 'change .js-attach-file': function(evt) { + var card = this.card; + FS.Utility.eachFile(evt, function(f) { + var file = new FS.File(f); + + // set Ids + file.boardId = card.boardId; + file.cardId = card._id; + + // upload file + Attachments.insert(file); + + Popup.close(); + }); + }, + 'click .js-computer-upload': function(evt, t) { + t.find('.js-attach-file').click(); + evt.preventDefault(); + } +}); diff --git a/client/components/cards/helpers.js b/client/components/cards/helpers.js new file mode 100644 index 00000000..708b1b56 --- /dev/null +++ b/client/components/cards/helpers.js @@ -0,0 +1,48 @@ +Template.cardMembersPopup.helpers({ + isCardMember: function() { + var cardId = Template.parentData()._id; + var cardMembers = Cards.findOne(cardId).members || []; + return _.contains(cardMembers, this.userId); + }, + user: function() { + return Users.findOne(this.userId); + } +}); + +Template.cardLabelsPopup.helpers({ + isLabelSelected: function(cardId) { + return _.contains(Cards.findOne(cardId).labelIds, this._id); + } +}); + +var labelColors; +Meteor.startup(function() { + labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; +}); + +Template.createLabelPopup.helpers({ + // This is the default color for a new label. We search the first color that + // is not already used in the board (although it's not a problem if two + // labels have the same color). + defaultColor: function() { + var labels = this.labels || this.card.board().labels; + var usedColors = _.pluck(labels, 'color'); + var availableColors = _.difference(labelColors, usedColors); + return availableColors.length > 1 ? availableColors[0] : 'green'; + } +}); + +Template.formLabel.helpers({ + labels: function() { + return _.map(labelColors, function(color) { + return { color: color, name: '' }; + }); + } +}); + +Blaze.registerHelper('currentCard', function() { + var cardId = Session.get('currentCard'); + if (cardId) { + return Cards.findOne(cardId); + } +}); diff --git a/client/components/cards/labels.styl b/client/components/cards/labels.styl new file mode 100644 index 00000000..27058b21 --- /dev/null +++ b/client/components/cards/labels.styl @@ -0,0 +1,183 @@ +@import 'nib' + +// XXX Use .board-widget-labels as a flexbox container +.card-label + background-color: #b3b3b3 + border-radius: 4px + color: white + display: inline-block + font-weight: 700 + font-size: 13px + margin-right: 4px + padding: 3px 8px + position:relative + max-width: 100% + min-width: 8px + overflow: ellipsis + height: 18px + + &:hover + color: white + +.card-label-green + background-color: #3cb500 + +.card-label-yellow + background-color: #fad900 + +.card-label-orange + background-color: #ff9f19 + +.card-label-red + background-color: #eb4646 + +.card-label-purple + background-color: #a632db + +.card-label-blue + background-color: #0079bf + +.card-label-pink + background-color: #ff78cb + +.card-label-sky + background-color: #00c2e0 + +.card-label-black + background-color: #4d4d4d + +.card-label-lime + background-color: #51e898 + +.edit-label, +.create-label + .card-label + float: left + height: 25px + margin: 0px 3% 7px 0px + width: 10.5% + cursor: pointer + +.edit-labels + input[type="text"] + margin: 4px 0 6px 38px + width: 243px + + .card-label + height: 30px + left: 0 + padding: 1px 5px + position: absolute + top: 0 + width: 24px + + .labels-static .card-label + line-height: 30px + margin-bottom: 4px + position: relative + top: auto + left: 0 + width: 260px + +.minicard-labels + position: relative + z-index: 30 + top: -6px + + .card-label + border-radius: 0 + float: left + height: 4px + margin-bottom: 1px + padding: 0 + width: 40px + line-height: 100px + +.card-detail-item-labels .card-label + border-radius: 3px + display: block + float: left + height: 20px + line-height: 20px + margin: 0 4px 4px 0 + min-width: 30px + padding: 5px 10px + width: auto + +.editable-labels .card-label:hover + cursor: pointer + opacity: .75 + +.edit-labels-pop-over + margin-bottom: 8px + +.edit-labels-pop-over .shortcut + display: inline-block + +.card-label-selectable + border-radius: 3px + cursor: pointer + margin: 0 50px 4px 0 + min-height: 18px + padding: 8px + position: relative + transition: margin-right .1s + + .card-label-selectable-icon + position: absolute + top: 8px + right: -20px + + &.active:hover, + &.active, + &.active.selected:hover, + &.active.selected + margin-right: 38px + padding-right: 32px + + .card-label-selectable-icon + right: 6px + + &.active:hover:hover, + &.active:hover, + &.active.selected:hover:hover, + &.active.selected:hover + margin-right: 38px + + &.selected, + &:hover + margin-right: 38px + opacity: .8 + +.active .card-label-selectable + &, + &:hover + margin-right: 0 + + .card-label-selectable-icon + right: 8px + +.card-label-edit-button + border-radius: 3px + float: right + padding: 8px + + &:hover + background: #dbdbdb + +.card-label-color-select-icon + left: 14px + position: absolute + top: 9px + +.phenom .card-label + display: inline-block + font-size: 12px + height: 14px + line-height: 13px + padding: 0 4px + min-width: 16px + overflow: ellipsis + +.board-widget .phenom .card-label + max-width: 130px diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl new file mode 100644 index 00000000..a78cd46f --- /dev/null +++ b/client/components/cards/minicard.styl @@ -0,0 +1,136 @@ +.minicard + background-color: #fff + box-shadow: 0 1px 2px rgba(0,0,0,.2) + border-radius: 2px + cursor: pointer + margin-bottom: 9px + max-width: 300px + min-height: 20px + position: relative + z-index: 0 + overflow: hidden + + a + color: #4d4d4d + + &.active-card + background-color: #f0f0f0 + border-bottom-color: #c2c2c2 + + .minicard-operation + display: block + + &.draggable-hover-card + background-color: #f0f0f0 + border-bottom-color: #c2c2c2 + + .minicard-cover + background-position: center + background-repeat: no-repeat + background-size: cover + height: 145px + user-select: none + margin: -6px -8px 6px -8px + border-radius: top 2px + + &.no-preview-size + background-size: auto + background-position: center + + .minicard-details + padding: 6px 8px 2px + position: relative + z-index: 10 + + + &.is-selected + .minicard-details + padding-bottom: 0 + + a.minicard-details + text-decoration:none + + .minicard-details-overlay + background: transparent + bottom: 0 + left: 0 + position: absolute + right: 0 + top: 0 + + .minicard-dropzone + display: none + + .minicard.drophover .minicard-dropzone + background: rgba(255, 255, 255, .8) + // border-radius: 3px + // bottom: 0 + // display: block + // font-weight: 700 + // line-height: 100% + // left: 0 + // margin: 0 + // opacity: 1 + // padding: 0 + // position: absolute + // right: 0 + // text-align: center + // top: 0 + // z-index: 40 + + .minicard-title + display: block + font-weight: 400 + margin: 0 0 4px + overflow: hidden + text-decoration: none + word-wrap: break-word + + &::selection + background: transparent + + .minicard-labels + padding-top: 3px + margin-top: 4px + float: right + + .minicard-label + float: right + width: 8px + height: @width + border-radius: 2px + margin-left: 4px + + .minicard-members + float: right + margin: 2px -8px -2px 0 + + .member + float: right + border-radius: 50% + height: 28px + width: @height + + + .badges + margin-top: 10px + + .minicard-members:empty + display: none + +.badges + float: left + + &:empty + display: none + +textarea.minicard-composer-textarea, +textarea.minicard-composer-textarea:focus + background: none + border: none + box-shadow: none + height: auto + margin-bottom: 4px + padding: 0 + max-height: 162px + min-height: 54px + overflow-y: auto diff --git a/client/components/cards/popups.jade b/client/components/cards/popups.jade new file mode 100644 index 00000000..0b5aa4c0 --- /dev/null +++ b/client/components/cards/popups.jade @@ -0,0 +1,12 @@ +template(name="cardMembersPopup") + //- input.js-search-mem(autofocus placeholder="Search members…" type="text") + ul.pop-over-member-list.checkable.js-mem-list + each board.members + li.item.js-member-item(class="{{#if isCardMember}}active{{/if}}") + a.name.js-select-member(href="#") + +userAvatar(user=user size="small") + span.full-name + = user.profile.name + | (<span class="username">{{ user.username }}</span>) + if isCardMember + i.fa.fa-check diff --git a/client/components/cards/router.js b/client/components/cards/router.js new file mode 100644 index 00000000..48bb9a95 --- /dev/null +++ b/client/components/cards/router.js @@ -0,0 +1,15 @@ +Router.route('/boards/:boardId/:slug/:cardId', { + name: 'Card', + template: 'board', + waitOn: function() { + var params = this.params; + // XXX We probably shouldn't rely on Session + Session.set('currentBoard', params.boardId); + Session.set('currentCard', params.cardId); + + return BoardSubsManager.subscribe('board', params.boardId, params.slug); + }, + data: function() { + return Boards.findOne(this.params.boardId); + } +}); diff --git a/client/components/cards/templates.html b/client/components/cards/templates.html new file mode 100644 index 00000000..4c65e429 --- /dev/null +++ b/client/components/cards/templates.html @@ -0,0 +1,336 @@ +<template name="cardModal"> + {{ > modal template='cardDetailWindow' card=this board=this.board }} +</template> + +<template name="cardMemberPopup"> + <div class="board-member-menu"> + <div class="mini-profile-info"> + {{> userAvatar user=user }} + <div class="info"> + <h3 class="bottom" style="margin-right: 40px;"> + <a class="js-profile" href="{{ pathFor route='Profile' username=user.username }}">{{ user.profile.name }}</a> + </h3> + <p class="quiet bottom">@{{ user.username }}</p> + </div> + </div> + {{# if currentUser.isBoardMember }} + <ul class="pop-over-list"> + <li><a class="js-remove-member">{{_ 'remove-member-from-card'}}</a></li> + </ul> + {{/ if }} + </div> +</template> + +<template name="cardMorePopup"> + <p class="quiet bottom"> + <span class="clearfix"> + <span>{{_ 'link-card'}}</span> + <span class="icon-sm fa {{#if card.board.isPublic}}fa-globe{{else}}fa-lock{{/if}}"></span> + <input class="js-url js-autoselect inline-input" type="text" readonly="readonly" value="{{ card.rootUrl }}"> + </span> + {{_ 'added'}} <span class="date" title="{{ card.createdAt }}">{{ moment card.createdAt 'LLL' }}</span> - + <a class="js-delete" href="#" title="{{_ 'card-delete-notice'}}">{{_ 'delete'}}</a> + </p> +</template> + +<template name="cardLabelsPopup"> + <div> + {{! <input id="labelSearch" name="search" class="js-autofocus js-label-search" placeholder="Search labels…" value="" type="text"> }} + <ul class="edit-labels-pop-over js-labels-list"> + {{# each card.board.labels }} + <li> + <a href="#" class="card-label-edit-button icon-sm fa fa-pencil js-edit-label"></a> + <span class="card-label card-label-selectable card-label-{{color}} js-select-label {{# if isLabelSelected ../card._id }}active{{/ if }}"> + {{name}} + {{# if currentUser.isBoardAdmin }} + <span class="card-label-selectable-icon icon-sm fa fa-check light"></span> + {{/ if }} + </span> + </li> + {{/ each}} + </ul> + <a class="quiet-button full js-add-label">{{_ 'label-create'}}</a> + </div> +</template> + +<template name="cardAttachmentsPopup"> + <div> + <ul class="pop-over-list"> + <li> + <input type="file" name="file" class="js-attach-file hide" multiple> + <a class="js-computer-upload" href="#"> + {{_ 'computer'}} + </a> + </li> + </ul> + </div> +</template> + +<template name="formLabel"> + <div class="colors clearfix"> + <label for="labelName">{{_ 'name'}}</label> + <input id="labelName" type="text" name="name" class="js-label-name" value='{{ name }}' autofocus> + <label>{{_ "select-color"}}</label> + {{# each labels }} + <span class="card-label card-label--selectable card-label-{{ color }} palette-color js-palette-color"> + <span class="card-label-color-select-icon icon-sm fa fa-check light js-palette-select {{#if $neq color ../color}}hide{{/if}}"></span> + </span> + {{/each}} + </div> +</template> + +<template name="createLabelPopup"> + <form class="create-label"> + {{#with color=defaultColor}} + {{> formLabel}} + {{/with}} + <input type="submit" class="primary wide left" value="{{_ 'create'}}"> + </form> +</template> + +<template name="editLabelPopup"> + <form class="edit-label"> + {{> formLabel}} + <input type="submit" class="primary wide left" value="{{_ 'save'}}"> + <span class="right"> + <input type="submit" value="{{_ 'delete'}}" class="negate js-delete-label"> + </span> + </form> +</template> + +<template name="deleteLabelPopup"> + <p>{{_ "label-delete-pop"}}</p> + <input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> +</template> + +<template name="cardDeletePopup"> + <p>{{_ "card-delete-pop"}}</p> + <input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> +</template> + +<template name="attachmentDeletePopup"> + <p>{{_ "attachment-delete-pop"}}</p> + <input type="submit" class="js-confirm negate full" value="{{_ 'delete'}}"> +</template> + +<template name="cardDetailSidebarOld"> + <div class="card-detail-window clearfix"> + {{# if card.cover }} + <div class="window-cover js-card-cover-box js-open-card-cover-in-viewer has-cover" style="background-image: url({{ card.cover.url }}); background-color: rgb(119, 119, 119); background-size: contain;"> + </div> + {{ /if }} + {{ #if card.archived }} + <div class="window-archive-banner js-archive-banner"> + <span class="icon-lg fa fa-archive window-archive-banner-icon"></span> + <p class="window-archive-banner-text">{{_ "card-archived"}}</p> + </div> + {{ /if }} + <div class="window-header clearfix"> + <span class="window-header-icon icon-lg fa fa-calendar-o"></span> + <div class="window-title card-detail-title non-empty inline {{# if currentUser.isBoardMember }}editable{{/ if }}"> + <h2 class="window-title-text current hide-on-edit js-card-title">{{ card.title }}</h2> + <div class="edit edit-heavy"> + <form id="WindowTitleEdit"> + <textarea type="text" class="field single-line" id="title">{{ card.title }}</textarea> + <div class="edit-controls clearfix"> + <input type="submit" class="primary confirm js-title-save-edit" value="{{_ 'save'}}"> + <a href="#" class="icon-lg fa fa-times dark-hover cancel js-cancel-edit"></a> + </div> + </form> + </div> + <div class="quiet hide-on-edit window-header-inline-content js-current-list"> + <p class="inline-block bottom"> + {{_ 'in-list'}} + <a href="#" class="{{# if currentUser.isBoardMember }}js-open-move-from-header{{else}}disabled{{/ if }}"><strong>{{ card.list.title }}</strong></a> + </p> + </div> + </div> + </div> + <div class="window-main-col clearfix"> + <div class="card-detail-data gutter clearfix"> + <div class="card-detail-item card-detail-item-block clear clearfix editable"> + {{# if card.members }} + <div class="card-detail-item card-detail-item-members clearfix js-card-detail-members"> + <h3 class="card-detail-item-header">{{_ 'members'}}</h3> + <div class="js-card-detail-members-list clearfix"> + {{# each card.members }} + {{> userAvatar userId=this size="small" cardId=../card._id }} + {{/ each }} + <a class="card-detail-item-add-button dark-hover js-details-edit-members"> + <span class="icon-sm fa fa-plus"></span> + </a> + </div> + </div> + {{/ if }} + {{# if card.labels }} + <div class="card-detail-item card-detail-item-labels clearfix js-card-detail-labels"> + <h3 class="card-detail-item-header">{{_ 'labels'}}</h3> + <div class="js-card-detail-labels-list clearfix editable-labels js-edit-label"> + {{# each card.labels }} + <span class="card-label card-label-{{color}}" title="{{name}}">{{ name }}</span> + {{/ each }} + <a class="card-detail-item-add-button dark-hover js-details-edit-labels"> + <span class="icon-sm fa fa-plus"></span> + </a> + </div> + </div> + {{/ if }} + <div class="card-detail-item card-detail-item-block clear clearfix editable" attr="desc"> + {{# if card.description }} + <h3 class="card-detail-item-header js-show-with-desc">{{_ 'description'}}</h3> + {{# if currentUser.isBoardMember }} + <a href="#" class="card-detail-item-header-edit hide-on-edit js-show-with-desc js-edit-desc">{{_ 'edit'}}</a> + {{/ if }} + <div class="current markeddown hide-on-edit js-card-desc js-show-with-desc"> + {{#viewer}}{{ card.description }}{{/viewer}} + </div> + {{ else }} + {{# if currentUser.isBoardMember }} + <p class="bottom"> + <a href="#" class="hide-on-edit quiet-button w-img js-edit-desc js-hide-with-desc"> + <span class="icon-sm fa fa-align-left"></span> + {{_ 'edit-description'}} + </a> + </p> + {{/ if }} + {{/ if }} + <div class="card-detail-edit edit"> + <form id="WindowDescEdit"> + {{#editor class="field single-line2" id="desc"}}{{ card.description }}{{/editor}} + <div class="edit-controls clearfix"> + <input type="submit" class="primary confirm js-title-save-edit" value="{{_ 'save'}}"> + <a href="#" class="icon-lg fa fa-times dark-hover cancel js-cancel-edit"></a> + </div> + </form> + </div> + </div> + </div> + </div> + {{# if card.attachments.count }} + {{ > WindowAttachmentsModule card=card }} + {{/ if}} + {{ > WindowActivityModule card=card }} + </div> + {{# if currentUser.isBoardMember }} + {{ > WindowSidebarModule card=card }} + {{/if}} + </div> +</template> + +<template name="WindowActivityModule"> + <div class="card-detailwindow-module"> + <div class="window-module-title window-module-title-no-divider"> + <span class="window-module-title-icon icon-lg fa fa-comments-o"></span> + <h3>{{ _ 'activity'}}</h3> + </div> + {{# if currentUser.isBoardMember }} + <div class="new-comment js-new-comment"> + {{> userAvatar user=currentUser size="small" class="member-no-menu" }} + <form id="CommentForm"> + {{#editor class="new-comment-input js-new-comment-input"}}{{/editor}} + <div class="add-controls clearfix"> + <input type="submit" class="primary confirm clear js-add-comment" value="{{_ 'comment'}}" tabindex="2"> + </div> + </form> + </div> + {{/ if }} + {{ > activities mode="card" }} + </div> +</template> + +<template name="WindowAttachmentsModule"> + <div class="window-module js-attachments-section clearfix"> + <div class="window-module-title window-module-title-no-divider"> + <span class="window-module-title-icon icon-lg fa fa-paperclip"></span> + <h3 class="inline-block">{{_ 'attachments'}}</h3> + </div> + <div class="gutter"> + <div class="clearfix js-attachment-list"> + {{# each card.attachments }} + <div class="attachment-thumbnail"> + {{# if isUploaded }} + <a href="{{ url download=true }}" class="attachment-thumbnail-preview js-open-viewer attachment-thumbnail-preview-is-cover"> + {{# if isImage }} + <img src="{{ url }}"> + {{ else }} + <span class="attachment-thumbnail-preview-ext">{{ extension }}</span> + {{ /if }} + </a> + <p class="attachment-thumbnail-details js-open-viewer"> + <a href="" class="attachment-thumbnail-details-title js-attachment-thumbnail-details"> + {{ name }} + <span class="block quiet"> + {{_ 'added'}} <span class="date">{{ moment uploadedAt }}</span> + </span> + </a> + <span class="quiet attachment-thumbnail-details-options"> + <a href="{{ url download=true }}" class="attachment-thumbnail-details-options-item dark-hover js-download"> + <span class="icon-sm fa fa-download"></span> + <span class="attachment-thumbnail-details-options-item-text">{{_ 'download'}}</span> + </a> + {{# if isImage }} + <a class="attachment-thumbnail-details-options-item dark-hover {{#if $eq ../card.coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}"> + <span class="icon-sm fa fa-thumb-tack"></span> + <span class="attachment-thumbnail-details-options-item-text">{{#if $eq ../card.coverId _id}}{{_ 'remove-cover'}}{{else}}{{_ 'add-cover'}}{{/if}}</span> + </a> + {{/if}} + <a href="#" class="attachment-thumbnail-details-options-item attachment-thumbnail-details-options-item-delete dark-hover js-confirm-delete"> + <span class="icon-sm fa fa-close"></span> + <span class="attachment-thumbnail-details-options-item-text">{{_ 'delete'}}</span> + </a> + </span> + </p> + {{ else }} + +spinner + {{/ if }} + </div> + {{/each}} + </div> + <p> + <a href="#" class="quiet-button js-attach">{{_ 'add-attachment' }}</a> + </p> + </div> + </div> +</template> + +<template name="WindowSidebarModule"> + <div class="window-sidebar" style="position: relative;"> + <div class="window-module clearfix"> + <h3>{{_ 'add'}}</h3> + <div class="clearfix"> + <a href="#" class="button-link js-change-card-members" title="{{_ 'members-title'}}"> + <span class="icon-sm fa fa-user"></span> {{_ 'members'}} + </a> + <a href="#" class="button-link js-edit-labels" title="{{_ 'labels-title'}}"> + <span class="icon-sm fa fa-tags"></span> {{_ 'labels'}} + </a> + <a href="#" class="button-link js-attach" title="{{_ 'attachment-title'}}"> + <span class="icon-sm fa fa-paperclip"></span> {{_ 'attachment'}} + </a> + </div> + </div> + <div class="window-module other-actions clearfix"> + <h3>{{_ 'actions'}}</h3> + <div class="clearfix"> + <hr> + {{ #if card.archived }} + <a href="#" class="button-link js-unarchive-card" title="{{_ 'send-to-board-title'}}"> + <span class="icon-sm fa fa-recycle"></span> {{_ 'send-to-board'}} + </a> + <a href="#" class="button-link negate js-delete-card" title="{{_ 'delete-title'}}"> + <span class="icon-sm fa fa-trash-o"></span> {{_ 'delete'}} + </a> + {{ else }} + <a href="#" class="button-link js-archive-card" title="{{_ 'archive-title'}}"> + <span class="icon-sm fa fa-archive"></span> {{_ 'archive'}} + </a> + {{ /if }} + </div> + </div> + <div class="window-module clearfix"> + <p class="quiet bottom"> + <a href="#" class="quiet-button js-more-menu" title="{{_ 'share-and-more-title'}}">{{_ 'share-and-more'}}</a> + </p> + </div> + </div> +</template> |