← Back to Realms

Realm

gno.land/r/moul/x/daily/blog/v1

Overview

Realm Path
gno.land/r/moul/x/daily/blog/v1
Exported Functions
3
State Entries
2
Source Files
3
Total Package Entries
8

Exported Functions

3 exported functions

State

2 state entries

Source Code

FILES
blog.gno
go
1// Package blog is a multi-author on-chain blog realm for gno.land.
2//
3// Any caller can Publish a post; posts are stored persistently and rendered
4// newest-first. Each post records its author (the calling address) and the
5// block height at which it was published.
6package blog
7
8import (
9	"strconv"
10	"strings"
11
12	"chain"
13	"chain/runtime"
14	"chain/runtime/unsafe"
15
16	"gno.land/p/moul/kit/store/v0"
17)
18
19// Post is a single blog entry. It carries no id field: the id belongs to the
20// store, which hands it back on lookup and iteration.
21type Post struct {
22	Title  string
23	Body   string
24	Author address
25	Height int64
26}
27
28// posts holds every published post. The store keys by seqid, so iteration is
29// in publication order for any number of posts; v0 kept its own nextID plus a
30// key() that zero-padded to width 12 and stopped ordering past it.
31var posts = store.Named("post")
32
33// Publish creates a new post authored by the caller and returns its id.
34// It is a state-mutating exported function, so it takes `cur realm`.
35func Publish(cur realm, title string, body string) int {
36	title = strings.TrimSpace(title)
37	body = strings.TrimSpace(body)
38	if title == "" {
39		panic("blog: title must not be empty")
40	}
41	if body == "" {
42		panic("blog: body must not be empty")
43	}
44
45	author := unsafe.PreviousRealm().Address()
46	id := posts.Add(&Post{
47		Title:  title,
48		Body:   body,
49		Author: author,
50		Height: runtime.ChainHeight(),
51	})
52
53	chain.Emit(
54		"PostPublished",
55		"id", id.String(),
56		"author", author.String(),
57	)
58	return int(id)
59}
60
61// PostCount returns the number of published posts.
62func PostCount() int {
63	return posts.Len()
64}
65
66// snippet returns a short one-line preview of the body.
67func snippet(body string) string {
68	// collapse newlines so the preview stays on a single markdown line
69	s := strings.ReplaceAll(body, "\n", " ")
70	s = strings.TrimSpace(s)
71	const max = 140
72	if len(s) > max {
73		return s[:max] + "…"
74	}
75	return s
76}
77
78// Render lists all posts newest-first at the root, or a single post's full
79// text at path "/<id>". Render is NOT a crossing function.
80func Render(path string) string {
81	path = strings.TrimSpace(path)
82	if path == "" || path == "/" {
83		return renderList()
84	}
85	idStr := strings.TrimPrefix(path, "/")
86	id, ok := store.ParseID(idStr)
87	if !ok {
88		return "# Not found\n\nInvalid post path: `" + path + "`\n\n[← back](/r/REPLACE_ADDR/blog)\n"
89	}
90	v, ok := posts.Get(id)
91	if !ok {
92		return "# Not found\n\nNo post with id " + idStr + ".\n\n[← back](/r/REPLACE_ADDR/blog)\n"
93	}
94	return renderPost(v.(*Post))
95}
96
97func renderList() string {
98	var b strings.Builder
99	b.WriteString("# 📝 Blog\n\n")
100	if posts.Len() == 0 {
101		b.WriteString("_No posts yet. Be the first to `Publish`._\n")
102		return b.String()
103	}
104	b.WriteString(strconv.Itoa(posts.Len()) + " post(s), newest first.\n\n")
105
106	// Ids ascend with publication, so reverse iteration is newest-first.
107	posts.EachReverse(func(id store.ID, v any) {
108		p := v.(*Post)
109		b.WriteString("## [")
110		b.WriteString(p.Title)
111		b.WriteString("](/r/REPLACE_ADDR/blog:/")
112		b.WriteString(id.String())
113		b.WriteString(")\n\n")
114		b.WriteString("by `")
115		b.WriteString(p.Author.String())
116		b.WriteString("` · height ")
117		b.WriteString(strconv.FormatInt(p.Height, 10))
118		b.WriteString("\n\n")
119		b.WriteString(snippet(p.Body))
120		b.WriteString("\n\n---\n\n")
121	})
122	return b.String()
123}
124
125func renderPost(p *Post) string {
126	var b strings.Builder
127	b.WriteString("# ")
128	b.WriteString(p.Title)
129	b.WriteString("\n\n")
130	b.WriteString("by `")
131	b.WriteString(p.Author.String())
132	b.WriteString("` · height ")
133	b.WriteString(strconv.FormatInt(p.Height, 10))
134	b.WriteString("\n\n")
135	b.WriteString(p.Body)
136	b.WriteString("\n\n---\n\n[← back to all posts](/r/REPLACE_ADDR/blog)\n")
137	return b.String()
138}
139

Raw Package Data

Raw JSON data