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

reactions.gno

5.58 Kb · 138 lines
  1// Package reactions is a reaction block any realm can embed, keyed on the page
  2// it appears on rather than on the realm that stores it.
  3//
  4// It is the shape the web settled on for this in 2010: one hosted widget, the
  5// same snippet pasted everywhere, deciding what to show from the page
  6// identifier it is handed. Here the snippet is two lines of gno:
  7//
  8//	import "gno.land/r/moul/reactions/v0"
  9//
 10//	func Render(path string) string {
 11//		return body + reactions.RenderBlock()
 12//	}
 13//
 14// [RenderBlock] takes no argument because it does not need one: a plain read
 15// exported by a realm and called by another opens no realm frame, so
 16// unsafe.CurrentRealm() inside it reports the CALLER. The host realm's own path
 17// is the page key, and nothing has to be configured, registered or passed.
 18// [RenderBlockFor] is the explicit form, for a realm that renders more than one
 19// page or already holds its path as a constant.
 20//
 21// The host realm stores nothing. Every tally lives here, so a realm can add
 22// reactions without a redeploy the next time it wants to change them, and a
 23// reader's reaction survives the host realm being replaced.
 24//
 25// # What it is not
 26//
 27// Not a comment system. There is no text field anywhere, which is what makes
 28// it possible to run with no moderation at all: six emoji, one reaction per
 29// address per page, changeable and removable. The page shows the tally and who
 30// reacted last. That is the whole feature.
 31package reactions
 32
 33import (
 34	"chain"
 35	"chain/runtime"
 36	"chain/runtime/unsafe"
 37
 38	rx "gno.land/p/moul/reactions/v0"
 39)
 40
 41// realmPath is this realm's own path, the one its gnomod.toml module line
 42// declares. It is written out rather than read from unsafe.CurrentRealm(),
 43// which reports the CALLER in every read this realm exports and would therefore
 44// build every link against whichever realm embedded the block.
 45const realmPath = "gno.land/r/moul/reactions/v0"
 46
 47// board holds every page's tally.
 48//
 49// A redeploy would wipe it, which is why this realm is not private (see
 50// gnomod.toml): the state here belongs to everyone who reacted, and the only
 51// honest way to change this realm is a v1 beside it.
 52var board = rx.NewBoard()
 53
 54// React records the caller's reaction to page.
 55//
 56// page is a package path, the full one: "gno.land/r/moul/home". It does not
 57// have to be a realm that embeds the block, and nothing checks that it exists.
 58// A page is just a name, which is what lets a realm key the block on something
 59// finer than itself.
 60//
 61// key is one of the palette names, not a glyph: "up", "heart", "fire", "party",
 62// "rocket", "eyes". Render("") lists them with their emoji.
 63//
 64// One reaction per address per page. Reacting again with a different key moves
 65// it; reacting with the same key changes nothing but the "last reaction" line.
 66func React(cur realm, page, key string) {
 67	if !cur.IsCurrent() {
 68		panic("spoofed realm: cur is not the live crossing frame")
 69	}
 70	if !rx.Valid(key) {
 71		panic("reactions: no such reaction: " + key)
 72	}
 73	if !rx.ValidPage(page) {
 74		panic("reactions: not a page key: " + page)
 75	}
 76
 77	who := unsafe.PreviousRealm().Address()
 78	board.React(page, who, key, runtime.ChainHeight())
 79	chain.Emit("React", "page", page, "key", key, "who", who.String())
 80}
 81
 82// Unreact removes the caller's reaction from page. It aborts when there is
 83// none, rather than succeeding silently, so a misspelled page key is visible
 84// instead of looking like a no-op.
 85func Unreact(cur realm, page string) {
 86	if !cur.IsCurrent() {
 87		panic("spoofed realm: cur is not the live crossing frame")
 88	}
 89	who := unsafe.PreviousRealm().Address()
 90	if !board.Unreact(page, who) {
 91		panic("reactions: nothing to remove on " + page)
 92	}
 93	chain.Emit("Unreact", "page", page, "who", who.String())
 94}
 95
 96// RenderBlock returns the block for the realm CALLING it, which is the form a
 97// host realm embeds.
 98//
 99// It has no cur realm parameter on purpose. A plain read is borrowed: gno opens
100// no realm frame for it, so unsafe.CurrentRealm() reports the caller's path and
101// the block keys itself with no argument. Adding a cur realm here would
102// silently reverse that and key every embed to this realm instead.
103//
104// Calling it from inside this realm therefore keys on this realm, which is
105// never what a caller wants; the views below use the unexported blockFor.
106func RenderBlock() string {
107	return blockFor(unsafe.CurrentRealm().PkgPath())
108}
109
110// RenderBlockFor is [RenderBlock] for a named page. Use it from a crossing
111// function, where there is no caller to read off the stack, or when one realm
112// renders several pages.
113func RenderBlockFor(page string) string { return blockFor(page) }
114
115// blockFor is the shared body, so no exported read of this realm calls another
116// and picks up the wrong path.
117func blockFor(page string) string {
118	return rx.Block(realmPath, page, board.Page(page))
119}
120
121// Count reports how many addresses have a reaction on page.
122func Count(page string) int { return board.Page(page).Total() }
123
124// CountOf reports how many addresses hold one particular reaction on page.
125func CountOf(page, key string) int { return board.Page(page).Count(key) }
126
127// ReactionOf returns who's current reaction key on page, or "" when they have
128// none. A host realm can use it to say "you reacted" without reading the tree.
129func ReactionOf(page string, who address) string { return board.Page(page).Of(who) }
130
131// Last returns the most recent reaction on page: the palette key, the address,
132// and the height. The key is "" when the page has never been reacted to.
133func Last(page string) (key string, who address, height int64) {
134	return board.Page(page).Last()
135}
136
137// Pages reports how many pages have ever been reacted to.
138func Pages() int { return board.Pages() }