Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

todos.gno

3.50 Kb · 150 lines
  1// Package todos is a shared, on-chain to-do board realm.
  2//
  3// Anyone can add an item; the caller's address is recorded as the author.
  4// Items can be toggled (open/done) or removed. Render exposes the board as
  5// Markdown checkboxes with open/done counts, newest item first.
  6package todos
  7
  8import (
  9	"strconv"
 10	"strings"
 11
 12	"gno.land/p/nt/avl/v0"
 13)
 14
 15// item is one entry on the board. `address` is the uverse bech32 type — it is
 16// persistable (unlike a `realm` value), so it is safe to store across txs.
 17type item struct {
 18	id     int
 19	text   string
 20	done   bool
 21	author address
 22}
 23
 24var (
 25	// items is keyed by zero-padded id string so avl iteration is ordered by id.
 26	items  avl.Tree
 27	nextID int
 28)
 29
 30// Add appends a new item authored by the caller and returns its id.
 31//
 32// Crossing function: takes `cur realm` first (gno 0.9 interrealm convention),
 33// authenticates with cur.IsCurrent() before trusting cur.Previous().
 34func Add(cur realm, text string) int {
 35	if !cur.IsCurrent() {
 36		panic("spoofed realm")
 37	}
 38	text = strings.TrimSpace(text)
 39	if text == "" {
 40		panic("todos: text must not be empty")
 41	}
 42
 43	author := cur.Previous().Address()
 44
 45	nextID++
 46	id := nextID
 47	items.Set(idKey(id), &item{
 48		id:     id,
 49		text:   text,
 50		done:   false,
 51		author: author,
 52	})
 53	return id
 54}
 55
 56// Toggle flips the done state of the item with the given id.
 57func Toggle(cur realm, id int) {
 58	if !cur.IsCurrent() {
 59		panic("spoofed realm")
 60	}
 61	v := items.Get(idKey(id))
 62	if v == nil {
 63		panic("todos: item not found: #" + strconv.Itoa(id))
 64	}
 65	it := v.(*item)
 66	it.done = !it.done
 67}
 68
 69// Remove deletes the item with the given id.
 70func Remove(cur realm, id int) {
 71	if !cur.IsCurrent() {
 72		panic("spoofed realm")
 73	}
 74	if _, removed := items.Remove(idKey(id)); !removed {
 75		panic("todos: item not found: #" + strconv.Itoa(id))
 76	}
 77}
 78
 79// idKey renders an id as a fixed-width, zero-padded key so avl.Tree's
 80// byte-lexicographic ordering matches numeric id ordering.
 81func idKey(id int) string {
 82	s := strconv.Itoa(id)
 83	const width = 12
 84	if len(s) >= width {
 85		return s
 86	}
 87	return strings.Repeat("0", width-len(s)) + s
 88}
 89
 90// shortAddr renders an address as "g1abc…" (first 6 chars + ellipsis).
 91func shortAddr(a address) string {
 92	s := a.String()
 93	if len(s) <= 6 {
 94		return s
 95	}
 96	return s[:6] + "…"
 97}
 98
 99// counts returns the number of open and done items.
100func counts() (open, done int) {
101	items.Iterate("", "", func(_ string, v interface{}) bool {
102		if v.(*item).done {
103			done++
104		} else {
105			open++
106		}
107		return false
108	})
109	return open, done
110}
111
112// Render returns the board as Markdown. Newest item (highest id) first.
113//
114// Render is NOT a crossing function (no `cur realm` param) — it is read-only
115// and invoked by gnoweb, not via MsgCall.
116func Render(path string) string {
117	open, done := counts()
118
119	var b strings.Builder
120	b.WriteString("# Shared To-Do Board\n\n")
121	b.WriteString(strconv.Itoa(open) + " open · ")
122	b.WriteString(strconv.Itoa(done) + " done · ")
123	b.WriteString(strconv.Itoa(open+done) + " total\n\n")
124
125	if open+done == 0 {
126		b.WriteString("_No items yet. Add one with `Add(text)`._\n")
127		return b.String()
128	}
129
130	// Collect in ascending id order, then emit newest first.
131	var lines []string
132	items.Iterate("", "", func(_ string, v interface{}) bool {
133		it := v.(*item)
134		mark := " "
135		if it.done {
136			mark = "x"
137		}
138		line := "- [" + mark + "] #" + strconv.Itoa(it.id) + " " +
139			it.text + " (" + shortAddr(it.author) + ")"
140		lines = append(lines, line)
141		return false
142	})
143
144	for i := len(lines) - 1; i >= 0; i-- {
145		b.WriteString(lines[i])
146		b.WriteString("\n")
147	}
148
149	return b.String()
150}