mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
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:
parent
3bbd7c7d9b
commit
70adf353ee
9 changed files with 409 additions and 27 deletions
|
@ -719,8 +719,44 @@ type LoginSession struct {
|
|||
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.
|
||||
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.
|
||||
type Account struct {
|
||||
|
@ -850,9 +886,15 @@ func OpenAccountDB(log mlog.Log, accountDir, accountName string) (a *Account, re
|
|||
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
|
||||
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 {
|
||||
if !mentioned {
|
||||
mentioned = true
|
||||
|
@ -886,7 +928,7 @@ func OpenAccountDB(log mlog.Log, accountDir, accountName string) (a *Account, re
|
|||
return tx.Insert(&du)
|
||||
})
|
||||
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.
|
||||
|
@ -974,6 +1016,9 @@ func initAccount(db *bstore.DB) error {
|
|||
if err := tx.Insert(&DiskUsage{ID: 1}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Insert(&Settings{ID: 1}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(mox.Conf.Static.DefaultMailboxes) > 0 {
|
||||
// Deprecated in favor of InitialMailboxes.
|
||||
|
|
|
@ -1536,6 +1536,24 @@ func (Webmail) DecodeMIMEWords(ctx context.Context, text string) string {
|
|||
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 {
|
||||
r := make([]any, len(l))
|
||||
for i, v := range l {
|
||||
|
|
|
@ -366,6 +366,19 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SettingsSave",
|
||||
"Docs": "SettingsSave saves settings, e.g. for composing.",
|
||||
"Params": [
|
||||
{
|
||||
"Name": "settings",
|
||||
"Typewords": [
|
||||
"Settings"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Returns": []
|
||||
},
|
||||
{
|
||||
"Name": "SSETypes",
|
||||
"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",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Settings",
|
||||
"Docs": "",
|
||||
"Typewords": [
|
||||
"Settings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Version",
|
||||
"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",
|
||||
"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.",
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
||||
// 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
|
||||
// basic data to populate its UI. After this event, messages will follow quickly in
|
||||
// an EventViewMsgs event.
|
||||
|
@ -201,6 +209,7 @@ export interface EventStart {
|
|||
MailboxName: string
|
||||
Mailboxes?: Mailbox[] | null
|
||||
RejectsMailbox: string
|
||||
Settings: Settings
|
||||
Version: string
|
||||
}
|
||||
|
||||
|
@ -513,14 +522,21 @@ export enum SecurityResult {
|
|||
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 "@".
|
||||
// For quoted strings, values do not hold the double quote or escaping backslashes.
|
||||
// An empty string can be a valid localpart.
|
||||
// Localparts are in Unicode NFC.
|
||||
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 stringsTypes: {[typename: string]: boolean} = {"AttachmentType":true,"CSRFToken":true,"Localpart":true,"SecurityResult":true,"ThreadMode":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,"Quoting":true,"SecurityResult":true,"ThreadMode":true}
|
||||
export const intsTypes: {[typename: string]: boolean} = {"ModSeq":true,"UID":true,"Validation":true}
|
||||
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"]}]},
|
||||
|
@ -539,7 +555,8 @@ export const types: TypenameMap = {
|
|||
"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"]}]},
|
||||
"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"]}]},
|
||||
"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"]}]},
|
||||
|
@ -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":""}]},
|
||||
"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":""}]},
|
||||
"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},
|
||||
}
|
||||
|
||||
|
@ -588,6 +606,7 @@ export const parser = {
|
|||
ForwardAttachments: (v: any) => parse("ForwardAttachments", v) as ForwardAttachments,
|
||||
Mailbox: (v: any) => parse("Mailbox", v) as Mailbox,
|
||||
RecipientSecurity: (v: any) => parse("RecipientSecurity", v) as RecipientSecurity,
|
||||
Settings: (v: any) => parse("Settings", v) as Settings,
|
||||
EventStart: (v: any) => parse("EventStart", v) as EventStart,
|
||||
DomainAddressConfig: (v: any) => parse("DomainAddressConfig", v) as DomainAddressConfig,
|
||||
EventViewErr: (v: any) => parse("EventViewErr", v) as EventViewErr,
|
||||
|
@ -617,6 +636,7 @@ export const parser = {
|
|||
ThreadMode: (v: any) => parse("ThreadMode", v) as ThreadMode,
|
||||
AttachmentType: (v: any) => parse("AttachmentType", v) as AttachmentType,
|
||||
SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult,
|
||||
Quoting: (v: any) => parse("Quoting", v) as Quoting,
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
async SSETypes(): Promise<[EventStart, EventViewErr, EventViewReset, EventViewMsgs, EventViewChanges, ChangeMsgAdd, ChangeMsgRemove, ChangeMsgFlags, ChangeMsgThread, ChangeMailboxRemove, ChangeMailboxAdd, ChangeMailboxRename, ChangeMailboxCounts, ChangeMailboxSpecialUse, ChangeMailboxKeywords, Flags]> {
|
||||
const fn: string = "SSETypes"
|
||||
|
|
|
@ -274,8 +274,15 @@ var api;
|
|||
// lookups.
|
||||
SecurityResult["SecurityResultUnknown"] = "unknown";
|
||||
})(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 };
|
||||
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true };
|
||||
// Quoting is a setting for how to quote in replies/forwards.
|
||||
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.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"] }] },
|
||||
|
@ -294,7 +301,8 @@ var api;
|
|||
"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"] }] },
|
||||
"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"] }] },
|
||||
"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"] }] },
|
||||
|
@ -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": "" }] },
|
||||
"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": "" }] },
|
||||
"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 },
|
||||
};
|
||||
api.parser = {
|
||||
|
@ -342,6 +351,7 @@ var api;
|
|||
ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
|
||||
Mailbox: (v) => api.parse("Mailbox", v),
|
||||
RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
|
||||
Settings: (v) => api.parse("Settings", v),
|
||||
EventStart: (v) => api.parse("EventStart", v),
|
||||
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
|
||||
EventViewErr: (v) => api.parse("EventViewErr", v),
|
||||
|
@ -371,6 +381,7 @@ var api;
|
|||
ThreadMode: (v) => api.parse("ThreadMode", v),
|
||||
AttachmentType: (v) => api.parse("AttachmentType", v),
|
||||
SecurityResult: (v) => api.parse("SecurityResult", v),
|
||||
Quoting: (v) => api.parse("Quoting", v),
|
||||
Localpart: (v) => api.parse("Localpart", v),
|
||||
};
|
||||
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
|
||||
|
@ -588,6 +599,14 @@ var api;
|
|||
const params = [text];
|
||||
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.
|
||||
async SSETypes() {
|
||||
const fn = "SSETypes";
|
||||
|
|
|
@ -274,8 +274,15 @@ var api;
|
|||
// lookups.
|
||||
SecurityResult["SecurityResultUnknown"] = "unknown";
|
||||
})(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 };
|
||||
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true };
|
||||
// Quoting is a setting for how to quote in replies/forwards.
|
||||
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.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"] }] },
|
||||
|
@ -294,7 +301,8 @@ var api;
|
|||
"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"] }] },
|
||||
"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"] }] },
|
||||
"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"] }] },
|
||||
|
@ -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": "" }] },
|
||||
"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": "" }] },
|
||||
"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 },
|
||||
};
|
||||
api.parser = {
|
||||
|
@ -342,6 +351,7 @@ var api;
|
|||
ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
|
||||
Mailbox: (v) => api.parse("Mailbox", v),
|
||||
RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
|
||||
Settings: (v) => api.parse("Settings", v),
|
||||
EventStart: (v) => api.parse("EventStart", v),
|
||||
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
|
||||
EventViewErr: (v) => api.parse("EventViewErr", v),
|
||||
|
@ -371,6 +381,7 @@ var api;
|
|||
ThreadMode: (v) => api.parse("ThreadMode", v),
|
||||
AttachmentType: (v) => api.parse("AttachmentType", v),
|
||||
SecurityResult: (v) => api.parse("SecurityResult", v),
|
||||
Quoting: (v) => api.parse("Quoting", v),
|
||||
Localpart: (v) => api.parse("Localpart", v),
|
||||
};
|
||||
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
|
||||
|
@ -588,6 +599,14 @@ var api;
|
|||
const params = [text];
|
||||
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.
|
||||
async SSETypes() {
|
||||
const fn = "SSETypes";
|
||||
|
|
|
@ -215,6 +215,7 @@ type EventStart struct {
|
|||
MailboxName string
|
||||
Mailboxes []store.Mailbox
|
||||
RejectsMailbox string
|
||||
Settings store.Settings
|
||||
Version string
|
||||
}
|
||||
|
||||
|
@ -664,6 +665,7 @@ func serveEvents(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *ht
|
|||
}()
|
||||
|
||||
var mbl []store.Mailbox
|
||||
settings := store.Settings{ID: 1}
|
||||
|
||||
// We only take the rlock when getting the tx.
|
||||
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()
|
||||
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.
|
||||
|
@ -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.
|
||||
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)
|
||||
|
||||
// The goroutine doing the querying will send messages on these channels, which
|
||||
|
|
|
@ -274,8 +274,15 @@ var api;
|
|||
// lookups.
|
||||
SecurityResult["SecurityResultUnknown"] = "unknown";
|
||||
})(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 };
|
||||
api.stringsTypes = { "AttachmentType": true, "CSRFToken": true, "Localpart": true, "SecurityResult": true, "ThreadMode": true };
|
||||
// Quoting is a setting for how to quote in replies/forwards.
|
||||
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.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"] }] },
|
||||
|
@ -294,7 +301,8 @@ var api;
|
|||
"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"] }] },
|
||||
"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"] }] },
|
||||
"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"] }] },
|
||||
|
@ -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": "" }] },
|
||||
"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": "" }] },
|
||||
"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 },
|
||||
};
|
||||
api.parser = {
|
||||
|
@ -342,6 +351,7 @@ var api;
|
|||
ForwardAttachments: (v) => api.parse("ForwardAttachments", v),
|
||||
Mailbox: (v) => api.parse("Mailbox", v),
|
||||
RecipientSecurity: (v) => api.parse("RecipientSecurity", v),
|
||||
Settings: (v) => api.parse("Settings", v),
|
||||
EventStart: (v) => api.parse("EventStart", v),
|
||||
DomainAddressConfig: (v) => api.parse("DomainAddressConfig", v),
|
||||
EventViewErr: (v) => api.parse("EventViewErr", v),
|
||||
|
@ -371,6 +381,7 @@ var api;
|
|||
ThreadMode: (v) => api.parse("ThreadMode", v),
|
||||
AttachmentType: (v) => api.parse("AttachmentType", v),
|
||||
SecurityResult: (v) => api.parse("SecurityResult", v),
|
||||
Quoting: (v) => api.parse("Quoting", v),
|
||||
Localpart: (v) => api.parse("Localpart", v),
|
||||
};
|
||||
let defaultOptions = { slicesNullable: true, mapsNullable: true, nullableOptional: true };
|
||||
|
@ -588,6 +599,14 @@ var api;
|
|||
const params = [text];
|
||||
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.
|
||||
async SSETypes() {
|
||||
const fn = "SSETypes";
|
||||
|
@ -1257,6 +1276,7 @@ try {
|
|||
}
|
||||
}
|
||||
catch (err) { }
|
||||
let accountSettings;
|
||||
const defaultSettings = {
|
||||
showShortcuts: true,
|
||||
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
|
||||
// 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
|
||||
|
@ -2082,6 +2116,29 @@ const popup = (...kids) => {
|
|||
content.focus();
|
||||
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.
|
||||
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' })))), [
|
||||
|
@ -2328,6 +2385,9 @@ const compose = (opts) => {
|
|||
let rcptSecAborter = {};
|
||||
let autosizeElem, inputElem, securityBar;
|
||||
const fetchRecipientSecurity = () => {
|
||||
if (!accountSettings?.ShowAddressSecurity) {
|
||||
return;
|
||||
}
|
||||
if (inputElem.value === rcptSecAddr) {
|
||||
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 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) {
|
||||
remove();
|
||||
}
|
||||
|
@ -2547,7 +2607,7 @@ const compose = (opts) => {
|
|||
}))))), 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
|
||||
// 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') {
|
||||
checkAttachments();
|
||||
}
|
||||
|
@ -3075,13 +3135,17 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
body = pm.Texts[0];
|
||||
}
|
||||
body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim();
|
||||
let editOffset = 0;
|
||||
if (forward) {
|
||||
body = '\n\n---- Forwarded Message ----\n\n' + body;
|
||||
}
|
||||
else {
|
||||
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';
|
||||
editOffset = body.length;
|
||||
body += '\n\n' + sig;
|
||||
}
|
||||
else {
|
||||
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();
|
||||
onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n';
|
||||
}
|
||||
body = '\n\n' + onWroteLine + body;
|
||||
body = '\n\n' + sig + '\n' + onWroteLine + body;
|
||||
}
|
||||
}
|
||||
const subjectPrefix = forward ? 'Fwd:' : 'Re:';
|
||||
|
@ -3108,6 +3172,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad
|
|||
attachmentsMessageItem: forward ? mi : undefined,
|
||||
responseMessageID: m.ID,
|
||||
isList: m.IsMailingList,
|
||||
editOffset: editOffset,
|
||||
};
|
||||
compose(opts);
|
||||
};
|
||||
|
@ -5853,7 +5918,14 @@ const init = async () => {
|
|||
ensureSearchView();
|
||||
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 mb = mailboxlistView.findMailboxByName('Inbox');
|
||||
if (mb) {
|
||||
|
@ -5876,6 +5948,7 @@ const init = async () => {
|
|||
'ctrl ?': cmdTooltip,
|
||||
c: cmdCompose,
|
||||
'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() {
|
||||
shortcutCmd(cmdCompose, shortcuts);
|
||||
|
@ -5932,7 +6005,7 @@ const init = async () => {
|
|||
else {
|
||||
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);
|
||||
localStorageRemove('webmailcsrftoken');
|
||||
if (eventSource) {
|
||||
|
@ -6329,6 +6402,7 @@ const init = async () => {
|
|||
lastServerVersion = data.Version;
|
||||
const start = checkParse(() => api.parser.EventStart(data));
|
||||
log('event start', start);
|
||||
accountSettings = start.Settings;
|
||||
connecting = false;
|
||||
sseID = start.SSEID;
|
||||
loginAddress = start.LoginAddress;
|
||||
|
|
|
@ -124,6 +124,8 @@ try {
|
|||
}
|
||||
} catch (err) {}
|
||||
|
||||
let accountSettings: api.Settings
|
||||
|
||||
const defaultSettings = {
|
||||
showShortcuts: true, // Whether to briefly show shortcuts in bottom left when a button is clicked that has a keyboard shortcut.
|
||||
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
|
||||
// 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
|
||||
|
@ -1045,6 +1060,69 @@ const popup = (...kids: ElemArg[]) => {
|
|||
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.
|
||||
const cmdHelp = async () => {
|
||||
const remove = popup(
|
||||
|
@ -1271,6 +1349,7 @@ type ComposeOptions = {
|
|||
responseMessageID?: number
|
||||
// Whether message is to a list, due to List-Id header.
|
||||
isList?: boolean
|
||||
editOffset?: number // For cursor, default at start.
|
||||
}
|
||||
|
||||
interface ComposeView {
|
||||
|
@ -1410,6 +1489,9 @@ const compose = (opts: ComposeOptions) => {
|
|||
let autosizeElem: HTMLElement, inputElem: HTMLInputElement, securityBar: HTMLElement
|
||||
|
||||
const fetchRecipientSecurity = () => {
|
||||
if (!accountSettings?.ShowAddressSecurity) {
|
||||
return
|
||||
}
|
||||
if (inputElem.value === rcptSecAddr) {
|
||||
return
|
||||
}
|
||||
|
@ -1505,7 +1587,7 @@ const compose = (opts: ComposeOptions) => {
|
|||
style({width: 'auto'}),
|
||||
attr.value(addr),
|
||||
newAddressComplete(),
|
||||
attr.title(recipientSecurityTitle),
|
||||
accountSettings?.ShowAddressSecurity ? attr.title(recipientSecurityTitle) : [],
|
||||
function keydown(e: KeyboardEvent) {
|
||||
if (e.key === '-' && e.ctrlKey) {
|
||||
remove()
|
||||
|
@ -1721,7 +1803,7 @@ const compose = (opts: ComposeOptions) => {
|
|||
// Explicit string object so it doesn't get the highlight-unicode-block-changes
|
||||
// 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}) : [],
|
||||
prop({selectionStart: opts.editOffset || 0, selectionEnd: opts.editOffset || 0}),
|
||||
function keyup(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
checkAttachments()
|
||||
|
@ -2464,12 +2546,16 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
body = pm.Texts[0]
|
||||
}
|
||||
body = body.replace(/\r/g, '').replace(/\n\n\n\n*/g, '\n\n').trim()
|
||||
let editOffset = 0
|
||||
if (forward) {
|
||||
body = '\n\n---- Forwarded Message ----\n\n'+body
|
||||
} else {
|
||||
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'
|
||||
editOffset = body.length
|
||||
body += '\n\n' + sig
|
||||
} else {
|
||||
let onWroteLine = ''
|
||||
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()
|
||||
onWroteLine = 'On ' + datetime + ', ' + name + ' wrote:\n'
|
||||
}
|
||||
body = '\n\n' + onWroteLine + body
|
||||
body = '\n\n' + sig + '\n' + onWroteLine + body
|
||||
}
|
||||
}
|
||||
const subjectPrefix = forward ? 'Fwd:' : 'Re:'
|
||||
|
@ -2495,6 +2581,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l
|
|||
attachmentsMessageItem: forward ? mi : undefined,
|
||||
responseMessageID: m.ID,
|
||||
isList: m.IsMailingList,
|
||||
editOffset: editOffset,
|
||||
}
|
||||
compose(opts)
|
||||
}
|
||||
|
@ -6029,7 +6116,14 @@ const init = async () => {
|
|||
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 mb = mailboxlistView.findMailboxByName('Inbox')
|
||||
if (mb) {
|
||||
|
@ -6053,6 +6147,7 @@ const init = async () => {
|
|||
'ctrl ?': cmdTooltip,
|
||||
c: cmdCompose,
|
||||
'ctrl m': cmdFocusMsg,
|
||||
'ctrl !': cmdSettings,
|
||||
}
|
||||
|
||||
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('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: MouseEvent) {
|
||||
|
@ -6650,6 +6747,7 @@ const init = async () => {
|
|||
const start = checkParse(() => api.parser.EventStart(data))
|
||||
log('event start', start)
|
||||
|
||||
accountSettings = start.Settings
|
||||
connecting = false
|
||||
sseID = start.SSEID
|
||||
loginAddress = start.LoginAddress
|
||||
|
|
Loading…
Reference in a new issue