Skip to content

Commit

Permalink
Move parsing to utilities , add tests and change style
Browse files Browse the repository at this point in the history
Signed-off-by: DorraJaouad <dorra.jaoued7@gmail.com>
  • Loading branch information
DorraJaouad committed Jan 31, 2024
1 parent f69f70c commit 0f775f6
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 45 deletions.
11 changes: 5 additions & 6 deletions src/components/ConversationSettings/EditableTextField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -672,6 +673,12 @@ export default {
apiVersion: 'v3',
}
},
lastEditActorLabel() {
return t('spreed', 'Edited by {actor}', {
actor: this.lastEditActorDisplayName,
})
},
},
watch: {
Expand All @@ -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)
Expand Down Expand Up @@ -851,6 +850,9 @@ export default {
},
editMessage() {
if (!canEditMessage) {
return
}
this.$emit('edit')
},
},
Expand All @@ -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>
10 changes: 4 additions & 6 deletions src/components/NewMessage/NewMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -461,7 +462,8 @@ export default {
},
showMentionEditHint() {
return this.chatEditInput?.includes('@')
const mentionPattern = /(^|\s)@/
return mentionPattern.test(this.chatEditInput)
},
},
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 35 additions & 0 deletions src/stores/__tests__/chatExtras.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
})
26 changes: 5 additions & 21 deletions src/stores/chatExtras.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
},

Expand All @@ -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)
},
Expand Down
68 changes: 68 additions & 0 deletions src/utils/textParse.js
Original file line number Diff line number Diff line change
@@ -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,
}

0 comments on commit 0f775f6

Please sign in to comment.