Add search page

This commit is contained in:
r 2019-12-26 19:18:09 +00:00
parent 591360f2a8
commit 14bb18fbc7
10 changed files with 157 additions and 14 deletions

View file

@ -336,7 +336,7 @@ type Emoji struct {
type Results struct { type Results struct {
Accounts []*Account `json:"accounts"` Accounts []*Account `json:"accounts"`
Statuses []*Status `json:"statuses"` Statuses []*Status `json:"statuses"`
Hashtags []string `json:"hashtags"` // Hashtags []string `json:"hashtags"`
} }
// Pagination is a struct for specifying the get range. // Pagination is a struct for specifying the get range.

View file

@ -284,12 +284,15 @@ func (c *Client) DeleteStatus(ctx context.Context, id string) error {
} }
// Search search content with query. // Search search content with query.
func (c *Client) Search(ctx context.Context, q string, resolve bool) (*Results, error) { func (c *Client) Search(ctx context.Context, q string, qType string, limit int, resolve bool, offset int) (*Results, error) {
params := url.Values{} params := url.Values{}
params.Set("q", q) params.Set("q", q)
params.Set("type", qType)
params.Set("limit", fmt.Sprint(limit))
params.Set("resolve", fmt.Sprint(resolve)) params.Set("resolve", fmt.Sprint(resolve))
params.Set("offset", fmt.Sprint(offset))
var results Results var results Results
err := c.doAPI(ctx, http.MethodGet, "/api/v1/search", params, &results, nil) err := c.doAPI(ctx, http.MethodGet, "/api/v2/search", params, &results, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -89,3 +89,13 @@ type RetweetedByData struct {
HasNext bool HasNext bool
NextLink string NextLink string
} }
type SearchData struct {
*CommonData
Q string
Type string
Users []*mastodon.Account
Statuses []*mastodon.Status
HasNext bool
NextLink string
}

View file

@ -23,6 +23,7 @@ type Renderer interface {
RenderEmojiPage(ctx context.Context, writer io.Writer, data *EmojiData) (err error) RenderEmojiPage(ctx context.Context, writer io.Writer, data *EmojiData) (err error)
RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error) RenderLikedByPage(ctx context.Context, writer io.Writer, data *LikedByData) (err error)
RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error) RenderRetweetedByPage(ctx context.Context, writer io.Writer, data *RetweetedByData) (err error)
RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error)
} }
type renderer struct { type renderer struct {
@ -91,6 +92,10 @@ func (r *renderer) RenderRetweetedByPage(ctx context.Context, writer io.Writer,
return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", data) return r.template.ExecuteTemplate(writer, "retweetedby.tmpl", data)
} }
func (r *renderer) RenderSearchPage(ctx context.Context, writer io.Writer, data *SearchData) (err error) {
return r.template.ExecuteTemplate(writer, "search.tmpl", data)
}
func EmojiFilter(content string, emojis []mastodon.Emoji) string { func EmojiFilter(content string, emojis []mastodon.Emoji) string {
var replacements []string var replacements []string
for _, e := range emojis { for _, e := range emojis {

View file

@ -149,6 +149,14 @@ func (s *authService) ServeRetweetedByPage(ctx context.Context, client io.Writer
return s.Service.ServeRetweetedByPage(ctx, client, c, id) return s.Service.ServeRetweetedByPage(ctx, client, c, id)
} }
func (s *authService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
c, err = s.getClient(ctx)
if err != nil {
return
}
return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
}
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) (err error) {
c, err = s.getClient(ctx) c, err = s.getClient(ctx)
if err != nil { if err != nil {

View file

@ -125,6 +125,14 @@ func (s *loggingService) ServeRetweetedByPage(ctx context.Context, client io.Wri
return s.Service.ServeRetweetedByPage(ctx, client, c, id) return s.Service.ServeRetweetedByPage(ctx, client, c, id)
} }
func (s *loggingService) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
defer func(begin time.Time) {
s.logger.Printf("method=%v, q=%v, type=%v, offset=%v, took=%v, err=%v\n",
"ServeSearchPage", q, qType, offset, time.Since(begin), err)
}(time.Now())
return s.Service.ServeSearchPage(ctx, client, c, q, qType, offset)
}
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) (err error) {
defer func(begin time.Time) { defer func(begin time.Time) {
s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n", s.logger.Printf("method=%v, id=%v, took=%v, err=%v\n",

View file

@ -39,6 +39,7 @@ type Service interface {
ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error) ServeEmojiPage(ctx context.Context, client io.Writer, c *model.Client) (err error)
ServeLikedByPage(ctx context.Context, client io.Writer, c *model.Client, id string) (err error) 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) 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)
Like(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) (err error)
UnLike(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) Retweet(ctx context.Context, client io.Writer, c *model.Client, id string) (err error)
@ -593,6 +594,51 @@ func (svc *service) ServeRetweetedByPage(ctx context.Context, client io.Writer,
return return
} }
func (svc *service) ServeSearchPage(ctx context.Context, client io.Writer, c *model.Client, q string, qType string, offset int) (err error) {
var hasNext bool
var nextLink string
results, err := c.Search(ctx, q, qType, 20, true, offset)
if err != nil {
return
}
switch qType {
case "accounts":
hasNext = len(results.Accounts) == 20
case "statuses":
hasNext = len(results.Statuses) == 20
}
if hasNext {
offset += 20
nextLink = fmt.Sprintf("/search?q=%s&type=%s&offset=%d", q, qType, offset)
}
commonData, err := svc.getCommonData(ctx, client, c)
if err != nil {
return
}
data := &renderer.SearchData{
CommonData: commonData,
Q: q,
Type: qType,
Users: results.Accounts,
Statuses: results.Statuses,
HasNext: hasNext,
NextLink: nextLink,
}
err = svc.renderer.RenderSearchPage(ctx, client, data)
if err != nil {
return
}
return
}
func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *model.Client) (data *renderer.CommonData, err error) { func (svc *service) getCommonData(ctx context.Context, client io.Writer, c *model.Client) (data *renderer.CommonData, err error) {
data = new(renderer.CommonData) data = new(renderer.CommonData)

View file

@ -6,6 +6,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"path" "path"
"strconv"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@ -280,6 +281,30 @@ func NewHandler(s Service, staticDir string) http.Handler {
} }
}).Methods(http.MethodGet) }).Methods(http.MethodGet)
r.HandleFunc("/search", func(w http.ResponseWriter, req *http.Request) {
ctx := getContextWithSession(context.Background(), req)
q := req.URL.Query().Get("q")
qType := req.URL.Query().Get("type")
offsetStr := req.URL.Query().Get("offset")
var offset int
var err error
if len(offsetStr) > 1 {
offset, err = strconv.Atoi(offsetStr)
if err != nil {
s.ServeErrorPage(ctx, w, err)
return
}
}
err = s.ServeSearchPage(ctx, w, nil, q, qType, offset)
if err != nil {
s.ServeErrorPage(ctx, w, err)
return
}
}).Methods(http.MethodGet)
r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) { r.HandleFunc("/signout", func(w http.ResponseWriter, req *http.Request) {
// TODO remove session from database // TODO remove session from database
w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0")) w.Header().Add("Set-Cookie", fmt.Sprintf("session_id=;max-age=0"))

View file

@ -16,6 +16,7 @@
<a class="nav-link" href="/notifications">notifications{{if gt .NotificationCount 0}}({{.NotificationCount}}){{end}}</a> <a class="nav-link" href="/notifications">notifications{{if gt .NotificationCount 0}}({{.NotificationCount}}){{end}}</a>
<a class="nav-link" href="/timeline/local">local</a> <a class="nav-link" href="/timeline/local">local</a>
<a class="nav-link" href="/timeline/twkn">twkn</a> <a class="nav-link" href="/timeline/twkn">twkn</a>
<a class="nav-link" href="/search">search</a>
<a class="nav-link" href="/about">about</a> <a class="nav-link" href="/about">about</a>
</div> </div>
<div> <div>

37
templates/search.tmpl Normal file
View file

@ -0,0 +1,37 @@
{{template "header.tmpl" .HeaderData}}
{{template "navigation.tmpl" .NavbarData}}
<div class="page-title"> Search </div>
<div>
<form action="/search" method="GET">
<span class="post-form-field>
<label for="query"> Query </label>
<input id="query" name="q" value="{{.Q}}">
</span>
<span class="post-form-field>
<label for="type"> Type </label>
<select id="type" name="type">
<option value="statuses" {{if eq .Type "statuses"}}selected{{end}}>Statuses</option>
<option value="accounts" {{if eq .Type "accounts"}}selected{{end}}>Accounts</option>
</select>
</span>
<button type="submit"> Search </button>
</form>
</div>
{{if eq .Type "statuses"}}
{{range .Statuses}}
{{template "status.tmpl" .}}
{{end}}
{{end}}
{{if eq .Type "accounts"}}
{{template "userlist.tmpl" .Users}}
{{end}}
<div class="pagination">
{{if .HasNext}}
<a href="{{.NextLink}}">next</a>
{{end}}
</div>
{{template "footer.tmpl"}}