diff --git a/collections.go b/collections.go
index b20db85..e3674fa 100644
--- a/collections.go
+++ b/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"]
diff --git a/database.go b/database.go
index 28cca46..f8d5a2d 100644
--- a/database.go
+++ b/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 {
diff --git a/routes.go b/routes.go
index 5ec4f53..106221d 100644
--- a/routes.go
+++ b/routes.go
@@ -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))
diff --git a/templates/chorus-collection.tmpl b/templates/chorus-collection.tmpl
index 2bc165d..0fb5eaf 100644
--- a/templates/chorus-collection.tmpl
+++ b/templates/chorus-collection.tmpl
@@ -9,8 +9,8 @@
{{if .CustomCSS}}{{end}}
- {{if gt .CurrentPage 1}}{{end}}
- {{if lt .CurrentPage .TotalPages}}{{end}}
+ {{if gt .CurrentPage 1}}{{end}}
+ {{if lt .CurrentPage .TotalPages}}{{end}}
{{if not .IsPrivate}}{{end}}
@@ -92,11 +92,11 @@ body#collection header nav.tabs a:first-child {
{{if gt .TotalPages 1}}{{end}}
diff --git a/templates/collection.tmpl b/templates/collection.tmpl
index 6e3d2dc..16ef873 100644
--- a/templates/collection.tmpl
+++ b/templates/collection.tmpl
@@ -9,8 +9,8 @@
{{if .CustomCSS}}{{end}}
- {{if gt .CurrentPage 1}}{{end}}
- {{if lt .CurrentPage .TotalPages}}{{end}}
+ {{if gt .CurrentPage 1}}{{end}}
+ {{if lt .CurrentPage .TotalPages}}{{end}}
{{if not .IsPrivate}}{{end}}
@@ -107,11 +107,11 @@
{{if gt .TotalPages 1}}{{end}}