diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fcf53fde..0ae19953a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ Notable changes to Mailpit will be documented in this file. +## [v1.20.1] + +### Chore +- Shift inbox pagination to inbox component +- Live load up to 100 new messages in sidebar ([#336](https://github.com/axllent/mailpit/issues/336)) +- Show icon attachment in new side navigation message listing ([#345](https://github.com/axllent/mailpit/issues/345)) + +### Fix +- Correctly decode X-Tags message headers (RFC 2047) ([#344](https://github.com/axllent/mailpit/issues/344)) + + ## [v1.20.0] ### Feature diff --git a/internal/storage/messages.go b/internal/storage/messages.go index 841b2873b..0007e1c69 100644 --- a/internal/storage/messages.go +++ b/internal/storage/messages.go @@ -50,7 +50,7 @@ func Store(body *[]byte) (string, error) { ReplyTo: addressToSlice(env, "Reply-To"), } - messageID := strings.Trim(env.Root.Header.Get("Message-ID"), "<>") + messageID := strings.Trim(env.GetHeader("Message-ID"), "<>") created := time.Now() // use message date instead of created date @@ -116,7 +116,7 @@ func Store(body *[]byte) (string, error) { tags := findTagsInRawMessage(body) if !config.TagsDisableXTags { - xTagsHdr := env.Root.Header.Get("X-Tags") + xTagsHdr := env.GetHeader("X-Tags") if xTagsHdr != "" { // extract tags from X-Tags header tags = append(tags, tools.SetTagCasing(strings.Split(strings.TrimSpace(xTagsHdr), ","))...) diff --git a/server/ui-src/components/Notifications.vue b/server/ui-src/components/Notifications.vue index 45ae01759..2e647e450 100644 --- a/server/ui-src/components/Notifications.vue +++ b/server/ui-src/components/Notifications.vue @@ -21,7 +21,6 @@ export default { socketBreaks: 0, // to track sockets that continually connect & disconnect, reset every 15s pauseNotifications: false, // prevent spamming version: false, - paginationDelayed: false, // for delayed pagination URL changes } }, @@ -56,20 +55,7 @@ export default { // new messages if (response.Type == "new" && response.Data) { - if (!mailbox.searching) { - if (pagination.start < 1) { - // push results directly into first page - mailbox.messages.unshift(response.Data) - if (mailbox.messages.length > pagination.limit) { - mailbox.messages.pop() - } - } else { - // update pagination offset - pagination.start++ - // prevent "Too many calls to Location or History APIs within a short time frame" - this.delayedPaginationUpdate() - } - } + this.eventBus.emit("new", response.Data) for (let i in response.Data.Tags) { if (mailbox.tags.findIndex(e => { return e.toLowerCase() === response.Data.Tags[i].toLowerCase() }) < 0) { @@ -106,10 +92,10 @@ export default { } } else if (response.Type == "delete" && response.Data) { // broadcast for components - this.eventBus.emit("delete", response.Data); + this.eventBus.emit("delete", response.Data) } else if (response.Type == "update" && response.Data) { // broadcast for components - this.eventBus.emit("update", response.Data); + this.eventBus.emit("update", response.Data) } else if (response.Type == "truncate") { // broadcast for components this.eventBus.emit("truncate") @@ -171,39 +157,6 @@ export default { }, 15000) }, - // This will only update the pagination offset at a maximum of 2x per second - // when viewing the inbox on > page 1, while receiving an influx of new messages. - delayedPaginationUpdate() { - if (this.paginationDelayed) { - return - } - - this.paginationDelayed = true - - window.setTimeout(() => { - const path = this.$route.path - const p = { - ...this.$route.query - } - if (pagination.start > 0) { - p.start = pagination.start.toString() - } else { - delete p.start - } - if (pagination.limit != pagination.defaultLimit) { - p.limit = pagination.limit.toString() - } else { - delete p.limit - } - - mailbox.autoPaginating = false // prevent reload of messages when URL changes - const params = new URLSearchParams(p) - this.$router.replace(path + '?' + params.toString()) - - this.paginationDelayed = false - }, 500) - }, - browserNotify(title, message) { if (!("Notification" in window)) { return diff --git a/server/ui-src/mixins/CommonMixins.js b/server/ui-src/mixins/CommonMixins.js index 4517f9b54..21493b7bc 100644 --- a/server/ui-src/mixins/CommonMixins.js +++ b/server/ui-src/mixins/CommonMixins.js @@ -275,11 +275,6 @@ export default { return 'bi-file-arrow-down-fill' }, - // wrapper to update one or more messages with - updateMessages(messages, updates) { - - }, - // Returns a hex color based on a string. // Values are stored in an array for faster lookup / processing. colorHash(s) { diff --git a/server/ui-src/views/MailboxView.vue b/server/ui-src/views/MailboxView.vue index ea4679da3..782c99db3 100644 --- a/server/ui-src/views/MailboxView.vue +++ b/server/ui-src/views/MailboxView.vue @@ -31,6 +31,7 @@ export default { return { mailbox, delayedRefresh: false, + paginationDelayed: false, // for delayed pagination URL changes } }, @@ -46,6 +47,7 @@ export default { this.loadMailbox() // subscribe to events + this.eventBus.on("new", this.handleWSNew) this.eventBus.on("update", this.handleWSUpdate) this.eventBus.on("delete", this.handleWSDelete) this.eventBus.on("truncate", this.handleWSTruncate) @@ -53,6 +55,7 @@ export default { unmounted() { // unsubscribe from events + this.eventBus.off("new", this.handleWSNew) this.eventBus.off("update", this.handleWSUpdate) this.eventBus.off("delete", this.handleWSDelete) this.eventBus.off("truncate", this.handleWSTruncate) @@ -73,6 +76,55 @@ export default { this.loadMessages() }, + // This will only update the pagination offset at a maximum of 2x per second + // when viewing the inbox on > page 1, while receiving an influx of new messages. + delayedPaginationUpdate() { + if (this.paginationDelayed) { + return + } + + this.paginationDelayed = true + + window.setTimeout(() => { + const path = this.$route.path + const p = { + ...this.$route.query + } + if (pagination.start > 0) { + p.start = pagination.start.toString() + } else { + delete p.start + } + if (pagination.limit != pagination.defaultLimit) { + p.limit = pagination.limit.toString() + } else { + delete p.limit + } + + mailbox.autoPaginating = false // prevent reload of messages when URL changes + const params = new URLSearchParams(p) + this.$router.replace(path + '?' + params.toString()) + + this.paginationDelayed = false + }, 500) + }, + + // handler for websocket new messages + handleWSNew(data) { + if (pagination.start < 1) { + // push results directly into first page + mailbox.messages.unshift(data) + if (mailbox.messages.length > pagination.limit) { + mailbox.messages.pop() + } + } else { + // update pagination offset + pagination.start++ + // prevent "Too many calls to Location or History APIs within a short time frame" + this.delayedPaginationUpdate() + } + }, + // handler for websocket message updates handleWSUpdate(data) { for (let x = 0; x < this.mailbox.messages.length; x++) { diff --git a/server/ui-src/views/MessageView.vue b/server/ui-src/views/MessageView.vue index 2715b4983..288735fea 100644 --- a/server/ui-src/views/MessageView.vue +++ b/server/ui-src/views/MessageView.vue @@ -33,6 +33,7 @@ export default { apiSideNavParams: URLSearchParams, apiIsMore: true, messagesList: [], + liveLoaded: 0, // the number new messages prepended tp messageList scrollLoading: false, canLoadMore: true, } @@ -62,6 +63,7 @@ export default { this.refreshUI() // subscribe to events + this.eventBus.on("new", this.handleWSNew) this.eventBus.on("update", this.handleWSUpdate) this.eventBus.on("delete", this.handleWSDelete) this.eventBus.on("truncate", this.handleWSTruncate) @@ -69,6 +71,7 @@ export default { unmounted() { // unsubscribe from events + this.eventBus.off("new", this.handleWSNew) this.eventBus.off("update", this.handleWSUpdate) this.eventBus.off("delete", this.handleWSDelete) this.eventBus.off("truncate", this.handleWSTruncate) @@ -212,6 +215,18 @@ export default { }, 30000) }, + // handler for websocket new messages + handleWSNew(data) { + // do not add when searching or >= 100 new messages have been received + if (this.mailbox.searching || this.liveLoaded >= 100) { + return + } + + this.liveLoaded++ + this.messagesList.unshift(data) + this.scrollSidebarToCurrent() + }, + // handler for websocket message updates handleWSUpdate(data) { for (let x = 0; x < this.messagesList.length; x++) { @@ -419,6 +434,10 @@ export default { } }, + reloadWindow() { + location.reload() + }, + initReleaseModal() { this.modal('ReleaseModal').show() window.setTimeout(() => { @@ -537,7 +556,7 @@ export default { Return to search - mailbox + inbox @@ -548,6 +567,9 @@ export default { + + Reload to see newer messages + + {{ getRelativeCreated(message) }}