Add settings page
authorr <r@freesoftwareextremist.com>
Fri, 27 Dec 2019 08:06:43 +0000 (08:06 +0000)
committerr <r@freesoftwareextremist.com>
Fri, 27 Dec 2019 08:09:40 +0000 (08:09 +0000)
model/settings.go
renderer/model.go
renderer/renderer.go
service/auth.go
service/logging.go
service/service.go
service/transport.go
static/main.css
templates/navigation.tmpl
templates/postform.tmpl
templates/settings.tmpl [new file with mode: 0644]

index 7afe936e310d0bd2aba65d891edc34622e0040cb..bc8f09fbdea017cb9bcbc8e32bf5d692778291a3 100644 (file)
@@ -2,4 +2,5 @@ package model
 
 type Settings struct {
        DefaultVisibility string `json:"default_visibility"`
+       CopyScope         bool   `json:"copy_scope"`
 }
index ffeb2d1b376ca0d63d31124643bdcad02be6b21d..12fa90f5b65e74efcb369c6d908954fe98eaf15a 100644 (file)
@@ -92,10 +92,15 @@ type RetweetedByData struct {
 
 type SearchData struct {
        *CommonData
-       Q         string
-       Type      string
-       Users     []*mastodon.Account
-       Statuses  []*mastodon.Status
-       HasNext bool
+       Q        string
+       Type     string
+       Users    []*mastodon.Account
+       Statuses []*mastodon.Status
+       HasNext  bool
        NextLink string
 }
+
+type SettingsData struct {
+       *CommonData
+       Settings *model.Settings
+}
index 5e5f005dc145836d04b3d19a56a72403a261c354..a46cfd25aba47dcb60be98684d8b079c87976073 100644 (file)
@@ -24,6 +24,7 @@ type Renderer interface {
        RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error)
        RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error)
        RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error)
+       RenderSettingsPage(ctx context.Context, writer io.Writer, data *SettingsData) (err error)
 }
 
 type renderer struct {
@@ -96,6 +97,10 @@ func (r *renderer) RenderSearchPage(ctx context.Context, writer io.Writer, data
        return r.template.ExecuteTemplate(writer, "search.tmpl", data)
 }
 
+func (r *renderer) RenderSettingsPage(ctx context.Context, writer io.Writer, data *SettingsData) (err error) {
+       return r.template.ExecuteTemplate(writer, "settings.tmpl", data)
+}
+
 func EmojiFilter(content string, emojis []mastodon.Emoji) string {
        var replacements []string
        for _, e := range emojis {
index 431e093237ccb649d34e32dfb6a7db5640dc1dfa..4d1b5af86b5715b8c35fe004c6cbda92838e8f61 100644 (file)
@@ -157,6 +157,22 @@ func (s *authService) ServeSearchPage(ctx context.Context, client io.Writer, c *
        return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
 }
 
+func (s *authService) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+       c, err = s.getClient(ctx)
+       if err != nil {
+               return
+       }
+       return s.Service.ServeSettingsPage(ctx, client, c)
+}
+
+func (s *authService) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
+       c, err = s.getClient(ctx)
+       if err != nil {
+               return
+       }
+       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) {
        c, err = s.getClient(ctx)
        if err != nil {
index a5ffd57915e534659b76efeff438a80d074a871c..cfd3654645bba3806059e6ed6fc42be700e17e8a 100644 (file)
@@ -133,6 +133,22 @@ func (s *loggingService) ServeSearchPage(ctx context.Context, client io.Writer,
        return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
 }
 
+func (s *loggingService) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+       defer func(begin time.Time) {
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "ServeSettingsPage", time.Since(begin), err)
+       }(time.Now())
+       return s.Service.ServeSettingsPage(ctx, client, c)
+}
+
+func (s *loggingService) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
+       defer func(begin time.Time) {
+               s.logger.Printf("method=%v, took=%v, err=%v\n",
+                       "SaveSettings", time.Since(begin), err)
+       }(time.Now())
+       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) {
        defer func(begin time.Time) {
                s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",
index 157ddf8be7c70d16f96d6baac19ae81adb9f48f7..bf7967d38bdc72fb93a302d18046d0d0c982483f 100644 (file)
@@ -40,6 +40,8 @@ type Service interface {
        ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
        ServeRetweetedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
        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)
@@ -350,13 +352,19 @@ func (svc *service) ServeThreadPage(ctx context.Context, client io.Writer, c *mo
                        }
                }
 
-               s, err := c.GetStatus(ctx, id)
-               if err != nil {
-                       return err
+               var visibility string
+               if c.Session.Settings.CopyScope {
+                       s, err := c.GetStatus(ctx, id)
+                       if err != nil {
+                               return err
+                       }
+                       visibility = s.Visibility
+               } else {
+                       visibility = c.Session.Settings.DefaultVisibility
                }
 
                postContext = model.PostContext{
-                       DefaultVisibility: s.Visibility,
+                       DefaultVisibility: visibility,
                        Formats:           svc.postFormats,
                        ReplyContext: &model.ReplyContext{
                                InReplyToID:   id,
@@ -639,6 +647,40 @@ func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *mo
        return
 }
 
+func (svc *service) ServeSettingsPage(ctx context.Context, client io.Writer, c *model.Client) (err error) {
+       commonData, err := svc.getCommonData(ctx, client, c)
+       if err != nil {
+               return
+       }
+
+       data := &renderer.SettingsData{
+               CommonData: commonData,
+               Settings:   &c.Session.Settings,
+       }
+
+       err = svc.renderer.RenderSettingsPage(ctx, client, data)
+       if err != nil {
+               return
+       }
+
+       return
+}
+
+func (svc *service) SaveSettings(ctx context.Context, client io.Writer, c *model.Client, settings *model.Settings) (err error) {
+       session, err := svc.sessionRepo.Get(c.Session.ID)
+       if err != nil {
+               return
+       }
+
+       session.Settings = *settings
+       err = svc.sessionRepo.Add(session)
+       if err != nil {
+               return
+       }
+
+       return
+}
+
 func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *model.Client) (data *renderer.CommonData, err error) {
        data = new(renderer.CommonData)
 
@@ -707,12 +749,6 @@ func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *model.Cl
                mediaIds = append(mediaIds, a.ID)
        }
 
-       // save visibility if it's a non-reply post
-       if len(replyToID) < 1 && visibility != c.Session.Settings.DefaultVisibility {
-               c.Session.Settings.DefaultVisibility = visibility
-               svc.sessionRepo.Add(c.Session)
-       }
-
        tweet := &mastodon.Toot{
                Status:      content,
                InReplyToID: replyToID,
index c42462f11ff9d3861c4ba019e8d849e92470f60b..3ab25467e0fa6b91fdaad7977fb0a930ff089e4b 100644 (file)
@@ -7,6 +7,7 @@ import (
        "net/http"
        "path"
        "strconv"
+       "web/model"
 
        "github.com/gorilla/mux"
 )
@@ -305,6 +306,36 @@ func NewHandler(s Service, staticDir string) http.Handler {
                }
        }).Methods(http.MethodGet)
 
+       r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
+               ctx := getContextWithSession(context.Background(), req)
+
+               err := s.ServeSettingsPage(ctx, w, nil)
+               if err != nil {
+                       s.ServeErrorPage(ctx, w, err)
+                       return
+               }
+       }).Methods(http.MethodGet)
+
+       r.HandleFunc("/settings", func(w http.ResponseWriter, req *http.Request) {
+               ctx := getContextWithSession(context.Background(), req)
+
+               visibility := req.FormValue("visibility")
+               copyScope := req.FormValue("copy_scope") == "true"
+               settings := &model.Settings{
+                       DefaultVisibility: visibility,
+                       CopyScope: copyScope,
+               }
+
+               err := s.SaveSettings(ctx, w, nil, settings)
+               if err != nil {
+                       s.ServeErrorPage(ctx, w, err)
+                       return
+               }
+
+               w.Header().Add("Location", req.Header.Get("Referer"))
+               w.WriteHeader(http.StatusFound)
+       }).Methods(http.MethodPost)
+
        r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
                // TODO remove session from database
                w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))
index 87e2cfa0fd92409d5ee7b58d28e21e6cf5bfc841..f17a17116705cd122b9d6e4e7d5f991c766216b9 100644 (file)
        vertical-align: top;
        margin: 0 4px 8px 0;
 }
+
+#settings-form {
+       margin: 8px 0;
+}
+
+.settings-form-field {
+       margin: 4px 0;
+}
+
+.settings-form-field * {
+       vertical-align: middle;
+}
+
+#settings-form button[type=submit] {
+       margin-top: 8px;
+}
index b82c7f53f60faea8470ed539da64bf257801f4ec..fe834761decbb01908fab03b09de192648ce1a3b 100644 (file)
@@ -20,6 +20,7 @@
                        <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>
index 0e104b46bb1bc9802da54408c86ca9dec398374c..a79f86bd50c45e1c791db26fbe3704320c877361 100644 (file)
@@ -21,7 +21,7 @@
                </span>
                {{end}}
                <span class="post-form-field">
-                       <label for="post-visilibity"> Visibility </label>
+                       <label for="post-visilibity"> Scope </label>
                        <select id="post-visilibity" name="visibility">
                                <option value="public" {{if eq .DefaultVisibility "public"}}selected{{end}}>Public</option>
                                <option value="unlisted" {{if eq .DefaultVisibility "unlisted"}}selected{{end}}>Unlisted</option>
diff --git a/templates/settings.tmpl b/templates/settings.tmpl
new file mode 100644 (file)
index 0000000..e2fd778
--- /dev/null
@@ -0,0 +1,22 @@
+{{template "header.tmpl" .HeaderData}}
+{{template "navigation.tmpl" .NavbarData}}
+<div class="page-title"> Settings </div>
+
+<form id="settings-form" action="/settings" method="POST">
+       <div class="settings-form-field">
+               <label for="visibility"> Default scope </label>
+               <select id="visibility" name="visibility">
+                       <option value="public" {{if eq .Settings.DefaultVisibility "public"}}selected{{end}}>Public</option>
+                       <option value="unlisted" {{if eq .Settings.DefaultVisibility "unlisted"}}selected{{end}}>Unlisted</option>
+                       <option value="private" {{if eq .Settings.DefaultVisibility "private"}}selected{{end}}>Private</option>
+                       <option value="direct" {{if eq .Settings.DefaultVisibility "direct"}}selected{{end}}>Direct</option>
+               </select>
+       </div>
+       <div class="settings-form-field">
+               <input id="copy-scope" name="copy_scope" type="checkbox" value="true" {{if .Settings.CopyScope}}checked{{end}}>
+               <label for="copy-scope"> Copy scope when replying </label>
+       </div>
+       <button type="submit"> Save </button>
+</form>
+
+{{template "footer.tmpl"}}