aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mastodon/lists.go4
-rw-r--r--mastodon/status.go3
-rw-r--r--renderer/model.go13
-rw-r--r--renderer/renderer.go2
-rw-r--r--service/service.go106
-rw-r--r--service/transport.go76
-rw-r--r--static/style.css11
-rw-r--r--templates/about.tmpl4
-rw-r--r--templates/list.tmpl63
-rw-r--r--templates/lists.tmpl35
-rw-r--r--templates/nav.tmpl9
-rw-r--r--templates/userlist.tmpl14
-rw-r--r--templates/userlistitem.tmpl15
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}}