v0.0.4: e-mailtemplates met velden ({voornaam}, {model}, {afzender}…), te kiezen bij nieuwe e-mail, antwoord en contact; inbox met nieuwe e-mail, doorsturen, CC en bijlagen, Verzonden, Archief, zoeken, als ongelezen markeren en alles gelezen; agenda per gebruiker met CalDAV (/dav/, app-wachtwoord in Mijn account); zoeken en Nieuw in de bovenbalk; e-mailinstellingen met voorinstellingen (OVHcloud, Gmail), beveiliging per server, kopie in Verzonden en verbindingstest; offerte in het klantportaal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
81fc27fa86
commit
366a3f9d17
@@ -0,0 +1,58 @@
|
||||
/* Zoeken in de bovenbalk: live resultaten (search_live) onder het veld; Enter opent de volledige zoekpagina.
|
||||
* Pijltjes lopen door de resultaten, Escape sluit, "/" springt naar het zoekveld. */
|
||||
(() => {
|
||||
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
||||
|
||||
document.querySelectorAll('form.tsearch').forEach((form) => {
|
||||
const input = form.querySelector('input[name=q]');
|
||||
const box = form.querySelector('.tsearch-results');
|
||||
let timer = null, ctrl = null, last = '';
|
||||
|
||||
const items = () => [...box.querySelectorAll('a')];
|
||||
const close = () => { box.classList.remove('open'); input.setAttribute('aria-expanded', 'false'); };
|
||||
const open = () => { if (box.innerHTML) { box.classList.add('open'); input.setAttribute('aria-expanded', 'true'); } };
|
||||
|
||||
const render = (data) => {
|
||||
box.innerHTML = data.groups.length
|
||||
? data.groups.map((g) => `<div class="ts-group">${esc(g.label)}</div>` + g.items.map((i) =>
|
||||
`<a class="ts-item" role="option" href="${esc(i.url)}">${esc(i.title)}${i.sub ? `<small>${esc(i.sub)}</small>` : ''}</a>`).join('')).join('')
|
||||
+ `<a class="ts-all" href="${esc(data.url)}">Alle resultaten voor “${esc(data.q)}”</a>`
|
||||
: `<div class="ts-empty">Niets gevonden voor “${esc(data.q)}”.</div>`;
|
||||
open();
|
||||
};
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
clearTimeout(timer);
|
||||
const v = input.value.trim();
|
||||
if (v.length < 2) { box.innerHTML = ''; close(); return; }
|
||||
timer = setTimeout(() => {
|
||||
if (v === last && box.innerHTML) { open(); return; }
|
||||
if (ctrl) ctrl.abort();
|
||||
ctrl = new AbortController();
|
||||
fetch(`${form.dataset.live}?q=${encodeURIComponent(v)}`, { signal: ctrl.signal, headers: { Accept: 'application/json' } })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((d) => { if (d && input.value.trim() === v) { last = v; render(d); } })
|
||||
.catch(() => {});
|
||||
}, 180);
|
||||
});
|
||||
|
||||
form.addEventListener('keydown', (e) => {
|
||||
const list = items(), at = list.indexOf(document.activeElement);
|
||||
if (e.key === 'ArrowDown' && list.length) { e.preventDefault(); open(); list[Math.min(at + 1, list.length - 1)].focus(); }
|
||||
else if (e.key === 'ArrowUp' && list.length) { e.preventDefault(); (at <= 0 ? input : list[at - 1]).focus(); }
|
||||
else if (e.key === 'Escape') { close(); input.focus(); }
|
||||
});
|
||||
input.addEventListener('focus', () => { if (input.value.trim().length >= 2) open(); });
|
||||
document.addEventListener('click', (e) => { if (!form.contains(e.target)) close(); });
|
||||
|
||||
// mobiel: het veld krijgt de focus zodra het uitklapmenu opengaat
|
||||
const toggle = form.closest('.dropdown')?.querySelector('[data-bs-toggle="dropdown"]');
|
||||
if (toggle) toggle.addEventListener('shown.bs.dropdown', () => input.focus());
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key !== '/' || e.ctrlKey || e.metaKey || e.altKey || e.target.closest('input, textarea, select, [contenteditable]')) return;
|
||||
const input = [...document.querySelectorAll('form.tsearch input[name=q]')].find((i) => i.offsetParent);
|
||||
if (input) { e.preventDefault(); input.focus(); input.select(); }
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user