From 0f735a17100b81cc2f83ced4de4b1391a10a03ad Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sat, 20 Apr 2024 21:25:52 +0200 Subject: [PATCH] webmail: remember per from-address whether we should show the text/html/html-with-external-resources version of a message --- store/account.go | 18 +++++++++++++ webmail/api.go | 49 +++++++++++++++++++++++++++++++++ webmail/api.json | 66 +++++++++++++++++++++++++++++++++++++++++++++ webmail/api.ts | 35 +++++++++++++++++++++--- webmail/api_test.go | 7 ++++- webmail/message.go | 2 +- webmail/msg.js | 27 ++++++++++++++++--- webmail/text.js | 27 ++++++++++++++++--- webmail/view.go | 13 ++++++--- webmail/webmail.js | 45 ++++++++++++++++++++++++++----- webmail/webmail.ts | 18 ++++++++++--- 11 files changed, 284 insertions(+), 23 deletions(-) diff --git a/store/account.go b/store/account.go index bc3dd04..4de7e71 100644 --- a/store/account.go +++ b/store/account.go @@ -740,6 +740,23 @@ type Settings struct { ShowAddressSecurity bool } +// ViewMode how a message should be viewed: its text parts, html parts, or html +// with loading external resources. +type ViewMode string + +const ( + ModeDefault ViewMode = "" + ModeText ViewMode = "text" + ModeHTML ViewMode = "html" + ModeHTMLExt ViewMode = "htmlext" // HTML with external resources. +) + +// FromAddressSettings are webmail client settings per "From" address. +type FromAddressSettings struct { + FromAddress string // Unicode. + ViewMode ViewMode +} + // Types stored in DB. var DBTypes = []any{ NextUIDValidity{}, @@ -756,6 +773,7 @@ var DBTypes = []any{ DiskUsage{}, LoginSession{}, Settings{}, + FromAddressSettings{}, } // Account holds the information about a user, includings mailboxes, messages, imap subscriptions. diff --git a/webmail/api.go b/webmail/api.go index 5efd0c5..62f50ca 100644 --- a/webmail/api.go +++ b/webmail/api.go @@ -174,9 +174,58 @@ func (Webmail) ParsedMessage(ctx context.Context, msgID int64) (pm ParsedMessage defer state.clear() pm, err = parsedMessage(log, m, &state, true, false) xcheckf(ctx, err, "parsing message") + + if len(pm.envelope.From) == 1 { + xdbread(ctx, acc, func(tx *bstore.Tx) { + pm.ViewMode, err = fromAddrViewMode(tx, pm.envelope.From[0]) + xcheckf(ctx, err, "looking up view mode for from address") + }) + } + return } +// fromAddrViewMode returns the view mode for a from address. +func fromAddrViewMode(tx *bstore.Tx, from MessageAddress) (store.ViewMode, error) { + lp, err := smtp.ParseLocalpart(from.User) + if err != nil { + return store.ModeDefault, nil + } + fromAddr := smtp.Address{Localpart: lp, Domain: from.Domain}.Pack(true) + fas := store.FromAddressSettings{FromAddress: fromAddr} + err = tx.Get(&fas) + if err == bstore.ErrAbsent { + return store.ModeDefault, nil + } + return fas.ViewMode, err +} + +// FromAddressSettingsSave saves per-"From"-address settings. +func (Webmail) FromAddressSettingsSave(ctx context.Context, fas store.FromAddressSettings) { + log := pkglog.WithContext(ctx) + reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo) + acc, err := store.OpenAccount(log, reqInfo.AccountName) + xcheckf(ctx, err, "open account") + defer func() { + err := acc.Close() + log.Check(err, "closing account") + }() + + if fas.FromAddress == "" { + xcheckuserf(ctx, errors.New("empty from address"), "checking address") + } + + xdbwrite(ctx, acc, func(tx *bstore.Tx) { + if tx.Get(&store.FromAddressSettings{FromAddress: fas.FromAddress}) == nil { + err := tx.Update(&fas) + xcheckf(ctx, err, "updating settings for from address") + } else { + err := tx.Insert(&fas) + xcheckf(ctx, err, "inserting settings for from address") + } + }) +} + // MessageFindMessageID looks up a message by Message-Id header, and returns the ID // of the message in storage. Used when opening a previously saved draft message // for editing again. diff --git a/webmail/api.json b/webmail/api.json index 6304d11..68ec1ba 100644 --- a/webmail/api.json +++ b/webmail/api.json @@ -99,6 +99,19 @@ } ] }, + { + "Name": "FromAddressSettingsSave", + "Docs": "FromAddressSettingsSave saves per-\"From\"-address settings.", + "Params": [ + { + "Name": "fas", + "Typewords": [ + "FromAddressSettings" + ] + } + ], + "Returns": [] + }, { "Name": "MessageFindMessageID", "Docs": "MessageFindMessageID looks up a message by Message-Id header, and returns the ID\nof the message in storage. Used when opening a previously saved draft message\nfor editing again.\nIf no message is find, zero is returned, not an error.", @@ -833,6 +846,13 @@ "string" ] }, + { + "Name": "ViewMode", + "Docs": "", + "Typewords": [ + "ViewMode" + ] + }, { "Name": "Texts", "Docs": "Text parts, can be empty.", @@ -1129,6 +1149,26 @@ } ] }, + { + "Name": "FromAddressSettings", + "Docs": "FromAddressSettings are webmail client settings per \"From\" address.", + "Fields": [ + { + "Name": "FromAddress", + "Docs": "Unicode.", + "Typewords": [ + "string" + ] + }, + { + "Name": "ViewMode", + "Docs": "", + "Typewords": [ + "ViewMode" + ] + } + ] + }, { "Name": "ComposeMessage", "Docs": "ComposeMessage is a message to be composed, for saving draft messages.", @@ -2982,6 +3022,32 @@ } ] }, + { + "Name": "ViewMode", + "Docs": "ViewMode how a message should be viewed: its text parts, html parts, or html\nwith loading external resources.", + "Values": [ + { + "Name": "ModeDefault", + "Value": "", + "Docs": "" + }, + { + "Name": "ModeText", + "Value": "text", + "Docs": "" + }, + { + "Name": "ModeHTML", + "Value": "html", + "Docs": "" + }, + { + "Name": "ModeHTMLExt", + "Value": "htmlext", + "Docs": "HTML with external resources." + } + ] + }, { "Name": "SecurityResult", "Docs": "SecurityResult indicates whether a security feature is supported.", diff --git a/webmail/api.ts b/webmail/api.ts index 38d4349..9d7fb9b 100644 --- a/webmail/api.ts +++ b/webmail/api.ts @@ -64,6 +64,7 @@ export interface ParsedMessage { ID: number Part: Part Headers?: { [key: string]: string[] | null } + ViewMode: ViewMode Texts?: string[] | null // Text parts, can be empty. HasHTML: boolean // Whether there is an HTML part. The webclient renders HTML message parts through an iframe and a separate request with strict CSP headers to prevent script execution and loading of external resources, which isn't possible when loading in iframe with inline HTML because not all browsers support the iframe csp attribute. ListReplyAddress?: MessageAddress | null // From List-Post. @@ -127,6 +128,12 @@ export interface Domain { Unicode: string // Name as U-labels, in Unicode NFC. Empty if this is an ASCII-only domain. No trailing dot. } +// FromAddressSettings are webmail client settings per "From" address. +export interface FromAddressSettings { + FromAddress: string // Unicode. + ViewMode: ViewMode +} + // ComposeMessage is a message to be composed, for saving draft messages. export interface ComposeMessage { From: string @@ -527,6 +534,15 @@ export enum AttachmentType { AttachmentPresentation = "presentation", // odp, pptx, ... } +// ViewMode how a message should be viewed: its text parts, html parts, or html +// with loading external resources. +export enum ViewMode { + ModeDefault = "", + ModeText = "text", + ModeHTML = "html", + ModeHTMLExt = "htmlext", // HTML with external resources. +} + // SecurityResult indicates whether a security feature is supported. export enum SecurityResult { SecurityResultError = "error", @@ -551,8 +567,8 @@ export enum Quoting { // 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,"ComposeMessage":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 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,"ComposeMessage":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,"FromAddressSettings":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,"ViewMode":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"]}]}, @@ -560,12 +576,13 @@ export const types: TypenameMap = { "Filter": {"Name":"Filter","Docs":"","Fields":[{"Name":"MailboxID","Docs":"","Typewords":["int64"]},{"Name":"MailboxChildrenIncluded","Docs":"","Typewords":["bool"]},{"Name":"MailboxName","Docs":"","Typewords":["string"]},{"Name":"Words","Docs":"","Typewords":["[]","string"]},{"Name":"From","Docs":"","Typewords":["[]","string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Oldest","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"Newest","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"Subject","Docs":"","Typewords":["[]","string"]},{"Name":"Attachments","Docs":"","Typewords":["AttachmentType"]},{"Name":"Labels","Docs":"","Typewords":["[]","string"]},{"Name":"Headers","Docs":"","Typewords":["[]","[]","string"]},{"Name":"SizeMin","Docs":"","Typewords":["int64"]},{"Name":"SizeMax","Docs":"","Typewords":["int64"]}]}, "NotFilter": {"Name":"NotFilter","Docs":"","Fields":[{"Name":"Words","Docs":"","Typewords":["[]","string"]},{"Name":"From","Docs":"","Typewords":["[]","string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Subject","Docs":"","Typewords":["[]","string"]},{"Name":"Attachments","Docs":"","Typewords":["AttachmentType"]},{"Name":"Labels","Docs":"","Typewords":["[]","string"]}]}, "Page": {"Name":"Page","Docs":"","Fields":[{"Name":"AnchorMessageID","Docs":"","Typewords":["int64"]},{"Name":"Count","Docs":"","Typewords":["int32"]},{"Name":"DestMessageID","Docs":"","Typewords":["int64"]}]}, - "ParsedMessage": {"Name":"ParsedMessage","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Part","Docs":"","Typewords":["Part"]},{"Name":"Headers","Docs":"","Typewords":["{}","[]","string"]},{"Name":"Texts","Docs":"","Typewords":["[]","string"]},{"Name":"HasHTML","Docs":"","Typewords":["bool"]},{"Name":"ListReplyAddress","Docs":"","Typewords":["nullable","MessageAddress"]}]}, + "ParsedMessage": {"Name":"ParsedMessage","Docs":"","Fields":[{"Name":"ID","Docs":"","Typewords":["int64"]},{"Name":"Part","Docs":"","Typewords":["Part"]},{"Name":"Headers","Docs":"","Typewords":["{}","[]","string"]},{"Name":"ViewMode","Docs":"","Typewords":["ViewMode"]},{"Name":"Texts","Docs":"","Typewords":["[]","string"]},{"Name":"HasHTML","Docs":"","Typewords":["bool"]},{"Name":"ListReplyAddress","Docs":"","Typewords":["nullable","MessageAddress"]}]}, "Part": {"Name":"Part","Docs":"","Fields":[{"Name":"BoundaryOffset","Docs":"","Typewords":["int64"]},{"Name":"HeaderOffset","Docs":"","Typewords":["int64"]},{"Name":"BodyOffset","Docs":"","Typewords":["int64"]},{"Name":"EndOffset","Docs":"","Typewords":["int64"]},{"Name":"RawLineCount","Docs":"","Typewords":["int64"]},{"Name":"DecodedSize","Docs":"","Typewords":["int64"]},{"Name":"MediaType","Docs":"","Typewords":["string"]},{"Name":"MediaSubType","Docs":"","Typewords":["string"]},{"Name":"ContentTypeParams","Docs":"","Typewords":["{}","string"]},{"Name":"ContentID","Docs":"","Typewords":["string"]},{"Name":"ContentDescription","Docs":"","Typewords":["string"]},{"Name":"ContentTransferEncoding","Docs":"","Typewords":["string"]},{"Name":"Envelope","Docs":"","Typewords":["nullable","Envelope"]},{"Name":"Parts","Docs":"","Typewords":["[]","Part"]},{"Name":"Message","Docs":"","Typewords":["nullable","Part"]}]}, "Envelope": {"Name":"Envelope","Docs":"","Fields":[{"Name":"Date","Docs":"","Typewords":["timestamp"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"From","Docs":"","Typewords":["[]","Address"]},{"Name":"Sender","Docs":"","Typewords":["[]","Address"]},{"Name":"ReplyTo","Docs":"","Typewords":["[]","Address"]},{"Name":"To","Docs":"","Typewords":["[]","Address"]},{"Name":"CC","Docs":"","Typewords":["[]","Address"]},{"Name":"BCC","Docs":"","Typewords":["[]","Address"]},{"Name":"InReplyTo","Docs":"","Typewords":["string"]},{"Name":"MessageID","Docs":"","Typewords":["string"]}]}, "Address": {"Name":"Address","Docs":"","Fields":[{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"User","Docs":"","Typewords":["string"]},{"Name":"Host","Docs":"","Typewords":["string"]}]}, "MessageAddress": {"Name":"MessageAddress","Docs":"","Fields":[{"Name":"Name","Docs":"","Typewords":["string"]},{"Name":"User","Docs":"","Typewords":["string"]},{"Name":"Domain","Docs":"","Typewords":["Domain"]}]}, "Domain": {"Name":"Domain","Docs":"","Fields":[{"Name":"ASCII","Docs":"","Typewords":["string"]},{"Name":"Unicode","Docs":"","Typewords":["string"]}]}, + "FromAddressSettings": {"Name":"FromAddressSettings","Docs":"","Fields":[{"Name":"FromAddress","Docs":"","Typewords":["string"]},{"Name":"ViewMode","Docs":"","Typewords":["ViewMode"]}]}, "ComposeMessage": {"Name":"ComposeMessage","Docs":"","Fields":[{"Name":"From","Docs":"","Typewords":["string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Cc","Docs":"","Typewords":["[]","string"]},{"Name":"Bcc","Docs":"","Typewords":["[]","string"]},{"Name":"ReplyTo","Docs":"","Typewords":["string"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"TextBody","Docs":"","Typewords":["string"]},{"Name":"ResponseMessageID","Docs":"","Typewords":["int64"]},{"Name":"DraftMessageID","Docs":"","Typewords":["int64"]}]}, "SubmitMessage": {"Name":"SubmitMessage","Docs":"","Fields":[{"Name":"From","Docs":"","Typewords":["string"]},{"Name":"To","Docs":"","Typewords":["[]","string"]},{"Name":"Cc","Docs":"","Typewords":["[]","string"]},{"Name":"Bcc","Docs":"","Typewords":["[]","string"]},{"Name":"ReplyTo","Docs":"","Typewords":["string"]},{"Name":"Subject","Docs":"","Typewords":["string"]},{"Name":"TextBody","Docs":"","Typewords":["string"]},{"Name":"Attachments","Docs":"","Typewords":["[]","File"]},{"Name":"ForwardAttachments","Docs":"","Typewords":["ForwardAttachments"]},{"Name":"IsForward","Docs":"","Typewords":["bool"]},{"Name":"ResponseMessageID","Docs":"","Typewords":["int64"]},{"Name":"UserAgent","Docs":"","Typewords":["string"]},{"Name":"RequireTLS","Docs":"","Typewords":["nullable","bool"]},{"Name":"FutureRelease","Docs":"","Typewords":["nullable","timestamp"]},{"Name":"ArchiveThread","Docs":"","Typewords":["bool"]},{"Name":"DraftMessageID","Docs":"","Typewords":["int64"]}]}, "File": {"Name":"File","Docs":"","Fields":[{"Name":"Filename","Docs":"","Typewords":["string"]},{"Name":"DataURI","Docs":"","Typewords":["string"]}]}, @@ -601,6 +618,7 @@ export const types: TypenameMap = { "CSRFToken": {"Name":"CSRFToken","Docs":"","Values":null}, "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":""}]}, + "ViewMode": {"Name":"ViewMode","Docs":"","Values":[{"Name":"ModeDefault","Value":"","Docs":""},{"Name":"ModeText","Value":"text","Docs":""},{"Name":"ModeHTML","Value":"html","Docs":""},{"Name":"ModeHTMLExt","Value":"htmlext","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}, @@ -618,6 +636,7 @@ export const parser = { Address: (v: any) => parse("Address", v) as Address, MessageAddress: (v: any) => parse("MessageAddress", v) as MessageAddress, Domain: (v: any) => parse("Domain", v) as Domain, + FromAddressSettings: (v: any) => parse("FromAddressSettings", v) as FromAddressSettings, ComposeMessage: (v: any) => parse("ComposeMessage", v) as ComposeMessage, SubmitMessage: (v: any) => parse("SubmitMessage", v) as SubmitMessage, File: (v: any) => parse("File", v) as File, @@ -653,6 +672,7 @@ export const parser = { CSRFToken: (v: any) => parse("CSRFToken", v) as CSRFToken, ThreadMode: (v: any) => parse("ThreadMode", v) as ThreadMode, AttachmentType: (v: any) => parse("AttachmentType", v) as AttachmentType, + ViewMode: (v: any) => parse("ViewMode", v) as ViewMode, SecurityResult: (v: any) => parse("SecurityResult", v) as SecurityResult, Quoting: (v: any) => parse("Quoting", v) as Quoting, Localpart: (v: any) => parse("Localpart", v) as Localpart, @@ -748,6 +768,15 @@ export class Client { return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as ParsedMessage } + // FromAddressSettingsSave saves per-"From"-address settings. + async FromAddressSettingsSave(fas: FromAddressSettings): Promise { + const fn: string = "FromAddressSettingsSave" + const paramTypes: string[][] = [["FromAddressSettings"]] + const returnTypes: string[][] = [] + const params: any[] = [fas] + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void + } + // MessageFindMessageID looks up a message by Message-Id header, and returns the ID // of the message in storage. Used when opening a previously saved draft message // for editing again. diff --git a/webmail/api_test.go b/webmail/api_test.go index e88f919..e1ced10 100644 --- a/webmail/api_test.go +++ b/webmail/api_test.go @@ -219,13 +219,18 @@ func TestAPI(t *testing.T) { // ParsedMessage // todo: verify contents api.ParsedMessage(ctx, inboxMinimal.ID) - api.ParsedMessage(ctx, inboxText.ID) api.ParsedMessage(ctx, inboxHTML.ID) api.ParsedMessage(ctx, inboxAlt.ID) api.ParsedMessage(ctx, inboxAltRel.ID) api.ParsedMessage(ctx, testbox1Alt.ID) tneedError(t, func() { api.ParsedMessage(ctx, 0) }) tneedError(t, func() { api.ParsedMessage(ctx, testmsgs[len(testmsgs)-1].ID+1) }) + pm := api.ParsedMessage(ctx, inboxText.ID) + tcompare(t, pm.ViewMode, store.ModeDefault) + + api.FromAddressSettingsSave(ctx, store.FromAddressSettings{FromAddress: "mjl@mox.example", ViewMode: store.ModeHTMLExt}) + pm = api.ParsedMessage(ctx, inboxText.ID) + tcompare(t, pm.ViewMode, store.ModeHTMLExt) // MailboxDelete api.MailboxDelete(ctx, testbox1.ID) diff --git a/webmail/message.go b/webmail/message.go index 73cb822..29ed794 100644 --- a/webmail/message.go +++ b/webmail/message.go @@ -209,7 +209,7 @@ func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem return r } - if msgitem { + if full || msgitem { env := MessageEnvelope{} if state.part.Envelope != nil { e := *state.part.Envelope diff --git a/webmail/msg.js b/webmail/msg.js index 14757c1..8ee2d0b 100644 --- a/webmail/msg.js +++ b/webmail/msg.js @@ -263,6 +263,15 @@ var api; AttachmentType["AttachmentDocument"] = "document"; AttachmentType["AttachmentPresentation"] = "presentation"; })(AttachmentType = api.AttachmentType || (api.AttachmentType = {})); + // ViewMode how a message should be viewed: its text parts, html parts, or html + // with loading external resources. + let ViewMode; + (function (ViewMode) { + ViewMode["ModeDefault"] = ""; + ViewMode["ModeText"] = "text"; + ViewMode["ModeHTML"] = "html"; + ViewMode["ModeHTMLExt"] = "htmlext"; + })(ViewMode = api.ViewMode || (api.ViewMode = {})); // SecurityResult indicates whether a security feature is supported. let SecurityResult; (function (SecurityResult) { @@ -281,8 +290,8 @@ var api; 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, "ComposeMessage": 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.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, "ComposeMessage": 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, "FromAddressSettings": 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, "ViewMode": 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"] }] }, @@ -290,12 +299,13 @@ var api; "Filter": { "Name": "Filter", "Docs": "", "Fields": [{ "Name": "MailboxID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MailboxChildrenIncluded", "Docs": "", "Typewords": ["bool"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Oldest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Newest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Headers", "Docs": "", "Typewords": ["[]", "[]", "string"] }, { "Name": "SizeMin", "Docs": "", "Typewords": ["int64"] }, { "Name": "SizeMax", "Docs": "", "Typewords": ["int64"] }] }, "NotFilter": { "Name": "NotFilter", "Docs": "", "Fields": [{ "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }] }, "Page": { "Name": "Page", "Docs": "", "Fields": [{ "Name": "AnchorMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Count", "Docs": "", "Typewords": ["int32"] }, { "Name": "DestMessageID", "Docs": "", "Typewords": ["int64"] }] }, - "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, + "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, "Part": { "Name": "Part", "Docs": "", "Fields": [{ "Name": "BoundaryOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "HeaderOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "BodyOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "EndOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "RawLineCount", "Docs": "", "Typewords": ["int64"] }, { "Name": "DecodedSize", "Docs": "", "Typewords": ["int64"] }, { "Name": "MediaType", "Docs": "", "Typewords": ["string"] }, { "Name": "MediaSubType", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTypeParams", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "ContentID", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentDescription", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTransferEncoding", "Docs": "", "Typewords": ["string"] }, { "Name": "Envelope", "Docs": "", "Typewords": ["nullable", "Envelope"] }, { "Name": "Parts", "Docs": "", "Typewords": ["[]", "Part"] }, { "Name": "Message", "Docs": "", "Typewords": ["nullable", "Part"] }] }, "Envelope": { "Name": "Envelope", "Docs": "", "Fields": [{ "Name": "Date", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "Sender", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "CC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "BCC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "InReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "MessageID", "Docs": "", "Typewords": ["string"] }] }, "Address": { "Name": "Address", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Host", "Docs": "", "Typewords": ["string"] }] }, "MessageAddress": { "Name": "MessageAddress", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }] }, "Domain": { "Name": "Domain", "Docs": "", "Fields": [{ "Name": "ASCII", "Docs": "", "Typewords": ["string"] }, { "Name": "Unicode", "Docs": "", "Typewords": ["string"] }] }, + "FromAddressSettings": { "Name": "FromAddressSettings", "Docs": "", "Fields": [{ "Name": "FromAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }] }, "ComposeMessage": { "Name": "ComposeMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "SubmitMessage": { "Name": "SubmitMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["[]", "File"] }, { "Name": "ForwardAttachments", "Docs": "", "Typewords": ["ForwardAttachments"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "UserAgent", "Docs": "", "Typewords": ["string"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["nullable", "bool"] }, { "Name": "FutureRelease", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "ArchiveThread", "Docs": "", "Typewords": ["bool"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "File": { "Name": "File", "Docs": "", "Fields": [{ "Name": "Filename", "Docs": "", "Typewords": ["string"] }, { "Name": "DataURI", "Docs": "", "Typewords": ["string"] }] }, @@ -331,6 +341,7 @@ var api; "CSRFToken": { "Name": "CSRFToken", "Docs": "", "Values": null }, "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": "" }] }, + "ViewMode": { "Name": "ViewMode", "Docs": "", "Values": [{ "Name": "ModeDefault", "Value": "", "Docs": "" }, { "Name": "ModeText", "Value": "text", "Docs": "" }, { "Name": "ModeHTML", "Value": "html", "Docs": "" }, { "Name": "ModeHTMLExt", "Value": "htmlext", "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 }, @@ -347,6 +358,7 @@ var api; Address: (v) => api.parse("Address", v), MessageAddress: (v) => api.parse("MessageAddress", v), Domain: (v) => api.parse("Domain", v), + FromAddressSettings: (v) => api.parse("FromAddressSettings", v), ComposeMessage: (v) => api.parse("ComposeMessage", v), SubmitMessage: (v) => api.parse("SubmitMessage", v), File: (v) => api.parse("File", v), @@ -382,6 +394,7 @@ var api; CSRFToken: (v) => api.parse("CSRFToken", v), ThreadMode: (v) => api.parse("ThreadMode", v), AttachmentType: (v) => api.parse("AttachmentType", v), + ViewMode: (v) => api.parse("ViewMode", v), SecurityResult: (v) => api.parse("SecurityResult", v), Quoting: (v) => api.parse("Quoting", v), Localpart: (v) => api.parse("Localpart", v), @@ -465,6 +478,14 @@ var api; const params = [msgID]; return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); } + // FromAddressSettingsSave saves per-"From"-address settings. + async FromAddressSettingsSave(fas) { + const fn = "FromAddressSettingsSave"; + const paramTypes = [["FromAddressSettings"]]; + const returnTypes = []; + const params = [fas]; + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); + } // MessageFindMessageID looks up a message by Message-Id header, and returns the ID // of the message in storage. Used when opening a previously saved draft message // for editing again. diff --git a/webmail/text.js b/webmail/text.js index 68605bf..6814784 100644 --- a/webmail/text.js +++ b/webmail/text.js @@ -263,6 +263,15 @@ var api; AttachmentType["AttachmentDocument"] = "document"; AttachmentType["AttachmentPresentation"] = "presentation"; })(AttachmentType = api.AttachmentType || (api.AttachmentType = {})); + // ViewMode how a message should be viewed: its text parts, html parts, or html + // with loading external resources. + let ViewMode; + (function (ViewMode) { + ViewMode["ModeDefault"] = ""; + ViewMode["ModeText"] = "text"; + ViewMode["ModeHTML"] = "html"; + ViewMode["ModeHTMLExt"] = "htmlext"; + })(ViewMode = api.ViewMode || (api.ViewMode = {})); // SecurityResult indicates whether a security feature is supported. let SecurityResult; (function (SecurityResult) { @@ -281,8 +290,8 @@ var api; 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, "ComposeMessage": 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.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, "ComposeMessage": 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, "FromAddressSettings": 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, "ViewMode": 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"] }] }, @@ -290,12 +299,13 @@ var api; "Filter": { "Name": "Filter", "Docs": "", "Fields": [{ "Name": "MailboxID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MailboxChildrenIncluded", "Docs": "", "Typewords": ["bool"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Oldest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Newest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Headers", "Docs": "", "Typewords": ["[]", "[]", "string"] }, { "Name": "SizeMin", "Docs": "", "Typewords": ["int64"] }, { "Name": "SizeMax", "Docs": "", "Typewords": ["int64"] }] }, "NotFilter": { "Name": "NotFilter", "Docs": "", "Fields": [{ "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }] }, "Page": { "Name": "Page", "Docs": "", "Fields": [{ "Name": "AnchorMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Count", "Docs": "", "Typewords": ["int32"] }, { "Name": "DestMessageID", "Docs": "", "Typewords": ["int64"] }] }, - "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, + "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, "Part": { "Name": "Part", "Docs": "", "Fields": [{ "Name": "BoundaryOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "HeaderOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "BodyOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "EndOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "RawLineCount", "Docs": "", "Typewords": ["int64"] }, { "Name": "DecodedSize", "Docs": "", "Typewords": ["int64"] }, { "Name": "MediaType", "Docs": "", "Typewords": ["string"] }, { "Name": "MediaSubType", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTypeParams", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "ContentID", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentDescription", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTransferEncoding", "Docs": "", "Typewords": ["string"] }, { "Name": "Envelope", "Docs": "", "Typewords": ["nullable", "Envelope"] }, { "Name": "Parts", "Docs": "", "Typewords": ["[]", "Part"] }, { "Name": "Message", "Docs": "", "Typewords": ["nullable", "Part"] }] }, "Envelope": { "Name": "Envelope", "Docs": "", "Fields": [{ "Name": "Date", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "Sender", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "CC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "BCC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "InReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "MessageID", "Docs": "", "Typewords": ["string"] }] }, "Address": { "Name": "Address", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Host", "Docs": "", "Typewords": ["string"] }] }, "MessageAddress": { "Name": "MessageAddress", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }] }, "Domain": { "Name": "Domain", "Docs": "", "Fields": [{ "Name": "ASCII", "Docs": "", "Typewords": ["string"] }, { "Name": "Unicode", "Docs": "", "Typewords": ["string"] }] }, + "FromAddressSettings": { "Name": "FromAddressSettings", "Docs": "", "Fields": [{ "Name": "FromAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }] }, "ComposeMessage": { "Name": "ComposeMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "SubmitMessage": { "Name": "SubmitMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["[]", "File"] }, { "Name": "ForwardAttachments", "Docs": "", "Typewords": ["ForwardAttachments"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "UserAgent", "Docs": "", "Typewords": ["string"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["nullable", "bool"] }, { "Name": "FutureRelease", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "ArchiveThread", "Docs": "", "Typewords": ["bool"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "File": { "Name": "File", "Docs": "", "Fields": [{ "Name": "Filename", "Docs": "", "Typewords": ["string"] }, { "Name": "DataURI", "Docs": "", "Typewords": ["string"] }] }, @@ -331,6 +341,7 @@ var api; "CSRFToken": { "Name": "CSRFToken", "Docs": "", "Values": null }, "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": "" }] }, + "ViewMode": { "Name": "ViewMode", "Docs": "", "Values": [{ "Name": "ModeDefault", "Value": "", "Docs": "" }, { "Name": "ModeText", "Value": "text", "Docs": "" }, { "Name": "ModeHTML", "Value": "html", "Docs": "" }, { "Name": "ModeHTMLExt", "Value": "htmlext", "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 }, @@ -347,6 +358,7 @@ var api; Address: (v) => api.parse("Address", v), MessageAddress: (v) => api.parse("MessageAddress", v), Domain: (v) => api.parse("Domain", v), + FromAddressSettings: (v) => api.parse("FromAddressSettings", v), ComposeMessage: (v) => api.parse("ComposeMessage", v), SubmitMessage: (v) => api.parse("SubmitMessage", v), File: (v) => api.parse("File", v), @@ -382,6 +394,7 @@ var api; CSRFToken: (v) => api.parse("CSRFToken", v), ThreadMode: (v) => api.parse("ThreadMode", v), AttachmentType: (v) => api.parse("AttachmentType", v), + ViewMode: (v) => api.parse("ViewMode", v), SecurityResult: (v) => api.parse("SecurityResult", v), Quoting: (v) => api.parse("Quoting", v), Localpart: (v) => api.parse("Localpart", v), @@ -465,6 +478,14 @@ var api; const params = [msgID]; return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); } + // FromAddressSettingsSave saves per-"From"-address settings. + async FromAddressSettingsSave(fas) { + const fn = "FromAddressSettingsSave"; + const paramTypes = [["FromAddressSettings"]]; + const returnTypes = []; + const params = [fas]; + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); + } // MessageFindMessageID looks up a message by Message-Id header, and returns the ID // of the message in storage. Used when opening a previously saved draft message // for editing again. diff --git a/webmail/view.go b/webmail/view.go index ac2afbe..81ba9ee 100644 --- a/webmail/view.go +++ b/webmail/view.go @@ -180,9 +180,10 @@ type MessageItem struct { // for rendering the (contents of the) message. Information from MessageItem is // not duplicated. type ParsedMessage struct { - ID int64 - Part message.Part - Headers map[string][]string + ID int64 + Part message.Part + Headers map[string][]string + ViewMode store.ViewMode // Text parts, can be empty. Texts []string @@ -1536,6 +1537,12 @@ func queryMessages(ctx context.Context, log mlog.Log, acc *store.Account, tx *bs } else { have++ } + if pm != nil && len(pm.envelope.From) == 1 { + pm.ViewMode, err = fromAddrViewMode(tx, pm.envelope.From[0]) + if err != nil { + return fmt.Errorf("gathering view mode for id %d: %v", m.ID, err) + } + } mrc <- msgResp{mil: mil, pm: pm} return nil }) diff --git a/webmail/webmail.js b/webmail/webmail.js index 18d877a..84fc976 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -263,6 +263,15 @@ var api; AttachmentType["AttachmentDocument"] = "document"; AttachmentType["AttachmentPresentation"] = "presentation"; })(AttachmentType = api.AttachmentType || (api.AttachmentType = {})); + // ViewMode how a message should be viewed: its text parts, html parts, or html + // with loading external resources. + let ViewMode; + (function (ViewMode) { + ViewMode["ModeDefault"] = ""; + ViewMode["ModeText"] = "text"; + ViewMode["ModeHTML"] = "html"; + ViewMode["ModeHTMLExt"] = "htmlext"; + })(ViewMode = api.ViewMode || (api.ViewMode = {})); // SecurityResult indicates whether a security feature is supported. let SecurityResult; (function (SecurityResult) { @@ -281,8 +290,8 @@ var api; 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, "ComposeMessage": 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.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, "ComposeMessage": 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, "FromAddressSettings": 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, "ViewMode": 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"] }] }, @@ -290,12 +299,13 @@ var api; "Filter": { "Name": "Filter", "Docs": "", "Fields": [{ "Name": "MailboxID", "Docs": "", "Typewords": ["int64"] }, { "Name": "MailboxChildrenIncluded", "Docs": "", "Typewords": ["bool"] }, { "Name": "MailboxName", "Docs": "", "Typewords": ["string"] }, { "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Oldest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Newest", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Headers", "Docs": "", "Typewords": ["[]", "[]", "string"] }, { "Name": "SizeMin", "Docs": "", "Typewords": ["int64"] }, { "Name": "SizeMax", "Docs": "", "Typewords": ["int64"] }] }, "NotFilter": { "Name": "NotFilter", "Docs": "", "Fields": [{ "Name": "Words", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["AttachmentType"] }, { "Name": "Labels", "Docs": "", "Typewords": ["[]", "string"] }] }, "Page": { "Name": "Page", "Docs": "", "Fields": [{ "Name": "AnchorMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Count", "Docs": "", "Typewords": ["int32"] }, { "Name": "DestMessageID", "Docs": "", "Typewords": ["int64"] }] }, - "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, + "ParsedMessage": { "Name": "ParsedMessage", "Docs": "", "Fields": [{ "Name": "ID", "Docs": "", "Typewords": ["int64"] }, { "Name": "Part", "Docs": "", "Typewords": ["Part"] }, { "Name": "Headers", "Docs": "", "Typewords": ["{}", "[]", "string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }, { "Name": "Texts", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "HasHTML", "Docs": "", "Typewords": ["bool"] }, { "Name": "ListReplyAddress", "Docs": "", "Typewords": ["nullable", "MessageAddress"] }] }, "Part": { "Name": "Part", "Docs": "", "Fields": [{ "Name": "BoundaryOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "HeaderOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "BodyOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "EndOffset", "Docs": "", "Typewords": ["int64"] }, { "Name": "RawLineCount", "Docs": "", "Typewords": ["int64"] }, { "Name": "DecodedSize", "Docs": "", "Typewords": ["int64"] }, { "Name": "MediaType", "Docs": "", "Typewords": ["string"] }, { "Name": "MediaSubType", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTypeParams", "Docs": "", "Typewords": ["{}", "string"] }, { "Name": "ContentID", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentDescription", "Docs": "", "Typewords": ["string"] }, { "Name": "ContentTransferEncoding", "Docs": "", "Typewords": ["string"] }, { "Name": "Envelope", "Docs": "", "Typewords": ["nullable", "Envelope"] }, { "Name": "Parts", "Docs": "", "Typewords": ["[]", "Part"] }, { "Name": "Message", "Docs": "", "Typewords": ["nullable", "Part"] }] }, "Envelope": { "Name": "Envelope", "Docs": "", "Fields": [{ "Name": "Date", "Docs": "", "Typewords": ["timestamp"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "From", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "Sender", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "CC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "BCC", "Docs": "", "Typewords": ["[]", "Address"] }, { "Name": "InReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "MessageID", "Docs": "", "Typewords": ["string"] }] }, "Address": { "Name": "Address", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Host", "Docs": "", "Typewords": ["string"] }] }, "MessageAddress": { "Name": "MessageAddress", "Docs": "", "Fields": [{ "Name": "Name", "Docs": "", "Typewords": ["string"] }, { "Name": "User", "Docs": "", "Typewords": ["string"] }, { "Name": "Domain", "Docs": "", "Typewords": ["Domain"] }] }, "Domain": { "Name": "Domain", "Docs": "", "Fields": [{ "Name": "ASCII", "Docs": "", "Typewords": ["string"] }, { "Name": "Unicode", "Docs": "", "Typewords": ["string"] }] }, + "FromAddressSettings": { "Name": "FromAddressSettings", "Docs": "", "Fields": [{ "Name": "FromAddress", "Docs": "", "Typewords": ["string"] }, { "Name": "ViewMode", "Docs": "", "Typewords": ["ViewMode"] }] }, "ComposeMessage": { "Name": "ComposeMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "SubmitMessage": { "Name": "SubmitMessage", "Docs": "", "Fields": [{ "Name": "From", "Docs": "", "Typewords": ["string"] }, { "Name": "To", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Cc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "Bcc", "Docs": "", "Typewords": ["[]", "string"] }, { "Name": "ReplyTo", "Docs": "", "Typewords": ["string"] }, { "Name": "Subject", "Docs": "", "Typewords": ["string"] }, { "Name": "TextBody", "Docs": "", "Typewords": ["string"] }, { "Name": "Attachments", "Docs": "", "Typewords": ["[]", "File"] }, { "Name": "ForwardAttachments", "Docs": "", "Typewords": ["ForwardAttachments"] }, { "Name": "IsForward", "Docs": "", "Typewords": ["bool"] }, { "Name": "ResponseMessageID", "Docs": "", "Typewords": ["int64"] }, { "Name": "UserAgent", "Docs": "", "Typewords": ["string"] }, { "Name": "RequireTLS", "Docs": "", "Typewords": ["nullable", "bool"] }, { "Name": "FutureRelease", "Docs": "", "Typewords": ["nullable", "timestamp"] }, { "Name": "ArchiveThread", "Docs": "", "Typewords": ["bool"] }, { "Name": "DraftMessageID", "Docs": "", "Typewords": ["int64"] }] }, "File": { "Name": "File", "Docs": "", "Fields": [{ "Name": "Filename", "Docs": "", "Typewords": ["string"] }, { "Name": "DataURI", "Docs": "", "Typewords": ["string"] }] }, @@ -331,6 +341,7 @@ var api; "CSRFToken": { "Name": "CSRFToken", "Docs": "", "Values": null }, "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": "" }] }, + "ViewMode": { "Name": "ViewMode", "Docs": "", "Values": [{ "Name": "ModeDefault", "Value": "", "Docs": "" }, { "Name": "ModeText", "Value": "text", "Docs": "" }, { "Name": "ModeHTML", "Value": "html", "Docs": "" }, { "Name": "ModeHTMLExt", "Value": "htmlext", "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 }, @@ -347,6 +358,7 @@ var api; Address: (v) => api.parse("Address", v), MessageAddress: (v) => api.parse("MessageAddress", v), Domain: (v) => api.parse("Domain", v), + FromAddressSettings: (v) => api.parse("FromAddressSettings", v), ComposeMessage: (v) => api.parse("ComposeMessage", v), SubmitMessage: (v) => api.parse("SubmitMessage", v), File: (v) => api.parse("File", v), @@ -382,6 +394,7 @@ var api; CSRFToken: (v) => api.parse("CSRFToken", v), ThreadMode: (v) => api.parse("ThreadMode", v), AttachmentType: (v) => api.parse("AttachmentType", v), + ViewMode: (v) => api.parse("ViewMode", v), SecurityResult: (v) => api.parse("SecurityResult", v), Quoting: (v) => api.parse("Quoting", v), Localpart: (v) => api.parse("Localpart", v), @@ -465,6 +478,14 @@ var api; const params = [msgID]; return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); } + // FromAddressSettingsSave saves per-"From"-address settings. + async FromAddressSettingsSave(fas) { + const fn = "FromAddressSettingsSave"; + const paramTypes = [["FromAddressSettings"]]; + const returnTypes = []; + const params = [fas]; + return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params); + } // MessageFindMessageID looks up a message by Message-Id header, and returns the ID // of the message in storage. Used when opening a previously saved draft message // for editing again. @@ -3463,6 +3484,12 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad } } }; + const fromAddressSettingsSave = async (mode) => { + const froms = mi.Envelope.From || []; + if (froms.length === 1) { + await withStatus('Saving view mode settings for address', client.FromAddressSettingsSave({ FromAddress: froms[0].User + "@" + (froms[0].Domain.Unicode || froms[0].Domain.ASCII), ViewMode: mode })); + } + }; const cmdShowText = async () => { if (!textbtn) { return; @@ -3470,6 +3497,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad loadText(await parsedMessagePromise); settingsPut({ ...settings, showHTML: false }); activeBtn(textbtn); + await fromAddressSettingsSave(api.ViewMode.ModeText); }; const cmdShowHTML = async () => { if (!htmlbtn || !htmlextbtn) { @@ -3477,6 +3505,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad } loadHTML(); activeBtn(htmlbtn); + await fromAddressSettingsSave(api.ViewMode.ModeHTML); }; const cmdShowHTMLExternal = async () => { if (!htmlbtn || !htmlextbtn) { @@ -3484,6 +3513,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad } loadHTMLexternal(); activeBtn(htmlextbtn); + await fromAddressSettingsSave(api.ViewMode.ModeHTMLExt); }; const cmdShowHTMLCycle = async () => { if (urlType === 'html') { @@ -3759,15 +3789,18 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad dom._kids(msgmodeElem); } else { - const text = haveText && !settings.showHTML; - dom._kids(msgmodeElem, dom.div(dom._class('pad'), style({ borderTop: '1px solid #ccc' }), !haveText ? dom.span('HTML-only message', attr.title(htmlNote), style({ backgroundColor: '#ffca91', padding: '0 .15em', marginRight: '.25em' })) : [], dom.span(dom._class('btngroup'), haveText ? textbtn = dom.clickbutton(text ? dom._class('active') : [], 'Text', clickCmd(cmdShowText, shortcuts)) : [], htmlbtn = dom.clickbutton(text ? [] : dom._class('active'), 'HTML', attr.title(htmlNote), async function click() { + const text = haveText && (pm.ViewMode == api.ViewMode.ModeText || pm.ViewMode == api.ViewMode.ModeDefault && !settings.showHTML); + dom._kids(msgmodeElem, dom.div(dom._class('pad'), style({ borderTop: '1px solid #ccc' }), !haveText ? dom.span('HTML-only message', attr.title(htmlNote), style({ backgroundColor: '#ffca91', padding: '0 .15em', marginRight: '.25em' })) : [], dom.span(dom._class('btngroup'), haveText ? textbtn = dom.clickbutton(text ? dom._class('active') : [], 'Text', clickCmd(cmdShowText, shortcuts)) : [], htmlbtn = dom.clickbutton(text || pm.ViewMode != api.ViewMode.ModeHTML ? [] : dom._class('active'), 'HTML', attr.title(htmlNote), async function click() { // Shortcuts has a function that cycles through html and htmlexternal. showShortcut('T'); await cmdShowHTML(); - }), htmlextbtn = dom.clickbutton('HTML with external resources', attr.title(htmlNote), clickCmd(cmdShowHTMLExternal, shortcuts))))); + }), htmlextbtn = dom.clickbutton(text || pm.ViewMode != api.ViewMode.ModeHTMLExt ? [] : dom._class('active'), 'HTML with external resources', attr.title(htmlNote), clickCmd(cmdShowHTMLExternal, shortcuts))))); if (text) { loadText(pm); } + else if (pm.ViewMode == api.ViewMode.ModeHTMLExt) { + loadHTMLexternal(); + } else { loadHTML(); } diff --git a/webmail/webmail.ts b/webmail/webmail.ts index 8265c3d..70fa910 100644 --- a/webmail/webmail.ts +++ b/webmail/webmail.ts @@ -2883,6 +2883,13 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l } } + const fromAddressSettingsSave = async (mode: api.ViewMode) => { + const froms = mi.Envelope.From || [] + if (froms.length === 1) { + await withStatus('Saving view mode settings for address', client.FromAddressSettingsSave({FromAddress: froms[0].User + "@" + (froms[0].Domain.Unicode || froms[0].Domain.ASCII), ViewMode: mode})) + } + } + const cmdShowText = async () => { if (!textbtn) { return @@ -2890,6 +2897,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l loadText(await parsedMessagePromise) settingsPut({...settings, showHTML: false}) activeBtn(textbtn) + await fromAddressSettingsSave(api.ViewMode.ModeText) } const cmdShowHTML = async () => { if (!htmlbtn || !htmlextbtn) { @@ -2897,6 +2905,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l } loadHTML() activeBtn(htmlbtn) + await fromAddressSettingsSave(api.ViewMode.ModeHTML) } const cmdShowHTMLExternal = async () => { if (!htmlbtn || !htmlextbtn) { @@ -2904,6 +2913,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l } loadHTMLexternal() activeBtn(htmlextbtn) + await fromAddressSettingsSave(api.ViewMode.ModeHTMLExt) } const cmdShowHTMLCycle = async () => { if (urlType === 'html') { @@ -3367,24 +3377,26 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l loadText(pm) dom._kids(msgmodeElem) } else { - const text = haveText && !settings.showHTML + const text = haveText && (pm.ViewMode == api.ViewMode.ModeText || pm.ViewMode == api.ViewMode.ModeDefault && !settings.showHTML) dom._kids(msgmodeElem, dom.div(dom._class('pad'), style({borderTop: '1px solid #ccc'}), !haveText ? dom.span('HTML-only message', attr.title(htmlNote), style({backgroundColor: '#ffca91', padding: '0 .15em', marginRight: '.25em'})) : [], dom.span(dom._class('btngroup'), haveText ? textbtn=dom.clickbutton(text ? dom._class('active') : [], 'Text', clickCmd(cmdShowText, shortcuts)) : [], - htmlbtn=dom.clickbutton(text ? [] : dom._class('active'), 'HTML', attr.title(htmlNote), async function click() { + htmlbtn=dom.clickbutton(text || pm.ViewMode != api.ViewMode.ModeHTML ? [] : dom._class('active'), 'HTML', attr.title(htmlNote), async function click() { // Shortcuts has a function that cycles through html and htmlexternal. showShortcut('T') await cmdShowHTML() }), - htmlextbtn=dom.clickbutton('HTML with external resources', attr.title(htmlNote), clickCmd(cmdShowHTMLExternal, shortcuts)), + htmlextbtn=dom.clickbutton(text || pm.ViewMode != api.ViewMode.ModeHTMLExt ? [] : dom._class('active'), 'HTML with external resources', attr.title(htmlNote), clickCmd(cmdShowHTMLExternal, shortcuts)), ), ) ) if (text) { loadText(pm) + } else if (pm.ViewMode == api.ViewMode.ModeHTMLExt) { + loadHTMLexternal() } else { loadHTML() }