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

v0 source pure

Package wiki is a Wikipedia-shaped wiki engine for gno.land: namespaced titles, an append-only revision chain, wikili...

Readme View source

gno.land/p/moul/x/wiki/v0

A Wikipedia-shaped wiki engine: namespaced titles, an append-only revision chain, wikilinks with backlinks, categories, redirects, protection levels, per-page discussion threads, line diffs, and markdown rendering.

Pure: no realm globals, no chain imports. Every mutating call takes the author address, the wall clock and the block height from its caller, so the engine is unit-testable off-chain and the realm keeps all the authority.

Live demo: gno.land/r/moul/x/wiki/v0.

The storage model

A realm write locks a storage deposit proportional to the bytes it adds (100ugnot per byte, gnolang/gno#6171). A wiki that kept every revision in full would therefore charge its editors rent on the whole history forever: at 5 KB per revision that is 0.5 GNOT locked per edit, permanently.

This engine keeps a content-addressed spine plus a bounded body window:

kept forever, per revision kept only inside the window
id, parent, kind, author, time, height, summary, byte size, SHA-256 of the body, minor flag the body text

Roughly 200 bytes per revision regardless of article size, plus the bodies of the newest Retention revisions of each page (3 by default).

An evicted body is not lost. Its bytes were an argument of the transaction that wrote it, so they are still in the chain's transaction history, and the retained hash proves which bytes were the real ones. The realm stops paying rent for deep history; the archive lives where archives belong. What you lose is the ability to diff or revert to an evicted revision from inside the realm, and both failures are explicit (ErrBodyEvicted) rather than silent.

Purge evicts every body of a page at once and returns the bytes released. It is the lever for content that must stop being served out of realm state; it cannot and does not remove the transactions that wrote it.

Who gets the deposit back

Not who paid it. Verified in processStorageDeposit (gno.land/pkg/sdk/vm/keeper.go, gno master, 2026-09-19):

  • A write sends the caller's ugnot to a per-realm deposit address and adds to two realm-wide pools, rlm.Deposit and rlm.Storage. There is no per-depositor accounting.
  • A release refunds rlm.Deposit * released / rlm.Storage (big-integer, truncating, so dust accrues in the pool) to the caller of the transaction that frees the bytes, at the realm's blended rate.
  • If ugnot is a restricted denom at the time, the refund goes to params.StorageFeeCollector instead, not to any user.

For a wiki that inverts a comfortable assumption. Adding bytes costs the adder, but removing bytes pays the remover. Replacing a long article with a short one evicts an old body in the same transaction, so deletion can be profitable, and reverting the damage costs the good actor who reverts it. That asymmetry is why Blank, Purge and HideComment are steward-gated, and why the realm keeps a ban list, protection levels and an optional cooldown rather than relying on the deposit alone.

Rendering untrusted markdown

Article bodies are attacker-controlled markdown rendered by gnoweb, so the render path has a fixed order:

  1. sanitize.BlockRich the body (gno.land/p/nt/markdown/sanitize/v0).
  2. Then rewrite the wikilinks.

Not the other way around. The sanitizer escapes every [, so [[Gno land]] becomes \[\[Gno land\]\] in its output; a rewriter that ran first would hand the markdown links it just generated to the escaper and every link on the wiki would render as literal text. That is why ScanLinks takes its delimiters as parameters: indexing scans the raw body, rendering scans the escaped one.

The blank lines BlockRich adds around its output are load-bearing, not cosmetic. A CommonMark HTML block of type 6 or 7 is not escaped in any mode, and without the surrounding blank line it would absorb the realm chrome appended after the body.

Two layers, two jobs: sanitize stops markdown structure injection, and gnoweb's own link extension stops URL scheme abuse (javascript: and friends). Neither replaces the other.

The title charset is narrower than MediaWiki's for the same reason. /, |, #, *, [, <, ?, % and & are rejected rather than escaped, which keeps Title.String, Title.Slug and the rendered link byte-identical. TestTitleSurvivesTheRenderPipeline pins that coupling end to end.

Determinism and gas

Render runs under maxGasQuery (3e9 in gno.land/pkg/sdk/vm/keeper.go), which a reader cannot raise, so an unbounded render makes a page permanently unreadable. Three bounds keep it away from that ceiling:

  • MaxBody (32 KiB) caps a revision, and therefore caps every render.
  • DiffMaxLines (80) caps the changed region a diff computes exactly. The common prefix and suffix are trimmed first, so an ordinary edit to a long article still diffs exactly; past the bound a diff degrades to a block replacement instead of failing.
  • Histories, indexes and listings are paginated by the caller.

Measured with gno test -v on gno master.184 (2026-09-17): the diff of a 33 KB body against itself plus one line costs 691M gas, and the article render of a 33 KB body with links costs 693M, each about 23% of the ceiling.

Everything else follows gno's determinism rules: no map iteration anywhere in a render path, avl for every ordered index, and namespace keys padded by hand because ufmt has no width flags (ufmt.Sprintf("%02d", 7) silently returns "7", which would sort User between two main-namespace pages).

Discussion

Discussion is an append-only comment store attached to a page, not a Talk: article. A talk page is an article, so whoever edits last can rewrite what someone else said, and moderating one bad message means editing the whole page. A comment store gives each message its own author, timestamp and moderation: HideComment clears one body and releases its bytes while the message stays in the thread, marked as removed.

Replies nest exactly one level (MaxReplyDepth). Deeper nesting needs recursive rendering with no natural bound, which is the shape a query gas ceiling punishes hardest. Comments are sanitized like article bodies but are not a wikilink slot: brackets in a comment stay literal.

Wiki syntax

syntax meaning
[[Target]] link, rendered from the canonical title
[[Target|label]] link with a display label
[[Category:Name]] join a category; removed from the text flow
[[:Category:Name]] link to the category instead of joining it
#REDIRECT [[Target]] on line 1 redirect, followed one hop only

Links to pages that do not exist yet are still indexed, so creating a page immediately knows who was already pointing at it.

Known limits

  • Page.Revision(id) is a linear scan of the page's history, and so is comment lookup within a thread.
  • Redirects are followed one hop; chains are not resolved.
  • Templates and transclusion are not implemented.
  • There is no full-text search, and there cannot be a cheap one on chain.

Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

Dependency graph:

gno.land/p/moul/x/wiki/v0 dependency graph

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package wiki is a Wikipedia-shaped wiki engine for gno.land: namespaced titles, an append-only revision chain, wikilinks with backlinks, categories, redirects, protection levels, line diffs, and markdown rendering.

It is pure: no realm globals, no chain imports. Every call takes the context it needs (author address, wall clock, block height) from the caller, so the whole engine is unit-testable off-chain. The live demo realm is gno.land/r/moul/x/wiki/v0.

Storage model

A realm write locks a storage deposit proportional to the bytes it adds (100ugnot per byte since gnolang/gno#6171), so a wiki that kept every revision in full would charge its editors for the whole history forever. This engine instead stores a content-addressed spine plus a bounded body window:

  • Every revision keeps its metadata permanently: id, parent, author, time, height, summary, byte size, and the SHA-256 of the body. That is roughly 200 bytes, independent of article size.
  • Only the newest Retention revisions of a page keep their body. Older bodies are evicted (set to ""), which releases their deposit.

An evicted body is not lost: its bytes were an argument of the transaction that wrote it, so they remain in the chain's transaction history, and the retained hash proves which bytes were the real ones. The realm stops paying rent for deep history; the archive lives where archives belong.

Untrusted markdown

Article bodies are attacker-controlled markdown. Render passes every body through gno.land/p/nt/markdown/sanitize/v0 before any wikilink rewriting, never after: sanitize escapes "[" to "\\[", so a rewriter that ran first would hand its own generated links to the escaper. See links.gno.

Constants 10

const DepositPerByte

1const DepositPerByte = 100
source

DepositPerByte is the storage deposit a realm write locks per byte, in ugnot (gnolang/gno#6171). It is used only to show a reader what a page costs; the chain, not this package, does the accounting.

const DiffMaxLines

1const DiffMaxLines = 80
source

DiffMaxLines bounds the changed region a diff will compute exactly.

The cost of an LCS over the changed region is O(n*m), and this runs inside vm/qrender, which has a gas ceiling a reader cannot raise. 80 lines caps the table at 6,400 cells; beyond it, a diff degrades to "this block was replaced" rather than making the page unrenderable. Both revisions are still available in full through the raw view, so nothing is hidden by the degradation.

const MaxCommentLen

1const MaxCommentLen = 2000
source

MaxCommentLen bounds one comment in bytes.

const MaxReplyDepth

1const MaxReplyDepth = 1
source

MaxReplyDepth is 1: a comment replies to a top-level comment, and that is all. Deeper nesting needs recursive rendering with no natural bound, which is exactly the shape a query gas ceiling punishes.

const MaxTitleLen

1const MaxTitleLen = 255
source

MaxTitleLen bounds a title name in bytes, matching MediaWiki's limit.

const NSMain, NSUser, NSCategory, NSTemplate, NSHelp, NSSpecial

1const (
2	NSMain     Namespace = iota // articles
3	NSUser                      // per-address pages
4	NSCategory                  // category descriptions; membership is an index
5	NSTemplate                  // reusable fragments
6	NSHelp                      // documentation about the wiki itself
7	NSSpecial                   // generated listings; never stored
8)
source

There is deliberately no Talk namespace. Discussion is not an article with a prefix: it is an append-only thread attached to a page, which threads properly, moderates per message, and cannot be silently rewritten by whoever edits last. See talk.gno.

const Open, SemiProtected, Locked

1const (
2	Open          Protection = iota // anyone the realm lets through
3	SemiProtected                   // realm-defined trusted editors
4	Locked                          // stewards only
5)
source

Variables 3

var ErrNoSuchPage, ErrPageExists, ErrBodyTooLarge, ErrEmptyBody, ErrNoChange, ErrSummaryTooLong, ErrBadProtection, ErrNoSuchRevision, ErrBodyEvicted, ErrSpecial

 1var (
 2	ErrNoSuchPage     = errors.New("wiki: no such page")
 3	ErrPageExists     = errors.New("wiki: page already exists")
 4	ErrBodyTooLarge   = errors.New("wiki: body exceeds the size limit")
 5	ErrEmptyBody      = errors.New("wiki: empty body")
 6	ErrNoChange       = errors.New("wiki: body is identical to the current revision")
 7	ErrSummaryTooLong = errors.New("wiki: edit summary too long")
 8	ErrBadProtection  = errors.New("wiki: unknown protection level")
 9	ErrNoSuchRevision = errors.New("wiki: no such revision")
10	ErrBodyEvicted    = errors.New("wiki: that revision's body is no longer held on chain")
11	ErrSpecial        = errors.New("wiki: the Special namespace is generated, not stored")
12)
source

Functions 22

func DiffStat

1func DiffStat(lines []DiffLine) (added, removed int)
source

DiffStat counts added and removed lines.

func RenderAllPages

1func RenderAllPages(c Ctx, w *Wiki, ns Namespace, offset, count int) string
source

RenderAllPages renders the page index for a namespace prefix.

func RenderArticle

1func RenderArticle(c Ctx, w *Wiki, p *Page, act Action) string
source

RenderArticle renders a page for reading: the sanitized body with its wikilinks resolved, a header identifying the current revision, and a footer with categories and cost.

func RenderCategories

1func RenderCategories(c Ctx, w *Wiki) string
source

RenderCategories lists every category that has at least one member.

func RenderCategory

1func RenderCategory(c Ctx, w *Wiki, t Title, p *Page, act Action) string
source

RenderCategory renders a category page: its own text, then its members.

func RenderDiff

1func RenderDiff(c Ctx, p *Page, from, to *Revision) string
source

RenderDiff renders the line diff between two revisions of a page.

func RenderHistory

1func RenderHistory(c Ctx, p *Page, offset, count int, act Action) string
source

RenderHistory renders a page's revision list, newest first.

func RenderIndex

1func RenderIndex(c Ctx, w *Wiki, recent int) string
source

RenderIndex renders the wiki's front page: recent changes and a page count.

func RenderMissing

1func RenderMissing(c Ctx, w *Wiki, t Title, act Action) string
source

RenderMissing renders the stub shown for a title with no page: the red-link destination, listing whoever already points at it.

func RenderRaw

1func RenderRaw(c Ctx, p *Page, r *Revision) string
source

RenderRaw renders a revision's source inside a code block, which is what a reader needs before editing and what a verifier needs to re-hash.

func RenderRecent

1func RenderRecent(c Ctx, w *Wiki, n int) string
source

RenderRecent renders the recent-changes feed.

func RenderRevision

1func RenderRevision(c Ctx, p *Page, r *Revision) string
source

RenderRevision renders one stored revision verbatim.

func RenderStats

1func RenderStats(c Ctx, w *Wiki) string
source

RenderStats renders the wiki's size and what it is paying the chain.

func RenderTalk

1func RenderTalk(c Ctx, w *Wiki, p *Page, offset, count int, act Action) string
source

RenderTalk renders a page's discussion: top-level messages oldest first, each with its replies.

func DiffLines

1func DiffLines(older, newer string) (out []DiffLine, exact bool)
source

DiffLines compares two bodies line by line.

exact reports whether the result is a real line diff. When it is false the changed region was larger than DiffMaxLines and the result is the coarse form: every removed line, then every added line.

func ParseProtection

1func ParseProtection(s string) (Protection, error)
source

ParseProtection maps a user-supplied level name onto a Protection.

func MustParseTitle

1func MustParseTitle(raw string) Title
source

MustParseTitle is ParseTitle for titles known at development time.

func ParseTitle

1func ParseTitle(raw string) (Title, error)
source

ParseTitle normalizes a user-supplied title.

The normalization is a deliberate subset of MediaWiki's: underscores become spaces, runs of whitespace collapse, the first letter is upper-cased, and a recognized "Namespace:" prefix (case-insensitive) selects the namespace. "Talk:gno land" and "talk:Gno_land" both parse to Talk:Gno land.

The accepted character set is narrower than MediaWiki's on purpose. A title is rendered inside markdown and inside a URL path, so anything that is markdown-significant ("*", "`", "[", "|", "#", "<"), path-significant ("/", "?", "%", "&") or invisible (bidi and zero-width controls) is rejected rather than escaped. Rejecting keeps Title.String, Title.Slug and the sanitized render byte-identical, which is what TestTitleCharsSurviveSanitize pins.

func New

1func New(retention, maxBody int) *Wiki
source

New returns an empty wiki. A retention or maxBody below 1 falls back to the package default.

Types 16

type Action

func
1type Action func(fn string, args ...string) string
source

Action builds a transaction link for a realm function. The realm supplies it (txlink.Realm("…").Call is the usual value); a nil Action renders a page with no edit controls, which is what an archived or read-only mirror wants.

type Change

struct
1type Change struct {
2	Title Title
3	Rev   *Revision
4}
source

Change is a recent-changes entry: a revision plus the page it landed on.

type Comment

struct
1type Comment struct {
2	ID     uint64
3	Parent uint64 // 0 for a top-level comment
4	Author address
5	Time   time.Time
6	Height int64
7	Body   string
8	Hidden bool // a steward hid it; Body is cleared and its deposit released
9}
source

Comment is one message in a page's discussion.

Discussion is a comment store rather than a "Talk:" article, which is the one place this design departs from MediaWiki on purpose. A talk page is an article, so the last editor can rewrite what someone else said, and moderating one bad message means editing the whole page. An append-only store gives each message its own author, its own timestamp and its own moderation, and nobody can silently rewrite anyone else's words.

type Ctx

struct
1type Ctx struct {
2	Base   string           // realm path prefix, e.g. "/r/moul/x/wiki/v0"
3	Exists func(Title) bool // nil treats every title as existing
4}
source

Ctx carries what rendering needs from the realm: where the realm lives, and which titles exist. Its function fields are read during a single Render call and never stored, so no closure is ever persisted.

Methods on Ctx

func SpecialURL

method on Ctx
1func (c Ctx) SpecialURL(name, query string) string
source

SpecialURL is the render path of a Special: page.

func Sub

method on Ctx
1func (c Ctx) Sub(t Title, route string) string
source

Sub is the render path of a sub-route of a title, e.g. "history".

func URL

method on Ctx
1func (c Ctx) URL(t Title) string
source

URL is the render path of a title under this realm.

type DiffLine

struct
1type DiffLine struct {
2	Op   DiffOp
3	Text string
4}
source

DiffLine is one line of a rendered diff.

type DiffOp

ident
1type DiffOp uint8
source

DiffOp is what happened to one line between two revisions.

type Kind

ident
1type Kind string
source

Kind labels what a revision did, for the recent-changes feed.

type Namespace

ident
1type Namespace uint8
source

Namespace partitions the title space the way MediaWiki does: the same name can exist once per namespace, and each namespace gets its own rendering and its own listing.

Methods on Namespace

func Prefix

method on Namespace
1func (ns Namespace) Prefix() string
source

Prefix returns the avl key prefix that selects a whole namespace.

func String

method on Namespace
1func (ns Namespace) String() string
source

String returns the namespace's display prefix, "" for the main namespace.

type Page

struct
 1type Page struct {
 2	Title      Title
 3	Protection Protection
 4	Created    time.Time
 5	Blanked    bool // a steward blanked it; history is retained
 6
 7	revs *ulist.List // *Revision, oldest first
 8	head *Revision
 9
10	redirect string   // target title, "" when this page is not a redirect
11	links    []string // outgoing wikilink target keys of head
12	cats     []string // category keys head belongs to
13}
source

Page is one title's history plus the indexes derived from its current revision. Revisions live in a ulist: append is O(1) and does not rewrite the existing entries, which matters when a popular page accumulates thousands of edits and every write would otherwise re-serialize the whole slice.

Methods on Page

func Body

method on Page
1func (p *Page) Body() (string, bool)
source

Body returns the current text and whether it is held on chain.

func Contributors

method on Page
1func (p *Page) Contributors() []address
source

Contributors returns the distinct authors of the page, oldest edit first.

func Head

method on Page
1func (p *Page) Head() *Revision
source

Head returns the current revision, or nil for a page with no revisions.

func History

method on Page
1func (p *Page) History(offset, count int) []*Revision
source

History returns up to count revisions, newest first, skipping the newest offset of them.

func NumRevisions

method on Page
1func (p *Page) NumRevisions() int
source

NumRevisions returns how many revisions the page has.

func Redirect

method on Page
1func (p *Page) Redirect() string
source

Redirect returns the title this page redirects to, or "" if it does not.

func Revision

method on Page
1func (p *Page) Revision(id uint64) *Revision
source

Revision returns the revision with the given id, or nil.

type Protection

ident
1type Protection uint8
source

Protection is a page's edit gate. The engine stores it and reports it; it never enforces it, because authority belongs to the realm that owns the wiki, not to a pure library. See Wiki.Edit.

Methods on Protection

func String

method on Protection
1func (p Protection) String() string
source

String returns the protection level's display name.

type Revision

struct
 1type Revision struct {
 2	ID      uint64
 3	Prev    uint64 // 0 for the first revision of a page
 4	Kind    Kind
 5	Author  address
 6	Time    time.Time
 7	Height  int64
 8	Summary string
 9	Hash    string // hex-encoded SHA-256 of the body
10	Size    int    // body length in bytes
11	Minor   bool
12
13	body string
14	kept bool
15}
source

Revision is one entry in a page's append-only history.

The exported fields are the permanent spine: they are never evicted, and they are what makes the history tamper-evident. Hash is the SHA-256 of the body bytes as submitted, so a body recovered from transaction history can be checked against the chain's own record of it.

body is the only field under a retention policy. When a revision falls out of the page's body window it is evicted, which releases the storage deposit those bytes locked.

Methods on Revision

func Body

method on Revision
1func (r *Revision) Body() (string, bool)
source

Body returns the revision's text and whether it is still held on chain. A false second return is not corruption: the body aged out of the retention window and must be recovered from the transaction that wrote it, then checked against Hash.

func Kept

method on Revision
1func (r *Revision) Kept() bool
source

Kept reports whether this revision's body is still on chain.

func ShortHash

method on Revision
1func (r *Revision) ShortHash() string
source

ShortHash is the first 12 hex characters of Hash, for display.

type Stats

struct
1type Stats struct {
2	Pages     int
3	Revisions int
4	Comments  int
5	BytesHeld int // bytes of article text and comments currently on chain
6	Retention int
7}
source

Stats is a snapshot of the wiki's size and of what it is paying for.

type Thread

struct
1type Thread struct {
2	Root    *Comment
3	Replies []*Comment
4}
source

Thread is a top-level comment and its replies.

type Title

struct
1type Title struct {
2	NS   Namespace
3	Name string
4}
source

Title is a normalized (namespace, name) pair. The zero Title is invalid; build one with ParseTitle.

Methods on Title

func Key

method on Title
1func (t Title) Key() string
source

Key is the avl key: a fixed-width namespace code followed by the name, so iteration yields main-namespace articles first, then Talk, and so on, each group in name order.

func Slug

method on Title
1func (t Title) Slug() string
source

Slug is the URL form used in render paths, "Talk:Gno_land". Titles cannot contain "/", so a slug is always exactly one path segment and the segment after it is unambiguously a sub-route.

func String

method on Title
1func (t Title) String() string
source

String is the canonical display form, "Talk:Gno land".

type Wiki

struct
 1type Wiki struct {
 2	pages     *avl.Tree // Title.Key() -> *Page
 3	backlinks *avl.Tree // target Title.Key() -> *avl.Tree (source key -> Title)
 4	cats      *avl.Tree // category Title.Key() -> *avl.Tree (member key -> Title)
 5	talk      *avl.Tree // Title.Key() -> *ulist.List of *Comment (see talk.gno)
 6	recent    *fifo.List
 7
 8	nextRev     uint64
 9	nextComment uint64
10	retention   int
11	maxBody     int
12
13	numPages    int // pages with at least one revision and not blanked
14	numRevs     int
15	numComments int
16	bytesHeld   int // retained body and comment bytes, the realm's visible rent
17}
source

Wiki is the whole encyclopedia: pages, the indexes derived from them, and the recent-changes feed.

Every mutating method takes author, now and height from the caller instead of reading them from the chain, so the engine has no chain imports and the tests drive time and height explicitly.

Methods on Wiki

func Blank

method on Wiki
1func (w *Wiki) Blank(author address, now time.Time, height int64, raw, reason string) (*Revision, error)
source

Blank replaces a page's content with a tombstone revision. It is the deletion a chain can honestly offer: the page stops rendering and stops costing rent as its bodies age out, while the revision spine stays as proof that something was there and who removed it. Use Purge to release the retained bytes immediately.

func Categories

method on Wiki
1func (w *Wiki) Categories() []Title
source

Categories returns every category that has at least one member.

func CategoryMembers

method on Wiki
1func (w *Wiki) CategoryMembers(t Title) []Title
source

CategoryMembers returns the pages that declare [[Category:name]].

func Comment

method on Wiki
1func (w *Wiki) Comment(author address, now time.Time, height int64, raw, body string, replyTo uint64) (*Comment, error)
source

Comment appends a message to a page's discussion. replyTo is 0 for a top-level message, or the id of a top-level message to reply to.

The engine does not decide who may comment: as with Edit, that is the realm's call. It does enforce the shape, because the shape is what bounds the render.

func Comments

method on Wiki
1func (w *Wiki) Comments(t Title, offset, count int) []*Thread
source

Comments returns up to count top-level messages of a page's discussion, oldest first, skipping offset of them, each paired with its replies in the order they were written.

func Edit

method on Wiki
1func (w *Wiki) Edit(author address, now time.Time, height int64, raw, body, summary string, minor bool) (*Revision, error)
source

Edit writes a new revision. It does not check authority: the caller decides who may write, using Page.Protection and whatever roster it keeps. Passing a body byte-identical to the current one is an error, so a no-op edit cannot be used to spam the history or the recent-changes feed.

func Exists

method on Wiki
1func (w *Wiki) Exists(t Title) bool
source

Exists reports whether a title has a page. A blanked page still exists: its history is the point of the wiki.

func HideComment

method on Wiki
1func (w *Wiki) HideComment(raw string, id uint64) (int, error)
source

HideComment clears one message's body and returns the bytes released. The message itself stays in the thread, so a deleted comment reads as "removed" rather than as a gap someone has to reconstruct from block explorers.

func Move

method on Wiki
1func (w *Wiki) Move(author address, now time.Time, height int64, from, to, summary string) error
source

Move renames a page, keeping its history, and leaves a redirect behind at the old title so existing links keep resolving.

func NumComments

method on Wiki
1func (w *Wiki) NumComments(t Title) int
source

NumComments returns how many messages a page's discussion holds, replies and hidden messages included.

func Page

method on Wiki
1func (w *Wiki) Page(raw string) (*Page, error)
source

Page returns the page stored at raw, without following redirects.

func PageByTitle

method on Wiki
1func (w *Wiki) PageByTitle(t Title) (*Page, error)
source

PageByTitle is Page for an already-parsed title.

func Purge

method on Wiki
1func (w *Wiki) Purge(raw string) (int, error)
source

Purge evicts every retained body of a page immediately and returns the number of bytes released. The spine, including each body's hash, is untouched. This is the lever for content that must stop being served from realm state; it cannot and does not remove the transactions that wrote it.

func Recent

method on Wiki
1func (w *Wiki) Recent(n int) []*Change
source

Recent returns up to n changes, newest first.

func Resolve

method on Wiki
1func (w *Wiki) Resolve(raw string) (dest, asked *Page, err error)
source

Resolve follows at most one redirect hop and returns the destination page along with the page that was asked for. MediaWiki also stops at one hop: chains are a vandalism vector and a loop is unrenderable.

func Revert

method on Wiki
1func (w *Wiki) Revert(author address, now time.Time, height int64, raw string, revID uint64, summary string) (*Revision, error)
source

Revert restores the body of an earlier revision as a new revision, the way a wiki undo works: the vandalized revision stays in the history, it is just no longer current. It fails if that revision's body has aged out of the retention window, which is the honest failure mode of a bounded history.

func SetProtection

method on Wiki
1func (w *Wiki) SetProtection(author address, now time.Time, height int64, raw, level string) error
source

SetProtection changes a page's edit gate.

func Stats

method on Wiki
1func (w *Wiki) Stats() Stats
source

Stats returns the current counters.

func Titles

method on Wiki
1func (w *Wiki) Titles(prefix string, offset, count int) []Title
source

Titles returns up to count page titles in key order, skipping offset of them. An empty ns prefix walks every namespace.

Imports 12

Source Files 18