Add lists

This commit is contained in:
r 2022-02-11 11:18:02 +00:00
parent c2f237e901
commit c390a0c327
13 changed files with 322 additions and 33 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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"

View File

@ -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,25 +179,47 @@ 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)
title = "The Whole Known Network"
}
if err != nil {
return err
}
title = "The Whole Known Network"
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 {
if statuses[i].Reblog != nil {
@ -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
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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>

63
templates/list.tmpl Normal file
View File

@ -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}}

35
templates/lists.tmpl Normal file
View File

@ -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}}

View File

@ -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>

View File

@ -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}}

View File

@ -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}}