page.gno
3.26 Kb · 122 lines
1package wiki
2
3import (
4 "time"
5
6 "gno.land/p/moul/ulist/v0"
7)
8
9// Protection is a page's edit gate. The engine stores it and reports it; it
10// never enforces it, because authority belongs to the realm that owns the
11// wiki, not to a pure library. See Wiki.Edit.
12type Protection uint8
13
14const (
15 Open Protection = iota // anyone the realm lets through
16 SemiProtected // realm-defined trusted editors
17 Locked // stewards only
18)
19
20// String returns the protection level's display name.
21func (p Protection) String() string {
22 switch p {
23 case SemiProtected:
24 return "semi-protected"
25 case Locked:
26 return "locked"
27 default:
28 return "open"
29 }
30}
31
32// ParseProtection maps a user-supplied level name onto a Protection.
33func ParseProtection(s string) (Protection, error) {
34 switch s {
35 case "open", "":
36 return Open, nil
37 case "semi", "semi-protected":
38 return SemiProtected, nil
39 case "locked":
40 return Locked, nil
41 }
42 return Open, ErrBadProtection
43}
44
45// Page is one title's history plus the indexes derived from its current
46// revision. Revisions live in a ulist: append is O(1) and does not rewrite the
47// existing entries, which matters when a popular page accumulates thousands of
48// edits and every write would otherwise re-serialize the whole slice.
49type Page struct {
50 Title Title
51 Protection Protection
52 Created time.Time
53 Blanked bool // a steward blanked it; history is retained
54
55 revs *ulist.List // *Revision, oldest first
56 head *Revision
57
58 redirect string // target title, "" when this page is not a redirect
59 links []string // outgoing wikilink target keys of head
60 cats []string // category keys head belongs to
61}
62
63// Head returns the current revision, or nil for a page with no revisions.
64func (p *Page) Head() *Revision { return p.head }
65
66// NumRevisions returns how many revisions the page has.
67func (p *Page) NumRevisions() int { return p.revs.Size() }
68
69// Redirect returns the title this page redirects to, or "" if it does not.
70func (p *Page) Redirect() string { return p.redirect }
71
72// Body returns the current text and whether it is held on chain.
73func (p *Page) Body() (string, bool) {
74 if p.head == nil {
75 return "", false
76 }
77 return p.head.Body()
78}
79
80// Revision returns the revision with the given id, or nil.
81func (p *Page) Revision(id uint64) *Revision {
82 var found *Revision
83 p.revs.Iterator(0, p.revs.Size()-1, func(_ int, v any) bool {
84 r := v.(*Revision)
85 if r.ID == id {
86 found = r
87 return true
88 }
89 return false
90 })
91 return found
92}
93
94// History returns up to count revisions, newest first, skipping the newest
95// offset of them.
96func (p *Page) History(offset, count int) []*Revision {
97 out := []*Revision{}
98 if count <= 0 {
99 return out
100 }
101 size := p.revs.Size()
102 // Walk backwards: index size-1 is the newest.
103 for i := size - 1 - offset; i >= 0 && len(out) < count; i-- {
104 out = append(out, p.revs.MustGet(i).(*Revision))
105 }
106 return out
107}
108
109// Contributors returns the distinct authors of the page, oldest edit first.
110func (p *Page) Contributors() []address {
111 seen := map[string]bool{}
112 out := []address{}
113 p.revs.Iterator(0, p.revs.Size()-1, func(_ int, v any) bool {
114 r := v.(*Revision)
115 if !seen[r.Author.String()] {
116 seen[r.Author.String()] = true
117 out = append(out, r.Author)
118 }
119 return false
120 })
121 return out
122}