Skip to content

Commit 97bf9c2

Browse files
committed
Merge branch 'release/0.1.3'
2 parents 8affa0f + 18b0f5b commit 97bf9c2

9 files changed

Lines changed: 155 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
Notable changes to Mailpit will be documented in this file.
44

55

6+
## 0.1.3
7+
8+
### Feature
9+
- Mark all messages as read
10+
11+
### UI
12+
- Better error handling when connection to server is broken
13+
- Add reset search button
14+
- Minor UI tweaks
15+
- Update pagination values when new mail arrives when not on first page
16+
17+
18+
### Pull Requests
19+
- Merge pull request [#6](https://github.com/axllent/mailpit/issues/6) from KaptinLin/develop
20+
621
## 0.1.2
722

823
### Feature

cmd/root.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,21 @@ func init() {
8787
if len(os.Getenv("MP_UI_AUTH_FILE")) > 0 {
8888
config.UIAuthFile = os.Getenv("MP_UI_AUTH_FILE")
8989
}
90-
if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 {
91-
config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE")
92-
}
9390
if len(os.Getenv("MP_UI_SSL_CERT")) > 0 {
9491
config.UISSLCert = os.Getenv("MP_UI_SSL_CERT")
9592
}
9693
if len(os.Getenv("MP_UI_SSL_KEY")) > 0 {
9794
config.UISSLKey = os.Getenv("MP_UI_SSL_KEY")
9895
}
96+
if len(os.Getenv("MP_SMTP_AUTH_FILE")) > 0 {
97+
config.SMTPAuthFile = os.Getenv("MP_SMTP_AUTH_FILE")
98+
}
99+
if len(os.Getenv("MP_SMTP_SSL_CERT")) > 0 {
100+
config.SMTPSSLCert = os.Getenv("MP_SMTP_SSL_CERT")
101+
}
102+
if len(os.Getenv("MP_SMTP_SSL_KEY")) > 0 {
103+
config.SMTPSSLKey = os.Getenv("MP_SMTP_SSL_KEY")
104+
}
99105

100106
// deprecated 2022/08/06
101107
if len(os.Getenv("MP_AUTH_FILE")) > 0 {

screenshot.png

7.84 KB
Loading

server/api.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,22 @@ func apiUnreadOne(w http.ResponseWriter, r *http.Request) {
218218
_, _ = w.Write([]byte("ok"))
219219
}
220220

221+
// Mark single message as unread
222+
func apiMarkAllRead(w http.ResponseWriter, r *http.Request) {
223+
vars := mux.Vars(r)
224+
225+
mailbox := vars["mailbox"]
226+
227+
err := storage.MarkAllRead(mailbox)
228+
if err != nil {
229+
httpError(w, err.Error())
230+
return
231+
}
232+
233+
w.Header().Add("Content-Type", "text/plain")
234+
_, _ = w.Write([]byte("ok"))
235+
}
236+
221237
// Websocket to broadcast changes
222238
func apiWebsocket(w http.ResponseWriter, r *http.Request) {
223239
websockets.ServeWs(websockets.MessageHub, w, r)

server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func Listen() {
3939
r.HandleFunc("/api/{mailbox}/search", middleWareFunc(apiSearchMailbox))
4040
r.HandleFunc("/api/{mailbox}/delete", middleWareFunc(apiDeleteAll))
4141
r.HandleFunc("/api/{mailbox}/events", apiWebsocket)
42+
r.HandleFunc("/api/{mailbox}/read", apiMarkAllRead)
4243
r.HandleFunc("/api/{mailbox}/{id}/source", middleWareFunc(apiDownloadSource))
4344
r.HandleFunc("/api/{mailbox}/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment))
4445
r.HandleFunc("/api/{mailbox}/{id}/delete", middleWareFunc(apiDeleteOne))

server/ui-src/App.vue

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ export default {
9999
this.loadMessages();
100100
},
101101
102+
resetSearch: function(e) {
103+
e.preventDefault();
104+
this.search = '';
105+
this.scrollInPlace = true;
106+
this.loadMessages();
107+
},
108+
102109
reloadMessages: function() {
103110
this.search = "";
104111
this.start = 0;
@@ -198,6 +205,16 @@ export default {
198205
});
199206
},
200207
208+
markAllRead: function() {
209+
let self = this;
210+
let uri = 'api/' + self.mailbox + '/read'
211+
self.get(uri, false, function(response) {
212+
window.location.hash = "";
213+
self.scrollInPlace = true;
214+
self.loadMessages();
215+
});
216+
},
217+
201218
// websocket connect
202219
connect: function () {
203220
let wsproto = location.protocol == 'https:' ? 'wss' : 'ws';
@@ -210,12 +227,14 @@ export default {
210227
}
211228
// new messages
212229
if (response.Type == "new" && response.Data) {
213-
if (self.start < 1) {
214-
if (!self.searching) {
230+
if (!self.searching) {
231+
if (self.start < 1) {
215232
self.items.unshift(response.Data);
216233
if (self.items.length > self.limit) {
217234
self.items.pop();
218235
}
236+
} else {
237+
self.start++;
219238
}
220239
}
221240
self.total++;
@@ -299,7 +318,7 @@ export default {
299318
</script>
300319

301320
<template>
302-
<div class="navbar navbar-expand-lg navbar-light row flex-shrink-0 bg-light">
321+
<div class="navbar navbar-expand-lg navbar-light row flex-shrink-0 bg-light shadow-sm">
303322
<div class="col-lg-2 col-md-3 col-auto">
304323
<a class="navbar-brand" href="#" v-on:click="reloadMessages">
305324
<img src="mailpit.svg" alt="Mailpit">
@@ -325,7 +344,10 @@ export default {
325344
<div class="col col-md-9 col-lg-5" v-if="!message && total">
326345
<form v-on:submit="doSearch">
327346
<div class="input-group">
328-
<input type="text" class="form-control" v-model.trim="search" placeholder="Search mailbox">
347+
<div class="d-flex bg-white border rounded-start flex-fill position-relative">
348+
<input type="text" class="form-control border-0" v-model.trim="search" placeholder="Search mailbox">
349+
<span class="btn btn-link position-absolute end-0 text-muted" v-if="search" v-on:click="resetSearch"><i class="bi bi-x-circle"></i></span>
350+
</div>
329351
<button class="btn btn-outline-secondary" type="submit"><i class="bi bi-search"></i></button>
330352
</div>
331353
</form>
@@ -358,15 +380,15 @@ export default {
358380
<div class="row flex-fill" style="min-height:0">
359381
<div class="d-none d-md-block col-lg-2 col-md-3 mh-100 position-relative" style="overflow-y: auto;">
360382
<ul class="list-unstyled mt-3 mb-5">
361-
<li v-if="isConnected" title="Messages will auto-load">
383+
<li v-if="isConnected" title="Messages will auto-load" class="mb-2">
362384
<i class="bi bi-power text-success"></i>
363385
Connected
364386
</li>
365-
<li v-else title="Messages will auto-load">
387+
<li v-else title="You need to manually refresh your mailbox" class="mb-3">
366388
<i class="bi bi-power text-danger"></i>
367389
Disconnected
368390
</li>
369-
<li class="mt-3">
391+
<li class="mb-5">
370392
<a class="position-relative ps-0" href="#" v-on:click="reloadMessages">
371393
<i class="bi bi-envelope me-1" v-if="isConnected"></i>
372394
<i class="bi bi-arrow-clockwise me-1" v-else></i>
@@ -376,20 +398,26 @@ export default {
376398
</span>
377399
</a>
378400
</li>
379-
<li class="mt-3">
380-
<a v-if="total" href="#" data-bs-toggle="modal" data-bs-target="#DeleteAllModal">
401+
<li class="my-3" v-if="unread">
402+
<a href="#" data-bs-toggle="modal" data-bs-target="#MarkAllReadModal">
403+
<i class="bi bi-check2-square"></i>
404+
Mark all read
405+
</a>
406+
</li>
407+
<li class="my-3" v-if="total">
408+
<a href="#" data-bs-toggle="modal" data-bs-target="#DeleteAllModal">
381409
<i class="bi bi-trash-fill me-1 text-danger"></i>
382410
Delete all
383411
</a>
384412
</li>
385-
<li class="mt-3" v-if="notificationsSupported && !notificationsEnabled">
413+
<li class="my-3" v-if="notificationsSupported && !notificationsEnabled">
386414
<a href="#" data-bs-toggle="modal" data-bs-target="#EnableNotificationsModal" title="Enable browser notifications">
387415
<i class="bi bi-bell"></i>
388416
Enable alerts
389417
</a>
390418
</li>
391419
<li class="mt-5 position-fixed bottom-0">
392-
<a href="https://github.com/axllent/mailpit" target="_blank" class="text-muted w-100 d-block bg-white py-2">
420+
<a href="https://github.com/axllent/mailpit" target="_blank" class="text-muted w-100 d-block bg-white my-3">
393421
<i class="bi bi-github"></i>
394422
GitHub
395423
</a>
@@ -433,7 +461,14 @@ export default {
433461
</div>
434462
</a>
435463
</div>
436-
<div v-else class="text-muted py-3">No messages</div>
464+
<div v-else class="text-muted my-3">
465+
<span v-if="searching">
466+
No results matching your search
467+
</span>
468+
<span v-else>
469+
There are no emails in your mailbox
470+
</span>
471+
</div>
437472
</div>
438473

439474
<Message v-if="message" :message="message" :mailbox="mailbox"></Message>
@@ -466,6 +501,25 @@ export default {
466501
</div>
467502
</div>
468503

504+
<!-- Modal -->
505+
<div class="modal fade" id="MarkAllReadModal" tabindex="-1" aria-labelledby="MarkAllReadModalLabel" aria-hidden="true">
506+
<div class="modal-dialog">
507+
<div class="modal-content">
508+
<div class="modal-header">
509+
<h5 class="modal-title" id="MarkAllReadModalLabel">Mark all messages as read?</h5>
510+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
511+
</div>
512+
<div class="modal-body">
513+
This will mark {{ formatNumber(unread) }} message<span v-if="unread > 1">s</span> as read.
514+
</div>
515+
<div class="modal-footer">
516+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
517+
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="markAllRead">Confirm</button>
518+
</div>
519+
</div>
520+
</div>
521+
</div>
522+
469523
<!-- Modal -->
470524
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel" aria-hidden="true">
471525
<div class="modal-dialog modal-lg">

server/ui-src/assets/styles.scss

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
// @import "../../../node_modules/bootstrap-icons"; ///scss/root";
2-
31
@import "bootstrap";
42

53
[v-cloak] {
64
display: none !important;
75
}
86

9-
.navbar-brand {
10-
color: #2d4a5d;
7+
.navbar {
8+
z-index: 99;
9+
10+
.navbar-brand {
11+
color: #2d4a5d;
1112

12-
img {
13-
width: 40px;
13+
img {
14+
width: 40px;
15+
}
1416
}
1517
}
1618

@@ -25,7 +27,6 @@
2527
}
2628

2729
.message.read:not(.active) {
28-
// background: $gray-100;
2930
color: $gray-500;
3031
}
3132

@@ -52,3 +53,7 @@
5253
vertical-align: top;
5354
}
5455
}
56+
57+
.list-group-item:first-child {
58+
border-top: 0;
59+
}

server/ui-src/mixins.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const commonMixins = {
2727
// Ajax error message
2828
handleError: function (error) {
2929
// handle error
30-
if (error.response) {
30+
if (error.response && error.response.data) {
3131
// The request was made and the server responded with a status code
3232
// that falls out of the range of 2xx
3333
if (error.response.data.Error) {

storage/database.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,3 +574,38 @@ func DeleteAllMessages(mailbox string) error {
574574

575575
return nil
576576
}
577+
578+
// MarkAllRead will mark every unread message in a mailbox as read
579+
func MarkAllRead(mailbox string) error {
580+
mailbox = sanitizeMailboxName(mailbox)
581+
582+
totalStart := time.Now()
583+
584+
q, err := db.FindAll(clover.NewQuery(mailbox).
585+
Where(clover.Field("Read").IsFalse()))
586+
if err != nil {
587+
return err
588+
}
589+
590+
total := len(q)
591+
592+
updates := make(map[string]interface{})
593+
updates["Read"] = true
594+
595+
for _, m := range q {
596+
if err := db.UpdateById(mailbox, m.ObjectId(), updates); err != nil {
597+
logger.Log().Error(err)
598+
return err
599+
}
600+
}
601+
602+
if err := statsRefresh(mailbox); err != nil {
603+
return err
604+
}
605+
606+
elapsed := time.Since(totalStart)
607+
608+
logger.Log().Debugf("[db] marked %d messages in %s as read in %s", total, mailbox, elapsed)
609+
610+
return nil
611+
}

0 commit comments

Comments
 (0)