diff options
-rw-r--r-- | mastodon/lists.go | 4 | ||||
-rw-r--r-- | mastodon/status.go | 3 | ||||
-rw-r--r-- | renderer/model.go | 13 | ||||
-rw-r--r-- | renderer/renderer.go | 2 | ||||
-rw-r--r-- | service/service.go | 106 | ||||
-rw-r--r-- | service/transport.go | 76 | ||||
-rw-r--r-- | static/style.css | 11 | ||||
-rw-r--r-- | templates/about.tmpl | 4 | ||||
-rw-r--r-- | templates/list.tmpl | 63 | ||||
-rw-r--r-- | templates/lists.tmpl | 35 | ||||
-rw-r--r-- | templates/nav.tmpl | 9 | ||||
-rw-r--r-- | templates/userlist.tmpl | 14 | ||||
-rw-r--r-- | templates/userlistitem.tmpl | 15 |
13 files changed, 322 insertions, 33 deletions
diff --git a/mastodon/lists.go b/mastodon/lists.go index d323b79..1b76bdc 100644 --- a/mastodon/lists.go +++ b/mastodon/lists.go @@ -90,7 +90,7 @@ func (c *Client) DeleteList(ctx context.Context, id string) error { func (c *Client) AddToList(ctx context.Context, list string, accounts ...string) error { params := url.Values{} for _, acct := range accounts { - params.Add("account_ids", string(acct)) + params.Add("account_ids[]", string(acct)) } return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/lists/%s/accounts", url.PathEscape(string(list))), params, nil, nil) @@ -100,7 +100,7 @@ func (c *Client) AddToList(ctx context.Context, list string, accounts ...string) func (c *Client) RemoveFromList(ctx context.Context, list string, accounts ...string) error { params := url.Values{} for _, acct := range accounts { - params.Add("account_ids", string(acct)) + params.Add("account_ids[]", string(acct)) } return c.doAPI(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/lists/%s/accounts", url.PathEscape(string(list))), params, nil, nil) diff --git a/mastodon/status.go b/mastodon/status.go index 8b148b3..2fae6ee 100644 --- a/mastodon/status.go +++ b/mastodon/status.go @@ -301,7 +301,7 @@ func (c *Client) DeleteStatus(ctx context.Context, id string) error { } // Search search content with query. -func (c *Client) Search(ctx context.Context, q string, qType string, limit int, resolve bool, offset int, accountID string) (*Results, error) { +func (c *Client) Search(ctx context.Context, q string, qType string, limit int, resolve bool, offset int, accountID string, following bool) (*Results, error) { var results Results params := url.Values{} params.Set("q", q) @@ -309,6 +309,7 @@ func (c *Client) Search(ctx context.Context, q string, qType string, limit int, params.Set("limit", fmt.Sprint(limit)) params.Set("resolve", fmt.Sprint(resolve)) params.Set("offset", fmt.Sprint(offset)) + params.Set("following", fmt.Sprint(following)) if len(accountID) > 0 { params.Set("account_id", accountID) } diff --git a/renderer/model.go b/renderer/model.go index 4d09338..e7cfbfb 100644 --- a/renderer/model.go +++ b/renderer/model.go @@ -62,6 +62,19 @@ type TimelineData struct { PrevLink string } +type ListsData struct { + *CommonData + Lists []*mastodon.List +} + +type ListData struct { + *CommonData + List *mastodon.List + Accounts []*mastodon.Account + Q string + SearchAccounts []*mastodon.Account +} + type ThreadData struct { *CommonData Statuses []*mastodon.Status diff --git a/renderer/renderer.go b/renderer/renderer.go index 0d6776c..20a3c05 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -19,6 +19,8 @@ const ( NavPage = "nav.tmpl" RootPage = "root.tmpl" TimelinePage = "timeline.tmpl" + ListsPage = "lists.tmpl" + ListPage = "list.tmpl" ThreadPage = "thread.tmpl" QuickReplyPage = "quickreply.tmpl" NotificationPage = "notification.tmpl" diff --git a/service/service.go b/service/service.go index 8297764..64c7bf0 100644 --- a/service/service.go +++ b/service/service.go @@ -163,8 +163,8 @@ func (s *service) NavPage(c *client) (err error) { return s.renderer.Render(c.rctx, c.w, renderer.NavPage, data) } -func (s *service) TimelinePage(c *client, tType string, instance string, - maxID string, minID string) (err error) { +func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, + minID string) (err error) { var nextLink, prevLink, title string var statuses []*mastodon.Status @@ -179,24 +179,46 @@ func (s *service) TimelinePage(c *client, tType string, instance string, return errInvalidArgument case "home": statuses, err = c.GetTimelineHome(c.ctx, &pg) + if err != nil { + return err + } title = "Timeline" case "direct": statuses, err = c.GetTimelineDirect(c.ctx, &pg) + if err != nil { + return err + } title = "Direct Timeline" case "local": statuses, err = c.GetTimelinePublic(c.ctx, true, "", &pg) + if err != nil { + return err + } title = "Local Timeline" case "remote": if len(instance) > 0 { statuses, err = c.GetTimelinePublic(c.ctx, false, instance, &pg) + if err != nil { + return err + } } title = "Remote Timeline" case "twkn": statuses, err = c.GetTimelinePublic(c.ctx, false, "", &pg) + if err != nil { + return err + } title = "The Whole Known Network" - } - if err != nil { - return err + case "list": + statuses, err = c.GetTimelineList(c.ctx, listId, &pg) + if err != nil { + return err + } + list, err := c.GetList(c.ctx, listId) + if err != nil { + return err + } + title = "List Timeline - " + list.Title } for i := range statuses { @@ -211,6 +233,9 @@ func (s *service) TimelinePage(c *client, tType string, instance string, if len(instance) > 0 { v.Set("instance", instance) } + if len(listId) > 0 { + v.Set("list", listId) + } prevLink = "/timeline/" + tType + "?" + v.Encode() } @@ -220,6 +245,9 @@ func (s *service) TimelinePage(c *client, tType string, instance string, if len(instance) > 0 { v.Set("instance", instance) } + if len(listId) > 0 { + v.Set("list", listId) + } nextLink = "/timeline/" + tType + "?" + v.Encode() } @@ -252,6 +280,70 @@ func addToReplyMap(m map[string][]mastodon.ReplyInfo, key interface{}, m[keyStr] = append(m[keyStr], mastodon.ReplyInfo{val, number}) } +func (s *service) ListsPage(c *client) (err error) { + lists, err := c.GetLists(c.ctx) + if err != nil { + return + } + + cdata := s.cdata(c, "Lists", 0, 0, "") + data := renderer.ListsData{ + Lists: lists, + CommonData: cdata, + } + return s.renderer.Render(c.rctx, c.w, renderer.ListsPage, data) +} + +func (s *service) AddList(c *client, title string) (err error) { + _, err = c.CreateList(c.ctx, title) + return err +} + +func (s *service) RemoveList(c *client, id string) (err error) { + return c.DeleteList(c.ctx, id) +} + +func (s *service) RenameList(c *client, id, title string) (err error) { + _, err = c.RenameList(c.ctx, id, title) + return err +} + +func (s *service) ListPage(c *client, id string, q string) (err error) { + list, err := c.GetList(c.ctx, id) + if err != nil { + return + } + accounts, err := c.GetListAccounts(c.ctx, id) + if err != nil { + return + } + var searchAccounts []*mastodon.Account + if len(q) > 0 { + result, err := c.Search(c.ctx, q, "accounts", 20, true, 0, id, true) + if err != nil { + return err + } + searchAccounts = result.Accounts + } + cdata := s.cdata(c, "List "+list.Title, 0, 0, "") + data := renderer.ListData{ + List: list, + Accounts: accounts, + Q: q, + SearchAccounts: searchAccounts, + CommonData: cdata, + } + return s.renderer.Render(c.rctx, c.w, renderer.ListPage, data) +} + +func (s *service) ListAddUser(c *client, id string, uid string) (err error) { + return c.AddToList(c.ctx, id, uid) +} + +func (s *service) ListRemoveUser(c *client, id string, uid string) (err error) { + return c.RemoveFromList(c.ctx, id, uid) +} + func (s *service) ThreadPage(c *client, id string, reply bool) (err error) { var pctx model.PostContext @@ -608,7 +700,7 @@ func (s *service) UserSearchPage(c *client, var results *mastodon.Results if len(q) > 0 { - results, err = c.Search(c.ctx, q, "statuses", 20, true, offset, id) + results, err = c.Search(c.ctx, q, "statuses", 20, true, offset, id, false) if err != nil { return err } @@ -666,7 +758,7 @@ func (s *service) SearchPage(c *client, var results *mastodon.Results if len(q) > 0 { - results, err = c.Search(c.ctx, q, qType, 20, true, offset, "") + results, err = c.Search(c.ctx, q, qType, 20, true, offset, "", false) if err != nil { return err } diff --git a/service/transport.go b/service/transport.go index 615fe2a..4518b1a 100644 --- a/service/transport.go +++ b/service/transport.go @@ -160,9 +160,10 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { tType, _ := mux.Vars(c.r)["type"] q := c.r.URL.Query() instance := q.Get("instance") + list := q.Get("list") maxID := q.Get("max_id") minID := q.Get("min_id") - return s.TimelinePage(c, tType, instance, maxID, minID) + return s.TimelinePage(c, tType, instance, list, maxID, minID) }, SESSION, HTML) defaultTimelinePage := handle(func(c *client) error { @@ -597,6 +598,72 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { return nil }, CSRF, HTML) + listsPage := handle(func(c *client) error { + return s.ListsPage(c) + }, SESSION, HTML) + + addList := handle(func(c *client) error { + title := c.r.FormValue("title") + err := s.AddList(c, title) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + + removeList := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + err := s.RemoveList(c, id) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + + renameList := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + title := c.r.FormValue("title") + err := s.RenameList(c, id, title) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + + listPage := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() + sq := q.Get("q") + return s.ListPage(c, id, sq) + }, SESSION, HTML) + + listAddUser := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() + uid := q.Get("uid") + err := s.ListAddUser(c, id, uid) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + + listRemoveUser := handle(func(c *client) error { + id, _ := mux.Vars(c.r)["id"] + q := c.r.URL.Query() + uid := q.Get("uid") + err := s.ListRemoveUser(c, id, uid) + if err != nil { + return err + } + redirect(c, c.r.FormValue("referrer")) + return nil + }, CSRF, HTML) + signout := handle(func(c *client) error { s.Signout(c) setSessionCookie(c.w, "", 0) @@ -685,6 +752,13 @@ func NewHandler(s *service, logger *log.Logger, staticDir string) http.Handler { r.HandleFunc("/unbookmark/{id}", unBookmark).Methods(http.MethodPost) r.HandleFunc("/filter", filter).Methods(http.MethodPost) r.HandleFunc("/unfilter/{id}", unFilter).Methods(http.MethodPost) + r.HandleFunc("/lists", listsPage).Methods(http.MethodGet) + r.HandleFunc("/list", addList).Methods(http.MethodPost) + r.HandleFunc("/list/{id}", listPage).Methods(http.MethodGet) + r.HandleFunc("/list/{id}/remove", removeList).Methods(http.MethodPost) + r.HandleFunc("/list/{id}/rename", renameList).Methods(http.MethodPost) + r.HandleFunc("/list/{id}/adduser", listAddUser).Methods(http.MethodPost) + r.HandleFunc("/list/{id}/removeuser", listRemoveUser).Methods(http.MethodPost) r.HandleFunc("/signout", signout).Methods(http.MethodPost) 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 4e2a196..10aff68 100644 --- a/static/style.css +++ b/static/style.css @@ -290,6 +290,10 @@ textarea { display: inline; } +.p-0 { + padding: 0; +} + .btn-link { border: none; outline: none; @@ -422,9 +426,6 @@ img.emoji { margin-right: 2px; } -.user-list-container { -} - .user-list-item { overflow: auto; margin: 0 0 12px 0; @@ -441,6 +442,10 @@ img.emoji { overflow: auto; } +.user-list-action { + margin: 0 12px; +} + #settings-form { margin: 8px 0; } diff --git a/templates/about.tmpl b/templates/about.tmpl index 54316cf..0e4d001 100644 --- a/templates/about.tmpl +++ b/templates/about.tmpl @@ -46,11 +46,11 @@ <td> <kbd>6</kbd> </td> </tr> <tr> - <td> Settings </td> + <td> Lists </td> <td> <kbd>7</kbd> </td> </tr> <tr> - <td> Signout </td> + <td> Settings </td> <td> <kbd>8</kbd> </td> </tr> <tr> diff --git a/templates/list.tmpl b/templates/list.tmpl new file mode 100644 index 0000000..1b15278 --- /dev/null +++ b/templates/list.tmpl @@ -0,0 +1,63 @@ +{{with .Data}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} +<div class="page-title"> List {{.List.Title}} </div> + +<form action="/list/{{.List.ID}}/rename" method="POST"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> + <input id="title" name="title" value="{{.List.Title}}"> + <button type="submit"> Rename </button> +</form> + +<div class="page-title"> Users </div> +{{if .Accounts}} +<table> +{{range .Accounts}} + <tr> + <td class="p-0"> {{template "userlistitem.tmpl" (WithContext . $.Ctx)}} </td> + <td class="p-0"> + <form class="user-list-action" action="/list/{{$.Data.List.ID}}/removeuser?uid={{.ID}}" method="POST"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> + <button type="submit"> Remove </button> + </form> + </td> + </tr> +{{end}} +</table> +{{else}} +<div class="no-data-found">No data found</div> +{{end}} + +<div class="page-title"> Add user </div> +<form class="search-form" action="/list/{{.List.ID}}" method="GET"> + <span class="post-form-field"> + <label for="query"> Query </label> + <input id="query" name="q" value="{{.Q | html}}"> + </span> + <button type="submit"> Search </button> +</form> + +{{if .Q}} +{{if .SearchAccounts}} +<table> +{{range .SearchAccounts}} + <tr> + <td> {{template "userlistitem.tmpl" (WithContext . $.Ctx)}} </td> + <td> + <form class="user-list-action" action="/list/{{$.Data.List.ID}}/adduser?uid={{.ID}}" method="POST"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> + <button type="submit"> Add </button> + </form> + </td> + </tr> +{{end}} +</table> +{{else}} +<div class="no-data-found">No data found</div> +{{end}} +{{end}} + +{{template "footer.tmpl"}} +{{end}} diff --git a/templates/lists.tmpl b/templates/lists.tmpl new file mode 100644 index 0000000..27979cb --- /dev/null +++ b/templates/lists.tmpl @@ -0,0 +1,35 @@ +{{with .Data}} +{{template "header.tmpl" (WithContext .CommonData $.Ctx)}} +<div class="page-title"> Lists </div> + +{{range .Lists}} +<div> + <a href="/timeline/list?list={{.ID}}"> {{.Title}} timeline </a> + - + <form class="d-inline" action="/list/{{.ID}}" method="GET"> + <button type="submit" class="btn-link"> edit </button> + </form> + - + <form class="d-inline" action="/list/{{.ID}}/remove" method="POST"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> + <button type="submit" class="btn-link"> delete </button> + </form> +</div> +{{else}} +<div class="no-data-found">No data found</div> +{{end}} + +<div class="page-title"> Add list </div> +<form action="/list" method="POST"> + <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> + <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> + <span class="settings-form-field"> + <label for="title"> Title </label> + <input id="title" name="title" required> + </span> + <button type="submit"> Add </button> +</form> + +{{template "footer.tmpl"}} +{{end}} diff --git a/templates/nav.tmpl b/templates/nav.tmpl index ea18a5f..e7a7981 100644 --- a/templates/nav.tmpl +++ b/templates/nav.tmpl @@ -17,16 +17,17 @@ <a class="nav-link" href="/timeline/home" accesskey="1" title="Home timeline (1)">home</a> <a class="nav-link" href="/timeline/direct" accesskey="2" title="Direct timeline (2)">direct</a> <a class="nav-link" href="/timeline/local" accesskey="3" title="Local timeline (3)">local</a> - <a class="nav-link" href="/timeline/twkn" accesskey="5" title="The Whole Known Netwwork (4)">twkn</a> - <a class="nav-link" href="/timeline/remote" accesskey="4" title="Remote timeline (5)">remote</a> + <a class="nav-link" href="/timeline/twkn" accesskey="4" title="The Whole Known Netwwork (4)">twkn</a> + <a class="nav-link" href="/timeline/remote" accesskey="5" title="Remote timeline (5)">remote</a> <a class="nav-link" href="/search" accesskey="6" title="Search (6)">search</a> </div> <div> - <a class="nav-link" href="/settings" target="_top" accesskey="7" title="Settings (7)">settings</a> + <a class="nav-link" href="/lists" accesskey="7" title="Lists (7)">lists</a> + <a class="nav-link" href="/settings" target="_top" accesskey="8" title="Settings (8)">settings</a> <form class="signout" action="/signout" method="post" target="_top"> <input type="hidden" name="csrf_token" value="{{$.Ctx.CSRFToken}}"> <input type="hidden" name="referrer" value="{{$.Ctx.Referrer}}"> - <input type="submit" value="signout" class="btn-link nav-link" accesskey="8" title="Signout (8)"> + <input type="submit" value="signout" class="btn-link nav-link" title="Signout"> </form> <a class="nav-link" href="/about" accesskey="9" title="About (9)">about</a> </div> diff --git a/templates/userlist.tmpl b/templates/userlist.tmpl index b8e0e5d..f206397 100644 --- a/templates/userlist.tmpl +++ b/templates/userlist.tmpl @@ -1,19 +1,7 @@ {{with .Data}} <div> {{range .}} - <div class="user-list-item"> - <div class="user-list-profile-img"> - <a class="img-link" href="/user/{{.ID}}"> - <img class="status-profile-img" src="{{.Avatar}}" title="@{{.Acct}}" alt="avatar" height="48" /> - </a> - </div> - <div class="user-list-name"> - <div class="status-dname"> {{EmojiFilter (html .DisplayName) .Emojis}} </div> - <a class="img-link" href="/user/{{.ID}}"> - <div class="status-uname"> @{{.Acct}} </div> - </a> - </div> - </div> + {{template "userlistitem.tmpl" (WithContext . $.Ctx)}} {{else}} <div class="no-data-found">No data found</div> {{end}} diff --git a/templates/userlistitem.tmpl b/templates/userlistitem.tmpl new file mode 100644 index 0000000..51261c8 --- /dev/null +++ b/templates/userlistitem.tmpl @@ -0,0 +1,15 @@ +{{with .Data}} +<div class="user-list-item"> + <div class="user-list-profile-img"> + <a class="img-link" href="/user/{{.ID}}"> + <img class="status-profile-img" src="{{.Avatar}}" title="@{{.Acct}}" alt="avatar" height="48" /> + </a> + </div> + <div class="user-list-name"> + <div class="status-dname"> {{EmojiFilter (html .DisplayName) .Emojis}} </div> + <a class="img-link" href="/user/{{.ID}}"> + <div class="status-uname"> @{{.Acct}} </div> + </a> + </div> +</div> +{{end}} |