mirror of
https://github.com/axllent/mailpit.git
synced 2025-03-17 21:18:19 +02:00
Merge branch 'feature/date-search' into develop
This commit is contained in:
commit
a5c7ae34e3
@ -19,7 +19,7 @@ If you have several thousand messages in your mailbox, then it is advised to shu
|
||||
Mailpit while you reindex as this process will likely result in database locking issues.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config.DataFile = args[0]
|
||||
config.Database = args[0]
|
||||
config.MaxMessages = 0
|
||||
|
||||
if err := storage.InitDB(); err != nil {
|
||||
|
22
cmd/root.go
22
cmd/root.go
@ -80,8 +80,8 @@ func init() {
|
||||
// load environment variables
|
||||
initConfigFromEnv()
|
||||
|
||||
rootCmd.Flags().StringVarP(&config.DataFile, "db-file", "d", config.DataFile, "Database file to store persistent data")
|
||||
rootCmd.Flags().StringVar(&config.TenantID, "db-tenant-id", config.TenantID, "Database tenant ID to isolate data")
|
||||
rootCmd.Flags().StringVarP(&config.Database, "database", "d", config.Database, "Database to store persistent data")
|
||||
rootCmd.Flags().StringVar(&config.TenantID, "tenant-id", config.TenantID, "Database tenant ID to isolate data")
|
||||
rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store")
|
||||
rootCmd.Flags().BoolVar(&config.UseMessageDates, "use-message-dates", config.UseMessageDates, "Use message dates as the received dates")
|
||||
rootCmd.Flags().BoolVar(&config.IgnoreDuplicateIDs, "ignore-duplicate-ids", config.IgnoreDuplicateIDs, "Ignore duplicate messages (by Message-Id)")
|
||||
@ -133,6 +133,10 @@ func init() {
|
||||
rootCmd.Flags().StringVar(&config.WebhookURL, "webhook-url", config.WebhookURL, "Send a webhook request for new messages")
|
||||
rootCmd.Flags().IntVar(&webhook.RateLimit, "webhook-limit", webhook.RateLimit, "Limit webhook requests per second")
|
||||
|
||||
// DEPRECATED FLAG 2024/04/12 - but will not be removed to maintain backwards compatibility
|
||||
rootCmd.Flags().StringVar(&config.Database, "db-file", config.Database, "Database file to store persistent data")
|
||||
rootCmd.Flags().Lookup("db-file").Hidden = true
|
||||
|
||||
// DEPRECATED FLAGS 2023/03/12
|
||||
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-ssl-cert", config.UITLSCert, "SSL certificate for web UI - requires ui-ssl-key")
|
||||
rootCmd.Flags().StringVar(&config.UITLSKey, "ui-ssl-key", config.UITLSKey, "SSL key for web UI - requires ui-ssl-cert")
|
||||
@ -156,11 +160,11 @@ func init() {
|
||||
// Load settings from environment
|
||||
func initConfigFromEnv() {
|
||||
// General
|
||||
if len(os.Getenv("MP_DB_FILE")) > 0 {
|
||||
config.DataFile = os.Getenv("MP_DB_FILE")
|
||||
if len(os.Getenv("MP_DATABASE")) > 0 {
|
||||
config.Database = os.Getenv("MP_DATABASE")
|
||||
}
|
||||
|
||||
config.TenantID = os.Getenv("MP_DB_TENANT")
|
||||
config.TenantID = os.Getenv("MP_TENANT_ID")
|
||||
|
||||
if len(os.Getenv("MP_MAX_MESSAGES")) > 0 {
|
||||
config.MaxMessages, _ = strconv.Atoi(os.Getenv("MP_MAX_MESSAGES"))
|
||||
@ -229,7 +233,6 @@ func initConfigFromEnv() {
|
||||
if getEnabledFromEnv("MP_SMTP_REQUIRE_TLS") {
|
||||
config.SMTPRequireTLS = true
|
||||
}
|
||||
|
||||
if getEnabledFromEnv("MP_SMTP_AUTH_ALLOW_INSECURE") {
|
||||
config.SMTPAuthAllowInsecure = true
|
||||
}
|
||||
@ -295,12 +298,11 @@ func initConfigFromEnv() {
|
||||
|
||||
// load deprecated settings from environment and warn
|
||||
func initDeprecatedConfigFromEnv() {
|
||||
// deprecated 2024/04/08
|
||||
// deprecated 2024/04/12 - but will not be removed to maintain backwards compatibility
|
||||
if len(os.Getenv("MP_DATA_FILE")) > 0 {
|
||||
// do not warn - this will remain for quite some time
|
||||
// logger.Log().Warn("ENV MP_DATA_FILE has been deprecated, use MP_DB_FILE")
|
||||
config.DataFile = os.Getenv("MP_DATA_FILE")
|
||||
config.Database = os.Getenv("MP_DATA_FILE")
|
||||
}
|
||||
|
||||
// deprecated 2023/03/12
|
||||
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
|
||||
logger.Log().Warn("ENV MP_UI_SSL_CERT has been deprecated, use MP_UI_TLS_CERT")
|
||||
|
@ -26,8 +26,8 @@ var (
|
||||
// HTTPListen to listen on <interface>:<port>
|
||||
HTTPListen = "[::]:8025"
|
||||
|
||||
// DataFile for mail (optional)
|
||||
DataFile string
|
||||
// Database for mail (optional)
|
||||
Database string
|
||||
|
||||
// TenantID is an optional prefix to be applied to all database tables,
|
||||
// allowing multiple isolated instances of Mailpit to share a database.
|
||||
@ -189,8 +189,8 @@ func VerifyConfig() error {
|
||||
cssFontRestriction, cssFontRestriction,
|
||||
)
|
||||
|
||||
if DataFile != "" && isDir(DataFile) {
|
||||
DataFile = filepath.Join(DataFile, "mailpit.db")
|
||||
if Database != "" && isDir(Database) {
|
||||
Database = filepath.Join(Database, "mailpit.db")
|
||||
}
|
||||
|
||||
TenantID = strings.TrimSpace(TenantID)
|
||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.20
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.9.1
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/axllent/semver v0.0.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47
|
||||
|
5
go.sum
5
go.sum
@ -6,6 +6,8 @@ github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXE
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/axllent/semver v0.0.1 h1:QqF+KSGxgj8QZzSXAvKFqjGWE5792ksOnQhludToK8E=
|
||||
github.com/axllent/semver v0.0.1/go.mod h1:2xSPzvG8n9mRfdtxSvWvfTfQGWfHsMsHO1iZnKATMSc=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
@ -60,6 +62,7 @@ github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQ
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
@ -80,6 +83,7 @@ github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@ -88,6 +92,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
||||
github.com/rqlite/gorqlite v0.0.0-20240227123050-397b03f02418 h1:gYUQqzapdN4PQF5j0zDFI9ANQVAVFoJivNp5bTZEZMo=
|
||||
github.com/rqlite/gorqlite v0.0.0-20240227123050-397b03f02418/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
|
@ -94,7 +94,7 @@ func Load() AppInformation {
|
||||
}
|
||||
}
|
||||
|
||||
info.Database = config.DataFile
|
||||
info.Database = config.Database
|
||||
info.DatabaseSize = storage.DbSize()
|
||||
info.Messages = storage.CountTotal()
|
||||
info.Unread = storage.CountUnread()
|
||||
|
@ -39,7 +39,7 @@ var (
|
||||
|
||||
// InitDB will initialise the database
|
||||
func InitDB() error {
|
||||
p := config.DataFile
|
||||
p := config.Database
|
||||
var dsn string
|
||||
|
||||
if p == "" {
|
||||
@ -61,7 +61,7 @@ func InitDB() error {
|
||||
logger.Log().Debugf("[db] opening database %s", p)
|
||||
}
|
||||
|
||||
config.DataFile = p
|
||||
config.Database = p
|
||||
|
||||
var err error
|
||||
|
||||
|
@ -415,7 +415,7 @@ func LatestID(r *http.Request) (string, error) {
|
||||
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search != "" {
|
||||
messages, _, err = Search(search, 0, 1)
|
||||
messages, _, err = Search(search, r.URL.Query().Get("tz"), 0, 1)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/araddon/dateparse"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/tools"
|
||||
"github.com/leporo/sqlf"
|
||||
@ -17,7 +18,7 @@ import (
|
||||
// The search is broken up by segments (exact phrases can be quoted), and interprets specific terms such as:
|
||||
// is:read, is:unread, has:attachment, to:<term>, from:<term> & subject:<term>
|
||||
// Negative searches also also included by prefixing the search term with a `-` or `!`
|
||||
func Search(search string, start, limit int) ([]MessageSummary, int, error) {
|
||||
func Search(search, timezone string, start, limit int) ([]MessageSummary, int, error) {
|
||||
results := []MessageSummary{}
|
||||
allResults := []MessageSummary{}
|
||||
tsStart := time.Now()
|
||||
@ -26,7 +27,7 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
q := searchQueryBuilder(search)
|
||||
q := searchQueryBuilder(search, timezone)
|
||||
var err error
|
||||
|
||||
if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
|
||||
@ -95,8 +96,8 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
|
||||
// The search is broken up by segments (exact phrases can be quoted), and interprets specific terms such as:
|
||||
// is:read, is:unread, has:attachment, to:<term>, from:<term> & subject:<term>
|
||||
// Negative searches also also included by prefixing the search term with a `-` or `!`
|
||||
func DeleteSearch(search string) error {
|
||||
q := searchQueryBuilder(search)
|
||||
func DeleteSearch(search, timezone string) error {
|
||||
q := searchQueryBuilder(search, timezone)
|
||||
|
||||
ids := []string{}
|
||||
deleteSize := float64(0)
|
||||
@ -203,10 +204,19 @@ func DeleteSearch(search string) error {
|
||||
}
|
||||
|
||||
// SearchParser returns the SQL syntax for the database search based on the search arguments
|
||||
func searchQueryBuilder(searchString string) *sqlf.Stmt {
|
||||
func searchQueryBuilder(searchString, timezone string) *sqlf.Stmt {
|
||||
// group strings with quotes as a single argument and remove quotes
|
||||
args := tools.ArgsParser(searchString)
|
||||
|
||||
if timezone != "" {
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
logger.Log().Warnf("ignoring invalid timezone:\"%s\"", timezone)
|
||||
} else {
|
||||
time.Local = loc
|
||||
}
|
||||
}
|
||||
|
||||
q := sqlf.From(tenant("mailbox") + " m").
|
||||
Select(`m.Created, m.ID, m.MessageID, m.Subject, m.Metadata, m.Size, m.Attachments, m.Read,
|
||||
m.Snippet,
|
||||
@ -335,6 +345,36 @@ func searchQueryBuilder(searchString string) *sqlf.Stmt {
|
||||
} else {
|
||||
q.Where("Attachments > 0")
|
||||
}
|
||||
} else if strings.HasPrefix(lw, "after:") {
|
||||
w = cleanString(w[6:])
|
||||
if w != "" {
|
||||
t, err := dateparse.ParseLocal(w)
|
||||
if err != nil {
|
||||
logger.Log().Warnf("ignoring invalid after: date \"%s\"", w)
|
||||
} else {
|
||||
timestamp := t.UnixMilli()
|
||||
if exclude {
|
||||
q.Where(`m.Created <= ?`, timestamp)
|
||||
} else {
|
||||
q.Where(`m.Created >= ?`, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(lw, "before:") {
|
||||
w = cleanString(w[7:])
|
||||
if w != "" {
|
||||
t, err := dateparse.ParseLocal(w)
|
||||
if err != nil {
|
||||
logger.Log().Warnf("ignoring invalid before: date \"%s\"", w)
|
||||
} else {
|
||||
timestamp := t.UnixMilli()
|
||||
if exclude {
|
||||
q.Where(`m.Created >= ?`, timestamp)
|
||||
} else {
|
||||
q.Where(`m.Created <= ?`, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// search text
|
||||
if exclude {
|
||||
|
@ -69,7 +69,7 @@ func TestSearch(t *testing.T) {
|
||||
|
||||
search := uniqueSearches[searchIdx]
|
||||
|
||||
summaries, _, err := Search(search, 0, 100)
|
||||
summaries, _, err := Search(search, "", 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@ -85,7 +85,7 @@ func TestSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
// search something that will return 200 results
|
||||
summaries, _, err := Search("This is the email body", 0, testRuns)
|
||||
summaries, _, err := Search("This is the email body", "", 0, testRuns)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@ -109,7 +109,7 @@ func TestSearchDelete100(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
_, total, err := Search("from:sender@example.com", 0, 100)
|
||||
_, total, err := Search("from:sender@example.com", "", 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@ -117,12 +117,12 @@ func TestSearchDelete100(t *testing.T) {
|
||||
|
||||
assertEqual(t, total, 100, "100 search results expected")
|
||||
|
||||
if err := DeleteSearch("from:sender@example.com"); err != nil {
|
||||
if err := DeleteSearch("from:sender@example.com", ""); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, total, err = Search("from:sender@example.com", 0, 100)
|
||||
_, total, err = Search("from:sender@example.com", "", 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@ -143,7 +143,7 @@ func TestSearchDelete1100(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
_, total, err := Search("from:sender@example.com", 0, 100)
|
||||
_, total, err := Search("from:sender@example.com", "", 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
@ -151,12 +151,12 @@ func TestSearchDelete1100(t *testing.T) {
|
||||
|
||||
assertEqual(t, total, 1100, "100 search results expected")
|
||||
|
||||
if err := DeleteSearch("from:sender@example.com"); err != nil {
|
||||
if err := DeleteSearch("from:sender@example.com", ""); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
_, total, err = Search("from:sender@example.com", 0, 100)
|
||||
_, total, err = Search("from:sender@example.com", "", 0, 100)
|
||||
if err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
|
@ -19,7 +19,7 @@ var (
|
||||
func setup() {
|
||||
logger.NoLogging = true
|
||||
config.MaxMessages = 0
|
||||
config.DataFile = os.Getenv("MP_DATA_FILE")
|
||||
config.Database = os.Getenv("MP_DATABASE")
|
||||
|
||||
if err := InitDB(); err != nil {
|
||||
panic(err)
|
||||
|
@ -109,6 +109,11 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
// required: false
|
||||
// type: integer
|
||||
// default: 50
|
||||
// + name: tz
|
||||
// in: query
|
||||
// description: Timezone for `before:` & `after:` queries, eg: "Pacific/Auckland"
|
||||
// required: false
|
||||
// type: string
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessagesSummaryResponse
|
||||
@ -121,7 +126,7 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
start, limit := getStartLimit(r)
|
||||
|
||||
messages, results, err := storage.Search(search, start, limit)
|
||||
messages, results, err := storage.Search(search, r.URL.Query().Get("tz"), start, limit)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
@ -173,7 +178,7 @@ func DeleteSearch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := storage.DeleteSearch(search); err != nil {
|
||||
if err := storage.DeleteSearch(search, r.URL.Query().Get("tz")); err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ func RedirectToLatestMessage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
search := strings.TrimSpace(r.URL.Query().Get("query"))
|
||||
if search != "" {
|
||||
messages, _, err = storage.Search(search, 0, 1)
|
||||
messages, _, err = storage.Search(search, "", 0, 1)
|
||||
if err != nil {
|
||||
httpError(w, err.Error())
|
||||
return
|
||||
|
@ -205,7 +205,7 @@ func TestAPIv1Search(t *testing.T) {
|
||||
func setup() {
|
||||
logger.NoLogging = true
|
||||
config.MaxMessages = 0
|
||||
config.DataFile = os.Getenv("MP_DATA_FILE")
|
||||
config.Database = os.Getenv("MP_DATABASE")
|
||||
|
||||
if err := storage.InitDB(); err != nil {
|
||||
panic(err)
|
||||
|
@ -16,6 +16,7 @@ export default {
|
||||
beforeMount() {
|
||||
document.title = document.title + ' - ' + location.hostname
|
||||
mailbox.showTagColors = !localStorage.getItem('hideTagColors') == '1'
|
||||
mailbox.timeZone = localStorage.getItem('timezone') ? localStorage.getItem('timezone') : Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
// load global config
|
||||
this.get(this.resolve('/api/v1/webui'), false, function (response) {
|
||||
|
@ -19,6 +19,7 @@ export const mailbox = reactive({
|
||||
appInfo: {}, // application information
|
||||
uiConfig: {}, // configuration for UI
|
||||
lastMessage: false, // return scrolling
|
||||
timeZone: '', // browser timezone
|
||||
})
|
||||
|
||||
watch(
|
||||
|
@ -59,6 +59,9 @@ export default {
|
||||
}
|
||||
|
||||
this.apiURI = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s)
|
||||
if (mailbox.timeZone != '' && (s.indexOf('after:') != -1 || s.indexOf('before:') != -1)) {
|
||||
this.apiURI += '&tz=' + encodeURIComponent(mailbox.timeZone)
|
||||
}
|
||||
this.loadMessages()
|
||||
}
|
||||
}
|
||||
|
@ -550,6 +550,12 @@
|
||||
"description": "Limit results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Timezone for `before:` \u0026 `after:` queries, eg: \"Pacific/Auckland\"",
|
||||
"name": "tz",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user