mirror of
https://github.com/writefreely/writefreely
synced 2024-11-10 11:24:13 +00:00
Merge pull request #508 from writefreely/lang-posts-filter
Support filtering blog posts by language Closes T805
This commit is contained in:
commit
334d499fb3
5 changed files with 206 additions and 18 deletions
130
collections.go
130
collections.go
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/writeas/web-core/activitystreams"
|
||||
"github.com/writeas/web-core/auth"
|
||||
"github.com/writeas/web-core/bots"
|
||||
"github.com/writeas/web-core/i18n"
|
||||
"github.com/writeas/web-core/log"
|
||||
"github.com/writeas/web-core/posts"
|
||||
"github.com/writefreely/writefreely/author"
|
||||
|
@ -74,6 +75,7 @@ type (
|
|||
DisplayCollection struct {
|
||||
*CollectionObj
|
||||
Prefix string
|
||||
NavSuffix string
|
||||
IsTopLevel bool
|
||||
CurrentPage int
|
||||
TotalPages int
|
||||
|
@ -261,16 +263,16 @@ func (c *Collection) RedirectingCanonicalURL(isRedir bool) string {
|
|||
|
||||
// PrevPageURL provides a full URL for the previous page of collection posts,
|
||||
// returning a /page/N result for pages >1
|
||||
func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
|
||||
func (c *Collection) PrevPageURL(prefix, navSuffix string, n int, tl bool) string {
|
||||
u := ""
|
||||
if n == 2 {
|
||||
// Previous page is 1; no need for /page/ prefix
|
||||
if prefix == "" {
|
||||
u = "/"
|
||||
u = navSuffix + "/"
|
||||
}
|
||||
// Else leave off trailing slash
|
||||
} else {
|
||||
u = fmt.Sprintf("/page/%d", n-1)
|
||||
u = fmt.Sprintf("%s/page/%d", navSuffix, n-1)
|
||||
}
|
||||
|
||||
if tl {
|
||||
|
@ -280,11 +282,12 @@ func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
|
|||
}
|
||||
|
||||
// NextPageURL provides a full URL for the next page of collection posts
|
||||
func (c *Collection) NextPageURL(prefix string, n int, tl bool) string {
|
||||
func (c *Collection) NextPageURL(prefix, navSuffix string, n int, tl bool) string {
|
||||
|
||||
if tl {
|
||||
return fmt.Sprintf("/page/%d", n+1)
|
||||
return fmt.Sprintf("%s/page/%d", navSuffix, n+1)
|
||||
}
|
||||
return fmt.Sprintf("/%s%s/page/%d", prefix, c.Alias, n+1)
|
||||
return fmt.Sprintf("/%s%s%s/page/%d", prefix, c.Alias, navSuffix, n+1)
|
||||
}
|
||||
|
||||
func (c *Collection) DisplayTitle() string {
|
||||
|
@ -389,6 +392,16 @@ func (c CollectionPage) DisplayMonetization() string {
|
|||
return displayMonetization(c.Monetization, c.Alias)
|
||||
}
|
||||
|
||||
func (c *DisplayCollection) Direction() string {
|
||||
if c.Language == "" {
|
||||
return "auto"
|
||||
}
|
||||
if i18n.LangIsRTL(c.Language) {
|
||||
return "rtl"
|
||||
}
|
||||
return "ltr"
|
||||
}
|
||||
|
||||
func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||
reqJSON := IsJSON(r)
|
||||
alias := r.FormValue("alias")
|
||||
|
@ -1050,6 +1063,111 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
lang := vars["lang"]
|
||||
|
||||
cr := &collectionReq{}
|
||||
err := processCollectionRequest(cr, vars, w, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := checkUserForCollection(app, cr, r, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
page := getCollectionPage(vars)
|
||||
|
||||
c, err := processCollectionPermissions(app, cr, u, w, r)
|
||||
if c == nil || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
coll := newDisplayCollection(c, cr, page)
|
||||
coll.Language = lang
|
||||
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)
|
||||
|
||||
ttlPosts, err := app.db.GetCollLangTotalPosts(coll.ID, lang)
|
||||
if err != nil {
|
||||
log.Error("Unable to getCollLangTotalPosts: %s", err)
|
||||
}
|
||||
pagePosts := coll.Format.PostsPerPage()
|
||||
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
|
||||
if coll.TotalPages > 0 && page > coll.TotalPages {
|
||||
redirURL := fmt.Sprintf("/lang:%s/page/%d", lang, 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.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner)
|
||||
if err != nil {
|
||||
return ErrCollectionPageNotFound
|
||||
}
|
||||
|
||||
// Serve collection
|
||||
displayPage := struct {
|
||||
CollectionPage
|
||||
Tag string
|
||||
}{
|
||||
CollectionPage: CollectionPage{
|
||||
DisplayCollection: coll,
|
||||
StaticPage: pageForReq(app, r),
|
||||
IsCustomDomain: cr.isCustomDomain,
|
||||
},
|
||||
Tag: lang,
|
||||
}
|
||||
var owner *User
|
||||
if u != nil {
|
||||
displayPage.Username = u.Username
|
||||
displayPage.IsOwner = u.ID == coll.OwnerID
|
||||
if displayPage.IsOwner {
|
||||
// Add in needed information for users viewing their own collection
|
||||
owner = u
|
||||
displayPage.CanPin = true
|
||||
|
||||
pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host)
|
||||
if err != nil {
|
||||
log.Error("unable to fetch collections: %v", err)
|
||||
}
|
||||
displayPage.Collections = pubColls
|
||||
}
|
||||
}
|
||||
isOwner := owner != nil
|
||||
if !isOwner {
|
||||
// Current user doesn't own collection; retrieve owner information
|
||||
owner, err = app.db.GetUserByID(coll.OwnerID)
|
||||
if err != nil {
|
||||
// Log the error and just continue
|
||||
log.Error("Error getting user for collection: %v", err)
|
||||
}
|
||||
if owner.IsSilenced() {
|
||||
return ErrCollectionNotFound
|
||||
}
|
||||
}
|
||||
displayPage.Silenced = owner != nil && owner.IsSilenced()
|
||||
displayPage.Owner = owner
|
||||
coll.Owner = displayPage.Owner
|
||||
// Add more data
|
||||
// TODO: fix this mess of collections inside collections
|
||||
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
|
||||
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
|
||||
|
||||
collTmpl := "collection"
|
||||
if app.cfg.App.Chorus {
|
||||
collTmpl = "chorus-collection"
|
||||
}
|
||||
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
|
||||
if err != nil {
|
||||
log.Error("Unable to render collection lang page: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||
vars := mux.Vars(r)
|
||||
slug := vars["slug"]
|
||||
|
|
68
database.go
68
database.go
|
@ -1346,6 +1346,74 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin
|
|||
return &posts, nil
|
||||
}
|
||||
|
||||
func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) {
|
||||
var articles uint64
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ? AND created <= "+db.now(), collID, lang).Scan(&articles)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
log.Error("Couldn't get total lang posts count for collection %d: %v", collID, err)
|
||||
return 0, err
|
||||
}
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string, page int, includeFuture bool) (*[]PublicPost, error) {
|
||||
collID := c.ID
|
||||
|
||||
cf := c.NewFormat()
|
||||
order := "DESC"
|
||||
if cf.Ascending() {
|
||||
order = "ASC"
|
||||
}
|
||||
|
||||
pagePosts := cf.PostsPerPage()
|
||||
start := page*pagePosts - pagePosts
|
||||
if page == 0 {
|
||||
start = 0
|
||||
pagePosts = 1000
|
||||
}
|
||||
|
||||
limitStr := ""
|
||||
if page > 0 {
|
||||
limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
|
||||
}
|
||||
timeCondition := ""
|
||||
if !includeFuture {
|
||||
timeCondition = "AND created <= " + db.now()
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT `+postCols+`
|
||||
FROM posts
|
||||
WHERE collection_id = ? AND language = ? `+timeCondition+`
|
||||
ORDER BY created `+order+limitStr, collID, lang)
|
||||
if err != nil {
|
||||
log.Error("Failed selecting from posts: %v", err)
|
||||
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."}
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// TODO: extract this common row scanning logic for queries using `postCols`
|
||||
posts := []PublicPost{}
|
||||
for rows.Next() {
|
||||
p := &Post{}
|
||||
err = rows.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content)
|
||||
if err != nil {
|
||||
log.Error("Failed scanning row: %v", err)
|
||||
break
|
||||
}
|
||||
p.extractData()
|
||||
p.augmentContent(c)
|
||||
p.formatContent(cfg, c, includeFuture, false)
|
||||
|
||||
posts = append(posts, p.processPost())
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
log.Error("Error after Next() on rows: %v", err)
|
||||
}
|
||||
|
||||
return &posts, nil
|
||||
}
|
||||
|
||||
func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) {
|
||||
rows, err := db.Query("SELECT actor_id, inbox, shared_inbox FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID)
|
||||
if err != nil {
|
||||
|
|
|
@ -216,6 +216,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
|||
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("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional))
|
||||
r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional))
|
||||
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))
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="canonical" href="{{.CanonicalURL}}">
|
||||
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if not .IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.DisplayTitle}} » Feed" href="{{.CanonicalURL}}feed/" />{{end}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
|
@ -92,11 +92,11 @@ body#collection header nav.tabs a:first-child {
|
|||
|
||||
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
|
||||
{{if or (and .Format.Ascending (le .CurrentPage .TotalPages)) (isRTL .Direction)}}
|
||||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (le .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}}
|
||||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (le .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .NavSuffix .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}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">⇠ Older</a>{{end}}
|
||||
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">Newer ⇢</a>{{end}}
|
||||
{{end}}
|
||||
</nav>{{end}}
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="canonical" href="{{.CanonicalURL}}">
|
||||
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
|
||||
{{if not .IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.DisplayTitle}} » Feed" href="{{.CanonicalURL}}feed/" />{{end}}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
|
@ -107,11 +107,11 @@
|
|||
|
||||
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
|
||||
{{if or (and .Format.Ascending (le .CurrentPage .TotalPages)) (isRTL .Direction)}}
|
||||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (le .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}}
|
||||
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">⇠ {{if and .Format.Ascending (le .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .NavSuffix .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}}
|
||||
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">⇠ Older</a>{{end}}
|
||||
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">Newer ⇢</a>{{end}}
|
||||
{{end}}
|
||||
</nav>{{end}}
|
||||
|
||||
|
|
Loading…
Reference in a new issue