diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | BUGS | 3 | ||||
-rw-r--r-- | model/settings.go | 26 | ||||
-rw-r--r-- | renderer/model.go | 46 | ||||
-rw-r--r-- | renderer/renderer.go | 12 | ||||
-rw-r--r-- | service/auth.go | 29 | ||||
-rw-r--r-- | service/logging.go | 25 | ||||
-rw-r--r-- | service/service.go | 189 | ||||
-rw-r--r-- | service/transport.go | 65 | ||||
-rw-r--r-- | static/style.css | 13 | ||||
-rw-r--r-- | templates/about.tmpl | 3 | ||||
-rw-r--r-- | templates/emoji.tmpl | 3 | ||||
-rw-r--r-- | templates/error.tmpl | 4 | ||||
-rw-r--r-- | templates/header.tmpl | 5 | ||||
-rw-r--r-- | templates/likedby.tmpl | 3 | ||||
-rw-r--r-- | templates/nav.tmpl | 34 | ||||
-rw-r--r-- | templates/navigation.tmpl | 30 | ||||
-rw-r--r-- | templates/notification.tmpl | 23 | ||||
-rw-r--r-- | templates/postform.tmpl | 2 | ||||
-rw-r--r-- | templates/retweetedby.tmpl | 3 | ||||
-rw-r--r-- | templates/root.tmpl | 17 | ||||
-rw-r--r-- | templates/search.tmpl | 3 | ||||
-rw-r--r-- | templates/settings.tmpl | 11 | ||||
-rw-r--r-- | templates/signin.tmpl | 2 | ||||
-rw-r--r-- | templates/status.tmpl | 38 | ||||
-rw-r--r-- | templates/thread.tmpl | 3 | ||||
-rw-r--r-- | templates/timeline.tmpl | 5 | ||||
-rw-r--r-- | templates/user.tmpl | 3 | ||||
-rw-r--r-- | templates/usersearch.tmpl | 3 |
29 files changed, 347 insertions, 257 deletions
@@ -1,2 +1,3 @@ bloat database +bloat.def.conf @@ -0,0 +1,3 @@ +Here's a list of known bugs in bloat: + +- <frameset> and <frame> tags are not supported in HTML5 diff --git a/model/settings.go b/model/settings.go index 7d22747..b1463c7 100644 --- a/model/settings.go +++ b/model/settings.go @@ -1,21 +1,23 @@ package model type Settings struct { - DefaultVisibility string `json:"default_visibility"` - CopyScope bool `json:"copy_scope"` - ThreadInNewTab bool `json:"thread_in_new_tab"` - MaskNSFW bool `json:"mask_nfsw"` - FluorideMode bool `json:"fluoride_mode"` - DarkMode bool `json:"dark_mode"` + DefaultVisibility string `json:"default_visibility"` + CopyScope bool `json:"copy_scope"` + ThreadInNewTab bool `json:"thread_in_new_tab"` + MaskNSFW bool `json:"mask_nfsw"` + AutoRefreshNotifications bool `json:"auto_refresh_notifications"` + FluorideMode bool `json:"fluoride_mode"` + DarkMode bool `json:"dark_mode"` } func NewSettings() *Settings { return &Settings{ - DefaultVisibility: "public", - CopyScope: true, - ThreadInNewTab: false, - MaskNSFW: true, - FluorideMode: false, - DarkMode: false, + DefaultVisibility: "public", + CopyScope: true, + ThreadInNewTab: false, + MaskNSFW: true, + AutoRefreshNotifications: false, + FluorideMode: false, + DarkMode: false, } } diff --git a/renderer/model.go b/renderer/model.go index 842dd71..45d3117 100644 --- a/renderer/model.go +++ b/renderer/model.go @@ -14,30 +14,17 @@ type Context struct { UserID string } -type HeaderData struct { - Title string - NotificationCount int - CustomCSS string - CSRFToken string -} - -type NavbarData struct { - User *mastodon.Account - NotificationCount int +type NavData struct { + CommonData *CommonData + User *mastodon.Account + PostContext model.PostContext } type CommonData struct { - HeaderData *HeaderData - NavbarData *NavbarData -} - -func (c CommonData) IsCurrentUser(id string) bool { - if c.NavbarData != nil && - c.NavbarData.User != nil && - c.NavbarData.User.ID == id { - return true - } - return false + Title string + CustomCSS string + CSRFToken string + AutoRefresh bool } type ErrorData struct { @@ -53,13 +40,16 @@ type SigninData struct { *CommonData } +type RootData struct { + Title string +} + type TimelineData struct { *CommonData - Title string - Statuses []*mastodon.Status - NextLink string - PrevLink string - PostContext model.PostContext + Title string + Statuses []*mastodon.Status + NextLink string + PrevLink string } type ThreadData struct { @@ -72,8 +62,9 @@ type ThreadData struct { type NotificationData struct { *CommonData Notifications []*mastodon.Notification + UnreadCount int + ReadID string NextLink string - DarkMode bool } type UserData struct { @@ -84,7 +75,6 @@ type UserData struct { Users []*mastodon.Account Statuses []*mastodon.Status NextLink string - DarkMode bool } type UserSearchData struct { diff --git a/renderer/renderer.go b/renderer/renderer.go index bd9ccd8..293a6c6 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -19,6 +19,8 @@ type TemplateData struct { type Renderer interface { RenderSigninPage(ctx *Context, writer io.Writer, data *SigninData) (err error) RenderErrorPage(ctx *Context, writer io.Writer, data *ErrorData) + RenderRootPage(ctx *Context, writer io.Writer, data *RootData) (err error) + RenderNavPage(ctx *Context, writer io.Writer, data *NavData) (err error) RenderTimelinePage(ctx *Context, writer io.Writer, data *TimelineData) (err error) RenderThreadPage(ctx *Context, writer io.Writer, data *ThreadData) (err error) RenderNotificationPage(ctx *Context, writer io.Writer, data *NotificationData) (err error) @@ -67,6 +69,16 @@ func (r *renderer) RenderErrorPage(ctx *Context, writer io.Writer, return } +func (r *renderer) RenderNavPage(ctx *Context, writer io.Writer, + data *NavData) (err error) { + return r.template.ExecuteTemplate(writer, "nav.tmpl", WithContext(data, ctx)) +} + +func (r *renderer) RenderRootPage(ctx *Context, writer io.Writer, + data *RootData) (err error) { + return r.template.ExecuteTemplate(writer, "root.tmpl", WithContext(data, ctx)) +} + func (r *renderer) RenderTimelinePage(ctx *Context, writer io.Writer, data *TimelineData) (err error) { return r.template.ExecuteTemplate(writer, "timeline.tmpl", WithContext(data, ctx)) diff --git a/service/auth.go b/service/auth.go index 4c5b38b..9e6f709 100644 --- a/service/auth.go +++ b/service/auth.go @@ -68,6 +68,22 @@ func (s *as) ServeSigninPage(ctx context.Context, c *model.Client) (err error) { return s.Service.ServeSigninPage(ctx, c) } +func (s *as) ServeRootPage(ctx context.Context, c *model.Client) (err error) { + err = s.authenticateClient(ctx, c) + if err != nil { + return + } + return s.Service.ServeRootPage(ctx, c) +} + +func (s *as) ServeNavPage(ctx context.Context, c *model.Client) (err error) { + err = s.authenticateClient(ctx, c) + if err != nil { + return + } + return s.Service.ServeNavPage(ctx, c) +} + func (s *as) ServeTimelinePage(ctx context.Context, c *model.Client, tType string, maxID string, minID string) (err error) { err = s.authenticateClient(ctx, c) @@ -382,3 +398,16 @@ func (s *as) Delete(ctx context.Context, c *model.Client, id string) (err error) } return s.Service.Delete(ctx, c, id) } + +func (s *as) ReadNotifications(ctx context.Context, c *model.Client, + maxID string) (err error) { + err = s.authenticateClient(ctx, c) + if err != nil { + return + } + err = checkCSRF(ctx, c) + if err != nil { + return + } + return s.Service.ReadNotifications(ctx, c, maxID) +} diff --git a/service/logging.go b/service/logging.go index 055dadd..795f329 100644 --- a/service/logging.go +++ b/service/logging.go @@ -34,6 +34,22 @@ func (s *ls) ServeSigninPage(ctx context.Context, c *model.Client) (err error) { return s.Service.ServeSigninPage(ctx, c) } +func (s *ls) ServeRootPage(ctx context.Context, c *model.Client) (err error) { + defer func(begin time.Time) { + s.logger.Printf("method=%v, took=%v, err=%v\n", + "ServeRootPage", time.Since(begin), err) + }(time.Now()) + return s.Service.ServeRootPage(ctx, c) +} + +func (s *ls) ServeNavPage(ctx context.Context, c *model.Client) (err error) { + defer func(begin time.Time) { + s.logger.Printf("method=%v, took=%v, err=%v\n", + "ServeNavPage", time.Since(begin), err) + }(time.Now()) + return s.Service.ServeNavPage(ctx, c) +} + func (s *ls) ServeTimelinePage(ctx context.Context, c *model.Client, tType string, maxID string, minID string) (err error) { defer func(begin time.Time) { @@ -276,3 +292,12 @@ func (s *ls) Delete(ctx context.Context, c *model.Client, id string) (err error) }(time.Now()) return s.Service.Delete(ctx, c, id) } + +func (s *ls) ReadNotifications(ctx context.Context, c *model.Client, + maxID string) (err error) { + defer func(begin time.Time) { + s.logger.Printf("method=%v, max_id=%v, took=%v, err=%v\n", + "ReadNotifications", maxID, time.Since(begin), err) + }(time.Now()) + return s.Service.ReadNotifications(ctx, c, maxID) +} diff --git a/service/service.go b/service/service.go index ecd0d3f..9e01509 100644 --- a/service/service.go +++ b/service/service.go @@ -21,6 +21,8 @@ var ( type Service interface { ServeErrorPage(ctx context.Context, c *model.Client, err error) ServeSigninPage(ctx context.Context, c *model.Client) (err error) + ServeRootPage(ctx context.Context, c *model.Client) (err error) + ServeNavPage(ctx context.Context, c *model.Client) (err error) ServeTimelinePage(ctx context.Context, c *model.Client, tType string, maxID string, minID string) (err error) ServeThreadPage(ctx context.Context, c *model.Client, id string, reply bool) (err error) ServeLikedByPage(ctx context.Context, c *model.Client, id string) (err error) @@ -53,6 +55,7 @@ type Service interface { MuteConversation(ctx context.Context, c *model.Client, id string) (err error) UnMuteConversation(ctx context.Context, c *model.Client, id string) (err error) Delete(ctx context.Context, c *model.Client, id string) (err error) + ReadNotifications(ctx context.Context, c *model.Client, maxID string) (err error) } type service struct { @@ -126,45 +129,14 @@ func addToReplyMap(m map[string][]mastodon.ReplyInfo, key interface{}, } func (svc *service) getCommonData(ctx context.Context, c *model.Client, - title string) (data *renderer.CommonData, err error) { - - data = new(renderer.CommonData) - data.HeaderData = &renderer.HeaderData{ - Title: title + " - " + svc.clientName, - NotificationCount: 0, - CustomCSS: svc.customCSS, - } - - if c == nil || !c.Session.IsLoggedIn() { - return - } - - notifications, err := c.GetNotifications(ctx, nil) - if err != nil { - return nil, err - } - - var notificationCount int - for i := range notifications { - if notifications[i].Pleroma != nil && - !notifications[i].Pleroma.IsSeen { - notificationCount++ - } - } - - u, err := c.GetAccountCurrentUser(ctx) - if err != nil { - return nil, err + title string) (data *renderer.CommonData) { + data = &renderer.CommonData{ + Title: title + " - " + svc.clientName, + CustomCSS: svc.customCSS, } - - data.NavbarData = &renderer.NavbarData{ - User: u, - NotificationCount: notificationCount, + if c != nil && c.Session.IsLoggedIn() { + data.CSRFToken = c.Session.CSRFToken } - - data.HeaderData.NotificationCount = notificationCount - data.HeaderData.CSRFToken = c.Session.CSRFToken - return } @@ -174,11 +146,7 @@ func (svc *service) ServeErrorPage(ctx context.Context, c *model.Client, err err errStr = err.Error() } - commonData, err := svc.getCommonData(ctx, nil, "error") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, nil, "error") data := &renderer.ErrorData{ CommonData: commonData, Error: errStr, @@ -191,11 +159,7 @@ func (svc *service) ServeErrorPage(ctx context.Context, c *model.Client, err err func (svc *service) ServeSigninPage(ctx context.Context, c *model.Client) ( err error) { - commonData, err := svc.getCommonData(ctx, nil, "signin") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, nil, "signin") data := &renderer.SigninData{ CommonData: commonData, } @@ -204,6 +168,37 @@ func (svc *service) ServeSigninPage(ctx context.Context, c *model.Client) ( return svc.renderer.RenderSigninPage(rCtx, c.Writer, data) } +func (svc *service) ServeRootPage(ctx context.Context, c *model.Client) (err error) { + data := &renderer.RootData{ + Title: svc.clientName, + } + + rCtx := getRendererContext(c) + return svc.renderer.RenderRootPage(rCtx, c.Writer, data) +} + +func (svc *service) ServeNavPage(ctx context.Context, c *model.Client) (err error) { + u, err := c.GetAccountCurrentUser(ctx) + if err != nil { + return + } + + postContext := model.PostContext{ + DefaultVisibility: c.Session.Settings.DefaultVisibility, + Formats: svc.postFormats, + } + + commonData := svc.getCommonData(ctx, c, "Nav") + data := &renderer.NavData{ + User: u, + CommonData: commonData, + PostContext: postContext, + } + + rCtx := getRendererContext(c) + return svc.renderer.RenderNavPage(rCtx, c.Writer, data) +} + func (svc *service) ServeTimelinePage(ctx context.Context, c *model.Client, tType string, maxID string, minID string) (err error) { @@ -269,23 +264,13 @@ func (svc *service) ServeTimelinePage(ctx context.Context, c *model.Client, nextLink = fmt.Sprintf("/timeline/%s?max_id=%s", tType, pg.MaxID) } - postContext := model.PostContext{ - DefaultVisibility: c.Session.Settings.DefaultVisibility, - Formats: svc.postFormats, - } - - commonData, err := svc.getCommonData(ctx, c, tType+" timeline ") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, tType+" timeline ") data := &renderer.TimelineData{ - Title: title, - Statuses: statuses, - NextLink: nextLink, - PrevLink: prevLink, - PostContext: postContext, - CommonData: commonData, + Title: title, + Statuses: statuses, + NextLink: nextLink, + PrevLink: prevLink, + CommonData: commonData, } rCtx := getRendererContext(c) @@ -356,11 +341,7 @@ func (svc *service) ServeThreadPage(ctx context.Context, c *model.Client, addToReplyMap(replies, statuses[i].InReplyToID, statuses[i].ID, i+1) } - commonData, err := svc.getCommonData(ctx, c, "post by "+status.Account.DisplayName) - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "post by "+status.Account.DisplayName) data := &renderer.ThreadData{ Statuses: statuses, PostContext: postContext, @@ -380,11 +361,7 @@ func (svc *service) ServeLikedByPage(ctx context.Context, c *model.Client, return } - commonData, err := svc.getCommonData(ctx, c, "likes") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "likes") data := &renderer.LikedByData{ CommonData: commonData, Users: likers, @@ -402,11 +379,7 @@ func (svc *service) ServeRetweetedByPage(ctx context.Context, c *model.Client, return } - commonData, err := svc.getCommonData(ctx, c, "retweets") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "retweets") data := &renderer.RetweetedByData{ CommonData: commonData, Users: retweeters, @@ -421,6 +394,7 @@ func (svc *service) ServeNotificationPage(ctx context.Context, c *model.Client, var nextLink string var unreadCount int + var readID string var pg = mastodon.Pagination{ MaxID: maxID, MinID: minID, @@ -439,23 +413,19 @@ func (svc *service) ServeNotificationPage(ctx context.Context, c *model.Client, } if unreadCount > 0 { - err := c.ReadNotifications(ctx, notifications[0].ID) - if err != nil { - return err - } + readID = notifications[0].ID } - if len(pg.MaxID) > 0 { + if len(notifications) == 20 && len(pg.MaxID) > 0 { nextLink = "/notifications?max_id=" + pg.MaxID } - commonData, err := svc.getCommonData(ctx, c, "notifications") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "notifications") + commonData.AutoRefresh = c.Session.Settings.AutoRefreshNotifications data := &renderer.NotificationData{ Notifications: notifications, + UnreadCount: unreadCount, + ReadID: readID, NextLink: nextLink, CommonData: commonData, } @@ -521,14 +491,10 @@ func (svc *service) ServeUserPage(ctx context.Context, c *model.Client, return errInvalidArgument } - commonData, err := svc.getCommonData(ctx, c, user.DisplayName) - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, user.DisplayName) data := &renderer.UserData{ User: user, - IsCurrent: commonData.IsCurrentUser(user.ID), + IsCurrent: c.Session.UserID == user.ID, Type: pageType, Users: users, Statuses: statuses, @@ -564,11 +530,7 @@ func (svc *service) ServeUserSearchPage(ctx context.Context, c *model.Client, title += " \"" + q + "\"" } - commonData, err := svc.getCommonData(ctx, c, title) - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, title) data := &renderer.UserSearchData{ CommonData: commonData, User: user, @@ -582,11 +544,7 @@ func (svc *service) ServeUserSearchPage(ctx context.Context, c *model.Client, } func (svc *service) ServeAboutPage(ctx context.Context, c *model.Client) (err error) { - commonData, err := svc.getCommonData(ctx, c, "about") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "about") data := &renderer.AboutData{ CommonData: commonData, } @@ -596,16 +554,12 @@ func (svc *service) ServeAboutPage(ctx context.Context, c *model.Client) (err er } func (svc *service) ServeEmojiPage(ctx context.Context, c *model.Client) (err error) { - commonData, err := svc.getCommonData(ctx, c, "emojis") - if err != nil { - return - } - emojis, err := c.GetInstanceEmojis(ctx) if err != nil { return } + commonData := svc.getCommonData(ctx, c, "emojis") data := &renderer.EmojiData{ Emojis: emojis, CommonData: commonData, @@ -636,11 +590,7 @@ func (svc *service) ServeSearchPage(ctx context.Context, c *model.Client, title += " \"" + q + "\"" } - commonData, err := svc.getCommonData(ctx, c, title) - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, title) data := &renderer.SearchData{ CommonData: commonData, Q: q, @@ -655,11 +605,7 @@ func (svc *service) ServeSearchPage(ctx context.Context, c *model.Client, } func (svc *service) ServeSettingsPage(ctx context.Context, c *model.Client) (err error) { - commonData, err := svc.getCommonData(ctx, c, "settings") - if err != nil { - return - } - + commonData := svc.getCommonData(ctx, c, "settings") data := &renderer.SettingsData{ CommonData: commonData, Settings: &c.Session.Settings, @@ -911,3 +857,8 @@ func (svc *service) Delete(ctx context.Context, c *model.Client, id string) (err error) { return c.DeleteStatus(ctx, id) } + +func (svc *service) ReadNotifications(ctx context.Context, c *model.Client, + maxID string) (err error) { + return c.ReadNotifications(ctx, maxID) +} diff --git a/service/transport.go b/service/transport.go index 5ce0e56..48e2ee2 100644 --- a/service/transport.go +++ b/service/transport.go @@ -64,14 +64,30 @@ func NewHandler(s Service, staticDir string) http.Handler { rootPage := func(w http.ResponseWriter, req *http.Request) { sessionID, _ := req.Cookie("session_id") - - location := "/signin" if sessionID != nil && len(sessionID.Value) > 0 { - location = "/timeline/home" + c := newClient(w) + ctx := newCtxWithSesion(req) + err := s.ServeRootPage(ctx, c) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + s.ServeErrorPage(ctx, c, err) + return + } + } else { + w.Header().Add("Location", "/signin") + w.WriteHeader(http.StatusFound) } + } - w.Header().Add("Location", location) - w.WriteHeader(http.StatusFound) + navPage := func(w http.ResponseWriter, req *http.Request) { + c := newClient(w) + ctx := newCtxWithSesion(req) + err := s.ServeNavPage(ctx, c) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + s.ServeErrorPage(ctx, c, err) + return + } } signinPage := func(w http.ResponseWriter, req *http.Request) { @@ -297,7 +313,7 @@ func NewHandler(s Service, staticDir string) http.Handler { return } - w.Header().Add("Location", "/timeline/home") + w.Header().Add("Location", "/") w.WriteHeader(http.StatusFound) } @@ -326,7 +342,7 @@ func NewHandler(s Service, staticDir string) http.Handler { return } - location := "/timeline/home" + "#status-" + id + location := req.Header.Get("Referer") if len(replyToID) > 0 { location = "/thread/" + replyToID + "#status-" + id } @@ -540,16 +556,18 @@ func NewHandler(s Service, staticDir string) http.Handler { copyScope := req.FormValue("copy_scope") == "true" threadInNewTab := req.FormValue("thread_in_new_tab") == "true" maskNSFW := req.FormValue("mask_nsfw") == "true" + arn := req.FormValue("auto_refresh_notifications") == "true" fluorideMode := req.FormValue("fluoride_mode") == "true" darkMode := req.FormValue("dark_mode") == "true" settings := &model.Settings{ - DefaultVisibility: visibility, - CopyScope: copyScope, - ThreadInNewTab: threadInNewTab, - MaskNSFW: maskNSFW, - FluorideMode: fluorideMode, - DarkMode: darkMode, + DefaultVisibility: visibility, + CopyScope: copyScope, + ThreadInNewTab: threadInNewTab, + MaskNSFW: maskNSFW, + AutoRefreshNotifications: arn, + FluorideMode: fluorideMode, + DarkMode: darkMode, } err := s.SaveSettings(ctx, c, settings) @@ -559,7 +577,7 @@ func NewHandler(s Service, staticDir string) http.Handler { return } - w.Header().Add("Location", req.Header.Get("Referer")) + w.Header().Add("Location", "/") w.WriteHeader(http.StatusFound) } @@ -611,6 +629,22 @@ func NewHandler(s Service, staticDir string) http.Handler { w.WriteHeader(http.StatusFound) } + readNotifications := func(w http.ResponseWriter, req *http.Request) { + c := newClient(w) + ctx := newCtxWithSesionCSRF(req, req.FormValue("csrf_token")) + maxID := req.URL.Query().Get("max_id") + + err := s.ReadNotifications(ctx, c, maxID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + s.ServeErrorPage(ctx, c, err) + return + } + + w.Header().Add("Location", req.Header.Get("Referer")) + w.WriteHeader(http.StatusFound) + } + signout := func(w http.ResponseWriter, req *http.Request) { // TODO remove session from database http.SetCookie(w, &http.Cookie{ @@ -694,7 +728,9 @@ func NewHandler(s Service, staticDir string) http.Handler { } r.HandleFunc("/", rootPage).Methods(http.MethodGet) + r.HandleFunc("/nav", navPage).Methods(http.MethodGet) r.HandleFunc("/signin", signinPage).Methods(http.MethodGet) + r.HandleFunc("//{type}", timelinePage).Methods(http.MethodGet) r.HandleFunc("/timeline/{type}", timelinePage).Methods(http.MethodGet) r.HandleFunc("/timeline", timelineOldPage).Methods(http.MethodGet) r.HandleFunc("/thread/{id}", threadPage).Methods(http.MethodGet) @@ -726,6 +762,7 @@ func NewHandler(s Service, staticDir string) http.Handler { r.HandleFunc("/muteconv/{id}", muteConversation).Methods(http.MethodPost) r.HandleFunc("/unmuteconv/{id}", unMuteConversation).Methods(http.MethodPost) r.HandleFunc("/delete/{id}", delete).Methods(http.MethodPost) + r.HandleFunc("/notifications/read", readNotifications).Methods(http.MethodPost) r.HandleFunc("/signout", signout).Methods(http.MethodGet) r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost) r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost) diff --git a/static/style.css b/static/style.css index 2f80af9..a647d4f 100644 --- a/static/style.css +++ b/static/style.css @@ -460,6 +460,19 @@ a:hover, margin-top: 6px; } +.notification-title-container>* { + display: inline; +} + +.notification-title { + font-size: 18pt; + margin-right: 8px; +} + +.notification-refresh { + margin-right: 8px; +} + .dark { background-color: #222222; background-image: none; diff --git a/templates/about.tmpl b/templates/about.tmpl index b3c9a49..d4761de 100644 --- a/templates/about.tmpl +++ b/templates/about.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> About </div> <div> diff --git a/templates/emoji.tmpl b/templates/emoji.tmpl index 2066afa..3e9f0f0 100644 --- a/templates/emoji.tmpl +++ b/templates/emoji.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Emojis </div> <div class="emoji-list-container"> diff --git a/templates/error.tmpl b/templates/error.tmpl index 0d6115a..fc925ca 100644 --- a/templates/error.tmpl +++ b/templates/error.tmpl @@ -1,11 +1,11 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Error </div> <div class="error-text"> {{.Error}} </div> <div> <a href="/timeline/home">Home</a> - <a href="/signin">Sign In</a> + <a href="/signin" target="_top">Sign In</a> </div> {{template "footer.tmpl"}} diff --git a/templates/header.tmpl b/templates/header.tmpl index 2889ead..76831f2 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -7,7 +7,10 @@ {{if .CSRFToken}} <meta name="csrf_token" content="{{.CSRFToken}}"> {{end}} - <title>{{if gt .NotificationCount 0}}({{.NotificationCount}}) {{end}}{{.Title}}</title> + {{if .AutoRefresh}} + <meta http-equiv="refresh" content="30"> + {{end}} + <title>{{.Title}}</title> <link rel="stylesheet" href="/static/style.css"> {{if .CustomCSS}} <link rel="stylesheet" href="{{.CustomCSS}}"> diff --git a/templates/likedby.tmpl b/templates/likedby.tmpl index 00857c1..222254c 100644 --- a/templates/likedby.tmpl +++ b/templates/likedby.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Liked By </div> {{template "userlist.tmpl" (WithContext .Users $.Ctx)}} diff --git a/templates/nav.tmpl b/templates/nav.tmpl new file mode 100644 index 0000000..620643e --- /dev/null +++ b/templates/nav.tmpl @@ -0,0 +1,34 @@ +{{with .Data}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} +<div class="user-info"> + <div class="user-info-img-container"> + <a class="img-link" href="/timeline/home" title="home" target="main"> + <img class="user-info-img" src="{{.User.AvatarStatic}}" alt="profile-avatar" /> + </a> + </div> + <div class="user-info-details-container"> + <div> + <span class="status-dname"> {{EmojiFilter .User.DisplayName .User.Emojis}} </span> + <a class="nav-link" href="/user/{{.User.ID}}" target="main"> + <span class="status-uname"> {{.User.Acct}} </span> + </a> + </div> + <div> + <a class="nav-link" href="/timeline/home" target="main">home</a> + <a class="nav-link" href="/timeline/direct" target="main">direct</a> + <a class="nav-link" href="/timeline/local" target="main">local</a> + <a class="nav-link" href="/timeline/twkn" target="main">twkn</a> + <a class="nav-link" href="/search" target="main">search</a> + <a class="nav-link" href="/about" target="main">about</a> + </div> + <div> + <a class="nav-link" href="/settings" target="_top">settings</a> + <a class="nav-link" href="/signout" target="_top">sign out</a> + </div> + </div> +</div> + +{{template "postform.tmpl" (WithContext .PostContext $.Ctx)}} + +{{template "footer.tmpl"}} +{{end}} diff --git a/templates/navigation.tmpl b/templates/navigation.tmpl deleted file mode 100644 index a85f9fd..0000000 --- a/templates/navigation.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -{{with .Data}} -<div class="user-info"> - <div class="user-info-img-container"> - <a class="img-link" href="/timeline/home" title="home"> - <img class="user-info-img" src="{{.User.AvatarStatic}}" alt="profile-avatar" /> - </a> - </div> - <div class="user-info-details-container"> - <div> - <span class="status-dname"> {{EmojiFilter .User.DisplayName .User.Emojis}} </span> - <a class="nav-link" href="/user/{{.User.ID}}"> - <span class="status-uname"> {{.User.Acct}} </span> - </a> - </div> - <div> - <a class="nav-link" href="/timeline/home">home</a> - <a class="nav-link" href="/notifications">notifications{{if gt .NotificationCount 0}}({{.NotificationCount}}){{end}}</a> - <a class="nav-link" href="/timeline/direct">direct</a> - <a class="nav-link" href="/timeline/local">local</a> - <a class="nav-link" href="/timeline/twkn">twkn</a> - <a class="nav-link" href="/search">search</a> - <a class="nav-link" href="/about">about</a> - </div> - <div> - <a class="nav-link" href="/settings">settings</a> - <a class="nav-link" href="/signout">sign out</a> - </div> - </div> -</div> -{{end}} diff --git a/templates/notification.tmpl b/templates/notification.tmpl index 51cc6e3..9e1854c 100644 --- a/templates/notification.tmpl +++ b/templates/notification.tmpl @@ -1,14 +1,25 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} -<div class="page-title"> Notifications </div> +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} +<div class="notification-title-container"> + <div class="notification-title"> + Notifications + {{if gt .UnreadCount 0}}({{.UnreadCount }}){{end}} + </div> + <a class="notification-refresh" href="/notifications">refresh</a> + {{if .ReadID}} + <form action="/notifications/read?max_id={{.ReadID}}" method="post"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="submit" value="read" class="btn-link"> + </form> + {{end}} +</div> {{range .Notifications}} <div class="notification-container {{if .Pleroma}}{{if not .Pleroma.IsSeen}}unread{{end}}{{end}}"> {{if eq .Type "follow"}} <div class="notification-follow-container"> <div class="status-profile-img-container"> - <a class="img-link" href="/user/{{.Account.ID}}" > + <a class="img-link" href="/user/{{.Account.ID}}" target="main" > <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="profile-avatar" /> </a> </div> @@ -31,7 +42,7 @@ {{else if eq .Type "reblog"}} <div class="retweet-info"> - <a class="img-link" href="/user/{{.Account.ID}}"> + <a class="img-link" href="/user/{{.Account.ID}}" target="main"> <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="avatar" /> </a> <span class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </span> @@ -44,7 +55,7 @@ {{else if eq .Type "favourite"}} <div class="retweet-info"> - <a class="img-link" href="/user/{{.Account.ID}}"> + <a class="img-link" href="/user/{{.Account.ID}}" target="main"> <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="avatar" /> </a> <span class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </span> diff --git a/templates/postform.tmpl b/templates/postform.tmpl index 51ac5e6..d5c1fb9 100644 --- a/templates/postform.tmpl +++ b/templates/postform.tmpl @@ -11,7 +11,7 @@ emoji list </a> <div class="post-form-content-container"> - <textarea id="post-content" name="content" class="post-content" cols="50" rows="5">{{if .ReplyContext}}{{.ReplyContext.ReplyContent}}{{end}}</textarea> + <textarea id="post-content" name="content" class="post-content" cols="34" rows="5">{{if .ReplyContext}}{{.ReplyContext.ReplyContent}}{{end}}</textarea> </div> <div> {{if gt (len .Formats) 0}} diff --git a/templates/retweetedby.tmpl b/templates/retweetedby.tmpl index ce0d337..9492ee6 100644 --- a/templates/retweetedby.tmpl +++ b/templates/retweetedby.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Retweeted By </div> {{template "userlist.tmpl" (WithContext .Users $.Ctx)}} diff --git a/templates/root.tmpl b/templates/root.tmpl new file mode 100644 index 0000000..cd33139 --- /dev/null +++ b/templates/root.tmpl @@ -0,0 +1,17 @@ +{{with .Data}} +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset='utf-8'> + <meta content='width=device-width, initial-scale=1' name='viewport'> + <title>{{.Title}}</title> +</head> +<frameset cols="30%,*"> + <frameset rows="316px,*"> + <frame name="nav" class="nav-frame" src="/nav" /> + <frame name="notification" class="notification-frame" src="/notifications" /> + </frameset> + <frame name="main" class="main-frame" src="/timeline/home" /> +</frameset> +</html> +{{end}} diff --git a/templates/search.tmpl b/templates/search.tmpl index 96548b5..ede147e 100644 --- a/templates/search.tmpl +++ b/templates/search.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Search </div> <form class="search-form" action="/search" method="GET"> diff --git a/templates/settings.tmpl b/templates/settings.tmpl index e7d49e9..d8ede87 100644 --- a/templates/settings.tmpl +++ b/templates/settings.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Settings </div> <form id="settings-form" action="/settings" method="POST"> @@ -24,11 +23,15 @@ </div> <div class="settings-form-field"> <input id="mask-nsfw" name="mask_nsfw" type="checkbox" value="true" {{if .Settings.MaskNSFW}}checked{{end}}> - <label for="mask-nsfw"> Mask NSFW Attachments </label> + <label for="mask-nsfw"> Mask NSFW attachments </label> + </div> + <div class="settings-form-field"> + <input id="auto-refresh-notifications" name="auto_refresh_notifications" type="checkbox" value="true" {{if .Settings.AutoRefreshNotifications}}checked{{end}}> + <label for="auto-refresh-notifications"> Auto refresh notifications </label> </div> <div class="settings-form-field"> <input id="fluoride-mode" name="fluoride_mode" type="checkbox" value="true" {{if .Settings.FluorideMode}}checked{{end}}> - <label for="fluoride-mode"> Enable Fluoride Mode </label> + <label for="fluoride-mode"> Enable fluoride mode </label> </div> <div class="settings-form-field"> <input id="dark-mode" name="dark_mode" type="checkbox" value="true" {{if .Settings.DarkMode}}checked{{end}}> diff --git a/templates/signin.tmpl b/templates/signin.tmpl index a199948..069572b 100644 --- a/templates/signin.tmpl +++ b/templates/signin.tmpl @@ -1,5 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Signin </div> <form class="signin-form" action="/signin" method="post"> diff --git a/templates/status.tmpl b/templates/status.tmpl index 95dee20..c4f2e5f 100644 --- a/templates/status.tmpl +++ b/templates/status.tmpl @@ -2,7 +2,7 @@ <div id="status-{{.ID}}" class="status-container-container"> {{if .Reblog}} <div class="retweet-info"> - <a class="img-link" href="/user/{{.Account.ID}}"> + <a class="img-link" href="/user/{{.Account.ID}}" target="main"> <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="avatar" /> </a> <span class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </span> @@ -14,39 +14,39 @@ {{with $s := .Data}} <div class="status-container status-{{.ID}}" data-id="{{.ID}}"> <div class="status-profile-img-container"> - <a class="img-link" href="/user/{{.Account.ID}}"> + <a class="img-link" href="/user/{{.Account.ID}}" target="main"> <img class="status-profile-img" src="{{.Account.AvatarStatic}}" alt="avatar" /> </a> </div> <div class="status"> <div class="status-name"> <span class="status-dname"> {{EmojiFilter .Account.DisplayName .Account.Emojis}} </span> - <a href="/user/{{.Account.ID}}" > + <a href="/user/{{.Account.ID}}" target="main"> <span class="status-uname"> {{.Account.Acct}} </span> </a> - <div class="more-container" title="more"> + <div class="more-container"> <div class="remote-link"> {{.Visibility}} </div> <div class="more-content"> - <a class="more-link" href="{{.URL}}" target="_blank" title="source"> + <a class="more-link" href="{{.URL}}" target="_blank"> source </a> {{if .Muted}} <form action="/unmuteconv/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> - <input type="submit" value="unmute" class="btn-link more-link" title="unmute"> + <input type="submit" value="unmute" class="btn-link more-link"> </form> {{else}} <form action="/muteconv/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> - <input type="submit" value="mute" class="btn-link more-link" title="mute"> + <input type="submit" value="mute" class="btn-link more-link"> </form> {{end}} {{if eq $.Ctx.UserID .Account.ID}} <form action="/delete/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> - <input type="submit" value="delete" class="btn-link more-link" title="delete"> + <input type="submit" value="delete" class="btn-link more-link"> </form> {{end}} </div> @@ -55,7 +55,7 @@ <div class="status-reply-container"> {{if .InReplyToID}} <div class="status-reply-to"> - <a class="status-reply-to-link" href="{{if not .ShowReplies}}/thread/{{.InReplyToID}}{{end}}#status-{{.InReplyToID}}"> + <a class="status-reply-to-link" href="{{if not .ShowReplies}}/thread/{{.InReplyToID}}{{end}}#status-{{.InReplyToID}}" target="main"> reply to {{.Pleroma.InReplyToAccountAcct}} </a> </div> @@ -139,16 +139,16 @@ {{end}} <div class="status-action-container"> <div class="status-action"> - <a href="/thread/{{.ID}}?reply=true#status-{{.ID}}" title="reply"> + <a href="/thread/{{.ID}}?reply=true#status-{{.ID}}" target="main"> reply </a> - <a class="status-reply-count" href="/thread/{{.ID}}#status-{{.ID}}" {{if $.Ctx.ThreadInNewTab}}target="_blank"{{end}}> + <a class="status-reply-count" href="/thread/{{.ID}}#status-{{.ID}}" target="{{if $.Ctx.ThreadInNewTab}}_blank{{else}}main{{end}}"> {{if .RepliesCount}} ({{DisplayInteractionCount .RepliesCount}}) {{end}} </a> </div> <div class="status-action"> {{if or (eq .Visibility "private") (eq .Visibility "direct")}} - <a class="status-retweet" title="this status cannot be retweeted"> + <a class="status-retweet" href="" title="this status cannot be retweeted" target="main"> retweet </a> {{else}} @@ -156,17 +156,17 @@ <form class="status-retweet" data-action="unretweet" action="/unretweet/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}"> - <input type="submit" value="unretweet" class="btn-link" title="unretweet"> + <input type="submit" value="unretweet" class="btn-link"> </form> {{else}} <form class="status-retweet" data-action="retweet" action="/retweet/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}"> - <input type="submit" value="retweet" class="btn-link" title="retweet"> + <input type="submit" value="retweet" class="btn-link"> </form> {{end}} {{end}} - <a class="status-retweet-count" href="/retweetedby/{{.ID}}" title="click to see the the list"> + <a class="status-retweet-count" href="/retweetedby/{{.ID}}" title="click to see the the list" target="main"> {{if .ReblogsCount}} ({{DisplayInteractionCount .ReblogsCount}}) {{end}} </a> </div> @@ -175,22 +175,22 @@ <form class="status-like" data-action="unlike" action="/unlike/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}"> - <input type="submit" value="unlike" class="btn-link" title="unlike"> + <input type="submit" value="unlike" class="btn-link"> </form> {{else}} <form class="status-like" data-action="like" action="/like/{{.ID}}" method="post"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}"> - <input type="submit" value="like" class="btn-link" title="like"> + <input type="submit" value="like" class="btn-link"> </form> {{end}} - <a class="status-like-count" href="/likedby/{{.ID}}" title="click to see the the list"> + <a class="status-like-count" href="/likedby/{{.ID}}" title="click to see the the list" target="main"> {{if .FavouritesCount}} ({{DisplayInteractionCount .FavouritesCount}}) {{end}} </a> </div> <div class="status-action"> <a class="status-time" href="{{if not .ShowReplies}}/thread/{{.ID}}{{end}}#status-{{.ID}}" - {{if $.Ctx.ThreadInNewTab}}target="_blank"{{end}}> + target="{{if $.Ctx.ThreadInNewTab}}_blank{{else}}main{{end}}"> <time datetime="{{FormatTimeRFC3339 .CreatedAt}}" title="{{FormatTimeRFC822 .CreatedAt}}"> {{TimeSince .CreatedAt}} </time> diff --git a/templates/thread.tmpl b/templates/thread.tmpl index 2927ee4..8cf9ead 100644 --- a/templates/thread.tmpl +++ b/templates/thread.tmpl @@ -1,6 +1,5 @@ {{with $s := .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Thread </div> {{range .Statuses}} diff --git a/templates/timeline.tmpl b/templates/timeline.tmpl index 0321c7f..82b624d 100644 --- a/templates/timeline.tmpl +++ b/templates/timeline.tmpl @@ -1,10 +1,7 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> {{.Title}} </div> -{{template "postform.tmpl" (WithContext .PostContext $.Ctx)}} - {{range .Statuses}} {{template "status.tmpl" (WithContext . $.Ctx)}} {{end}} diff --git a/templates/user.tmpl b/templates/user.tmpl index de2b5c4..6ea79f7 100644 --- a/templates/user.tmpl +++ b/templates/user.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> User </div> <div class="user-info-container"> diff --git a/templates/usersearch.tmpl b/templates/usersearch.tmpl index 8e19fd1..ca99b4c 100644 --- a/templates/usersearch.tmpl +++ b/templates/usersearch.tmpl @@ -1,6 +1,5 @@ {{with .Data}} -{{template "header.tmpl" (WithContext .HeaderData $.Ctx)}} -{{template "navigation.tmpl" (WithContext .NavbarData $.Ctx)}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} <div class="page-title"> Search {{EmojiFilter .User.DisplayName .User.Emojis}}'s statuses </div> <form class="search-form" action="/usersearch/{{.User.ID}}" method="GET"> |