From a8dbbec988cf1ed42a051d1a0dfa9987d890d440 Mon Sep 17 00:00:00 2001 From: r Date: Wed, 8 Jan 2020 18:16:06 +0000 Subject: Add fluoride mode --- model/settings.go | 2 + renderer/model.go | 1 + service/auth.go | 8 ++-- service/logging.go | 8 ++-- service/service.go | 43 ++++++++++++++------ service/transport.go | 74 +++++++++++++++++++++++++++++++++ static/fluoride.js | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ static/main.css | 4 +- templates/header.tmpl | 3 ++ templates/settings.tmpl | 4 ++ templates/status.tmpl | 22 +++++----- 11 files changed, 243 insertions(+), 32 deletions(-) create mode 100644 static/fluoride.js diff --git a/model/settings.go b/model/settings.go index 02bebcb..b8eeffc 100644 --- a/model/settings.go +++ b/model/settings.go @@ -5,6 +5,7 @@ type Settings struct { CopyScope bool `json:"copy_scope"` ThreadInNewTab bool `json:"thread_in_new_tab"` MaskNSFW bool `json:"mask_nfsw"` + FluorideMode bool `json:"fluoride_mode"` } func NewSettings() *Settings { @@ -13,5 +14,6 @@ func NewSettings() *Settings { CopyScope: true, ThreadInNewTab: false, MaskNSFW: true, + FluorideMode: false, } } diff --git a/renderer/model.go b/renderer/model.go index f086e1d..102ce55 100644 --- a/renderer/model.go +++ b/renderer/model.go @@ -9,6 +9,7 @@ type HeaderData struct { Title string NotificationCount int CustomCSS string + FluorideMode bool } type NavbarData struct { diff --git a/service/auth.go b/service/auth.go index 2aa71d9..2f63717 100644 --- a/service/auth.go +++ b/service/auth.go @@ -190,7 +190,7 @@ func (s *authService) SaveSettings(ctx context.Context, client io.Writer, c *mod return s.Service.SaveSettings(ctx, client, c, settings) } -func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { c, err = s.getClient(ctx) if err != nil { return @@ -198,7 +198,7 @@ func (s *authService) Like(ctx context.Context, client io.Writer, c *model.Clien return s.Service.Like(ctx, client, c, id) } -func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { c, err = s.getClient(ctx) if err != nil { return @@ -206,7 +206,7 @@ func (s *authService) UnLike(ctx context.Context, client io.Writer, c *model.Cli return s.Service.UnLike(ctx, client, c, id) } -func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { c, err = s.getClient(ctx) if err != nil { return @@ -214,7 +214,7 @@ func (s *authService) Retweet(ctx context.Context, client io.Writer, c *model.Cl return s.Service.Retweet(ctx, client, c, id) } -func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { c, err = s.getClient(ctx) if err != nil { return diff --git a/service/logging.go b/service/logging.go index 7d53f89..27c038d 100644 --- a/service/logging.go +++ b/service/logging.go @@ -166,7 +166,7 @@ func (s *loggingService) SaveSettings(ctx context.Context, client io.Writer, c * return s.Service.SaveSettings(ctx, client, c, settings) } -func (s *loggingService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *loggingService) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { defer func(begin time.Time) { s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", "Like", id, time.Since(begin), err) @@ -174,7 +174,7 @@ func (s *loggingService) Like(ctx context.Context, client io.Writer, c *model.Cl return s.Service.Like(ctx, client, c, id) } -func (s *loggingService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *loggingService) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { defer func(begin time.Time) { s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", "UnLike", id, time.Since(begin), err) @@ -182,7 +182,7 @@ func (s *loggingService) UnLike(ctx context.Context, client io.Writer, c *model. return s.Service.UnLike(ctx, client, c, id) } -func (s *loggingService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *loggingService) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { defer func(begin time.Time) { s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", "Retweet", id, time.Since(begin), err) @@ -190,7 +190,7 @@ func (s *loggingService) Retweet(ctx context.Context, client io.Writer, c *model return s.Service.Retweet(ctx, client, c, id) } -func (s *loggingService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { +func (s *loggingService) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { defer func(begin time.Time) { s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", "UnRetweet", id, time.Since(begin), err) diff --git a/service/service.go b/service/service.go index 47e3de9..301e33d 100644 --- a/service/service.go +++ b/service/service.go @@ -44,10 +44,10 @@ type Service interface { ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) - Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) - UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) - Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) - UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) + Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) + UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) + Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) + UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) PostTweet(ctx context.Context, client io.Writer, c *model.Client, content string, replyToID string, format string, visibility string, isNSFW bool, files []*multipart.FileHeader) (id string, err error) Follow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) UnFollow(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) @@ -795,6 +795,7 @@ func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *mode Title: "Web", NotificationCount: 0, CustomCSS: svc.customCSS, + FluorideMode: c.Session.Settings.FluorideMode, } if c != nil && c.Session.IsLoggedIn() { @@ -826,23 +827,41 @@ func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *mode return } -func (svc *service) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { - _, err = c.Favourite(ctx, id) +func (svc *service) Like(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { + s, err := c.Favourite(ctx, id) + if err != nil { + return + } + count = s.FavouritesCount return } -func (svc *service) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { - _, err = c.Unfavourite(ctx, id) +func (svc *service) UnLike(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { + s, err := c.Unfavourite(ctx, id) + if err != nil { + return + } + count = s.FavouritesCount return } -func (svc *service) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { - _, err = c.Reblog(ctx, id) +func (svc *service) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { + s, err := c.Reblog(ctx, id) + if err != nil { + return + } + if s.Reblog != nil { + count = s.Reblog.ReblogsCount + } return } -func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) { - _, err = c.Unreblog(ctx, id) +func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *model.Client, id string) (count int64, err error) { + s, err := c.Unreblog(ctx, id) + if err != nil { + return + } + count = s.ReblogsCount return } diff --git a/service/transport.go b/service/transport.go index 041330d..d89b854 100644 --- a/service/transport.go +++ b/service/transport.go @@ -2,6 +2,8 @@ package service import ( "context" + "encoding/json" + "io" "mime/multipart" "net/http" "path" @@ -232,6 +234,70 @@ func NewHandler(s Service, staticDir string) http.Handler { w.WriteHeader(http.StatusFound) }).Methods(http.MethodPost) + r.HandleFunc("/fluoride/like/{id}", func(w http.ResponseWriter, req *http.Request) { + ctx := getContextWithSession(context.Background(), req) + id, _ := mux.Vars(req)["id"] + count, err := s.Like(ctx, w, nil, id) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + + err = serveJson(w, count) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + }).Methods(http.MethodPost) + + r.HandleFunc("/fluoride/unlike/{id}", func(w http.ResponseWriter, req *http.Request) { + ctx := getContextWithSession(context.Background(), req) + id, _ := mux.Vars(req)["id"] + count, err := s.UnLike(ctx, w, nil, id) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + + err = serveJson(w, count) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + }).Methods(http.MethodPost) + + r.HandleFunc("/fluoride/retweet/{id}", func(w http.ResponseWriter, req *http.Request) { + ctx := getContextWithSession(context.Background(), req) + id, _ := mux.Vars(req)["id"] + count, err := s.Retweet(ctx, w, nil, id) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + + err = serveJson(w, count) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + }).Methods(http.MethodPost) + + r.HandleFunc("/fluoride/unretweet/{id}", func(w http.ResponseWriter, req *http.Request) { + ctx := getContextWithSession(context.Background(), req) + id, _ := mux.Vars(req)["id"] + count, err := s.UnRetweet(ctx, w, nil, id) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + + err = serveJson(w, count) + if err != nil { + s.ServeErrorPage(ctx, w, err) + return + } + }).Methods(http.MethodPost) + r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) { ctx := getContextWithSession(context.Background(), req) @@ -381,11 +447,13 @@ 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" + fluorideMode := req.FormValue("fluoride_mode") == "true" settings := &model.Settings{ DefaultVisibility: visibility, CopyScope: copyScope, ThreadInNewTab: threadInNewTab, MaskNSFW: maskNSFW, + FluorideMode: fluorideMode, } err := s.SaveSettings(ctx, w, nil, settings) @@ -430,3 +498,9 @@ func getMultipartFormValue(mf *multipart.Form, key string) (val string) { } return vals[0] } + +func serveJson(w io.Writer, data interface{}) (err error) { + var d = make(map[string]interface{}) + d["data"] = data + return json.NewEncoder(w).Encode(d) +} diff --git a/static/fluoride.js b/static/fluoride.js new file mode 100644 index 0000000..3020da0 --- /dev/null +++ b/static/fluoride.js @@ -0,0 +1,106 @@ +var actionIcons = { + "like": "/static/icons/star-o.png", + "unlike": "/static/icons/liked.png", + "retweet": "/static/icons/retweet.png", + "unretweet": "/static/icons/retweeted.png" +}; + +var reverseActions = { + "like": "unlike", + "unlike": "like", + "retweet": "unretweet", + "unretweet": "retweet" +}; + +function http(method, url, success, error) { + var req = new XMLHttpRequest(); + req.onload = function() { + if (this.status === 200 && typeof success === "function") { + success(this.responseText, this.responseType); + } else if (typeof error === "function") { + error(this.responseText); + } + }; + req.onerror = function() { + if (typeof error === "function") { + error(this.responseText); + } + }; + req.open(method, url); + req.send(); +} + +function updateActionForm(id, f, action) { + f.children[1].src = actionIcons[action]; + f.action = "/" + action + "/" + id; + f.dataset.action = action; +} + +function handleLikeForm(id, f) { + f.onsubmit = function(event) { + event.preventDefault(); + + var action = f.dataset.action; + var forms = document.querySelectorAll(".status-"+id+" .status-like"); + forms.forEach(function(f) { + updateActionForm(id, f, reverseActions[action]); + }); + + http("POST", "/fluoride/" + action + "/" + id, function(res, type) { + var data = JSON.parse(res); + var count = data.data; + if (count === 0) { + count = ""; + } + var counts = document.querySelectorAll(".status-"+id+" .status-like-count"); + counts.forEach(function(c) { + c.innerHTML = count; + }); + }, function(err) { + forms.forEach(function(f) { + updateActionForm(id, f, action); + }); + }); + } +} + +function handleRetweetForm(id, f) { + f.onsubmit = function(event) { + event.preventDefault(); + + var action = f.dataset.action; + var forms = document.querySelectorAll(".status-"+id+" .status-retweet"); + forms.forEach(function(f) { + updateActionForm(id, f, reverseActions[action]); + }); + + http("POST", "/fluoride/" + action + "/" + id, function(res, type) { + var data = JSON.parse(res); + var count = data.data; + if (count === 0) { + count = ""; + } + var counts = document.querySelectorAll(".status-"+id+" .status-retweet-count"); + counts.forEach(function(c) { + c.innerHTML = count; + }); + }, function(err) { + forms.forEach(function(f) { + updateActionForm(id, f, action); + }); + }); + } +} + +document.addEventListener("DOMContentLoaded", function() { + var statuses = document.querySelectorAll(".status-container"); + statuses.forEach(function(s) { + var id = s.dataset.id; + + var likeForm = s.querySelector(".status-like"); + handleLikeForm(id, likeForm); + + var retweetForm = s.querySelector(".status-retweet"); + handleRetweetForm(id, retweetForm); + }); +}); diff --git a/static/main.css b/static/main.css index e82586d..12f2a80 100644 --- a/static/main.css +++ b/static/main.css @@ -105,7 +105,9 @@ width: auto; } -.status-action-count span { +.status-reply-count, +.status-retweet-count, +.status-like-count { vertical-align: middle; } diff --git a/templates/header.tmpl b/templates/header.tmpl index 1ff15d6..d511590 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -8,5 +8,8 @@ {{if .CustomCSS}} {{end}} + {{if .FluorideMode}} + + {{end}} diff --git a/templates/settings.tmpl b/templates/settings.tmpl index c4a1012..d15c47b 100644 --- a/templates/settings.tmpl +++ b/templates/settings.tmpl @@ -24,6 +24,10 @@ +
+ + +
diff --git a/templates/status.tmpl b/templates/status.tmpl index 10b7d40..669fca8 100644 --- a/templates/status.tmpl +++ b/templates/status.tmpl @@ -11,7 +11,7 @@ {{template "status" .Reblog}} {{else}} {{block "status" .}} -
+