FEATURE: Use new bookmark menu in topic footer buttons (#26670)

Followup to 67a8080e33

This commit makes it so the topic footer button for bookmarks
uses the new BookmarkMenu component, and makes some tweaks to
that component to allow for a label and CSS class options.

Also introduces a TopicBookmarkManager to manage the saving/editing/
deleting of the topic level bookmarks and the reactivity that happens
in the topic UI afterward.

Next commit should rip out old bookmark associated code in the
topic controller as it will no longer be needed.

---------

Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit is contained in:
Martin Brennan
2024-04-18 19:06:12 +10:00
committed by GitHub
parent 09311c7dab
commit 2d2329095c
7 changed files with 202 additions and 80 deletions

View File

@@ -6,7 +6,6 @@ import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import BookmarkModal from "discourse/components/modal/bookmark";
import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error";
import {
TIME_SHORTCUT_TYPES,
@@ -59,18 +58,61 @@ export default class BookmarkMenu extends Component {
return I18n.t("bookmarks.not_bookmarked");
} else {
if (this.existingBookmark.reminderAt) {
return I18n.t("bookmarks.created_with_reminder", {
return I18n.t("bookmarks.created_with_reminder_generic", {
date: this.existingBookmark.formattedReminder(this.timezone),
name: this.existingBookmark.name || "",
});
} else {
return I18n.t("bookmarks.created", {
return I18n.t("bookmarks.created_generic", {
name: this.existingBookmark.name || "",
});
}
}
}
get buttonClasses() {
let cssClasses = ["bookmark widget-button bookmark-menu__trigger"];
if (!this.args.showLabel) {
cssClasses.push("btn-icon no-text");
} else {
cssClasses.push("btn-icon-text");
}
if (this.args.buttonClasses) {
cssClasses.push(this.args.buttonClasses);
}
if (this.existingBookmark) {
cssClasses.push("bookmarked");
if (this.existingBookmark.reminderAt) {
cssClasses.push("with-reminder");
}
}
return cssClasses.join(" ");
}
get buttonIcon() {
if (this.existingBookmark?.reminderAt) {
return "discourse-bookmark-clock";
} else {
return "bookmark";
}
}
get buttonLabel() {
if (!this.args.showLabel) {
return;
}
if (this.existingBookmark) {
return I18n.t("bookmarked.edit_bookmark");
} else {
return I18n.t("bookmarked.title");
}
}
@action
reminderShortcutTimeTitle(option) {
if (!option.time) {
@@ -196,25 +238,16 @@ export default class BookmarkMenu extends Component {
{{didInsert this.setReminderShortcuts}}
@identifier="bookmark-menu"
@triggers={{array "click"}}
class={{concatClass
"bookmark widget-button btn-flat no-text btn-icon bookmark-menu__trigger"
(if this.existingBookmark "bookmarked")
(if this.existingBookmark.reminderAt "with-reminder")
}}
class={{this.buttonClasses}}
@title={{this.buttonTitle}}
@label={{this.buttonLabel}}
@icon={{this.buttonIcon}}
@onClose={{this.onCloseMenu}}
@onShow={{this.onShowMenu}}
@onRegisterApi={{this.onRegisterApi}}
@modalForMobile={{true}}
@arrow={{false}}
>
<:trigger>
{{#if this.existingBookmark.reminderAt}}
{{icon "discourse-bookmark-clock"}}
{{else}}
{{icon "bookmark"}}
{{/if}}
</:trigger>
<:content>
<div class="bookmark-menu__body">

View File

@@ -27,20 +27,27 @@
{{#each this.inlineActionables as |actionable|}}
{{#if (eq actionable.type "inline-button")}}
<DButton
@action={{actionable.action}}
@icon={{actionable.icon}}
@translatedLabel={{actionable.label}}
@translatedTitle={{actionable.title}}
@translatedAriaLabel={{actionable.ariaLabel}}
@disabled={{actionable.disabled}}
id={{concat "topic-footer-button-" actionable.id}}
class={{concat-class
"btn-default"
"topic-footer-button"
actionable.classNames
}}
/>
{{#if (eq actionable.id "bookmark")}}
<BookmarkMenu
@showLabel={{true}}
@bookmarkManager={{this.topicBookmarkManager}}
/>
{{else}}
<DButton
@action={{actionable.action}}
@icon={{actionable.icon}}
@translatedLabel={{actionable.label}}
@translatedTitle={{actionable.title}}
@translatedAriaLabel={{actionable.ariaLabel}}
@disabled={{actionable.disabled}}
id={{concat "topic-footer-button-" actionable.id}}
class={{concat-class
"btn-default"
"topic-footer-button"
actionable.classNames
}}
/>
{{/if}}
{{else}}
<DropdownSelectBox
@id={{concat "topic-footer-dropdown-" actionable.id}}

View File

@@ -1,9 +1,11 @@
import { getOwner } from "@ember/application";
import Component from "@ember/component";
import { computed } from "@ember/object";
import { alias, or } from "@ember/object/computed";
import { NotificationLevels } from "discourse/lib/notification-levels";
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
import { getTopicFooterDropdowns } from "discourse/lib/register-topic-footer-dropdown";
import TopicBookmarkManager from "discourse/lib/topic-bookmark-manager";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
@@ -34,6 +36,10 @@ export default Component.extend({
}
),
topicBookmarkManager: computed("topic", function () {
return new TopicBookmarkManager(getOwner(this), this.topic);
}),
// topic.assigned_to_user is for backward plugin support
@discourseComputed("inlineButtons.[]", "topic.assigned_to_user")
dropdownButtons(inlineButtons) {

View File

@@ -0,0 +1,88 @@
import { tracked } from "@glimmer/tracking";
import { setOwner } from "@ember/application";
import { inject as controller } from "@ember/controller";
import { inject as service } from "@ember/service";
import { BookmarkFormData } from "discourse/lib/bookmark-form-data";
import Bookmark from "discourse/models/bookmark";
export default class TopicBookmarkManager {
@service currentUser;
@service bookmarkApi;
@controller("topic") topicController;
@tracked trackedBookmark;
@tracked bookmarkModel;
constructor(owner, topic) {
setOwner(this, owner);
this.model = topic;
this.type = "Topic";
this.bookmarkModel =
this.topicController.model?.bookmarks.find(
(bookmark) =>
bookmark.bookmarkable_id === this.model.id &&
bookmark.bookmarkable_type === this.type
) || this.bookmarkApi.buildNewBookmark(this.type, this.model.id);
this.trackedBookmark = new BookmarkFormData(this.bookmarkModel);
}
create() {
return this.bookmarkApi
.create(this.trackedBookmark)
.then((updatedBookmark) => {
this.trackedBookmark = updatedBookmark;
});
}
delete() {
return this.bookmarkApi.delete(this.trackedBookmark.id);
}
save() {
return this.bookmarkApi.update(this.trackedBookmark);
}
// noop for topics
afterModalClose() {
return;
}
afterSave(bookmarkFormData) {
this.trackedBookmark = bookmarkFormData;
this._syncBookmarks(bookmarkFormData.saveData);
this.topicController.model.set("bookmarking", false);
this.topicController.model.set("bookmarked", true);
this.topicController.model.incrementProperty("bookmarksWereChanged");
this.topicController.model.appEvents.trigger(
"bookmarks:changed",
bookmarkFormData.saveData,
this.bookmarkModel.attachedTo()
);
return [this.model.id];
}
afterDelete(deleteResponse, bookmarkId) {
this.topicController.model.removeBookmark(bookmarkId);
this.bookmarkModel = this.bookmarkApi.buildNewBookmark(
this.type,
this.model.id
);
this.trackedBookmark = new BookmarkFormData(this.bookmarkModel);
}
_syncBookmarks(data) {
if (!this.topicController.bookmarks) {
this.topicController.set("bookmarks", []);
}
const bookmark = this.topicController.bookmarks.findBy("id", data.id);
if (!bookmark) {
this.topicController.bookmarks.pushObject(Bookmark.create(data));
} else {
bookmark.reminder_at = data.reminder_at;
bookmark.name = data.name;
bookmark.auto_delete_preference = data.auto_delete_preference;
}
}
}

View File

@@ -4,5 +4,5 @@ import { registerWidgetShim } from "discourse/widgets/render-glimmer";
registerWidgetShim(
"bookmark-menu-shim",
"div.bookmark-menu-shim",
hbs`<BookmarkMenu @bookmarkManager={{@data.bookmarkManager}} />`
hbs`<BookmarkMenu @bookmarkManager={{@data.bookmarkManager}} @buttonClasses="btn-flat" />`
);