feat(email-to-pdf): add inline images, clickable links, and embedded attachments

- Add CID inline image support via base64 data URI replacement
- Implement clickable link extraction from HTML anchors using regex
- Embed email attachments into PDF using pymupdf embfile_add
- Reduce font sizes for more compact PDF output (18px subject, 12px body)
- Format date with timezone (UTC+HH:MM) while preserving original time
- Clean email address formatting (Name (email) instead of <brackets>)
- Add UI options: page size selector, CC/BCC toggle, attachments toggle
This commit is contained in:
abdullahalam123
2026-01-08 21:36:21 +05:30
parent 4a4a47158f
commit 280348763d
30 changed files with 3978 additions and 9416 deletions

View File

@@ -3,29 +3,45 @@ import LanguageDetector from 'i18next-browser-languagedetector';
import HttpBackend from 'i18next-http-backend';
// Supported languages
export const supportedLanguages = ['en', 'de', 'zh', 'vi', 'tr'] as const;
export const supportedLanguages = [
'en',
'de',
'zh',
'vi',
'tr',
'id',
'it',
] as const;
export type SupportedLanguage = (typeof supportedLanguages)[number];
export const languageNames: Record<SupportedLanguage, string> = {
en: 'English',
de: 'Deutsch',
zh: '中文',
vi: 'Tiếng Việt',
tr: 'Türkçe',
en: 'English',
de: 'Deutsch',
zh: '中文',
vi: 'Tiếng Việt',
tr: 'Türkçe',
id: 'Bahasa Indonesia',
it: 'Italiano',
};
export const getLanguageFromUrl = (): SupportedLanguage => {
const path = window.location.pathname;
const langMatch = path.match(/^\/(en|de|zh|vi|tr)(?:\/|$)/);
if (langMatch && supportedLanguages.includes(langMatch[1] as SupportedLanguage)) {
return langMatch[1] as SupportedLanguage;
}
const storedLang = localStorage.getItem('i18nextLng');
if (storedLang && supportedLanguages.includes(storedLang as SupportedLanguage)) {
return storedLang as SupportedLanguage;
}
const path = window.location.pathname;
const langMatch = path.match(/^\/(en|de|zh|vi|tr|id|it)(?:\/|$)/);
if (
langMatch &&
supportedLanguages.includes(langMatch[1] as SupportedLanguage)
) {
return langMatch[1] as SupportedLanguage;
}
const storedLang = localStorage.getItem('i18nextLng');
if (
storedLang &&
supportedLanguages.includes(storedLang as SupportedLanguage)
) {
return storedLang as SupportedLanguage;
}
return 'en';
return 'en';
};
let initialized = false;
@@ -66,22 +82,22 @@ export const t = (key: string, options?: Record<string, unknown>): string => {
};
export const changeLanguage = (lang: SupportedLanguage): void => {
if (!supportedLanguages.includes(lang)) return;
if (!supportedLanguages.includes(lang)) return;
const currentPath = window.location.pathname;
const currentLang = getLanguageFromUrl();
const currentPath = window.location.pathname;
const currentLang = getLanguageFromUrl();
let newPath: string;
if (currentPath.match(/^\/(en|de|zh|vi|tr)\//)) {
newPath = currentPath.replace(/^\/(en|de|zh|vi|tr)\//, `/${lang}/`);
} else if (currentPath.match(/^\/(en|de|zh|vi|tr)$/)) {
newPath = `/${lang}`;
} else {
newPath = `/${lang}${currentPath}`;
}
let newPath: string;
if (currentPath.match(/^\/(en|de|zh|vi|tr|id|it)\//)) {
newPath = currentPath.replace(/^\/(en|de|zh|vi|tr|id|it)\//, `/${lang}/`);
} else if (currentPath.match(/^\/(en|de|zh|vi|tr|id|it)$/)) {
newPath = `/${lang}`;
} else {
newPath = `/${lang}${currentPath}`;
}
const newUrl = newPath + window.location.search + window.location.hash;
window.location.href = newUrl;
const newUrl = newPath + window.location.search + window.location.hash;
window.location.href = newUrl;
};
// Apply translations to all elements with data-i18n attribute
@@ -120,38 +136,40 @@ export const applyTranslations = (): void => {
};
export const rewriteLinks = (): void => {
const currentLang = getLanguageFromUrl();
if (currentLang === 'en') return;
const currentLang = getLanguageFromUrl();
if (currentLang === 'en') return;
const links = document.querySelectorAll('a[href]');
links.forEach((link) => {
const href = link.getAttribute('href');
if (!href) return;
const links = document.querySelectorAll('a[href]');
links.forEach((link) => {
const href = link.getAttribute('href');
if (!href) return;
if (href.startsWith('http') ||
href.startsWith('mailto:') ||
href.startsWith('tel:') ||
href.startsWith('#') ||
href.startsWith('javascript:')) {
return;
}
if (
href.startsWith('http') ||
href.startsWith('mailto:') ||
href.startsWith('tel:') ||
href.startsWith('#') ||
href.startsWith('javascript:')
) {
return;
}
if (href.match(/^\/(en|de|zh|vi|tr|id)\//)) {
return;
}
let newHref: string;
if (href.startsWith('/')) {
newHref = `/${currentLang}${href}`;
} else if (href.startsWith('./')) {
newHref = href.replace('./', `/${currentLang}/`);
} else if (href === '/' || href === '') {
newHref = `/${currentLang}/`;
} else {
newHref = `/${currentLang}/${href}`;
}
if (href.match(/^\/(en|de|zh|vi|tr|id|it)\//)) {
return;
}
let newHref: string;
if (href.startsWith('/')) {
newHref = `/${currentLang}${href}`;
} else if (href.startsWith('./')) {
newHref = href.replace('./', `/${currentLang}/`);
} else if (href === '/' || href === '') {
newHref = `/${currentLang}/`;
} else {
newHref = `/${currentLang}/${href}`;
}
link.setAttribute('href', newHref);
});
link.setAttribute('href', newHref);
});
};
export default i18next;