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:
parent
3c72cbc5de
commit
d7f744490a
|
@ -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" },
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
);
|
Loading…
Reference in New Issue