FEATURE: Bookmark reminder type changes and bugfixes (#9329)

New Reminder Types
-------------------------------------

* Add a "later this week" reminder which is today + 2 days, will not show if we are on the days Thu-Sun
* Add a "start of next business week" reminder which is 8am Monday

Bugfixes and Tweaks
--------------------------------------

* Move dates out of translation for reminder types and yield HTML for tap-tile for more customizable content and styling
* Make sure double clicking the bookmark icon in quick access takes users to the new bookmarks-with-reminders page
* Sane default to 8am (start of day) for custom reminder with no time
This commit is contained in:
Martin Brennan 2020-04-02 09:57:48 +10:00 committed by GitHub
parent 393215266f
commit d261a809e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 113 deletions

View File

@ -3,7 +3,6 @@ import { Promise } from "rsvp";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { htmlSafe } from "@ember/template";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const START_OF_DAY_HOUR = 8; const START_OF_DAY_HOUR = 8;
@ -17,7 +16,9 @@ const REMINDER_TYPES = {
NEXT_MONTH: "next_month", NEXT_MONTH: "next_month",
CUSTOM: "custom", CUSTOM: "custom",
LAST_CUSTOM: "last_custom", LAST_CUSTOM: "last_custom",
NONE: "none" NONE: "none",
START_OF_NEXT_BUSINESS_WEEK: "start_of_next_business_week",
LATER_THIS_WEEK: "later_this_week"
}; };
export default Controller.extend(ModalFunctionality, { export default Controller.extend(ModalFunctionality, {
@ -115,60 +116,48 @@ export default Controller.extend(ModalFunctionality, {
); );
}, },
@discourseComputed()
showLaterThisWeek() {
return this.now().day() < 4; // 4 is Thursday
},
@discourseComputed("parsedLastCustomReminderDatetime") @discourseComputed("parsedLastCustomReminderDatetime")
lastCustomFormatted(parsedLastCustomReminderDatetime) { lastCustomFormatted(parsedLastCustomReminderDatetime) {
return htmlSafe( return parsedLastCustomReminderDatetime.format(
I18n.t("bookmarks.reminders.last_custom", { I18n.t("dates.long_no_year")
date: parsedLastCustomReminderDatetime.format(
I18n.t("dates.long_no_year")
)
})
); );
}, },
@discourseComputed()
startNextBusinessWeekFormatted() {
return this.nextWeek()
.day("Monday")
.format(I18n.t("dates.long_no_year"));
},
@discourseComputed() @discourseComputed()
laterTodayFormatted() { laterTodayFormatted() {
return htmlSafe( return this.laterToday().format(I18n.t("dates.time"));
I18n.t("bookmarks.reminders.later_today", {
date: this.laterToday().format(I18n.t("dates.time"))
})
);
}, },
@discourseComputed() @discourseComputed()
tomorrowFormatted() { tomorrowFormatted() {
return htmlSafe( return this.tomorrow().format(I18n.t("dates.time_short_day"));
I18n.t("bookmarks.reminders.tomorrow", {
date: this.tomorrow().format(I18n.t("dates.time_short_day"))
})
);
},
@discourseComputed()
nextBusinessDayFormatted() {
return htmlSafe(
I18n.t("bookmarks.reminders.next_business_day", {
date: this.nextBusinessDay().format(I18n.t("dates.time_short_day"))
})
);
}, },
@discourseComputed() @discourseComputed()
nextWeekFormatted() { nextWeekFormatted() {
return htmlSafe( return this.nextWeek().format(I18n.t("dates.long_no_year"));
I18n.t("bookmarks.reminders.next_week", { },
date: this.nextWeek().format(I18n.t("dates.long_no_year"))
}) @discourseComputed()
); laterThisWeekFormatted() {
return this.laterThisWeek().format(I18n.t("dates.time_short_day"));
}, },
@discourseComputed() @discourseComputed()
nextMonthFormatted() { nextMonthFormatted() {
return htmlSafe( return this.nextMonth().format(I18n.t("dates.long_no_year"));
I18n.t("bookmarks.reminders.next_month", {
date: this.nextMonth().format(I18n.t("dates.long_no_year"))
})
);
}, },
@discourseComputed() @discourseComputed()
@ -237,9 +226,17 @@ export default Controller.extend(ModalFunctionality, {
return this.tomorrow(); return this.tomorrow();
case REMINDER_TYPES.NEXT_WEEK: case REMINDER_TYPES.NEXT_WEEK:
return this.nextWeek(); return this.nextWeek();
case REMINDER_TYPES.START_OF_NEXT_BUSINESS_WEEK:
return this.nextWeek().day("Monday");
case REMINDER_TYPES.LATER_THIS_WEEK:
return this.laterThisWeek();
case REMINDER_TYPES.NEXT_MONTH: case REMINDER_TYPES.NEXT_MONTH:
return this.nextMonth(); return this.nextMonth();
case REMINDER_TYPES.CUSTOM: case REMINDER_TYPES.CUSTOM:
this.set(
"customReminderTime",
this.customReminderTime || `0${START_OF_DAY_HOUR}:00`
);
const customDateTime = this.parseCustomDateTime( const customDateTime = this.parseCustomDateTime(
this.customReminderDate, this.customReminderDate,
this.customReminderTime this.customReminderTime
@ -265,23 +262,6 @@ export default Controller.extend(ModalFunctionality, {
return this.startOfDay(this.now().add(1, "month")); return this.startOfDay(this.now().add(1, "month"));
}, },
nextBusinessDay() {
const currentDay = this.now().isoWeekday(); // 1=Mon, 7=Sun
let next = null;
// friday
if (currentDay === 5) {
next = this.now().add(3, "days");
// saturday
} else if (currentDay === 6) {
next = this.now().add(2, "days");
} else {
next = this.now().add(1, "day");
}
return this.startOfDay(next);
},
tomorrow() { tomorrow() {
return this.startOfDay(this.now().add(1, "day")); return this.startOfDay(this.now().add(1, "day"));
}, },
@ -301,6 +281,13 @@ export default Controller.extend(ModalFunctionality, {
: later.add(30, "minutes").startOf("hour"); : later.add(30, "minutes").startOf("hour");
}, },
laterThisWeek() {
if (!this.showLaterThisWeek) {
return;
}
return this.startOfDay(this.now().add(2, "days"));
},
handleSaveError(e) { handleSaveError(e) {
this.isSavingBookmarkManually = false; this.isSavingBookmarkManually = false;
if (typeof e === "string") { if (typeof e === "string") {

View File

@ -1,2 +1,2 @@
{{d-icon icon}} {{d-icon icon}}
{{text}} {{ yield }}

View File

@ -21,21 +21,51 @@
{{#if userHasTimezoneSet}} {{#if userHasTimezoneSet}}
{{#tap-tile-grid activeTile=selectedReminderType as |grid|}} {{#tap-tile-grid activeTile=selectedReminderType as |grid|}}
{{#if showAtDesktop}} {{#if showAtDesktop}}
{{tap-tile icon="desktop" text=(i18n "bookmarks.reminders.at_desktop") tileId=reminderTypes.AT_DESKTOP activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#tap-tile icon="desktop" tileId=reminderTypes.AT_DESKTOP activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.at_desktop"}}
{{/tap-tile}}
{{/if}} {{/if}}
{{#if showLaterToday}} {{#if showLaterToday}}
{{tap-tile icon="far-moon" text=laterTodayFormatted tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#tap-tile icon="far-moon" tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.later_today"}}<br>
{{laterTodayFormatted}}
{{/tap-tile}}
{{/if}} {{/if}}
{{tap-tile icon="briefcase" text=nextBusinessDayFormatted tileId=reminderTypes.NEXT_BUSINESS_DAY activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#tap-tile icon="far-sun" tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{tap-tile icon="far-sun" text=tomorrowFormatted tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{i18n "bookmarks.reminders.tomorrow"}}<br>
{{tap-tile icon="far-clock" text=nextWeekFormatted tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{tomorrowFormatted}}
{{tap-tile icon="far-calendar-plus" text=nextMonthFormatted tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{/tap-tile}}
{{tap-tile icon="calendar-alt" text=(I18n "bookmarks.reminders.custom") tileId=reminderTypes.CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#if showLaterThisWeek}}
{{#tap-tile icon="angle-double-right" tileId=reminderTypes.LATER_THIS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.later_this_week"}}<br>
{{laterThisWeekFormatted}}
{{/tap-tile}}
{{/if}}
{{#tap-tile icon="briefcase" tileId=reminderTypes.START_OF_NEXT_BUSINESS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.start_of_next_business_week"}}<br>
{{startNextBusinessWeekFormatted}}
{{/tap-tile}}
{{#tap-tile icon="far-clock" tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.next_week"}}<br>
{{nextWeekFormatted}}
{{/tap-tile}}
{{#tap-tile icon="far-calendar-plus" tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.next_month"}}<br>
{{nextMonthFormatted}}
{{/tap-tile}}
{{#tap-tile icon="calendar-alt" tileId=reminderTypes.CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.custom"}}
{{/tap-tile}}
{{#if showLastCustom}} {{#if showLastCustom}}
{{tap-tile icon="undo" text=lastCustomFormatted tileId=reminderTypes.LAST_CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#tap-tile icon="undo" tileId=reminderTypes.LAST_CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.last_custom"}}<br>
{{lastCustomFormatted}}
{{/tap-tile}}
{{/if}} {{/if}}
{{tap-tile icon="ban" text=(I18n "bookmarks.reminders.none") tileId=reminderTypes.NONE activeTile=grid.activeTile onChange=(action "selectReminderType")}} {{#tap-tile icon="ban" tileId=reminderTypes.NONE activeTile=grid.activeTile onChange=(action "selectReminderType")}}
{{i18n "bookmarks.reminders.none"}}
{{/tap-tile}}
{{/tap-tile-grid}} {{/tap-tile-grid}}
{{#if customDateTimeSelected}} {{#if customDateTimeSelected}}
<div class="control-group custom-date-time-wrap"> <div class="control-group custom-date-time-wrap">

View File

@ -56,13 +56,16 @@ createWidget("user-menu-links", {
}, },
bookmarksGlyph() { bookmarksGlyph() {
let path = this.siteSettings.enable_bookmarks_with_reminders
? "bookmarks-with-reminders"
: "bookmarks";
return { return {
action: UserMenuAction.QUICK_ACCESS, action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.BOOKMARKS, actionParam: QuickAccess.BOOKMARKS,
label: "user.bookmarks", label: "user.bookmarks",
className: "user-bookmarks-link", className: "user-bookmarks-link",
icon: "bookmark", icon: "bookmark",
href: `${this.attrs.path}/activity/bookmarks` href: `${this.attrs.path}/activity/${path}`
}; };
}, },

View File

@ -67,7 +67,9 @@ class Bookmark < ActiveRecord::Base
tomorrow: 3, tomorrow: 3,
next_week: 4, next_week: 4,
next_month: 5, next_month: 5,
custom: 6 custom: 6,
start_of_next_business_week: 7,
later_this_week: 8
) )
end end
end end

View File

@ -316,13 +316,15 @@ en:
list_permission_denied: "You do not have permission to view this user's bookmarks." list_permission_denied: "You do not have permission to view this user's bookmarks."
reminders: reminders:
at_desktop: "Next time I'm at my desktop" at_desktop: "Next time I'm at my desktop"
later_today: "Later today <br/>{{date}}" later_today: "Later today"
next_business_day: "Next business day <br/>{{date}}" next_business_day: "Next business day"
tomorrow: "Tomorrow <br/>{{date}}" tomorrow: "Tomorrow"
next_week: "Next week <br/>{{date}}" next_week: "Next week"
next_month: "Next month <br/>{{date}}" later_this_week: "Later this week"
start_of_next_business_week: "Next Monday"
next_month: "Next month"
custom: "Custom date and time" custom: "Custom date and time"
last_custom: "Last <br/>{{date}}" last_custom: "Last"
none: "No reminder needed" none: "No reminder needed"
drafts: drafts:

View File

@ -6,6 +6,7 @@ moduleFor("controller:bookmark", {
beforeEach() { beforeEach() {
logIn(); logIn();
BookmarkController = this.subject({ currentUser: User.current() }); BookmarkController = this.subject({ currentUser: User.current() });
BookmarkController.onShow();
}, },
afterEach() { afterEach() {
@ -58,54 +59,34 @@ QUnit.test("nextMonth gets next month correctly", function(assert) {
); );
}); });
QUnit.test( QUnit.test("laterThisWeek gets 2 days from now", function(assert) {
"nextBusinessDay gets next business day of monday correctly if today is friday", mockMomentTz("2019-12-10T08:00:00");
function(assert) {
mockMomentTz("2019-12-13T08:00:00");
assert.equal( assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"), BookmarkController.laterThisWeek().format("YYYY-MM-DD"),
"2019-12-16" "2019-12-12"
); );
} });
);
QUnit.test( QUnit.test("laterThisWeek returns null if we are at Thursday already", function(
"nextBusinessDay gets next business day of monday correctly if today is saturday", assert
function(assert) { ) {
mockMomentTz("2019-12-14T08:00:00"); mockMomentTz("2019-12-12T08:00:00");
assert.equal( assert.equal(BookmarkController.laterThisWeek(), null);
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"), });
"2019-12-16"
);
}
);
QUnit.test( QUnit.test("showLaterThisWeek returns true if < Thursday", function(assert) {
"nextBusinessDay gets next business day of monday correctly if today is sunday", mockMomentTz("2019-12-10T08:00:00");
function(assert) {
mockMomentTz("2019-12-15T08:00:00");
assert.equal( assert.equal(BookmarkController.showLaterThisWeek, true);
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"), });
"2019-12-16"
);
}
);
QUnit.test( QUnit.test("showLaterThisWeek returns false if > Thursday", function(assert) {
"nextBusinessDay gets next business day of thursday correctly if today is wednesday", mockMomentTz("2019-12-12T08:00:00");
function(assert) {
mockMomentTz("2019-12-11T08:00:00");
assert.equal(
BookmarkController.nextBusinessDay().format("YYYY-MM-DD"),
"2019-12-12"
);
}
);
assert.equal(BookmarkController.showLaterThisWeek, false);
});
QUnit.test("tomorrow gets tomorrow correctly", function(assert) { QUnit.test("tomorrow gets tomorrow correctly", function(assert) {
mockMomentTz("2019-12-11T08:00:00"); mockMomentTz("2019-12-11T08:00:00");
@ -154,6 +135,27 @@ QUnit.test(
} }
); );
QUnit.test(
"reminderAt - custom - defaults to 8:00am if the time is not selected",
function(assert) {
BookmarkController.customReminderDate = "2028-12-12";
BookmarkController.selectedReminderType =
BookmarkController.reminderTypes.CUSTOM;
const reminderAt = BookmarkController.reminderAt();
assert.equal(BookmarkController.customReminderTime, "08:00");
assert.equal(
reminderAt.toString(),
moment
.tz(
"2028-12-12 08:00",
BookmarkController.currentUser.resolvedTimezone()
)
.toString(),
"the custom date and time are parsed correctly with default time"
);
}
);
QUnit.test( QUnit.test(
"loadLastUsedCustomReminderDatetime fills the custom reminder date + time if present in localStorage", "loadLastUsedCustomReminderDatetime fills the custom reminder date + time if present in localStorage",
function(assert) { function(assert) {