Search Apps Documentation Source Content File Folder Download Copy Actions Download 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() }