Browse Source

Update layouting

- Fix and order layouting
- Fix few JS issues related to folder
- Limit number of email to 50
- Add paging support
Alexey Edelev 5 years ago
parent
commit
e2ff62bc63

+ 75 - 25
common/gostfix.pb.go

@@ -397,6 +397,53 @@ func (m *Frame) GetLimit() int32 {
 	return 0
 }
 
+type Folder struct {
+	Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Custom               bool     `protobuf:"varint,2,opt,name=custom,proto3" json:"custom,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-" bson:"-"`
+	XXX_unrecognized     []byte   `json:"-" bson:"-"`
+	XXX_sizecache        int32    `json:"-" bson:"-"`
+}
+
+func (m *Folder) Reset()         { *m = Folder{} }
+func (m *Folder) String() string { return proto.CompactTextString(m) }
+func (*Folder) ProtoMessage()    {}
+func (*Folder) Descriptor() ([]byte, []int) {
+	return fileDescriptor_0ab36b6dc6e1dcaa, []int{7}
+}
+
+func (m *Folder) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Folder.Unmarshal(m, b)
+}
+func (m *Folder) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Folder.Marshal(b, m, deterministic)
+}
+func (m *Folder) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Folder.Merge(m, src)
+}
+func (m *Folder) XXX_Size() int {
+	return xxx_messageInfo_Folder.Size(m)
+}
+func (m *Folder) XXX_DiscardUnknown() {
+	xxx_messageInfo_Folder.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Folder proto.InternalMessageInfo
+
+func (m *Folder) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Folder) GetCustom() bool {
+	if m != nil {
+		return m.Custom
+	}
+	return false
+}
+
 func init() {
 	proto.RegisterType((*MailBody)(nil), "common.MailBody")
 	proto.RegisterType((*MailHeader)(nil), "common.MailHeader")
@@ -405,6 +452,7 @@ func init() {
 	proto.RegisterType((*AttachmentHeader)(nil), "common.AttachmentHeader")
 	proto.RegisterType((*UserInfo)(nil), "common.UserInfo")
 	proto.RegisterType((*Frame)(nil), "common.Frame")
+	proto.RegisterType((*Folder)(nil), "common.Folder")
 }
 
 func init() {
@@ -412,29 +460,31 @@ func init() {
 }
 
 var fileDescriptor_0ab36b6dc6e1dcaa = []byte{
-	// 380 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x3d, 0x6f, 0xdb, 0x30,
-	0x10, 0x85, 0x3e, 0xac, 0xda, 0xa7, 0xb6, 0xb0, 0x0f, 0x1d, 0x88, 0xa2, 0x83, 0x20, 0x74, 0x30,
-	0x3a, 0x18, 0xad, 0xbb, 0x79, 0x6b, 0x87, 0xa2, 0x1d, 0x9a, 0x81, 0x70, 0x80, 0x8c, 0xa1, 0x28,
-	0x2a, 0x66, 0x22, 0x89, 0x82, 0x44, 0x03, 0xf6, 0x96, 0x9f, 0x1e, 0xf0, 0x24, 0xd9, 0x8e, 0x81,
-	0x6c, 0xf7, 0xee, 0x9e, 0xee, 0xbd, 0x7b, 0x22, 0x7c, 0x78, 0x30, 0x9d, 0x2d, 0xf4, 0x61, 0xd5,
-	0xb4, 0xc6, 0x1a, 0x8c, 0xa4, 0xa9, 0x2a, 0x53, 0xa7, 0xcf, 0x1e, 0x4c, 0xff, 0x0b, 0x5d, 0xfe,
-	0x36, 0xf9, 0x11, 0xbf, 0xc0, 0xac, 0x29, 0x85, 0xae, 0xb7, 0xea, 0x60, 0x99, 0x97, 0x78, 0xcb,
-	0x19, 0x3f, 0x37, 0xf0, 0x33, 0x4c, 0x5b, 0x2d, 0x77, 0x34, 0xf4, 0x69, 0x78, 0xc2, 0xb8, 0x81,
-	0x58, 0x58, 0x2b, 0xe4, 0xae, 0x52, 0xb5, 0xed, 0x58, 0x90, 0x04, 0xcb, 0x78, 0xcd, 0x56, 0xbd,
-	0xc8, 0xea, 0xd7, 0x69, 0xf4, 0x57, 0x89, 0x5c, 0xb5, 0xfc, 0x92, 0xec, 0x2c, 0x80, 0xb3, 0xd0,
-	0xcf, 0x10, 0x21, 0x2c, 0x5a, 0x53, 0x0d, 0xfa, 0x54, 0xe3, 0x47, 0xf0, 0xad, 0x19, 0x44, 0x7d,
-	0x6b, 0x1c, 0x96, 0x92, 0x05, 0x3d, 0x96, 0x12, 0xe7, 0x10, 0x64, 0x52, 0xb2, 0x90, 0x1a, 0xae,
-	0x74, 0x5b, 0x72, 0x61, 0x15, 0x9b, 0x24, 0xde, 0x12, 0x39, 0xd5, 0xc8, 0xe0, 0x5d, 0xb7, 0xcf,
-	0x1e, 0x95, 0xb4, 0x2c, 0x22, 0xe6, 0x08, 0xd3, 0x3b, 0x08, 0x9d, 0x03, 0xfc, 0x06, 0xd1, 0x8e,
-	0x5c, 0x90, 0x7a, 0xbc, 0xc6, 0xf1, 0x82, 0xb3, 0x3f, 0x3e, 0x30, 0xf0, 0x2b, 0x84, 0x99, 0xc9,
-	0x8f, 0xe4, 0x2a, 0x5e, 0xcf, 0x2f, 0x99, 0x2e, 0x4c, 0x4e, 0xd3, 0x94, 0x03, 0x9c, 0xaf, 0xc7,
-	0xef, 0x57, 0xfb, 0xdf, 0x4e, 0x68, 0x54, 0xe9, 0xef, 0x10, 0xa4, 0xf2, 0x9e, 0xee, 0x10, 0xe9,
-	0x3d, 0xcc, 0xaf, 0xf9, 0x2e, 0x11, 0x9d, 0x0f, 0x99, 0xf9, 0x3a, 0x77, 0x3f, 0xab, 0xd0, 0xa5,
-	0xba, 0x11, 0x95, 0x1a, 0x7f, 0xd6, 0x88, 0x31, 0x81, 0x58, 0x9a, 0xda, 0xaa, 0xda, 0x6e, 0x8f,
-	0x8d, 0x1a, 0x62, 0xbc, 0x6c, 0xa5, 0x1b, 0x98, 0xde, 0x76, 0xaa, 0xfd, 0x57, 0x17, 0xc6, 0x39,
-	0xd8, 0x77, 0x83, 0xe3, 0x19, 0xa7, 0x9a, 0xb6, 0xef, 0xcb, 0xf2, 0xd5, 0xf6, 0x01, 0xa7, 0x3f,
-	0x60, 0xf2, 0xa7, 0x75, 0x32, 0x08, 0x61, 0xf7, 0xa4, 0x1b, 0xfa, 0x70, 0xc1, 0xa9, 0xc6, 0x4f,
-	0x30, 0x29, 0x75, 0xa5, 0xfb, 0x07, 0xb4, 0xe0, 0x3d, 0xc8, 0x22, 0x7a, 0x93, 0x3f, 0x5f, 0x02,
-	0x00, 0x00, 0xff, 0xff, 0x17, 0x2e, 0x20, 0xce, 0xa4, 0x02, 0x00, 0x00,
+	// 403 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x52, 0x4d, 0x8f, 0xd3, 0x30,
+	0x10, 0x55, 0x3e, 0x1a, 0xd2, 0x09, 0xa0, 0xee, 0x08, 0x21, 0x0b, 0x71, 0x88, 0x22, 0x0e, 0x15,
+	0x87, 0x0a, 0x0a, 0xa7, 0xbd, 0xc1, 0x61, 0x05, 0x07, 0x38, 0x58, 0x8b, 0xc4, 0x11, 0xc7, 0x71,
+	0xa8, 0x21, 0x89, 0xa3, 0xc4, 0x91, 0xb6, 0x37, 0x7e, 0x3a, 0xf2, 0xc4, 0x69, 0x4b, 0x25, 0x6e,
+	0xef, 0x79, 0xc6, 0xf3, 0xde, 0x3c, 0x1b, 0x9e, 0xfc, 0x34, 0xa3, 0xad, 0xf5, 0xc3, 0xae, 0x1f,
+	0x8c, 0x35, 0x98, 0x48, 0xd3, 0xb6, 0xa6, 0x2b, 0xfe, 0x04, 0x90, 0x7e, 0x11, 0xba, 0xf9, 0x68,
+	0xaa, 0x23, 0xbe, 0x84, 0x75, 0xdf, 0x08, 0xdd, 0xdd, 0xab, 0x07, 0xcb, 0x82, 0x3c, 0xd8, 0xae,
+	0xf9, 0xf9, 0x00, 0x5f, 0x40, 0x3a, 0x68, 0x79, 0xa0, 0x62, 0x48, 0xc5, 0x13, 0xc7, 0x5b, 0xc8,
+	0x84, 0xb5, 0x42, 0x1e, 0x5a, 0xd5, 0xd9, 0x91, 0x45, 0x79, 0xb4, 0xcd, 0xf6, 0x6c, 0x37, 0x8b,
+	0xec, 0x3e, 0x9c, 0x4a, 0x9f, 0x94, 0xa8, 0xd4, 0xc0, 0x2f, 0x9b, 0x9d, 0x05, 0x70, 0x16, 0xe6,
+	0x1a, 0x22, 0xc4, 0xf5, 0x60, 0x5a, 0xaf, 0x4f, 0x18, 0x9f, 0x42, 0x68, 0x8d, 0x17, 0x0d, 0xad,
+	0x71, 0x5c, 0x4a, 0x16, 0xcd, 0x5c, 0x4a, 0xdc, 0x40, 0x54, 0x4a, 0xc9, 0x62, 0x3a, 0x70, 0xd0,
+	0x4d, 0xa9, 0x84, 0x55, 0x6c, 0x95, 0x07, 0x5b, 0xe4, 0x84, 0x91, 0xc1, 0xa3, 0x71, 0x2a, 0x7f,
+	0x29, 0x69, 0x59, 0x42, 0x9d, 0x0b, 0x2d, 0xbe, 0x43, 0xec, 0x1c, 0xe0, 0x6b, 0x48, 0x0e, 0xe4,
+	0x82, 0xd4, 0xb3, 0x3d, 0x2e, 0x1b, 0x9c, 0xfd, 0x71, 0xdf, 0x81, 0xaf, 0x20, 0x2e, 0x4d, 0x75,
+	0x24, 0x57, 0xd9, 0x7e, 0x73, 0xd9, 0xe9, 0xc2, 0xe4, 0x54, 0x2d, 0x38, 0xc0, 0x79, 0x7b, 0x7c,
+	0x73, 0x35, 0xff, 0xff, 0x09, 0x2d, 0x2a, 0xf3, 0x1e, 0x82, 0x54, 0x1e, 0xd3, 0x1e, 0xa2, 0xf8,
+	0x01, 0x9b, 0xeb, 0x7e, 0x97, 0x88, 0xae, 0x7c, 0x66, 0xa1, 0xae, 0xdc, 0x63, 0xd5, 0xba, 0x51,
+	0x5f, 0x45, 0xab, 0x96, 0xc7, 0x5a, 0x38, 0xe6, 0x90, 0x49, 0xd3, 0x59, 0xd5, 0xd9, 0xfb, 0x63,
+	0xaf, 0x7c, 0x8c, 0x97, 0x47, 0xc5, 0x2d, 0xa4, 0xdf, 0x46, 0x35, 0x7c, 0xee, 0x6a, 0xe3, 0x1c,
+	0x4c, 0xa3, 0x77, 0xbc, 0xe6, 0x84, 0x69, 0xfa, 0xd4, 0x34, 0xff, 0x4c, 0xf7, 0xbc, 0x78, 0x0b,
+	0xab, 0xbb, 0xc1, 0xc9, 0x20, 0xc4, 0xe3, 0x6f, 0xdd, 0xd3, 0xc5, 0x1b, 0x4e, 0x18, 0x9f, 0xc1,
+	0xaa, 0xd1, 0xad, 0x9e, 0x3f, 0xd0, 0x0d, 0x9f, 0x49, 0xf1, 0x1e, 0x92, 0x3b, 0xd3, 0xf8, 0x75,
+	0x3b, 0x37, 0xd4, 0x8b, 0x39, 0x8c, 0xcf, 0x21, 0x91, 0xd3, 0x68, 0x4d, 0x4b, 0x97, 0x52, 0xee,
+	0x59, 0x99, 0xd0, 0x4f, 0x7e, 0xf7, 0x37, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x88, 0x74, 0xc2, 0xda,
+	0x02, 0x00, 0x00,
 }

+ 5 - 0
common/gostfix.proto

@@ -41,4 +41,9 @@ message UserInfo {
 message Frame {
 	sint32 skip = 1;
 	sint32 limit = 2;
+}
+
+message Folder {
+	string name = 1;
+	bool custom = 2;
 }

+ 13 - 2
db/db.go

@@ -289,7 +289,7 @@ func (s *Storage) MailList(user, email, folder string, frame common.Frame) ([]*c
 	mailsCollection := s.db.Collection(qualifiedMailCollection(user))
 
 	request := bson.A{
-		bson.M{"$match": bson.M{"email": email}},
+		bson.M{"$match": bson.M{"email": email, "folder": folder}},
 		bson.M{"$sort": bson.M{"mail.header.date": 1}},
 	}
 
@@ -297,7 +297,9 @@ func (s *Storage) MailList(user, email, folder string, frame common.Frame) ([]*c
 		request = append(request, bson.M{"$skip": frame.Skip})
 	}
 
+	fmt.Printf("Trying limit number of mails: %v\n", frame)
 	if frame.Limit > 0 {
+		fmt.Printf("Limit number of mails: %v\n", frame)
 		request = append(request, bson.M{"$limit": frame.Limit})
 	}
 
@@ -329,7 +331,7 @@ func (s *Storage) GetUserInfo(user string) (*common.UserInfo, error) {
 	return result, err
 }
 
-func (s *Storage) GetEmailStats(user string, email string) (unread, total int, err error) {
+func (s *Storage) GetEmailStats(user string, email string, folder string) (unread, total int, err error) {
 	mailsCollection := s.db.Collection(qualifiedMailCollection(user))
 	result := &struct {
 		Total  int
@@ -418,3 +420,12 @@ func (s *Storage) GetAllEmails() (emails []string, err error) {
 	}
 	return nil, err
 }
+
+func (s *Storage) GetFolders(email string) (folders []*common.Folder) {
+	folders = []*common.Folder{
+		&common.Folder{Name: "Inbox", Custom: false},
+		&common.Folder{Name: "Trash", Custom: false},
+		&common.Folder{Name: "Spam", Custom: false},
+	}
+	return
+}

+ 17 - 3
scanner/parser.go

@@ -29,6 +29,7 @@ import (
 	"bufio"
 	"bytes"
 	"encoding/hex"
+	"errors"
 	"log"
 	"os"
 	"strings"
@@ -156,12 +157,12 @@ func (pd *parseData) parseHeader(headerRaw string) {
 			pd.previousHeader = &pd.email.Header.Subject
 		case "date":
 			pd.previousHeader = nil
-			time, err := time.Parse(time.RFC1123Z, strings.Trim(capture[2], " \t"))
+			unixTime, err := parseDate(strings.Trim(capture[2], " \t"))
 			if err == nil {
-				pd.email.Header.Date = time.Unix()
+				pd.email.Header.Date = unixTime
 				pd.mandatoryHeaders |= DateHeaderMask
 			} else {
-				log.Printf("Invalid date format %s, %s", strings.Trim(capture[2], " \t"), err)
+				log.Printf("Unable to parse message: %s\n", err)
 			}
 		case "content-type":
 			pd.previousHeader = &pd.bodyContentType
@@ -212,3 +213,16 @@ func (pd *parseData) parseBody() {
 		attachmentFile.Write(attachment.Content)
 	}
 }
+
+func parseDate(stringDate string) (int64, error) {
+	formatsToTest := []string{"Mon, _2 Jan 2006 15:04:05 -0700", time.RFC1123Z, time.RFC1123, time.UnixDate}
+	var err error
+	for _, format := range formatsToTest {
+		dateTime, err := time.Parse(format, stringDate)
+		if err == nil {
+			return dateTime.Unix(), nil
+		}
+	}
+
+	return 0, errors.New("Invalid date format " + stringDate + " , " + err.Error())
+}

+ 39 - 30
web/css/controls.css

@@ -10,8 +10,8 @@
     outline: none;
     box-shadow: 0 1pt 4pt rgba(0, 0, 0, .6);
 
-    background-color: #41cd52;
-    color: #ffffff;
+    background-color: var(--primary-color);
+    color: var(--secondary-text-color);
 
     font-size: 12pt;
     font-weight: bold;
@@ -20,7 +20,7 @@
 }
 
 .btn:hover, .btn:focus {
-    background-color: #3ab849;
+    background-color: var(--primary-dark-color);
     cursor: pointer;
 }
 
@@ -68,29 +68,22 @@
     display: block;
 }
 
-.scrollable {
-    width: 100%;
-    height: 100%;
-    overflow-x: hidden;
-    overflow-y: auto;
-}
-
 .fadeOut {
-    background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 1) 100%); /* FF3.6+ */
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0, 0, 0,0)), color-stop(100%,rgba(255, 255, 255, 1))); /* Chrome,Safari4+ */
-    background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 1) 100%); /* Chrome10+,Safari5.1+ */
-    background: -o-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 1) 100%); /* Opera 11.10+ */
-    background: -ms-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 1) 100%); /* IE10+ */
-    background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(255, 255, 255, 1) 100%); /* W3C */
+    background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) 0%, var(--bg-color) 100%); /* FF3.6+ */
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0, 0, 0,0)), color-stop(100%,var(--bg-color))); /* Chrome,Safari4+ */
+    background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, var(--bg-color) 100%); /* Chrome10+,Safari5.1+ */
+    background: -o-linear-gradient(top, rgba(0, 0, 0, 0) 0%, var(--bg-color) 100%); /* Opera 11.10+ */
+    background: -ms-linear-gradient(top, rgba(0, 0, 0, 0) 0%, var(--bg-color) 100%); /* IE10+ */
+    background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--bg-color) 100%); /* W3C */
 }
 
 .fadeIn {
-    background: -moz-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%); /* FF3.6+ */
-    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255, 255, 255, 1)),  color-stop(100%,rgba(0, 0, 0, 0))); /* Chrome,Safari4+ */
-    background: -webkit-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%); /* Chrome10+,Safari5.1+ */
-    background: -o-linear-gradient(top, rgba(255, 255, 255, 1) 0%, rgba(0, 0, 0, 0) 100%); /* Opera 11.10+ */
-    background: -ms-linear-gradient(top, rgba(255, 255, 255, 1) 0%,  rgba(0, 0, 0, 0) 100%); /* IE10+ */
-    background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%,  rgba(0, 0, 0, 0) 100%); /* W3C */
+    background: -moz-linear-gradient(top, var(--bg-color) 0%, rgba(0, 0, 0, 0) 100%); /* FF3.6+ */
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, var(--bg-color)),  color-stop(100%,rgba(0, 0, 0, 0))); /* Chrome,Safari4+ */
+    background: -webkit-linear-gradient(top, var(--bg-color) 0%, rgba(0, 0, 0, 0) 100%); /* Chrome10+,Safari5.1+ */
+    background: -o-linear-gradient(top, var(--bg-color) 0%, rgba(0, 0, 0, 0) 100%); /* Opera 11.10+ */
+    background: -ms-linear-gradient(top, var(--bg-color) 0%,  rgba(0, 0, 0, 0) 100%); /* IE10+ */
+    background: linear-gradient(to bottom, var(--bg-color) 0%,  rgba(0, 0, 0, 0) 100%); /* W3C */
 }
 
 /*Input fields*/
@@ -106,7 +99,7 @@
     display: block;
     width: 200pt;
     border: none;
-    border-bottom: 1pt solid #3ab849;
+    border-bottom: 1pt solid var(--primary-color);
 }
 
 .inpt input:invalid {
@@ -121,7 +114,7 @@
 }
 
 .inpt label {
-    color: #000000;
+    color: var(--primary-text-color);
     font-size: 18pt;
     font-weight: normal;
     position: absolute;
@@ -136,13 +129,13 @@
 .inpt input:focus ~ label {
     top: -20pt;
     font-size: 12pt;
-    color: #000000;
+    color: var(--primary-text-color);
 }
 
 .inpt input:valid ~ label {
     top: -20pt;
     font-size: 12pt;
-    color: #000000;
+    color: var(--primary-text-color);
 }
 .inpt .bar {
     position: relative;
@@ -156,7 +149,7 @@
     width: 0;
     bottom: 0;
     position: absolute;
-    background: #3ab849;
+    background: var(--primary-color);
     transition: 0.2s ease all;
     -moz-transition: 0.2s ease all;
     -webkit-transition: 0.2s ease all;
@@ -191,24 +184,40 @@
 }
 
 @-webkit-keyframes inputHighlighter {
-    from { background:#ebffee; }
+    from { background: var(--secondary-color); }
     to { width:0; background:transparent; }
 }
 
 @-moz-keyframes inputHighlighter {
-    from { background:#ebffee; }
+    from { background: var(--secondary-color); }
     to { width:0; background:transparent; }
 }
 
 @keyframes inputHighlighter {
-    from { background:#ebffee; }
+    from { background: var(--secondary-color); }
     to { width:0; background:transparent; }
 }
 
 .folderBtn {
+    padding-left: 5pt;
+    padding-top: 5pt;
+    padding-bottom: 5pt;
+    width: 100%;
+    border-bottom-right-radius: var(--default-radius);
+    border-bottom-left-radius: var(--default-radius);
+    background-color: var(--secondary-color);
+}
 
+.folderBtn:hover {
+    cursor: pointer;
+    background-color: var(--secondary-dark-color);
 }
 
 .iconBtn:hover, .iconBtn:focus {
     cursor: pointer;
+}
+
+.iconBtn {
+    width: 20pt;
+    min-width: 20pt;
 }

+ 114 - 49
web/css/index.css

@@ -1,78 +1,143 @@
 html, body {
-    height: 100%;
-    margin: 0px;
-}
-
-table {
-    border-collapse: collapse;
-    border-spacing: 0;
-}
-
-.main {
-    position: absolute;
     top: 0;
     left: 0;
     right: 0;
-    bottom: 0;
-    margin-bottom: 30pt!important;
+    left: 0;
+    margin: 0;
 }
 
-.contentBox {
+#main {
     position: absolute;
-    top: 40pt;
+    top: 0;
     left: 0;
     right: 0;
     bottom: 0;
+    margin: 0;
 
-    margin-left: 150pt!important;
-    margin-top: 5pt!important;
-    margin-right: 15pt!important;
+    padding: 5pt;
 
-    padding: 5pt
+    display: flex;
+    flex-direction: column;
 }
 
-.emailDetails {
-    background-color: white;
-    margin-left: 158pt!important;
-    margin-top: 13pt!important;
-    margin-bottom: -8pt!important;
-    margin-right: 8pt!important;
-    padding: 10pt;
+#statusLine {
+    flex: 0 1 auto;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    padding: var(--base-text-padding);
 }
 
-.foldersBox {
-    position: absolute;
-    top :0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-
-    margin-left: 2pt!important;
-    margin-top: 5pt!important;
-    margin-right: 5pt!important;
-    margin-bottom: 30pt!important;
+#contentBox {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+    height: 100%;
+}
 
-    overflow-y: auto;
+#foldersBox {
+    display: block;
     width: 150pt;
+    max-width: 150pt;
+    min-width: 150pt;
+    height: 100%;
+    max-height: 100%;
+    background-color: honeydew;
 }
 
-#statusLine {
+#mailInnerBox {
+    position: relative;
+    max-width: 100%;
+    max-height: 100%;
+    width: 100%;
+    height: 100%;
+}
+
+#mailList {
     position: absolute;
-    top: 0;
-    font-size: 16pt;
-    left: 150pt;
-    height: 40pt;
-    padding: 5pt
+    top: 5pt;
+    bottom: 5pt;
+    left: 5pt;
+    right: 5pt;
+    overflow-y: auto;
+    background-color: var(--bg-color);
 }
 
-.copyrights {
+#mailDetails {
     position: absolute;
-    right: 0;
+    top: 0;
     bottom: 0;
-    margin-right: 5pt!important;
-    margin-bottom: 5pt!important;
+    left: 0;
+    right: 0;
+
+    overflow: hidden;
+    display: none;
+    background-color: var(--bg-color);
+    padding: 5pt;
+}
+
+.mailHeader {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+}
+
+.mailFrom {
+    display: inline-block;
+    flex: 0 1 auto;
+    min-width: 250pt;
+    max-width: 250pt;
+    width: 250pt;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    padding: var(--base-text-padding);
+}
+
+.mailSubject {
+    display: block;
+    flex: 1 1 auto;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
-    max-width: 80%;
+    padding: var(--base-text-padding);
+}
+
+.mailDate {
+    display: block;
+    flex: 0 1 auto;
+    width: 100pt;
+    min-width: 100pt;
+    max-width: 100pt;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    padding: var(--base-text-padding);
+}
+
+#copyrightBox {
+    flex: 0 1 auto;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    word-wrap: none;
+    padding: var(--base-text-padding);
+    text-align: right;
+}
+
+.verticalPaddingBox {
+    display: block;
+    flex: 1 1 auto;
+    top: 0;
+    bottom: 0;
+
+    padding: 5pt;
+}
+
+.horizontalPaddingBox {
+    display: block;
+    flex: 1 1 auto;
+    left: 0;
+    right: 0;
+
+    padding: 5pt;
 }

+ 42 - 58
web/css/styles.css

@@ -2,84 +2,53 @@
     font-family: 'Titillium Web';
 }
 
-body {
-    background-color: white;
-}
-
-table.mailList {
-    width: 100%;
-    table-layout: fixed;
-}
+:root {
+    --primary-color: #41cd52;
+    --primary-dark-color: #3ab849;
+    --secondary-color: #ebffee;
+    --secondary-dark-color: #b8d4bc;
+    --bg-color: #ffffff;
+    --primary-text-color: #000000;
+    --primary-text-dark-color: #000000;
+    --secondary-text-color: #ffffff;
+    --secondary-text-dark-color: #ffffff;
+    --default-radius: 3pt;
 
-.mailList td {
-    padding: 10pt;
+    --base-text-padding: 10pt;
 }
 
-.mailList tr:last-child {
-    border-bottom: 0;
+body {
+    background-color: var(--bg-color);
 }
 
-.mailList tr {
-    background-color: #ffffff;
-    border-bottom: 1px solid #41cd52;
+.mailHeader {
+    background-color: var(--bg-color);
+    border-bottom: 1px solid var(--primary-color);
     cursor: pointer;
     transition: background-color .3s;
 }
 
-.mailList tr:hover {
-    background-color: #ebffee;
-}
-
-td.fromCol {
-    width: 300pt;
-}
-
-td.subjCol {
-    width: 100%;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-}
-
-td.dateCol {
-    width: 100pt;
-    overflow: hidden;
-    text-overflow:ellipsis;
-    white-space: nowrap;
-}
-
-tr.read {
-    font-weight: normal;
-}
-
-tr.unread {
-    font-weight: bold;
+.mailHeader:hover {
+    background-color: var(--secondary-color);
 }
 
 .materialLevel1 {
+    overflow: hidden;
     box-shadow: 0 1pt 3pt rgba(0, 0, 0, .6);
-    margin-top: 1pt;
-    margin-bottom: 1pt;
-    margin-left: 4pt;
-    margin-right: 4pt;
-    border-radius: 3pt;
+    border-radius: var(--default-radius);
 }
 
 .materialLevel2 {
     box-shadow: 0 2pt 6pt rgba(0, 0, 0, .6);
-    margin-top: 1pt;
-    margin-bottom: 1pt;
-    margin-left: 5pt;
-    margin-right: 5pt;
-    border-radius: 3pt;
+    border-radius: var(--default-radius);
 }
 
 .copyrights {
-    color: #b8d4bc
+    color: var(--secondary-dark-color);
 }
 
 .primaryText {
-    color: black;
+    color: var(--primary-text-color);
     font-weight: bold;
     font-size: 12pt;
 }
@@ -90,9 +59,24 @@ tr.unread {
     font-size: 10pt;
 }
 
-.mailBody {
-    border-radius: 3pt;
+#mailBody {
+    border-radius: var(--default-radius);
     border-width: 1pt;
     border-style: solid;
-    border-color: #41cd52;
+    border-color: var(--primary-color);
+    padding: 5pt;
+}
+
+.read {
+    font-weight: normal;
+}
+
+.unread {
+    font-weight: bold;
+}
+
+.iconBtn {
+}
+
+.iconBtn:hover {
 }

+ 95 - 40
web/js/index.js

@@ -23,19 +23,20 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
-var detailsUrl = "details/"
+var currentFolder = ""
+var currentPage = 0
+var currentMail = ""
 var updateTimerId = null
-var updateInterval = 5000
+var updateInterval = 50000
 var mailbox = ""
-var mailboxRegex = /^(\/m\d+)/g
+var pageMax = 10
+const mailboxRegex = /^(\/m\d+)/g
 
 $(document).ready(function(){
     $.ajaxSetup({
         global: false,
         type: "POST"
     })
-    $(window).bind('hashchange', requestDetails)
-    requestDetails()
 
     urlPaths = mailboxRegex.exec($(location).attr('pathname'))
     if (urlPaths != null && urlPaths.length === 2) {
@@ -44,42 +45,78 @@ $(document).ready(function(){
         mailbox = ""
     }
 
+    $(window).bind('hashchange', onHashChanged)
+    onHashChanged()
     loadFolders()
     loadStatusLine()
-    updateMailList()
-    if(mailbox != "") {
+
+    if (mailbox != "") {
         clearInterval(updateTimerId)
-        updateTimerId = setInterval(updateMailList, updateInterval)
+        updateTimerId = setInterval(updateMailList, updateInterval, currentFolder+currentPage)
     }
 })
 
 function openEmail(id) {
-    window.location.hash = detailsUrl + id
+    window.location.hash = currentFolder + currentPage + "/" + id
+}
+
+function openFolder(folder) {
+    window.location.hash = folder
 }
 
-function requestDetails() {
+function onHashChanged() {
     var hashLocation = window.location.hash
-    if (hashLocation.startsWith("#" + detailsUrl)) {
-        var mailId = hashLocation.replace(/^#details\//, "")
-        if (mailId != "") {
-            $.ajax({
-                url: "/mail",
-                data: {mailId: mailId},
-                success: function(result) {
-                    $("#mail"+mailId).removeClass("unread")
-                    $("#mail"+mailId).addClass("read")
-                    $("#details").html(result);
-                    setDetailsVisible(true);
-                },
-                error: function(jqXHR, textStatus, errorThrown) {
-                    $("#details").html(textStatus)
-                    setDetailsVisible(true)
-                }
-            })
+    if (hashLocation == "") {
+        setDetailsVisible(false)
+        openFolder("Inbox")
+        return
+    }
+
+    hashRegex = /^#([a-zA-Z]+)(\d*)\/?([A-Fa-f\d]*)/g
+    hashParts = hashRegex.exec(hashLocation)
+    console.log("Hash parts: " + hashParts)
+    page = 0
+    if (hashParts.length >= 3 && hashParts[2] != "") {
+        console.log("page found: " + hashParts[2])
+        page = hashParts[2]
+    }
+
+    if (hashParts.length >= 2 && (hashParts[1] != currentFolder || currentPage != page) && hashParts[1] != "") {
+
+        updateMailList(hashParts[1], page)
+    }
+
+    if (hashParts.length >= 4 && hashParts[3] != "") {
+        if (currentMail != hashParts[3]) {
+            requestMail(hashParts[3])
         }
     } else {
         setDetailsVisible(false)
     }
+
+
+}
+
+function requestMail(mailId) {
+    if (mailId != "") {
+        $.ajax({
+            url: "/mail",
+            data: {
+                mailId: mailId
+            },
+            success: function(result) {
+                currentMail = mailId
+                $("#mail"+mailId).removeClass("unread")
+                $("#mail"+mailId).addClass("read")
+                $("#mailDetails").html(result);
+                setDetailsVisible(true);
+            },
+            error: function(jqXHR, textStatus, errorThrown) {
+                $("#mailDetails").html(textStatus)
+                setDetailsVisible(true)
+            }
+        })
+    }
 }
 
 function loadFolders() {
@@ -100,7 +137,7 @@ function loadFolders() {
 }
 
 function closeDetails() {
-    window.location.hash = ""
+    window.location.hash = currentFolder + currentPage
 }
 
 function loadStatusLine() {
@@ -180,20 +217,21 @@ function removeMail(mailId) {
 
 function setDetailsVisible(visible) {
     if (visible) {
-        $("#details").show()
+        $("#mailDetails").show()
         $("#mailList").css({pointerEvents: "none"})
         clearInterval(updateTimerId)
     } else {
-        $("#details").hide()
-        $("#details").html("")
+        currentMail = ""
+        $("#mailDetails").hide()
+        $("#mailDetails").html("")
         $("#mailList").css({pointerEvents: "auto"})
-        updateTimerId = setInterval(updateMailList, updateInterval)
+        updateTimerId = setInterval(updateMailList, updateInterval, currentFolder+currentPage)
     }
 }
 
-function updateMailList() {
-    if (mailbox == "") {
-        if($("#mailList")) {
+function updateMailList(folder, page) {
+    if (mailbox == "" || folder == "") {
+        if ($("#mailList")) {
             $("#mailList").html("Unable to load message list")
         }
         return
@@ -201,16 +239,33 @@ function updateMailList() {
 
     $.ajax({
         url: mailbox + "/mailList",
+        data: {
+            folder: folder,
+            page: page
+        },
         success: function(result) {
-            if($("#mailList")) {
-                // console.log("result: " + result)
-                $("#mailList").html(result)
+            var data = jQuery.parseJSON(result)
+            var mailCount = data.total
+            if ($("#mailList")) {
+                $("#mailList").html(data.html)
             }
+            currentFolder = folder
+            currentPage = page
         },
         error: function(jqXHR, textStatus, errorThrown) {
-            if($("#mailList")) {
-                $("#mailList").html(textStatus)
+            if ($("#mailList")) {
+                $("#mailList").html("Unable to load message list")
             }
         }
     })
+}
+
+function nextPage() {
+    var newPage = currentPage > 0 ? currentPage - 1 : 0
+    window.location.hash = currentFolder + newPage
+}
+
+function prevPage() {
+    var newPage = currentPage < (pageMax - 1) ? currentPage + 1 : pageMax
+    window.location.hash = currentFolder + newPage
 }

+ 36 - 18
web/mailbox.go

@@ -30,12 +30,13 @@ import (
 	template "html/template"
 	"log"
 	"net/http"
+	"strconv"
 
 	common "git.semlanik.org/semlanik/gostfix/common"
 )
 
 func (s *Server) handleMailbox(w http.ResponseWriter, user, email string) {
-	mailList, err := s.storage.MailList(user, email, "Inbox", common.Frame{Skip: 0, Limit: 0})
+	mailList, err := s.storage.MailList(user, email, "Inbox", common.Frame{Skip: 0, Limit: 50})
 
 	if err != nil {
 		s.error(http.StatusInternalServerError, "Couldn't read email database", w)
@@ -79,26 +80,45 @@ func (s *Server) handleMailboxRequest(path, user string, mailbox int, w http.Res
 	case "statusLine":
 		s.handleStatusLine(w, user, emails[mailbox])
 	case "mailList":
-		s.handleMailList(w, user, emails[mailbox])
+		s.handleMailList(w, r, user, emails[mailbox])
 	default:
 		http.Redirect(w, r, "/m0", http.StatusTemporaryRedirect)
 	}
 }
 
 func (s *Server) handleFolders(w http.ResponseWriter, user, email string) {
-	folders := []string{"Inbox", "Trash"}
-	fmt.Fprintf(w, s.templater.ExecuteFolders(folders))
+	fmt.Fprintf(w, s.templater.ExecuteFolders(s.storage.GetFolders(email)))
 }
 
-func (s *Server) handleMailList(w http.ResponseWriter, user, email string) {
-	mailList, err := s.storage.MailList(user, email, "Inbox", common.Frame{Skip: 0, Limit: 0})
+func (s *Server) handleMailList(w http.ResponseWriter, r *http.Request, user, email string) {
+	folder := r.FormValue("folder")
+	page, err := strconv.Atoi(r.FormValue("page"))
+	if err != nil {
+		page = 0
+	}
+
+	folders := s.storage.GetFolders(email)
+	ok := false
+	for _, existFolder := range folders {
+		if folder == existFolder.Name {
+			ok = true
+			break
+		}
+	}
+
+	if !ok {
+		folder = "Inbox"
+	}
+	_, total, err := s.storage.GetEmailStats(user, email, folder)
+	mailList, err := s.storage.MailList(user, email, folder, common.Frame{Skip: int32(50 * page), Limit: 50})
 
 	if err != nil {
 		s.error(http.StatusInternalServerError, "Couldn't read email database", w)
 		return
 	}
 
-	fmt.Fprint(w, s.templater.ExecuteMailList(mailList))
+	out := fmt.Sprintf("{total: %d, html: \"%s\", }", total, s.templater.ExecuteMailList(mailList))
+	fmt.Fprint(w, out)
 }
 
 func (s *Server) handleStatusLine(w http.ResponseWriter, user, email string) {
@@ -108,19 +128,17 @@ func (s *Server) handleStatusLine(w http.ResponseWriter, user, email string) {
 		return
 	}
 
-	unread, total, err := s.storage.GetEmailStats(user, email)
-	if err != nil {
-		s.error(http.StatusInternalServerError, "Could not read user stats", w)
-		return
-	}
+	// unread, total, err := s.storage.GetEmailStats(user, email)
+	// if err != nil {
+	// 	s.error(http.StatusInternalServerError, "Could not read user stats", w)
+	// 	return
+	// }
 
 	fmt.Fprint(w, s.templater.ExecuteStatusLine(&struct {
-		Name   string
-		Unread int
-		Total  int
+		Name  string
+		Email string
 	}{
-		Name:   info.FullName,
-		Unread: unread,
-		Total:  total,
+		Name:  info.FullName,
+		Email: email,
 	}))
 }

+ 19 - 13
web/templates/details.html

@@ -1,17 +1,23 @@
-<div style="height: 100%; width: 100%; display:flex; flex-flow: column;">
-    <div style="width: 100%; display: flex; flex-flow: row;">
-        <div style="overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; flex-grow: 1;">
-            <span class="primaryText">From: {{.From}}</span></br>
-            <span class="secondaryText">To: {{.To}}</span></br>
-            <span class="primaryText">Subject: {{.Subject}}</span></br>
-        </div>
-        <img id="readIcon{{.MailId}}" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="toggleRead({{.MailId}});" src="/assets/read.svg"/>
-        <img id="deleteIcon" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="removeMail({{.MailId}});" src="/assets/remove.svg"/>
-        <div class="btn materialLevel1" style="width: 40pt; right: 0pt; top: 0pt; margin: auto;" onclick="closeDetails();">
-            <img src="/assets/back.svg" style="width: 20pt"/>
+<div style="height: 100%; width: 100%; display:flex; flex-direction: column;">
+    <div class="horizontalPaddingBox" style="flex-grow: 0!important;">
+        <div style="width: 100%; display: flex; flex-direction: row;">
+            <div style="display: block; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto;">
+                <span class="primaryText">From: {{.From}}</span></br>
+                <span class="secondaryText">To: {{.To}}</span></br>
+                <span class="primaryText">Subject: {{.Subject}}</span></br>
+            </div>
+            <img id="readIcon{{.MailId}}" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="toggleRead({{.MailId}});" src="/assets/read.svg"/>
+            <img id="deleteIcon" class="iconBtn" style="width: 20pt; margin-right: 10pt;" onclick="removeMail({{.MailId}});" src="/assets/remove.svg"/>
+            <div class="btn materialLevel1" style="width: 40pt; right: 0pt; top: 0pt; margin: auto;" onclick="closeDetails();">
+                <img src="/assets/back.svg" style="width: 20pt"/>
+            </div>
         </div>
     </div>
-    <div class="scrollable mailBody" style="margin-top: 10pt;">
-        <div style="padding: 5pt;">{{.Text}}</div>
+    <div id="mailBody" class="horizontalPaddingBox">
+        <div style="position: relative; max-width: 100%; max-height: 100%; width: 100%; height: 100%;">
+            <div style="position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: auto;">
+                <div>{{.Text}}</div>
+            </div>
+        </div>
     </div>
 </div>

+ 8 - 6
web/templates/error.html

@@ -10,13 +10,15 @@
         <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
-        <div style="width: 100%; height: 100%; display:flex; justify-content: center; align-items: center;">
-            <img src="/assets/error.svg" height="100pt"/>
-            <div style="display: block;">
-                <span style="font-size: 24pt;">{{.Code}}</span></br>
-                <span style="font-size: 14pt;">{{.Text}}</span>
+        <div id="main">
+            <div class="horizontalPaddingBox">
+                <div style="display: flex; width: 100%; height: 100%; justify-content: center;">
+                    <img src="/assets/error.svg" height="100pt"/>
+                    <span style="font-size: 24pt;">{{.Code}}</span></br>
+                    <span style="font-size: 14pt;">{{.Text}}</span>
+                </div>
             </div>
+            <div id="copyrightBox">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</div>
         </div>
-        <p class="copyrights">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</p>
     </body>
 </html>

+ 3 - 5
web/templates/folders.html

@@ -1,5 +1,3 @@
-<div>
-    {{range .}}
-    <div class="folderBtn">{{.}}</div>
-    {{end}}
-</div>
+{{range .}}
+<div class="folderBtn" onclick="openFolder('{{.Name}}')">{{.Name}}</div>
+{{end}}

+ 15 - 8
web/templates/index.html

@@ -11,16 +11,23 @@
         <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
-        <div class="main">
-            <div class="foldersBox">
-                <div class="btn materialLevel1" style="width: 110pt">New email</div>
-                <div id="folders"></div>
-            </div>
+        <div id="main">
             <div id="statusLine"></div>
-            <div id="mailList" class="materialLevel1 contentBox"></div>
-            <div id="details" class="materialLevel2 contentBox emailDetails" style="display: none;">
+            <div class="horizontalPaddingBox">
+                <div id="contentBox">
+                    <div id="foldersBox">
+                        <div class="btn materialLevel1" style="margin: 5pt;">New email</div>
+                        <div id="folders"></div>
+                    </div>
+                    <div class="verticalPaddingBox">
+                        <div id="mailInnerBox" class="materialLevel1">
+                            <div id="mailList"></div>
+                            <div id="mailDetails"></div>
+                        </div>
+                    </div>
+                </div>
             </div>
+            <div id="copyrightBox">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</div>
         </div>
-        <p class="copyrights">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</p>
     </body>
 </html>

+ 20 - 18
web/templates/login.html

@@ -10,25 +10,27 @@
         <title>Gostfix mail {{.Version}}</title>
     </head>
     <body>
-        <div style="width: 100%; height: 100%; display:flex; justify-content: center; align-items: center;">
-            <div>
-                <form method="POST" action="/login">
-                    <div class="inpt">
-                        <input name="user" type="text" required>
-                        <span class="highlight"></span>
-                        <span class="bar"></span>
-                        <label>Email</label>
-                    </div>
-                    <div class="inpt">
-                        <input name="password" type="password" required>
-                        <span class="highlight"></span>
-                        <span class="bar"></span>
-                        <label>Password</label>
-                    </div>
-                    <input type="submit" style="visibility: hidden;" />
-                </form>
+        <div id="main">
+            <div class="horizontalPaddingBox" >
+                <div style="display: flex; width: 100%; height: 100%; justify-content: center;">
+                    <form method="POST" action="/login" style="margin: auto;">
+                        <div class="inpt">
+                            <input name="user" type="text" required>
+                            <span class="highlight"></span>
+                            <span class="bar"></span>
+                            <label>Email</label>
+                        </div>
+                        <div class="inpt">
+                            <input name="password" type="password" required>
+                            <span class="highlight"></span>
+                            <span class="bar"></span>
+                            <label>Password</label>
+                        </div>
+                        <input type="submit" style="visibility: hidden;" />
+                    </form>
+                </div>
             </div>
+            <div id="copyrightBox">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</div>
         </div>
-        <p class="copyrights">gostfix {{.Version}} Web interface. Copyright (c) 2020 Alexey Edelev &lt;semlanik@gmail.com&gt;</p>
     </body>
 </html>

+ 9 - 17
web/templates/maillist.html

@@ -1,17 +1,9 @@
-<div class="scrollable">
-    <div class="fadeIn" style="position: absolute; top: 5pt; left: 0; right: 0; height: 10pt"></div>
-    <table class="mailList">
-        <tbody>
-            {{range .}}
-            <tr id="mail{{.Id}}" class="{{if .Read}}read{{else}}unread{{end}}" onclick="openEmail('{{.Id}}');">
-                <td class="fromCol">{{.Mail.Header.From}}</td>
-                <td class="subjCol">{{.Mail.Header.Subject}}</td>
-                <td id="mailDate{{.Id}}" class="dateCol" onload="$('#mailDate{{.Id}}').html(localDate({{.Mail.Header.Date}}))"></td>
-            </tr>
-            {{else}}
-            <tr><td><b>Mail folder is empty</b></td></tr>
-            {{end}}
-        </tbody>
-    </table>
-    <div class="fadeOut" style="position: absolute; bottom: 5pt; left: 0; right: 0; height:10pt"></div>
-</div>
+<!-- <div class="fadeIn" style="position: absolute; top: 5pt; left: 0; right: 0; height: 10pt"></div> -->
+{{range .}}
+<div id="mail{{.Id}}" class="mailHeader {{if .Read}}read{{else}}unread{{end}}" onclick="openEmail('{{.Id}}');">
+    <div class="mailFrom">{{.Mail.Header.From}}</div>
+    <div class="mailSubject">{{.Mail.Header.Subject}}</div>
+    <div id="mailDate{{.Id}}" class="mailDate" onload="$('#mailDate{{.Id}}').html(localDate({{.Mail.Header.Date}}))">{{.Mail.Header.Subject}}</div>
+</div>
+{{end}}
+<!-- <div class="fadeOut" style="position: absolute; bottom: 5pt; left: 0; right: 0; height:10pt"></div> -->

+ 1 - 1
web/templates/statusline.html

@@ -1 +1 @@
-Welcome {{.Name}}, you have {{.Unread}}({{.Total}}) messages
+Welcome {{.Name}} {{.Email}}