feat: add address book, fix email layout, update dependencies

- Address book with JMAP sync and local fallback (contacts CRUD,
  search/filter, composer autocomplete, i18n for 8 languages)
- Fix email layout: remove horizontal scroll, left-side clipping,
  and empty spaces from blocked external images in newsletters
- Update all dependencies to latest compatible versions
- Expand i18n from 3 to 8 languages (added ES, IT, DE, NL, PT)
- Upgrade Next.js to 16.1.6 for security patches
This commit is contained in:
Matthieu MALVACHE
2026-02-16 17:25:20 +01:00
committed by Matthieu MALVACHE
parent 5d60fe5186
commit 8a5bc9b88d
27 changed files with 2927 additions and 968 deletions
+32
View File
@@ -75,3 +75,35 @@ export function hasRichFormatting(html: string): boolean {
'h1, h2, h3, h4, h5, h6, ul, ol, blockquote'
);
}
/**
* Collapse empty containers left behind when external images are blocked.
* Walks up from each blocked img to find the nearest table cell or wrapper div
* and hides it if it contains no meaningful visible content.
*/
export function collapseBlockedImageContainers(html: string): string {
const doc = parseHtmlSafely(html);
const blockedImages = doc.querySelectorAll('img[data-blocked-src]');
blockedImages.forEach((img) => {
let el: HTMLElement | null = img.parentElement;
while (el && el !== doc.body) {
if (el.tagName === 'TD' || el.tagName === 'TH' || (el.tagName === 'DIV' && el.parentElement?.tagName === 'TD')) {
const hasVisibleText = el.textContent?.replace(/[\s\u00A0]+/g, '').trim();
const hasVisibleMedia = el.querySelector('img:not([data-blocked-src]), video, canvas');
const hasLinks = el.querySelector('a[href]');
if (!hasVisibleText && !hasVisibleMedia && !hasLinks) {
el.style.display = 'none';
el.style.height = '0';
el.style.padding = '0';
el.style.overflow = 'hidden';
}
break;
}
if (el.tagName === 'TABLE' || el.tagName === 'TR') break;
el = el.parentElement;
}
});
return doc.body.innerHTML;
}