summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDorraJaouad <dorra.jaoued7@gmail.com>2024-01-30 11:27:00 +0100
committerDorraJaouad <dorra.jaoued7@gmail.com>2024-01-31 17:40:30 +0100
commit0f775f6d5f86737a84e726a57e4c57e840d77c41 (patch)
treedbe6003944a8c1aab1c7a398d49fad9a3e2f37f6
parentf69f70ce29a60dfe39a7935b28e1b661bcdb9e9d (diff)
Move parsing to utilities , add tests and change style
Signed-off-by: DorraJaouad <dorra.jaoued7@gmail.com>
-rw-r--r--src/components/ConversationSettings/EditableTextField.vue11
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue30
-rw-r--r--src/components/NewMessage/NewMessage.vue10
-rw-r--r--src/stores/__tests__/chatExtras.spec.js35
-rw-r--r--src/stores/chatExtras.js26
-rw-r--r--src/utils/textParse.js68
6 files changed, 135 insertions, 45 deletions
diff --git a/src/components/ConversationSettings/EditableTextField.vue b/src/components/ConversationSettings/EditableTextField.vue
index b8515f739..76912b4fb 100644
--- a/src/components/ConversationSettings/EditableTextField.vue
+++ b/src/components/ConversationSettings/EditableTextField.vue
@@ -85,6 +85,8 @@ import NcRichContenteditable from '@nextcloud/vue/dist/Components/NcRichContente
import NcRichText from '@nextcloud/vue/dist/Components/NcRichText.js'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
+import { parseSpecialSymbols } from '../../utils/textParse.js'
+
export default {
name: 'EditableTextField',
components: {
@@ -232,12 +234,9 @@ export default {
if (!this.canSubmit) {
return
}
- // Remove leading/trailing whitespaces.
- // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
- const temp = document.createElement('textarea')
- temp.innerHTML = this.text.replace(/&/gmi, '&amp;')
- this.text = temp.value.replace(/\r\n|\n|\r/gm, '\n').replace(/&amp;/gmi, '&')
- .replace(/&lt;/gmi, '<').replace(/&gt;/gmi, '>').replace(/&sect;/gmi, '§').trim()
+
+ // Parse special symbols
+ this.text = parseSpecialSymbols(this.text)
// Submit text
this.$emit('submit-text', this.text)
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
index e71b65a0e..d0972cedf 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.vue
@@ -67,7 +67,8 @@
</NcActionText>
<!-- Edited message timestamp -->
<NcActionText v-if="lastEditTimestamp"
- :name="lastEditActorDisplayName">
+ class="edit-timestamp"
+ :name="lastEditActorLabel">
<template #icon>
<ClockEditOutline :size="16" />
</template>
@@ -306,6 +307,7 @@ import { getMessageReminder, removeMessageReminder, setMessageReminder } from '.
import { copyConversationLinkToClipboard } from '../../../../../services/urlService.js'
import { useIntegrationsStore } from '../../../../../stores/integrations.js'
import { useReactionsStore } from '../../../../../stores/reactions.js'
+import { parseMentions } from '../../../../../utils/textParse.js'
const EmojiIndex = new EmojiIndexFactory(data)
const supportReminders = getCapabilities()?.spreed?.features?.includes('remind-me-later')
@@ -527,8 +529,7 @@ export default {
return false
}
- return ((moment(this.timestamp * 1000).add(6, 'h')) > moment()
- || canDeleteMessageUnlimited)
+ return (canDeleteMessageUnlimited || (moment(this.timestamp * 1000).add(6, 'h')) > moment())
&& (this.messageType === 'comment' || this.messageType === 'voice-message')
&& !this.isDeleting
&& (this.isMyMsg
@@ -672,6 +673,12 @@ export default {
apiVersion: 'v3',
}
},
+
+ lastEditActorLabel() {
+ return t('spreed', 'Edited by {actor}', {
+ actor: this.lastEditActorDisplayName,
+ })
+ },
},
watch: {
@@ -694,15 +701,7 @@ export default {
},
async handleCopyMessageText() {
- let parsedText = this.message
-
- for (const [key, value] of Object.entries(this.messageParameters)) {
- if (value?.type === 'call') {
- parsedText = parsedText.replace(new RegExp(`{${key}}`, 'g'), '@all')
- } else if (value?.type === 'user') {
- parsedText = parsedText.replace(new RegExp(`{${key}}`, 'g'), `@${value.id}`)
- }
- }
+ const parsedText = parseMentions(this.message, this.messageParameters)
try {
await navigator.clipboard.writeText(parsedText)
@@ -851,6 +850,9 @@ export default {
},
editMessage() {
+ if (!canEditMessage) {
+ return
+ }
this.$emit('edit')
},
},
@@ -867,4 +869,8 @@ export default {
background: no-repeat center var(--icon-triangle-e-dark);
}
}
+
+.edit-timestamp :deep(.action-text__longtext-wrapper) {
+ padding: 0;
+}
</style>
diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue
index 0ed1550ec..c0fcfdc89 100644
--- a/src/components/NewMessage/NewMessage.vue
+++ b/src/components/NewMessage/NewMessage.vue
@@ -226,6 +226,7 @@ import { useChatExtrasStore } from '../../stores/chatExtras.js'
import { useSettingsStore } from '../../stores/settings.js'
import { fetchClipboardContent } from '../../utils/clipboard.js'
import { isDarkTheme } from '../../utils/isDarkTheme.js'
+import { parseSpecialSymbols } from '../../utils/textParse.js'
const disableKeyboardShortcuts = OCP.Accessibility.disableKeyboardShortcuts()
const supportTypingStatus = getCapabilities()?.spreed?.config?.chat?.['typing-privacy'] !== undefined
@@ -461,7 +462,8 @@ export default {
},
showMentionEditHint() {
- return this.chatEditInput?.includes('@')
+ const mentionPattern = /(^|\s)@/
+ return mentionPattern.test(this.chatEditInput)
},
},
@@ -614,12 +616,8 @@ export default {
}
}
- // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
if (this.hasText) {
- const temp = document.createElement('textarea')
- temp.innerHTML = this.text.replace(/&/gmi, '&amp;')
- this.text = temp.value.replace(/&amp;/gmi, '&').replace(/&lt;/gmi, '<')
- .replace(/&gt;/gmi, '>').replace(/&sect;/gmi, '§')
+ this.text = parseSpecialSymbols(this.text)
}
if (this.upload) {
diff --git a/src/stores/__tests__/chatExtras.spec.js b/src/stores/__tests__/chatExtras.spec.js
index ee8900222..c3ae0a162 100644
--- a/src/stores/__tests__/chatExtras.spec.js
+++ b/src/stores/__tests__/chatExtras.spec.js
@@ -161,4 +161,39 @@ describe('chatExtrasStore', () => {
expect(chatExtrasStore.chatInput['token-1']).not.toBeDefined()
})
})
+
+ describe('text parsing', () => {
+ it('should render mentions properly when editing message', () => {
+ // Arrange
+ const parameters = {
+ 'mention-call1': { type: 'call', name: 'Conversation101' },
+ 'mention-user1': { type: 'user', name: 'Alice Joel', id: 'alice' },
+ }
+ // Act
+ chatExtrasStore.setChatEditInput({
+ token: 'token-1',
+ text: 'Hello {mention-call1} and {mention-user1}',
+ parameters
+ })
+ // Assert
+ expect(chatExtrasStore.getChatEditInput('token-1')).toBe('Hello @all and @alice')
+ })
+
+ it('should store chat input without escaping special symbols', () => {
+ // Arrange
+ const message = 'These are special symbols &amp; &lt; &gt; &sect;'
+ // Act
+ chatExtrasStore.setChatInput({ token: 'token-1', text: message })
+ // Assert
+ expect(chatExtrasStore.getChatInput('token-1')).toBe('These are special symbols & < > §')
+ })
+ it('should remove leading/trailing whitespaces', () => {
+ // Arrange
+ const message = ' Many whitespaces '
+ // Act
+ chatExtrasStore.setChatInput({ token: 'token-1', text: message })
+ // Assert
+ expect(chatExtrasStore.getChatInput('token-1')).toBe('Many whitespaces')
+ })
+ })
})
diff --git a/src/stores/chatExtras.js b/src/stores/chatExtras.js
index c733fe05c..d060b4ec5 100644
--- a/src/stores/chatExtras.js
+++ b/src/stores/chatExtras.js
@@ -25,6 +25,7 @@ import { defineStore } from 'pinia'
import Vue from 'vue'
import { getUserAbsence } from '../services/participantsService.js'
+import { parseSpecialSymbols, parseMentions } from '../utils/textParse.js'
/**
* @typedef {string} Token
@@ -136,12 +137,7 @@ export const useChatExtrasStore = defineStore('chatExtras', {
* @param {string} payload.text The string to store
*/
setChatInput({ token, text }) {
- // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
- const temp = document.createElement('textarea')
- temp.innerHTML = text.replace(/&/gmi, '&amp;')
- const parsedText = temp.value.replace(/&amp;/gmi, '&').replace(/&lt;/gmi, '<')
- .replace(/&gt;/gmi, '>').replace(/&sect;/gmi, '§')
-
+ const parsedText = parseSpecialSymbols(text)
Vue.set(this.chatInput, token, parsedText)
},
@@ -156,21 +152,9 @@ export const useChatExtrasStore = defineStore('chatExtras', {
setChatEditInput({ token, text, parameters = {} }) {
let parsedText = text
- // Handle mentions
- if (parameters.length !== 0) {
- for (const [key, value] of Object.entries(parameters)) {
- if (value?.type === 'call') {
- parsedText = parsedText.replace(new RegExp(`{${key}}`, 'g'), '@all')
- } else if (value?.type === 'user') {
- parsedText = parsedText.replace(new RegExp(`{${key}}`, 'g'), `@${value.id}`)
- }
- }
- }
- // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
- const temp = document.createElement('textarea')
- temp.innerHTML = parsedText.replace(/&/gmi, '&amp;')
- parsedText = temp.value.replace(/&amp;/gmi, '&').replace(/&lt;/gmi, '<')
- .replace(/&gt;/gmi, '>').replace(/&sect;/gmi, '§')
+ // Handle mentions and special symbols
+ parsedText = parseMentions(parsedText, parameters)
+ parsedText = parseSpecialSymbols(parsedText)
Vue.set(this.chatEditInput, token, parsedText)
},
diff --git a/src/utils/textParse.js b/src/utils/textParse.js
new file mode 100644
index 000000000..f13ef0215
--- /dev/null
+++ b/src/utils/textParse.js
@@ -0,0 +1,68 @@
+/**
+ * @copyright Copyright (c) 2024 Dorra Jaouad <dorra.jaoued1@gmail.com>
+ *
+ * @author Dorra Jaouad <dorra.jaoued1@gmail.com>
+ * @author Maksim Sukharev <antreesy.web@gmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * Parse message text to return proper formatting for mentions
+ *
+ * @param {string} text The string to parse
+ * @param {object} parameters The parameters that contain the mentions
+ * @return {string}
+ */
+function parseMentions(text, parameters) {
+ if (Object.keys(parameters).some(key => key.startsWith('mention'))) {
+ for (const [key, value] of Object.entries(parameters)) {
+ let mention = ''
+ if (value?.type === 'call') {
+ mention = '@all'
+ } else if (value?.type === 'user') {
+ mention = value.id.includes(' ') ? `@"${value.id}"` : `@${value.id}`
+ }
+ if (mention) {
+ text = text.replace(new RegExp(`{${key}}`, 'g'), mention)
+ }
+ }
+ }
+ return text
+}
+
+/**
+ * Parse special symbols in text like &amp; &lt; &gt; &sect;
+ * FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
+ *
+ * @param {string} text The string to parse
+ * @return {string}
+ */
+function parseSpecialSymbols(text) {
+ const temp = document.createElement('textarea')
+ temp.innerHTML = text.replace(/&/gmi, '&amp;')
+ text = temp.value.replace(/&amp;/gmi, '&').replace(/&lt;/gmi, '<')
+ .replace(/&gt;/gmi, '>').replace(/&sect;/gmi, '§')
+ .replace(/^\s+|\s+$/g, '') // remove trailing and leading whitespaces
+ .replace(/\r\n|\n|\r/gm, '\n') // remove line breaks
+ return text
+}
+
+export {
+ parseSpecialSymbols,
+ parseMentions,
+}