There is now an explicit "Delete Bookmark" button in the edit modal. A confirmation is shown before deleting. Along with this, when the bookmarked post icon is clicked the modal is now shown instead of just deleting the bookmark. Also, the "Delete Bookmark" button from the user bookmark list now confirms the action. Add a `d d` shortcut in the modal to delete the bookmark.
465 lines
13 KiB
JavaScript
465 lines
13 KiB
JavaScript
import { and } from "@ember/object/computed";
|
|
import { isPresent } from "@ember/utils";
|
|
import Controller from "@ember/controller";
|
|
import { Promise } from "rsvp";
|
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
|
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 { formattedReminderTime, REMINDER_TYPES } from "discourse/lib/bookmark";
|
|
|
|
// global shortcuts that interfere with these modal shortcuts, they are rebound when the
|
|
// modal is closed
|
|
//
|
|
// c createTopic
|
|
// r replyToPost
|
|
// l toggle like
|
|
// d deletePost
|
|
// t replyAsNewTopic
|
|
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 BOOKMARK_BINDINGS = {
|
|
enter: { handler: "saveAndClose" },
|
|
"l t": { handler: "selectReminderType", args: [REMINDER_TYPES.LATER_TODAY] },
|
|
"l w": {
|
|
handler: "selectReminderType",
|
|
args: [REMINDER_TYPES.LATER_THIS_WEEK]
|
|
},
|
|
"n b d": {
|
|
handler: "selectReminderType",
|
|
args: [REMINDER_TYPES.NEXT_BUSINESS_DAY]
|
|
},
|
|
"n d": { handler: "selectReminderType", args: [REMINDER_TYPES.TOMORROW] },
|
|
"n w": { handler: "selectReminderType", args: [REMINDER_TYPES.NEXT_WEEK] },
|
|
"n b w": {
|
|
handler: "selectReminderType",
|
|
args: [REMINDER_TYPES.START_OF_NEXT_BUSINESS_WEEK]
|
|
},
|
|
"n m": { handler: "selectReminderType", args: [REMINDER_TYPES.NEXT_MONTH] },
|
|
"c r": { handler: "selectReminderType", args: [REMINDER_TYPES.CUSTOM] },
|
|
"n r": { handler: "selectReminderType", args: [REMINDER_TYPES.NONE] },
|
|
"d d": { handler: "delete" }
|
|
};
|
|
|
|
export default Controller.extend(ModalFunctionality, {
|
|
loading: false,
|
|
errorMessage: null,
|
|
selectedReminderType: null,
|
|
_closeWithoutSaving: false,
|
|
_savingBookmarkManually: false,
|
|
onCloseWithoutSaving: null,
|
|
customReminderDate: null,
|
|
customReminderTime: null,
|
|
lastCustomReminderDate: null,
|
|
lastCustomReminderTime: null,
|
|
mouseTrap: null,
|
|
userTimezone: null,
|
|
|
|
onShow() {
|
|
this.setProperties({
|
|
errorMessage: null,
|
|
selectedReminderType: REMINDER_TYPES.NONE,
|
|
_closeWithoutSaving: false,
|
|
_savingBookmarkManually: false,
|
|
customReminderDate: null,
|
|
customReminderTime: this._defaultCustomReminderTime(),
|
|
lastCustomReminderDate: null,
|
|
lastCustomReminderTime: null,
|
|
userTimezone: this.currentUser.resolvedTimezone()
|
|
});
|
|
|
|
this._bindKeyboardShortcuts();
|
|
this._loadLastUsedCustomReminderDatetime();
|
|
|
|
if (this._editingExistingBookmark()) {
|
|
this._initializeExistingBookmarkData();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* We always want to save the bookmark unless the user specifically
|
|
* clicks the save or cancel button to mimic browser behaviour.
|
|
*/
|
|
onClose() {
|
|
this._unbindKeyboardShortcuts();
|
|
this._restoreGlobalShortcuts();
|
|
if (!this._closeWithoutSaving && !this._savingBookmarkManually) {
|
|
this._saveBookmark().catch(e => this._handleSaveError(e));
|
|
}
|
|
if (this.onCloseWithoutSaving && this._closeWithoutSaving) {
|
|
this.onCloseWithoutSaving();
|
|
}
|
|
},
|
|
|
|
_initializeExistingBookmarkData() {
|
|
if (this._existingBookmarkHasReminder()) {
|
|
let parsedReminderAt = this._parseCustomDateTime(this.model.reminderAt);
|
|
this.setProperties({
|
|
customReminderDate: parsedReminderAt.format("YYYY-MM-DD"),
|
|
customReminderTime: parsedReminderAt.format("HH:mm"),
|
|
selectedReminderType: REMINDER_TYPES.CUSTOM
|
|
});
|
|
}
|
|
},
|
|
|
|
_editingExistingBookmark() {
|
|
return isPresent(this.model) && isPresent(this.model.id);
|
|
},
|
|
|
|
_existingBookmarkHasReminder() {
|
|
return isPresent(this.model) && isPresent(this.model.reminderAt);
|
|
},
|
|
|
|
_loadLastUsedCustomReminderDatetime() {
|
|
let lastTime = localStorage.lastCustomBookmarkReminderTime;
|
|
let lastDate = localStorage.lastCustomBookmarkReminderDate;
|
|
|
|
if (lastTime && lastDate) {
|
|
let parsed = this._parseCustomDateTime(lastDate, lastTime);
|
|
|
|
// can't set reminders in the past
|
|
if (parsed < this.now()) {
|
|
return;
|
|
}
|
|
|
|
this.setProperties({
|
|
lastCustomReminderDate: lastDate,
|
|
lastCustomReminderTime: lastTime,
|
|
parsedLastCustomReminderDatetime: parsed
|
|
});
|
|
}
|
|
},
|
|
|
|
_bindKeyboardShortcuts() {
|
|
KeyboardShortcuts.pause(GLOBAL_SHORTCUTS_TO_PAUSE);
|
|
Object.keys(BOOKMARK_BINDINGS).forEach(shortcut => {
|
|
KeyboardShortcuts.addShortcut(shortcut, () => {
|
|
let binding = BOOKMARK_BINDINGS[shortcut];
|
|
if (binding.args) {
|
|
return this.send(binding.handler, ...binding.args);
|
|
}
|
|
this.send(binding.handler);
|
|
});
|
|
});
|
|
},
|
|
|
|
_unbindKeyboardShortcuts() {
|
|
KeyboardShortcuts.unbind(BOOKMARK_BINDINGS);
|
|
},
|
|
|
|
_restoreGlobalShortcuts() {
|
|
KeyboardShortcuts.unpause(GLOBAL_SHORTCUTS_TO_PAUSE);
|
|
},
|
|
|
|
@discourseComputed("model.reminderAt")
|
|
showExistingReminderAt(existingReminderAt) {
|
|
return isPresent(existingReminderAt);
|
|
},
|
|
|
|
@discourseComputed("model.id")
|
|
showDelete(id) {
|
|
return isPresent(id);
|
|
},
|
|
|
|
@discourseComputed()
|
|
showAtDesktop() {
|
|
return (
|
|
this.siteSettings.enable_bookmark_at_desktop_reminders &&
|
|
this.site.mobileView
|
|
);
|
|
},
|
|
|
|
@discourseComputed("selectedReminderType")
|
|
customDateTimeSelected(selectedReminderType) {
|
|
return selectedReminderType === REMINDER_TYPES.CUSTOM;
|
|
},
|
|
|
|
@discourseComputed()
|
|
reminderTypes: () => {
|
|
return REMINDER_TYPES;
|
|
},
|
|
|
|
showLastCustom: and("lastCustomReminderTime", "lastCustomReminderDate"),
|
|
|
|
@discourseComputed()
|
|
showLaterToday() {
|
|
let later = this.laterToday();
|
|
return (
|
|
!later.isSame(this.tomorrow(), "date") &&
|
|
this.now().hour() < LATER_TODAY_CUTOFF_HOUR
|
|
);
|
|
},
|
|
|
|
@discourseComputed()
|
|
showLaterThisWeek() {
|
|
return this.now().day() < 4; // 4 is Thursday
|
|
},
|
|
|
|
@discourseComputed("parsedLastCustomReminderDatetime")
|
|
lastCustomFormatted(parsedLastCustomReminderDatetime) {
|
|
return parsedLastCustomReminderDatetime.format(
|
|
I18n.t("dates.long_no_year")
|
|
);
|
|
},
|
|
|
|
@discourseComputed("model.reminderAt")
|
|
existingReminderAtFormatted(existingReminderAt) {
|
|
return formattedReminderTime(existingReminderAt, this.userTimezone);
|
|
},
|
|
|
|
@discourseComputed()
|
|
startNextBusinessWeekFormatted() {
|
|
return this.nextWeek()
|
|
.day("Monday")
|
|
.format(I18n.t("dates.long_no_year"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
laterTodayFormatted() {
|
|
return this.laterToday().format(I18n.t("dates.time"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
tomorrowFormatted() {
|
|
return this.tomorrow().format(I18n.t("dates.time_short_day"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
nextWeekFormatted() {
|
|
return this.nextWeek().format(I18n.t("dates.long_no_year"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
laterThisWeekFormatted() {
|
|
return this.laterThisWeek().format(I18n.t("dates.time_short_day"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
nextMonthFormatted() {
|
|
return this.nextMonth().format(I18n.t("dates.long_no_year"));
|
|
},
|
|
|
|
@discourseComputed()
|
|
basePath() {
|
|
return Discourse.BaseUri;
|
|
},
|
|
|
|
@discourseComputed("userTimezone")
|
|
userHasTimezoneSet(userTimezone) {
|
|
return !_.isEmpty(userTimezone);
|
|
},
|
|
|
|
_saveBookmark() {
|
|
const reminderAt = this._reminderAt();
|
|
const reminderAtISO = reminderAt ? reminderAt.toISOString() : null;
|
|
|
|
if (this.selectedReminderType === REMINDER_TYPES.CUSTOM) {
|
|
if (!reminderAt) {
|
|
return Promise.reject(I18n.t("bookmarks.invalid_custom_datetime"));
|
|
}
|
|
|
|
localStorage.lastCustomBookmarkReminderTime = this.customReminderTime;
|
|
localStorage.lastCustomBookmarkReminderDate = this.customReminderDate;
|
|
}
|
|
|
|
let reminderType;
|
|
if (this.selectedReminderType === REMINDER_TYPES.NONE) {
|
|
reminderType = null;
|
|
} else if (this.selectedReminderType === REMINDER_TYPES.LAST_CUSTOM) {
|
|
reminderType = REMINDER_TYPES.CUSTOM;
|
|
} else {
|
|
reminderType = this.selectedReminderType;
|
|
}
|
|
|
|
const data = {
|
|
reminder_type: reminderType,
|
|
reminder_at: reminderAtISO,
|
|
name: this.model.name,
|
|
post_id: this.model.postId,
|
|
id: this.model.id
|
|
};
|
|
|
|
if (this._editingExistingBookmark()) {
|
|
return ajax("/bookmarks/" + this.model.id, {
|
|
type: "PUT",
|
|
data
|
|
}).then(() => {
|
|
if (this.afterSave) {
|
|
this.afterSave({
|
|
reminderAt: reminderAtISO,
|
|
reminderType: this.selectedReminderType,
|
|
id: this.model.id,
|
|
name: this.model.name
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
return ajax("/bookmarks", { type: "POST", data }).then(response => {
|
|
if (this.afterSave) {
|
|
this.afterSave({
|
|
reminderAt: reminderAtISO,
|
|
reminderType: this.selectedReminderType,
|
|
id: response.id,
|
|
name: this.model.name
|
|
});
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
_deleteBookmark() {
|
|
return ajax("/bookmarks/" + this.model.id, {
|
|
type: "DELETE"
|
|
}).then(response => {
|
|
if (this.afterDelete) {
|
|
this.afterDelete(response.topic_bookmarked);
|
|
}
|
|
});
|
|
},
|
|
|
|
_parseCustomDateTime(date, time) {
|
|
let dateTime = isPresent(time) ? date + " " + time : date;
|
|
return moment.tz(dateTime, this.userTimezone);
|
|
},
|
|
|
|
_defaultCustomReminderTime() {
|
|
return `0${START_OF_DAY_HOUR}:00`;
|
|
},
|
|
|
|
_reminderAt() {
|
|
if (!this.selectedReminderType) {
|
|
return;
|
|
}
|
|
|
|
switch (this.selectedReminderType) {
|
|
case REMINDER_TYPES.AT_DESKTOP:
|
|
return null;
|
|
case REMINDER_TYPES.LATER_TODAY:
|
|
return this.laterToday();
|
|
case REMINDER_TYPES.NEXT_BUSINESS_DAY:
|
|
return this.nextBusinessDay();
|
|
case REMINDER_TYPES.TOMORROW:
|
|
return this.tomorrow();
|
|
case REMINDER_TYPES.NEXT_WEEK:
|
|
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:
|
|
return this.nextMonth();
|
|
case REMINDER_TYPES.CUSTOM:
|
|
this.set(
|
|
"customReminderTime",
|
|
this.customReminderTime || this._defaultCustomReminderTime()
|
|
);
|
|
const customDateTime = this._parseCustomDateTime(
|
|
this.customReminderDate,
|
|
this.customReminderTime
|
|
);
|
|
if (!customDateTime.isValid()) {
|
|
this.setProperties({
|
|
customReminderTime: null,
|
|
customReminderDate: null
|
|
});
|
|
return;
|
|
}
|
|
return customDateTime;
|
|
case REMINDER_TYPES.LAST_CUSTOM:
|
|
return this.parsedLastCustomReminderDatetime;
|
|
}
|
|
},
|
|
|
|
nextWeek() {
|
|
return this.startOfDay(this.now().add(7, "days"));
|
|
},
|
|
|
|
nextMonth() {
|
|
return this.startOfDay(this.now().add(1, "month"));
|
|
},
|
|
|
|
tomorrow() {
|
|
return this.startOfDay(this.now().add(1, "day"));
|
|
},
|
|
|
|
startOfDay(momentDate) {
|
|
return momentDate.hour(START_OF_DAY_HOUR).startOf("hour");
|
|
},
|
|
|
|
now() {
|
|
return moment.tz(this.userTimezone);
|
|
},
|
|
|
|
laterToday() {
|
|
let later = this.now().add(3, "hours");
|
|
if (later.hour() >= LATER_TODAY_MAX_HOUR) {
|
|
return later.hour(LATER_TODAY_MAX_HOUR).startOf("hour");
|
|
}
|
|
return later.minutes() < 30
|
|
? later.startOf("hour")
|
|
: later.add(30, "minutes").startOf("hour");
|
|
},
|
|
|
|
laterThisWeek() {
|
|
if (!this.showLaterThisWeek) {
|
|
return;
|
|
}
|
|
return this.startOfDay(this.now().add(2, "days"));
|
|
},
|
|
|
|
_handleSaveError(e) {
|
|
this._savingBookmarkManually = false;
|
|
if (typeof e === "string") {
|
|
bootbox.alert(e);
|
|
} else {
|
|
popupAjaxError(e);
|
|
}
|
|
},
|
|
|
|
actions: {
|
|
saveAndClose() {
|
|
if (this._saving || this._deleting) {
|
|
return;
|
|
}
|
|
|
|
this._saving = true;
|
|
this._savingBookmarkManually = true;
|
|
this._saveBookmark()
|
|
.then(() => this.send("closeModal"))
|
|
.catch(e => this._handleSaveError(e))
|
|
.finally(() => (this._saving = false));
|
|
},
|
|
|
|
delete() {
|
|
this._deleting = true;
|
|
bootbox.confirm(I18n.t("bookmarks.confirm_delete"), result => {
|
|
if (result) {
|
|
this._closeWithoutSaving = true;
|
|
this._deleteBookmark()
|
|
.then(() => {
|
|
this._deleting = false;
|
|
this.send("closeModal");
|
|
})
|
|
.catch(e => this._handleSaveError(e));
|
|
}
|
|
});
|
|
},
|
|
|
|
closeWithoutSavingBookmark() {
|
|
this._closeWithoutSaving = true;
|
|
this.send("closeModal");
|
|
},
|
|
|
|
selectReminderType(type) {
|
|
if (type === REMINDER_TYPES.LATER_TODAY && !this.showLaterToday) {
|
|
return;
|
|
}
|
|
this.set("selectedReminderType", type);
|
|
}
|
|
}
|
|
});
|