Mail: Outlook.com inside the app through Microsoft Graph (folders, reader, compose, reply, unread badge); Cypht webmail removed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1564941ea5
commit
a9c277b497
23 files changed
+637
-405
No files matched your search
@@ -109,64 +109,6 @@ async function renderTasksCard(el) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Mail: with WEBMAIL_URL set, your own webmail (which you can allow to be framed) opens inside the app and keeps running between tabs.
|
||||
// The provider shortcuts (Gmail, Outlook, ...) stay one tap away, because those sites refuse to be framed.
|
||||
function viewMail(args = []) {
|
||||
if (cfg.webmail && args[0] === 'webmail') return viewWebmail(); // the shortcuts are the default; the webmail is one tap away
|
||||
return viewMailCards();
|
||||
}
|
||||
function viewWebmail() {
|
||||
const old = $('#audiowrap iframe[data-k="mail"]');
|
||||
$('#view').innerHTML = `<div class="abbar"><span class="grow"><b>Mail</b><small id="abst">${old ? '' : 'Opening…'}</small></span>
|
||||
<a class="ibtn" href="#/mail" aria-label="Mail shortcuts (Gmail, Outlook and others)" title="Mail shortcuts (Gmail, Outlook and others)">${icon('mail')}</a>
|
||||
<button class="ibtn" id="ar" aria-label="Reload" title="Reload">${icon('refresh')}</button>
|
||||
<button class="ibtn" id="af" aria-label="Full screen" title="Full screen">${icon('full')}</button>
|
||||
<a class="ibtn" href="${esc(cfg.webmail)}" target="_blank" rel="noopener" aria-label="Open in new tab" title="Open in new tab">${icon('ext')}</a></div>`;
|
||||
const setSt = t => { const e = $('#abst'); if (e) e.textContent = t; };
|
||||
const open = fresh => {
|
||||
setSt('Opening…'); let f = $('#audiowrap iframe[data-k="mail"]'); if (f) f.remove();
|
||||
showFrame('mail', cfg.webmail + (fresh ? (cfg.webmail.includes('?') ? '&' : '?') + '_=' + Date.now() : ''), 'Mail', 'fullscreen; clipboard-write; clipboard-read');
|
||||
f = $('#audiowrap iframe[data-k="mail"]'); let done = false;
|
||||
f.addEventListener('load', () => { done = true; setSt(''); webmailHooks(f); });
|
||||
setTimeout(() => { if (!done) setSt('Still loading… if it says it refused to connect, the webmail must allow framing (see the README)'); }, 10000);
|
||||
};
|
||||
if (!old) open(false); else { showFrame('mail', '', 'Mail', ''); setSt(''); }
|
||||
$('#ar').onclick = () => open(true);
|
||||
$('#af').onclick = () => { const f = $('#audiowrap iframe[data-k="mail"]'); if (f && f.requestFullscreen) f.requestFullscreen().catch(() => toast('Full screen is not available here')); else toast('Full screen is not available here'); };
|
||||
}
|
||||
// The webmail page shares our address, so we can help it from outside: sign in for you, and send links that cannot be framed to the right place.
|
||||
const SIGNIN_HOSTS = /^(accounts\.google\.com|login\.microsoftonline\.com|login\.live.com|appleid\.apple\.com|github\.com)$/;
|
||||
async function webmailHooks(f) {
|
||||
let d; try { d = f.contentDocument; if (!d || !d.body) return; } catch { return; }
|
||||
if (!d.__hubHooks) {
|
||||
d.__hubHooks = true;
|
||||
d.addEventListener('click', e => {
|
||||
const a = e.target.closest && e.target.closest('a[href]'); if (!a || e.defaultPrevented) return;
|
||||
let u; try { u = new URL(a.href, d.baseURI); } catch { return; }
|
||||
if (!/^https?:$/.test(u.protocol) || u.origin === location.origin) return;
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
if (SIGNIN_HOSTS.test(u.hostname)) location.href = u.href; // a provider's sign-in page refuses to be framed: use the whole window
|
||||
else window.open(u.href, '_blank', 'noopener');
|
||||
}, true);
|
||||
}
|
||||
const u = d.querySelector('input[name=username]'), p = d.querySelector('input[name=password]'), form = u && u.form;
|
||||
if (form && p && !d.querySelector('.error, .danger, .alert-danger') && !f.dataset.tried) { // the webmail's own login box: fill it in (once, so a wrong password is not retried)
|
||||
f.dataset.tried = 1;
|
||||
try { const c = await jfetch('/api/webmail/login'); if (c.user && c.pass) { u.value = c.user; p.value = c.pass; form.submit(); } } catch {}
|
||||
}
|
||||
}
|
||||
function viewMailCards() {
|
||||
const hints = mailHints(), on = mailOn(), shown = MAIL.filter(m => on.includes(m.id)), anyHint = shown.some(m => hints[m.id]);
|
||||
$('#view').innerHTML = `<div class="wrap"><h1>Mail</h1>
|
||||
<p class="sub">Each account opens in its own webmail tab. Nothing is stored here.</p>
|
||||
${cfg.webmail ? `<div class="bar" style="margin-bottom:0"><a class="btn" href="#/mail/webmail">${icon('mail')}Open webmail (all accounts in one place)</a></div>` : ''}
|
||||
${shown.length ? '' : '<p class="empty">No mail apps chosen. Pick the ones you use in <a href="#/settings" style="color:var(--accent)">Settings</a>.</p>'}<div class="mailgrid">${shown.map(m => `<div class="mcard" style="--mc:${m.color}">
|
||||
<div class="mhd"><span class="mbadge">${icon('mail')}</span><span class="grow"><b>${esc(m.name)}</b><small>${esc(hints[m.id] || (m.id === 'icloud' ? 'Opens iCloud Mail' : 'No address saved'))}</small></span></div>
|
||||
<div class="mact"><a class="btn" href="${esc(mailUrl(m, 'inbox'))}" target="_blank" rel="noopener">${icon('mail')}Inbox</a>
|
||||
<a class="btn ghost" href="${esc(mailUrl(m, 'compose'))}" target="_blank" rel="noopener">${icon('edit')}Compose</a></div></div>`).join('')}</div>
|
||||
${anyHint || !shown.length ? '' : '<p class="sub" style="margin-top:14px">Tip: save your address for each account in <a href="#/settings" style="color:var(--accent)">Settings</a> so the right mailbox opens.</p>'}</div>`;
|
||||
}
|
||||
|
||||
const officeHash = (repo, path) => '#/office/doc/' + encodeURIComponent(repo) + path.split('/').filter(Boolean).map(x => '/' + encodeURIComponent(x)).join('');
|
||||
|
||||
function viewOffice(args = []) {
|
||||
|
||||
Reference in new issue
Block a user