From 62b72a03c4889377169411c8cdbf372c71cac1af Mon Sep 17 00:00:00 2001 From: "Sam X. Chen" Date: Thu, 26 Sep 2019 10:53:40 -0400 Subject: Add feature: Add due timeline into Calendar view --- client/components/boards/boardBody.js | 52 ++++++++++++++++++++++++----------- client/lib/datepicker.js | 10 ++++++- i18n/en.i18n.json | 6 ++-- models/activities.js | 1 + models/boards.js | 7 +++++ server/notifications/email.js | 9 ++++-- 6 files changed, 62 insertions(+), 23 deletions(-) diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 07cd306a..d64636f4 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -309,26 +309,46 @@ BlazeComponent.extendComponent({ events(start, end, timezone, callback) { const currentBoard = Boards.findOne(Session.get('currentBoard')); const events = []; + const pushEvent = function(card, title, start, end, extraCls) { + start = start || card.startAt; + end = end || card.endAt; + title = title || card.title; + const className = + (extraCls ? `${extraCls} ` : '') + + (card.color ? `calendar-event-${card.color}` : ''); + events.push({ + id: card._id, + title, + start, + end: end || card.endAt, + allDay: + Math.abs(end.getTime() - start.getTime()) / 1000 === 24 * 3600, + url: FlowRouter.url('card', { + boardId: currentBoard._id, + slug: currentBoard.slug, + cardId: card._id, + }), + className, + }); + }; currentBoard .cardsInInterval(start.toDate(), end.toDate()) .forEach(function(card) { - events.push({ - id: card._id, - title: card.title, - start: card.startAt, - end: card.endAt, - allDay: - Math.abs(card.endAt.getTime() - card.startAt.getTime()) / - 1000 === - 24 * 3600, - url: FlowRouter.url('card', { - boardId: currentBoard._id, - slug: currentBoard.slug, - cardId: card._id, - }), - className: card.color ? `calendar-event-${card.color}` : null, - }); + pushEvent(card); + }); + currentBoard + .cardsDueInBetween(start.toDate(), end.toDate()) + .forEach(function(card) { + pushEvent( + card, + `${card.title} ${TAPi18n.__('card-due')}`, + card.dueAt, + new Date(card.dueAt.getTime() + 36e5), + ); }); + events.sort(function(first, second) { + return first.id > second.id ? 1 : -1; + }); callback(events); }, eventResize(event, delta, revertFunc) { diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js index eb5b60b8..da44bbc5 100644 --- a/client/lib/datepicker.js +++ b/client/lib/datepicker.js @@ -21,7 +21,15 @@ DatePicker = BlazeComponent.extendComponent({ function(evt) { this.find('#date').value = moment(evt.date).format('L'); this.error.set(''); - this.find('#time').focus(); + const timeInput = this.find('#time'); + timeInput.focus(); + if (!timeInput.value) { + const currentHour = evt.date.getHours(); + const defaultMoment = moment( + currentHour > 0 ? evt.date : '1970-01-01 08:00:00', + ); // default to 8:00 am local time + timeInput.value = defaultMoment.format('LT'); + } }.bind(this), ); diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 44304305..58fda954 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -731,12 +731,12 @@ "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", - "act-newDue": "__card__ has 1st due reminder [__board__]", - "act-withDue": "__card__ due reminders [__board__]", + "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", + "act-withDue": "__list__/__card__ due reminders [__board__]", "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "You were mentioned in [__board__] __card__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "accounts-allowUserDelete": "Allow users to self delete their account", "hide-minicard-label-text": "Hide minicard label text", diff --git a/models/activities.js b/models/activities.js index cb1dddaf..19e3fb7d 100644 --- a/models/activities.js +++ b/models/activities.js @@ -289,6 +289,7 @@ if (Meteor.isServer) { activities: { $in: [description, 'all'] }, }).fetch(); if (integrations.length > 0) { + params.watchers = watchers; integrations.forEach(integration => { Meteor.call( 'outgoingWebhooks', diff --git a/models/boards.js b/models/boards.js index af7685ae..a9348478 100644 --- a/models/boards.js +++ b/models/boards.js @@ -699,6 +699,13 @@ Boards.helpers({ return result; }, + cardsDueInBetween(start, end) { + return Cards.find({ + boardId: this._id, + dueAt: { $gte: start, $lte: end }, + }); + }, + cardsInInterval(start, end) { return Cards.find({ boardId: this._id, diff --git a/server/notifications/email.js b/server/notifications/email.js index 0373deb0..acafb4de 100644 --- a/server/notifications/email.js +++ b/server/notifications/email.js @@ -13,11 +13,14 @@ Meteor.startup(() => { const lan = user.getLanguage(); const subject = TAPi18n.__(title, params, lan); // the original function has a fault, i believe the title should be used according to original author const existing = user.getEmailBuffer().length > 0; - const text = `${existing ? `
\n${subject}
\n` : ''}${ + const htmlEnabled = + Meteor.settings.public && + Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false; + const text = `${existing ? `\n${subject}\n` : ''}${ params.user - } ${TAPi18n.__(description, quoteParams, lan)}
\n${params.url}`; + } ${TAPi18n.__(description, quoteParams, lan)}\n${params.url}`; - user.addEmailBuffer(text); + user.addEmailBuffer(htmlEnabled ? text.replace(/\n/g, '
') : text); // unlike setTimeout(func, delay, args), // Meteor.setTimeout(func, delay) does not accept args :-( -- cgit v1.2.3-1-g7c22 From e5f0dd7dd8a3629416c413381993fb791f314311 Mon Sep 17 00:00:00 2001 From: "Sam X. Chen" Date: Thu, 26 Sep 2019 12:20:14 -0400 Subject: Add feature: Add due timeline into Calendar view --- models/cards.js | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/models/cards.js b/models/cards.js index d30baaf1..8356b5f9 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1579,18 +1579,38 @@ const findDueCards = days => { const now = new Date(), aday = 3600 * 24 * 1e3, then = day => new Date(now.setHours(0, 0, 0, 0) + day * aday); - seekDue(then(1), then(days), 'almostdue'); - seekDue(then(0), then(1), 'duenow'); - seekDue(then(-days), now, 'pastdue'); + if (!days) return; + if (!days.map) days = [days]; + days.map(day => { + let args = []; + if (day == 0) { + args = [then(0), then(1), 'duenow']; + } else if (day > 0) { + args = [then(1), then(day), 'almostdue']; + } else { + args = [then(day), now, 'pastdue']; + } + seekDue.apply(null, args); + }); }; const addCronJob = _.debounce( Meteor.bindEnvironment(function findDueCardsDebounced() { - const notifydays = - parseInt(process.env.NOTIFY_DUE_DAYS_BEFORE_AND_AFTER, 10) || 2; // default as 2 days before and after - if (!(notifydays > 0 && notifydays < 15)) { - // notifying due is disabled + const envValue = process.env.NOTIFY_DUE_DAYS_BEFORE_AND_AFTER; + if (!envValue) { return; } + const notifydays = envValue + .split(',') + .map(value => { + const iValue = parseInt(value, 10); + if (!(iValue > 0 && iValue < 15)) { + // notifying due is disabled + return false; + } else { + return iValue; + } + }) + .filter(Boolean); const notifyitvl = process.env.NOTIFY_DUE_AT_HOUR_OF_DAY; //passed in the itvl has to be a number standing for the hour of current time const defaultitvl = 8; // default every morning at 8am, if the passed env variable has parsing error use default const itvl = parseInt(notifyitvl, 10) || defaultitvl; -- cgit v1.2.3-1-g7c22 From 020b66383643f42c183b758fe1bcb73ac6f689bb Mon Sep 17 00:00:00 2001 From: "Sam X. Chen" Date: Thu, 26 Sep 2019 12:24:40 -0400 Subject: Feature enhancement: Allow wekan master have more flexiblity on setting up due reminder --- models/cards.js | 4 ++-- snap-src/bin/config | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/cards.js b/models/cards.js index 8356b5f9..371ad185 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1583,14 +1583,14 @@ const findDueCards = days => { if (!days.map) days = [days]; days.map(day => { let args = []; - if (day == 0) { + if (day === 0) { args = [then(0), then(1), 'duenow']; } else if (day > 0) { args = [then(1), then(day), 'almostdue']; } else { args = [then(day), now, 'pastdue']; } - seekDue.apply(null, args); + seekDue(...args); }); }; const addCronJob = _.debounce( diff --git a/snap-src/bin/config b/snap-src/bin/config index 855caa32..21e2608d 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -108,7 +108,7 @@ DESCRIPTION_BIGEVENTS_PATTERN="Big events pattern: Notify always due etc regardl DEFAULT_BIGEVENTS_PATTERN="NONE" KEY_BIGEVENTS_PATTERN="bigevents-pattern" -DESCRIPTION_NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="Notify due days, default 2 days before and after. 0 = due notifications disabled. Default: 2" +DESCRIPTION_NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="Notify due days, accepts ',' delimited string, i.e. 2,0 means notify will be sent out 2 days before and right on due day. Default: empty" DEFAULT_NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="" KEY_NOTIFY_DUE_DAYS_BEFORE_AND_AFTER="notify-due-days-before-and-after" -- cgit v1.2.3-1-g7c22