From cb532cadff7d5195dea3c58921c6cbf46e23061c Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 15 Jun 2023 20:02:27 +0200 Subject: [PATCH] fix(NcRichContenteditable): Rename missing properties still named `label` to `title` * Also fix the mixin to allow `@` within userids Signed-off-by: Ferdinand Thiessen --- CHANGELOG.md | 9 +- .../NcAutoCompleteResult.vue | 10 +- .../NcRichContenteditable.vue | 22 ++--- src/mixins/richEditor/index.js | 4 +- tests/unit/mixins/richEditor.spec.js | 94 +++++++++++++++++++ 5 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 tests/unit/mixins/richEditor.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 684502b896..a66a5d748f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,13 @@ All notable changes to this project will be documented in this file. [Full Changelog](https://github.com/nextcloud/nextcloud-vue/compare/v7.11.2...v8.0.0-beta.1) ### :boom: Breaking changes -- The deprecated `NcPopoverMenu` component was removed [\#4088](https://github.com/nextcloud/nextcloud-vue/pull/4088) ([raimund-schluessler](https://github.com/raimund-schluessler)) +- The deprecated `NcPopoverMenu` component was removed [\#4081](https://github.com/nextcloud/nextcloud-vue/pull/4081) ([raimund-schluessler](https://github.com/raimund-schluessler)) - The deprecated `NcAppNavigationCounter` component was removed [\#4096](https://github.com/nextcloud/nextcloud-vue/pull/4096) ([raimund-schluessler](https://github.com/raimund-schluessler)) -- The deprecated `title` property was removed and the `name` propery is now required for `NcActions*`, `NcAppNavigationItem` and `NcBreadcrumb*` [\#4052](https://github.com/nextcloud/nextcloud-vue/pull/4052) ([raimund-schluessler](https://github.com/raimund-schluessler)) -- The deprecated `excludeClickOutsideClasses` property was removed from `clickOutsideOptions` [\#4052](https://github.com/nextcloud/nextcloud-vue/pull/4052) ([raimund-schluessler](https://github.com/raimund-schluessler)) +- The deprecated `excludeClickOutsideClasses` property was removed from `clickOutsideOptions` [\#4088](https://github.com/nextcloud/nextcloud-vue/pull/4088) ([raimund-schluessler](https://github.com/raimund-schluessler)) +- The deprecated `title` property was removed, every occurrence of `title` was renamed to `name` [\#4106](https://github.com/nextcloud/nextcloud-vue/pull/4106) ([raimund-schluessler](https://github.com/raimund-schluessler)),[\#4052](https://github.com/nextcloud/nextcloud-vue/pull/4052) ([raimund-schluessler](https://github.com/raimund-schluessler)) + - `label` property was renamed to `name` for `NcMentionBubble` + - `name` propery is now required for `NcActions*`, `NcAppNavigationItem` and `NcBreadcrumb*` + - See linked pull request for full migration guide ### :rocket: Enhancements - feat\(NcNoteCard\): Add new 'info' version to display informational messaged [\#4063](https://github.com/nextcloud/nextcloud-vue/pull/4063) ([moan0s](https://github.com/moan0s)) diff --git a/src/components/NcRichContenteditable/NcAutoCompleteResult.vue b/src/components/NcRichContenteditable/NcAutoCompleteResult.vue index b4e9af1901..527889431f 100644 --- a/src/components/NcRichContenteditable/NcAutoCompleteResult.vue +++ b/src/components/NcRichContenteditable/NcAutoCompleteResult.vue @@ -32,10 +32,10 @@ - + - - {{ label }} + + {{ title }} {{ subline }} @@ -51,7 +51,7 @@ export default { name: 'NcAutoCompleteResult', props: { - label: { + title: { type: String, required: true, }, @@ -180,7 +180,7 @@ $autocomplete-padding: 10px; padding-left: $autocomplete-padding; } - &__label, + &__title, &__subline { white-space: nowrap; overflow: hidden; diff --git a/src/components/NcRichContenteditable/NcRichContenteditable.vue b/src/components/NcRichContenteditable/NcRichContenteditable.vue index 38f67a174c..0d761a8439 100644 --- a/src/components/NcRichContenteditable/NcRichContenteditable.vue +++ b/src/components/NcRichContenteditable/NcRichContenteditable.vue @@ -64,14 +64,14 @@ export default { Test01: { icon: 'icon-user', id: 'Test01', - label: 'Test01', + title: 'Test01', source: 'users', primary: true, }, Test02: { icon: 'icon-user', id: 'Test02', - label: 'Test02', + title: 'Test02', source: 'users', status: { clearAt: null, @@ -81,10 +81,10 @@ export default { }, subline: 'Visiting London', }, - 'Test 03': { + 'Test@User': { icon: 'icon-user', - id: 'Test 03', - label: 'Test 03', + id: 'Test@User', + title: 'Test 03', source: 'users', status: { clearAt: null, @@ -97,7 +97,7 @@ export default { 'Test Offline': { icon: 'icon-user', id: 'Test Offline', - label: 'Test Offline', + title: 'Test Offline', source: 'users', status: { clearAt: null, @@ -110,7 +110,7 @@ export default { 'Test DND': { icon: 'icon-user', id: 'Test DND', - label: 'Test DND', + title: 'Test DND', source: 'users', status: { clearAt: null, @@ -279,8 +279,8 @@ export default { // Allow spaces in the middle of mentions allowSpaces: true, fillAttr: 'id', - // Search against id and label (display name) - lookup: result => `${result.id} ${result.label}`, + // Search against id and title (display name) + lookup: result => `${result.id} ${result.title}`, // Where to inject the menu popup menuContainer: this.menuContainer, // Popup mention autocompletion templates @@ -348,7 +348,7 @@ export default { // Where to inject the menu popup menuContainer: this.menuContainer, // Popup mention autocompletion templates - menuItemTemplate: item => ` ${item.original.title}`, + menuItemTemplate: item => ` ${item.original.title}`, // Hide if no results noMatchTemplate: () => t('No link provider found'), selectTemplate: this.getLink, @@ -815,7 +815,7 @@ export default { &__item { display: flex; align-items: center; - &__label { + &__title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/src/mixins/richEditor/index.js b/src/mixins/richEditor/index.js index dbaeaccb3c..1502cdda0c 100644 --- a/src/mixins/richEditor/index.js +++ b/src/mixins/richEditor/index.js @@ -66,8 +66,8 @@ export default { return Linkify(part) } - // Extracting the id, nuking the " and @ - const id = part.replace(/@|"/gi, '') + // Extracting the id, nuking the leading @ and all " + const id = part.slice(1).replace(/"/gi, '') // Compiling template and prepend with the space we removed during the split return ' ' + this.genSelectTemplate(id) }) diff --git a/tests/unit/mixins/richEditor.spec.js b/tests/unit/mixins/richEditor.spec.js new file mode 100644 index 0000000000..542eb73c33 --- /dev/null +++ b/tests/unit/mixins/richEditor.spec.js @@ -0,0 +1,94 @@ +/** + * @copyright Copyright (c) 2023 Ferdinand Thiessen + * + * @author Ferdinand Thiessen + * + * @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 . + * + */ + +import { shallowMount } from '@vue/test-utils' +import richEditor from '../../../src/mixins/richEditor/index.js' + +const TestEditor = { + mixins: [richEditor], + render: (h) => h('div'), +} + +describe('richEditor.js', () => { + 'use strict' + + describe('renderContent', () => { + it('sanitizes the input', () => { + const editor = shallowMount(TestEditor, { propsData: { userData: {} } }) + const input = 'Some html
' + const output = editor.vm.renderContent(input) + + expect(output).toEqual('Some <table>html</table>') + }) + + it('converts newline to hard line breaks', () => { + const editor = shallowMount(TestEditor, { propsData: { userData: {} } }) + const input = 'hard\nbreak' + const output = editor.vm.renderContent(input) + + expect(output).toEqual('hard
break') + }) + + it('no duplicated ampersand (from Linkify)', () => { + const editor = shallowMount(TestEditor, { propsData: { userData: {} } }) + const input = 'hello &' + const output = editor.vm.renderContent(input) + + expect(output).toEqual('hello &') + }) + + it('keeps mentions without user data', () => { + const editor = shallowMount(TestEditor, { propsData: { userData: {} } }) + const input = 'hello @foobar' + const output = editor.vm.renderContent(input) + + expect(output).toEqual('hello @foobar') + }) + + it('keeps mentions with user data', () => { + const editor = shallowMount(TestEditor, { + propsData: { + userData: { + jdoe: { + id: 'jdoe', + title: 'J. Doe', + source: 'users', + icon: 'icon-user', + }, + }, + }, + }) + const input = 'hello @jdoe' + const output = editor.vm.renderContent(input) + + expect(output).toMatch(/^hello { + const editor = shallowMount(TestEditor, { propsData: { userData: {} } }) + const input = 'hello @foo@bar - hello @"bar @ foo"' + const output = editor.vm.renderContent(input) + + expect(output).toEqual('hello @foo@bar - hello @"bar @ foo"') + }) + }) +})