From e21b76ba96e3deb5d1e56b141a5ff1b0660b1f06 Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Mon, 9 Sep 2024 10:19:49 +0200 Subject: [PATCH] feat: add mention to mail Signed-off-by: Hamza Mahjoubi --- .../ContactIntegrationController.php | 17 ++++++++-- .../ContactIntegrationService.php | 4 +-- src/ckeditor/smartpicker/InsertItemCommand.js | 17 ++++++---- src/components/Composer.vue | 5 +++ src/components/TextEditor.vue | 34 ++++++++++++++++++- src/service/ContactIntegrationService.js | 10 ++++-- 6 files changed, 72 insertions(+), 15 deletions(-) diff --git a/lib/Controller/ContactIntegrationController.php b/lib/Controller/ContactIntegrationController.php index 80c37bfaa4..2049f98095 100644 --- a/lib/Controller/ContactIntegrationController.php +++ b/lib/Controller/ContactIntegrationController.php @@ -15,18 +15,24 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\OpenAPI; use OCP\AppFramework\Http\JSONResponse; +use OCP\ICache; +use OCP\ICacheFactory; use OCP\IRequest; #[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)] class ContactIntegrationController extends Controller { private ContactIntegrationService $service; + private ICache $cache; + public function __construct(string $appName, IRequest $request, - ContactIntegrationService $service) { + ContactIntegrationService $service, + ICacheFactory $cacheFactory, ) { parent::__construct($appName, $request); $this->service = $service; + $this->cache = $cacheFactory->createLocal('mail.contacts'); } /** @@ -75,8 +81,13 @@ public function newContact(?string $contactName = null, ?string $mail = null): J * @return JSONResponse */ #[TrapError] - public function autoComplete(string $term): JSONResponse { - $res = $this->service->autoComplete($term); + public function autoComplete(string $term, bool $forceSAB = false): JSONResponse { + $cached = $this->cache->get($term); + if($cached !== null) { + return(new JSONResponse(json_decode($cached)))->cacheFor(60 * 60, false, true); + } + $res = $this->service->autoComplete($term, $forceSAB); + $this->cache->set($term, json_encode($res), 24 * 3600); return (new JSONResponse($res))->cacheFor(60 * 60, false, true); } } diff --git a/lib/Service/ContactIntegration/ContactIntegrationService.php b/lib/Service/ContactIntegration/ContactIntegrationService.php index 3a8babb3b3..d7ba4c1855 100644 --- a/lib/Service/ContactIntegration/ContactIntegrationService.php +++ b/lib/Service/ContactIntegration/ContactIntegrationService.php @@ -32,7 +32,7 @@ public function newContact(string $name, string $mail): ?array { return $this->contactsIntegration->newContact($name, $mail); } - public function autoComplete(string $term): array { - return $this->contactsIntegration->getContactsWithName($term); + public function autoComplete(string $term, bool $forceSAB = false): array { + return $this->contactsIntegration->getContactsWithName($term, $forceSAB); } } diff --git a/src/ckeditor/smartpicker/InsertItemCommand.js b/src/ckeditor/smartpicker/InsertItemCommand.js index 6539bb83f2..4ebb0534e3 100644 --- a/src/ckeditor/smartpicker/InsertItemCommand.js +++ b/src/ckeditor/smartpicker/InsertItemCommand.js @@ -19,29 +19,32 @@ export default class InsertItemCommand extends Command { // @TODO Add error to handle such a situation in the callback return } - const range = editor.model.createRange( currentPosition.getShiftedBy(-5), currentPosition, ) - // Iterate over all items in this range: const walker = range.getWalker({ shallow: false, direction: 'backward' }) for (const value of walker) { if (value.type === 'text' && value.item.data.includes(trigger)) { writer.remove(value.item) - const text = value.item.data const lastSlash = text.lastIndexOf(trigger) - const textElement = writer.createElement('paragraph') writer.insertText(text.substring(0, lastSlash), textElement) editor.model.insertContent(textElement) - const itemElement = writer.createElement('paragraph') - writer.insertText(item, itemElement) - editor.model.insertContent(itemElement) + if (trigger === '@') { + const mailtoHref = `mailto:${item.email}` + const anchorText = `@${item.label}` + const textElement = writer.createText(anchorText, { linkHref: mailtoHref }) + editor.model.insertContent(textElement) + } else { + const itemElement = writer.createElement('paragraph') + writer.insertText(item, itemElement) + editor.model.insertContent(itemElement) + } return } diff --git a/src/components/Composer.vue b/src/components/Composer.vue index c6bd49db93..4d29dab70a 100644 --- a/src/components/Composer.vue +++ b/src/components/Composer.vue @@ -245,6 +245,7 @@ :bus="bus" @input="onEditorInput" @ready="onEditorReady" + @mention="handleMention" @show-toolbar="handleShow" /> result.email.length > 0) + return contactResults }, customEmojiRenderer(item) { const itemElement = document.createElement('span') @@ -207,6 +221,20 @@ export default { return itemElement }, + customContactRenderer(item) { + const itemElement = document.createElement('span') + + itemElement.classList.add('custom-item') + itemElement.id = `mention-list-item-id-${item.id}` + const usernameElement = document.createElement('p') + + usernameElement.classList.add('custom-item-username') + usernameElement.textContent = item.label + + itemElement.appendChild(usernameElement) + + return itemElement + }, overrideDropdownPositionsToNorth(editor, toolbarView) { const { south, north, southEast, southWest, northEast, northWest, @@ -312,6 +340,10 @@ export default { console.debug('Smart picker promise rejected:', error) }) } + if (eventData.marker === '@') { + this.editorInstance.execute('insertItem', { email: item.email[0], label: item.label }, '@') + this.$emit('mention', { email: item.email[0], label: item.label }) + } }, { priority: 'high' }) this.editorInstance = editor diff --git a/src/service/ContactIntegrationService.js b/src/service/ContactIntegrationService.js index b7a7634116..c51292a8eb 100644 --- a/src/service/ContactIntegrationService.js +++ b/src/service/ContactIntegrationService.js @@ -26,10 +26,16 @@ export const newContact = (name, mailAddr) => { return Axios.put(url, { contactName: name, mail: mailAddr }).then((resp) => resp.data) } -export const autoCompleteByName = (term) => { +export const autoCompleteByName = (term, forceSAB = false) => { const url = generateUrl('/apps/mail/api/contactIntegration/autoComplete/{term}', { term, }) - return Axios.get(url).then((resp) => resp.data) + const config = { + params: { + forceSAB, + }, + } + + return Axios.get(url, config).then((resp) => resp.data) }