Add fluoride mode
authorr <r@freesoftwareextremist.com>
Wed, 8 Jan 2020 18:16:06 +0000 (18:16 +0000)
committerr <r@freesoftwareextremist.com>
Wed, 8 Jan 2020 18:16:06 +0000 (18:16 +0000)
model/settings.go
renderer/model.go
service/auth.go
service/logging.go
service/service.go
service/transport.go
static/fluoride.js [new file with mode: 0644]
static/main.css
templates/header.tmpl
templates/settings.tmpl
templates/status.tmpl

index 02bebcb46806ff7ea8ccc98cab1f4f191c103416..b8eeffc5c42f4b509cd93fc33b01b44e40b101b8 100644 (file)
@@ -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,
        }
 }
index f086e1d05e051c59bce824ba7d666c21014db417..102ce55a87beab8d87fe63211bcdb79116f42609 100644 (file)
@@ -9,6 +9,7 @@ type HeaderData struct {
        Title             string
        NotificationCount int
        CustomCSS         string
+       FluorideMode      bool
 }
 
 type NavbarData struct {
index 2aa71d93403d0ede277b536673d802bf931b6985..2f637175f9f464b2ea33b61025120f2f9eb7c99c 100644 (file)
@@ -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
index 7d53f8901146880a13299adba212e7628585b758..27c038d4ee26f5c7b53e26af18e3e5d0fe80f2bc 100644 (file)
@@ -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)
index 47e3de99bf601b87aab085b590c8065702ad023b..301e33d0327370fd9821bb452090f5e0b43cb750 100644 (file)
@@ -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
 }
 
index 041330d4824df1c9606932c4b2f3da74ab3e1dfb..d89b8547b2451922ac2a67a149a62ac86e672cd0 100644 (file)
@@ -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 (file)
index 0000000..3020da0
--- /dev/null
@@ -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);
+       });
+});
index e82586dedc5299759c7307b1d1a13c51237194a2..12f2a80bc0f47afe823f9efc548012a45028ad33 100644 (file)
        width: auto;
 }
 
-.status-action-count span {
+.status-reply-count,
+.status-retweet-count,
+.status-like-count {
        vertical-align: middle;
 }
 
index 1ff15d61151965fc20012bfe4a5ddef923a4b985..d51159056243a95ce09349998356a4abb06728c2 100644 (file)
@@ -8,5 +8,8 @@
        {{if .CustomCSS}}
        <link rel="stylesheet" href="{{.CustomCSS}}">
        {{end}}
+       {{if .FluorideMode}}
+       <script src="/static/fluoride.js"></script>
+       {{end}}
 </head>
 <body>
index c4a1012bdfd4a3445b3e885379a4d720e4afe1e8..d15c47b306b51dd762c9c0a068b196160690e702 100644 (file)
                <input id="mask-nsfw" name="mask_nsfw" type="checkbox" value="true" {{if .Settings.MaskNSFW}}checked{{end}}>
                <label for="mask-nsfw"> Mask NSFW Attachments </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>
+       </div>
        <button type="submit"> Save </button>
 </form>
 
index 10b7d40bfaa36bff0b7b109772be0359f65231e7..669fca840606f22c593226e4f1caacf88303c50b 100644 (file)
@@ -11,7 +11,7 @@
        {{template "status" .Reblog}}
        {{else}}
        {{block "status" .}}
-       <div class="status-container">
+       <div class="status-container status-{{.ID}}" data-id="{{.ID}}">
                {{if not .HideAccountInfo}}
                <div class="status-profile-img-container">
                        <a class="img-link" href="/user/{{.Account.ID}}">
@@ -90,8 +90,8 @@
                                        <a class="status-you" href="/thread/{{.ID}}?reply=true#status-{{.ID}}" title="reply"> 
                                                <img class="icon" src="/static/icons/reply.png" alt="reply" />
                                        </a>
-                                       <a class="status-action-count" href="/thread/{{.ID}}#status-{{.ID}}" {{if .ThreadInNewTab}}target="_blank"{{end}}>
-                                               <span> {{DisplayInteractionCount .RepliesCount}} </span>
+                                       <a class="status-reply-count" href="/thread/{{.ID}}#status-{{.ID}}" {{if .ThreadInNewTab}}target="_blank"{{end}}>
+                                               {{DisplayInteractionCount .RepliesCount}}
                                        </a>
                                </div>
                                <div class="status-action">
                                        </a>
                                        {{else}}
                                        {{if .Reblogged}}
-                                       <form class="status-retweet" action="/unretweet/{{.ID}}" method="post">
+                                       <form class="status-retweet" data-action="unretweet" action="/unretweet/{{.ID}}" method="post">
                                                <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
                                                <input type="image" src="/static/icons/retweeted.png" alt="undo retweet" class="icon" title="undo retweet">
                                        </form>
                                        {{else}}
-                                       <form class="status-retweet" action="/retweet/{{.ID}}" method="post">
+                                       <form class="status-retweet" data-action="retweet" action="/retweet/{{.ID}}" method="post">
                                                <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
                                                <input type="image" src="/static/icons/retweet.png" alt="retweet" class="icon" title="retweet">
                                        </form>
                                        {{end}}
                                        {{end}}
-                                       <a class="status-action-count" href="/retweetedby/{{.ID}}" title="click to see the the list"> 
-                                               <span> {{DisplayInteractionCount .ReblogsCount}} </span>
+                                       <a class="status-retweet-count" href="/retweetedby/{{.ID}}" title="click to see the the list"> 
+                                               {{DisplayInteractionCount .ReblogsCount}}
                                        </a>
                                </div>
                                <div class="status-action">
                                        {{if .Favourited}}
-                                       <form class="status-like" action="/unlike/{{.ID}}" method="post">
+                                       <form class="status-like" data-action="unlike" action="/unlike/{{.ID}}" method="post">
                                                <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
                                                <input type="image" src="/static/icons/liked.png" alt="unlike" class="icon" title="unlike">
                                        </form>
                                        {{else}}
-                                       <form class="status-like" action="/like/{{.ID}}" method="post">
+                                       <form class="status-like" data-action="like" action="/like/{{.ID}}" method="post">
                                                <input type="hidden" name="retweeted_by_id" value="{{.RetweetedByID}}" />
                                                <input type="image" src="/static/icons/star-o.png" alt="like" class="icon" title="like">
                                        </form>
                                        {{end}}
-                                       <a class="status-action-count" href="/likedby/{{.ID}}" title="click to see the the list"> 
-                                               <span> {{DisplayInteractionCount .FavouritesCount}} </span>
+                                       <a class="status-like-count" href="/likedby/{{.ID}}" title="click to see the the list"> 
+                                               {{DisplayInteractionCount .FavouritesCount}}
                                        </a>
                                </div>
                                <div class="status-action">