FEATURE: Decorate topic-level bookmark button with reminder time (#9426)

* Show the correct bookmark with clock icon when topic-level bookmark reminder time is set and show the time of the reminder in the title on hover.
* Add a new bookmark lib and reminder time formatting function to show time with today/tomorrow shorthand for readability. E.g. tomorrow at 8:00am instead of Apr 16 2020 at 8:00am. This only applies to today + tomorrow, future dates are still treated the same.
This commit is contained in:
Martin Brennan 2020-04-16 09:20:44 +10:00 committed by GitHub
parent 3c72cbc5de
commit d7f744490a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 25 deletions

View File

@ -7,6 +7,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { ajax } from "discourse/lib/ajax";
import KeyboardShortcuts from "discourse/lib/keyboard-shortcuts";
import { REMINDER_TYPES } from "discourse/lib/bookmark";
// global shortcuts that interfere with these modal shortcuts, they are rebound when the
// modal is closed
@ -20,19 +21,6 @@ const GLOBAL_SHORTCUTS_TO_PAUSE = ["c", "r", "l", "d", "t"];
const START_OF_DAY_HOUR = 8;
const LATER_TODAY_CUTOFF_HOUR = 17;
const LATER_TODAY_MAX_HOUR = 18;
const REMINDER_TYPES = {
AT_DESKTOP: "at_desktop",
LATER_TODAY: "later_today",
NEXT_BUSINESS_DAY: "next_business_day",
TOMORROW: "tomorrow",
NEXT_WEEK: "next_week",
NEXT_MONTH: "next_month",
CUSTOM: "custom",
LAST_CUSTOM: "last_custom",
NONE: "none",
START_OF_NEXT_BUSINESS_WEEK: "start_of_next_business_week",
LATER_THIS_WEEK: "later_this_week"
};
const BOOKMARK_BINDINGS = {
enter: { handler: "saveAndClose" },

View File

@ -1,10 +1,12 @@
import showModal from "discourse/lib/show-modal";
import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button";
import { formattedReminderTime } from "discourse/lib/bookmark";
export default {
name: "topic-footer-buttons",
initialize() {
initialize(container) {
const currentUser = container.lookup("current-user:main");
registerTopicFooterButton({
id: "share-and-invite",
icon: "link",
@ -84,7 +86,12 @@ export default {
registerTopicFooterButton({
dependentKeys: ["topic.bookmarked", "topic.isPrivateMessage"],
id: "bookmark",
icon: "bookmark",
icon() {
if (this.get("topic.bookmark_reminder_at")) {
return "discourse-bookmark-clock";
}
return "bookmark";
},
priority: 1000,
classNames() {
const bookmarked = this.get("topic.bookmarked");
@ -94,11 +101,21 @@ export default {
const bookmarked = this.get("topic.bookmarked");
return bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title";
},
title() {
translatedTitle() {
const bookmarked = this.get("topic.bookmarked");
return bookmarked
? "bookmarked.help.unbookmark"
: "bookmarked.help.bookmark";
const bookmark_reminder_at = this.get("topic.bookmark_reminder_at");
if (bookmarked) {
if (bookmark_reminder_at) {
return I18n.t("bookmarked.help.unbookmark_with_reminder", {
reminder_at: formattedReminderTime(
bookmark_reminder_at,
currentUser.resolvedTimezone()
)
});
}
return I18n.t("bookmarked.help.unbookmark");
}
return I18n.t("bookmarked.help.bookmark");
},
action: "toggleBookmark",
dropdown() {

View File

@ -0,0 +1,31 @@
export function formattedReminderTime(reminderAt, timezone) {
let reminderAtDate = moment.tz(reminderAt, timezone);
let formatted = reminderAtDate.format(I18n.t("dates.time"));
let now = moment.tz(timezone);
let tomorrow = moment(now).add(1, "day");
if (reminderAtDate.isSame(tomorrow, "date")) {
return I18n.t("bookmarks.reminders.tomorrow_with_time", {
time: formatted
});
} else if (reminderAtDate.isSame(now, "date")) {
return I18n.t("bookmarks.reminders.today_with_time", { time: formatted });
}
return I18n.t("bookmarks.reminders.at_time", {
date_time: reminderAtDate.format(I18n.t("dates.long_with_year"))
});
}
export const REMINDER_TYPES = {
AT_DESKTOP: "at_desktop",
LATER_TODAY: "later_today",
NEXT_BUSINESS_DAY: "next_business_day",
TOMORROW: "tomorrow",
NEXT_WEEK: "next_week",
NEXT_MONTH: "next_month",
CUSTOM: "custom",
LAST_CUSTOM: "last_custom",
NONE: "none",
START_OF_NEXT_BUSINESS_WEEK: "start_of_next_business_week",
LATER_THIS_WEEK: "later_this_week"
};

View File

@ -401,6 +401,7 @@ const Topic = RestModel.extend({
if (firstPost) {
firstPost.set("bookmarked", true);
if (this.siteSettings.enable_bookmarks_with_reminders) {
this.set("bookmark_reminder_at", firstPost.bookmark_reminder_at);
firstPost.set("bookmarked_with_reminder", true);
}
return [firstPost.id];
@ -440,7 +441,7 @@ const Topic = RestModel.extend({
if (this.siteSettings.enable_bookmarks_with_reminders) {
return firstPost.toggleBookmarkWithReminder().then(response => {
this.set("bookmarking", false);
if (response.closedWithoutSaving) {
if (response && response.closedWithoutSaving) {
this.set("bookmarked", false);
} else {
return this.afterTopicBookmarked(firstPost);
@ -459,6 +460,7 @@ const Topic = RestModel.extend({
return ajax(`/t/${this.id}/remove_bookmarks`, { type: "PUT" })
.then(() => {
this.toggleProperty("bookmarked");
this.set("bookmark_reminder_at", null);
if (posts) {
const updated = [];
posts.forEach(post => {
@ -474,6 +476,7 @@ const Topic = RestModel.extend({
updated.push(post.id);
}
});
firstPost.set("bookmarked_with_reminder", false);
return updated;
}
})

View File

@ -5,6 +5,7 @@ import { h } from "virtual-dom";
import showModal from "discourse/lib/show-modal";
import { Promise } from "rsvp";
import ENV from "discourse-common/config/environment";
import { formattedReminderTime } from "discourse/lib/bookmark";
const LIKE_ACTION = 2;
const VIBRATE_DURATION = 5;
@ -314,12 +315,13 @@ registerButton("bookmarkWithReminder", (attrs, state, siteSettings) => {
classNames.push("bookmarked");
if (attrs.bookmarkReminderAt) {
let reminderAtDate = moment(attrs.bookmarkReminderAt).tz(
let formattedReminder = formattedReminderTime(
attrs.bookmarkReminderAt,
Discourse.currentUser.resolvedTimezone()
);
title = "bookmarks.created_with_reminder";
titleOptions = {
date: reminderAtDate.format(I18n.t("dates.long_with_year"))
date: formattedReminder
};
} else if (attrs.bookmarkReminderType === "at_desktop") {
title = "bookmarks.created_with_at_desktop_reminder";

View File

@ -454,7 +454,8 @@ nav.post-controls {
color: $tertiary;
}
}
.bookmark.bookmarked .d-icon-bookmark {
.bookmark.bookmarked .d-icon-bookmark,
.bookmark.bookmarked .d-icon-discourse-bookmark-clock {
color: $tertiary;
}
.feature-on-profile.featured-on-profile .d-icon-id-card {

View File

@ -237,7 +237,8 @@ a.reply-to-tab {
#topic-footer-buttons {
@include clearfix;
padding: 20px 0 0 0;
.d-icon-bookmark.bookmarked {
.d-icon-bookmark.bookmarked,
.d-icon-discourse-bookmark-clock.bookmarked {
color: $tertiary;
}
.combobox {

View File

@ -61,6 +61,7 @@ class TopicViewSerializer < ApplicationSerializer
:is_warning,
:chunk_size,
:bookmarked,
:bookmark_reminder_at,
:message_archived,
:topic_timer,
:private_topic_timer,
@ -192,6 +193,14 @@ class TopicViewSerializer < ApplicationSerializer
end
end
def include_bookmark_reminder_at?
SiteSetting.enable_bookmarks_with_reminders? && bookmarked
end
def bookmark_reminder_at
object.first_post_bookmark_reminder_at
end
def topic_timer
TopicTimerSerializer.new(object.topic.public_topic_timer, root: false)
end

View File

@ -302,11 +302,13 @@ en:
help:
bookmark: "Click to bookmark the first post on this topic"
unbookmark: "Click to remove all bookmarks in this topic"
unbookmark_with_reminder: "Click to remove all bookmarks and reminders in this topic. You have a reminder set %{reminder_at} for this topic."
bookmarks:
created: "you've bookmarked this post"
not_bookmarked: "bookmark this post"
created_with_reminder: "you've bookmarked this post with a reminder at %{date}"
created_with_reminder: "you've bookmarked this post with a reminder %{date}"
created_with_at_desktop_reminder: "you've bookmarked this post and will be reminded next time you are at your desktop"
remove: "Remove Bookmark"
confirm_clear: "Are you sure you want to clear all your bookmarks from this topic?"
@ -326,6 +328,9 @@ en:
custom: "Custom date and time"
last_custom: "Last"
none: "No reminder needed"
today_with_time: "today at %{time}"
tomorrow_with_time: "tomorrow at %{time}"
at_time: "at %{date_time}"
drafts:
resume: "Resume"

View File

@ -354,6 +354,10 @@ class TopicView
@topic.bookmarks.exists?(user_id: @user.id)
end
def first_post_bookmark_reminder_at
@topic.first_post.bookmarks.where(user: @user).pluck_first(:reminder_at)
end
MAX_PARTICIPANTS = 24
def post_counts_by_user

View File

@ -0,0 +1,51 @@
import { formattedReminderTime } from "discourse/lib/bookmark";
QUnit.module("lib:bookmark", {
beforeEach() {
// set the current now time for all tests
let now = moment.tz("2020-04-11 08:00:00", "Australia/Brisbane");
sandbox.useFakeTimers(now.valueOf());
}
});
QUnit.test(
"formattedReminderTime works when the reminder time is tomorrow",
assert => {
let reminderAt = "2020-04-12 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"tomorrow at " + reminderAtDate
);
}
);
QUnit.test(
"formattedReminderTime works when the reminder time is today",
assert => {
let reminderAt = "2020-04-11 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"today at " + reminderAtDate
);
}
);
QUnit.test(
"formattedReminderTime works when the reminder time is in the future",
assert => {
let reminderAt = "2020-04-15 09:45:00";
let reminderAtDate = moment
.tz(reminderAt, "Australia/Brisbane")
.format("H:mm a");
assert.equal(
formattedReminderTime(reminderAt, "Australia/Brisbane"),
"at Apr 15, 2020 " + reminderAtDate
);
}
);