webmail: add server-side stored settings, for signature, top/bottom reply and showing the security indications below address input fields

should solve #102
This commit is contained in:
Mechiel Lukkien 2024-04-19 17:24:54 +02:00
parent 3bbd7c7d9b
commit 70adf353ee
No known key found for this signature in database
9 changed files with 409 additions and 27 deletions

View file

@ -719,8 +719,44 @@ type LoginSession struct {
csrfToken CSRFToken csrfToken CSRFToken
} }
// Quoting is a setting for how to quote in replies/forwards.
type Quoting string
const (
Default Quoting = "" // Bottom-quote if text is selected, top-quote otherwise.
Bottom Quoting = "bottom"
Top Quoting = "top"
)
// Settings are webmail client settings.
type Settings struct {
ID uint8 // Singleton ID 1.
Signature string
Quoting Quoting
// Whether to show the bars underneath the address input fields indicating
// starttls/dnssec/dane/mtasts/requiretls support by address.
ShowAddressSecurity bool
}
// Types stored in DB. // Types stored in DB.
var DBTypes = []any{NextUIDValidity{}, Message{}, Recipient{}, Mailbox{}, Subscription{}, Outgoing{}, Password{}, Subjectpass{}, SyncState{}, Upgrade{}, RecipientDomainTLS{}, DiskUsage{}, LoginSession{}} var DBTypes = []any{
NextUIDValidity{},
Message{},
Recipient{},
Mailbox{},
Subscription{},
Outgoing{},
Password{},
Subjectpass{},
SyncState{},
Upgrade{},
RecipientDomainTLS{},
DiskUsage{},
LoginSession{},
Settings{},
}
// Account holds the information about a user, includings mailboxes, messages, imap subscriptions. // Account holds the information about a user, includings mailboxes, messages, imap subscriptions.
type Account struct { type Account struct {
@ -850,9 +886,15 @@ func OpenAccountDB(log mlog.Log, accountDir, accountName string) (a *Account, re
return acc, nil return acc, nil
} }
// Ensure mailbox counts and total message size are set. // Ensure singletons are present. Mailbox counts and total message size, Settings.
var mentioned bool var mentioned bool
err = db.Write(context.TODO(), func(tx *bstore.Tx) error { err = db.Write(context.TODO(), func(tx *bstore.Tx) error {
if tx.Get(&Settings{ID: 1}) == bstore.ErrAbsent {
if err := tx.Insert(&Settings{ID: 1, ShowAddressSecurity: true}); err != nil {
return err
}
}
err := bstore.QueryTx[Mailbox](tx).FilterEqual("HaveCounts", false).ForEach(func(mb Mailbox) error { err := bstore.QueryTx[Mailbox](tx).FilterEqual("HaveCounts", false).ForEach(func(mb Mailbox) error {
if !mentioned { if !mentioned {
mentioned = true mentioned = true
@ -886,7 +928,7 @@ func OpenAccountDB(log mlog.Log, accountDir, accountName string) (a *Account, re
return tx.Insert(&du) return tx.Insert(&du)
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("calculating counts for mailbox: %v", err) return nil, fmt.Errorf("calculating counts for mailbox or inserting settings: %v", err)
} }
// Start adding threading if needed. // Start adding threading if needed.
@ -974,6 +1016,9 @@ func initAccount(db *bstore.DB) error {
if err := tx.Insert(&DiskUsage{ID: 1}); err != nil { if err := tx.Insert(&DiskUsage{ID: 1}); err != nil {
return err return err
} }
if err := tx.Insert(&Settings{ID: 1}); err != nil {
return err
}
if len(mox.Conf.Static.DefaultMailboxes) > 0 { if len(mox.Conf.Static.DefaultMailboxes) > 0 {
// Deprecated in favor of InitialMailboxes. // Deprecated in favor of InitialMailboxes.

View file

@ -1536,6 +1536,24 @@ func (Webmail) DecodeMIMEWords(ctx context.Context, text string) string {
return s return s
} }
// SettingsSave saves settings, e.g. for composing.
func (Webmail) SettingsSave(ctx context.Context, settings store.Settings) {
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
if acc != nil {
err := acc.Close()
log.Check(err, "closing account")
}
}()
settings.ID = 1
err = acc.DB.Update(ctx, &settings)
xcheckf(ctx, err, "save settings")
}
func slicesAny[T any](l []T) []any { func slicesAny[T any](l []T) []any {
r := make([]any, len(l)) r := make([]any, len(l))
for i, v := range l { for i, v := range l {

View file

@ -366,6 +366,19 @@
} }
] ]
}, },
{
"Name": "SettingsSave",
"Docs": "SettingsSave saves settings, e.g. for composing.",
"Params": [
{
"Name": "settings",
"Typewords": [
"Settings"
]
}
],
"Returns": []
},
{ {
"Name": "SSETypes", "Name": "SSETypes",
"Docs": "SSETypes exists to ensure the generated API contains the types, for use in SSE events.", "Docs": "SSETypes exists to ensure the generated API contains the types, for use in SSE events.",
@ -1382,6 +1395,40 @@
} }
] ]
}, },
{
"Name": "Settings",
"Docs": "Settings are webmail client settings.",
"Fields": [
{
"Name": "ID",
"Docs": "Singleton ID 1.",
"Typewords": [
"uint8"
]
},
{
"Name": "Signature",
"Docs": "",
"Typewords": [
"string"
]
},
{
"Name": "Quoting",
"Docs": "",
"Typewords": [
"Quoting"
]
},
{
"Name": "ShowAddressSecurity",
"Docs": "Whether to show the bars underneath the address input fields indicating starttls/dnssec/dane/mtasts/requiretls support by address.",
"Typewords": [
"bool"
]
}
]
},
{ {
"Name": "EventStart", "Name": "EventStart",
"Docs": "EventStart is the first message sent on an SSE connection, giving the client\nbasic data to populate its UI. After this event, messages will follow quickly in\nan EventViewMsgs event.", "Docs": "EventStart is the first message sent on an SSE connection, giving the client\nbasic data to populate its UI. After this event, messages will follow quickly in\nan EventViewMsgs event.",
@ -1438,6 +1485,13 @@
"string" "string"
] ]
}, },
{
"Name": "Settings",
"Docs": "",
"Typewords": [
"Settings"
]
},
{ {
"Name": "Version", "Name": "Version",
"Docs": "", "Docs": "",
@ -2815,6 +2869,27 @@
} }
] ]
}, },
{
"Name": "Quoting",
"Docs": "Quoting is a setting for how to quote in replies/forwards.",
"Values": [
{
"Name": "Default",
"Value": "",
"Docs": "Bottom-quote if text is selected, top-quote otherwise."
},
{
"Name": "Bottom",
"Value": "bottom",
"Docs": ""
},
{
"Name": "Top",
"Value": "top",
"Docs": ""
}
]
},
{ {
"Name": "Localpart", "Name": "Localpart",
"Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.\nLocalparts are in Unicode NFC.", "Docs": "Localpart is a decoded local part of an email address, before the \"@\".\nFor quoted strings, values do not hold the double quote or escaping backslashes.\nAn empty string can be a valid localpart.\nLocalparts are in Unicode NFC.",

View file

@ -190,6 +190,14 @@ export interface RecipientSecurity {
RequireTLS: SecurityResult // Whether recipient domain is known to implement the REQUIRETLS SMTP extension. Will be "unknown" if no delivery to the domain has been attempted yet. RequireTLS: SecurityResult // Whether recipient domain is known to implement the REQUIRETLS SMTP extension. Will be "unknown" if no delivery to the domain has been attempted yet.
} }
// Settings are webmail client settings.
export interface Settings {
ID: number // Singleton ID 1.
Signature: string
Quoting: Quoting
ShowAddressSecurity: boolean // Whether to show the bars underneath the address input fields indicating starttls/dnssec/dane/mtasts/requiretls support by address.
}
// EventStart is the first message sent on an SSE connection, giving the client // EventStart is the first message sent on an SSE connection, giving the client
// basic data to populate its UI. After this event, messages will follow quickly in // basic data to populate its UI. After this event, messages will follow quickly in
// an EventViewMsgs event. // an EventViewMsgs event.
@ -201,6 +209,7 @@ export interface EventStart {
MailboxName: string MailboxName: string
Mailboxes?: Mailbox[] | null Mailboxes?: Mailbox[] | null
RejectsMailbox: string RejectsMailbox: string
Settings: Settings
Version: string Version: string
} }
@ -513,14 +522,21 @@ export enum SecurityResult {
SecurityResultUnknown = "unknown", SecurityResultUnknown = "unknown",
} }
// Quoting is a setting for how to quote in replies/forwards.
export enum Quoting {
Default = "", // Bottom-quote if text is selected, top-quote otherwise.
Bottom = "bottom",
Top = "top",
}
// Localpart is a decoded local part of an email address, before the "@". // Localpart is a decoded local part of an email address, before the "@".
// For quoted strings, values do not hold the double quote or escaping backslashes. // For quoted strings, values do not hold the double quote or escaping backslashes.
// An empty string can be a valid localpart. // An empty string can be a valid localpart.
// Localparts are in Unicode NFC. // Localparts are in Unicode NFC.
export type Localpart = string export type Localpart = string
export const structTypes: {[typename: string]: boolean} = {"Address":true,"Attachment":true,"ChangeMailboxAdd":true,"ChangeMailboxCounts":true,"ChangeMailboxKeywords":true,"ChangeMailboxRemove":true,"ChangeMailboxRename":true,"ChangeMailboxSpecialUse":true,"ChangeMsgAdd":true,"ChangeMsgFlags":true,"ChangeMsgRemove":true,"ChangeMsgThread":true,"Domain":true,"DomainAddressConfig":true,"Envelope":true,"EventStart":true,"EventViewChanges":true,"EventViewErr":true,"EventViewMsgs":true,"EventViewReset":true,"File":true,"Filter":true,"Flags":true,"ForwardAttachments":true,"Mailbox":true,"Message":true,"MessageAddress":true,"MessageEnvelope":true,"MessageItem":true,"NotFilter":true,"Page":true,"ParsedMessage":true,"Part":true,"Query":true,"RecipientSecurity":true,"Request":true,"SpecialUse":true,"SubmitMessage":true} export const structTypes: {[typename: string]: boolean} = {"Address":true,"Attachment":true,"ChangeMailboxAdd":true,"ChangeMailboxCounts":true,"ChangeMailboxKeywords":true,"ChangeMailboxRemove":true,"ChangeMailboxRename":true,"ChangeMailboxSpecialUse":true,"ChangeMsgAdd":true,"ChangeMsgFlags":true,"ChangeMsgRemove":true,"ChangeMsgThread":true,"Domain":true,"DomainAddressConfig":true,"Envelope":true,"EventStart":true,"EventViewChanges":true,"EventViewErr":true,"EventViewMsgs":true,"EventViewReset":true,"File":true,"Filter":true,"Flags":true,"ForwardAttachments":true,"Mailbox":true,"Message":true,"MessageAddress":true,"MessageEnvelope":true,"MessageItem":true,"NotFilter":true,"Page":true,"ParsedMessage":true,"Part":true,"Query":true,"RecipientSecurity":true,"Request":true,"Settings":true,"SpecialUse":true,"SubmitMessage":true}
export const stringsTypes: {[typename: string]: boolean} = {"AttachmentType":true,"CSRFToken":true,"Localpart":true,"SecurityResult":true,"ThreadMode":true} export const stringsTypes: {[typename: string]: boolean} = {"AttachmentType":true,"CSRFToken":true,"Localpart":true,"Quoting":true,"SecurityResult":true,"ThreadMode":true}
export const intsTypes: {[typename: string]: boolean} = {"ModSeq":true,"UID":true,"Validation":true} export const intsTypes: {[typename: string]: boolean} = {"ModSeq":true,"UID":true,"Validation":true}
export const types: TypenameMap = { export const types: TypenameMap = {
"Request": {"Name":"Request","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"Cancel","Docs":"","Typewords":["bool"]},{"Name":"Query","Docs":"","Typewords":["Query"]},{"Name":"Page","Docs":"","Typewords":["Page"]}]}, "Request": {"Name":"Request","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"Cancel","Docs":"","Typewords":["bool"]},{"Name":"Query","Docs":"","Typewords":["Query"]},{"Name":"Page","Docs":"","Typewords":["Page"]}]},
@ -539,7 +555,8 @@ export const types: TypenameMap = {
"ForwardAttachments": {"Name":"ForwardAttachments","Docs":"","Fields":[{"Name":"MessageID","Docs":"","Typewords":["int64"]},{"Name":"Paths","Docs":"","Typewords":["[]","[]","int32"]}]}, "ForwardAttachments": {"Name":"ForwardAttachments","Docs":"","Fields":[{"Name":"MessageID","Docs":"","Typewords":["int64"]},{"Name":"Paths","Docs":"","Typewords":["[]","[]","int32"]}]},
"Mailbox": {"Name":"Mailbox","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"UIDValidity","Docs":"","Typewords":["uint32"]},{"Name":"UIDNext","Docs":"","Typewords":["UID"]},{"Name":"Archive","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Sent","Docs":"","Typewords":["bool"]},{"Name":"Trash","Docs":"","Typewords":["bool"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]},{"Name":"HaveCounts","Docs":"","Typewords":["bool"]},{"Name":"Total","Docs":"","Typewords":["int64"]},{"Name":"Deleted","Docs":"","Typewords":["int64"]},{"Name":"Unread","Docs":"","Typewords":["int64"]},{"Name":"Unseen","Docs":"","Typewords":["int64"]},{"Name":"Size","Docs":"","Typewords":["int64"]}]}, "Mailbox": {"Name":"Mailbox","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"UIDValidity","Docs":"","Typewords":["uint32"]},{"Name":"UIDNext","Docs":"","Typewords":["UID"]},{"Name":"Archive","Docs":"","Typewords":["bool"]},{"Name":"Draft","Docs":"","Typewords":["bool"]},{"Name":"Junk","Docs":"","Typewords":["bool"]},{"Name":"Sent","Docs":"","Typewords":["bool"]},{"Name":"Trash","Docs":"","Typewords":["bool"]},{"Name":"Keywords","Docs":"","Typewords":["[]","string"]},{"Name":"HaveCounts","Docs":"","Typewords":["bool"]},{"Name":"Total","Docs":"","Typewords":["int64"]},{"Name":"Deleted","Docs":"","Typewords":["int64"]},{"Name":"Unread","Docs":"","Typewords":["int64"]},{"Name":"Unseen","Docs":"","Typewords":["int64"]},{"Name":"Size","Docs":"","Typewords":["int64"]}]},
"RecipientSecurity": {"Name":"RecipientSecurity","Docs":"","Fields":[{"Name":"STARTTLS","Docs":"","Typewords":["SecurityResult"]},{"Name":"MTASTS","Docs":"","Typewords":["SecurityResult"]},{"Name":"DNSSEC","Docs":"","Typewords":["SecurityResult"]},{"Name":"DANE","Docs":"","Typewords":["SecurityResult"]},{"Name":"RequireTLS","Docs":"","Typewords":["SecurityResult"]}]}, "RecipientSecurity": {"Name":"RecipientSecurity","Docs":"","Fields":[{"Name":"STARTTLS","Docs":"","Typewords":["SecurityResult"]},{"Name":"MTASTS","Docs":"","Typewords":["SecurityResult"]},{"Name":"DNSSEC","Docs":"","Typewords":["SecurityResult"]},{"Name":"DANE","Docs":"","Typewords":["SecurityResult"]},{"Name":"RequireTLS","Docs":"","Typewords":["SecurityResult"]}]},
"EventStart": {"Name":"EventStart","Docs":"","Fields":[{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"LoginAddress","Docs":"","Typewords":["MessageAddress"]},{"Name":"Addresses","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"DomainAddressConfigs","Docs":"","Typewords":["{}","DomainAddressConfig"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Mailboxes","Docs":"","Typewords":["[]","Mailbox"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"Version","Docs":"","Typewords":["string"]}]}, "Settings": {"Name":"Settings","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["uint8"]},{"Name":"Signature","Docs":"","Typewords":["string"]},{"Name":"Quoting","Docs":"","Typewords":["Quoting"]},{"Name":"ShowAddressSecurity","Docs":"","Typewords":["bool"]}]},
"EventStart": {"Name":"EventStart","Docs":"","Fields":[{"Name":"SSEID","Docs":"","Typewords":["int64"]},{"Name":"LoginAddress","Docs":"","Typewords":["MessageAddress"]},{"Name":"Addresses","Docs":"","Typewords":["[]","MessageAddress"]},{"Name":"DomainAddressConfigs","Docs":"","Typewords":["{}","DomainAddressConfig"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Mailboxes","Docs":"","Typewords":["[]","Mailbox"]},{"Name":"RejectsMailbox","Docs":"","Typewords":["string"]},{"Name":"Settings","Docs":"","Typewords":["Settings"]},{"Name":"Version","Docs":"","Typewords":["string"]}]},
"DomainAddressConfig": {"Name":"DomainAddressConfig","Docs":"","Fields":[{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]}]}, "DomainAddressConfig": {"Name":"DomainAddressConfig","Docs":"","Fields":[{"Name":"LocalpartCatchallSeparator","Docs":"","Typewords":["string"]},{"Name":"LocalpartCaseSensitive","Docs":"","Typewords":["bool"]}]},
"EventViewErr": {"Name":"EventViewErr","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"Err","Docs":"","Typewords":["string"]}]}, "EventViewErr": {"Name":"EventViewErr","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]},{"Name":"Err","Docs":"","Typewords":["string"]}]},
"EventViewReset": {"Name":"EventViewReset","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]}]}, "EventViewReset": {"Name":"EventViewReset","Docs":"","Fields":[{"Name":"ViewID","Docs":"","Typewords":["int64"]},{"Name":"RequestID","Docs":"","Typewords":["int64"]}]},
@ -568,6 +585,7 @@ export const types: TypenameMap = {
"ThreadMode": {"Name":"ThreadMode","Docs":"","Values":[{"Name":"ThreadOff","Value":"off","Docs":""},{"Name":"ThreadOn","Value":"on","Docs":""},{"Name":"ThreadUnread","Value":"unread","Docs":""}]}, "ThreadMode": {"Name":"ThreadMode","Docs":"","Values":[{"Name":"ThreadOff","Value":"off","Docs":""},{"Name":"ThreadOn","Value":"on","Docs":""},{"Name":"ThreadUnread","Value":"unread","Docs":""}]},
"AttachmentType": {"Name":"AttachmentType","Docs":"","Values":[{"Name":"AttachmentIndifferent","Value":"","Docs":""},{"Name":"AttachmentNone","Value":"none","Docs":""},{"Name":"AttachmentAny","Value":"any","Docs":""},{"Name":"AttachmentImage","Value":"image","Docs":""},{"Name":"AttachmentPDF","Value":"pdf","Docs":""},{"Name":"AttachmentArchive","Value":"archive","Docs":""},{"Name":"AttachmentSpreadsheet","Value":"spreadsheet","Docs":""},{"Name":"AttachmentDocument","Value":"document","Docs":""},{"Name":"AttachmentPresentation","Value":"presentation","Docs":""}]}, "AttachmentType": {"Name":"AttachmentType","Docs":"","Values":[{"Name":"AttachmentIndifferent","Value":"","Docs":""},{"Name":"AttachmentNone","Value":"none","Docs":""},{"Name":"AttachmentAny","Value":"any","Docs":""},{"Name":"AttachmentImage","Value":"image","Docs":""},{"Name":"AttachmentPDF","Value":"pdf","Docs":""},{"Name":"AttachmentArchive","Value":"archive","Docs":""},{"Name":"AttachmentSpreadsheet","Value":"spreadsheet","Docs":""},{"Name":"AttachmentDocument","Value":"document","Docs":""},{"Name":"AttachmentPresentation","Value":"presentation","Docs":""}]},
"SecurityResult": {"Name":"SecurityResult","Docs":"","Values":[{"Name":"SecurityResultError","Value":"error","Docs":""},{"Name":"SecurityResultNo","Value":"no","Docs":""},{"Name":"SecurityResultYes","Value":"yes","Docs":""},{"Name":"SecurityResultUnknown","Value":"unknown","Docs":""}]}, "SecurityResult": {"Name":"SecurityResult","Docs":"","Values":[{"Name":"SecurityResultError","Value":"error","Docs":""},{"Name":"SecurityResultNo","Value":"no","Docs":""},{"Name":"SecurityResultYes","Value":"yes","Docs":""},{"Name":"SecurityResultUnknown","Value":"unknown","Docs":""}]},
"Quoting": {"Name":"Quoting","Docs":"","Values":[{"Name":"Default","Value":"","Docs":""},{"Name":"Bottom","Value":"bottom","Docs":""},{"Name":"Top","Value":"top","Docs":""}]},
"Localpart": {"Name":"Localpart","Docs":"","Values":null}, "Localpart": {"Name":"Localpart","Docs":"","Values":null},
} }
@ -588,6 +606,7 @@ export const parser = {
ForwardAttachments: (v: any) => parse("ForwardAttachments", v) as ForwardAttachments, ForwardAttachments: (v: any) => parse("ForwardAttachments", v) as ForwardAttachments,
Mailbox: (v: any) => parse("Mailbox", v) as Mailbox, Mailbox: (v: any) => parse("Mailbox", v) as Mailbox,
RecipientSecurity: (v: any) => parse("RecipientSecurity", v) as RecipientSecurity, RecipientSecurity: (v: any) => parse("RecipientSecurity", v) as RecipientSecurity,
Settings: (v: any) => parse("Settings", v) as Settings,
EventStart: (v: any) => parse("EventStart", v) as EventStart, EventStart: (v: any) => parse("EventStart", v) as EventStart,
DomainAddressConfig: (v: any) => parse("DomainAddressConfig", v) as DomainAddressConfig, DomainAddressConfig: (v: any) => parse("DomainAddressConfig", v) as DomainAddressConfig,
EventViewErr: (v: any) => parse("EventViewErr", v) as EventViewErr, EventViewErr: (v: any) => parse("EventViewErr", v) as EventViewErr,
@ -617,6 +636,7 @@ export const parser = {
ThreadMode: (v: any) => parse("ThreadMode", v) as ThreadMode, ThreadMode: (v: any) => parse("ThreadMode", v) as ThreadMode,
AttachmentType: (v: any) => parse("AttachmentType", v) as AttachmentType, AttachmentType: (v: any) => parse("AttachmentType", v) as AttachmentType,
SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult, SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult,
Quoting: (v: any) => parse("Quoting", v) as Quoting,
Localpart: (v: any) => parse("Localpart", v) as Localpart, Localpart: (v: any) => parse("Localpart", v) as Localpart,
} }
@ -861,6 +881,15 @@ export class Client {
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as string
} }
// SettingsSave saves settings, e.g. for composing.
async SettingsSave(settings: Settings): Promise<void> {
const fn: string = "SettingsSave"
const paramTypes: string[][] = [["Settings"]]
const returnTypes: string[][] = []
const params: any[] = [settings]
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
}
// SSETypes exists to ensure the generated API contains the types, for use in SSE events. // SSETypes exists to ensure the generated API contains the types, for use in SSE events.
async SSETypes(): Promise<[EventStart, EventViewErr, EventViewReset, EventViewMsgs, EventViewChanges, ChangeMsgAdd, ChangeMsgRemove, ChangeMsgFlags, ChangeMsgThread, ChangeMailboxRemove, ChangeMailboxAdd, ChangeMailboxRename, ChangeMailboxCounts, ChangeMailboxSpecialUse, ChangeMailboxKeywords, Flags]> { async SSETypes(): Promise<[EventStart, EventViewErr, EventViewReset, EventViewMsgs, EventViewChanges, ChangeMsgAdd, ChangeMsgRemove, ChangeMsgFlags, ChangeMsgThread, ChangeMailboxRemove, ChangeMailboxAdd, ChangeMailboxRename, ChangeMailboxCounts, ChangeMailboxSpecialUse, ChangeMailboxKeywords, Flags]> {
const fn: string = "SSETypes" const fn: string = "SSETypes"

View file

@ -274,8 +274,15 @@ var api;
// lookups. // lookups.
SecurityResult["SecurityResultUnknown"] = "unknown"; SecurityResult["SecurityResultUnknown"] = "unknown";
})(SecurityResult = api.SecurityResult || (api.SecurityResult = {})); })(SecurityResult = api.SecurityResult || (api.SecurityResult = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "SpecialUse": true, "SubmitMessage": true }; // Quoting is a setting for how to quote in replies/forwards.
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true }; let Quoting;
(function (Quoting) {
Quoting["Default"] = "";
Quoting["Bottom"] = "bottom";
Quoting["Top"] = "top";
})(Quoting = api.Quoting || (api.Quoting = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "Settings": true, "SpecialUse": true, "SubmitMessage": true };
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "Quoting": true, "SecurityResult": true, "ThreadMode": true };
api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true }; api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true };
api.types = { api.types = {
"Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] }, "Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] },
@ -294,7 +301,8 @@ var api;
"ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] }, "ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] },
"Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] }, "Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] },
"RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] }, "RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] }, "Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] }, "DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] }, "EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] }, "EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
@ -323,6 +331,7 @@ var api;
"ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] }, "ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] },
"AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] }, "AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] },
"SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] }, "SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] },
"Quoting": { "Name": "Quoting", "Docs": "", "Values": [{ "Name": "Default", "Value": "", "Docs": "" }, { "Name": "Bottom", "Value": "bottom", "Docs": "" }, { "Name": "Top", "Value": "top", "Docs": "" }] },
"Localpart": { "Name": "Localpart", "Docs": "", "Values": null }, "Localpart": { "Name": "Localpart", "Docs": "", "Values": null },
}; };
api.parser = { api.parser = {
@ -342,6 +351,7 @@ var api;
ForwardAttachments: (v) => api.parse("ForwardAttachments", v), ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
Mailbox: (v) => api.parse("Mailbox", v), Mailbox: (v) => api.parse("Mailbox", v),
RecipientSecurity: (v) => api.parse("RecipientSecurity", v), RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
Settings: (v) => api.parse("Settings", v),
EventStart: (v) => api.parse("EventStart", v), EventStart: (v) => api.parse("EventStart", v),
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v), DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
EventViewErr: (v) => api.parse("EventViewErr", v), EventViewErr: (v) => api.parse("EventViewErr", v),
@ -371,6 +381,7 @@ var api;
ThreadMode: (v) => api.parse("ThreadMode", v), ThreadMode: (v) => api.parse("ThreadMode", v),
AttachmentType: (v) => api.parse("AttachmentType", v), AttachmentType: (v) => api.parse("AttachmentType", v),
SecurityResult: (v) => api.parse("SecurityResult", v), SecurityResult: (v) => api.parse("SecurityResult", v),
Quoting: (v) => api.parse("Quoting", v),
Localpart: (v) => api.parse("Localpart", v), Localpart: (v) => api.parse("Localpart", v),
}; };
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true }; let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
@ -588,6 +599,14 @@ var api;
const params = [text]; const params = [text];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// SettingsSave saves settings, e.g. for composing.
async SettingsSave(settings) {
const fn = "SettingsSave";
const paramTypes = [["Settings"]];
const returnTypes = [];
const params = [settings];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// SSETypes exists to ensure the generated API contains the types, for use in SSE events. // SSETypes exists to ensure the generated API contains the types, for use in SSE events.
async SSETypes() { async SSETypes() {
const fn = "SSETypes"; const fn = "SSETypes";

View file

@ -274,8 +274,15 @@ var api;
// lookups. // lookups.
SecurityResult["SecurityResultUnknown"] = "unknown"; SecurityResult["SecurityResultUnknown"] = "unknown";
})(SecurityResult = api.SecurityResult || (api.SecurityResult = {})); })(SecurityResult = api.SecurityResult || (api.SecurityResult = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "SpecialUse": true, "SubmitMessage": true }; // Quoting is a setting for how to quote in replies/forwards.
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true }; let Quoting;
(function (Quoting) {
Quoting["Default"] = "";
Quoting["Bottom"] = "bottom";
Quoting["Top"] = "top";
})(Quoting = api.Quoting || (api.Quoting = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "Settings": true, "SpecialUse": true, "SubmitMessage": true };
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "Quoting": true, "SecurityResult": true, "ThreadMode": true };
api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true }; api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true };
api.types = { api.types = {
"Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] }, "Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] },
@ -294,7 +301,8 @@ var api;
"ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] }, "ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] },
"Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] }, "Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] },
"RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] }, "RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] }, "Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] }, "DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] }, "EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] }, "EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
@ -323,6 +331,7 @@ var api;
"ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] }, "ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] },
"AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] }, "AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] },
"SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] }, "SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] },
"Quoting": { "Name": "Quoting", "Docs": "", "Values": [{ "Name": "Default", "Value": "", "Docs": "" }, { "Name": "Bottom", "Value": "bottom", "Docs": "" }, { "Name": "Top", "Value": "top", "Docs": "" }] },
"Localpart": { "Name": "Localpart", "Docs": "", "Values": null }, "Localpart": { "Name": "Localpart", "Docs": "", "Values": null },
}; };
api.parser = { api.parser = {
@ -342,6 +351,7 @@ var api;
ForwardAttachments: (v) => api.parse("ForwardAttachments", v), ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
Mailbox: (v) => api.parse("Mailbox", v), Mailbox: (v) => api.parse("Mailbox", v),
RecipientSecurity: (v) => api.parse("RecipientSecurity", v), RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
Settings: (v) => api.parse("Settings", v),
EventStart: (v) => api.parse("EventStart", v), EventStart: (v) => api.parse("EventStart", v),
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v), DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
EventViewErr: (v) => api.parse("EventViewErr", v), EventViewErr: (v) => api.parse("EventViewErr", v),
@ -371,6 +381,7 @@ var api;
ThreadMode: (v) => api.parse("ThreadMode", v), ThreadMode: (v) => api.parse("ThreadMode", v),
AttachmentType: (v) => api.parse("AttachmentType", v), AttachmentType: (v) => api.parse("AttachmentType", v),
SecurityResult: (v) => api.parse("SecurityResult", v), SecurityResult: (v) => api.parse("SecurityResult", v),
Quoting: (v) => api.parse("Quoting", v),
Localpart: (v) => api.parse("Localpart", v), Localpart: (v) => api.parse("Localpart", v),
}; };
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true }; let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
@ -588,6 +599,14 @@ var api;
const params = [text]; const params = [text];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// SettingsSave saves settings, e.g. for composing.
async SettingsSave(settings) {
const fn = "SettingsSave";
const paramTypes = [["Settings"]];
const returnTypes = [];
const params = [settings];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// SSETypes exists to ensure the generated API contains the types, for use in SSE events. // SSETypes exists to ensure the generated API contains the types, for use in SSE events.
async SSETypes() { async SSETypes() {
const fn = "SSETypes"; const fn = "SSETypes";

View file

@ -215,6 +215,7 @@ type EventStart struct {
MailboxName string MailboxName string
Mailboxes []store.Mailbox Mailboxes []store.Mailbox
RejectsMailbox string RejectsMailbox string
Settings store.Settings
Version string Version string
} }
@ -664,6 +665,7 @@ func serveEvents(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *ht
}() }()
var mbl []store.Mailbox var mbl []store.Mailbox
settings := store.Settings{ID: 1}
// We only take the rlock when getting the tx. // We only take the rlock when getting the tx.
acc.WithRLock(func() { acc.WithRLock(func() {
@ -673,6 +675,9 @@ func serveEvents(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *ht
mbl, err = bstore.QueryTx[store.Mailbox](qtx).List() mbl, err = bstore.QueryTx[store.Mailbox](qtx).List()
xcheckf(ctx, err, "list mailboxes") xcheckf(ctx, err, "list mailboxes")
err = qtx.Get(&settings)
xcheckf(ctx, err, "get settings")
}) })
// Find the designated mailbox if a mailbox name is set, or there are no filters at all. // Find the designated mailbox if a mailbox name is set, or there are no filters at all.
@ -728,7 +733,7 @@ func serveEvents(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *ht
} }
// Write first event, allowing client to fill its UI with mailboxes. // Write first event, allowing client to fill its UI with mailboxes.
start := EventStart{sse.ID, loginAddress, addresses, domainAddressConfigs, mailbox.Name, mbl, accConf.RejectsMailbox, moxvar.Version} start := EventStart{sse.ID, loginAddress, addresses, domainAddressConfigs, mailbox.Name, mbl, accConf.RejectsMailbox, settings, moxvar.Version}
writer.xsendEvent(ctx, log, "start", start) writer.xsendEvent(ctx, log, "start", start)
// The goroutine doing the querying will send messages on these channels, which // The goroutine doing the querying will send messages on these channels, which

View file

@ -274,8 +274,15 @@ var api;
// lookups. // lookups.
SecurityResult["SecurityResultUnknown"] = "unknown"; SecurityResult["SecurityResultUnknown"] = "unknown";
})(SecurityResult = api.SecurityResult || (api.SecurityResult = {})); })(SecurityResult = api.SecurityResult || (api.SecurityResult = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "SpecialUse": true, "SubmitMessage": true }; // Quoting is a setting for how to quote in replies/forwards.
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true }; let Quoting;
(function (Quoting) {
Quoting["Default"] = "";
Quoting["Bottom"] = "bottom";
Quoting["Top"] = "top";
})(Quoting = api.Quoting || (api.Quoting = {}));
api.structTypes = { "Address": true, "Attachment": true, "ChangeMailboxAdd": true, "ChangeMailboxCounts": true, "ChangeMailboxKeywords": true, "ChangeMailboxRemove": true, "ChangeMailboxRename": true, "ChangeMailboxSpecialUse": true, "ChangeMsgAdd": true, "ChangeMsgFlags": true, "ChangeMsgRemove": true, "ChangeMsgThread": true, "Domain": true, "DomainAddressConfig": true, "Envelope": true, "EventStart": true, "EventViewChanges": true, "EventViewErr": true, "EventViewMsgs": true, "EventViewReset": true, "File": true, "Filter": true, "Flags": true, "ForwardAttachments": true, "Mailbox": true, "Message": true, "MessageAddress": true, "MessageEnvelope": true, "MessageItem": true, "NotFilter": true, "Page": true, "ParsedMessage": true, "Part": true, "Query": true, "RecipientSecurity": true, "Request": true, "Settings": true, "SpecialUse": true, "SubmitMessage": true };
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "Quoting": true, "SecurityResult": true, "ThreadMode": true };
api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true }; api.intsTypes = { "ModSeq": true, "UID": true, "Validation": true };
api.types = { api.types = {
"Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] }, "Request": { "Name": "Request", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Cancel", "Docs": "", "Typewords": ["bool"] }, { "Name": "Query", "Docs": "", "Typewords": ["Query"] }, { "Name": "Page", "Docs": "", "Typewords": ["Page"] }] },
@ -294,7 +301,8 @@ var api;
"ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] }, "ForwardAttachments": { "Name": "ForwardAttachments", "Docs": "", "Fields": [{ "Name": "MessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Paths", "Docs": "", "Typewords": ["[]", "[]", "int32"] }] },
"Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] }, "Mailbox": { "Name": "Mailbox", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "UIDValidity", "Docs": "", "Typewords": ["uint32"] }, { "Name": "UIDNext", "Docs": "", "Typewords": ["UID"] }, { "Name": "Archive", "Docs": "", "Typewords": ["bool"] }, { "Name": "Draft", "Docs": "", "Typewords": ["bool"] }, { "Name": "Junk", "Docs": "", "Typewords": ["bool"] }, { "Name": "Sent", "Docs": "", "Typewords": ["bool"] }, { "Name": "Trash", "Docs": "", "Typewords": ["bool"] }, { "Name": "Keywords", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HaveCounts", "Docs": "", "Typewords": ["bool"] }, { "Name": "Total", "Docs": "", "Typewords": ["int64"] }, { "Name": "Deleted", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unread", "Docs": "", "Typewords": ["int64"] }, { "Name": "Unseen", "Docs": "", "Typewords": ["int64"] }, { "Name": "Size", "Docs": "", "Typewords": ["int64"] }] },
"RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] }, "RecipientSecurity": { "Name": "RecipientSecurity", "Docs": "", "Fields": [{ "Name": "STARTTLS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "MTASTS", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DNSSEC", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "DANE", "Docs": "", "Typewords": ["SecurityResult"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["SecurityResult"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] }, "Settings": { "Name": "Settings", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["uint8"] }, { "Name": "Signature", "Docs": "", "Typewords": ["string"] }, { "Name": "Quoting", "Docs": "", "Typewords": ["Quoting"] }, { "Name": "ShowAddressSecurity", "Docs": "", "Typewords": ["bool"] }] },
"EventStart": { "Name": "EventStart", "Docs": "", "Fields": [{ "Name": "SSEID", "Docs": "", "Typewords": ["int64"] }, { "Name": "LoginAddress", "Docs": "", "Typewords": ["MessageAddress"] }, { "Name": "Addresses", "Docs": "", "Typewords": ["[]", "MessageAddress"] }, { "Name": "DomainAddressConfigs", "Docs": "", "Typewords": ["{}", "DomainAddressConfig"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Mailboxes", "Docs": "", "Typewords": ["[]", "Mailbox"] }, { "Name": "RejectsMailbox", "Docs": "", "Typewords": ["string"] }, { "Name": "Settings", "Docs": "", "Typewords": ["Settings"] }, { "Name": "Version", "Docs": "", "Typewords": ["string"] }] },
"DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] }, "DomainAddressConfig": { "Name": "DomainAddressConfig", "Docs": "", "Fields": [{ "Name": "LocalpartCatchallSeparator", "Docs": "", "Typewords": ["string"] }, { "Name": "LocalpartCaseSensitive", "Docs": "", "Typewords": ["bool"] }] },
"EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] }, "EventViewErr": { "Name": "EventViewErr", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Err", "Docs": "", "Typewords": ["string"] }] },
"EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] }, "EventViewReset": { "Name": "EventViewReset", "Docs": "", "Fields": [{ "Name": "ViewID", "Docs": "", "Typewords": ["int64"] }, { "Name": "RequestID", "Docs": "", "Typewords": ["int64"] }] },
@ -323,6 +331,7 @@ var api;
"ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] }, "ThreadMode": { "Name": "ThreadMode", "Docs": "", "Values": [{ "Name": "ThreadOff", "Value": "off", "Docs": "" }, { "Name": "ThreadOn", "Value": "on", "Docs": "" }, { "Name": "ThreadUnread", "Value": "unread", "Docs": "" }] },
"AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] }, "AttachmentType": { "Name": "AttachmentType", "Docs": "", "Values": [{ "Name": "AttachmentIndifferent", "Value": "", "Docs": "" }, { "Name": "AttachmentNone", "Value": "none", "Docs": "" }, { "Name": "AttachmentAny", "Value": "any", "Docs": "" }, { "Name": "AttachmentImage", "Value": "image", "Docs": "" }, { "Name": "AttachmentPDF", "Value": "pdf", "Docs": "" }, { "Name": "AttachmentArchive", "Value": "archive", "Docs": "" }, { "Name": "AttachmentSpreadsheet", "Value": "spreadsheet", "Docs": "" }, { "Name": "AttachmentDocument", "Value": "document", "Docs": "" }, { "Name": "AttachmentPresentation", "Value": "presentation", "Docs": "" }] },
"SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] }, "SecurityResult": { "Name": "SecurityResult", "Docs": "", "Values": [{ "Name": "SecurityResultError", "Value": "error", "Docs": "" }, { "Name": "SecurityResultNo", "Value": "no", "Docs": "" }, { "Name": "SecurityResultYes", "Value": "yes", "Docs": "" }, { "Name": "SecurityResultUnknown", "Value": "unknown", "Docs": "" }] },
"Quoting": { "Name": "Quoting", "Docs": "", "Values": [{ "Name": "Default", "Value": "", "Docs": "" }, { "Name": "Bottom", "Value": "bottom", "Docs": "" }, { "Name": "Top", "Value": "top", "Docs": "" }] },
"Localpart": { "Name": "Localpart", "Docs": "", "Values": null }, "Localpart": { "Name": "Localpart", "Docs": "", "Values": null },
}; };
api.parser = { api.parser = {
@ -342,6 +351,7 @@ var api;
ForwardAttachments: (v) => api.parse("ForwardAttachments", v), ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
Mailbox: (v) => api.parse("Mailbox", v), Mailbox: (v) => api.parse("Mailbox", v),
RecipientSecurity: (v) => api.parse("RecipientSecurity", v), RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
Settings: (v) => api.parse("Settings", v),
EventStart: (v) => api.parse("EventStart", v), EventStart: (v) => api.parse("EventStart", v),
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v), DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
EventViewErr: (v) => api.parse("EventViewErr", v), EventViewErr: (v) => api.parse("EventViewErr", v),
@ -371,6 +381,7 @@ var api;
ThreadMode: (v) => api.parse("ThreadMode", v), ThreadMode: (v) => api.parse("ThreadMode", v),
AttachmentType: (v) => api.parse("AttachmentType", v), AttachmentType: (v) => api.parse("AttachmentType", v),
SecurityResult: (v) => api.parse("SecurityResult", v), SecurityResult: (v) => api.parse("SecurityResult", v),
Quoting: (v) => api.parse("Quoting", v),
Localpart: (v) => api.parse("Localpart", v), Localpart: (v) => api.parse("Localpart", v),
}; };
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true }; let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
@ -588,6 +599,14 @@ var api;
const params = [text]; const params = [text];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
} }
// SettingsSave saves settings, e.g. for composing.
async SettingsSave(settings) {
const fn = "SettingsSave";
const paramTypes = [["Settings"]];
const returnTypes = [];
const params = [settings];
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
}
// SSETypes exists to ensure the generated API contains the types, for use in SSE events. // SSETypes exists to ensure the generated API contains the types, for use in SSE events.
async SSETypes() { async SSETypes() {
const fn = "SSETypes"; const fn = "SSETypes";
@ -1257,6 +1276,7 @@ try {
} }
} }
catch (err) { } catch (err) { }
let accountSettings;
const defaultSettings = { const defaultSettings = {
showShortcuts: true, showShortcuts: true,
mailboxesWidth: 240, mailboxesWidth: 240,
@ -1981,6 +2001,20 @@ const withStatus = async (action, promise, disablable, noAlert) => {
} }
} }
}; };
const withDisabled = async (elem, p) => {
try {
elem.disabled = true;
return await p;
}
catch (err) {
console.log({ err });
window.alert('Error: ' + errmsg(err));
throw err;
}
finally {
elem.disabled = false;
}
};
// Popover shows kids in a div on top of a mostly transparent overlay on top of // Popover shows kids in a div on top of a mostly transparent overlay on top of
// the document. If transparent is set, the div the kids are in will not get a // the document. If transparent is set, the div the kids are in will not get a
// white background. If focus is set, it will be called after adding the // white background. If focus is set, it will be called after adding the
@ -2082,6 +2116,29 @@ const popup = (...kids) => {
content.focus(); content.focus();
return close; return close;
}; };
// Show settings screen.
const cmdSettings = async () => {
let fieldset;
let signature;
let quoting;
let showAddressSecurity;
if (!accountSettings) {
window.alert('No account settings fetched yet.');
}
const remove = popup(style({ padding: '1em 1em 2em 1em', minWidth: '30em' }), dom.h1('Settings'), dom.form(async function submit(e) {
e.preventDefault();
e.stopPropagation();
const accSet = {
ID: accountSettings.ID,
Signature: signature.value,
Quoting: quoting.value,
ShowAddressSecurity: showAddressSecurity.checked,
};
await withDisabled(fieldset, client.SettingsSave(accSet));
accountSettings = accSet;
remove();
}, fieldset = dom.fieldset(dom.label(style({ margin: '1ex 0', display: 'block' }), dom.div('Signature'), signature = dom.textarea(new String(accountSettings.Signature), style({ width: '100%' }), attr.rows('' + Math.max(3, 1 + accountSettings.Signature.split('\n').length)))), dom.label(style({ margin: '1ex 0', display: 'block' }), dom.div('Reply above/below original'), attr.title('Auto: If text is selected, only the replied text is quoted and editing starts below. Otherwise, the full message is quoted and editing starts at the top.'), quoting = dom.select(dom.option(attr.value(''), 'Auto'), dom.option(attr.value('bottom'), 'Bottom', accountSettings.Quoting === api.Quoting.Bottom ? attr.selected('') : []), dom.option(attr.value('top'), 'Top', accountSettings.Quoting === api.Quoting.Top ? attr.selected('') : []))), dom.label(style({ margin: '1ex 0', display: 'block' }), showAddressSecurity = dom.input(attr.type('checkbox'), accountSettings.ShowAddressSecurity ? attr.checked('') : []), ' Show address security indications', attr.title('Show bars underneath address input fields, indicating support for STARTTLS/DNSSEC/DANE/MTA-STS/RequireTLS.')), dom.br(), dom.div(dom.submitbutton('Save')))));
};
// Show help popup, with shortcuts and basic explanation. // Show help popup, with shortcuts and basic explanation.
const cmdHelp = async () => { const cmdHelp = async () => {
const remove = popup(style({ padding: '1em 1em 2em 1em' }), dom.h1('Help and keyboard shortcuts'), dom.div(style({ display: 'flex' }), dom.div(style({ width: '40em' }), dom.table(dom.tr(dom.td(attr.colspan('2'), dom.h2('Global', style({ margin: '0' })))), [ const remove = popup(style({ padding: '1em 1em 2em 1em' }), dom.h1('Help and keyboard shortcuts'), dom.div(style({ display: 'flex' }), dom.div(style({ width: '40em' }), dom.table(dom.tr(dom.td(attr.colspan('2'), dom.h2('Global', style({ margin: '0' })))), [
@ -2328,6 +2385,9 @@ const compose = (opts) => {
let rcptSecAborter = {}; let rcptSecAborter = {};
let autosizeElem, inputElem, securityBar; let autosizeElem, inputElem, securityBar;
const fetchRecipientSecurity = () => { const fetchRecipientSecurity = () => {
if (!accountSettings?.ShowAddressSecurity) {
return;
}
if (inputElem.value === rcptSecAddr) { if (inputElem.value === rcptSecAddr) {
return; return;
} }
@ -2411,7 +2471,7 @@ const compose = (opts) => {
}); });
}; };
const recipientSecurityTitle = 'Description of security mechanisms recipient domains may implement:\n1. STARTTLS: Opportunistic (unverified) TLS with STARTTLS, successfully negotiated during the most recent delivery attempt.\n2. MTA-STS: For PKIX/WebPKI-verified TLS.\n3. DNSSEC: MX DNS records are DNSSEC-signed.\n4. DANE: First delivery destination host implements DANE for verified TLS.\n5. RequireTLS: SMTP extension for verified TLS delivery into recipient mailbox, support detected during the most recent delivery attempt.\n\nChecks STARTTLS, DANE and RequireTLS cover the most recently used delivery path, not necessarily all possible delivery paths.\n\nThe bars below the input field indicate implementation status by the recipient domain:\n- Red, not implemented/unsupported\n- Green, implemented/supported\n- Gray, error while determining\n- Absent/white, unknown or skipped (e.g. no previous delivery attempt, or DANE check skipped due to DNSSEC-lookup error)'; const recipientSecurityTitle = 'Description of security mechanisms recipient domains may implement:\n1. STARTTLS: Opportunistic (unverified) TLS with STARTTLS, successfully negotiated during the most recent delivery attempt.\n2. MTA-STS: For PKIX/WebPKI-verified TLS.\n3. DNSSEC: MX DNS records are DNSSEC-signed.\n4. DANE: First delivery destination host implements DANE for verified TLS.\n5. RequireTLS: SMTP extension for verified TLS delivery into recipient mailbox, support detected during the most recent delivery attempt.\n\nChecks STARTTLS, DANE and RequireTLS cover the most recently used delivery path, not necessarily all possible delivery paths.\n\nThe bars below the input field indicate implementation status by the recipient domain:\n- Red, not implemented/unsupported\n- Green, implemented/supported\n- Gray, error while determining\n- Absent/white, unknown or skipped (e.g. no previous delivery attempt, or DANE check skipped due to DNSSEC-lookup error)';
const root = dom.span(autosizeElem = dom.span(dom._class('autosize'), inputElem = dom.input(focusPlaceholder('Jane <jane@example.org>'), style({ width: 'auto' }), attr.value(addr), newAddressComplete(), attr.title(recipientSecurityTitle), function keydown(e) { const root = dom.span(autosizeElem = dom.span(dom._class('autosize'), inputElem = dom.input(focusPlaceholder('Jane <jane@example.org>'), style({ width: 'auto' }), attr.value(addr), newAddressComplete(), accountSettings?.ShowAddressSecurity ? attr.title(recipientSecurityTitle) : [], function keydown(e) {
if (e.key === '-' && e.ctrlKey) { if (e.key === '-' && e.ctrlKey) {
remove(); remove();
} }
@ -2547,7 +2607,7 @@ const compose = (opts) => {
}))))), body = dom.textarea(dom._class('mono'), attr.rows('15'), style({ width: '100%' }), }))))), body = dom.textarea(dom._class('mono'), attr.rows('15'), style({ width: '100%' }),
// Explicit string object so it doesn't get the highlight-unicode-block-changes // Explicit string object so it doesn't get the highlight-unicode-block-changes
// treatment, which would cause characters to disappear. // treatment, which would cause characters to disappear.
new String(opts.body || ''), opts.body && !opts.isForward && !opts.body.startsWith('\n\n') ? prop({ selectionStart: opts.body.length, selectionEnd: opts.body.length }) : [], function keyup(e) { new String(opts.body || ''), prop({ selectionStart: opts.editOffset || 0, selectionEnd: opts.editOffset || 0 }), function keyup(e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
checkAttachments(); checkAttachments();
} }
@ -3075,13 +3135,17 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
body = pm.Texts[0]; body = pm.Texts[0];
} }
body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim(); body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim();
let editOffset = 0;
if (forward) { if (forward) {
body = '\n\n---- Forwarded Message ----\n\n' + body; body = '\n\n---- Forwarded Message ----\n\n' + body;
} }
else { else {
body = body.split('\n').map(line => '> ' + line).join('\n'); body = body.split('\n').map(line => '> ' + line).join('\n');
if (haveSel) { let sig = accountSettings?.Signature || '';
if (!accountSettings?.Quoting && haveSel || accountSettings?.Quoting === api.Quoting.Bottom) {
body += '\n\n'; body += '\n\n';
editOffset = body.length;
body += '\n\n' + sig;
} }
else { else {
let onWroteLine = ''; let onWroteLine = '';
@ -3091,7 +3155,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
const datetime = mi.Envelope.Date.toLocaleDateString(undefined, { weekday: "short", year: "numeric", month: "short", day: "numeric" }) + ' at ' + mi.Envelope.Date.toLocaleTimeString(); const datetime = mi.Envelope.Date.toLocaleDateString(undefined, { weekday: "short", year: "numeric", month: "short", day: "numeric" }) + ' at ' + mi.Envelope.Date.toLocaleTimeString();
onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n'; onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n';
} }
body = '\n\n' + onWroteLine + body; body = '\n\n' + sig + '\n' + onWroteLine + body;
} }
} }
const subjectPrefix = forward ? 'Fwd:' : 'Re:'; const subjectPrefix = forward ? 'Fwd:' : 'Re:';
@ -3108,6 +3172,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
attachmentsMessageItem: forward ? mi : undefined, attachmentsMessageItem: forward ? mi : undefined,
responseMessageID: m.ID, responseMessageID: m.ID,
isList: m.IsMailingList, isList: m.IsMailingList,
editOffset: editOffset,
}; };
compose(opts); compose(opts);
}; };
@ -5853,7 +5918,14 @@ const init = async () => {
ensureSearchView(); ensureSearchView();
searchView.updateForm(); searchView.updateForm();
}; };
const cmdCompose = async () => { compose({}); }; const cmdCompose = async () => {
let body = '';
let sig = accountSettings?.Signature || '';
if (sig) {
body += '\n\n' + sig;
}
compose({ body: body, editOffset: 0 });
};
const cmdOpenInbox = async () => { const cmdOpenInbox = async () => {
const mb = mailboxlistView.findMailboxByName('Inbox'); const mb = mailboxlistView.findMailboxByName('Inbox');
if (mb) { if (mb) {
@ -5876,6 +5948,7 @@ const init = async () => {
'ctrl ?': cmdTooltip, 'ctrl ?': cmdTooltip,
c: cmdCompose, c: cmdCompose,
'ctrl m': cmdFocusMsg, 'ctrl m': cmdFocusMsg,
'ctrl !': cmdSettings,
}; };
const webmailroot = dom.div(style({ display: 'flex', flexDirection: 'column', alignContent: 'stretch', height: '100dvh' }), dom.div(dom._class('topbar'), style({ display: 'flex' }), attr.role('region'), attr.arialabel('Top bar'), topcomposeboxElem = dom.div(dom._class('pad'), style({ width: settings.mailboxesWidth + 'px', textAlign: 'center' }), dom.clickbutton('Compose', attr.title('Compose new email message.'), function click() { const webmailroot = dom.div(style({ display: 'flex', flexDirection: 'column', alignContent: 'stretch', height: '100dvh' }), dom.div(dom._class('topbar'), style({ display: 'flex' }), attr.role('region'), attr.arialabel('Top bar'), topcomposeboxElem = dom.div(dom._class('pad'), style({ width: settings.mailboxesWidth + 'px', textAlign: 'center' }), dom.clickbutton('Compose', attr.title('Compose new email message.'), function click() {
shortcutCmd(cmdCompose, shortcuts); shortcutCmd(cmdCompose, shortcuts);
@ -5932,7 +6005,7 @@ const init = async () => {
else { else {
selectLayout(layoutElem.value); selectLayout(layoutElem.value);
} }
}), ' ', dom.clickbutton('Tooltip', attr.title('Show tooltips, based on the title attributes (underdotted text) for the focused element and all user interface elements below it. Use the keyboard shortcut "ctrl ?" instead of clicking on the tooltip button, which changes focus to the tooltip button.'), clickCmd(cmdTooltip, shortcuts)), ' ', dom.clickbutton('Help', attr.title('Show popup with basic usage information and a keyboard shortcuts.'), clickCmd(cmdHelp, shortcuts)), ' ', loginAddressElem = dom.span(), ' ', dom.clickbutton('Logout', attr.title('Logout, invalidating this session.'), async function click(e) { }), ' ', dom.clickbutton('Tooltip', attr.title('Show tooltips, based on the title attributes (underdotted text) for the focused element and all user interface elements below it. Use the keyboard shortcut "ctrl ?" instead of clicking on the tooltip button, which changes focus to the tooltip button.'), clickCmd(cmdTooltip, shortcuts)), ' ', dom.clickbutton('Help', attr.title('Show popup with basic usage information and a keyboard shortcuts.'), clickCmd(cmdHelp, shortcuts)), ' ', dom.clickbutton('Settings', attr.title('Change settings for composing messages.'), clickCmd(cmdSettings, shortcuts)), ' ', loginAddressElem = dom.span(), ' ', dom.clickbutton('Logout', attr.title('Logout, invalidating this session.'), async function click(e) {
await withStatus('Logging out', client.Logout(), e.target); await withStatus('Logging out', client.Logout(), e.target);
localStorageRemove('webmailcsrftoken'); localStorageRemove('webmailcsrftoken');
if (eventSource) { if (eventSource) {
@ -6329,6 +6402,7 @@ const init = async () => {
lastServerVersion = data.Version; lastServerVersion = data.Version;
const start = checkParse(() => api.parser.EventStart(data)); const start = checkParse(() => api.parser.EventStart(data));
log('event start', start); log('event start', start);
accountSettings = start.Settings;
connecting = false; connecting = false;
sseID = start.SSEID; sseID = start.SSEID;
loginAddress = start.LoginAddress; loginAddress = start.LoginAddress;

View file

@ -124,6 +124,8 @@ try {
} }
} catch (err) {} } catch (err) {}
let accountSettings: api.Settings
const defaultSettings = { const defaultSettings = {
showShortcuts: true, // Whether to briefly show shortcuts in bottom left when a button is clicked that has a keyboard shortcut. showShortcuts: true, // Whether to briefly show shortcuts in bottom left when a button is clicked that has a keyboard shortcut.
mailboxesWidth: 240, mailboxesWidth: 240,
@ -916,6 +918,19 @@ const withStatus = async <T>(action: string, promise: Promise<T>, disablable?: D
} }
} }
const withDisabled = async <T>(elem: {disabled: boolean}, p: Promise<T>): Promise<T> => {
try {
elem.disabled = true
return await p
} catch (err) {
console.log({err})
window.alert('Error: ' + errmsg(err))
throw err
} finally {
elem.disabled = false
}
}
// Popover shows kids in a div on top of a mostly transparent overlay on top of // Popover shows kids in a div on top of a mostly transparent overlay on top of
// the document. If transparent is set, the div the kids are in will not get a // the document. If transparent is set, the div the kids are in will not get a
// white background. If focus is set, it will be called after adding the // white background. If focus is set, it will be called after adding the
@ -1045,6 +1060,69 @@ const popup = (...kids: ElemArg[]) => {
return close return close
} }
// Show settings screen.
const cmdSettings = async () => {
let fieldset: HTMLFieldSetElement
let signature: HTMLTextAreaElement
let quoting: HTMLSelectElement
let showAddressSecurity: HTMLInputElement
if (!accountSettings) {
window.alert('No account settings fetched yet.')
}
const remove = popup(
style({padding: '1em 1em 2em 1em', minWidth: '30em'}),
dom.h1('Settings'),
dom.form(
async function submit(e: SubmitEvent) {
e.preventDefault()
e.stopPropagation()
const accSet: api.Settings = {
ID: accountSettings.ID,
Signature: signature.value,
Quoting: quoting.value as api.Quoting,
ShowAddressSecurity: showAddressSecurity.checked,
}
await withDisabled(fieldset, client.SettingsSave(accSet))
accountSettings = accSet
remove()
},
fieldset=dom.fieldset(
dom.label(
style({margin: '1ex 0', display: 'block'}),
dom.div('Signature'),
signature=dom.textarea(
new String(accountSettings.Signature),
style({width: '100%'}),
attr.rows(''+Math.max(3, 1+accountSettings.Signature.split('\n').length)),
),
),
dom.label(
style({margin: '1ex 0', display: 'block'}),
dom.div('Reply above/below original'),
attr.title('Auto: If text is selected, only the replied text is quoted and editing starts below. Otherwise, the full message is quoted and editing starts at the top.'),
quoting=dom.select(
dom.option(attr.value(''), 'Auto'),
dom.option(attr.value('bottom'), 'Bottom', accountSettings.Quoting === api.Quoting.Bottom ? attr.selected('') : []),
dom.option(attr.value('top'), 'Top', accountSettings.Quoting === api.Quoting.Top ? attr.selected('') : []),
),
),
dom.label(
style({margin: '1ex 0', display: 'block'}),
showAddressSecurity=dom.input(attr.type('checkbox'), accountSettings.ShowAddressSecurity ? attr.checked('') : []),
' Show address security indications',
attr.title('Show bars underneath address input fields, indicating support for STARTTLS/DNSSEC/DANE/MTA-STS/RequireTLS.'),
),
dom.br(),
dom.div(
dom.submitbutton('Save'),
),
),
),
)
}
// Show help popup, with shortcuts and basic explanation. // Show help popup, with shortcuts and basic explanation.
const cmdHelp = async () => { const cmdHelp = async () => {
const remove = popup( const remove = popup(
@ -1271,6 +1349,7 @@ type ComposeOptions = {
responseMessageID?: number responseMessageID?: number
// Whether message is to a list, due to List-Id header. // Whether message is to a list, due to List-Id header.
isList?: boolean isList?: boolean
editOffset?: number // For cursor, default at start.
} }
interface ComposeView { interface ComposeView {
@ -1410,6 +1489,9 @@ const compose = (opts: ComposeOptions) => {
let autosizeElem: HTMLElement, inputElem: HTMLInputElement, securityBar: HTMLElement let autosizeElem: HTMLElement, inputElem: HTMLInputElement, securityBar: HTMLElement
const fetchRecipientSecurity = () => { const fetchRecipientSecurity = () => {
if (!accountSettings?.ShowAddressSecurity) {
return
}
if (inputElem.value === rcptSecAddr) { if (inputElem.value === rcptSecAddr) {
return return
} }
@ -1505,7 +1587,7 @@ const compose = (opts: ComposeOptions) => {
style({width: 'auto'}), style({width: 'auto'}),
attr.value(addr), attr.value(addr),
newAddressComplete(), newAddressComplete(),
attr.title(recipientSecurityTitle), accountSettings?.ShowAddressSecurity ? attr.title(recipientSecurityTitle) : [],
function keydown(e: KeyboardEvent) { function keydown(e: KeyboardEvent) {
if (e.key === '-' && e.ctrlKey) { if (e.key === '-' && e.ctrlKey) {
remove() remove()
@ -1721,7 +1803,7 @@ const compose = (opts: ComposeOptions) => {
// Explicit string object so it doesn't get the highlight-unicode-block-changes // Explicit string object so it doesn't get the highlight-unicode-block-changes
// treatment, which would cause characters to disappear. // treatment, which would cause characters to disappear.
new String(opts.body || ''), new String(opts.body || ''),
opts.body && !opts.isForward && !opts.body.startsWith('\n\n') ? prop({selectionStart: opts.body.length, selectionEnd: opts.body.length}) : [], prop({selectionStart: opts.editOffset || 0, selectionEnd: opts.editOffset || 0}),
function keyup(e: KeyboardEvent) { function keyup(e: KeyboardEvent) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
checkAttachments() checkAttachments()
@ -2464,12 +2546,16 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
body = pm.Texts[0] body = pm.Texts[0]
} }
body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim() body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim()
let editOffset = 0
if (forward) { if (forward) {
body = '\n\n---- Forwarded Message ----\n\n'+body body = '\n\n---- Forwarded Message ----\n\n'+body
} else { } else {
body = body.split('\n').map(line => '> ' + line).join('\n') body = body.split('\n').map(line => '> ' + line).join('\n')
if (haveSel) { let sig = accountSettings?.Signature || ''
if (!accountSettings?.Quoting && haveSel || accountSettings?.Quoting === api.Quoting.Bottom) {
body += '\n\n' body += '\n\n'
editOffset = body.length
body += '\n\n' + sig
} else { } else {
let onWroteLine = '' let onWroteLine = ''
if (mi.Envelope.Date && mi.Envelope.From && mi.Envelope.From.length === 1) { if (mi.Envelope.Date && mi.Envelope.From && mi.Envelope.From.length === 1) {
@ -2478,7 +2564,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
const datetime = mi.Envelope.Date.toLocaleDateString(undefined, {weekday: "short", year: "numeric", month: "short", day: "numeric"}) + ' at ' + mi.Envelope.Date.toLocaleTimeString() const datetime = mi.Envelope.Date.toLocaleDateString(undefined, {weekday: "short", year: "numeric", month: "short", day: "numeric"}) + ' at ' + mi.Envelope.Date.toLocaleTimeString()
onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n' onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n'
} }
body = '\n\n' + onWroteLine + body body = '\n\n' + sig + '\n' + onWroteLine + body
} }
} }
const subjectPrefix = forward ? 'Fwd:' : 'Re:' const subjectPrefix = forward ? 'Fwd:' : 'Re:'
@ -2495,6 +2581,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
attachmentsMessageItem: forward ? mi : undefined, attachmentsMessageItem: forward ? mi : undefined,
responseMessageID: m.ID, responseMessageID: m.ID,
isList: m.IsMailingList, isList: m.IsMailingList,
editOffset: editOffset,
} }
compose(opts) compose(opts)
} }
@ -6029,7 +6116,14 @@ const init = async () => {
searchView.updateForm() searchView.updateForm()
} }
const cmdCompose = async () => { compose({}) } const cmdCompose = async () => {
let body = ''
let sig = accountSettings?.Signature || ''
if (sig) {
body += '\n\n' + sig
}
compose({body: body, editOffset: 0})
}
const cmdOpenInbox = async () => { const cmdOpenInbox = async () => {
const mb = mailboxlistView.findMailboxByName('Inbox') const mb = mailboxlistView.findMailboxByName('Inbox')
if (mb) { if (mb) {
@ -6053,6 +6147,7 @@ const init = async () => {
'ctrl ?': cmdTooltip, 'ctrl ?': cmdTooltip,
c: cmdCompose, c: cmdCompose,
'ctrl m': cmdFocusMsg, 'ctrl m': cmdFocusMsg,
'ctrl !': cmdSettings,
} }
const webmailroot = dom.div( const webmailroot = dom.div(
@ -6160,6 +6255,8 @@ const init = async () => {
' ', ' ',
dom.clickbutton('Help', attr.title('Show popup with basic usage information and a keyboard shortcuts.'), clickCmd(cmdHelp, shortcuts)), dom.clickbutton('Help', attr.title('Show popup with basic usage information and a keyboard shortcuts.'), clickCmd(cmdHelp, shortcuts)),
' ', ' ',
dom.clickbutton('Settings', attr.title('Change settings for composing messages.'), clickCmd(cmdSettings, shortcuts)),
' ',
loginAddressElem=dom.span(), loginAddressElem=dom.span(),
' ', ' ',
dom.clickbutton('Logout', attr.title('Logout, invalidating this session.'), async function click(e: MouseEvent) { dom.clickbutton('Logout', attr.title('Logout, invalidating this session.'), async function click(e: MouseEvent) {
@ -6650,6 +6747,7 @@ const init = async () => {
const start = checkParse(() => api.parser.EventStart(data)) const start = checkParse(() => api.parser.EventStart(data))
log('event start', start) log('event start', start)
accountSettings = start.Settings
connecting = false connecting = false
sseID = start.SSEID sseID = start.SSEID
loginAddress = start.LoginAddress loginAddress = start.LoginAddress