Add pagination to tag subpagespull/695/merge
@@ -610,6 +610,30 @@ type CollectionPage struct { | |||
CollAlias string | |||
} | |||
type TagCollectionPage struct { | |||
CollectionPage | |||
Tag string | |||
} | |||
func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string { | |||
u := fmt.Sprintf("/tag:%s", tcp.Tag) | |||
if n > 2 { | |||
u += fmt.Sprintf("/page/%d", n-1) | |||
} | |||
if tl { | |||
return u | |||
} | |||
return "/" + prefix + tcp.Alias + u | |||
} | |||
func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string { | |||
if tl { | |||
return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1) | |||
} | |||
return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1) | |||
} | |||
func NewCollectionObj(c *Collection) *CollectionObj { | |||
return &CollectionObj{ | |||
Collection: *c, | |||
@@ -951,16 +975,29 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e | |||
coll := newDisplayCollection(c, cr, page) | |||
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner) | |||
if err != nil { | |||
return err | |||
} | |||
ttlPosts := len(taggedPostIDs) | |||
pagePosts := coll.Format.PostsPerPage() | |||
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts))) | |||
if coll.TotalPages > 0 && page > coll.TotalPages { | |||
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages) | |||
if !app.cfg.App.SingleUser { | |||
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL) | |||
} | |||
return impart.HTTPError{http.StatusFound, redirURL} | |||
} | |||
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner) | |||
if coll.Posts != nil && len(*coll.Posts) == 0 { | |||
return ErrCollectionPageNotFound | |||
} | |||
// Serve collection | |||
displayPage := struct { | |||
CollectionPage | |||
Tag string | |||
}{ | |||
displayPage := TagCollectionPage{ | |||
CollectionPage: CollectionPage{ | |||
DisplayCollection: coll, | |||
StaticPage: pageForReq(app, r), | |||
@@ -113,6 +113,7 @@ type writestore interface { | |||
GetPostsCount(c *CollectionObj, includeFuture bool) | |||
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) | |||
GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) | |||
GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) | |||
GetAPFollowers(c *Collection) (*[]RemoteUser, error) | |||
@@ -1195,6 +1196,51 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu | |||
return &posts, nil | |||
} | |||
func (db *datastore) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) { | |||
collID := c.ID | |||
cf := c.NewFormat() | |||
order := "DESC" | |||
if cf.Ascending() { | |||
order = "ASC" | |||
} | |||
timeCondition := "" | |||
if !includeFuture { | |||
timeCondition = "AND created <= NOW()" | |||
} | |||
var rows *sql.Rows | |||
var err error | |||
if db.driverName == driverSQLite { | |||
rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) regexp ? "+timeCondition+" ORDER BY created "+order, collID, `.*#`+strings.ToLower(tag)+`\b.*`) | |||
} else { | |||
rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) RLIKE ? "+timeCondition+" ORDER BY created "+order, collID, "#"+strings.ToLower(tag)+"[[:>:]]") | |||
} | |||
if err != nil { | |||
log.Error("Failed selecting tagged posts: %v", err) | |||
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve tagged collection posts."} | |||
} | |||
defer rows.Close() | |||
ids := []string{} | |||
for rows.Next() { | |||
var id string | |||
err = rows.Scan(&id) | |||
if err != nil { | |||
log.Error("Failed scanning row: %v", err) | |||
break | |||
} | |||
ids = append(ids, id) | |||
} | |||
err = rows.Err() | |||
if err != nil { | |||
log.Error("Error after Next() on rows: %v", err) | |||
} | |||
return ids, nil | |||
} | |||
// GetPostsTagged retrieves all posts on the given Collection that contain the | |||
// given tag. | |||
// It will return future posts if `includeFuture` is true. | |||
@@ -217,6 +217,7 @@ func RouteCollections(handler *Handler, r *mux.Router) { | |||
r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) | |||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) | |||
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) | |||
r.HandleFunc("/tag:{tag}/page/{page:[0-9]+}", handler.Web(handleViewCollectionTag, UserLevelReader)) | |||
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) | |||
r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) | |||
r.HandleFunc("/feed/", handler.AllReader(ViewFeed)) | |||
@@ -61,6 +61,17 @@ | |||
{{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}} | |||
<h1>{{.Tag}}</h1> | |||
{{template "posts" .}} | |||
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix"> | |||
{{if or (and .Format.Ascending (lt .CurrentPage .TotalPages)) (isRTL .Direction)}} | |||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}} | |||
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Next{{else}}Older{{end}} ⇢</a>{{end}} | |||
{{else}} | |||
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ Older</a>{{end}} | |||
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">Newer ⇢</a>{{end}} | |||
{{end}} | |||
</nav>{{end}} | |||
{{if .Posts}}</section>{{else}}</div>{{end}} | |||
{{ if .Collection.ShowFooterBranding }} | |||