summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDorra <dorra.jaoued7@gmail.com>2023-10-04 16:52:55 +0200
committerGitHub <noreply@github.com>2023-10-04 16:52:55 +0200
commit6159317f095e167a7f730608179894bd93e2523b (patch)
tree3147fd48fa926ce9b230438f7386d3fd26984759
parent4df5ae8a4d5e9260681ec93cbeaa5a74e75ea741 (diff)
parent7f84892054742f7cb2fbbc26f185edcfbd83a275 (diff)
Merge pull request #10603 from nextcloud/feat/10405/note-to-self
feat(conversation) - add Note to self conversation
-rw-r--r--docs/constants.md1
-rw-r--r--src/components/ConversationSettings/ConversationSettingsDialog.vue17
-rw-r--r--src/components/LeftSidebar/LeftSidebar.vue32
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue59
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue24
-rw-r--r--src/components/RightSidebar/RightSidebar.vue16
-rw-r--r--src/components/TopBar/CallButton.vue1
-rw-r--r--src/constants.js1
-rw-r--r--src/services/conversationsService.js9
-rw-r--r--src/store/messagesStore.js65
-rw-r--r--src/store/messagesStore.spec.js183
11 files changed, 325 insertions, 83 deletions
diff --git a/docs/constants.md b/docs/constants.md
index f5c4b2105..92f2f4991 100644
--- a/docs/constants.md
+++ b/docs/constants.md
@@ -8,6 +8,7 @@
* `3` Public
* `4` Changelog
* `5` Former "One to one" (When a user is deleted from the server or removed from all their conversations, `1` "One to one" rooms are converted to this type)
+* `6` Note to self
### Object types
diff --git a/src/components/ConversationSettings/ConversationSettingsDialog.vue b/src/components/ConversationSettings/ConversationSettingsDialog.vue
index 8a174ef30..2086b8021 100644
--- a/src/components/ConversationSettings/ConversationSettingsDialog.vue
+++ b/src/components/ConversationSettings/ConversationSettingsDialog.vue
@@ -36,7 +36,8 @@
<template v-if="!isBreakoutRoom">
<!-- Notifications settings and devices preview screen -->
- <NcAppSettingsSection id="notifications"
+ <NcAppSettingsSection v-if="!isNoteToSelf"
+ id="notifications"
:title="t('spreed', 'Personal')">
<NcCheckboxRadioSwitch :checked.sync="showMediaSettings"
type="switch">
@@ -49,8 +50,8 @@
<NcAppSettingsSection v-if="canFullModerate"
id="conversation-settings"
:title="t('spreed', 'Moderation')">
- <ListableSettings :token="token" />
- <LinkShareSettings ref="linkShareSettings" />
+ <ListableSettings v-if="!isNoteToSelf" :token="token" />
+ <LinkShareSettings v-if="!isNoteToSelf" ref="linkShareSettings" />
<ExpirationSettings :token="token" can-full-moderate />
</NcAppSettingsSection>
<NcAppSettingsSection v-else
@@ -60,7 +61,7 @@
</NcAppSettingsSection>
<!-- Meeting: lobby and sip -->
- <NcAppSettingsSection v-if="canFullModerate"
+ <NcAppSettingsSection v-if="canFullModerate && !isNoteToSelf"
id="meeting"
:title="t('spreed', 'Meeting')">
<LobbySettings :token="token" />
@@ -68,7 +69,7 @@
</NcAppSettingsSection>
<!-- Conversation permissions -->
- <NcAppSettingsSection v-if="canFullModerate"
+ <NcAppSettingsSection v-if="canFullModerate && !isNoteToSelf"
id="permissions"
:title="t('spreed', 'Permissions')">
<ConversationPermissionsSettings :token="token" />
@@ -99,7 +100,7 @@
<NcAppSettingsSection v-if="canLeaveConversation || canDeleteConversation"
id="dangerzone"
:title="t('spreed', 'Danger zone')">
- <LockingSettings :token="token" />
+ <LockingSettings v-if="canFullModerate && !isNoteToSelf" :token="token" />
<DangerZone :conversation="conversation"
:can-leave-conversation="canLeaveConversation"
:can-delete-conversation="canDeleteConversation" />
@@ -173,6 +174,10 @@ export default {
return this.conversation.canEnableSIP
},
+ isNoteToSelf() {
+ return this.conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF
+ },
+
token() {
return this.$store.getters.getConversationSettingsToken()
|| this.$store.getters.getToken()
diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue
index 1fce07c42..f8ab62abd 100644
--- a/src/components/LeftSidebar/LeftSidebar.vue
+++ b/src/components/LeftSidebar/LeftSidebar.vue
@@ -90,6 +90,15 @@
{{ t('spreed','Create a new conversation') }}
</NcActionButton>
+ <NcActionButton v-if="!hasNoteToSelf"
+ close-after-click
+ @click="restoreNoteToSelfConversation">
+ <template #icon>
+ <Note :size="20" />
+ </template>
+ {{ t('spreed','New personal note') }}
+ </NcActionButton>
+
<NcActionButton close-after-click
@click="showModalListConversations">
<template #icon>
@@ -239,6 +248,7 @@ import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterRemoveIcon from 'vue-material-design-icons/FilterRemove.vue'
import List from 'vue-material-design-icons/FormatListBulleted.vue'
import MessageBadge from 'vue-material-design-icons/MessageBadge.vue'
+import Note from 'vue-material-design-icons/NoteEditOutline.vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import { showError } from '@nextcloud/dialogs'
@@ -267,6 +277,7 @@ import { CONVERSATION } from '../../constants.js'
import BrowserStorage from '../../services/BrowserStorage.js'
import {
createPrivateConversation,
+ fetchNoteToSelfConversation,
searchPossibleConversations,
searchListedConversations,
} from '../../services/conversationsService.js'
@@ -303,6 +314,7 @@ export default {
ChatPlus,
List,
DotsVertical,
+ Note,
},
mixins: [
@@ -396,6 +408,10 @@ export default {
return this.searchText !== ''
},
+ hasNoteToSelf() {
+ return this.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF)
+ },
+
sourcesWithoutResults() {
return !this.searchResultsUsers.length
|| !this.searchResultsGroups.length
@@ -616,9 +632,7 @@ export default {
}
},
- async createConversation(name) {
- const response = await createPrivateConversation(name)
- const conversation = response.data.ocs.data
+ switchToConversation(conversation) {
this.$store.dispatch('addConversation', conversation)
this.abortSearch()
this.$router.push({
@@ -627,6 +641,18 @@ export default {
}).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`))
},
+ async createConversation(name) {
+ const response = await createPrivateConversation(name)
+ const conversation = response.data.ocs.data
+ this.switchToConversation(conversation)
+ },
+
+ async restoreNoteToSelfConversation() {
+ const response = await fetchNoteToSelfConversation()
+ const conversation = response.data.ocs.data
+ this.switchToConversation(conversation)
+ },
+
hasOneToOneConversationWith(userId) {
return !!this.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.ONE_TO_ONE && conversation.name === userId)
},
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
index e9f1e807f..e44e05051 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
@@ -68,7 +68,6 @@
</template>
<script>
-import cloneDeep from 'lodash/cloneDeep.js'
import Check from 'vue-material-design-icons/Check.vue'
@@ -128,70 +127,22 @@ export default {
return this.$store.getters?.conversation(this.selectedConversationToken).displayName
},
- /**
- * Object containing all the mentions in the message that will be forwarded
- *
- * @return {object} mentions.
- */
- mentions() {
- const mentions = {}
- for (const key in this.messageObject.messageParameters) {
- if (key.startsWith('mention')) {
- mentions[key] = this.messageObject.messageParameters[key]
- }
- }
- return mentions
- },
},
methods: {
async setSelectedConversationToken(token) {
this.selectedConversationToken = token
- const messageToBeForwarded = cloneDeep(this.messageObject)
- // Overwrite the selected conversation token
- messageToBeForwarded.token = token
-
- if (messageToBeForwarded.parent) {
- delete messageToBeForwarded.parent
- }
-
- if (messageToBeForwarded.message === '{object}' && messageToBeForwarded.messageParameters.object) {
- const richObject = messageToBeForwarded.messageParameters.object
- try {
- const response = await this.$store.dispatch('forwardRichObject', {
- token,
- richObject: {
- objectId: richObject.id,
- objectType: richObject.type,
- metaData: JSON.stringify(richObject),
- referenceId: '',
- },
- })
- this.showForwardedConfirmation = true
- this.forwardedMessageID = response.data.ocs.data.id
- } catch (error) {
- console.error('Error while forwarding message', error)
- showError(t('spreed', 'Error while forwarding message'))
- }
- return
- }
-
- // If there are mentions in the message to be forwarded, replace them in the message
- // text.
- if (this.mentions !== {}) {
- for (const mention in this.mentions) {
- messageToBeForwarded.message = messageToBeForwarded.message.replace(`{${mention}}`, '@' + this.mentions[mention].name)
- }
- }
try {
- const response = await this.$store.dispatch('forwardMessage', { messageToBeForwarded })
- this.showForwardedConfirmation = true
+ const response = await this.$store.dispatch('forwardMessage', {
+ targetToken: this.selectedConversationToken,
+ messageToBeForwarded: this.messageObject,
+ })
this.forwardedMessageID = response.data.ocs.data.id
+ this.showForwardedConfirmation = true
} catch (error) {
console.error('Error while forwarding message', error)
showError(t('spreed', 'Error while forwarding message'))
}
-
},
openConversation() {
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
index 61c5c10c7..d3af548ec 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
@@ -111,6 +111,14 @@
</template>
{{ t('spreed', 'Go to file') }}
</NcActionLink>
+ <NcActionButton v-if="canForwardMessage && !isInNoteToSelf"
+ close-after-click
+ @click="forwardToNote">
+ <template #icon>
+ <Note :size="16" />
+ </template>
+ {{ t('spreed', 'Note to self') }}
+ </NcActionButton>
<NcActionButton v-if="canForwardMessage"
close-after-click
@click.stop="openForwarder">
@@ -256,6 +264,7 @@ import DeleteIcon from 'vue-material-design-icons/Delete.vue'
import EmoticonOutline from 'vue-material-design-icons/EmoticonOutline.vue'
import EyeOffOutline from 'vue-material-design-icons/EyeOffOutline.vue'
import File from 'vue-material-design-icons/File.vue'
+import Note from 'vue-material-design-icons/NoteEditOutline.vue'
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import Reply from 'vue-material-design-icons/Reply.vue'
@@ -311,6 +320,7 @@ export default {
EmoticonOutline,
EyeOffOutline,
File,
+ Note,
OpenInNewIcon,
Plus,
Reply,
@@ -536,6 +546,10 @@ export default {
&& this.messageParameters?.object?.type === 'talk-poll'
},
+ isInNoteToSelf() {
+ return this.conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF
+ },
+
canForwardMessage() {
return !this.isCurrentGuest
&& !this.isFileShare
@@ -702,6 +716,16 @@ export default {
this.$emit('update:isReactionsMenuOpen', true)
},
+ async forwardToNote() {
+ try {
+ await this.$store.dispatch('forwardMessage', { messageToBeForwarded: this.messageObject })
+ showSuccess(t('spreed', 'Message forwarded to "Note to self"'))
+ } catch (error) {
+ console.error('Error while forwarding message to "Note to self"', error)
+ showError(t('spreed', 'Error while forwarding message to "Note to self"'))
+ }
+ },
+
openForwarder() {
this.$emit('update:isForwarderOpen', true)
},
diff --git a/src/components/RightSidebar/RightSidebar.vue b/src/components/RightSidebar/RightSidebar.vue
index 93e876896..97df245eb 100644
--- a/src/components/RightSidebar/RightSidebar.vue
+++ b/src/components/RightSidebar/RightSidebar.vue
@@ -41,7 +41,7 @@
</template>
<ChatView :is-visible="opened" />
</NcAppSidebarTab>
- <NcAppSidebarTab v-if="(getUserId || isModeratorOrUser) && !isOneToOne"
+ <NcAppSidebarTab v-if="showParticipantsTab"
id="participants"
ref="participantsTab"
:order="2"
@@ -266,6 +266,14 @@ export default {
&& (this.breakoutRoomsConfigured || this.conversation.breakoutRoomMode === CONVERSATION.BREAKOUT_ROOM_MODE.FREE || this.conversation.objectType === 'room')
},
+ showParticipantsTab() {
+ return (this.getUserId || this.isModeratorOrUser) && !this.isOneToOne && !this.isNoteToSelf
+ },
+
+ isNoteToSelf() {
+ return this.conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF
+ },
+
breakoutRoomsText() {
return t('spreed', 'Breakout rooms')
},
@@ -277,7 +285,7 @@ export default {
this.conversationName = this.conversation.displayName
}
- if (newConversation.token === oldConversation.token || this.isOneToOne) {
+ if (newConversation.token === oldConversation.token || !this.showParticipantsTab) {
return
}
@@ -294,10 +302,10 @@ export default {
}
},
- isOneToOne: {
+ showParticipantsTab: {
immediate: true,
handler(value) {
- if (value) {
+ if (!value) {
this.activeTab = 'shared-items'
}
},
diff --git a/src/components/TopBar/CallButton.vue b/src/components/TopBar/CallButton.vue
index 22b3efb2f..de642fde3 100644
--- a/src/components/TopBar/CallButton.vue
+++ b/src/components/TopBar/CallButton.vue
@@ -254,6 +254,7 @@ export default {
showStartCallButton() {
return this.callEnabled
+ && this.conversation.type !== CONVERSATION.TYPE.NOTE_TO_SELF
&& this.conversation.readOnly === CONVERSATION.STATE.READ_WRITE
&& !this.isInCall
},
diff --git a/src/constants.js b/src/constants.js
index 60a9a55bf..7834b0526 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -72,6 +72,7 @@ export const CONVERSATION = {
PUBLIC: 3,
CHANGELOG: 4,
ONE_TO_ONE_FORMER: 5,
+ NOTE_TO_SELF: 6,
},
BREAKOUT_ROOM_MODE: {
diff --git a/src/services/conversationsService.js b/src/services/conversationsService.js
index 2f84b4e92..28d5a7189 100644
--- a/src/services/conversationsService.js
+++ b/src/services/conversationsService.js
@@ -62,6 +62,14 @@ const searchListedConversations = async function({ searchText }, options) {
}
/**
+ * Generate note-to-self conversation
+ *
+ */
+const fetchNoteToSelfConversation = async function() {
+ return axios.get(generateOcsUrl('apps/spreed/api/v4/room/note-to-self'))
+}
+
+/**
* Fetch possible conversations
*
* @param {object} data the wrapping object;
@@ -438,6 +446,7 @@ const deleteConversationAvatar = async function(token) {
export {
fetchConversations,
fetchConversation,
+ fetchNoteToSelfConversation,
searchListedConversations,
searchPossibleConversations,
createOneToOneConversation,
diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js
index 020300ad6..6dac72083 100644
--- a/src/store/messagesStore.js
+++ b/src/store/messagesStore.js
@@ -21,6 +21,7 @@
*/
import Hex from 'crypto-js/enc-hex.js'
import SHA256 from 'crypto-js/sha256.js'
+import cloneDeep from 'lodash/cloneDeep.js'
import Vue from 'vue'
import { showError } from '@nextcloud/dialogs'
@@ -28,7 +29,9 @@ import { showError } from '@nextcloud/dialogs'
import {
ATTENDEE,
CHAT,
+ CONVERSATION,
} from '../constants.js'
+import { fetchNoteToSelfConversation } from '../services/conversationsService.js'
import {
deleteMessage,
updateLastReadMessage,
@@ -1256,30 +1259,60 @@ const actions = {
},
/**
- * Posts a simple text message to a room
+ * Forwards message to a conversation. By default , the message is forwarded to Note to self.
*
* @param {object} context default store context;
* will be forwarded;
* @param {object} data the wrapping object;
+ * @param {string} [data.targetToken] the conversation token to where the message will be forwarded;
* @param {object} data.messageToBeForwarded the message object;
*/
- async forwardMessage(context, { messageToBeForwarded }) {
- const response = await postNewMessage(messageToBeForwarded, { silent: false })
- return response
- },
+ async forwardMessage(context, { targetToken, messageToBeForwarded }) {
+ const message = cloneDeep(messageToBeForwarded)
+
+ // when there is no token provided, the message will be forwarded to the Note to self conversation
+ if (!targetToken) {
+ let noteToSelf = context.getters.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.NOTE_TO_SELF)
+ // If Note to self doesn't exist, it will be regenerated
+ if (!noteToSelf) {
+ const response = await fetchNoteToSelfConversation()
+ noteToSelf = response.data.ocs.data
+ context.dispatch('addConversation', noteToSelf)
+ }
+ targetToken = noteToSelf.token
+ }
+ // Overwrite with the target conversation token
+ message.token = targetToken
+ if (message.parent) {
+ delete message.parent
+ }
+
+ if (message.message === '{object}' && message.messageParameters.object) {
+ const richObject = message.messageParameters.object
+ const response = await postRichObjectToConversation(
+ targetToken,
+ {
+ objectId: richObject.id,
+ objectType: richObject.type,
+ metaData: JSON.stringify(richObject),
+ referenceId: '',
+ },
+ )
+ return response
+ }
- /**
- * Posts a simple text message to a room
- *
- * @param {object} context default store context;
- * will be forwarded;
- * @param {object} data the wrapping object;
- * @param {string} data.token token of the target conversation
- * @param {object} data.richObject the rich object;
- */
- async forwardRichObject(context, { token, richObject }) {
- const response = await postRichObjectToConversation(token, richObject)
+ // If there are mentions in the message to be forwarded, replace them in the message
+ // text.
+ for (const key in message.messageParameters) {
+ if (key.startsWith('mention')) {
+ const mention = message.messageParameters[key]
+ message.message = message.message.replace(`{${key}}`, `@${mention.name}`)
+ }
+ }
+
+ const response = await postNewMessage(message, { silent: false })
return response
+
},
/**
diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js
index 1c0514dd2..44e00ba2b 100644
--- a/src/store/messagesStore.spec.js
+++ b/src/store/messagesStore.spec.js
@@ -11,12 +11,16 @@ import {
ATTENDEE, CHAT,
} from '../constants.js'
import {
+ fetchNoteToSelfConversation,
+} from '../services/conversationsService.js'
+import {
deleteMessage,
updateLastReadMessage,
fetchMessages,
getMessageContext,
lookForNewMessages,
postNewMessage,
+ postRichObjectToConversation,
} from '../services/messagesService.js'
import { useGuestNameStore } from '../stores/guestName.js'
import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js'
@@ -31,6 +35,11 @@ jest.mock('../services/messagesService', () => ({
getMessageContext: jest.fn(),
lookForNewMessages: jest.fn(),
postNewMessage: jest.fn(),
+ postRichObjectToConversation: jest.fn(),
+}))
+
+jest.mock('../services/conversationsService', () => ({
+ fetchNoteToSelfConversation: jest.fn(),
}))
jest.mock('../utils/cancelableRequest')
@@ -1778,4 +1787,178 @@ describe('messagesStore', () => {
expect(store.getters.hasMoreMessagesToLoad(TOKEN)).toBe(false)
})
})
+
+ describe('Forward a message', () => {
+ let conversations
+ let message1
+ let messageToBeForwarded
+ let targetToken
+ let messageExpected
+
+ beforeEach(() => {
+ localVue = createLocalVue()
+ localVue.use(Vuex)
+
+ testStoreConfig = cloneDeep(storeConfig)
+ store = new Vuex.Store(testStoreConfig)
+
+ message1 = {
+ id: 1,
+ token: TOKEN,
+ message: 'simple text message',
+ messageParameters: {},
+ }
+ conversations = [
+ {
+ token: TOKEN,
+ type: 3,
+ displayName: 'conversation 1',
+ },
+ {
+ token: 'token-self',
+ type: 6,
+ displayName: 'Note to self',
+ },
+ {
+ token: 'token-2',
+ type: 3,
+ displayName: 'conversation 2',
+ },
+ ]
+ })
+
+ test('forwards a message to the conversation when a token is given', () => {
+ // Arrange
+ targetToken = 'token-2'
+ messageToBeForwarded = message1
+ messageExpected = cloneDeep(message1)
+ messageExpected.token = targetToken
+
+ // Act
+ store.dispatch('forwardMessage', { targetToken, messageToBeForwarded })
+
+ // Assert
+ expect(postNewMessage).toHaveBeenCalledWith(messageExpected, { silent: false })
+ })
+ test('forwards a message to Note to self when no token is given ', () => {
+ // Arrange
+ targetToken = 'token-self'
+ messageToBeForwarded = message1
+ messageExpected = cloneDeep(message1)
+ messageExpected.token = targetToken
+
+ store.dispatch('addConversation', conversations[1])
+
+ // Act
+ store.dispatch('forwardMessage', { messageToBeForwarded })
+
+ // Assert
+ expect(postNewMessage).toHaveBeenCalledWith(messageExpected, { silent: false })
+ })
+
+ test('generates Note to self when it does not exist ', async () => {
+ // Arrange
+ messageToBeForwarded = message1
+ messageExpected = cloneDeep(message1)
+ messageExpected.token = 'token-self'
+
+ const response = {
+ data: {
+ ocs: {
+ data: conversations[1],
+ },
+ },
+ }
+ fetchNoteToSelfConversation.mockResolvedValueOnce(response)
+
+ // Act
+ store.dispatch('forwardMessage', { messageToBeForwarded })
+ await flushPromises()
+
+ // Assert
+ expect(store.getters.conversationsList).toContain(conversations[1])
+ expect(postNewMessage).toHaveBeenCalledWith(messageExpected, { silent: false })
+ })
+