Add live contact search to invitee input

The static datalist only carried the first 50 address book
contacts, so typed fragments could not match anything beyond
that. A new read-only GET /contacts/search endpoint queries
the full address book per keystroke, and a small vanilla JS
combobox shows the results and replaces only the fragment
after the last separator, so comma-separated lists keep
working. The server-rendered datalist remains as the no-JS
fallback; the JS removes the list attribute at runtime so
the native popup does not double the custom one.
This commit is contained in:
stefan.tollkuehn committed 2026-10-22 18:50:46 +02:00
1 parent 0e5b190971
commit cc1fd4e508
7 files changed
+352 -19

No files matched your search

+25 -6
View File
@@ -11,6 +11,7 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Calendar\ICreateFromString;
use OCP\Calendar\IManager;
@@ -292,6 +293,19 @@ class CalendarController extends Controller
return new RedirectResponse($calendarUrl);
}
/**
* Live contact search for the invitee input.
*
* Read-only GET endpoint, needs no CSRF protection.
*
* @return DataResponse<Http::STATUS_OK, list<array{email: string, name: string}>>
*/
#[NoAdminRequired]
public function searchContacts(string $query = ''): DataResponse
{
return new DataResponse($this->contactSuggestions($query));
}
/**
* All writable calendars of the given principal.
*
@@ -402,10 +416,11 @@ class CalendarController extends Controller
array $contacts = []
): TemplateResponse {
/*
* Stylesheets are not auto-loaded, css/style.css
* must be registered for the page layout.
* Stylesheets and scripts are not auto-loaded, they must
* be registered for the page layout.
*/
Util::addStyle(Application::APP_ID, 'style');
Util::addScript(Application::APP_ID, 'invitees');
return new TemplateResponse(
Application::APP_ID,
@@ -432,6 +447,9 @@ class CalendarController extends Controller
'calendarId' => $calendarId,
'attendees' => $attendees,
'contacts' => $contacts,
'contactsSearchUrl' => $this->urlGenerator->linkToRoute(
Application::APP_ID . '.calendar.searchContacts'
),
'cancelUrl' => $this->urlGenerator->getAbsoluteURL('/'),
'createUrl' => $this->urlGenerator->linkToRoute(
Application::APP_ID . '.calendar.create'
@@ -485,16 +503,17 @@ class CalendarController extends Controller
}
/**
* Address book entries with an email address, offered as
* autocomplete options by the invite input.
* Address book entries with an email address, offered by the
* invitee input: as autocomplete options on the initial page
* and as live search results while typing.
*
* @return list<array{email: string, name: string}>
*/
private function contactSuggestions(): array
private function contactSuggestions(string $query = ''): array
{
$contacts = [];
$results = $this->contactsManager->search('', ['FN', 'EMAIL']);
$results = $this->contactsManager->search(trim($query), ['FN', 'EMAIL']);
foreach ($results ?? [] as $result) {
$name = (string)($result['FN'] ?? '');