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,
apiExtraButtons,
removeButton,
replaceButton,
} from "discourse/widgets/post-menu";
import {
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
// 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.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.
function canModify(klass, type, resolverName, changes) {
@ -656,6 +657,26 @@ class PluginApi {
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
* use this to add custom editor buttons.

View File

@ -20,6 +20,7 @@ const _builders = {};
export let apiExtraButtons = {};
let _extraButtons = {};
let _buttonsToRemoveCallbacks = {};
let _buttonsToReplace = {};
export function addButton(name, builder) {
_extraButtons[name] = builder;
@ -32,6 +33,7 @@ export function resetPostMenuExtraButtons() {
_extraButtons = {};
_buttonsToRemoveCallbacks = {};
_buttonsToReplace = {};
}
export function removeButton(name, callback) {
@ -40,6 +42,10 @@ export function removeButton(name, callback) {
_buttonsToRemoveCallbacks[name].push(callback || (() => true));
}
export function replaceButton(name, replaceWith) {
_buttonsToReplace[name] = replaceWith;
}
function registerButton(name, builder) {
_builders[name] = builder;
}
@ -47,17 +53,28 @@ function registerButton(name, builder) {
export function buildButton(name, widget) {
let { attrs, state, siteSettings, settings, currentUser } = widget;
let shouldAddButton = true;
if (_buttonsToRemoveCallbacks[name]) {
shouldAddButton = !_buttonsToRemoveCallbacks[name].some((c) =>
// Return early if the button is supposed to be removed via the plugin API
if (
_buttonsToRemoveCallbacks[name] &&
_buttonsToRemoveCallbacks[name].some((c) =>
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];
if (shouldAddButton && builder) {
if (builder) {
let button = builder(attrs, state, siteSettings, settings, currentUser);
if (button && !button.id) {
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);
if (idx !== -1) {
buttons[idx] = replace;
@ -468,6 +485,13 @@ export default createWidget("post-menu", {
attachButton(name) {
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) {
let button = this.attach(this.settings.buttonType, buttonAtts);
if (buttonAtts.before) {
@ -509,8 +533,8 @@ export default createWidget("post-menu", {
// If the post is a wiki, make Edit more prominent
if (attrs.wiki && attrs.canEdit) {
replaceButton(orderedButtons, "edit", "reply-small");
replaceButton(orderedButtons, "reply", "wiki-edit");
_replaceButton(orderedButtons, "edit", "reply-small");
_replaceButton(orderedButtons, "reply", "wiki-edit");
}
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 { resetPostMenuExtraButtons } from "discourse/widgets/post-menu";
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) {
setupRenderingTest(hooks);
@ -88,4 +90,51 @@ module("Integration | Component | Widget | post-menu", function (hooks) {
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/),
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
### Added