summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2016-11-29 03:34:49 +0200
committerGitHub <noreply@github.com>2016-11-29 03:34:49 +0200
commitb540eef8558c9c5969ed085be4ce8e4d03ac093f (patch)
tree405da27ca659e65ba2fa8c94b6b0abaa3b568667
parent96e5e8d0585fe781518ea85734e12cfe46e9583f (diff)
parentde3be14d014f900358b118d91a1d90d849c6382c (diff)
downloadwekan-b540eef8558c9c5969ed085be4ce8e4d03ac093f.tar.gz
wekan-b540eef8558c9c5969ed085be4ce8e4d03ac093f.tar.bz2
wekan-b540eef8558c9c5969ed085be4ce8e4d03ac093f.zip
Merge pull request #26 from shoetten/duedate
Start- and Duedate for cards
-rw-r--r--.meteor/packages1
-rw-r--r--.meteor/versions1
-rw-r--r--client/components/cards/cardDate.jade20
-rw-r--r--client/components/cards/cardDate.js228
-rw-r--r--client/components/cards/cardDate.styl58
-rw-r--r--client/components/cards/cardDetails.jade13
-rw-r--r--client/components/cards/cardDetails.js2
-rw-r--r--client/components/cards/cardDetails.styl7
-rw-r--r--client/components/cards/minicard.jade6
-rw-r--r--client/components/cards/minicard.styl5
-rwxr-xr-xi18n/en.i18n.json9
-rw-r--r--models/cards.js24
12 files changed, 372 insertions, 2 deletions
diff --git a/.meteor/packages b/.meteor/packages
index e57bdb19..32d7389c 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -75,3 +75,4 @@ seriousm:emoji-continued
templates:tabs
verron:autosize
simple:json-routes
+rajit:bootstrap3-datepicker
diff --git a/.meteor/versions b/.meteor/versions
index e1c0b9cf..4ca2f780 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -121,6 +121,7 @@ perak:markdown@1.0.5
promise@0.7.3
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
+rajit:bootstrap3-datepicker@1.5.1
random@1.0.10
rate-limit@1.0.5
reactive-dict@1.1.8
diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade
new file mode 100644
index 00000000..a2a28bbd
--- /dev/null
+++ b/client/components/cards/cardDate.jade
@@ -0,0 +1,20 @@
+template(name="editCardDate")
+ .edit-card-date
+ form.edit-date
+ .fields
+ .left
+ label(for="date") {{_ 'date'}}
+ input.js-date-field#date(type="text" name="date" value=showDate placeholder=dateFormat autofocus)
+ .right
+ label(for="time") {{_ 'time'}}
+ input.js-time-field#time(type="text" name="time" value=showTime placeholder=timeFormat)
+ .js-datepicker
+ if error.get
+ .warning {{_ error.get}}
+ button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
+ button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
+
+template(name="dateBadge")
+ a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
+ time(datetime="{{showISODate}}")
+ | {{showDate}}
diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js
new file mode 100644
index 00000000..4d129e8e
--- /dev/null
+++ b/client/components/cards/cardDate.js
@@ -0,0 +1,228 @@
+// Edit start & due dates
+const EditCardDate = BlazeComponent.extendComponent({
+ template() {
+ return 'editCardDate';
+ },
+
+ onCreated() {
+ this.error = new ReactiveVar('');
+ this.card = this.data();
+ this.date = new ReactiveVar(moment.invalid());
+ },
+
+ onRendered() {
+ const $picker = this.$('.js-datepicker').datepicker({
+ todayHighlight: true,
+ todayBtn: 'linked',
+ language: TAPi18n.getLanguage(),
+ }).on('changeDate', function(evt) {
+ this.find('#date').value = moment(evt.date).format('L');
+ this.error.set('');
+ this.find('#time').focus();
+ }.bind(this));
+
+ if (this.date.get().isValid()) {
+ $picker.datepicker('update', this.date.get().toDate());
+ }
+ },
+
+ showDate() {
+ if (this.date.get().isValid())
+ return this.date.get().format('L');
+ return '';
+ },
+ showTime() {
+ if (this.date.get().isValid())
+ return this.date.get().format('LT');
+ return '';
+ },
+ dateFormat() {
+ return moment.localeData().longDateFormat('L');
+ },
+ timeFormat() {
+ return moment.localeData().longDateFormat('LT');
+ },
+
+ events() {
+ return [{
+ 'keyup .js-date-field'() {
+ // parse for localized date format in strict mode
+ const dateMoment = moment(this.find('#date').value, 'L', true);
+ if (dateMoment.isValid()) {
+ this.error.set('');
+ this.$('.js-datepicker').datepicker('update', dateMoment.toDate());
+ }
+ },
+ 'keyup .js-time-field'() {
+ // parse for localized time format in strict mode
+ const dateMoment = moment(this.find('#time').value, 'LT', true);
+ if (dateMoment.isValid()) {
+ this.error.set('');
+ }
+ },
+ 'submit .edit-date'(evt) {
+ evt.preventDefault();
+
+ // if no time was given, init with 12:00
+ const time = evt.target.time.value || moment(new Date().setHours(12, 0, 0)).format('LT');
+
+ const dateString = `${evt.target.date.value} ${time}`;
+ const newDate = moment(dateString, 'L LT', true);
+ if (newDate.isValid()) {
+ this._storeDate(newDate.toDate());
+ Popup.close();
+ }
+ else {
+ this.error.set('invalid-date');
+ evt.target.date.focus();
+ }
+ },
+ 'click .js-delete-date'(evt) {
+ evt.preventDefault();
+ this._deleteDate();
+ Popup.close();
+ },
+ }];
+ },
+});
+
+// editCardStartDatePopup
+(class extends EditCardDate {
+ onCreated() {
+ super.onCreated();
+ this.data().startAt && this.date.set(moment(this.data().startAt));
+ }
+
+ _storeDate(date) {
+ this.card.setStart(date);
+ }
+
+ _deleteDate() {
+ this.card.unsetStart();
+ }
+}).register('editCardStartDatePopup');
+
+// editCardDueDatePopup
+(class extends EditCardDate {
+ onCreated() {
+ super.onCreated();
+ this.data().dueAt && this.date.set(moment(this.data().dueAt));
+ }
+
+ onRendered() {
+ super.onRendered();
+ if (moment.isDate(this.card.startAt)) {
+ this.$('.js-datepicker').datepicker('setStartDate', this.card.startAt);
+ }
+ }
+
+ _storeDate(date) {
+ this.card.setDue(date);
+ }
+
+ _deleteDate() {
+ this.card.unsetDue();
+ }
+}).register('editCardDueDatePopup');
+
+
+// Display start & due dates
+const CardDate = BlazeComponent.extendComponent({
+ template() {
+ return 'dateBadge';
+ },
+
+ onCreated() {
+ const self = this;
+ self.date = ReactiveVar();
+ self.now = ReactiveVar(moment());
+ window.setInterval(() => {
+ self.now.set(moment());
+ }, 60000);
+ },
+
+ showDate() {
+ // this will start working once mquandalle:moment
+ // is updated to at least moment.js 2.10.5
+ // until then, the date is displayed in the "L" format
+ return this.date.get().calendar(null, {
+ sameElse: 'llll',
+ });
+ },
+
+ showISODate() {
+ return this.date.get().toISOString();
+ },
+});
+
+class CardStartDate extends CardDate {
+ onCreated() {
+ super.onCreated();
+ const self = this;
+ self.autorun(() => {
+ self.date.set(moment(self.data().startAt));
+ });
+ }
+
+ classes() {
+ if (this.date.get().isBefore(this.now.get(), 'minute') &&
+ this.now.get().isBefore(this.data().dueAt)) {
+ return 'current';
+ }
+ return '';
+ }
+
+ showTitle() {
+ return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
+ }
+
+ events() {
+ return super.events().concat({
+ 'click .js-edit-date': Popup.open('editCardStartDate'),
+ });
+ }
+}
+CardStartDate.register('cardStartDate');
+
+class CardDueDate extends CardDate {
+ onCreated() {
+ super.onCreated();
+ const self = this;
+ self.autorun(() => {
+ self.date.set(moment(self.data().dueAt));
+ });
+ }
+
+ classes() {
+ if (this.now.get().diff(this.date.get(), 'days') >= 2)
+ return 'long-overdue';
+ else if (this.now.get().diff(this.date.get(), 'minute') >= 0)
+ return 'due';
+ else if (this.now.get().diff(this.date.get(), 'days') >= -1)
+ return 'almost-due';
+ return '';
+ }
+
+ showTitle() {
+ return `${TAPi18n.__('card-due-on')} ${this.date.get().format('LLLL')}`;
+ }
+
+ events() {
+ return super.events().concat({
+ 'click .js-edit-date': Popup.open('editCardDueDate'),
+ });
+ }
+}
+CardDueDate.register('cardDueDate');
+
+(class extends CardStartDate {
+ showDate() {
+ return this.date.get().format('l');
+ }
+}).register('minicardStartDate');
+
+(class extends CardDueDate {
+ showDate() {
+ return this.date.get().format('l');
+ }
+}).register('minicardDueDate');
diff --git a/client/components/cards/cardDate.styl b/client/components/cards/cardDate.styl
new file mode 100644
index 00000000..1631baa5
--- /dev/null
+++ b/client/components/cards/cardDate.styl
@@ -0,0 +1,58 @@
+.edit-card-date
+ .fields
+ .left
+ width: 56%
+ .right
+ width: 38%
+ .datepicker
+ width: 100%
+ table
+ width: 100%
+ border: none
+ border-spacing: 0
+ border-collapse: collapse
+ thead
+ background: none
+ td, th
+ box-sizing: border-box
+
+
+.card-date
+ display: block
+ border-radius: 4px
+ padding: 1px 3px
+
+ background-color: #dbdbdb
+ &:hover, &.is-active
+ background-color: #b3b3b3
+
+ &.current, &.almost-due, &.due, &.long-overdue
+ color: #fff
+
+ &.current
+ background-color: #5ba639
+ &:hover, &.is-active
+ background-color: darken(#5ba639, 10)
+
+ &.almost-due
+ background-color: #edc909
+ &:hover, &.is-active
+ background-color: darken(#edc909, 10)
+
+ &.due
+ background-color: #fa3f00
+ &:hover, &.is-active
+ background-color: darken(#fa3f00, 10)
+
+ &.long-overdue
+ background-color: #fd5d47
+ &:hover, &.is-active
+ background-color: darken(#fd5d47, 7)
+
+ time
+ &::before
+ font: normal normal normal 14px/1 FontAwesome
+ font-size: inherit
+ -webkit-font-smoothing: antialiased
+ content: "\f017" // clock symbol
+ margin-right: 0.3em \ No newline at end of file
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 734fc7e3..f4212d83 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -35,6 +35,17 @@ template(name="cardDetails")
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus
+ if startAt
+ .card-details-item.card-details-item-start
+ h3.card-details-item-title {{_ 'card-start'}}
+ +cardStartDate
+
+ if dueAt
+ .card-details-item.card-details-item-due
+ h3.card-details-item-title {{_ 'card-due'}}
+ +cardDueDate
+
+
//- XXX We should use "editable" to avoid repetiting ourselves
if currentUser.isBoardMember
h3.card-details-item-title {{_ 'description'}}
@@ -91,6 +102,8 @@ template(name="cardDetailsActionsPopup")
li: a.js-members {{_ 'card-edit-members'}}
li: a.js-labels {{_ 'card-edit-labels'}}
li: a.js-attachments {{_ 'card-edit-attachments'}}
+ li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
+ li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
hr
ul.pop-over-list
li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index b6f17c23..32980926 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -143,6 +143,8 @@ Template.cardDetailsActionsPopup.events({
'click .js-members': Popup.open('cardMembers'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'),
+ 'click .js-start-date': Popup.open('editCardStartDate'),
+ 'click .js-due-date': Popup.open('editCardDueDate'),
'click .js-move-card': Popup.open('moveCard'),
'click .js-move-card-to-top'(evt) {
evt.preventDefault();
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index d7d29551..f209862c 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -73,8 +73,13 @@
margin: 15px 0
.card-details-item
+ margin-right: 0.5em
+ &:last-child
+ margin-right: 0
&.card-details-item-labels,
- &.card-details-item-members
+ &.card-details-item-members,
+ &.card-details-item-start,
+ &.card-details-item-due
width: 50%
flex-shrink: 1
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index 1dfd2f8e..edc7d2d3 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -23,3 +23,9 @@ template(name="minicard")
.badge
span.badge-icon.fa.fa-paperclip
span.badge-text= attachments.count
+ if startAt
+ .badge
+ +minicardStartDate
+ if dueAt
+ .badge
+ +minicardDueDate
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index 0f6f8ad2..a61f6067 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -91,10 +91,13 @@
margin-right: 11px
margin-bottom: 3px
font-size: 0.9em
+
+ &:last-of-type
+ margin-right: 0
.badge-icon,
.badge-text
- vertical-align: top
+ vertical-align: middle
.badge-text
font-size: 0.9em
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index 6e2098c4..83ff2975 100755
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -88,11 +88,15 @@
"card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.",
"card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.",
"card-delete-suggest-archive": "You can archive a card to remove it from the board and preserve the activity.",
+ "card-due": "Due",
+ "card-due-on": "Due on",
"card-edit-attachments": "Edit attachments",
"card-edit-labels": "Edit labels",
"card-edit-members": "Edit members",
"card-labels-title": "Change the labels for the card.",
"card-members-title": "Add or remove members of the board from the card.",
+ "card-start": "Start",
+ "card-start-on": "Starts on",
"cardAttachmentsPopup-title": "Attach From",
"cardDeletePopup-title": "Delete Card?",
"cardDetailsActionsPopup-title": "Card Actions",
@@ -133,6 +137,7 @@
"createBoardPopup-title": "Create Board",
"createLabelPopup-title": "Create Label",
"current": "current",
+ "date": "Date",
"decline": "Decline",
"default-avatar": "Default avatar",
"delete": "Delete",
@@ -146,6 +151,8 @@
"edit": "Edit",
"edit-avatar": "Change Avatar",
"edit-profile": "Edit Profile",
+ "editCardStartDatePopup-title": "Change start date",
+ "editCardDueDatePopup-title": "Change due date",
"editLabelPopup-title": "Change Label",
"editNotificationPopup-title": "Edit Notification",
"editProfilePopup-title": "Edit Profile",
@@ -197,6 +204,7 @@
"importMapMembersAddPopup-title": "Select Wekan member",
"info": "Infos",
"initials": "Initials",
+ "invalid-date": "Invalid date",
"joined": "joined",
"just-invited": "You are just invited to this board",
"keyboard-shortcuts": "Keyboard shortcuts",
@@ -286,6 +294,7 @@
"team": "Team",
"this-board": "this board",
"this-card": "this card",
+ "time": "Time",
"title": "Title",
"tracking": "Tracking",
"tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
diff --git a/models/cards.js b/models/cards.js
index 84fbb6c2..9e7d58c8 100644
--- a/models/cards.js
+++ b/models/cards.js
@@ -56,6 +56,14 @@ Cards.attachSchema(new SimpleSchema({
type: [String],
optional: true,
},
+ startAt: {
+ type: Date,
+ optional: true,
+ },
+ dueAt: {
+ type: Date,
+ optional: true,
+ },
// XXX Should probably be called `authorId`. Is it even needed since we have
// the `members` field?
userId: {
@@ -207,6 +215,22 @@ Cards.mutations({
unsetCover() {
return { $unset: { coverId: '' }};
},
+
+ setStart(startAt) {
+ return { $set: { startAt }};
+ },
+
+ unsetStart() {
+ return { $unset: { startAt: '' }};
+ },
+
+ setDue(dueAt) {
+ return { $set: { dueAt }};
+ },
+
+ unsetDue() {
+ return { $unset: { dueAt: '' }};
+ },
});
if (Meteor.isServer) {