diff options
Diffstat (limited to 'client/components')
29 files changed, 565 insertions, 165 deletions
diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index f1414e44..6633a91a 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -8,16 +8,24 @@ BlazeComponent.extendComponent({ const sidebar = this.parentComponent(); // XXX for some reason not working sidebar.callFirstWith(null, 'resetNextPeak'); this.autorun(() => { - const mode = this.data().mode; + let mode = this.data().mode; const capitalizedMode = Utils.capitalize(mode); - const id = Session.get(`current${capitalizedMode}`); + let thisId, searchId; + if (mode === 'linkedcard' || mode === 'linkedboard') { + thisId = Session.get('currentCard'); + searchId = Cards.findOne({_id: thisId}).linkedId; + mode = mode.replace('linked', ''); + } else { + thisId = Session.get(`current${capitalizedMode}`); + searchId = thisId; + } const limit = this.page.get() * activitiesPerPage; const user = Meteor.user(); const hideSystem = user ? user.hasHiddenSystemMessages() : false; - if (id === null) + if (searchId === null) return; - this.subscribe('activities', mode, id, limit, hideSystem, () => { + this.subscribe('activities', mode, searchId, limit, hideSystem, () => { this.loadNextPageLocked = false; // If the sibear peak hasn't increased, that mean that there are no more diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index 9b6aedd6..34b6402c 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -21,11 +21,18 @@ BlazeComponent.extendComponent({ 'submit .js-new-comment-form'(evt) { const input = this.getInput(); const text = input.val().trim(); + const card = this.currentData(); + let boardId = card.boardId; + let cardId = card._id; + if (card.isLinkedCard()) { + boardId = Cards.findOne(card.linkedId).boardId; + cardId = card.linkedId; + } if (text) { CardComments.insert({ text, - boardId: this.currentData().boardId, - cardId: this.currentData()._id, + boardId, + cardId, }); resetCommentInput(input); Tracker.flush(); diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index dbebdd70..8f4d5434 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -37,8 +37,7 @@ BlazeComponent.extendComponent({ const currentBoard = Boards.findOne(Session.get('currentBoard')); Boards.remove(currentBoard._id); } - const board = this.currentData(); - Boards.remove(board._id); + Boards.remove(this._id); FlowRouter.go('home'); }), }]; diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index bc7d3979..5cac930d 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -57,8 +57,13 @@ Template.cardAttachmentsPopup.events({ const card = this; FS.Utility.eachFile(evt, (f) => { const file = new FS.File(f); - file.boardId = card.boardId; - file.cardId = card._id; + if (card.isLinkedCard()) { + file.boardId = Cards.findOne(card.linkedId).boardId; + file.cardId = card.linkedId; + } else { + file.boardId = card.boardId; + file.cardId = card._id; + } file.userId = Meteor.userId(); const attachment = Attachments.insert(file); diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index b0f2baa3..182705d5 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -96,7 +96,7 @@ Template.dateBadge.helpers({ (class extends DatePicker { onCreated() { super.onCreated(); - this.data().receivedAt && this.date.set(moment(this.data().receivedAt)); + this.data().getReceived() && this.date.set(moment(this.data().getReceived())); } _storeDate(date) { @@ -104,7 +104,7 @@ Template.dateBadge.helpers({ } _deleteDate() { - this.card.unsetReceived(); + this.card.setReceived(null); } }).register('editCardReceivedDatePopup'); @@ -113,13 +113,13 @@ Template.dateBadge.helpers({ (class extends DatePicker { onCreated() { super.onCreated(); - this.data().startAt && this.date.set(moment(this.data().startAt)); + this.data().getStart() && this.date.set(moment(this.data().getStart())); } onRendered() { super.onRendered(); - if (moment.isDate(this.card.receivedAt)) { - this.$('.js-datepicker').datepicker('setStartDate', this.card.receivedAt); + if (moment.isDate(this.card.getReceived())) { + this.$('.js-datepicker').datepicker('setStartDate', this.card.getReceived()); } } @@ -128,7 +128,7 @@ Template.dateBadge.helpers({ } _deleteDate() { - this.card.unsetStart(); + this.card.setStart(null); } }).register('editCardStartDatePopup'); @@ -136,13 +136,13 @@ Template.dateBadge.helpers({ (class extends DatePicker { onCreated() { super.onCreated(); - this.data().dueAt && this.date.set(moment(this.data().dueAt)); + this.data().getDue() && this.date.set(moment(this.data().getDue())); } onRendered() { super.onRendered(); - if (moment.isDate(this.card.startAt)) { - this.$('.js-datepicker').datepicker('setStartDate', this.card.startAt); + if (moment.isDate(this.card.getStart())) { + this.$('.js-datepicker').datepicker('setStartDate', this.card.getStart()); } } @@ -151,7 +151,7 @@ Template.dateBadge.helpers({ } _deleteDate() { - this.card.unsetDue(); + this.card.setDue(null); } }).register('editCardDueDatePopup'); @@ -159,13 +159,13 @@ Template.dateBadge.helpers({ (class extends DatePicker { onCreated() { super.onCreated(); - this.data().endAt && this.date.set(moment(this.data().endAt)); + this.data().getEnd() && this.date.set(moment(this.data().getEnd())); } onRendered() { super.onRendered(); - if (moment.isDate(this.card.startAt)) { - this.$('.js-datepicker').datepicker('setStartDate', this.card.startAt); + if (moment.isDate(this.card.getStart())) { + this.$('.js-datepicker').datepicker('setStartDate', this.card.getStart()); } } @@ -174,7 +174,7 @@ Template.dateBadge.helpers({ } _deleteDate() { - this.card.unsetEnd(); + this.card.setEnd(null); } }).register('editCardEndDatePopup'); @@ -213,19 +213,23 @@ class CardReceivedDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().receivedAt)); + self.date.set(moment(self.data().getReceived())); }); } classes() { let classes = 'received-date '; - const dueAt = this.data().dueAt; - if (dueAt) { - if (this.date.get().isBefore(this.now.get(), 'minute') && - this.now.get().isBefore(dueAt)) { - classes += 'current'; - } - } + const dueAt = this.data().getDue(); + const endAt = this.data().getEnd(); + const startAt = this.data().getStart(); + const theDate = this.date.get(); + // if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged + if (((startAt) && (theDate.isAfter(dueAt))) || + ((endAt) && (theDate.isAfter(endAt))) || + ((dueAt) && (theDate.isAfter(dueAt)))) + classes += 'long-overdue'; + else + classes += 'current'; return classes; } @@ -246,19 +250,24 @@ class CardStartDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().startAt)); + self.date.set(moment(self.data().getStart())); }); } classes() { let classes = 'start-date' + ' '; - const dueAt = this.data().dueAt; - if (dueAt) { - if (this.date.get().isBefore(this.now.get(), 'minute') && - this.now.get().isBefore(dueAt)) { - classes += 'current'; - } - } + const dueAt = this.data().getDue(); + const endAt = this.data().getEnd(); + const theDate = this.date.get(); + const now = this.now.get(); + // if dueAt or endAt exist & are > startAt, startAt doesn't need to be flagged + if (((endAt) && (theDate.isAfter(endAt))) || + ((dueAt) && (theDate.isAfter(dueAt)))) + classes += 'long-overdue'; + else if (theDate.isBefore(now, 'minute')) + classes += 'almost-due'; + else + classes += 'current'; return classes; } @@ -279,24 +288,21 @@ class CardDueDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().dueAt)); + self.date.set(moment(self.data().getDue())); }); } classes() { let classes = 'due-date' + ' '; - - // if endAt exists & is < dueAt, dueAt doesn't need to be flagged - const endAt = this.data().endAt; + const endAt = this.data().getEnd(); const theDate = this.date.get(); const now = this.now.get(); - - if ((endAt !== 0) && - (endAt !== null) && - (endAt !== '') && - (endAt !== undefined) && - (theDate.isBefore(endAt))) + // if the due date is after the end date, green - done early + if ((endAt) && (theDate.isAfter(endAt))) classes += 'current'; + // if there is an end date, don't need to flag the due date + else if (endAt) + classes += ''; else if (now.diff(theDate, 'days') >= 2) classes += 'long-overdue'; else if (now.diff(theDate, 'minute') >= 0) @@ -323,22 +329,20 @@ class CardEndDate extends CardDate { super.onCreated(); const self = this; self.autorun(() => { - self.date.set(moment(self.data().endAt)); + self.date.set(moment(self.data().getEnd())); }); } classes() { let classes = 'end-date' + ' '; - const dueAt = this.data.dueAt; - if (dueAt) { - const diff = dueAt.diff(this.date.get(), 'days'); - if (diff >= 2) - classes += 'long-overdue'; - else if (diff > 0) - classes += 'due'; - else if (diff <= 0) - classes += 'current'; - } + const dueAt = this.data().getDue(); + const theDate = this.date.get(); + if (theDate.diff(dueAt, 'days') >= 2) + classes += 'long-overdue'; + else if (theDate.diff(dueAt, 'days') >= 0) + classes += 'due'; + else if (theDate.diff(dueAt, 'days') >= -2) + classes += 'almost-due'; return classes; } diff --git a/client/components/cards/cardDate.styl b/client/components/cards/cardDate.styl index 9775e82b..62cfdcd9 100644 --- a/client/components/cards/cardDate.styl +++ b/client/components/cards/cardDate.styl @@ -43,12 +43,12 @@ &.start-date time &::before - content: "\f08b" // symbol: fa-sign-out + content: "\f251" // symbol: fa-hourglass-start &.received-date time &::before - content: "\f251" // symbol: fa-hourglass-start + content: "\f08b" // symbol: fa-sign-out time &::before diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index aaad7c7c..33d6d3b7 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -10,7 +10,7 @@ template(name="cardDetails") h2.card-details-title.js-card-title( class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") +viewer - = title + = getTitle if isWatching i.fa.fa-eye.card-details-watch .card-details-path @@ -19,43 +19,54 @@ template(name="cardDetails") a.js-parent-card(href=linkForCard) {{title}} // else {{_ 'top-level-card'}} + if isLinkedCard + h3.linked-card-location + +viewer + | {{getBoardTitle}} > {{getTitle}} - if archived - p.warning {{_ 'card-archived'}} + if getArchived + if isLinkedBoard + p.warning {{_ 'board-archived'}} + else + p.warning {{_ 'card-archived'}} .card-details-items .card-details-item.card-details-item-received h3.card-details-item-title {{_ 'card-received'}} - if receivedAt + if getReceived +cardReceivedDate else - a.js-received-date {{_ 'add'}} + if canModifyCard + a.js-received-date {{_ 'add'}} .card-details-item.card-details-item-start h3.card-details-item-title {{_ 'card-start'}} - if startAt + if getStart +cardStartDate else - a.js-start-date {{_ 'add'}} + if canModifyCard + a.js-start-date {{_ 'add'}} .card-details-item.card-details-item-due h3.card-details-item-title {{_ 'card-due'}} - if dueAt + if getDue +cardDueDate else - a.js-due-date {{_ 'add'}} + if canModifyCard + a.js-due-date {{_ 'add'}} .card-details-item.card-details-item-end h3.card-details-item-title {{_ 'card-end'}} - if endAt + if getEnd +cardEndDate else - a.js-end-date {{_ 'add'}} + if canModifyCard + a.js-end-date {{_ 'add'}} .card-details-items .card-details-item.card-details-item-members h3.card-details-item-title {{_ 'members'}} - each members + each getMembers +userAvatar(userId=this cardId=../_id) | {{! XXX Hack to hide syntaxic coloration /// }} if canModifyCard @@ -79,9 +90,9 @@ template(name="cardDetails") +cardCustomField .card-details-items - if spentTime + if getSpentTime .card-details-item.card-details-item-spent - if isOvertime + if getIsOvertime h3.card-details-item-title {{_ 'overtime-hours'}} else h3.card-details-item-title {{_ 'spent-time-hours'}} @@ -92,15 +103,15 @@ template(name="cardDetails") h3.card-details-item-title {{_ 'description'}} +inlinedCardDescription(classNames="card-description js-card-description") +editor(autofocus=true) - | {{getUnsavedValue 'cardDescription' _id description}} + | {{getUnsavedValue 'cardDescription' _id getDescription}} .edit-controls.clearfix button.primary(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form else a.js-open-inlined-form - if description + if getDescription +viewer - = description + = getDescription else | {{_ 'edit'}} if (hasUnsavedValue 'cardDescription' _id) @@ -109,10 +120,10 @@ template(name="cardDetails") a.js-open-inlined-form {{_ 'view-it'}} = ' - ' a.js-close-inlined-form {{_ 'discard'}} - else if description + else if getDescription h3.card-details-item-title {{_ 'description'}} +viewer - = description + = getDescription .card-details-items .card-details-item.card-details-item-name @@ -122,14 +133,14 @@ template(name="cardDetails") +editCardRequesterForm else a.js-open-inlined-form - if requestedBy + if getRequestedBy +viewer - = requestedBy + = getRequestedBy else | {{_ 'add'}} - else if requestedBy + else if getRequestedBy +viewer - = requestedBy + = getRequestedBy .card-details-item.card-details-item-name h3.card-details-item-title {{_ 'assigned-by'}} @@ -138,14 +149,14 @@ template(name="cardDetails") +editCardAssignerForm else a.js-open-inlined-form - if assignedBy + if getAssignedBy +viewer - = assignedBy + = getAssignedBy else | {{_ 'add'}} - else if requestedBy + else if getRequestedBy +viewer - = assignedBy + = getAssignedBy hr +checklists(cardId = _id) @@ -162,36 +173,44 @@ template(name="cardDetails") +attachmentsGalery hr - .activity-title - h3 {{ _ 'activity'}} - if currentUser.isBoardMember - .material-toggle-switch - span.toggle-switch-title {{_ 'hide-system-messages'}} - if hiddenSystemMessages - input.toggle-switch(type="checkbox" id="toggleButton" checked="checked") - else - input.toggle-switch(type="checkbox" id="toggleButton") - label.toggle-label(for="toggleButton") + unless currentUser.isNoComments + .activity-title + h3 {{ _ 'activity'}} + if currentUser.isBoardMember + .material-toggle-switch + span.toggle-switch-title {{_ 'hide-system-messages'}} + if hiddenSystemMessages + input.toggle-switch(type="checkbox" id="toggleButton" checked="checked") + else + input.toggle-switch(type="checkbox" id="toggleButton") + label.toggle-label(for="toggleButton") if currentUser.isBoardMember - +commentForm - if isLoaded.get - +activities(card=this mode="card") + unless currentUser.isNoComments + +commentForm + unless currentUser.isNoComments + if isLoaded.get + if isLinkedCard + +activities(card=this mode="linkedcard") + else if isLinkedBoard + +activities(card=this mode="linkedboard") + else + +activities(card=this mode="card") template(name="editCardTitleForm") textarea.js-edit-card-title(rows='1' autofocus) - = title + = getTitle .edit-controls.clearfix button.primary.confirm.js-submit-edit-card-title-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form template(name="editCardRequesterForm") - input.js-edit-card-requester(type='text' autofocus value=requestedBy) + input.js-edit-card-requester(type='text' autofocus value=getRequestedBy) .edit-controls.clearfix button.primary.confirm.js-submit-edit-card-requester-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form template(name="editCardAssignerForm") - input.js-edit-card-assigner(type='text' autofocus value=assignedBy) + input.js-edit-card-assigner(type='text' autofocus value=getAssignedBy) .edit-controls.clearfix button.primary.confirm.js-submit-edit-card-assigner-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form @@ -230,7 +249,7 @@ template(name="moveCardPopup") template(name="copyCardPopup") label(for='copy-card-title') {{_ 'title'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) - = title + = getTitle +boardsAndLists template(name="copyChecklistToManyCardsPopup") diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b41bfc17..2cd399c1 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -274,7 +274,7 @@ BlazeComponent.extendComponent({ close(isReset = false) { if (this.isOpen.get() && !isReset) { const draft = this.getValue().trim(); - if (draft !== Cards.findOne(Session.get('currentCard')).description) { + if (draft !== Cards.findOne(Session.get('currentCard')).getDescription()) { UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue()); } } diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 11660593..1e290305 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -5,7 +5,8 @@ flex-shrink: 0 flex-basis: 470px will-change: flex-basis - overflow: hidden + overflow-y: scroll + overflow-x: hidden background: darken(white, 3%) border-radius: bottom 3px z-index: 20 !important @@ -46,6 +47,12 @@ margin: 7px 0 0 padding: 0 + .linked-card-location + font-style: italic + font-size: 1em + margin-bottom: 0 + & p + margin-bottom: 0 form.inlined-form margin-top: 5px diff --git a/client/components/cards/cardTime.jade b/client/components/cards/cardTime.jade index dcfc92f0..8af8c414 100644 --- a/client/components/cards/cardTime.jade +++ b/client/components/cards/cardTime.jade @@ -3,10 +3,10 @@ template(name="editCardSpentTime") form.edit-time .fields label(for="time") {{_ 'time'}} - input.js-time-field#time(type="number" step="0.01" name="time" value="{{card.spentTime}}" placeholder=timeFormat autofocus) + input.js-time-field#time(type="number" step="0.01" name="time" value="{{card.getSpentTime}}" placeholder=timeFormat autofocus) label(for="overtime") {{_ 'overtime'}} a.js-toggle-overtime - .materialCheckBox#overtime(class="{{#if card.isOvertime}}is-checked{{/if}}" name="overtime") + .materialCheckBox#overtime(class="{{#if getIsOvertime}}is-checked{{/if}}" name="overtime") if error.get .warning {{_ error.get}} @@ -15,8 +15,8 @@ template(name="editCardSpentTime") template(name="timeBadge") if canModifyCard - a.js-edit-time.card-time(title="{{showTitle}}" class="{{#if isOvertime}}card-label-red{{else}}card-label-green{{/if}}") + a.js-edit-time.card-time(title="{{showTitle}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}") | {{showTime}} else - a.card-time(title="{{showTitle}}" class="{{#if isOvertime}}card-label-red{{else}}card-label-green{{/if}}") + a.card-time(title="{{showTitle}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}") | {{showTime}} diff --git a/client/components/cards/cardTime.js b/client/components/cards/cardTime.js index eadcc88e..80b7fc84 100644 --- a/client/components/cards/cardTime.js +++ b/client/components/cards/cardTime.js @@ -7,17 +7,17 @@ BlazeComponent.extendComponent({ this.card = this.data(); }, toggleOvertime() { - this.card.isOvertime = !this.card.isOvertime; + this.card.setIsOvertime(!this.card.getIsOvertime()); $('#overtime .materialCheckBox').toggleClass('is-checked'); $('#overtime').toggleClass('is-checked'); }, storeTime(spentTime, isOvertime) { this.card.setSpentTime(spentTime); - this.card.setOvertime(isOvertime); + this.card.setIsOvertime(isOvertime); }, deleteTime() { - this.card.unsetSpentTime(); + this.card.setSpentTime(null); }, events() { return [{ @@ -26,7 +26,7 @@ BlazeComponent.extendComponent({ evt.preventDefault(); const spentTime = parseFloat(evt.target.time.value); - const isOvertime = this.card.isOvertime; + const isOvertime = this.card.getIsOvertime(); if (spentTime >= 0) { this.storeTime(spentTime, isOvertime); @@ -55,17 +55,14 @@ BlazeComponent.extendComponent({ self.time = ReactiveVar(); }, showTitle() { - if (this.data().isOvertime) { - return `${TAPi18n.__('overtime')} ${this.data().spentTime} ${TAPi18n.__('hours')}`; + if (this.data().getIsOvertime()) { + return `${TAPi18n.__('overtime')} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`; } else { - return `${TAPi18n.__('card-spent')} ${this.data().spentTime} ${TAPi18n.__('hours')}`; + return `${TAPi18n.__('card-spent')} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`; } }, showTime() { - return this.data().spentTime; - }, - isOvertime() { - return this.data().isOvertime; + return this.data().getSpentTime(); }, events() { return [{ diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index e014abba..5d789351 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -74,8 +74,10 @@ BlazeComponent.extendComponent({ event.preventDefault(); const textarea = this.find('textarea.js-add-checklist-item'); const title = textarea.value.trim(); - const cardId = this.currentData().cardId; + let cardId = this.currentData().cardId; const card = Cards.findOne(cardId); + if (card.isLinked()) + cardId = card.linkedId; if (title) { Checklists.insert({ diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 3f7e0940..5c609802 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -1,5 +1,7 @@ template(name="minicard") - .minicard + .minicard( + class="{{#if isLinkedCard}}linked-card{{/if}}" + class="{{#if isLinkedBoard}}linked-board{{/if}}") if cover .minicard-cover(style="background-image: url('{{cover.url}}');") if labels @@ -13,8 +15,16 @@ template(name="minicard") if $eq 'prefix-with-parent' currentBoard.presentParentTask .parent-prefix | {{ parentCardName }} + if isLinkedBoard + a.js-linked-link + span.linked-icon.fa.fa-folder + else if isLinkedCard + a.js-linked-link + span.linked-icon.fa.fa-id-card + if getArchived + span.linked-icon.linked-archived.fa.fa-archive +viewer - | {{ title }} + = getTitle if $eq 'subtext-with-full-path' currentBoard.presentParentTask .parent-subtext | {{ parentString ' > ' }} @@ -23,23 +33,19 @@ template(name="minicard") | {{ parentCardName }} .dates - if receivedAt - unless startAt - unless dueAt - unless endAt + if getReceived + unless getStart + unless getDue + unless getEnd .date +minicardReceivedDate - if startAt + if getStart .date +minicardStartDate - if dueAt - unless endAt - .date - +minicardDueDate - if endAt + if getDue .date - +minicardEndDate - if spentTime + +minicardDueDate + if getSpentTime .date +cardSpentTime @@ -53,18 +59,19 @@ template(name="minicard") +viewer = trueValue - if members + if getMembers .minicard-members.js-minicard-members - each members + each getMembers +userAvatar(userId=this) .badges - if comments.count - .badge(title="{{_ 'card-comments-title' comments.count }}") - span.badge-icon.fa.fa-comment-o.badge-comment - span.badge-text= comments.count - if description - .badge.badge-state-image-only(title=description) + unless currentUser.isNoComments + if comments.count + .badge(title="{{_ 'card-comments-title' comments.count }}") + span.badge-icon.fa.fa-comment-o.badge-comment + span.badge-text= comments.count + if getDescription + .badge.badge-state-image-only(title=getDescription) span.badge-icon.fa.fa-align-left if attachments.count .badge diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index a98b5730..da7f9e01 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -6,4 +6,15 @@ BlazeComponent.extendComponent({ template() { return 'minicard'; }, + + events() { + return [{ + 'click .js-linked-link' () { + if (this.data().isLinkedCard()) + Utils.goCardId(this.data().linkedId); + else if (this.data().isLinkedBoard()) + Utils.goBoardId(this.data().linkedId); + }, + }]; + }, }).register('minicard'); diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 5624787c..8fec7238 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -44,6 +44,16 @@ transition: transform 0.2s, border-radius 0.2s + &.linked-board + &.linked-card + .linked-icon + display: inline-block + margin-right: 11px + vertical-align: baseline + font-size: 0.9em + .linked-archived + color: #937760 + .is-selected & transform: translateX(11px) border-bottom-right-radius: 0 @@ -87,6 +97,8 @@ .minicard-title p:last-child margin-bottom: 0 + .viewer + display: inline-block .dates display: flex; flex-direction: row; diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js index 9c6f265e..1651d449 100644 --- a/client/components/cards/subtasks.js +++ b/client/components/cards/subtasks.js @@ -29,6 +29,7 @@ BlazeComponent.extendComponent({ boardId: targetBoard._id, sort: sortIndex, swimlaneId, + type: 'cardType-card', }); // In case the filter is active we need to add the newly inserted card in diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 4fff1e02..892a6e74 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -226,9 +226,12 @@ textarea .edit-controls, .add-controls + display: flex + align-items: baseline margin-top: 0 button[type=submit] + input[type=button] float: left height: 32px margin-top: -2px diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 267af31c..00908faa 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -47,6 +47,7 @@ BlazeComponent.extendComponent({ items: itemsSelector, placeholder: 'minicard-wrapper placeholder', start(evt, ui) { + ui.helper.css('z-index', 1000); ui.placeholder.height(ui.helper.height()); EscapeActions.executeUpTo('popup-close'); boardComponent.setIsDragging(true); diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl index fa32ff6d..72cb19f4 100644 --- a/client/components/lists/list.styl +++ b/client/components/lists/list.styl @@ -187,3 +187,14 @@ padding: 7px top: -@padding right: 17px + +.link-board-wrapper + display: flex + align-items: baseline + + .js-link-board + margin-left: 15px + +.search-card-results + max-height: 250px + overflow: hidden diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index 32c6b278..f2b3e941 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -34,8 +34,60 @@ template(name="addCardForm") .add-controls.clearfix button.primary.confirm(type="submit") {{_ 'add'}} - a.fa.fa-times-thin.js-close-inlined-form + span.quiet + | {{_ 'or'}} + a.js-link {{_ 'link'}} + span.quiet + | + | / + a.js-search {{_ 'search'}} template(name="autocompleteLabelLine") .minicard-label(class="card-label-{{colorName}}" title=labelName) span(class="{{#if hasNoName}}quiet{{/if}}")= labelName + +template(name="linkCardPopup") + label {{_ 'boards'}}: + .link-board-wrapper + select.js-select-boards + each boards + if $eq _id currentBoard._id + option(value="{{_id}}" selected) {{_ 'current'}} + else + option(value="{{_id}}") {{title}} + input.primary.confirm.js-link-board(type="button" value="{{_ 'link'}}") + + label {{_ 'swimlanes'}}: + select.js-select-swimlanes + each swimlanes + option(value="{{_id}}") {{title}} + + label {{_ 'lists'}}: + select.js-select-lists + each lists + option(value="{{_id}}") {{title}} + + label {{_ 'cards'}}: + select.js-select-cards + each cards + option(value="{{getId}}") {{getTitle}} + + .edit-controls.clearfix + input.primary.confirm.js-done(type="button" value="{{_ 'link'}}") + +template(name="searchCardPopup") + label {{_ 'boards'}}: + .link-board-wrapper + select.js-select-boards + each boards + if $eq _id currentBoard._id + option(value="{{_id}}" selected) {{_ 'current'}} + else + option(value="{{_id}}") {{title}} + form.js-search-term-form + input(type="text" name="searchTerm" placeholder="{{_ 'search-example'}}" autofocus) + .list-body.js-perfect-scrollbar.search-card-results + .minicards.clearfix.js-minicards + each results + a.minicard-wrapper.js-minicard + +minicard(this) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index b93b7e67..ce8396b9 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,3 +1,5 @@ +const subManager = new SubsManager(); + BlazeComponent.extendComponent({ mixins() { return [Mixins.PerfectScrollbar]; @@ -55,6 +57,7 @@ BlazeComponent.extendComponent({ boardId: boardId._id, sort: sortIndex, swimlaneId, + type: 'cardType-card', }); // 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 @@ -199,6 +202,8 @@ BlazeComponent.extendComponent({ events() { return [{ keydown: this.pressKey, + 'click .js-link': Popup.open('linkCard'), + 'click .js-search': Popup.open('searchCard'), }]; }, @@ -270,3 +275,226 @@ BlazeComponent.extendComponent({ }); }, }).register('addCardForm'); + +BlazeComponent.extendComponent({ + onCreated() { + // Prefetch first non-current board id + const boardId = Boards.findOne({ + archived: false, + 'members.userId': Meteor.userId(), + _id: {$ne: Session.get('currentBoard')}, + }, { + sort: ['title'], + })._id; + // Subscribe to this board + subManager.subscribe('board', boardId); + this.selectedBoardId = new ReactiveVar(boardId); + this.selectedSwimlaneId = new ReactiveVar(''); + this.selectedListId = new ReactiveVar(''); + + this.boardId = Session.get('currentBoard'); + // In order to get current board info + subManager.subscribe('board', this.boardId); + this.board = Boards.findOne(this.boardId); + // List where to insert card + const list = $(Popup._getTopStack().openerElement).closest('.js-list'); + this.listId = Blaze.getData(list[0])._id; + // Swimlane where to insert card + const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane'); + this.swimlaneId = ''; + const boardView = Meteor.user().profile.boardView; + if (boardView === 'board-view-swimlanes') + this.swimlaneId = Blaze.getData(swimlane[0])._id; + else if (boardView === 'board-view-lists') + this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id; + }, + + boards() { + const boards = Boards.find({ + archived: false, + 'members.userId': Meteor.userId(), + _id: {$ne: Session.get('currentBoard')}, + }, { + sort: ['title'], + }); + return boards; + }, + + swimlanes() { + if (!this.selectedBoardId) { + return []; + } + const swimlanes = Swimlanes.find({boardId: this.selectedBoardId.get()}); + if (swimlanes.count()) + this.selectedSwimlaneId.set(swimlanes.fetch()[0]._id); + return swimlanes; + }, + + lists() { + if (!this.selectedBoardId) { + return []; + } + const lists = Lists.find({boardId: this.selectedBoardId.get()}); + if (lists.count()) + this.selectedListId.set(lists.fetch()[0]._id); + return lists; + }, + + cards() { + if (!this.board) { + return []; + } + const ownCardsIds = this.board.cards().map((card) => { return card.linkedId || card._id; }); + return Cards.find({ + boardId: this.selectedBoardId.get(), + swimlaneId: this.selectedSwimlaneId.get(), + listId: this.selectedListId.get(), + archived: false, + linkedId: {$nin: ownCardsIds}, + _id: {$nin: ownCardsIds}, + }); + }, + + events() { + return [{ + 'change .js-select-boards'(evt) { + subManager.subscribe('board', $(evt.currentTarget).val()); + this.selectedBoardId.set($(evt.currentTarget).val()); + }, + 'change .js-select-swimlanes'(evt) { + this.selectedSwimlaneId.set($(evt.currentTarget).val()); + }, + 'change .js-select-lists'(evt) { + this.selectedListId.set($(evt.currentTarget).val()); + }, + 'click .js-done' (evt) { + // LINK CARD + evt.stopPropagation(); + evt.preventDefault(); + const linkedId = $('.js-select-cards option:selected').val(); + if (!linkedId) { + Popup.close(); + return; + } + const _id = Cards.insert({ + title: $('.js-select-cards option:selected').text(), //dummy + listId: this.listId, + swimlaneId: this.swimlaneId, + boardId: this.boardId, + sort: Lists.findOne(this.listId).cards().count(), + type: 'cardType-linkedCard', + linkedId, + }); + Filter.addException(_id); + Popup.close(); + }, + 'click .js-link-board' (evt) { + //LINK BOARD + evt.stopPropagation(); + evt.preventDefault(); + const impBoardId = $('.js-select-boards option:selected').val(); + if (!impBoardId || Cards.findOne({linkedId: impBoardId, archived: false})) { + Popup.close(); + return; + } + const _id = Cards.insert({ + title: $('.js-select-boards option:selected').text(), //dummy + listId: this.listId, + swimlaneId: this.swimlaneId, + boardId: this.boardId, + sort: Lists.findOne(this.listId).cards().count(), + type: 'cardType-linkedBoard', + linkedId: impBoardId, + }); + Filter.addException(_id); + Popup.close(); + }, + }]; + }, +}).register('linkCardPopup'); + +BlazeComponent.extendComponent({ + mixins() { + return [Mixins.PerfectScrollbar]; + }, + + onCreated() { + // Prefetch first non-current board id + let board = Boards.findOne({ + archived: false, + 'members.userId': Meteor.userId(), + _id: {$ne: Session.get('currentBoard')}, + }); + if (!board) { + Popup.close(); + return; + } + const boardId = board._id; + // Subscribe to this board + subManager.subscribe('board', boardId); + this.selectedBoardId = new ReactiveVar(boardId); + + this.boardId = Session.get('currentBoard'); + // In order to get current board info + subManager.subscribe('board', this.boardId); + board = Boards.findOne(this.boardId); + // List where to insert card + const list = $(Popup._getTopStack().openerElement).closest('.js-list'); + this.listId = Blaze.getData(list[0])._id; + // Swimlane where to insert card + const swimlane = $(Popup._getTopStack().openerElement).closest('.js-swimlane'); + this.swimlaneId = ''; + if (board.view === 'board-view-swimlanes') + this.swimlaneId = Blaze.getData(swimlane[0])._id; + else + this.swimlaneId = Swimlanes.findOne({boardId: this.boardId})._id; + this.term = new ReactiveVar(''); + }, + + boards() { + const boards = Boards.find({ + archived: false, + 'members.userId': Meteor.userId(), + _id: {$ne: Session.get('currentBoard')}, + }, { + sort: ['title'], + }); + return boards; + }, + + results() { + if (!this.selectedBoardId) { + return []; + } + const board = Boards.findOne(this.selectedBoardId.get()); + return board.searchCards(this.term.get(), false); + }, + + events() { + return [{ + 'change .js-select-boards'(evt) { + subManager.subscribe('board', $(evt.currentTarget).val()); + this.selectedBoardId.set($(evt.currentTarget).val()); + }, + 'submit .js-search-term-form'(evt) { + evt.preventDefault(); + this.term.set(evt.target.searchTerm.value); + }, + 'click .js-minicard'(evt) { + // LINK CARD + const card = Blaze.getData(evt.currentTarget); + const _id = Cards.insert({ + title: card.title, //dummy + listId: this.listId, + swimlaneId: this.swimlaneId, + boardId: this.boardId, + sort: Lists.findOne(this.listId).cards().count(), + type: 'cardType-linkedCard', + linkedId: card.linkedId || card._id, + }); + Filter.addException(_id); + Popup.close(); + }, + }]; + }, +}).register('searchCardPopup'); diff --git a/client/components/main/header.jade b/client/components/main/header.jade index dd071b3e..2751c0cc 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -66,7 +66,8 @@ template(name="header") .announcement p i.fa.fa-bullhorn - | #{announcement} + +viewer + | #{announcement} i.fa.fa-times-circle.js-close-announcement template(name="offlineWarning") diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade index ff2d8d0a..ac7da3af 100644 --- a/client/components/main/layouts.jade +++ b/client/components/main/layouts.jade @@ -9,6 +9,7 @@ head packages. link(rel="shortcut icon" href="/wekan-favicon.png") link(rel="apple-touch-icon" href="/wekan-favicon.png") + link(rel="mask-icon" href="/wekan-150.svg") link(rel="manifest" href="/wekan-manifest.json") template(name="userFormsLayout") diff --git a/client/components/settings/invitationCode.js b/client/components/settings/invitationCode.js index a403d8ab..fa355179 100644 --- a/client/components/settings/invitationCode.js +++ b/client/components/settings/invitationCode.js @@ -1,6 +1,13 @@ -Template.invitationCode.onRendered(() => { - const setting = Settings.findOne(); - if (!setting || !setting.disableRegistration) { - $('#invitationcode').hide(); - } +Template.invitationCode.onRendered(function() { + Meteor.subscribe('setting', { + onReady() { + const setting = Settings.findOne(); + + if (!setting || !setting.disableRegistration) { + $('#invitationcode').hide(); + } + + return this.stop(); + }, + }); }); diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade index 5bc7972d..dcf71f4d 100644 --- a/client/components/settings/settingBody.jade +++ b/client/components/settings/settingBody.jade @@ -72,7 +72,7 @@ template(name='email') li.smtp-form .title {{_ 'smtp-password'}} .form-group - input.form-control#mail-server-password(type="text", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}") + input.form-control#mail-server-password(type="text", placeholder="{{_ 'password'}}" value="") li.smtp-form .title {{_ 'smtp-tls'}} .form-group diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 6085c2ad..ec88ce7e 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -23,10 +23,11 @@ template(name='homeSidebar') hr +labelsWidget hr - h3 - i.fa.fa-comments-o - | {{_ 'activities'}} - +activities(mode="board") + unless currentUser.isNoComments + h3 + i.fa.fa-comments-o + | {{_ 'activities'}} + +activities(mode="board") template(name="membersWidget") .board-widget.board-widget-members @@ -146,6 +147,12 @@ template(name="changePermissionsPopup") i.fa.fa-check span.sub-name {{_ 'normal-desc'}} li + a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}") + | {{_ 'no-comments'}} + if isNoComments + i.fa.fa-check + span.sub-name {{_ 'no-comments-desc'}} + li a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-only{{/if}}") | {{_ 'comment-only'}} if isCommentOnly diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 5a9de74b..5d34c4a8 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -126,8 +126,11 @@ Template.memberPopup.helpers({ if(type === 'normal'){ const currentBoard = Boards.findOne(Session.get('currentBoard')); const commentOnly = currentBoard.hasCommentOnly(this.userId); + const noComments = currentBoard.hasNoComments(this.userId); if(commentOnly){ return TAPi18n.__('comment-only').toLowerCase(); + } else if(noComments) { + return TAPi18n.__('no-comments').toLowerCase(); } else { return TAPi18n.__(type).toLowerCase(); } @@ -324,12 +327,13 @@ BlazeComponent.extendComponent({ }).register('addMemberPopup'); Template.changePermissionsPopup.events({ - 'click .js-set-admin, click .js-set-normal, click .js-set-comment-only'(event) { + 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only'(event) { const currentBoard = Boards.findOne(Session.get('currentBoard')); const memberId = this.userId; const isAdmin = $(event.currentTarget).hasClass('js-set-admin'); const isCommentOnly = $(event.currentTarget).hasClass('js-set-comment-only'); - currentBoard.setMemberPermission(memberId, isAdmin, isCommentOnly); + const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments'); + currentBoard.setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly); Popup.back(1); }, }); @@ -342,7 +346,12 @@ Template.changePermissionsPopup.helpers({ isNormal() { const currentBoard = Boards.findOne(Session.get('currentBoard')); - return !currentBoard.hasAdmin(this.userId) && !currentBoard.hasCommentOnly(this.userId); + return !currentBoard.hasAdmin(this.userId) && !currentBoard.hasNoComments(this.userId) && !currentBoard.hasCommentOnly(this.userId); + }, + + isNoComments() { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + return !currentBoard.hasAdmin(this.userId) && currentBoard.hasNoComments(this.userId); }, isCommentOnly() { diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade index 83e2c8d0..ebfa48ba 100644 --- a/client/components/users/userAvatar.jade +++ b/client/components/users/userAvatar.jade @@ -1,7 +1,7 @@ template(name="userAvatar") a.member.js-member(title="{{userData.profile.fullname}} ({{userData.username}})") if userData.profile.avatarUrl - img.avatar.avatar-image(src=userData.profile.avatarUrl) + img.avatar.avatar-image(src="{{userData.profile.avatarUrl}}") else +userAvatarInitials(userId=userData._id) diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js index be7a85d2..91cad237 100644 --- a/client/components/users/userAvatar.js +++ b/client/components/users/userAvatar.js @@ -134,8 +134,9 @@ BlazeComponent.extendComponent({ Template.cardMembersPopup.helpers({ isCardMember() { - const cardId = Template.parentData()._id; - const cardMembers = Cards.findOne(cardId).members || []; + const card = Template.parentData(); + const cardMembers = card.getMembers(); + return _.contains(cardMembers, this.userId); }, |