1// Package reactions is a reaction block any realm can embed, keyed on the page2// 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, the5// same snippet pasted everywhere, deciding what to show from the page6// 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 read15// exported by a realm and called by another opens no realm frame, so16// unsafe.CurrentRealm() inside it reports the CALLER. The host realm's own path17// 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 one19// page or already holds its path as a constant.20//21// The host realm stores nothing. Every tally lives here, so a realm can add22// reactions without a redeploy the next time it wants to change them, and a23// reader's reaction survives the host realm being replaced.24//25// # What it is not26//27// Not a comment system. There is no text field anywhere, which is what makes28// it possible to run with no moderation at all: six emoji, one reaction per29// address per page, changeable and removable. The page shows the tally and who30// reacted last. That is the whole feature.31package reactions3233import (34 "chain"35 "chain/runtime"36 "chain/runtime/unsafe"3738 rx "gno.land/p/moul/reactions/v0"39)4041// realmPath is this realm's own path, the one its gnomod.toml module line42// declares. It is written out rather than read from unsafe.CurrentRealm(),43// which reports the CALLER in every read this realm exports and would therefore44// build every link against whichever realm embedded the block.45const realmPath = "gno.land/r/moul/reactions/v0"4647// board holds every page's tally.48//49// A redeploy would wipe it, which is why this realm is not private (see50// gnomod.toml): the state here belongs to everyone who reacted, and the only51// honest way to change this realm is a v1 beside it.52var board = rx.NewBoard()5354// 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 not57// 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 something59// 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 moves65// 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 }7677 who := unsafe.PreviousRealm().Address()78 board.React(page, who, key, runtime.ChainHeight())79 chain.Emit("React", "page", page, "key", key, "who", who.String())80}8182// Unreact removes the caller's reaction from page. It aborts when there is83// none, rather than succeeding silently, so a misspelled page key is visible84// 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}9596// RenderBlock returns the block for the realm CALLING it, which is the form a97// host realm embeds.98//99// It has no cur realm parameter on purpose. A plain read is borrowed: gno opens100// no realm frame for it, so unsafe.CurrentRealm() reports the caller's path and101// the block keys itself with no argument. Adding a cur realm here would102// 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 is105// never what a caller wants; the views below use the unexported blockFor.106func RenderBlock() string {107 return blockFor(unsafe.CurrentRealm().PkgPath())108}109110// RenderBlockFor is [RenderBlock] for a named page. Use it from a crossing111// function, where there is no caller to read off the stack, or when one realm112// renders several pages.113func RenderBlockFor(page string) string { return blockFor(page) }114115// blockFor is the shared body, so no exported read of this realm calls another116// and picks up the wrong path.117func blockFor(page string) string {118 return rx.Block(realmPath, page, board.Page(page))119}120121// Count reports how many addresses have a reaction on page.122func Count(page string) int { return board.Page(page).Total() }123124// CountOf reports how many addresses hold one particular reaction on page.125func CountOf(page, key string) int { return board.Page(page).Count(key) }126127// ReactionOf returns who's current reaction key on page, or "" when they have128// 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) }130131// 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}136137// Pages reports how many pages have ever been reacted to.138func Pages() int { return board.Pages() }139Count(page string) int
CountOf(page string, key string) int
Last(page string) (key string, who string, height int64)
Pages() int
React(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, page string, key string)
ReactionOf(page string, who string) string
Render(path string) string
RenderBlock() string
RenderBlockFor(page string) string
Unreact(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, page string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.