DEV: Plugin API method to replace post-menu buttons (#22995)

This commit is contained in:
Mark VanLandingham 2023-08-08 14:00:45 -05:00 committed by GitHub
parent 5b6493ff4b
commit 1b63e046af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 11 deletions

View File

@ -8,6 +8,7 @@ import {
addButton, addButton,
apiExtraButtons, apiExtraButtons,
removeButton, removeButton,
replaceButton,
} from "discourse/widgets/post-menu"; } from "discourse/widgets/post-menu";
import { import {
addExtraIconRenderer, addExtraIconRenderer,
@ -129,7 +130,7 @@ import { _addBulkButton } from "discourse/components/modal/topic-bulk-actions";
// based on Semantic Versioning 2.0.0. Please update the changelog at // based on Semantic Versioning 2.0.0. Please update the changelog at
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
// using the format described at https://keepachangelog.com/en/1.0.0/. // using the format described at https://keepachangelog.com/en/1.0.0/.
export const PLUGIN_API_VERSION = "1.8.0"; export const PLUGIN_API_VERSION = "1.8.1";
// This helper prevents us from applying the same `modifyClass` over and over in test mode. // This helper prevents us from applying the same `modifyClass` over and over in test mode.
function canModify(klass, type, resolverName, changes) { function canModify(klass, type, resolverName, changes) {
@ -656,6 +657,26 @@ class PluginApi {
removeButton(name, callback); removeButton(name, callback);
} }
/**
* Replace an existing button with a widget
*
* Example:
* ```
* api.replacePostMenuButton("like", {
* name: "widget-name",
* buildAttrs: (widget) => {
* return { post: widget.findAncestorModel() };
* },
* shouldRender: (widget) => {
* const post = widget.findAncestorModel();
* return post.id === 1
* }
* });
**/
replacePostMenuButton(name, widget) {
replaceButton(name, widget);
}
/** /**
* A hook that is called when the editor toolbar is created. You can * A hook that is called when the editor toolbar is created. You can
* use this to add custom editor buttons. * use this to add custom editor buttons.

View File

@ -20,6 +20,7 @@ const _builders = {};
export let apiExtraButtons = {}; export let apiExtraButtons = {};
let _extraButtons = {}; let _extraButtons = {};
let _buttonsToRemoveCallbacks = {}; let _buttonsToRemoveCallbacks = {};
let _buttonsToReplace = {};
export function addButton(name, builder) { export function addButton(name, builder) {
_extraButtons[name] = builder; _extraButtons[name] = builder;
@ -32,6 +33,7 @@ export function resetPostMenuExtraButtons() {
_extraButtons = {}; _extraButtons = {};
_buttonsToRemoveCallbacks = {}; _buttonsToRemoveCallbacks = {};
_buttonsToReplace = {};
} }
export function removeButton(name, callback) { export function removeButton(name, callback) {
@ -40,6 +42,10 @@ export function removeButton(name, callback) {
_buttonsToRemoveCallbacks[name].push(callback || (() => true)); _buttonsToRemoveCallbacks[name].push(callback || (() => true));
} }
export function replaceButton(name, replaceWith) {
_buttonsToReplace[name] = replaceWith;
}
function registerButton(name, builder) { function registerButton(name, builder) {
_builders[name] = builder; _builders[name] = builder;
} }
@ -47,17 +53,28 @@ function registerButton(name, builder) {
export function buildButton(name, widget) { export function buildButton(name, widget) {
let { attrs, state, siteSettings, settings, currentUser } = widget; let { attrs, state, siteSettings, settings, currentUser } = widget;
let shouldAddButton = true; // Return early if the button is supposed to be removed via the plugin API
if (
if (_buttonsToRemoveCallbacks[name]) { _buttonsToRemoveCallbacks[name] &&
shouldAddButton = !_buttonsToRemoveCallbacks[name].some((c) => _buttonsToRemoveCallbacks[name].some((c) =>
c(attrs, state, siteSettings, settings, currentUser) c(attrs, state, siteSettings, settings, currentUser)
); )
) {
return;
}
// Look for a button replacement, build and return widget attrs if present
let replacement = _buttonsToReplace[name];
if (replacement && replacement?.shouldRender(widget)) {
return {
replaced: true,
name: replacement.name,
attrs: replacement.buildAttrs(widget),
};
} }
let builder = _builders[name]; let builder = _builders[name];
if (builder) {
if (shouldAddButton && builder) {
let button = builder(attrs, state, siteSettings, settings, currentUser); let button = builder(attrs, state, siteSettings, settings, currentUser);
if (button && !button.id) { if (button && !button.id) {
button.id = name; button.id = name;
@ -438,7 +455,7 @@ registerButton("delete", (attrs) => {
} }
}); });
function replaceButton(buttons, find, replace) { function _replaceButton(buttons, find, replace) {
const idx = buttons.indexOf(find); const idx = buttons.indexOf(find);
if (idx !== -1) { if (idx !== -1) {
buttons[idx] = replace; buttons[idx] = replace;
@ -468,6 +485,13 @@ export default createWidget("post-menu", {
attachButton(name) { attachButton(name) {
let buttonAtts = buildButton(name, this); let buttonAtts = buildButton(name, this);
// If the button is replaced via the plugin API, we need to render the
// replacement rather than a button
if (buttonAtts?.replaced) {
return this.attach(buttonAtts.name, buttonAtts.attrs);
}
if (buttonAtts) { if (buttonAtts) {
let button = this.attach(this.settings.buttonType, buttonAtts); let button = this.attach(this.settings.buttonType, buttonAtts);
if (buttonAtts.before) { if (buttonAtts.before) {
@ -509,8 +533,8 @@ export default createWidget("post-menu", {
// If the post is a wiki, make Edit more prominent // If the post is a wiki, make Edit more prominent
if (attrs.wiki && attrs.canEdit) { if (attrs.wiki && attrs.canEdit) {
replaceButton(orderedButtons, "edit", "reply-small"); _replaceButton(orderedButtons, "edit", "reply-small");
replaceButton(orderedButtons, "reply", "wiki-edit"); _replaceButton(orderedButtons, "reply", "wiki-edit");
} }
orderedButtons.forEach((i) => { orderedButtons.forEach((i) => {

View File

@ -5,6 +5,8 @@ import { count, exists } from "discourse/tests/helpers/qunit-helpers";
import { hbs } from "ember-cli-htmlbars"; import { hbs } from "ember-cli-htmlbars";
import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu"; import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
import { createWidget } from "discourse/widgets/widget";
import { h } from "virtual-dom";
module("Integration | Component | Widget | post-menu", function (hooks) { module("Integration | Component | Widget | post-menu", function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
@ -88,4 +90,51 @@ module("Integration | Component | Widget | post-menu", function (hooks) {
assert.ok(!exists(".actions .reply"), "it removes reply button"); assert.ok(!exists(".actions .reply"), "it removes reply button");
}); });
createWidget("post-menu-replacement", {
html(attrs) {
return h("h1.post-menu-replacement", {}, attrs.id);
},
});
test("buttons are replaced when shouldRender is true", async function (assert) {
this.set("args", { id: 1, canCreatePost: true });
withPluginApi("0.14.0", (api) => {
api.replacePostMenuButton("reply", {
name: "post-menu-replacement",
buildAttrs: (widget) => {
return widget.attrs;
},
shouldRender: (widget) => widget.attrs.id === 1, // true!
});
});
await render(hbs`<MountWidget @widget="post-menu" @args={{this.args}} />`);
assert.ok(exists("h1.post-menu-replacement"), "replacement is rendered");
assert.ok(!exists(".actions .reply"), "reply button is replaced button");
});
test("buttons are not replaced when shouldRender is false", async function (assert) {
this.set("args", { id: 1, canCreatePost: true, canRemoveReply: false });
withPluginApi("0.14.0", (api) => {
api.replacePostMenuButton("reply", {
name: "post-menu-replacement",
buildAttrs: (widget) => {
return widget.attrs;
},
shouldRender: (widget) => widget.attrs.id === 102323948, // false!
});
});
await render(hbs`<MountWidget @widget="post-menu" @args={{this.args}} />`);
assert.ok(
!exists("h1.post-menu-replacement"),
"replacement is not rendered"
);
assert.ok(exists(".actions .reply"), "reply button is present");
});
}); });

View File

@ -7,6 +7,20 @@ in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.8.1] - 2023-08-08
### Added
- Adds `replacePostMenuButton` which allows plugins to replace a post menu button with a widget.
## [1.8.0] - 2023-07-18
### Added
- Adds `addSidebarPanel` which is experimental, and adds a Sidebar panel by returning a class which extends from the
BaseCustomSidebarPanel class.
- Adds `setSidebarPanel` which is experimental, and sets the current sidebar panel.
## [1.7.1] - 2023-07-18 ## [1.7.1] - 2023-07-18
### Added ### Added