Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blog): add sorting, better rendering #1541

Merged
merged 33 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
51a7593
add sorting by title & pub date
leohhhn Jan 16, 2024
66e6cdc
Merge branch 'master' into feat/blog-sort
leohhhn Jan 16, 2024
57fe188
add publisher, authors, modify publish date. add nicer rendering
leohhhn Jan 20, 2024
013a6a2
remove usage of seqid
leohhhn Jan 20, 2024
2fcd656
reverse sort order
leohhhn Jan 20, 2024
fd4e93d
Merge branch 'master' into feat/blog-sort
leohhhn Jan 22, 2024
4fa2e98
update ModEditPost
leohhhn Jan 22, 2024
524df94
modify post footer, post list item
leohhhn Jan 23, 2024
1f6afa0
wip fixing bugs
leohhhn Jan 23, 2024
a52b6e2
add rendering back
leohhhn Jan 26, 2024
7b44d93
Merge branch 'master' into feat/blog-sort
leohhhn Jan 26, 2024
dd38742
remove leftovers
leohhhn Jan 26, 2024
713f307
update gnomod
leohhhn Jan 26, 2024
f1ef78f
update tests
leohhhn Jan 26, 2024
84b9a53
render tags better
leohhhn Feb 5, 2024
7ed1b5d
return gen txs
leohhhn Feb 5, 2024
6b272f8
remove newlines
leohhhn Feb 6, 2024
a8694e7
fixup genesis blog tx, start reworking tests
leohhhn Feb 8, 2024
132d2ef
wip
leohhhn Feb 9, 2024
4f916c8
extract post errors
leohhhn Feb 9, 2024
e158248
update dates on pages
leohhhn Feb 9, 2024
d06608b
update dates on pages v2
leohhhn Feb 12, 2024
a15bd8b
temp add test1 as admin to blog realm
leohhhn Feb 13, 2024
28a49d0
fix tests
leohhhn Feb 13, 2024
7aeaa66
fix lint
leohhhn Feb 13, 2024
7a6c2e6
Merge branch 'master' into feat/blog-sort
leohhhn Feb 13, 2024
4283930
fixup CI
leohhhn Feb 14, 2024
b1582a0
update rendering, fix tests
leohhhn Feb 22, 2024
50182ee
Merge branch 'master' into feat/blog-sort
leohhhn Feb 22, 2024
db57f7d
Merge branch 'master' into feat/blog-sort
moul Feb 23, 2024
6c32b09
update comments rendering
leohhhn Feb 24, 2024
d3bc364
fix tests
leohhhn Feb 24, 2024
60a5d61
fix tests 2
leohhhn Feb 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 117 additions & 45 deletions examples/gno.land/p/demo/blog/blog.gno
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package blog

import (
"errors"
"std"
"strconv"
"strings"
Expand All @@ -13,10 +12,12 @@ import (
)

type Blog struct {
Title string
Prefix string // i.e. r/gnoland/blog:
Posts avl.Tree // slug -> Post
NoBreadcrumb bool
Title string
Prefix string // i.e. r/gnoland/blog:
Posts avl.Tree // slug -> *Post
PostsPublished avl.Tree // published-date -> *Post
PostsAlphabetical avl.Tree // title -> *Post
NoBreadcrumb bool
}

func (b Blog) RenderLastPostsWidget(limit int) string {
Expand All @@ -42,7 +43,7 @@ func (b Blog) RenderHome(res *mux.ResponseWriter, req *mux.Request) {
}

res.Write("<div class='columns-3'>")
b.Posts.Iterate("", "", func(key string, value interface{}) bool {
b.PostsPublished.ReverseIterate("", "", func(key string, value interface{}) bool {
post := value.(*Post)
res.Write(post.RenderListItem())
return false
Expand All @@ -62,26 +63,25 @@ func (b Blog) RenderPost(res *mux.ResponseWriter, req *mux.Request) {
}
p := post.(*Post)

if !b.NoBreadcrumb {
breadStr := breadcrumb([]string{
ufmt.Sprintf("[%s](%s)", b.Title, b.Prefix),
"p",
p.Title,
})
res.Write(breadStr)
}

// output += ufmt.Sprintf("## [%s](%s)\n", p.Title, p.URL())
res.Write("# " + p.Title + "\n\n")
res.Write(p.Body + "\n\n")
res.Write("---\n\n")

res.Write(p.RenderTagList() + "\n\n")
res.Write(formatAuthorAndDate(p.Author, p.CreatedAt) + "\n\n")
res.Write(p.RenderAuthorList() + "\n\n")
res.Write(p.RenderPublishData() + "\n\n")

res.Write("---\n")
res.Write("<details><summary>Comment section</summary>\n\n")

// comments
p.Comments.ReverseIterate("", "", func(key string, value interface{}) bool {
comment := value.(*Comment)
res.Write(comment.RenderListItem())
return false
})

res.Write("</details>\n")
}

func (b Blog) RenderTag(res *mux.ResponseWriter, req *mux.Request) {
Expand Down Expand Up @@ -124,42 +124,70 @@ func (b Blog) Render(path string) string {
return router.Render(path)
}

func (b *Blog) NewPost(author std.Address, slug, title, body string, tags []string) error {
_, found := b.Posts.Get(slug)
if found {
return errors.New("slug already exists.")
func (b *Blog) NewPost(publisher std.Address, slug, title, body, pubDate string, authors, tags []string) error {
if _, found := b.Posts.Get(slug); found {
return ErrPostSlugExists
}

post := Post{
Author: author,
var parsedTime time.Time
var err error
if pubDate != "" {
parsedTime, err = time.Parse(time.RFC3339, pubDate)
if err != nil {
return err
}
} else {
// If no publication date was passed in by caller, take current block time
parsedTime = time.Now()
}

post := &Post{
Publisher: publisher,
Authors: authors,
Slug: slug,
Title: title,
Body: body,
Tags: tags,
CreatedAt: time.Now(),
CreatedAt: parsedTime,
}
return b.prepareAndSetPost(&post)

return b.prepareAndSetPost(post)
}

func (b *Blog) prepareAndSetPost(post *Post) error {
post.Title = strings.TrimSpace(post.Title)
post.Body = strings.TrimSpace(post.Body)

if post.Title == "" {
return errors.New("title is missing.")
return ErrPostTitleMissing
}
if post.Body == "" {
return errors.New("body is missing.")
return ErrPostBodyMissing
}
if post.Slug == "" {
return errors.New("slug is missing.")
return ErrPostSlugMissing
}
// more input sanitization?

post.Blog = b
post.UpdatedAt = time.Now()

trimmedTitleKey := strings.Replace(post.Title, " ", "", -1)
pubDateKey := post.CreatedAt.Format(time.RFC3339)

// Cannot have two posts with same title key
if _, found := b.PostsAlphabetical.Get(trimmedTitleKey); found {
return ErrPostTitleExists
}
// Cannot have two posts with *exact* same timestamp
if _, found := b.PostsPublished.Get(pubDateKey); found {
return ErrPostPubDateExists
}

// Store post under keys
b.PostsAlphabetical.Set(trimmedTitleKey, post)
b.PostsPublished.Set(pubDateKey, post)
b.Posts.Set(post.Slug, post)

return nil
}

Expand All @@ -179,15 +207,24 @@ type Post struct {
CreatedAt time.Time
UpdatedAt time.Time
Comments avl.Tree
Author std.Address
Authors []string
Publisher std.Address
Tags []string
CommentIndex int
}

func (p *Post) Update(title, body string, tags []string) error {
func (p *Post) Update(title, body, publicationDate string, authors, tags []string) error {
p.Title = title
p.Body = body
p.Tags = tags
p.Authors = authors

parsedTime, err := time.Parse(time.RFC3339, publicationDate)
if err != nil {
return err
}

p.CreatedAt = parsedTime
return p.Blog.prepareAndSetPost(p)
}

Expand Down Expand Up @@ -234,31 +271,66 @@ func (p *Post) RenderListItem() string {
return "error: no such post\n"
}
output := "<div>\n\n"
output += ufmt.Sprintf("## [%s](%s)\n", p.Title, p.URL())
output += ufmt.Sprintf("**[Learn More](%s)**\n", p.URL())
output += ufmt.Sprintf("### [%s](%s)\n", p.Title, p.URL())
// output += ufmt.Sprintf("**[Learn More](%s)**\n\n", p.URL())

output += " " + p.CreatedAt.Format("02 Jan 2006")
// output += p.Summary() + "\n\n"
// output += p.RenderTagList() + "\n\n"
// output += formatAuthorAndDate(p.Author, p.CreatedAt) + "\n"
output += "\n"
output += "</div>"
return output
}

// Render post tags
func (p *Post) RenderTagList() string {
if p == nil {
return "error: no such post\n"
}
output := ""
if len(p.Tags) == 0 {
return ""
}

output := "Tags: "
for idx, tag := range p.Tags {
if idx > 0 {
output += " "
}
tagURL := p.Blog.Prefix + "t/" + tag
output += ufmt.Sprintf("[#%s](%s)", tag, tagURL)

}
return output
}

// Render authors if there are any
func (p *Post) RenderAuthorList() string {
out := "Written"
if len(p.Authors) != 0 {
out += " by "

for idx, author := range p.Authors {
out += author
if idx < len(p.Authors)-1 {
out += ", "
}
}
}
out += " on " + p.CreatedAt.Format("02 Jan 2006")

return out
}

func (p *Post) RenderPublishData() string {
out := "Published "
if p.Publisher != "" {
out += "by " + p.Publisher.String() + " "
}
out += "to " + p.Blog.Title

return out
}

func (p *Post) URL() string {
if p == nil {
return p.Blog.Prefix + "404"
Expand Down Expand Up @@ -287,15 +359,15 @@ type Comment struct {
}

func (c Comment) RenderListItem() string {
output := ""
output += ufmt.Sprintf("#### %s\n", formatAuthorAndDate(c.Author, c.CreatedAt))
output += c.Comment + "\n"
output += "\n"
return output
}
output := "<h5>"
output += c.Comment + "\n\n"
output += "</h5>"

output += "<h6>"
output += ufmt.Sprintf("by %s on %s", c.Author, c.CreatedAt.Format(time.RFC822))
output += "</h6>\n\n"

func formatAuthorAndDate(author std.Address, createdAt time.Time) string {
authorString := author.String() // FIXME: username.
createdAtString := createdAt.Format("2006-01-02 3:04pm MST")
return ufmt.Sprintf("by %s on %s", authorString, createdAtString)
output += "---\n\n"

return output
}
10 changes: 9 additions & 1 deletion examples/gno.land/p/demo/blog/errors.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ package blog

import "errors"

var ErrNoSuchPost = errors.New("no such post")
var (
ErrPostTitleMissing = errors.New("post title is missing")
ErrPostSlugMissing = errors.New("post slug is missing")
ErrPostBodyMissing = errors.New("post body is missing")
ErrPostSlugExists = errors.New("post with specified slug already exists")
ErrPostPubDateExists = errors.New("post with specified publication date exists")
ErrPostTitleExists = errors.New("post with specified title already exists")
ErrNoSuchPost = errors.New("no such post")
)
22 changes: 17 additions & 5 deletions examples/gno.land/r/gnoland/blog/admin.gno
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,32 @@ func AdminRemoveModerator(addr std.Address) {
moderatorList.Set(addr.String(), false) // FIXME: delete instead?
}

func ModAddPost(slug, title, body, tags string) {
func ModAddPost(slug, title, body, publicationDate, authors, tags string) {
assertIsModerator()

caller := std.GetOrigCaller()
tagList := strings.Split(tags, ",")
err := b.NewPost(caller, slug, title, body, tagList)

var tagList []string
if tags != "" {
tagList = strings.Split(tags, ",")
}
var authorList []string
if authors != "" {
authorList = strings.Split(authors, ",")
}

err := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)

checkErr(err)
}

func ModEditPost(slug, title, body, tags string) {
func ModEditPost(slug, title, body, publicationDate, authors, tags string) {
assertIsModerator()

tagList := strings.Split(tags, ",")
err := b.GetPost(slug).Update(title, body, tagList)
authorList := strings.Split(authors, ",")

err := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)
checkErr(err)
}

Expand Down
7 changes: 7 additions & 0 deletions examples/gno.land/r/gnoland/blog/gnoblog.gno
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ func Render(path string) string {
func RenderLastPostsWidget(limit int) string {
return b.RenderLastPostsWidget(limit)
}

func PostExists(slug string) bool {
if b.GetPost(slug) == nil {
return false
}
return true
}
Loading
Loading