Show email subject lines.

This includes a bunch of new things. I've introduced "ensureEmail..." to
indicate that the UI would like some data to be populated, but if it is
already present we don't need to do anything.

I've also introduced a cache for emails that is keyed on the email ID. I
don't know if email IDs are unique. They look like they should be
globally unique within a given server, but I'm not sure and the standard
is unclear. It'll need some experimentation.
This commit is contained in:
Eli Ribble 2024-08-28 09:21:31 -07:00
parent 5c7e67a2bd
commit a34d8f53b3
4 changed files with 107 additions and 23 deletions

View File

@ -3,6 +3,7 @@ import Stack from "react-bootstrap/Stack";
import Client from "./client/Client";
import { IAccount, IMailbox } from "./client/types";
import EmailSummary from "./EmailSummary";
type EmailListProps = {
account: IAccount | null;
@ -17,37 +18,31 @@ class EmailList extends React.Component<EmailListProps, EmailListState> {
if (this.props.account == null) return;
if (this.props.client == null) return;
if (this.props.mailbox == null) return;
this.props.client.emailList(
this.props.client.ensureEmailList(
this.props.account.id,
this.props.mailbox.id,
[],
);
}
render() {
if (
this.props.account == null ||
this.props.client == null ||
this.props.mailbox == null ||
this.props.mailbox.emailIds == null
) {
return <Stack />;
} else if (this.props.mailbox.emails == null) {
return (
<Stack>
{this.props.mailbox.emailIds.map((e) => (
<div className="p-2" key={e}>
Email {e}
</div>
))}
</Stack>
);
} else {
return (
<Stack>
{this.props.mailbox.emails.map((m) => (
<div className="p-2" key={m.id}>
{m.subject}
</div>
{this.props.mailbox.emailIds.slice(0, 5).map((e) => (
<EmailSummary
account={this.props.account}
client={this.props.client}
emailId={e}
email={this.props.client!.email(e)}
key={e}
/>
))}
</Stack>
);

40
src/EmailSummary.tsx Normal file
View File

@ -0,0 +1,40 @@
import React from "react";
import Client from "./client/Client";
import { IAccount, IEmail } from "./client/types";
type EmailSummaryProps = {
account: IAccount | null;
client: Client | null;
email: IEmail | null;
emailId: string;
};
type EmailSummaryState = {};
class EmailSummary extends React.Component<
EmailSummaryProps,
EmailSummaryState
> {
componentDidMount() {
this.ensureData();
}
componentDidUpdate() {
this.ensureData();
}
ensureData() {
if (this.props.account == null) return;
if (this.props.client == null) return;
this.props.client.ensureEmailGet(this.props.account.id, this.props.emailId);
}
render() {
return (
<div className="p-2" key={this.props.emailId}>
{this.props.email != null
? this.props.email.subject
: this.props.emailId}
</div>
);
}
}
export default EmailSummary;

View File

@ -6,7 +6,7 @@ import * as base64 from "base-64";
import * as jmapclient from "jmap-client-ts";
import { FetchTransport } from "jmap-client-ts/lib/utils/fetch-transport";
import { IAccount, IMailbox, ISession } from "./types";
import { IAccount, IEmail, IMailbox, ISession } from "./types";
type Callback = () => void;
@ -36,11 +36,27 @@ export default class Client {
this.callbacks.push(f);
}
// Ensure we have the fully-fleshed email
ensureEmailGet(accountId: string, emailId: string) {
if (this.state.session == null) return;
const existing = this.state.session.emails[emailId];
if (existing != null) return;
this.emailGet(accountId, emailId);
}
// Ensure we have the list of emails for the provided account and mailbox
ensureEmailList(accountId: string, mailboxId: string) {
const mailbox = this.mailbox(accountId, mailboxId);
if (mailbox != null && mailbox.emailIds != null) return;
this.emailList(accountId, mailboxId, []);
}
// Ensure that login happens. If this is called many times, only login once.
ensureLogin(auth: IAuth) {
if (this.jclient != null) return;
this.doLogin(auth);
}
// Make the request to get system metadata
doLogin(auth: IAuth) {
const domain = auth.email.split("@")[1];
@ -65,13 +81,41 @@ export default class Client {
return;
}
email(emailId: string): IEmail | null {
if (this.state.session == null) return null;
return this.state.session.emails[emailId];
}
emailGet(accountId: string, emailId: string) {
if (this.jclient == null) return;
this.jclient
.email_get({
accountId: accountId,
ids: [emailId],
properties: ["mailboxIds", "subject"],
})
.then((response) => {
console.log("Email response", response);
response.list.forEach((e) => {
if (this.state.session == null) return;
const existing = this.state.session.emails[e.id];
if (existing !== undefined && existing.subject !== e.subject) {
console.error(
"Expectations violation: email ID is not unique within the server!",
);
}
this.state.session.emails[e.id] = e;
this._triggerChange();
});
})
.catch((x) => {
console.error("Failed to get email", emailId, x);
});
}
emailList(accountId: string, mailboxId: string, ids: Array<string>) {
if (this.jclient == null) return;
/*this.jclient.email_get({
accountId: accountId,
ids: [],
properties: ["threadId"]
});*/
this.jclient
.email_query({
accountId: accountId,
@ -84,7 +128,7 @@ export default class Client {
this._triggerChange();
})
.catch(() => {
console.error("OH NOES");
console.error("Failed to get email list from mailbox", mailboxId);
});
}
@ -145,6 +189,7 @@ export default class Client {
{ ...account, id: key.toString(), mailboxes: null },
]),
),
emails: {},
};
if (!this.state.session) return;
this._triggerChange();

View File

@ -5,13 +5,17 @@ export interface IMailbox extends client.IMailboxProperties {
emails: Array<client.IEmailProperties> | null;
}
export interface IEmail extends client.IEmailProperties {}
export interface IAccount extends client.IAccount {
id: string;
mailboxes: Array<IMailbox> | null;
}
export type AccountIdMap = { [accountId: string]: IAccount };
export type EmailIdMap = { [emailId: string]: IEmail };
export interface ISession extends client.ISession {
accounts: AccountIdMap;
emails: EmailIdMap;
}