Merge pull request #1128 from writefreely/archive-page
Add Archive page Closes T873
This commit is contained in:
commit
17039e620e
15 changed files with 237 additions and 36 deletions
|
@ -201,7 +201,7 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques
|
||||||
ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
|
ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
|
||||||
ocp.OrderedItems = []interface{}{}
|
ocp.OrderedItems = []interface{}{}
|
||||||
|
|
||||||
posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
|
posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false, "")
|
||||||
for _, pp := range *posts {
|
for _, pp := range *posts {
|
||||||
pp.Collection = res
|
pp.Collection = res
|
||||||
o := pp.ActivityObject(app)
|
o := pp.ActivityObject(app)
|
||||||
|
|
7
app.go
7
app.go
|
@ -46,9 +46,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
staticDir = "static"
|
staticDir = "static"
|
||||||
assumedTitleLen = 80
|
assumedTitleLen = 80
|
||||||
postsPerPage = 10
|
postsPerPage = 10
|
||||||
|
postsPerArchPage = 40
|
||||||
|
|
||||||
serverSoftware = "WriteFreely"
|
serverSoftware = "WriteFreely"
|
||||||
softwareURL = "https://writefreely.org"
|
softwareURL = "https://writefreely.org"
|
||||||
|
|
|
@ -608,7 +608,7 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
|
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -828,15 +828,18 @@ func checkUserForCollection(app *App, cr *collectionReq, r *http.Request, isPost
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDisplayCollection(c *Collection, cr *collectionReq, page int) *DisplayCollection {
|
func newDisplayCollection(c *Collection, cr *collectionReq, page int) (*DisplayCollection, error) {
|
||||||
coll := &DisplayCollection{
|
coll := &DisplayCollection{
|
||||||
CollectionObj: NewCollectionObj(c),
|
CollectionObj: NewCollectionObj(c),
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
Prefix: cr.prefix,
|
Prefix: cr.prefix,
|
||||||
IsTopLevel: isSingleUser,
|
IsTopLevel: isSingleUser,
|
||||||
}
|
}
|
||||||
c.db.GetPostsCount(coll.CollectionObj, cr.isCollOwner)
|
err := c.db.GetPostsCount(coll.CollectionObj, cr.isCollOwner)
|
||||||
return coll
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return coll, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCollectionPage returns the collection page as an int. If the parsed page value is not
|
// getCollectionPage returns the collection page as an int. If the parsed page value is not
|
||||||
|
@ -888,9 +891,23 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
|
|
||||||
// Fetch extra data about the Collection
|
// Fetch extra data about the Collection
|
||||||
// TODO: refactor out this logic, shared in collection.go:fetchCollection()
|
// TODO: refactor out this logic, shared in collection.go:fetchCollection()
|
||||||
coll := newDisplayCollection(c, cr, page)
|
coll, err := newDisplayCollection(c, cr, page)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage())))
|
var ct PostType
|
||||||
|
if isArchiveView(r) {
|
||||||
|
ct = postArch
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: this number will be off when user has pinned posts but isn't a Pro user
|
||||||
|
ppp := coll.Format.PostsPerPage()
|
||||||
|
if ct == postArch {
|
||||||
|
ppp = postsPerArchPage
|
||||||
|
}
|
||||||
|
|
||||||
|
coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(ppp)))
|
||||||
if coll.TotalPages > 0 && page > coll.TotalPages {
|
if coll.TotalPages > 0 && page > coll.TotalPages {
|
||||||
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
|
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
|
||||||
if !app.cfg.App.SingleUser {
|
if !app.cfg.App.SingleUser {
|
||||||
|
@ -899,7 +916,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
return impart.HTTPError{http.StatusFound, redirURL}
|
return impart.HTTPError{http.StatusFound, redirURL}
|
||||||
}
|
}
|
||||||
|
|
||||||
coll.Posts, _ = app.db.GetPosts(app.cfg, c, page, cr.isCollOwner, false, false)
|
coll.Posts, _ = app.db.GetPosts(app.cfg, c, page, cr.isCollOwner, false, false, "")
|
||||||
|
|
||||||
// Serve collection
|
// Serve collection
|
||||||
displayPage := CollectionPage{
|
displayPage := CollectionPage{
|
||||||
|
@ -958,6 +975,9 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
collTmpl := "collection"
|
collTmpl := "collection"
|
||||||
if app.cfg.App.Chorus {
|
if app.cfg.App.Chorus {
|
||||||
collTmpl = "chorus-collection"
|
collTmpl = "chorus-collection"
|
||||||
|
} else if isArchiveView(r) {
|
||||||
|
displayPage.NavSuffix = "/archive/"
|
||||||
|
collTmpl = "collection-archive"
|
||||||
}
|
}
|
||||||
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
|
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -984,6 +1004,10 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isArchiveView(r *http.Request) bool {
|
||||||
|
return strings.HasSuffix(r.RequestURI, "/archive/") || mux.Vars(r)["archive"] == "archive"
|
||||||
|
}
|
||||||
|
|
||||||
func handleViewMention(app *App, w http.ResponseWriter, r *http.Request) error {
|
func handleViewMention(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
handle := vars["handle"]
|
handle := vars["handle"]
|
||||||
|
@ -1019,7 +1043,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
coll := newDisplayCollection(c, cr, page)
|
coll, _ := newDisplayCollection(c, cr, page)
|
||||||
|
|
||||||
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
|
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1117,7 +1141,7 @@ func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
coll := newDisplayCollection(c, cr, page)
|
coll, _ := newDisplayCollection(c, cr, page)
|
||||||
coll.Language = lang
|
coll.Language = lang
|
||||||
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)
|
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)
|
||||||
|
|
||||||
|
|
34
database.go
34
database.go
|
@ -19,6 +19,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/writeas/monday"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/writeas/web-core/silobridge"
|
"github.com/writeas/web-core/silobridge"
|
||||||
wf_db "github.com/writefreely/writefreely/db"
|
wf_db "github.com/writefreely/writefreely/db"
|
||||||
|
@ -116,8 +118,8 @@ type writestore interface {
|
||||||
ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
|
ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
|
||||||
|
|
||||||
GetPostLikeCounts(postID string) (int64, error)
|
GetPostLikeCounts(postID string) (int64, error)
|
||||||
GetPostsCount(c *CollectionObj, includeFuture bool)
|
GetPostsCount(c *CollectionObj, includeFuture bool) error
|
||||||
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error)
|
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool, contentType PostType) (*[]PublicPost, error)
|
||||||
GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error)
|
GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error)
|
||||||
GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
|
GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
|
||||||
|
|
||||||
|
@ -1258,7 +1260,7 @@ func (db *datastore) GetPostLikeCounts(postID string) (int64, error) {
|
||||||
// GetPostsCount modifies the CollectionObj to include the correct number of
|
// GetPostsCount modifies the CollectionObj to include the correct number of
|
||||||
// standard (non-pinned) posts. It will return future posts if `includeFuture`
|
// standard (non-pinned) posts. It will return future posts if `includeFuture`
|
||||||
// is true.
|
// is true.
|
||||||
func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
|
func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) error {
|
||||||
var count int64
|
var count int64
|
||||||
timeCondition := ""
|
timeCondition := ""
|
||||||
if !includeFuture {
|
if !includeFuture {
|
||||||
|
@ -1271,16 +1273,18 @@ func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Error("Failed selecting from collections: %v", err)
|
log.Error("Failed selecting from collections: %v", err)
|
||||||
c.TotalPosts = 0
|
c.TotalPosts = 0
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.TotalPosts = int(count)
|
c.TotalPosts = int(count)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPosts retrieves all posts for the given Collection.
|
// GetPosts retrieves all posts for the given Collection.
|
||||||
// It will return future posts if `includeFuture` is true.
|
// It will return future posts if `includeFuture` is true.
|
||||||
// It will include only standard (non-pinned) posts unless `includePinned` is true.
|
// It will include only standard (non-pinned) posts unless `includePinned` is true.
|
||||||
// TODO: change includeFuture to isOwner, since that's how it's used
|
// TODO: change includeFuture to isOwner, since that's how it's used
|
||||||
func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) {
|
func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool, contentType PostType) (*[]PublicPost, error) {
|
||||||
collID := c.ID
|
collID := c.ID
|
||||||
|
|
||||||
cf := c.NewFormat()
|
cf := c.NewFormat()
|
||||||
|
@ -1294,6 +1298,9 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
|
||||||
if page == 0 {
|
if page == 0 {
|
||||||
start = 0
|
start = 0
|
||||||
pagePosts = 1000
|
pagePosts = 1000
|
||||||
|
} else if contentType == postArch {
|
||||||
|
pagePosts = postsPerArchPage
|
||||||
|
start = page*pagePosts - pagePosts
|
||||||
}
|
}
|
||||||
|
|
||||||
limitStr := ""
|
limitStr := ""
|
||||||
|
@ -1308,6 +1315,7 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
|
||||||
if !includePinned {
|
if !includePinned {
|
||||||
pinnedCondition = "AND pinned_position IS NULL"
|
pinnedCondition = "AND pinned_position IS NULL"
|
||||||
}
|
}
|
||||||
|
// FUTURE: handle different post contentType's here
|
||||||
rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? "+pinnedCondition+" "+timeCondition+" ORDER BY created "+order+limitStr, collID)
|
rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? "+pinnedCondition+" "+timeCondition+" ORDER BY created "+order+limitStr, collID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed selecting from posts: %v", err)
|
log.Error("Failed selecting from posts: %v", err)
|
||||||
|
@ -1328,7 +1336,13 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
|
||||||
p.augmentContent(c)
|
p.augmentContent(c)
|
||||||
p.formatContent(cfg, c, includeFuture, false)
|
p.formatContent(cfg, c, includeFuture, false)
|
||||||
|
|
||||||
posts = append(posts, p.processPost())
|
pubPost := p.processPost()
|
||||||
|
if contentType == postArch {
|
||||||
|
// Overwrite DisplayDate with special Archive page version
|
||||||
|
loc := monday.FuzzyLocale(pubPost.Language.String)
|
||||||
|
pubPost.DisplayDate = monday.Format(pubPost.Created, monday.LongNoYrFormatsByLocale[loc], loc)
|
||||||
|
}
|
||||||
|
posts = append(posts, pubPost)
|
||||||
}
|
}
|
||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2001,7 +2015,7 @@ func (db *datastore) GetMeStats(u *User) userMeStats {
|
||||||
|
|
||||||
func (db *datastore) GetTotalCollections() (collCount int64, err error) {
|
func (db *datastore) GetTotalCollections() (collCount int64, err error) {
|
||||||
err = db.QueryRow(`
|
err = db.QueryRow(`
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM collections c
|
FROM collections c
|
||||||
LEFT JOIN users u ON u.id = c.owner_id
|
LEFT JOIN users u ON u.id = c.owner_id
|
||||||
WHERE u.status = 0`).Scan(&collCount)
|
WHERE u.status = 0`).Scan(&collCount)
|
||||||
|
@ -3127,10 +3141,10 @@ func (db *datastore) GetEmailSubscribers(collID int64, reqConfirmed bool) ([]*Em
|
||||||
if reqConfirmed {
|
if reqConfirmed {
|
||||||
cond = " AND confirmed = 1"
|
cond = " AND confirmed = 1"
|
||||||
}
|
}
|
||||||
rows, err := db.Query(`SELECT s.id, collection_id, user_id, s.email, u.email, subscribed, token, confirmed, allow_export
|
rows, err := db.Query(`SELECT s.id, collection_id, user_id, s.email, u.email, subscribed, token, confirmed, allow_export
|
||||||
FROM emailsubscribers s
|
FROM emailsubscribers s
|
||||||
LEFT JOIN users u
|
LEFT JOIN users u
|
||||||
ON u.id = user_id
|
ON u.id = user_id
|
||||||
WHERE collection_id = ?`+cond+`
|
WHERE collection_id = ?`+cond+`
|
||||||
ORDER BY subscribed DESC`, collID)
|
ORDER BY subscribed DESC`, collID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -119,7 +119,7 @@ func compileFullExport(app *App, u *User) *ExportUser {
|
||||||
var collObjs []CollectionObj
|
var collObjs []CollectionObj
|
||||||
for _, c := range *colls {
|
for _, c := range *colls {
|
||||||
co := &CollectionObj{Collection: c}
|
co := &CollectionObj{Collection: c}
|
||||||
co.Posts, err = app.db.GetPosts(app.cfg, &c, 0, true, false, true)
|
co.Posts, err = app.db.GetPosts(app.cfg, &c, 0, true, false, true, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("unable to get collection posts: %v", err)
|
log.Error("unable to get collection posts: %v", err)
|
||||||
}
|
}
|
||||||
|
|
2
feed.go
2
feed.go
|
@ -67,7 +67,7 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, 1, false)
|
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, 1, false)
|
||||||
} else {
|
} else {
|
||||||
coll.Posts, _ = app.db.GetPosts(app.cfg, c, 1, false, true, false)
|
coll.Posts, _ = app.db.GetPosts(app.cfg, c, 1, false, true, false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
author := ""
|
author := ""
|
||||||
|
|
|
@ -111,7 +111,7 @@ func handleGopherCollection(app *App, w gopher.ResponseWriter, r *gopher.Request
|
||||||
w.WriteInfo(c.Description)
|
w.WriteInfo(c.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
|
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ body {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #111;
|
color: #111;
|
||||||
|
|
||||||
h1, header h2 {
|
h1, header h2 {
|
||||||
a {
|
a {
|
||||||
color: @headerTextColor;
|
color: @headerTextColor;
|
||||||
|
@ -672,6 +672,26 @@ body#collection article, body#subpage article {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#wrapper.archive {
|
||||||
|
h1 {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
body#post article {
|
body#post article {
|
||||||
p.badge {
|
p.badge {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
@ -1101,7 +1121,7 @@ li {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.errors {
|
ul.errors {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-indent: 0;
|
text-indent: 0;
|
||||||
li.urgent {
|
li.urgent {
|
||||||
|
@ -1637,4 +1657,4 @@ p#emailsub {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 0.25em;
|
margin-left: 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ body {
|
||||||
&:hover {
|
&:hover {
|
||||||
.opacity(1);
|
.opacity(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
article, pre, .hljs {
|
article, pre, .hljs, #wrapper.archive ul {
|
||||||
padding: 0.5em 2rem 1.5em;
|
padding: 0.5em 2rem 1.5em;
|
||||||
}
|
}
|
||||||
body#post article, pre, .hljs {
|
body#post article, pre, .hljs {
|
||||||
|
|
10
posts.go
10
posts.go
|
@ -49,6 +49,12 @@ const (
|
||||||
postIDLen = 10
|
postIDLen = 10
|
||||||
|
|
||||||
postMetaDateFormat = "2006-01-02 15:04:05"
|
postMetaDateFormat = "2006-01-02 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
postArch PostType = "archive"
|
||||||
|
|
||||||
shortCodePaid = "<!--paid-->"
|
shortCodePaid = "<!--paid-->"
|
||||||
)
|
)
|
||||||
|
@ -1510,6 +1516,10 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
|
||||||
// User tried to access blog feed without a trailing slash, and
|
// User tried to access blog feed without a trailing slash, and
|
||||||
// there's no post with a slug "feed"
|
// there's no post with a slug "feed"
|
||||||
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "feed/"}
|
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "feed/"}
|
||||||
|
} else if slug == "archive" {
|
||||||
|
// User tried to access blog Archive without a trailing slash, and
|
||||||
|
// there's no post with a slug "archive"
|
||||||
|
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "archive/"}
|
||||||
}
|
}
|
||||||
|
|
||||||
po := &Post{
|
po := &Post{
|
||||||
|
|
|
@ -221,6 +221,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
||||||
func RouteCollections(handler *Handler, r *mux.Router) {
|
func RouteCollections(handler *Handler, r *mux.Router) {
|
||||||
r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional))
|
r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional))
|
||||||
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
|
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
|
||||||
|
r.HandleFunc("/archive/", handler.Web(handleViewCollection, UserLevelReader))
|
||||||
|
r.HandleFunc("/{archive:archive}/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}}", handler.Web(handleViewCollectionLang, UserLevelOptional))
|
||||||
r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", 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}", handler.Web(handleViewCollectionTag, UserLevelReader))
|
||||||
|
|
|
@ -66,7 +66,7 @@ func handleViewSitemap(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
host = c.CanonicalURL()
|
host = c.CanonicalURL()
|
||||||
|
|
||||||
sm := buildSitemap(host, pre)
|
sm := buildSitemap(host, pre)
|
||||||
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
|
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting posts: %v", err)
|
log.Error("Error getting posts: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -70,14 +70,14 @@ func initTemplate(parentDir, name string) {
|
||||||
filepath.Join(parentDir, templatesDir, "base.tmpl"),
|
filepath.Join(parentDir, templatesDir, "base.tmpl"),
|
||||||
filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
|
filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
|
||||||
}
|
}
|
||||||
if name == "collection" || name == "collection-tags" || name == "chorus-collection" || name == "read" {
|
if name == "collection" || name == "collection-tags" || name == "collection-archive" || name == "chorus-collection" || name == "read" {
|
||||||
// These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
|
// These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
|
||||||
files = append(files, filepath.Join(parentDir, templatesDir, "include", "posts.tmpl"))
|
files = append(files, filepath.Join(parentDir, templatesDir, "include", "posts.tmpl"))
|
||||||
}
|
}
|
||||||
if name == "chorus-collection" || name == "chorus-collection-post" {
|
if name == "chorus-collection" || name == "chorus-collection-post" {
|
||||||
files = append(files, filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"))
|
files = append(files, filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"))
|
||||||
}
|
}
|
||||||
if name == "collection" || name == "collection-tags" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
|
if name == "collection" || name == "collection-tags" || name == "collection-archive" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
|
||||||
files = append(files, filepath.Join(parentDir, templatesDir, "include", "post-render.tmpl"))
|
files = append(files, filepath.Join(parentDir, templatesDir, "include", "post-render.tmpl"))
|
||||||
}
|
}
|
||||||
templates[name] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))
|
templates[name] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))
|
||||||
|
|
118
templates/collection-archive.tmpl
Normal file
118
templates/collection-archive.tmpl
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
{{define "collection"}}<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>Archive — {{.Collection.DisplayTitle}}</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/write.css" />
|
||||||
|
{{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 .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" />
|
||||||
|
|
||||||
|
<meta name="generator" content="WriteFreely">
|
||||||
|
<meta name="description" content="{{.PlainDescription}}">
|
||||||
|
<meta itemprop="name" content="{{.DisplayTitle}}">
|
||||||
|
<meta itemprop="description" content="{{.PlainDescription}}">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:title" content="{{.DisplayTitle}}">
|
||||||
|
<meta name="twitter:image" content="{{.AvatarURL}}">
|
||||||
|
<meta name="twitter:description" content="{{.PlainDescription}}">
|
||||||
|
<meta property="og:title" content="{{.DisplayTitle}}" />
|
||||||
|
<meta property="og:site_name" content="{{.DisplayTitle}}" />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:url" content="{{.CanonicalURL}}" />
|
||||||
|
<meta property="og:description" content="{{.PlainDescription}}" />
|
||||||
|
<meta property="og:image" content="{{.AvatarURL}}">
|
||||||
|
{{template "collection-meta" .}}
|
||||||
|
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body id="subpage">
|
||||||
|
|
||||||
|
<div id="overlay"></div>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1 dir="{{.Direction}}" id="blog-title"><a href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1>
|
||||||
|
<nav>
|
||||||
|
{{if .PinnedPosts}}
|
||||||
|
{{range .PinnedPosts}}<a class="pinned" href="{{if $.IsOwner}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.DisplayTitle}}</a>{{end}}
|
||||||
|
{{end}}
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{{if .Posts -}}
|
||||||
|
<section id="wrapper" class="archive" itemscope itemtype="http://schema.org/Blog">
|
||||||
|
{{- else -}}
|
||||||
|
<div id="wrapper" class="archive">
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
<h1>Archive</h1>
|
||||||
|
|
||||||
|
{{if .Flash}}
|
||||||
|
<div class="alert success flash">
|
||||||
|
<p>{{.Flash}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{{ $curYear := 0 }}
|
||||||
|
{{ range $el := .Posts }}
|
||||||
|
{{if ne $curYear .Created.Year}}<li class="year">{{.Created.Year}}</li>{{ $curYear = .Created.Year }}{{end}}
|
||||||
|
<li>
|
||||||
|
{{if .HasTitleLink -}}
|
||||||
|
{{.HTMLTitleArrow}}
|
||||||
|
{{- else -}}
|
||||||
|
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url" class="u-url">
|
||||||
|
{{- if .DisplayTitle -}}
|
||||||
|
{{- .DisplayTitle -}}
|
||||||
|
{{- else -}}
|
||||||
|
(Untitled)
|
||||||
|
{{end}}
|
||||||
|
</a>
|
||||||
|
{{- end}}
|
||||||
|
{{if .IsScheduled}}[Scheduled]{{end}}
|
||||||
|
{{if $.Format.ShowDates -}}
|
||||||
|
<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}">
|
||||||
|
{{- if .HasTitleLink -}}
|
||||||
|
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url">
|
||||||
|
{{- end -}}
|
||||||
|
{{.DisplayDate}}
|
||||||
|
{{- if .HasTitleLink -}}
|
||||||
|
{{- if .IsPaid}}{{template "paid-badge" (dict "CDNHost" $.CDNHost)}}{{end -}}</a>
|
||||||
|
{{- end -}}
|
||||||
|
</time>
|
||||||
|
{{- else -}}
|
||||||
|
{{- if .HasTitleLink -}}
|
||||||
|
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url">view</a>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end}}
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{template "paging" .}}
|
||||||
|
|
||||||
|
{{if .Posts}}</section>{{else}}</div>{{end}}
|
||||||
|
|
||||||
|
{{if .ShowFooterBranding }}
|
||||||
|
<footer>
|
||||||
|
<hr />
|
||||||
|
<nav dir="ltr">
|
||||||
|
{{if not .SingleUser}}<a class="home pubd" href="/">{{.SiteName}}</a> · {{end}}powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
{{if .CanShowScript}}
|
||||||
|
{{range .ExternalScripts}}<script type="text/javascript" src="{{.}}" async></script>{{end}}
|
||||||
|
{{if .Collection.Script}}<script type="text/javascript">{{.ScriptDisplay}}</script>{{end}}
|
||||||
|
{{end}}
|
||||||
|
<script src="/js/localdate.js"></script>
|
||||||
|
</html>{{end}}
|
|
@ -61,4 +61,16 @@
|
||||||
<a class="read-more" href="{{$.CanonicalURL}}{{.Slug.String}}">{{localstr "Read more..." .Language.String}}</a>{{else}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book e-content">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}{{.HTMLContent}}</div>{{end}}</article>{{ end }}
|
<a class="read-more" href="{{$.CanonicalURL}}{{.Slug.String}}">{{localstr "Read more..." .Language.String}}</a>{{else}}<div {{if .Language}}lang="{{.Language.String}}"{{end}} dir="{{.Direction}}" class="book e-content">{{if and (and (not $.IsOwner) (not $.Format.ShowDates)) (not .Title.String)}}<a class="hidden action" href="{{if $.IsOwner}}/{{$.Alias}}/{{.Slug.String}}{{else}}{{$.CanonicalURL}}{{.Slug.String}}{{end}}">view</a>{{end}}{{.HTMLContent}}</div>{{end}}</article>{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{define "paid-badge"}}<img class="paid" alt="Paid article" src="/img/paidarticle.svg" /> {{end}}
|
{{define "paid-badge"}}<img class="paid" alt="Paid article" src="/img/paidarticle.svg" /> {{end}}
|
||||||
|
|
||||||
|
{{define "paging"}}
|
||||||
|
{{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 .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 .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}}
|
||||||
|
{{end}}
|
||||||
|
|
Loading…
Add table
Reference in a new issue