mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 19:37:55 +08:00
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: <template>tbd</template>, 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" ); } }); ```
This commit is contained in:
parent
7a936da05c
commit
9c5fc6f1df
103
app/assets/javascripts/discourse/app/components/more-topics.gjs
Normal file
103
app/assets/javascripts/discourse/app/components/more-topics.gjs
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="more-topics__container">
|
||||
{{#if (gt this.tabs.length 1)}}
|
||||
<div class="row">
|
||||
<ul class="nav nav-pills">
|
||||
{{#each this.tabs as |tab|}}
|
||||
<li>
|
||||
<DButton
|
||||
@action={{fn this.selectTab tab}}
|
||||
@translatedLabel={{tab.name}}
|
||||
@translatedTitle={{tab.name}}
|
||||
@icon={{tab.icon}}
|
||||
class={{if (eq tab.id this.activeTab.id) "active"}}
|
||||
/>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.activeTab}}
|
||||
<div
|
||||
class={{concatClass
|
||||
"more-topics__lists"
|
||||
(if (eq this.tabs.length 1) "single-list")
|
||||
}}
|
||||
>
|
||||
<this.activeTab.component @topic={{@topic}} />
|
||||
</div>
|
||||
|
||||
{{#if @topic.suggestedTopics.length}}
|
||||
<BrowseMore @topic={{@topic}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
<div class="more-topics__container">
|
||||
{{#unless this.singleList}}
|
||||
<div class="row">
|
||||
<ul class="nav nav-pills">
|
||||
{{#each this.availableTabs as |tab|}}
|
||||
<li>
|
||||
<DButton
|
||||
@translatedTitle={{tab.name}}
|
||||
@translatedLabel={{tab.name}}
|
||||
@action={{fn this.rememberTopicListPreference tab.id}}
|
||||
@icon={{tab.icon}}
|
||||
class={{if (eq tab.id this.selectedTab) "active"}}
|
||||
/>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="more-topics__lists {{if this.singleList 'single-list'}}">
|
||||
{{#if @topic.relatedMessages.length}}
|
||||
<RelatedMessages @topic={{@topic}} />
|
||||
{{/if}}
|
||||
|
||||
{{#if @topic.suggestedTopics.length}}
|
||||
<SuggestedTopics @topic={{@topic}} />
|
||||
|
||||
<span>
|
||||
<PluginOutlet
|
||||
@name="below-suggested-topics"
|
||||
@connectorTagName="div"
|
||||
@outletArgs={{hash topic=@topic}}
|
||||
/>
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="topic-more-content"
|
||||
@outletArgs={{hash model=@topic}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{{#if @topic.suggestedTopics.length}}
|
||||
<h3 class="more-topics__browse-more">
|
||||
{{html-safe this.browseMoreMessage}}
|
||||
</h3>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -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 `<a class="group-link" href="${getURL(
|
||||
`/u/${this.currentUser.username}/messages/group/${groupName}`
|
||||
)}">${iconHTML("users")} ${groupName}</a>`;
|
||||
}
|
||||
|
||||
@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 `<a class="group-link" href="${getURL(
|
||||
`/u/${username}/messages/group/${groupName}`
|
||||
)}">${iconHTML("users")} ${groupName}</a>`;
|
||||
}
|
||||
<template>
|
||||
<h3 class="more-topics__browse-more">
|
||||
{{#if @topic.isPrivateMessage}}
|
||||
{{htmlSafe this.privateMessageBrowseMoreMessage}}
|
||||
{{else}}
|
||||
{{htmlSafe this.topicBrowseMoreMessage}}
|
||||
{{/if}}
|
||||
</h3>
|
||||
</template>
|
||||
}
|
|
@ -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 {
|
|||
|
||||
<template>
|
||||
<div
|
||||
id="related-messages"
|
||||
class={{concatClass "more-topics__list" (if this.hidden "hidden")}}
|
||||
role="complementary"
|
||||
aria-labelledby="related-messages-title"
|
||||
{{didInsert this.registerList}}
|
||||
{{willDestroy this.removeList}}
|
||||
id="related-messages"
|
||||
class="more-topics__list"
|
||||
>
|
||||
<h3 id="related-messages-title" class="more-topics__list-title">
|
||||
{{i18n "related_messages.title"}}
|
||||
|
@ -75,9 +50,9 @@ export default class RelatedMessages extends Component {
|
|||
|
||||
<div class="topics">
|
||||
<BasicTopicList
|
||||
@hideCategory="true"
|
||||
@showPosters="true"
|
||||
@topics={{@topic.relatedMessages}}
|
||||
@hideCategory={{true}}
|
||||
@showPosters={{true}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import BasicTopicList from "discourse/components/basic-topic-list";
|
||||
import UserTip from "discourse/components/user-tip";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default class SuggestedTopics extends Component {
|
||||
@service currentUser;
|
||||
|
||||
get suggestedTitle() {
|
||||
const href = this.currentUser?.pmPath(this.args.topic);
|
||||
if (href && this.args.topic.isPrivateMessage) {
|
||||
return i18n("suggested_topics.pm_title");
|
||||
} else {
|
||||
return i18n("suggested_topics.title");
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="complementary"
|
||||
aria-labelledby="suggested-topics-title"
|
||||
id="suggested-topics"
|
||||
class="more-topics__list"
|
||||
>
|
||||
<UserTip
|
||||
@id="suggested_topics"
|
||||
@titleText={{i18n "user_tips.suggested_topics.title"}}
|
||||
@contentText={{i18n "user_tips.suggested_topics.content"}}
|
||||
@placement="top-start"
|
||||
@priority={{700}}
|
||||
/>
|
||||
|
||||
<h3 id="suggested-topics-title" class="more-topics__list-title">
|
||||
{{this.suggestedTitle}}
|
||||
</h3>
|
||||
|
||||
<div class="topics">
|
||||
{{#if @topic.isPrivateMessage}}
|
||||
<BasicTopicList
|
||||
@topics={{@topic.suggestedTopics}}
|
||||
@hideCategory={{true}}
|
||||
@showPosters={{true}}
|
||||
/>
|
||||
{{else}}
|
||||
<BasicTopicList @topics={{@topic.suggestedTopics}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<div
|
||||
id="suggested-topics"
|
||||
class="more-topics__list {{if this.hidden 'hidden'}}"
|
||||
role="complementary"
|
||||
aria-labelledby="suggested-topics-title"
|
||||
{{did-insert this.registerList}}
|
||||
{{will-destroy this.removeList}}
|
||||
>
|
||||
{{#unless this.hidden}}
|
||||
<UserTip
|
||||
@id="suggested_topics"
|
||||
@titleText={{i18n "user_tips.suggested_topics.title"}}
|
||||
@contentText={{i18n "user_tips.suggested_topics.content"}}
|
||||
@placement="top-start"
|
||||
@priority={{700}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<h3 id="suggested-topics-title" class="more-topics__list-title">
|
||||
{{i18n this.suggestedTitleLabel}}
|
||||
</h3>
|
||||
|
||||
<div class="topics">
|
||||
{{#if @topic.isPrivateMessage}}
|
||||
<BasicTopicList
|
||||
@hideCategory="true"
|
||||
@showPosters="true"
|
||||
@topics={{@topic.suggestedTopics}}
|
||||
/>
|
||||
{{else}}
|
||||
<BasicTopicList @topics={{@topic.suggestedTopics}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -1,38 +0,0 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class SuggestedTopics extends Component {
|
||||
@service moreTopicsPreferenceTracking;
|
||||
@service currentUser;
|
||||
|
||||
listId = "suggested-topics";
|
||||
|
||||
get suggestedTitleLabel() {
|
||||
const href = this.currentUser && this.currentUser.pmPath(this.args.topic);
|
||||
if (this.args.topic.isPrivateMessage && href) {
|
||||
return "suggested_topics.pm_title";
|
||||
} else {
|
||||
return "suggested_topics.title";
|
||||
}
|
||||
}
|
||||
|
||||
@computed("moreTopicsPreferenceTracking.selectedTab")
|
||||
get hidden() {
|
||||
return this.moreTopicsPreferenceTracking.selectedTab !== this.listId;
|
||||
}
|
||||
|
||||
@action
|
||||
registerList() {
|
||||
this.moreTopicsPreferenceTracking.registerTopicList({
|
||||
name: I18n.t("suggested_topics.pill"),
|
||||
id: this.listId,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
removeList() {
|
||||
this.moreTopicsPreferenceTracking.removeTopicList(this.listId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import RelatedMessages from "discourse/components/related-messages";
|
||||
import SuggestedTopics from "discourse/components/suggested-topics";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default {
|
||||
initialize() {
|
||||
withPluginApi("1.37.2", (api) => {
|
||||
api.registerMoreTopicsTab({
|
||||
id: "related-messages",
|
||||
name: i18n("related_messages.pill"),
|
||||
component: RelatedMessages,
|
||||
condition: ({ context, topic }) =>
|
||||
context === "pm" && topic.relatedMessages?.length > 0,
|
||||
});
|
||||
|
||||
api.registerMoreTopicsTab({
|
||||
id: "suggested-topics",
|
||||
name: i18n("suggested_topics.pill"),
|
||||
component: SuggestedTopics,
|
||||
condition: ({ topic }) => topic.suggestedTopics?.length > 0,
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
||||
|
||||
export const PLUGIN_API_VERSION = "1.37.3";
|
||||
export const PLUGIN_API_VERSION = "1.38.0";
|
||||
|
||||
import $ from "jquery";
|
||||
import { h } from "virtual-dom";
|
||||
|
@ -23,6 +23,7 @@ import { forceDropdownForMenuPanels as glimmerForceDropdownForMenuPanels } from
|
|||
import { addGlobalNotice } from "discourse/components/global-notice";
|
||||
import { headerButtonsDAG } from "discourse/components/header";
|
||||
import { headerIconsDAG } from "discourse/components/header/icons";
|
||||
import { registeredTabs } from "discourse/components/more-topics";
|
||||
import { addWidgetCleanCallback } from "discourse/components/mount-widget";
|
||||
import { addPluginOutletDecorator } from "discourse/components/plugin-connector";
|
||||
import {
|
||||
|
@ -3286,6 +3287,50 @@ class PluginApi {
|
|||
registerPluginHeaderActionComponent(pluginId, componentClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: <template>tbd</template>,
|
||||
* 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"
|
||||
* );
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @callback tabCondition
|
||||
* @param {Object} opts
|
||||
* @param {"topic"|"pm"} opts.context - the type of the current page
|
||||
* @param {Topic} opts.topic - the current topic
|
||||
*
|
||||
* @param {Object} tab
|
||||
* @param {string} tab.id - an identifier used in more-topics-tabs value transformer
|
||||
* @param {string} tab.name - a name displayed on the tab
|
||||
* @param {string} tab.icon - an optional icon displayed on the tab
|
||||
* @param {Class} tab.component - contents of the tab
|
||||
* @param {tabCondition} tab.condition - an optional callback to conditionally show the tab
|
||||
*/
|
||||
registerMoreTopicsTab(tab) {
|
||||
registeredTabs.push(tab);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
#deprecatedWidgetOverride(widgetName, override) {
|
||||
// insert here the code to handle widget deprecations, e.g. for the header widgets we used:
|
||||
|
|
|
@ -7,8 +7,9 @@ export const VALUE_TRANSFORMERS = Object.freeze([
|
|||
// use only lowercase names
|
||||
"category-description-text",
|
||||
"category-display-name",
|
||||
"mentions-class",
|
||||
"header-notifications-avatar-size",
|
||||
"home-logo-href",
|
||||
"home-logo-image-url",
|
||||
"mentions-class",
|
||||
"more-topics-tabs",
|
||||
]);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { cached } from "@glimmer/tracking";
|
||||
import EmberObject, { computed } from "@ember/object";
|
||||
import { dependentKeyCompat } from "@ember/object/compat";
|
||||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import { Promise } from "rsvp";
|
||||
|
@ -430,18 +432,20 @@ export default class Topic extends RestModel {
|
|||
return newTags;
|
||||
}
|
||||
|
||||
@discourseComputed("related_messages")
|
||||
relatedMessages(relatedMessages) {
|
||||
if (relatedMessages) {
|
||||
return relatedMessages.map((st) => this.store.createRecord("topic", st));
|
||||
}
|
||||
@dependentKeyCompat
|
||||
@cached
|
||||
get relatedMessages() {
|
||||
return this.get("related_messages")?.map((st) =>
|
||||
this.store.createRecord("topic", st)
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed("suggested_topics")
|
||||
suggestedTopics(suggestedTopics) {
|
||||
if (suggestedTopics) {
|
||||
return suggestedTopics.map((st) => this.store.createRecord("topic", st));
|
||||
}
|
||||
@dependentKeyCompat
|
||||
@cached
|
||||
get suggestedTopics() {
|
||||
return this.get("suggested_topics")?.map((st) =>
|
||||
this.store.createRecord("topic", st)
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed("posts_count")
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import Service, { service } from "@ember/service";
|
||||
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
|
||||
|
||||
const TOPIC_LIST_PREFERENCE_KEY = "more-topics-list-preference";
|
||||
|
||||
@disableImplicitInjections
|
||||
export default class MoreTopicsPreferenceTracking extends Service {
|
||||
@service keyValueStore;
|
||||
|
||||
@tracked selectedTab = null;
|
||||
@tracked topicLists = [];
|
||||
|
||||
memoryTab = null;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
this.memoryTab = this.keyValueStore.get(TOPIC_LIST_PREFERENCE_KEY);
|
||||
}
|
||||
|
||||
updatePreference(value) {
|
||||
// Don't change the preference when selecting related PMs.
|
||||
// It messes with the topics pref.
|
||||
const rememberPref = value !== "related-messages";
|
||||
|
||||
if (rememberPref) {
|
||||
this.keyValueStore.set({ key: TOPIC_LIST_PREFERENCE_KEY, value });
|
||||
this.memoryTab = value;
|
||||
}
|
||||
|
||||
this.selectedTab = value;
|
||||
}
|
||||
|
||||
registerTopicList(item) {
|
||||
// We have a preference stored and the list exists.
|
||||
if (this.memoryTab && this.memoryTab === item.id) {
|
||||
this.selectedTab = item.id;
|
||||
}
|
||||
|
||||
// Use the first list as a default. Future lists may override this
|
||||
// if they match the stored preference.
|
||||
if (!this.selectedTab) {
|
||||
this.selectedTab = item.id;
|
||||
}
|
||||
|
||||
this.topicLists = [...this.topicLists, item];
|
||||
}
|
||||
|
||||
removeTopicList(itemId) {
|
||||
this.topicLists = this.topicLists.filter((item) => item.id !== itemId);
|
||||
|
||||
if (this.selectedTab === itemId) {
|
||||
this.selectedTab = this.topicLists[0]?.id;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { click, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { PLUGIN_API_VERSION, withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("More topics - Plugin API", function (needs) {
|
||||
needs.user();
|
||||
|
||||
test("displays the tabs", async function (assert) {
|
||||
withPluginApi(PLUGIN_API_VERSION, (api) => {
|
||||
api.registerMoreTopicsTab({
|
||||
id: "my-tab",
|
||||
name: "News",
|
||||
component: <template>hello there!</template>,
|
||||
condition: ({ context, topic }) =>
|
||||
context === "topic" && topic.id === 280,
|
||||
});
|
||||
|
||||
api.registerMoreTopicsTab({
|
||||
id: "my-pm-tab",
|
||||
name: "Other",
|
||||
component: <template>hi!</template>,
|
||||
condition: ({ context }) => context === "pm",
|
||||
});
|
||||
});
|
||||
|
||||
await visit("/t/-/280");
|
||||
assert.dom(".more-topics__container li").exists({ count: 2 });
|
||||
assert.dom(".more-topics__container li:last-of-type").hasText("News");
|
||||
|
||||
await click(`.more-topics__container button[title="News"]`);
|
||||
assert.dom(".more-topics__lists").hasText("hello there!");
|
||||
|
||||
await visit("/t/-/12");
|
||||
assert.dom(".more-topics__container li").exists({ count: 2 });
|
||||
assert.dom(".more-topics__container li:last-of-type").hasText("Other");
|
||||
|
||||
await click(`.more-topics__container button[title="Other"]`);
|
||||
assert.dom(".more-topics__lists").hasText("hi!");
|
||||
|
||||
await visit("/t/-/54077");
|
||||
assert.dom(".more-topics__container li").doesNotExist();
|
||||
assert.dom(".more-topics__container #suggested-topics-title").exists();
|
||||
});
|
||||
});
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { clearToolbarCallbacks } from "discourse/components/d-editor";
|
||||
import { clearExtraHeaderButtons as clearExtraGlimmerHeaderButtons } from "discourse/components/header";
|
||||
import { clearExtraHeaderIcons as clearExtraGlimmerHeaderIcons } from "discourse/components/header/icons";
|
||||
import { clearRegisteredTabs } from "discourse/components/more-topics";
|
||||
import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget";
|
||||
import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector";
|
||||
import { resetItemSelectCallbacks } from "discourse/components/search-menu/results/assistant-item";
|
||||
|
@ -253,6 +254,7 @@ export function testCleanup(container, app) {
|
|||
clearAboutPageActivities();
|
||||
resetWidgetCleanCallbacks();
|
||||
clearPluginHeaderActionComponents();
|
||||
clearRegisteredTabs();
|
||||
}
|
||||
|
||||
function cleanupCssGeneratorTags() {
|
||||
|
|
|
@ -7,11 +7,14 @@ in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.38.0] - 2024-10-30
|
||||
|
||||
- Added `registerMoreTopicsTab` and "more-topics-tabs" value transformer that allows to add or remove new tabs to the "more topics" (suggested/related) area.
|
||||
|
||||
## [1.37.3] - 2024-10-24
|
||||
|
||||
- Added `disableDefaultKeyboardShortcuts` which allows plugins/TCs to disable default keyboard shortcuts.
|
||||
|
||||
|
||||
## [1.37.2] - 2024-10-02
|
||||
|
||||
- Fixed comments and text references to Font Awesome 5 in favor of the more generic Font Awesome due to core now having the latest version and no longer needing to specify version 5.
|
||||
|
|
Loading…
Reference in New Issue
Block a user