DEV: Added chat api to remove secondary actions (#21982)

In some cases, plugins may want to hide some of these actions
at all times, overriding the rules for canX with hiding these
buttons. To achieve this, a plugin can call the API
`removeChatComposerSecondaryButtons` and pass the list of button
IDs that should be removed as argument, like the example below:

```
withPluginApi("1.2.0", (api) => {
  api.removeChatComposerSecondaryActions("copyLink", "select");
});
```

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
This commit is contained in:
Sérgio Saquetim 2023-06-08 11:37:50 -03:00 committed by GitHub
parent 44eabde12f
commit e306a521fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 19 deletions

View File

@ -10,7 +10,15 @@
}}
data-id={{this.message.id}}
>
<div class="chat-message-actions">
<div
class={{concat-class
"chat-message-actions"
(unless
this.messageInteractor.secondaryActions.length
"has-no-secondary-actions"
)
}}
>
{{#if this.shouldRenderFavoriteReactions}}
{{#each
this.messageInteractor.emojiReactions
@ -57,14 +65,14 @@
{{#if
(and
this.messageInteractor.message
this.messageInteractor.secondaryButtons.length
this.messageInteractor.secondaryActions.length
)
}}
<DropdownSelectBox
@class="more-buttons"
@class="more-buttons secondary-actions"
@options={{hash icon="ellipsis-v" placement="left"}}
@content={{this.messageInteractor.secondaryButtons}}
@onChange={{action this.messageInteractor.handleSecondaryButtons}}
@content={{this.messageInteractor.secondaryActions}}
@onChange={{action this.messageInteractor.handleSecondaryActions}}
/>
{{/if}}
</div>

View File

@ -31,12 +31,10 @@ export default class ChatMessageActionsDesktop extends Component {
}
get messageInteractor() {
const activeMessage = this.chat.activeMessage;
return new ChatMessageInteractor(
getOwner(this),
activeMessage.model,
activeMessage.context
this.message,
this.context
);
}

View File

@ -31,7 +31,7 @@
</div>
<ul class="secondary-actions">
{{#each this.messageInteractor.secondaryButtons as |button|}}
{{#each this.messageInteractor.secondaryActions as |button|}}
<li class="chat-message-action-item" data-id={{button.id}}>
<DButton
@class="chat-message-action"

View File

@ -21,13 +21,15 @@ export default class ChatMessageActionsMobile extends Component {
return this.chat.activeMessage.model;
}
get messageInteractor() {
const activeMessage = this.chat.activeMessage;
get context() {
return this.chat.activeMessage.context;
}
get messageInteractor() {
return new ChatMessageInteractor(
getOwner(this),
activeMessage.model,
activeMessage.context
this.message,
this.context
);
}

View File

@ -18,6 +18,16 @@ import ChatMessage from "discourse/plugins/chat/discourse/models/chat-message";
import { MESSAGE_CONTEXT_THREAD } from "discourse/plugins/chat/discourse/components/chat-message";
import I18n from "I18n";
const removedSecondaryActions = new Set();
export function removeChatComposerSecondaryActions(actionIds) {
actionIds.forEach((id) => removedSecondaryActions.add(id));
}
export function resetRemovedChatComposerSecondaryActions() {
removedSecondaryActions.clear();
}
export default class ChatMessageInteractor {
@service appEvents;
@service dialog;
@ -147,7 +157,7 @@ export default class ChatMessageInteractor {
: this.chatChannelComposer;
}
get secondaryButtons() {
get secondaryActions() {
const buttons = [];
buttons.push({
@ -204,7 +214,7 @@ export default class ChatMessageInteractor {
});
}
return buttons;
return buttons.reject((button) => removedSecondaryActions.has(button.id));
}
select(checked = true) {
@ -377,7 +387,7 @@ export default class ChatMessageInteractor {
}
@action
handleSecondaryButtons(id) {
handleSecondaryActions(id) {
this[id](this.message);
}
}

View File

@ -5,6 +5,7 @@ import {
} from "discourse/plugins/chat/discourse/components/chat-message";
import { registerChatComposerButton } from "discourse/plugins/chat/discourse/lib/chat-composer-buttons";
import { addChatDrawerStateCallback } from "discourse/plugins/chat/discourse/services/chat-state-manager";
import { removeChatComposerSecondaryActions } from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
/**
* Class exposing the javascript API available to plugins and themes.
@ -111,6 +112,18 @@ import { addChatDrawerStateCallback } from "discourse/plugins/chat/discourse/ser
* );
*/
/**
* Removes secondary actions from the chat composer
*
* @memberof PluginApi
* @instance
* @function removeChatComposerSecondaryActions
* @param {...string} [1] - List of secondary action ids to remove, eg: `"copyLink", "select"
* @example
*
* api.removeChatComposerSecondaryActions("copyLink", "select");
*/
export default {
name: "chat-plugin-api",
after: "inject-discourse-objects",
@ -156,6 +169,18 @@ export default {
},
});
}
if (!apiPrototype.hasOwnProperty("removeChatComposerSecondaryActions")) {
Object.defineProperty(
apiPrototype,
"removeChatComposerSecondaryActions",
{
value(...actionIds) {
removeChatComposerSecondaryActions(actionIds);
},
}
);
}
});
},

View File

@ -76,6 +76,15 @@
}
}
&.has-no-secondary-actions {
.reply-btn {
border-right: 1px solid var(--primary-300);
border-top: 1px solid var(--primary-300);
border-bottom: 1px solid var(--primary-300);
border-radius: 0 0.25em 0.25em 0;
}
}
.more-buttons.dropdown-select-box {
.select-kit-header {
background: none;

View File

@ -1,7 +1,14 @@
import { module, test } from "qunit";
import { withPluginApi } from "discourse/lib/plugin-api";
import { setupTest } from "ember-qunit";
import { module, test } from "qunit";
import { getOwner } from "discourse-common/lib/get-owner";
import { withPluginApi } from "discourse/lib/plugin-api";
import User from "discourse/models/user";
import ChatMessageInteractor, {
resetRemovedChatComposerSecondaryActions,
} from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
import pretender from "discourse/tests/helpers/create-pretender";
import { logIn } from "discourse/tests/helpers/qunit-helpers";
module("Chat | Unit | Utility | plugin-api", function (hooks) {
setupTest(hooks);
@ -20,4 +27,52 @@ module("Chat | Unit | Utility | plugin-api", function (hooks) {
await api.sendChatMessage(1, { message: "hello", threadId: 2 });
});
});
test("#removeChatComposerSecondaryActions", async function (assert) {
withPluginApi("1.1.0", async (api) => {
// assert that the api method is defined
assert.equal(typeof api.removeChatComposerSecondaryActions, "function");
logIn();
const currentUser = User.current();
getOwner(this).unregister("service:current-user");
getOwner(this).register("service:current-user", currentUser, {
instantiate: false,
});
const message = fabricators.message({ user: currentUser });
const context = "channel";
const interactor = new ChatMessageInteractor(
getOwner(this),
message,
context
);
// assert that the initial secondary actions are present
const secondaryActions = interactor.secondaryActions;
assert.ok(secondaryActions.length > 0);
try {
// remove the first secondary action listed
api.removeChatComposerSecondaryActions(secondaryActions[0].id);
const updatedSecondaryActions = interactor.secondaryActions;
// assert that the secondary action was removed
assert.ok(
updatedSecondaryActions.length < secondaryActions.length,
"the updated secondary actions must contain less items than the original"
);
assert.notOk(
updatedSecondaryActions
.map((v) => v.id)
.includes(secondaryActions[0]),
"the updated secondary actions must not include the removed action"
);
} finally {
// reset the secondary actions removed to prevent leakage to other tests
resetRemovedChatComposerSecondaryActions();
}
});
});
});