mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
fix(mentions): parse groups and federated user mentions, add test coverage
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
This commit is contained in:
parent
a91b44b0c4
commit
bb12e7adad
8 changed files with 189 additions and 23 deletions
|
|
@ -69,13 +69,14 @@ module.exports = {
|
|||
globalSetup: resolve(__dirname, 'jest.global.setup.js'),
|
||||
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/src/**/*.{js,vue}',
|
||||
'<rootDir>/src/**/*.{js,ts,vue}',
|
||||
],
|
||||
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'ts',
|
||||
'vue',
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ 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'
|
||||
import { parseSpecialSymbols } from '../../utils/textParse.ts'
|
||||
|
||||
export default {
|
||||
name: 'EditableTextField',
|
||||
|
|
|
|||
|
|
@ -307,7 +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'
|
||||
import { parseMentions } from '../../../../../utils/textParse.ts'
|
||||
|
||||
const EmojiIndex = new EmojiIndexFactory(data)
|
||||
const supportReminders = getCapabilities()?.spreed?.features?.includes('remind-me-later')
|
||||
|
|
|
|||
|
|
@ -226,7 +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'
|
||||
import { parseSpecialSymbols } from '../../utils/textParse.ts'
|
||||
|
||||
const disableKeyboardShortcuts = OCP.Accessibility.disableKeyboardShortcuts()
|
||||
const supportTypingStatus = getCapabilities()?.spreed?.config?.chat?.['typing-privacy'] !== undefined
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import Vue from 'vue'
|
|||
|
||||
import { EventBus } from '../services/EventBus.js'
|
||||
import { getUserAbsence } from '../services/participantsService.js'
|
||||
import { parseSpecialSymbols, parseMentions } from '../utils/textParse.js'
|
||||
import { parseSpecialSymbols, parseMentions } from '../utils/textParse.ts'
|
||||
|
||||
/**
|
||||
* @typedef {string} Token
|
||||
|
|
|
|||
|
|
@ -6,6 +6,33 @@ type ApiResponse<T> = Promise<{ data: T }>
|
|||
// Conversations
|
||||
export type Conversation = components['schemas']['Room']
|
||||
|
||||
// Chats
|
||||
type ParamObject = {
|
||||
id: string,
|
||||
name: string,
|
||||
type: string,
|
||||
}
|
||||
export type Mention = ParamObject & {
|
||||
server?: string,
|
||||
'call-type'?: string,
|
||||
'icon-url'?: string,
|
||||
}
|
||||
type File = ParamObject & {
|
||||
'size': number,
|
||||
'path': string,
|
||||
'link': string,
|
||||
'etag': string,
|
||||
'permissions': number,
|
||||
'mimetype': string,
|
||||
'preview-available': string,
|
||||
'width': number,
|
||||
'height': number,
|
||||
}
|
||||
type MessageParameters = Record<string, ParamObject | Mention | File>
|
||||
export type ChatMessage = Omit<components['schemas']['ChatMessage'], 'messageParameters'> & {
|
||||
messageParameters: MessageParameters
|
||||
}
|
||||
|
||||
// Bots
|
||||
export type Bot = components['schemas']['Bot']
|
||||
export type BotWithDetails = components['schemas']['BotWithDetails']
|
||||
|
|
|
|||
130
src/utils/__tests__/textParse.spec.js
Normal file
130
src/utils/__tests__/textParse.spec.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
import { parseMentions, parseSpecialSymbols } from '../textParse.ts'
|
||||
|
||||
jest.mock('@nextcloud/router', () => ({
|
||||
getBaseUrl: jest.fn().mockReturnValue('server2.com')
|
||||
}))
|
||||
|
||||
describe('textParse', () => {
|
||||
describe('parseMentions', () => {
|
||||
it('replaces {mention-call} correctly', () => {
|
||||
const input = 'test {mention-call1}'
|
||||
const output = 'test @all'
|
||||
const parameters = {
|
||||
'mention-call1': {
|
||||
id: 'room-id',
|
||||
name: 'Room Display Name',
|
||||
type: 'call',
|
||||
},
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
|
||||
it('replaces multiple entries correctly', () => {
|
||||
const input = 'test {mention-call1} test {mention-call1} test'
|
||||
const output = 'test @all test @all test'
|
||||
const parameters = {
|
||||
'mention-call1': {
|
||||
id: 'room-id',
|
||||
name: 'Room Display Name',
|
||||
type: 'call',
|
||||
},
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
|
||||
it('replaces {mention-user} correctly', () => {
|
||||
const input = 'test {mention-user1} test {mention-user2}'
|
||||
const output = 'test @alice test @"alice space@mail.com"'
|
||||
const parameters = {
|
||||
'mention-user1': {
|
||||
id: 'alice',
|
||||
name: 'Just Alice',
|
||||
type: 'user',
|
||||
},
|
||||
'mention-user2': {
|
||||
id: 'alice space@mail.com',
|
||||
name: 'Out of space Alice',
|
||||
type: 'user',
|
||||
}
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
|
||||
it('replaces {mention-group} correctly', () => {
|
||||
const input = 'test {mention-group1} test {mention-group2}'
|
||||
const output = 'test @"group/talk" test @"group/space talk"'
|
||||
const parameters = {
|
||||
'mention-group1': {
|
||||
id: 'talk',
|
||||
name: 'Talk Group',
|
||||
type: 'user-group',
|
||||
},
|
||||
'mention-group2': {
|
||||
id: 'space talk',
|
||||
name: 'Out of space Talk Group',
|
||||
type: 'user-group',
|
||||
}
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
|
||||
it('replaces {mention-federated-user} correctly (for host and other federations)', () => {
|
||||
const input = 'test {mention-federated-user1}'
|
||||
const output = 'test @"federated_user/alice@server3.com"'
|
||||
const parameters = {
|
||||
'mention-federated-user1': {
|
||||
id: 'alice',
|
||||
name: 'Feder Alice',
|
||||
type: 'user',
|
||||
server: 'server3.com'
|
||||
}
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
|
||||
it('replaces {mention-federated-user} correctly (for user from server2.com)', () => {
|
||||
const input = 'test {mention-federated-user1}'
|
||||
const output = 'test @"federated_user/alice@server2.com"'
|
||||
const parameters = {
|
||||
'mention-federated-user1': {
|
||||
id: 'alice',
|
||||
name: 'Feder Alice',
|
||||
type: 'user',
|
||||
}
|
||||
}
|
||||
expect(parseMentions(input, parameters)).toBe(output)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseSpecialSymbols', () => {
|
||||
it('converts escaped HTML correctly', () => {
|
||||
const input = '<div>Hello&world</div>'
|
||||
const output = '<div>Hello&world</div>'
|
||||
expect(parseSpecialSymbols(input)).toBe(output)
|
||||
})
|
||||
|
||||
it('converts special characters correctly', () => {
|
||||
const input = 'This is the § symbol.'
|
||||
const output = 'This is the § symbol.'
|
||||
expect(parseSpecialSymbols(input)).toBe(output)
|
||||
})
|
||||
|
||||
it('removes trailing and leading whitespaces', () => {
|
||||
const input = ' Hello '
|
||||
const output = 'Hello'
|
||||
expect(parseSpecialSymbols(input)).toBe(output)
|
||||
})
|
||||
|
||||
it('removes line breaks', () => {
|
||||
const input = 'Hello\rworld\r\n!'
|
||||
const output = 'Hello\nworld\n!'
|
||||
expect(parseSpecialSymbols(input)).toBe(output)
|
||||
})
|
||||
|
||||
it('returns the same text when there are no special symbols', () => {
|
||||
const input = 'Hello world!'
|
||||
const output = 'Hello world!'
|
||||
expect(parseSpecialSymbols(input)).toBe(output)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -21,25 +21,34 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import { getBaseUrl } from '@nextcloud/router'
|
||||
|
||||
import type { ChatMessage, Mention } from '../types'
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @param text The string to parse
|
||||
* @param parameters The parameters that contain the mentions
|
||||
*/
|
||||
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)
|
||||
}
|
||||
function parseMentions(text: string, parameters: ChatMessage['messageParameters']): string {
|
||||
for (const key of Object.keys(parameters).filter(key => key.startsWith('mention'))) {
|
||||
const value: Mention = parameters[key]
|
||||
let mention = ''
|
||||
|
||||
if (key.startsWith('mention-call') && value.type === 'call') {
|
||||
mention = '@all'
|
||||
} else if (key.startsWith('mention-federated-user') && value.type === 'user') {
|
||||
const server = value?.server ?? getBaseUrl().replace('https://', '')
|
||||
mention = `@"federated_user/${value.id}@${server}"`
|
||||
} else if (key.startsWith('mention-group') && value.type === 'user-group') {
|
||||
mention = `@"group/${value.id}"`
|
||||
} else if (key.startsWith('mention-user') && value.type === 'user') {
|
||||
mention = value.id.includes(' ') ? `@"${value.id}"` : `@${value.id}`
|
||||
}
|
||||
|
||||
if (mention) {
|
||||
text = text.replace(new RegExp(`{${key}}`, 'g'), mention)
|
||||
}
|
||||
}
|
||||
return text
|
||||
|
|
@ -49,10 +58,9 @@ function parseMentions(text, parameters) {
|
|||
* Parse special symbols in text like & < > §
|
||||
* FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492
|
||||
*
|
||||
* @param {string} text The string to parse
|
||||
* @return {string}
|
||||
* @param text The string to parse
|
||||
*/
|
||||
function parseSpecialSymbols(text) {
|
||||
function parseSpecialSymbols(text: string): string {
|
||||
const temp = document.createElement('textarea')
|
||||
temp.innerHTML = text.replace(/&/gmi, '&')
|
||||
text = temp.value.replace(/&/gmi, '&').replace(/</gmi, '<')
|
||||
Loading…
Add table
Add a link
Reference in a new issue