Search Apps Documentation Source Content File Folder Download Copy Actions Download

comment.gno

3.03 Kb · 165 lines
  1package blog
  2
  3import (
  4	"strconv"
  5	"time"
  6
  7	"gno.land/p/moul/md"
  8	"gno.land/p/nt/avl"
  9	"gno.land/p/nt/seqid"
 10)
 11
 12type Comment struct {
 13	id        seqid.ID
 14	author    string
 15	content   string
 16	createdAt time.Time
 17	editedAt  *time.Time // nil if unedited
 18	pinned    bool
 19	likes     int
 20	RepliesId seqid.ID
 21	Replies   *avl.Tree // id --> *Comment
 22
 23	footer       string // additional text, e.g. "via @lou" or txlink calls
 24	DisableLikes bool
 25}
 26
 27func (c Comment) ID() string {
 28	return c.id.String()
 29}
 30
 31func (c Comment) Author() string {
 32	return c.author
 33}
 34
 35func (c Comment) Content() string {
 36	return c.content
 37}
 38
 39func (c Comment) CreatedAt() time.Time {
 40	return c.createdAt
 41}
 42
 43func (c Comment) EditedAt() *time.Time {
 44	return c.editedAt
 45}
 46
 47func (c Comment) Pinned() bool {
 48	return c.pinned
 49}
 50
 51func (c Comment) Likes() int {
 52	return c.likes
 53}
 54
 55func (c Comment) Footer() string {
 56	return c.footer
 57}
 58
 59func NewComment(author, content string) (*Comment, error) {
 60	if content == "" {
 61		return nil, ErrEmptyComment
 62	}
 63	return &Comment{
 64		author:       author,
 65		content:      content,
 66		createdAt:    time.Now(),
 67		editedAt:     nil,
 68		pinned:       false,
 69		likes:        0,
 70		RepliesId:    seqid.ID(0),
 71		Replies:      avl.NewTree(),
 72		footer:       "",
 73		DisableLikes: false,
 74	}, nil
 75}
 76
 77func (c *Comment) Edit(content string) error {
 78	if content == "" {
 79		return ErrEmptyComment
 80	}
 81	c.content = content
 82	now := time.Now()
 83	c.editedAt = &now
 84	return nil
 85}
 86
 87func (c *Comment) Pin() {
 88	c.pinned = true
 89}
 90
 91func (c *Comment) Unpin() {
 92	c.pinned = false
 93}
 94
 95func (c *Comment) AddLike() {
 96	c.likes++
 97}
 98
 99func (c *Comment) RemoveLike() {
100	if c.likes > 0 {
101		c.likes--
102	}
103}
104
105func (c *Comment) SetID(id seqid.ID) {
106	c.id = id
107}
108
109func (c *Comment) SetFooter(footer string) {
110	c.footer = footer
111}
112
113func (c *Comment) Render(blogPrefix string, depth int, resolver UserResolver) string {
114	prefix := ""
115	for i := 0; i < depth; i++ {
116		prefix += "> "
117	}
118	user, _ := CheckUser(c.Author(), resolver)
119	out := prefix + md.Bold(md.Link("@"+user, blogPrefix+":commenters/"+user)) + " "
120	if c.Pinned() {
121		out += "📌 "
122	}
123	if c.EditedAt() != nil {
124		out += md.Italic(formatTime(*c.EditedAt(), "relative")+" (edited)") + "\n"
125	} else {
126		out += md.Italic(formatTime(c.CreatedAt(), "relative")) + "\n"
127	}
128	out += "\n" + prefix + c.Content() + "\n"
129	if !c.DisableLikes {
130		out += prefix + "❤️ " + strconv.Itoa(c.Likes()) + " \n\n"
131	}
132	if c.Footer() != "" {
133		out += prefix + c.Footer() + "\n\n"
134	}
135
136	if c.Replies != nil && c.Replies.Size() > 0 {
137		c.Replies.ReverseIterate("", "", func(key string, value any) bool {
138			reply := value.(*Comment)
139			out += reply.Render(blogPrefix, depth+1, resolver)
140			return false
141		})
142	}
143	return out
144}
145
146func searchInReplies(tree *avl.Tree, id string, result **Comment) bool {
147	if tree == nil {
148		return false
149	}
150	found := false
151	tree.ReverseIterate("", "", func(_ string, v interface{}) bool {
152		c := v.(*Comment)
153		if c.ID() == id {
154			*result = c
155			found = true
156			return true
157		}
158		if searchInReplies(c.Replies, id, result) {
159			found = true
160			return true
161		}
162		return false
163	})
164	return found
165}