Skip to content

Commit

Permalink
Merge pull request #12214 from nextcloud/fix/editing-message
Browse files Browse the repository at this point in the history
  • Loading branch information
DorraJaouad authored May 4, 2024
2 parents 665327a + d7b71e9 commit 6b44f1c
Show file tree
Hide file tree
Showing 16 changed files with 470 additions and 182 deletions.
3 changes: 2 additions & 1 deletion src/components/CallView/shared/EmptyCallView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export default {
},

isOneToOneConversation() {
return this.conversation && this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE
return this.conversation?.type === CONVERSATION.TYPE.ONE_TO_ONE
|| this.conversation?.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
},

isPasswordRequestConversation() {
Expand Down
1 change: 1 addition & 0 deletions src/components/ConversationIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export default {

isOneToOne() {
return this.item.type === CONVERSATION.TYPE.ONE_TO_ONE
|| this.item.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
},

conversationType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ describe('Message.vue', () => {
function renderRichObject(message, messageParameters, expectedRichParameters) {
messageProps.message = message
messageProps.messageParameters = messageParameters
store.dispatch('processMessage', { token: TOKEN, message: messageProps })
const wrapper = shallowMount(Message, {
localVue,
store,
Expand Down Expand Up @@ -744,7 +745,6 @@ describe('Message.vue', () => {
propsData: messageProps,
provide: injected,
})

const message = wrapper.findComponent({ name: 'NcRichText' })
expect(message.attributes('text')).toBe('test message')

Expand Down
4 changes: 0 additions & 4 deletions src/components/MessagesList/MessagesGroup/Message/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -446,10 +446,6 @@ export default {
&& this.messageType !== 'command'
&& this.messageType !== 'comment_deleted'
},

isFileShareOnly() {
return Object.keys(Object(this.messageParameters)).some(key => key.startsWith('file')) && this.message === '{file}'
},
},

mounted() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'

import MessageButtonsBar from './../MessageButtonsBar/MessageButtonsBar.vue'

import * as useMessageInfoModule from '../../../../../composables/useMessageInfo.js'
import { CONVERSATION, PARTICIPANT, ATTENDEE } from '../../../../../constants.js'
import storeConfig from '../../../../../store/storeConfig.js'
import { useIntegrationsStore } from '../../../../../stores/integrations.js'
Expand Down Expand Up @@ -96,13 +97,20 @@ describe('MessageButtonsBar.vue', () => {
})

describe('actions', () => {
let useMessageInfoSpy

beforeEach(() => {
store = new Store(testStoreConfig)

injected = {
getMessagesListScroller: jest.fn(),
}

useMessageInfoSpy = jest.spyOn(useMessageInfoModule, 'useMessageInfo')
})

afterEach(() => {
useMessageInfoSpy.mockRestore()
})

describe('reply action', () => {
Expand Down Expand Up @@ -212,6 +220,9 @@ describe('MessageButtonsBar.vue', () => {
}

test('hides private reply action for own messages', async () => {
useMessageInfoSpy.mockReturnValue({
isCurrentUserOwnMessage: () => true,
})
// using default message props which have the
// actor id set to the current user
testPrivateReplyActionVisible(false)
Expand Down Expand Up @@ -244,6 +255,9 @@ describe('MessageButtonsBar.vue', () => {
const mockDate = new Date('2020-05-07 10:00:00')
jest.spyOn(global.Date, 'now')
.mockImplementation(() => mockDate)
useMessageInfoSpy.mockReturnValue({
isDeleteable: () => true,
})
const wrapper = shallowMount(MessageButtonsBar, {
localVue,
store,
Expand All @@ -264,19 +278,8 @@ describe('MessageButtonsBar.vue', () => {

/**
* @param {boolean} visible Whether or not the delete action is visible
* @param {Date} mockDate The message date (deletion only works within 6h)
*/
function testDeleteMessageVisible(visible, mockDate) {
store = new Store(testStoreConfig)

// need to mock the date to be within 6h
if (!mockDate) {
mockDate = new Date('2020-05-07 10:00:00')
}

jest.spyOn(global.Date, 'now')
.mockImplementation(() => mockDate)

function testDeleteMessageVisible(visible) {
const wrapper = shallowMount(MessageButtonsBar, {
localVue,
store,
Expand All @@ -291,53 +294,16 @@ describe('MessageButtonsBar.vue', () => {
expect(actionButton.exists()).toBe(visible)
}

test('hides delete action when message is older than 6 hours', () => {
testDeleteMessageVisible(false, new Date('2020-05-07 15:24:00'))
})

test('hides delete action when the conversation is read-only', () => {
conversationProps.readOnly = CONVERSATION.STATE.READ_ONLY
test('hides delete action when it cannot be deleted', async () => {
testDeleteMessageVisible(false)
})

test('show delete action for file messages', () => {
messageProps.message = '{file}'
messageProps.messageParameters.file = {}
test('show delete action when it can be deleted', () => {
useMessageInfoSpy.mockReturnValue({
isDeleteable: () => true,
})
testDeleteMessageVisible(true)
})

test('hides delete action on other people messages for non-moderators', () => {
messageProps.actorId = 'another-user'
conversationProps.type = CONVERSATION.TYPE.GROUP
testDeleteMessageVisible(false)
})

test('shows delete action on other people messages for moderators', () => {
messageProps.actorId = 'another-user'
conversationProps.type = CONVERSATION.TYPE.GROUP
conversationProps.participantType = PARTICIPANT.TYPE.MODERATOR
testDeleteMessageVisible(true, null)
})

test('shows delete action on other people messages for owner', () => {
messageProps.actorId = 'another-user'
conversationProps.type = CONVERSATION.TYPE.PUBLIC
conversationProps.participantType = PARTICIPANT.TYPE.OWNER
testDeleteMessageVisible(true, null)
})

test('does not show delete action even for guest moderators', () => {
messageProps.actorId = 'another-user'
conversationProps.type = CONVERSATION.TYPE.PUBLIC
conversationProps.participantType = PARTICIPANT.TYPE.GUEST_MODERATOR
testDeleteMessageVisible(false, null)
})

test('does not show delete action on other people messages in one to one conversations', () => {
messageProps.actorId = 'another-user'
conversationProps.type = CONVERSATION.TYPE.ONE_TO_ONE
testDeleteMessageVisible(false)
})
})

test('marks message as unread', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
</template>
{{ t('spreed', 'Edit message') }}
</NcActionButton>
<NcActionButton v-if="!isFileShareOnly"
<NcActionButton v-if="!isFileShareWithoutCaption"
close-after-click
@click.stop="handleCopyMessageText">
<template #icon>
Expand Down Expand Up @@ -250,6 +250,7 @@
<script>
import { frequently, EmojiIndex as EmojiIndexFactory } from 'emoji-mart-vue-fast'
import data from 'emoji-mart-vue-fast/data/all.json'
import { toRefs } from 'vue'

import AccountIcon from 'vue-material-design-icons/Account.vue'
import AlarmIcon from 'vue-material-design-icons/Alarm.vue'
Expand Down Expand Up @@ -286,7 +287,8 @@ import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmojiPicker from '@nextcloud/vue/dist/Components/NcEmojiPicker.js'

import { PARTICIPANT, CONVERSATION, ATTENDEE } from '../../../../../constants.js'
import { useMessageInfo } from '../../../../../composables/useMessageInfo.js'
import { CONVERSATION, ATTENDEE } from '../../../../../constants.js'
import { getMessageReminder, removeMessageReminder, setMessageReminder } from '../../../../../services/remindersService.js'
import { useIntegrationsStore } from '../../../../../stores/integrations.js'
import { useReactionsStore } from '../../../../../stores/reactions.js'
Expand All @@ -295,8 +297,6 @@ import { parseMentions } from '../../../../../utils/textParse.ts'

const EmojiIndex = new EmojiIndexFactory(data)
const supportReminders = getCapabilities()?.spreed?.features?.includes('remind-me-later')
const canEditMessage = getCapabilities()?.spreed?.features?.includes('edit-messages')
const canDeleteMessageUnlimited = getCapabilities()?.spreed?.features?.includes('delete-messages-unlimited')

export default {
name: 'MessageButtonsBar',
Expand Down Expand Up @@ -462,14 +462,31 @@ export default {

emits: ['delete', 'update:isActionMenuOpen', 'update:isEmojiPickerOpen', 'update:isReactionsMenuOpen', 'update:isForwarderOpen', 'show-translate-dialog', 'reply', 'edit'],

setup() {
setup(props) {
const { token, id } = toRefs(props)
const reactionsStore = useReactionsStore()
const { messageActions } = useIntegrationsStore()
const {
isEditable,
isDeleteable,
isCurrentUserOwnMessage,
isFileShare,
isFileShareWithoutCaption,
isConversationReadOnly,
isConversationModifiable,
} = useMessageInfo(token, id)

return {
messageActions,
supportReminders,
reactionsStore,
isEditable,
isCurrentUserOwnMessage,
isFileShare,
isFileShareWithoutCaption,
isDeleteable,
isConversationReadOnly,
isConversationModifiable,
}
},

Expand Down Expand Up @@ -499,44 +516,11 @@ export default {
return this.getMessagesListScroller()
},

isModifiable() {
return !this.isConversationReadOnly && this.conversation.participantType !== PARTICIPANT.TYPE.GUEST
},

isOneToOne() {
return this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE
|| this.conversation.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
},

isEditable() {
if (!canEditMessage || !this.isModifiable || this.isObjectShare
|| ((!this.$store.getters.isModerator || this.isOneToOne) && !this.isMyMsg)) {
return false
}

return (moment(this.timestamp * 1000).add(1, 'd')) > moment()
},

isDeleteable() {
if (!this.isModifiable) {
return false
}

return (canDeleteMessageUnlimited || (moment(this.timestamp * 1000).add(6, 'h')) > moment())
&& (this.messageType === 'comment' || this.messageType === 'voice-message')
&& !this.isDeleting
&& (this.isMyMsg
|| (this.conversation.type !== CONVERSATION.TYPE.ONE_TO_ONE
&& this.conversation.type !== CONVERSATION.TYPE.ONE_TO_ONE_FORMER
&& (this.conversation.participantType === PARTICIPANT.TYPE.OWNER
|| this.conversation.participantType === PARTICIPANT.TYPE.MODERATOR)))
},

isPrivateReplyable() {
return this.isReplyable
&& (this.conversation.type === CONVERSATION.TYPE.PUBLIC
|| this.conversation.type === CONVERSATION.TYPE.GROUP)
&& !this.isMyMsg
&& !this.isCurrentUserOwnMessage
&& this.actorType === ATTENDEE.ACTOR_TYPE.USERS
&& this.$store.getters.isActorUser()
},
Expand All @@ -550,31 +534,10 @@ export default {
}
},

isFileShare() {
return Object.keys(Object(this.messageParameters)).some(key => key.startsWith('file'))
},

isFileShareOnly() {
return this.isFileShare && this.message === '{file}'
},

isObjectShare() {
return Object.keys(Object(this.messageParameters)).some(key => key.startsWith('object'))
},

isCurrentGuest() {
return this.$store.getters.isActorGuest()
},

isMyMsg() {
return this.actorId === this.$store.getters.getActorId()
&& this.actorType === this.$store.getters.getActorType()
},

isConversationReadOnly() {
return this.conversation.readOnly === CONVERSATION.STATE.READ_ONLY
},

isDeletedMessage() {
return this.messageType === 'comment_deleted'
},
Expand Down Expand Up @@ -845,7 +808,7 @@ export default {
},

editMessage() {
if (!canEditMessage) {
if (!this.isEditable) {
return
}
this.$emit('edit')
Expand Down
Loading

0 comments on commit 6b44f1c

Please sign in to comment.