From 9c5fc6f1df3e1422b05f68d449eb324c94f51052 Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 4 Nov 2024 17:32:49 +0100 Subject: [PATCH] DEV: Add more-topics plugin API (#29143) From plugin-api comment: Registers a new tab to be displayed in "more topics" area at the bottom of a topic page. ```gjs api.registerMoreTopicsTab({ id: "other-topics", name: i18n("other_topics.tab"), component: , condition: ({ topic }) => topic.otherTopics?.length > 0, }); ``` You can additionally use more-topics-tabs value transformer to conditionally show/hide specific tabs. ```js api.registerValueTransformer("more-topics-tabs", ({ value, context }) => { if (context.user?.aFeatureFlag) { // Remove "suggested" from the topics page return value.filter( (tab) => context.currentContext !== "topic" || tab.id !== "suggested-topics" ); } }); ``` --- .../discourse/app/components/more-topics.gjs | 103 ++++++++++++++++++ .../discourse/app/components/more-topics.hbs | 49 --------- .../browse-more.gjs} | 79 +++++--------- .../app/components/related-messages.gjs | 49 ++------- .../app/components/suggested-topics.gjs | 51 +++++++++ .../app/components/suggested-topics.hbs | 34 ------ .../app/components/suggested-topics.js | 38 ------- .../more-topics-tabs.gjs | 25 +++++ .../discourse/app/lib/plugin-api.gjs | 47 +++++++- .../discourse/app/lib/transformer/registry.js | 3 +- .../javascripts/discourse/app/models/topic.js | 24 ++-- .../more-topics-preference-tracking.js | 56 ---------- .../more-topics-plugin-api-test.gjs | 45 ++++++++ .../discourse/tests/helpers/qunit-helpers.js | 2 + docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md | 7 +- 15 files changed, 333 insertions(+), 279 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/more-topics.gjs delete mode 100644 app/assets/javascripts/discourse/app/components/more-topics.hbs rename app/assets/javascripts/discourse/app/components/{more-topics.js => more-topics/browse-more.gjs} (63%) create mode 100644 app/assets/javascripts/discourse/app/components/suggested-topics.gjs delete mode 100644 app/assets/javascripts/discourse/app/components/suggested-topics.hbs delete mode 100644 app/assets/javascripts/discourse/app/components/suggested-topics.js create mode 100644 app/assets/javascripts/discourse/app/instance-initializers/more-topics-tabs.gjs delete mode 100644 app/assets/javascripts/discourse/app/services/more-topics-preference-tracking.js create mode 100644 app/assets/javascripts/discourse/tests/acceptance/more-topics-plugin-api-test.gjs diff --git a/app/assets/javascripts/discourse/app/components/more-topics.gjs b/app/assets/javascripts/discourse/app/components/more-topics.gjs new file mode 100644 index 00000000000..e14c77be195 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/more-topics.gjs @@ -0,0 +1,103 @@ +import Component from "@glimmer/component"; +import { cached, tracked } from "@glimmer/tracking"; +import { fn } from "@ember/helper"; +import { action } from "@ember/object"; +import { service } from "@ember/service"; +import { eq, gt } from "truth-helpers"; +import DButton from "discourse/components/d-button"; +import BrowseMore from "discourse/components/more-topics/browse-more"; +import concatClass from "discourse/helpers/concat-class"; +import { applyValueTransformer } from "discourse/lib/transformer"; + +export let registeredTabs = []; + +export function clearRegisteredTabs() { + registeredTabs.length = 0; +} + +export default class MoreTopics extends Component { + @service currentUser; + @service keyValueStore; + + @tracked selectedTab = this.initialTab; + + get initialTab() { + let savedId = this.keyValueStore.get( + `more-topics-preference-${this.context}` + ); + + // Fallback to the old setting + savedId ||= this.keyValueStore.get("more-topics-list-preference"); + + return ( + (savedId && this.tabs.find((tab) => tab.id === savedId)) || this.tabs[0] + ); + } + + get activeTab() { + return this.tabs.find((tab) => tab === this.selectedTab) || this.tabs[0]; + } + + get context() { + return this.args.topic.get("isPrivateMessage") ? "pm" : "topic"; + } + + @cached + get tabs() { + const defaultTabs = registeredTabs.filter((tab) => + tab.condition({ topic: this.args.topic, context: this.context }) + ); + + return applyValueTransformer("more-topics-tabs", defaultTabs, { + currentContext: this.context, + user: this.currentUser, + topic: this.args.topic, + }); + } + + @action + selectTab(tab) { + this.selectedTab = tab; + this.keyValueStore.set({ + key: `more-topics-preference-${this.context}`, + value: tab.id, + }); + } + + +} diff --git a/app/assets/javascripts/discourse/app/components/more-topics.hbs b/app/assets/javascripts/discourse/app/components/more-topics.hbs deleted file mode 100644 index 8ae128cdb5a..00000000000 --- a/app/assets/javascripts/discourse/app/components/more-topics.hbs +++ /dev/null @@ -1,49 +0,0 @@ -
- {{#unless this.singleList}} -
- -
- {{/unless}} - -
- {{#if @topic.relatedMessages.length}} - - {{/if}} - - {{#if @topic.suggestedTopics.length}} - - - - - - {{/if}} - - - -
- - {{#if @topic.suggestedTopics.length}} -

- {{html-safe this.browseMoreMessage}} -

- {{/if}} -
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/more-topics.js b/app/assets/javascripts/discourse/app/components/more-topics/browse-more.gjs similarity index 63% rename from app/assets/javascripts/discourse/app/components/more-topics.js rename to app/assets/javascripts/discourse/app/components/more-topics/browse-more.gjs index 67559f38d3e..45d744ade66 100644 --- a/app/assets/javascripts/discourse/app/components/more-topics.js +++ b/app/assets/javascripts/discourse/app/components/more-topics/browse-more.gjs @@ -1,52 +1,25 @@ import Component from "@glimmer/component"; -import { action, computed } from "@ember/object"; import { service } from "@ember/service"; +import { htmlSafe } from "@ember/template"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; import getURL from "discourse-common/lib/get-url"; import { iconHTML } from "discourse-common/lib/icon-library"; import I18n from "discourse-i18n"; -export default class MoreTopics extends Component { - @service site; - @service moreTopicsPreferenceTracking; - @service pmTopicTrackingState; - @service topicTrackingState; +export default class BrowseMore extends Component { @service currentUser; + @service pmTopicTrackingState; + @service site; + @service topicTrackingState; - @action - rememberTopicListPreference(value) { - this.moreTopicsPreferenceTracking.updatePreference(value); + groupLink(groupName) { + return `${iconHTML("users")} ${groupName}`; } - @computed("moreTopicsPreferenceTracking.topicLists") - get singleList() { - return this.availableTabs.length === 1; - } - - @computed("moreTopicsPreferenceTracking.selectedTab") - get selectedTab() { - return this.moreTopicsPreferenceTracking.selectedTab; - } - - @computed("moreTopicsPreferenceTracking.topicLists") - get availableTabs() { - return this.moreTopicsPreferenceTracking.topicLists; - } - - @computed( - "pmTopicTrackingState.isTracking", - "pmTopicTrackingState.statesModificationCounter", - "topicTrackingState.messageCount" - ) - get browseMoreMessage() { - return this.args.topic.isPrivateMessage - ? this._privateMessageBrowseMoreMessage() - : this._topicBrowseMoreMessage(); - } - - _privateMessageBrowseMoreMessage() { - const username = this.currentUser.username; - const suggestedGroupName = this.args.topic.suggested_group_name; + get privateMessageBrowseMoreMessage() { + const suggestedGroupName = this.args.topic.get("suggested_group_name"); const inboxFilter = suggestedGroupName ? "group" : "user"; const unreadCount = this.pmTopicTrackingState.lookupCount("unread", { @@ -67,9 +40,9 @@ export default class MoreTopics extends Component { HAS_UNREAD_AND_NEW: hasBoth, UNREAD: unreadCount, NEW: newCount, - username, + username: this.currentUser.username, groupName: suggestedGroupName, - groupLink: this._groupLink(username, suggestedGroupName), + groupLink: this.groupLink(suggestedGroupName), basePath: getURL(""), }); } else { @@ -77,24 +50,24 @@ export default class MoreTopics extends Component { HAS_UNREAD_AND_NEW: hasBoth, UNREAD: unreadCount, NEW: newCount, - username, + username: this.currentUser.username, basePath: getURL(""), }); } } else if (suggestedGroupName) { return I18n.t("user.messages.read_more_in_group", { - groupLink: this._groupLink(username, suggestedGroupName), + groupLink: this.groupLink(suggestedGroupName), }); } else { return I18n.t("user.messages.read_more", { basePath: getURL(""), - username, + username: this.currentUser.username, }); } } - _topicBrowseMoreMessage() { - let category = this.args.topic.category; + get topicBrowseMoreMessage() { + let category = this.args.topic.get("category"); if (category && category.id === this.site.uncategorized_category_id) { category = null; @@ -113,7 +86,7 @@ export default class MoreTopics extends Component { HAS_UNREAD_AND_NEW: unreadTopics > 0 && newTopics > 0, UNREAD: unreadTopics, NEW: newTopics, - HAS_CATEGORY: category ? true : false, + HAS_CATEGORY: !!category, categoryLink: category ? categoryBadgeHTML(category) : null, basePath: getURL(""), }); @@ -130,9 +103,13 @@ export default class MoreTopics extends Component { } } - _groupLink(username, groupName) { - return `${iconHTML("users")} ${groupName}`; - } + } diff --git a/app/assets/javascripts/discourse/app/components/related-messages.gjs b/app/assets/javascripts/discourse/app/components/related-messages.gjs index d3bb638dd2d..506cec6b512 100644 --- a/app/assets/javascripts/discourse/app/components/related-messages.gjs +++ b/app/assets/javascripts/discourse/app/components/related-messages.gjs @@ -1,56 +1,33 @@ import Component from "@glimmer/component"; import { cached } from "@glimmer/tracking"; -import { action } from "@ember/object"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; -import willDestroy from "@ember/render-modifiers/modifiers/will-destroy"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; import BasicTopicList from "discourse/components/basic-topic-list"; -import concatClass from "discourse/helpers/concat-class"; import i18n from "discourse-common/helpers/i18n"; import getURL from "discourse-common/lib/get-url"; -import I18n from "discourse-i18n"; - -const LIST_ID = "related-Messages"; export default class RelatedMessages extends Component { - @service moreTopicsPreferenceTracking; @service currentUser; - get hidden() { - return this.moreTopicsPreferenceTracking.get("selectedTab") !== LIST_ID; - } - - @action - registerList() { - this.moreTopicsPreferenceTracking.registerTopicList({ - name: I18n.t("related_messages.pill"), - id: LIST_ID, - }); - } - - @action - removeList() { - this.moreTopicsPreferenceTracking.removeTopicList(LIST_ID); - } - @cached get targetUser() { - const topic = this.args.topic; + const { topic } = this.args; if (!topic || !topic.isPrivateMessage) { return; } - const allowedUsers = topic.details.allowed_users; - if ( topic.relatedMessages?.length >= 5 && - allowedUsers.length === 2 && topic.details.allowed_groups.length === 0 && - allowedUsers.find((u) => u.username === this.currentUser.username) + topic.details.allowed_users.length === 2 && + topic.details.allowed_users.find( + (u) => u.username === this.currentUser.username + ) ) { - return allowedUsers.find((u) => u.username !== this.currentUser.username); + return topic.details.allowed_users.find( + (u) => u.username !== this.currentUser.username + ); } } @@ -62,12 +39,10 @@ export default class RelatedMessages extends Component {