tinyrelayfile
tinyrelay/tinyclient/wiki.go/

tinyclient/wiki.go raw
1package tinyclient
2
3// Wiki pages: the view model derived from the browsewikipage result so the
4// templates read plain fields, and the URL helpers for page names.
5
6import (
7 "html/template"
8 "net/url"
9 "strings"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/wiki"
12)
13
14// wikiView is one page as the templates see it. Version is the version
15// being shown, Number its place counted from the oldest, Others the rest.
16// Revision and Revisions number the shown version among its author's
17// revisions; History lists every revision the viewer may see, newest
18// first. Archived is set when an older revision was opened by id, and
19// ReadersRevision names the approved revision readers see while the
20// shown one is a proposal that is not approved.
21type wikiView struct {
22 D, Title string
23 Version map[string]any
24 Author string
25 Number, Count int
26 Revision, Revisions int
27 Forks, Links int
28 Versions []map[string]any
29 Others []map[string]any
30 History []map[string]any
31 Archived bool
32 ReadersRevision int
33 Merges []map[string]any
34 OpenMerges []map[string]any
35 OpenCount int
36 RedirectsTo []map[string]any
37 RedirectsFrom []map[string]any
38 // CanApprove is set when the viewer may accept or reject proposals.
39 CanApprove bool
40}
41
42func wikiInt(value any) int {
43 number, _ := value.(float64)
44 return int(number)
45}
46
47// wikiProposalView is how a version's proposal state reads for one viewer.
48// Shown is set for the people who may see the state: the owner and
49// moderators, and the version's author. Decide is set when the viewer may
50// accept or reject it, which is only while it is pending.
51type wikiProposalView struct {
52 Shown, Decide bool
53 State string
54 At any
55 By string
56}
57
58// wikiProposal reads a version's proposal state for the viewer. canApprove
59// is the browse result's can_approve flag.
60func wikiProposal(version map[string]any, actor string, canApprove any) wikiProposalView {
61 if version == nil || version["proposal"] != true {
62 return wikiProposalView{}
63 }
64 approver, _ := canApprove.(bool)
65 if !approver && (actor == "" || actor != plainString(version["author"])) {
66 return wikiProposalView{}
67 }
68 view := wikiProposalView{Shown: true, State: plainString(version["approval"]), At: version["approval_at"], By: plainString(version["approval_by"])}
69 view.Decide = approver && view.State == "pending"
70 return view
71}
72
73func wikiMaps(value any) []map[string]any {
74 items, _ := value.([]any)
75 maps := make([]map[string]any, 0, len(items))
76 for _, item := range items {
77 if m := valueMap(item); m != nil {
78 maps = append(maps, m)
79 }
80 }
81 return maps
82}
83
84// wikiPageView reads the browsewikipage result. Open merge requests aimed at
85// the shown version's author come first in OpenMerges; OpenCount counts every
86// open request on the page.
87func wikiPageView(result any) wikiView {
88 page := valueMap(result)
89 view := wikiView{D: plainString(page["d"]), Title: plainString(page["title"])}
90 view.Versions = wikiMaps(page["versions"])
91 view.Merges = wikiMaps(page["merges"])
92 view.RedirectsTo = wikiMaps(page["redirects_to"])
93 view.RedirectsFrom = wikiMaps(page["redirects_from"])
94 view.History = wikiMaps(page["history"])
95 view.CanApprove, _ = page["can_approve"].(bool)
96 view.Count = len(view.Versions)
97 if current := valueMap(page["version"]); current != nil {
98 view.Version = current
99 view.Author = plainString(current["author"])
100 view.Revision, view.Revisions = wikiInt(current["revision"]), wikiInt(current["revisions"])
101 view.Archived = plainString(current["superseded_by"]) != "" && plainString(page["preferred_by"]) == "version"
102 if links, ok := current["links"].([]any); ok {
103 view.Links = len(links)
104 }
105 if current["proposal"] == true && plainString(current["approval"]) != "approved" {
106 for _, h := range view.History {
107 if plainString(h["author"]) == view.Author && plainString(h["approval"]) == "approved" {
108 view.ReadersRevision = wikiInt(h["revision"])
109 break
110 }
111 }
112 }
113 }
114 if view.Title == "" {
115 view.Title = view.D
116 }
117 id, coordinate := plainString(view.Version["id"]), plainString(view.Version["coordinate"])
118 for i, v := range view.Versions {
119 if plainString(v["id"]) == id {
120 view.Number = view.Count - i
121 continue
122 }
123 view.Others = append(view.Others, v)
124 fork := valueMap(v["fork"])
125 if fork != nil && (plainString(fork["e"]) == id || (coordinate != "" && plainString(fork["a"]) == coordinate)) {
126 view.Forks++
127 }
128 }
129 for _, m := range view.Merges {
130 if plainString(m["status"]) != "open" {
131 continue
132 }
133 view.OpenCount++
134 if plainString(m["destination"]) == view.Author {
135 view.OpenMerges = append(view.OpenMerges, m)
136 }
137 }
138 return view
139}
140
141// wikiURL builds the page path for a name, with optional query pairs.
142func wikiURL(d string, pairs ...string) string {
143 path := "/wiki/" + url.PathEscape(d)
144 values := url.Values{}
145 for i := 0; i+1 < len(pairs); i += 2 {
146 if pairs[i+1] != "" {
147 values.Set(pairs[i], pairs[i+1])
148 }
149 }
150 if len(values) == 0 {
151 return path
152 }
153 return path + "?" + values.Encode()
154}
155
156func (a *App) wikiHTML(content any) template.HTML {
157 return wiki.RenderHTMLWith(plainString(content), wiki.Options{Block: a.blocks()})
158}
159
160// wikiPageName is the name in a /wiki/<d> path, normalized.
161func wikiPageName(path string) string {
162 name, err := url.PathUnescape(strings.TrimPrefix(path, "/wiki/"))
163 if err != nil {
164 name = strings.TrimPrefix(path, "/wiki/")
165 }
166 return wiki.Normalize(name)
167}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs