FEATURE: Allow editing bookmark reminders (#9437)
Users can now edit the bookmark name and reminder time from their list of bookmarks. We use "Custom" for the date and time in the modal because if the user set a reminder for "tomorrow" then edit the reminder "tomorrow", the definition of what "tomorrow" is has changed.
This commit is contained in:
parent
6fad04635b
commit
8f0544137a
|
@ -20,6 +20,12 @@ export default DropdownSelectBoxComponent.extend({
|
|||
description: I18n.t(
|
||||
"post.bookmarks.actions.delete_bookmark.description"
|
||||
)
|
||||
},
|
||||
{
|
||||
id: "edit",
|
||||
icon: "pencil",
|
||||
name: I18n.t("post.bookmarks.actions.edit_bookmark.name"),
|
||||
description: I18n.t("post.bookmarks.actions.edit_bookmark.description")
|
||||
}
|
||||
];
|
||||
}),
|
||||
|
@ -28,6 +34,8 @@ export default DropdownSelectBoxComponent.extend({
|
|||
onChange(selectedAction) {
|
||||
if (selectedAction === "remove") {
|
||||
this.removeBookmark(this.bookmark);
|
||||
} else if (selectedAction === "edit") {
|
||||
this.editBookmark(this.bookmark);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { and } from "@ember/object/computed";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { next } from "@ember/runloop";
|
||||
import Controller from "@ember/controller";
|
||||
import { Promise } from "rsvp";
|
||||
|
@ -7,7 +8,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";
|
||||
import { formattedReminderTime, REMINDER_TYPES } from "discourse/lib/bookmark";
|
||||
|
||||
// global shortcuts that interfere with these modal shortcuts, they are rebound when the
|
||||
// modal is closed
|
||||
|
@ -47,7 +48,6 @@ const BOOKMARK_BINDINGS = {
|
|||
export default Controller.extend(ModalFunctionality, {
|
||||
loading: false,
|
||||
errorMessage: null,
|
||||
name: null,
|
||||
selectedReminderType: null,
|
||||
closeWithoutSaving: false,
|
||||
isSavingBookmarkManually: false,
|
||||
|
@ -62,7 +62,6 @@ export default Controller.extend(ModalFunctionality, {
|
|||
onShow() {
|
||||
this.setProperties({
|
||||
errorMessage: null,
|
||||
name: null,
|
||||
selectedReminderType: REMINDER_TYPES.NONE,
|
||||
closeWithoutSaving: false,
|
||||
isSavingBookmarkManually: false,
|
||||
|
@ -76,9 +75,47 @@ export default Controller.extend(ModalFunctionality, {
|
|||
this.bindKeyboardShortcuts();
|
||||
this.loadLastUsedCustomReminderDatetime();
|
||||
|
||||
// make sure the input is cleared, otherwise the keyboard shortcut to toggle
|
||||
// bookmark for post ends up in the input
|
||||
next(() => this.set("name", null));
|
||||
if (this.editingExistingBookmark()) {
|
||||
this.initializeExistingBookmarkData();
|
||||
} else {
|
||||
// make sure the input is cleared, otherwise the keyboard shortcut to toggle
|
||||
// bookmark for post ends up in the input
|
||||
next(() => this.set("model.name", null));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.isSavingBookmarkManually) {
|
||||
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() {
|
||||
|
@ -88,6 +125,7 @@ export default Controller.extend(ModalFunctionality, {
|
|||
if (lastTime && lastDate) {
|
||||
let parsed = this.parseCustomDateTime(lastDate, lastTime);
|
||||
|
||||
// can't set reminders in the past
|
||||
if (parsed < this.now()) {
|
||||
return;
|
||||
}
|
||||
|
@ -121,21 +159,11 @@ export default Controller.extend(ModalFunctionality, {
|
|||
KeyboardShortcuts.unpause(GLOBAL_SHORTCUTS_TO_PAUSE);
|
||||
},
|
||||
|
||||
// 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.isSavingBookmarkManually) {
|
||||
this.saveBookmark().catch(e => this.handleSaveError(e));
|
||||
}
|
||||
if (this.onCloseWithoutSaving && this.closeWithoutSaving) {
|
||||
this.onCloseWithoutSaving();
|
||||
}
|
||||
@discourseComputed("model.reminderAt")
|
||||
showExistingReminderAt(existingReminderAt) {
|
||||
return isPresent(existingReminderAt);
|
||||
},
|
||||
|
||||
showBookmarkReminderControls: true,
|
||||
|
||||
@discourseComputed()
|
||||
showAtDesktop() {
|
||||
return (
|
||||
|
@ -177,6 +205,11 @@ export default Controller.extend(ModalFunctionality, {
|
|||
);
|
||||
},
|
||||
|
||||
@discourseComputed("model.reminderAt")
|
||||
existingReminderAtFormatted(existingReminderAt) {
|
||||
return formattedReminderTime(existingReminderAt, this.userTimezone);
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
startNextBusinessWeekFormatted() {
|
||||
return this.nextWeek()
|
||||
|
@ -244,19 +277,32 @@ export default Controller.extend(ModalFunctionality, {
|
|||
const data = {
|
||||
reminder_type: reminderType,
|
||||
reminder_at: reminderAtISO,
|
||||
name: this.name,
|
||||
post_id: this.model.postId
|
||||
name: this.model.name,
|
||||
post_id: this.model.postId,
|
||||
id: this.model.id
|
||||
};
|
||||
|
||||
return ajax("/bookmarks", { type: "POST", data }).then(() => {
|
||||
if (this.afterSave) {
|
||||
this.afterSave(reminderAtISO, this.selectedReminderType);
|
||||
}
|
||||
});
|
||||
if (this.editingExistingBookmark()) {
|
||||
return ajax("/bookmarks/" + this.model.id, {
|
||||
type: "PUT",
|
||||
data
|
||||
}).then(() => {
|
||||
if (this.afterSave) {
|
||||
this.afterSave(reminderAtISO, this.selectedReminderType);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return ajax("/bookmarks", { type: "POST", data }).then(() => {
|
||||
if (this.afterSave) {
|
||||
this.afterSave(reminderAtISO, this.selectedReminderType);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
parseCustomDateTime(date, time) {
|
||||
return moment.tz(date + " " + time, this.userTimezone);
|
||||
let dateTime = isPresent(time) ? date + " " + time : date;
|
||||
return moment.tz(dateTime, this.userTimezone);
|
||||
},
|
||||
|
||||
defaultCustomReminderTime() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Controller from "@ember/controller";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { Promise } from "rsvp";
|
||||
import { inject } from "@ember/controller";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
@ -35,9 +36,9 @@ export default Controller.extend({
|
|||
);
|
||||
},
|
||||
|
||||
@discourseComputed("loaded", "content.length")
|
||||
noContent(loaded, contentLength) {
|
||||
return loaded && contentLength === 0;
|
||||
@discourseComputed("loaded", "content.length", "noResultsHelp")
|
||||
noContent(loaded, contentLength, noResultsHelp) {
|
||||
return loaded && contentLength === 0 && noResultsHelp !== null;
|
||||
},
|
||||
|
||||
processLoadResponse(response) {
|
||||
|
@ -63,6 +64,22 @@ export default Controller.extend({
|
|||
return bookmark.destroy().then(() => this.loadItems());
|
||||
},
|
||||
|
||||
editBookmark(bookmark) {
|
||||
let controller = showModal("bookmark", {
|
||||
model: {
|
||||
postId: bookmark.post_id,
|
||||
id: bookmark.id,
|
||||
reminderAt: bookmark.reminder_at,
|
||||
name: bookmark.name
|
||||
},
|
||||
title: "post.bookmarks.edit",
|
||||
modalClass: "bookmark-with-reminder"
|
||||
});
|
||||
controller.setProperties({
|
||||
afterSave: () => this.loadItems()
|
||||
});
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
if (this.loadingMore) {
|
||||
return Promise.resolve();
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ajax } from "discourse/lib/ajax";
|
|||
import { Promise } from "rsvp";
|
||||
import RestModel from "discourse/models/rest";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { formattedReminderTime } from "discourse/lib/bookmark";
|
||||
|
||||
const Bookmark = RestModel.extend({
|
||||
newBookmark: none("id"),
|
||||
|
@ -110,9 +111,10 @@ const Bookmark = RestModel.extend({
|
|||
|
||||
@discourseComputed("reminder_at", "currentUser")
|
||||
formattedReminder(bookmarkReminderAt, currentUser) {
|
||||
return moment
|
||||
.tz(bookmarkReminderAt, currentUser.resolvedTimezone())
|
||||
.format(I18n.t("dates.long_with_year"));
|
||||
return formattedReminderTime(
|
||||
bookmarkReminderAt,
|
||||
currentUser.resolvedTimezone()
|
||||
).capitalize();
|
||||
},
|
||||
|
||||
loadItems() {
|
||||
|
|
|
@ -9,89 +9,94 @@
|
|||
{{/if}}
|
||||
|
||||
<div class="control-group">
|
||||
{{input value=name name="name" class="bookmark-name" placeholder=(i18n "post.bookmarks.name_placeholder")}}
|
||||
{{input value=model.name name="name" class="bookmark-name" placeholder=(i18n "post.bookmarks.name_placeholder")}}
|
||||
</div>
|
||||
|
||||
{{#if showBookmarkReminderControls}}
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="set_reminder">
|
||||
{{i18n "post.bookmarks.set_reminder"}}
|
||||
</label>
|
||||
|
||||
{{#if userHasTimezoneSet}}
|
||||
{{#tap-tile-grid activeTile=selectedReminderType as |grid|}}
|
||||
{{#if showAtDesktop}}
|
||||
{{#tap-tile icon="desktop" tileId=reminderTypes.AT_DESKTOP activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.at_desktop"}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showLaterToday}}
|
||||
{{#tap-tile icon="far-moon" tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.later_today"}}</div>
|
||||
<div class="tap-tile-date">{{laterTodayFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
{{#tap-tile icon="far-sun" tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.tomorrow"}}</div>
|
||||
<div class="tap-tile-date">{{tomorrowFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#if showLaterThisWeek}}
|
||||
{{#tap-tile icon="angle-double-right" tileId=reminderTypes.LATER_THIS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.later_this_week"}}</div>
|
||||
<div class="tap-tile-date">{{laterThisWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
{{#tap-tile icon="briefcase" tileId=reminderTypes.START_OF_NEXT_BUSINESS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.start_of_next_business_week"}}</div>
|
||||
<div class="tap-tile-date">{{startNextBusinessWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="far-clock" tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.next_week"}}</div>
|
||||
<div class="tap-tile-date">{{nextWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="far-calendar-plus" tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.next_month"}}</div>
|
||||
<div class="tap-tile-date">{{nextMonthFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="calendar-alt" tileId=reminderTypes.CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.custom"}}</div>
|
||||
{{/tap-tile}}
|
||||
|
||||
{{#if customDateTimeSelected}}
|
||||
<div class="control-group custom-date-time-wrap">
|
||||
<div class="tap-tile-date-input">
|
||||
{{d-icon "calendar-alt"}}
|
||||
{{date-picker-future
|
||||
value=customReminderDate
|
||||
onSelect=(action (mut customReminderDate))
|
||||
}}
|
||||
</div>
|
||||
<div class="tap-tile-time-input">
|
||||
{{d-icon "far-clock"}}
|
||||
{{input placeholder="--:--" type="time" class="time-input" value=customReminderTime}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showLastCustom}}
|
||||
{{#tap-tile icon="undo" tileId=reminderTypes.LAST_CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.last_custom"}}</div>
|
||||
<div class="tap-tile-date">{{lastCustomFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
|
||||
{{#tap-tile icon="ban" tileId=reminderTypes.NONE activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.none"}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/tap-tile-grid}}
|
||||
|
||||
{{else}}
|
||||
<div class="alert alert-info">{{html-safe (i18n "bookmarks.no_timezone" basePath=basePath)}}</div>
|
||||
{{/if}}
|
||||
{{#if showExistingReminderAt }}
|
||||
<div class="alert alert-info existing-reminder-at-alert">
|
||||
{{d-icon "far-clock"}}
|
||||
<span>{{i18n "bookmarks.reminders.existing_reminder"}} {{existingReminderAtFormatted}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="set_reminder">
|
||||
{{i18n "post.bookmarks.set_reminder"}}
|
||||
</label>
|
||||
|
||||
{{#if userHasTimezoneSet}}
|
||||
{{#tap-tile-grid activeTile=selectedReminderType as |grid|}}
|
||||
{{#if showAtDesktop}}
|
||||
{{#tap-tile icon="desktop" tileId=reminderTypes.AT_DESKTOP activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.at_desktop"}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showLaterToday}}
|
||||
{{#tap-tile icon="far-moon" tileId=reminderTypes.LATER_TODAY activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.later_today"}}</div>
|
||||
<div class="tap-tile-date">{{laterTodayFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
{{#tap-tile icon="far-sun" tileId=reminderTypes.TOMORROW activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.tomorrow"}}</div>
|
||||
<div class="tap-tile-date">{{tomorrowFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#if showLaterThisWeek}}
|
||||
{{#tap-tile icon="angle-double-right" tileId=reminderTypes.LATER_THIS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.later_this_week"}}</div>
|
||||
<div class="tap-tile-date">{{laterThisWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
{{#tap-tile icon="briefcase" tileId=reminderTypes.START_OF_NEXT_BUSINESS_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.start_of_next_business_week"}}</div>
|
||||
<div class="tap-tile-date">{{startNextBusinessWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="far-clock" tileId=reminderTypes.NEXT_WEEK activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.next_week"}}</div>
|
||||
<div class="tap-tile-date">{{nextWeekFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="far-calendar-plus" tileId=reminderTypes.NEXT_MONTH activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.next_month"}}</div>
|
||||
<div class="tap-tile-date">{{nextMonthFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{#tap-tile icon="calendar-alt" tileId=reminderTypes.CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.custom"}}</div>
|
||||
{{/tap-tile}}
|
||||
|
||||
{{#if customDateTimeSelected}}
|
||||
<div class="control-group custom-date-time-wrap">
|
||||
<div class="tap-tile-date-input">
|
||||
{{d-icon "calendar-alt"}}
|
||||
{{date-picker-future
|
||||
value=customReminderDate
|
||||
onSelect=(action (mut customReminderDate))
|
||||
}}
|
||||
</div>
|
||||
<div class="tap-tile-time-input">
|
||||
{{d-icon "far-clock"}}
|
||||
{{input placeholder="--:--" type="time" class="time-input" value=customReminderTime}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if showLastCustom}}
|
||||
{{#tap-tile icon="undo" tileId=reminderTypes.LAST_CUSTOM activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.last_custom"}}</div>
|
||||
<div class="tap-tile-date">{{lastCustomFormatted}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/if}}
|
||||
|
||||
{{#tap-tile icon="ban" tileId=reminderTypes.NONE activeTile=grid.activeTile onChange=(action "selectReminderType")}}
|
||||
<div class="tap-tile-title">{{i18n "bookmarks.reminders.none"}}</div>
|
||||
{{/tap-tile}}
|
||||
{{/tap-tile-grid}}
|
||||
|
||||
{{else}}
|
||||
<div class="alert alert-info">{{html-safe (i18n "bookmarks.no_timezone" basePath=basePath)}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
{{d-button label="bookmarks.save" class="btn-primary" action=(action "saveAndClose")}}
|
||||
{{d-modal-cancel close=(action "closeWithoutSavingBookmark")}}
|
||||
|
|
|
@ -42,7 +42,11 @@
|
|||
<td>{{format-date bookmark.created_at format="tiny"}}</td>
|
||||
{{raw "list/activity-column" topic=bookmark class="num" tagName="td"}}
|
||||
<td>
|
||||
{{bookmark-actions-dropdown bookmark=bookmark removeBookmark=(action "removeBookmark")}}
|
||||
{{bookmark-actions-dropdown
|
||||
bookmark=bookmark
|
||||
removeBookmark=(action "removeBookmark")
|
||||
editBookmark=(action "editBookmark")
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
|
|
@ -19,6 +19,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
.existing-reminder-at-alert {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-date-time-wrap {
|
||||
padding: 1em 1em 0.5em;
|
||||
border: 1px solid $primary-low;
|
||||
|
|
|
@ -26,4 +26,22 @@ class BookmarksController < ApplicationController
|
|||
BookmarkManager.new(current_user).destroy(params[:id])
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def update
|
||||
params.require(:id)
|
||||
|
||||
bookmark_manager = BookmarkManager.new(current_user)
|
||||
bookmark_manager.update(
|
||||
bookmark_id: params[:id],
|
||||
name: params[:name],
|
||||
reminder_type: params[:reminder_type],
|
||||
reminder_at: params[:reminder_at]
|
||||
)
|
||||
|
||||
if bookmark_manager.errors.empty?
|
||||
return render json: success_json
|
||||
end
|
||||
|
||||
render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400
|
||||
end
|
||||
end
|
||||
|
|
|
@ -331,6 +331,7 @@ en:
|
|||
today_with_time: "today at %{time}"
|
||||
tomorrow_with_time: "tomorrow at %{time}"
|
||||
at_time: "at %{date_time}"
|
||||
existing_reminder: "You have a reminder set for this bookmark which will be sent"
|
||||
|
||||
drafts:
|
||||
resume: "Resume"
|
||||
|
@ -2716,6 +2717,7 @@ en:
|
|||
|
||||
bookmarks:
|
||||
create: "Create bookmark"
|
||||
edit: "Edit bookmark"
|
||||
created: "Created"
|
||||
name: "Name"
|
||||
name_placeholder: "What is this bookmark for?"
|
||||
|
@ -2724,6 +2726,9 @@ en:
|
|||
delete_bookmark:
|
||||
name: "Delete bookmark"
|
||||
description: "Removes the bookmark from your profile and stops all reminders for the bookmark"
|
||||
edit_bookmark:
|
||||
name: "Edit bookmark"
|
||||
description: "Edit the bookmark name or change the reminder date and time"
|
||||
|
||||
category:
|
||||
can: "can… "
|
||||
|
|
|
@ -606,7 +606,7 @@ Discourse::Application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :bookmarks, only: %i[create destroy]
|
||||
resources :bookmarks, only: %i[create destroy update]
|
||||
|
||||
resources :notifications, except: :show do
|
||||
collection do
|
||||
|
|
|
@ -66,6 +66,24 @@ class BookmarkManager
|
|||
BookmarkReminderNotificationHandler.send_notification(bookmark)
|
||||
end
|
||||
|
||||
def update(bookmark_id:, name:, reminder_type:, reminder_at:)
|
||||
bookmark = Bookmark.find_by(id: bookmark_id)
|
||||
|
||||
raise Discourse::NotFound if bookmark.blank?
|
||||
raise Discourse::InvalidAccess.new if !Guardian.new(@user).can_edit?(bookmark)
|
||||
|
||||
if bookmark.errors.any?
|
||||
return add_errors_from(bookmark)
|
||||
end
|
||||
|
||||
bookmark.update(
|
||||
name: name,
|
||||
reminder_at: reminder_at,
|
||||
reminder_type: reminder_type,
|
||||
reminder_set_at: Time.zone.now
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clear_at_desktop_cache_if_required
|
||||
|
|
|
@ -5,6 +5,10 @@ module BookmarkGuardian
|
|||
@user == bookmark.user
|
||||
end
|
||||
|
||||
def can_edit_bookmark?(bookmark)
|
||||
@user == bookmark.user
|
||||
end
|
||||
|
||||
def can_create_bookmark?(bookmark)
|
||||
can_see_topic?(bookmark.topic)
|
||||
end
|
||||
|
|
|
@ -160,6 +160,43 @@ RSpec.describe BookmarkManager do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".update" do
|
||||
let!(:bookmark) { Fabricate(:bookmark_next_business_day_reminder, user: user, post: post, name: "Old name") }
|
||||
let(:new_name) { "Some new name" }
|
||||
let(:new_reminder_at) { 10.days.from_now }
|
||||
let(:new_reminder_type) { Bookmark.reminder_types[:custom] }
|
||||
|
||||
def update_bookmark
|
||||
subject.update(
|
||||
bookmark_id: bookmark.id, name: new_name, reminder_type: new_reminder_type, reminder_at: new_reminder_at
|
||||
)
|
||||
end
|
||||
|
||||
it "saves the time and new reminder type sucessfully" do
|
||||
update_bookmark
|
||||
bookmark.reload
|
||||
expect(bookmark.name).to eq(new_name)
|
||||
expect(bookmark.reminder_at).to eq_time(new_reminder_at)
|
||||
expect(bookmark.reminder_type).to eq(new_reminder_type)
|
||||
end
|
||||
|
||||
context "if the bookmark is belonging to some other user" do
|
||||
let!(:bookmark) { Fabricate(:bookmark, user: Fabricate(:admin), post: post) }
|
||||
it "raises an invalid access error" do
|
||||
expect { update_bookmark }.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
end
|
||||
|
||||
context "if the bookmark no longer exists" do
|
||||
before do
|
||||
bookmark.destroy!
|
||||
end
|
||||
it "raises an invalid access error" do
|
||||
expect { update_bookmark }.to raise_error(Discourse::NotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".destroy_for_topic" do
|
||||
let!(:topic) { Fabricate(:topic) }
|
||||
let!(:bookmark1) { Fabricate(:bookmark, topic: topic, post: Fabricate(:post, topic: topic), user: user) }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { logIn } from "helpers/qunit-helpers";
|
||||
import User from "discourse/models/user";
|
||||
import KeyboardShortcutInitializer from "discourse/initializers/keyboard-shortcuts";
|
||||
import { REMINDER_TYPES } from "discourse/lib/bookmark";
|
||||
let BookmarkController;
|
||||
|
||||
moduleFor("controller:bookmark", {
|
||||
|
@ -263,3 +264,20 @@ QUnit.test("user timezone updates when the modal is shown", function(assert) {
|
|||
);
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"opening the modal with an existing bookmark with reminder at prefills the custom reminder type",
|
||||
function(assert) {
|
||||
let name = "test";
|
||||
let reminderAt = "2020-05-15T09:45:00";
|
||||
BookmarkController.model = { id: 1, name: name, reminderAt: reminderAt };
|
||||
BookmarkController.onShow();
|
||||
assert.equal(
|
||||
BookmarkController.selectedReminderType,
|
||||
REMINDER_TYPES.CUSTOM
|
||||
);
|
||||
assert.equal(BookmarkController.customReminderDate, "2020-05-15");
|
||||
assert.equal(BookmarkController.customReminderTime, "09:45");
|
||||
assert.equal(BookmarkController.model.name, name);
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue