Skip to content

Commit

Permalink
feat: add mention to mail
Browse files Browse the repository at this point in the history
Signed-off-by: Hamza Mahjoubi <[email protected]>
  • Loading branch information
hamza221 committed Oct 4, 2024
1 parent 9e52e17 commit 74ed684
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 15 deletions.
20 changes: 17 additions & 3 deletions lib/Controller/ContactIntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@
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;
private string $uid;


public function __construct(string $appName,
IRequest $request,
ContactIntegrationService $service) {
ContactIntegrationService $service,
ICacheFactory $cacheFactory,
string $UserId) {
parent::__construct($appName, $request);

$this->service = $service;
$this->cache = $cacheFactory->createLocal('mail.contacts');
$this->uid = $UserId;
}

/**
Expand Down Expand Up @@ -75,8 +84,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($this->uid.$term);
if($cached !== null) {
return(new JSONResponse(json_decode($cached)))->cacheFor(60 * 60, false, true);

Check failure on line 90 in lib/Controller/ContactIntegrationController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidTemplateParam

lib/Controller/ContactIntegrationController.php:90:10: InvalidTemplateParam: Extended template param T of OCP\AppFramework\Http\JSONResponse<200, mixed, array<never, never>>&static expects type array<array-key, mixed>|object, type mixed given (see https://psalm.dev/183)
}
$res = $this->service->autoComplete($term, $forceSAB);
$this->cache->set($this->uid.$term, json_encode($res), 24 * 3600);
return (new JSONResponse($res))->cacheFor(60 * 60, false, true);
}
}
4 changes: 2 additions & 2 deletions lib/Service/ContactIntegration/ContactIntegrationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
17 changes: 10 additions & 7 deletions src/ckeditor/smartpicker/InsertItemCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Composer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
:bus="bus"
@input="onEditorInput"
@ready="onEditorReady"
@mention="handleMention"
@show-toolbar="handleShow" />
<MailvelopeEditor v-else
ref="mailvelopeEditor"
Expand Down Expand Up @@ -1240,6 +1241,10 @@ export default {
this.mailvelope.keyRing = await mailvelope.getKeyring()
await this.checkRecipientsKeys()
},
handleMention(option) {
this.editorMode = EDITOR_MODE_HTML
this.onNewToAddr(option)
},
onNewToAddr(option) {
this.onNewAddr(option, this.selectTo, 'to')
},
Expand Down
34 changes: 33 additions & 1 deletion src/components/TextEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { searchProvider, getLinkWithPicker } from '@nextcloud/vue/dist/Component
import { getLanguage } from '@nextcloud/l10n'
import logger from '../logger.js'
import PickerPlugin from '../ckeditor/smartpicker/PickerPlugin.js'
import { autoCompleteByName } from '../service/ContactIntegrationService.js'
import { emojiSearch, emojiAddRecent } from '@nextcloud/vue'
export default {
Expand Down Expand Up @@ -86,6 +87,7 @@ export default {
QuotePlugin,
PickerPlugin,
Mention,
LinkPlugin,
]
const toolbar = ['undo', 'redo']
Expand All @@ -96,7 +98,6 @@ export default {
BoldPlugin,
ItalicPlugin,
BlockQuotePlugin,
LinkPlugin,
ListStyle,
FontPlugin,
RemoveFormat,
Expand Down Expand Up @@ -151,6 +152,11 @@ export default {
feed: this.getLink,
itemRenderer: this.customLinkRenderer,
},
{
marker: '@',
feed: this.getContact,
itemRenderer: this.customContactRenderer,
},
],
},
},
Expand All @@ -174,6 +180,14 @@ export default {
emojiResults.unshift(':' + text)
}
return emojiResults
},
async getContact(text) {
if (text.length === 0) {
return []
}
let contactResults = await autoCompleteByName(text, true)
contactResults = contactResults.filter(result => result.email.length > 0)
return contactResults
},
customEmojiRenderer(item) {
const itemElement = document.createElement('span')
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions src/service/ContactIntegrationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 74ed684

Please sign in to comment.