Open locked accounts handed to you, beside your own mail
When the server hands a locked account to the reader (urn:inbuxa:jmap delegation), the account popover offers it. Only mail follows the switch; the reader's own settings, push and notifications stay theirs. A red bar, a red wordmark with a padlock and the tab title say which account is in view. Read delegates can't change anything, organize delegates can't delete, and writing needs send-as. A delegation taken away drops back to the reader's own mail. 14 new strings in all nine catalogs, unreviewed (inbuxa AL-7, AL-8).
This commit is contained in:
1 parent
ffe1a898f5
commit
8674b70f62
22 files changed
+679
-25
No files matched your search
+57
-5
@@ -1,7 +1,7 @@
|
||||
import { Fragment, lazy, Suspense, useEffect, useState } from "react";
|
||||
import { Fragment, lazy, Suspense, useEffect, useRef, useState } from "react";
|
||||
import { Route, Switch, Redirect, useLocation, Router } from "wouter";
|
||||
import { useSession } from "@/store/session";
|
||||
import { useMail } from "@/store/mail";
|
||||
import { notifyOwnWhileAway, ownAccountAway, useMail } from "@/store/mail";
|
||||
import { scheduleSupported, useScheduled } from "@/store/scheduled";
|
||||
import { useContacts } from "@/store/contacts";
|
||||
import { useCalendar } from "@/store/calendar";
|
||||
@@ -119,6 +119,7 @@ export function App() {
|
||||
|
||||
function AuthedApp() {
|
||||
const accountId = useSession((s) => s.accountId);
|
||||
const viewing = useSession((s) => s.viewing);
|
||||
const [location] = useLocation();
|
||||
|
||||
/*
|
||||
@@ -230,6 +231,9 @@ function AuthedApp() {
|
||||
timer = null;
|
||||
for (const [a, types] of pending) {
|
||||
if (a === useMail.getState().accountId) void useMail.getState().applyChanges(types);
|
||||
// inbuxa AL-7: the reader's own mail, while a delegated account
|
||||
// is in view, is still announced
|
||||
if (a === ownAccountAway() && types.has("Email")) void notifyOwnWhileAway();
|
||||
if (a === useContacts.getState().accountId) useContacts.getState().applyChanges(types);
|
||||
if (a === useCalendar.getState().accountId) useCalendar.getState().applyChanges(types);
|
||||
if (a === useFiles.getState().accountId) useFiles.getState().applyChanges(types);
|
||||
@@ -253,16 +257,61 @@ function AuthedApp() {
|
||||
};
|
||||
}, [accountId]);
|
||||
|
||||
/*
|
||||
* inbuxa AL-7: a delegated account's mail, when it comes into view, and the
|
||||
* reader's own when it goes back. Push carries nothing for an account only
|
||||
* shared with the reader, so while one is open it is polled.
|
||||
*/
|
||||
const viewedOnce = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!viewedOnce.current) {
|
||||
viewedOnce.current = true;
|
||||
if (!viewing) return;
|
||||
}
|
||||
const mail = useMail.getState();
|
||||
void mail.loadMailboxes();
|
||||
void mail.loadIdentities();
|
||||
if (!viewing) return;
|
||||
const poll = window.setInterval(() => {
|
||||
if (document.visibilityState === "visible") {
|
||||
void useMail.getState().applyChanges(new Set(["Email", "Mailbox"]));
|
||||
}
|
||||
}, 60_000);
|
||||
return () => window.clearInterval(poll);
|
||||
}, [viewing]);
|
||||
|
||||
// inbuxa AL-7: a delegation given or taken away while the app is open shows
|
||||
// up when the reader comes back to it, without signing in again
|
||||
useEffect(() => {
|
||||
let last = 0;
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState !== "visible" || Date.now() - last < 60_000) return;
|
||||
last = Date.now();
|
||||
void useSession.getState().refresh();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisible);
|
||||
return () => document.removeEventListener("visibilitychange", onVisible);
|
||||
}, []);
|
||||
|
||||
const delegationEnded = useSession((s) => s.delegationEnded);
|
||||
useEffect(() => {
|
||||
if (!delegationEnded) return;
|
||||
toast.show(t("You no longer have access to {name}. Back to your own mail.", { name: delegationEnded }));
|
||||
useSession.getState().clearDelegationEnded();
|
||||
}, [delegationEnded]);
|
||||
|
||||
// Unread badge in title/favicon
|
||||
const inboxUnread = useMail((s) => {
|
||||
const id = s.roleId("inbox");
|
||||
return id ? (s.mailboxes[id]?.unreadEmails ?? 0) : 0;
|
||||
});
|
||||
const appName = useSession((s) => s.session?.ihasmail?.appName) || DEFAULT_APP_NAME;
|
||||
// inbuxa AL-7: a locked account in view is named, with a padlock, in the tab
|
||||
const viewingName = useSession((s) => (s.viewing ? s.session?.accounts[s.viewing]?.name : undefined));
|
||||
useEffect(() => {
|
||||
setBaseTitle(appName);
|
||||
setBaseTitle(viewingName ? `🔒 ${viewingName} · ${appName}` : appName);
|
||||
setUnreadBadge(inboxUnread);
|
||||
}, [inboxUnread, appName]);
|
||||
}, [inboxUnread, appName, viewingName]);
|
||||
|
||||
/*
|
||||
* Leave the service worker its briefing.
|
||||
@@ -276,8 +325,11 @@ function AuthedApp() {
|
||||
const archiveId = useMail((s) => s.roleId("archive"));
|
||||
const languageVersion = useLanguageVersion();
|
||||
useEffect(() => {
|
||||
// inbuxa AL-7: the worker acts on the reader's own mail; while a
|
||||
// delegated account is in view, the archive folder here is its
|
||||
if (viewing) return;
|
||||
void publishWorkerFacts(accountId, archiveId);
|
||||
}, [accountId, archiveId, languageVersion]);
|
||||
}, [accountId, archiveId, languageVersion, viewing]);
|
||||
|
||||
// Request notification permission lazily when enabled
|
||||
const notif = useSettings((s) => s.settings.desktopNotifications);
|
||||
|
||||
Reference in new issue
Block a user