diff options
author | Maksim Sukharev <antreesy.web@gmail.com> | 2024-06-19 11:54:15 +0200 |
---|---|---|
committer | Maksim Sukharev <antreesy.web@gmail.com> | 2024-06-19 14:31:35 +0200 |
commit | db3a2edf9757d494b89e9e2a0231b7883ad6269c (patch) | |
tree | 5a88196085e5ec6dc1b7d7debc6e8f2eec833230 /src | |
parent | 04b1d46a41affedd2065d98674beface67e437a3 (diff) |
feat(ban): ban participant from RightSidebar
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/RightSidebar/Participants/Participant.spec.js | 82 | ||||
-rw-r--r-- | src/components/RightSidebar/Participants/Participant.vue | 31 | ||||
-rw-r--r-- | src/store/participantsStore.js | 20 |
3 files changed, 130 insertions, 3 deletions
diff --git a/src/components/RightSidebar/Participants/Participant.spec.js b/src/components/RightSidebar/Participants/Participant.spec.js index 6dc3eaf88..b69bad0e1 100644 --- a/src/components/RightSidebar/Participants/Participant.spec.js +++ b/src/components/RightSidebar/Participants/Participant.spec.js @@ -14,7 +14,10 @@ import VideoIcon from 'vue-material-design-icons/Video.vue' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' +import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import Participant from './Participant.vue' import AvatarWrapper from '../../AvatarWrapper/AvatarWrapper.vue' @@ -107,7 +110,10 @@ describe('Participant.vue', () => { stubs: { NcActionButton, NcButton, + NcCheckboxRadioSwitch, NcDialog, + NcInputField, + NcTextField, }, directives: { tooltip: tooltipMock, @@ -639,6 +645,8 @@ describe('Participant.vue', () => { expect(removeAction).toHaveBeenCalledWith(expect.anything(), { token: 'current-token', attendeeId: 'alice-attendee-id', + banParticipant: false, + internalNote: '', }) } @@ -651,6 +659,56 @@ describe('Participant.vue', () => { expect(actionButton.exists()).toBe(false) } + /** + * @param {string} buttonText Label of the remove action to find + * @param {string} internalNote text of provided note + */ + async function testCanBan(buttonText = 'Remove participant', internalNote = 'test note') { + const wrapper = mountParticipant(participant) + const actionButton = findNcActionButton(wrapper, buttonText) + expect(actionButton.exists()).toBe(true) + + await actionButton.find('button').trigger('click') + + const dialog = wrapper.findComponent(NcDialog) + expect(dialog.exists()).toBeTruthy() + + const checkbox = dialog.findComponent(NcCheckboxRadioSwitch) + await checkbox.find('input').trigger('change') + + const input = dialog.findComponent(NcTextField) + expect(input.exists()).toBeTruthy() + input.find('input').setValue(internalNote) + await input.find('input').trigger('change') + + const button = findNcButton(dialog, 'Remove') + await button.find('button').trigger('click') + + expect(removeAction).toHaveBeenCalledWith(expect.anything(), { + token: 'current-token', + attendeeId: 'alice-attendee-id', + banParticipant: true, + internalNote + }) + } + + /** + * @param {string} buttonText Label of the remove action to find + */ + async function testCannotBan(buttonText = 'Remove participant') { + const wrapper = mountParticipant(participant) + const actionButton = findNcActionButton(wrapper, buttonText) + expect(actionButton.exists()).toBe(true) + + await actionButton.find('button').trigger('click') + + const dialog = wrapper.findComponent(NcDialog) + expect(dialog.exists()).toBeTruthy() + + const checkbox = dialog.findComponent(NcCheckboxRadioSwitch) + expect(checkbox.exists()).toBeFalsy() + } + test('allows a moderator to remove a moderator', async () => { conversation.participantType = PARTICIPANT.TYPE.MODERATOR participant.participantType = PARTICIPANT.TYPE.MODERATOR @@ -707,6 +765,30 @@ describe('Participant.vue', () => { conversation.participantType = PARTICIPANT.TYPE.USER await testCannotRemove() }) + + test('allows a moderator to ban a moderator', async () => { + conversation.participantType = PARTICIPANT.TYPE.MODERATOR + participant.participantType = PARTICIPANT.TYPE.USER + await testCanBan() + }) + + test('allows a moderator to ban a guest', async () => { + conversation.participantType = PARTICIPANT.TYPE.MODERATOR + participant.participantType = PARTICIPANT.TYPE.GUEST + await testCanBan() + }) + + test('does not allow a moderator to ban a moderator', async () => { + conversation.participantType = PARTICIPANT.TYPE.MODERATOR + participant.participantType = PARTICIPANT.TYPE.MODERATOR + await testCannotBan() + }) + + test('does not allow a moderator to ban a group', async () => { + conversation.participantType = PARTICIPANT.TYPE.MODERATOR + participant.actorType = ATTENDEE.ACTOR_TYPE.GROUPS + await testCannotBan('Remove group and members') + }) }) describe('dial-in PIN', () => { /** diff --git a/src/components/RightSidebar/Participants/Participant.vue b/src/components/RightSidebar/Participants/Participant.vue index 3d344ee2e..921258c3c 100644 --- a/src/components/RightSidebar/Participants/Participant.vue +++ b/src/components/RightSidebar/Participants/Participant.vue @@ -313,6 +313,17 @@ :name="removeParticipantLabel" :container="container"> <p> {{ removeDialogMessage }} </p> + <template v-if="supportBanV1 && showPermissionsOptions"> + <NcCheckboxRadioSwitch :checked.sync="isBanParticipant"> + {{ t('spreed', 'Also ban from this conversation') }} + </NcCheckboxRadioSwitch> + <template v-if="isBanParticipant"> + <NcTextField v-if="isBanParticipant" + class="participant-dialog__input" + :label="t('spreed', 'Internal note (reason to ban)')" + :value.sync="internalNote" /> + </template> + </template> <template #actions> <NcButton type="tertiary" @click="isRemoveDialogOpen = false"> {{ t('spreed', 'Dismiss') }} @@ -363,7 +374,9 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js' import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' import ParticipantPermissionsEditor from './ParticipantPermissionsEditor.vue' @@ -396,7 +409,9 @@ export default { NcActionText, NcActionSeparator, NcButton, + NcCheckboxRadioSwitch, NcDialog, + NcTextField, ParticipantPermissionsEditor, // Icons Account, @@ -470,6 +485,8 @@ export default { isStatusTooltipVisible: false, permissionsEditor: false, isRemoveDialogOpen: false, + isBanParticipant: false, + internalNote: '', disabled: false, } }, @@ -835,6 +852,10 @@ export default { || this.participant.actorType === ATTENDEE.ACTOR_TYPE.EMAILS) }, + supportBanV1() { + return hasTalkFeature(this.token, 'ban-v1') + }, + isLobbyEnabled() { return this.conversation.lobbyState === WEBINAR.LOBBY.NON_MODERATORS }, @@ -977,7 +998,11 @@ export default { await this.$store.dispatch('removeParticipant', { token: this.token, attendeeId: this.attendeeId, + banParticipant: this.isBanParticipant, + internalNote: this.internalNote, }) + this.isBanParticipant = false + this.internalNote = '' this.isRemoveDialogOpen = false }, @@ -1224,6 +1249,12 @@ export default { cursor: pointer; } +.participant-dialog { + &__input { + margin-block-end: 6px; + } +} + .utils { &__checkmark { margin-right: 11px; diff --git a/src/store/participantsStore.js b/src/store/participantsStore.js index 240d44af9..aed541c75 100644 --- a/src/store/participantsStore.js +++ b/src/store/participantsStore.js @@ -6,17 +6,18 @@ import Hex from 'crypto-js/enc-hex.js' import SHA1 from 'crypto-js/sha1.js' import Vue from 'vue' -import { showError } from '@nextcloud/dialogs' +import { showError, showSuccess } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import { generateUrl } from '@nextcloud/router' import { ATTENDEE, PARTICIPANT } from '../constants.js' +import { banActor } from '../services/banService.ts' import { joinCall, leaveCall, } from '../services/callsService.js' -import { setRemoteCapabilities } from '../services/CapabilitiesManager.ts' +import { hasTalkFeature, setRemoteCapabilities } from '../services/CapabilitiesManager.ts' import { EventBus } from '../services/EventBus.js' import { promoteToModerator, @@ -560,12 +561,25 @@ const actions = { commit('updateParticipant', { token, attendeeId, updatedData }) }, - async removeParticipant({ commit, getters }, { token, attendeeId }) { + async removeParticipant({ commit, getters }, { token, attendeeId, banParticipant, internalNote = '' }) { const attendee = getters.getParticipant(token, attendeeId) if (!attendee) { return } + if (hasTalkFeature(token, 'ban-v1') && banParticipant) { + try { + await banActor(token, { + actorId: attendee.actorId, + actorType: attendee.actorType, + internalNote, + }) + showSuccess(t('spreed', 'Participant is banned successfully')) + } catch (error) { + showError(t('spreed', 'Error while banning the participant')) + console.error('Error while banning the participant: ', error) + } + } await removeAttendeeFromConversation(token, attendeeId) commit('deleteParticipant', { token, attendeeId }) }, |