tinyrelaycommit

Commit d985fba24fa5

browse files at this commit | history

1diff --git a/internal/webui/style.css b/internal/webui/style.css
2index 5f1bb31..5d0c630 100644
3--- a/internal/webui/style.css
4+++ b/internal/webui/style.css
5@@ -230,7 +230,7 @@ merge-request,wiki-proposal,collaboration-proposal{display:block;border:1px soli
6 merge-request nostr-react,merge-request nostr-react form,wiki-proposal nostr-react,wiki-proposal nostr-react form,collaboration-proposal nostr-react,collaboration-proposal nostr-react form{display:inline}merge-request nostr-react output,wiki-proposal nostr-react output,collaboration-proposal nostr-react output{display:inline-block;margin-left:6px;font:12px var(--mono);color:var(--muted)}merge-request nostr-react output[data-error],wiki-proposal nostr-react output[data-error],collaboration-proposal nostr-react output[data-error]{color:var(--alert)}
7 wiki-proposal[data-state=pending],collaboration-proposal[data-state=pending]{border-color:var(--bark)}wiki-proposal[data-state=rejected],collaboration-proposal[data-state=rejected]{border-color:var(--alert)}#wiki-list #rows td:first-child small,#panel li[data-state]{font:12px var(--mono)}#wiki-list #rows td:first-child small{margin-left:6px}#wiki-list #rows tr[data-state=pending] small,#panel li[data-state=pending]{color:var(--bark)}#wiki-list #rows tr[data-state=approved] small,#panel li[data-state=approved]{color:var(--accent)}#wiki-list #rows tr[data-state=rejected] small,#panel li[data-state=rejected]{color:var(--alert)}
8 #events li>collaboration-proposal,diff-view td>collaboration-proposal{margin:6px 0 8px;max-width:none}#collaboration-list #rows tr[data-state] td:nth-child(2)>small:first-of-type{font:12px var(--mono)}#collaboration-list #rows tr[data-state=pending] td:nth-child(2)>small:first-of-type{color:var(--bark)}#collaboration-list #rows tr[data-state=approved] td:nth-child(2)>small:first-of-type{color:var(--accent)}#collaboration-list #rows tr[data-state=rejected] td:nth-child(2)>small:first-of-type{color:var(--alert)}
9-#merge-status{font:12px var(--mono);color:var(--muted)}#wiki-compare h2{margin-top:18px}#wiki-redirects{font:12px var(--mono);color:var(--muted)}
10+#merge-status{font:12px var(--mono);color:var(--muted)}#wiki-compare h2{margin-top:18px}#wiki-redirects,#wiki-revision-note{font:12px var(--mono);color:var(--muted)}#wiki-revision-note{margin:0 0 10px}wiki-proposal>small{display:block;margin-top:4px;font:12px var(--mono);color:var(--muted)}#wiki-page #rows td small{font:12px var(--mono);color:var(--muted)}
11 wiki-compose{display:block;max-width:720px;border-bottom:1px solid var(--line);padding:10px 0}
12 wiki-compose label{display:inline-block;margin:0 10px 6px 0;vertical-align:top;font:13px var(--sans);color:var(--muted)}wiki-compose label input{display:block;margin-top:2px;font:13px var(--mono);min-width:200px}
13 wiki-compose label:has(textarea){display:block}wiki-compose textarea{min-height:14em}wiki-compose button{margin-right:6px}
14diff --git a/internal/webui/wiki.go b/internal/webui/wiki.go
15index eab4f49..d438c68 100644
16--- a/internal/webui/wiki.go
17+++ b/internal/webui/wiki.go
18@@ -13,23 +13,37 @@ import (
19
20 // wikiView is one page as the templates see it. Version is the version
21 // being shown, Number its place counted from the oldest, Others the rest.
22+// Revision and Revisions number the shown version among its author's
23+// revisions; History lists every revision the viewer may see, newest
24+// first. Archived is set when an older revision was opened by id, and
25+// ReadersRevision names the approved revision readers see while the
26+// shown one is a proposal that is not approved.
27 type wikiView struct {
28- D, Title string
29- Version map[string]any
30- Author string
31- Number, Count int
32- Forks, Links int
33- Versions []map[string]any
34- Others []map[string]any
35- Merges []map[string]any
36- OpenMerges []map[string]any
37- OpenCount int
38- RedirectsTo []map[string]any
39- RedirectsFrom []map[string]any
40+ D, Title string
41+ Version map[string]any
42+ Author string
43+ Number, Count int
44+ Revision, Revisions int
45+ Forks, Links int
46+ Versions []map[string]any
47+ Others []map[string]any
48+ History []map[string]any
49+ Archived bool
50+ ReadersRevision int
51+ Merges []map[string]any
52+ OpenMerges []map[string]any
53+ OpenCount int
54+ RedirectsTo []map[string]any
55+ RedirectsFrom []map[string]any
56 // CanApprove is set when the viewer may accept or reject proposals.
57 CanApprove bool
58 }
59
60+func wikiInt(value any) int {
61+ number, _ := value.(float64)
62+ return int(number)
63+}
64+
65 // wikiProposalView is how a version's proposal state reads for one viewer.
66 // Shown is set for the people who may see the state: the owner and
67 // moderators, and the version's author. Decide is set when the viewer may
68@@ -77,14 +91,25 @@ func wikiPageView(result any) wikiView {
69 view.Merges = wikiMaps(page["merges"])
70 view.RedirectsTo = wikiMaps(page["redirects_to"])
71 view.RedirectsFrom = wikiMaps(page["redirects_from"])
72+ view.History = wikiMaps(page["history"])
73 view.CanApprove, _ = page["can_approve"].(bool)
74 view.Count = len(view.Versions)
75 if current := valueMap(page["version"]); current != nil {
76 view.Version = current
77 view.Author = plainString(current["author"])
78+ view.Revision, view.Revisions = wikiInt(current["revision"]), wikiInt(current["revisions"])
79+ view.Archived = plainString(current["superseded_by"]) != "" && plainString(page["preferred_by"]) == "version"
80 if links, ok := current["links"].([]any); ok {
81 view.Links = len(links)
82 }
83+ if current["proposal"] == true && plainString(current["approval"]) != "approved" {
84+ for _, h := range view.History {
85+ if plainString(h["author"]) == view.Author && plainString(h["approval"]) == "approved" {
86+ view.ReadersRevision = wikiInt(h["revision"])
87+ break
88+ }
89+ }
90+ }
91 }
92 if view.Title == "" {
93 view.Title = view.D
94diff --git a/internal/webui/wiki.html b/internal/webui/wiki.html
95index ac97c2e..d567c17 100644
96--- a/internal/webui/wiki.html
97+++ b/internal/webui/wiki.html
98@@ -23,13 +23,14 @@
99 <h1>{{$w.Title}}</h1>
100 {{template "alert" .Notice}}{{if not $w.Version}}<p>This name has no article of its own.</p>
101 {{template "wikiRedirects" $w}}{{if .Actor}}{{template "wikiEditor" .}}{{end}}
102-{{else}}{{$s := wikiProposal $w.Version $.Actor $w.CanApprove}}{{if $s.Shown}}<wiki-proposal data-state="{{$s.State}}">Proposed by {{template "nostrName" $w.Author}} | {{$s.State}}{{if $s.By}} {{when $s.At}} by {{template "nostrName" $s.By}}{{end}}{{if $s.Decide}}<nostr-react event="{{str (index $w.Version "id")}}" pubkey="{{$w.Author}}" kind="30818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>{{end}}</wiki-proposal>
103+{{else}}{{$s := wikiProposal $w.Version $.Actor $w.CanApprove}}{{if $s.Shown}}<wiki-proposal data-state="{{$s.State}}">Proposed by {{template "nostrName" $w.Author}} | {{$s.State}}{{if $s.By}} {{when $s.At}} by {{template "nostrName" $s.By}}{{end}}{{if $s.Decide}}<nostr-react event="{{str (index $w.Version "id")}}" pubkey="{{$w.Author}}" kind="30818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>{{end}}{{if $w.ReadersRevision}}<small>showing {{$s.State}} revision {{$w.Revision}}; readers see approved revision {{$w.ReadersRevision}}</small>{{end}}</wiki-proposal>
104 {{end}}{{range $m := $w.OpenMerges}}<merge-request>{{template "nostrName" (str (index $m "author"))}} proposes a new version of this page | <a href="{{wikiURL $w.D "merge" (str (index $m "id"))}}">compare</a>{{if eq $.Actor (str (index $m "destination"))}}<nostr-react event="{{str (index $m "id")}}" pubkey="{{str (index $m "author")}}" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>{{end}}<a href="/e/{{str (index $m "id")}}">Reply</a></merge-request>
105 {{end}}{{if .Query.Get "merge"}}{{template "wikiCompare" .}}
106 {{else if .Query.Get "edit"}}{{template "wikiEditor" .}}
107 {{else if .Query.Get "history"}}{{template "wikiHistory" .}}
108-{{else}}<wiki-article><header>wiki | {{$w.D}} | by {{template "nostrName" $w.Author}} | version {{$w.Number}} of {{$w.Count}} | {{$w.Forks}} fork{{if ne $w.Forks 1}}s{{end}}</header>
109-{{with asMap (index $w.Version "fork")}}<p><small>forked from <a href="{{wikiURL $w.D "version" (str (index . "e"))}}">{{shortID (index . "e")}}</a></small></p>
110+{{else}}<wiki-article><header>wiki | {{$w.D}} | revision {{$w.Revision}} by {{template "nostrName" $w.Author}} | version {{$w.Number}} of {{$w.Count}} | {{$w.Forks}} fork{{if ne $w.Forks 1}}s{{end}}</header>
111+{{if $w.Archived}}<p id="wiki-revision-note">revision {{$w.Revision}} of {{$w.Revisions}} by this author; a newer revision exists | <a href="{{wikiURL $w.D "author" $w.Author}}">show the newest</a></p>
112+{{end}}{{with asMap (index $w.Version "fork")}}<p><small>forked from <a href="{{wikiURL $w.D "version" (str (index . "e"))}}">{{shortID (index . "e")}}</a></small></p>
113 {{end}}{{with asMap (index $w.Version "defer")}}<p><small>the author defers to <a href="{{wikiURL $w.D "version" (str (index . "e"))}}">{{shortID (index . "e")}}</a></small></p>
114 {{end}}{{wikiHTML (index $w.Version "content")}}</wiki-article>
115 {{template "wikiRedirects" $w}}{{end}}{{end}}</section>
116@@ -62,18 +63,18 @@
117 {{else}}<p>The destination author has no version of this page.</p>
118 {{end}}<p><a href="{{wikiURL $w.D}}">back to the page</a></p></div>{{end}}{{end}}
119
120-{{/* wikiHistory lists every version of a page, newest first. */}}
121+{{/* wikiHistory lists every revision of a page across authors, newest first. */}}
122 {{define "wikiHistory"}}{{$w := wikiView .Event}}<table id="rows">
123-<thead><tr><th>author</th><th>version</th><th>notes</th><th>published</th></tr></thead>
124+<thead><tr><th>author</th><th>revision</th><th>version</th><th>notes</th><th>published</th></tr></thead>
125 <tbody>
126-{{range $w.Versions}}{{$s := wikiProposal . $.Actor $w.CanApprove}}<tr{{if $s.Shown}} data-state="{{$s.State}}"{{end}}><td>{{template "nostrName" (str (index . "author"))}}</td><td><a href="{{wikiURL $w.D "version" (str (index . "id"))}}">{{shortID (index . "id")}}</a></td><td>{{with asMap (index . "fork")}}fork of {{shortID (index . "e")}}{{end}}{{with asMap (index . "defer")}} defers to {{shortID (index . "e")}}{{end}}{{with index . "likes"}} | {{str .}} likes{{end}}{{if $s.Shown}} | proposed {{$s.State}}{{end}}</td><td>{{when (index . "created_at")}}</td></tr>
127+{{range $w.History}}{{$s := wikiProposal . $.Actor $w.CanApprove}}<tr{{if $s.Shown}} data-state="{{$s.State}}"{{end}}><td>{{template "nostrName" (str (index . "author"))}}</td><td>{{str (index . "revision")}} <small>{{if index . "superseded_by"}}replaced{{else}}current{{end}}</small></td><td><a href="{{wikiURL $w.D "version" (str (index . "id"))}}">{{shortID (index . "id")}}</a></td><td>{{with asMap (index . "fork")}}fork of {{shortID (index . "e")}}{{end}}{{with asMap (index . "defer")}} defers to {{shortID (index . "e")}}{{end}}{{with index . "likes"}} | {{str .}} likes{{end}}{{if $s.Shown}} | proposed {{$s.State}}{{end}}</td><td>{{when (index . "created_at")}}</td></tr>
128 {{end}}</tbody>
129 </table>
130 <p><a href="{{wikiURL $w.D}}">back to the page</a></p>{{end}}
131
132 {{define "panel-wiki"}}{{if eq .Tab "wiki"}}<section><h4>Wiki</h4><table><tr><th>protocol</th><td>NIP-54</td></tr><tr><th>markup</th><td>Djot</td></tr><tr><th>reads</th><td>{{.Policy.Reads}}</td></tr></table></section>
133 <section><h4>Actions</h4><ul>{{if .Actor}}<li><a href="/wiki?new=1">new page</a></li>{{else}}<li><a href="/signin">sign in to write</a></li>{{end}}</ul></section>
134-{{else}}{{$w := wikiView .Event}}<section><h4>Page</h4><table><tr><th>name</th><td><code>{{or $w.D (.Query.Get "d")}}</code></td></tr><tr><th>versions</th><td>{{$w.Count}}</td></tr><tr><th>merge requests</th><td>{{$w.OpenCount}} open</td></tr><tr><th>links</th><td>{{$w.Links}}</td></tr></table></section>
135+{{else}}{{$w := wikiView .Event}}<section><h4>Page</h4><table><tr><th>name</th><td><code>{{or $w.D (.Query.Get "d")}}</code></td></tr><tr><th>versions</th><td>{{$w.Count}}</td></tr>{{if $w.History}}<tr><th>revisions</th><td>{{len $w.History}}</td></tr>{{end}}<tr><th>merge requests</th><td>{{$w.OpenCount}} open</td></tr><tr><th>links</th><td>{{$w.Links}}</td></tr></table></section>
136 {{if $w.Others}}<section><h4>Other versions</h4><ul>{{range $w.Others}}{{$s := wikiProposal . $.Actor $w.CanApprove}}<li{{if $s.Shown}} data-state="{{$s.State}}"{{end}}><a href="{{wikiURL $w.D "version" (str (index . "id"))}}">{{shortID (index . "author")}}</a>{{with asMap (index . "fork")}} | fork{{end}}{{if $s.Shown}} | proposed {{$s.State}}{{end}} <small>{{short (index . "created_at")}}</small></li>{{end}}</ul></section>
137 {{end}}{{if $w.RedirectsTo}}<section><h4>Also known as</h4><ul>{{range $w.RedirectsTo}}<li><code>{{str (index . "d")}}</code></li>{{end}}</ul></section>
138 {{end}}<section><h4>Actions</h4><ul>{{if $w.Version}}{{if eq .Actor $w.Author}}<li><a href="{{wikiURL $w.D "edit" "1"}}">edit</a></li>{{else if .Actor}}<li><a href="{{wikiURL $w.D "edit" "1"}}">fork</a></li>{{end}}<li><a href="{{wikiURL $w.D "history" "1"}}">history</a></li>{{end}}{{if .Actor}}<li><a href="/wiki?new=1">new page</a></li>{{else}}<li><a href="/signin">sign in to write</a></li>{{end}}</ul></section>{{end}}{{end}}
139diff --git a/internal/webui/wiki_test.go b/internal/webui/wiki_test.go
140index fa3eb3b..630305b 100644
141--- a/internal/webui/wiki_test.go
142+++ b/internal/webui/wiki_test.go
143@@ -18,6 +18,10 @@ const (
144 wikiMerge = "3333333333333333333333333333333333333333333333333333333333333333"
145 wikiOwnerV = "4444444444444444444444444444444444444444444444444444444444444444"
146 wikiOtherV = "5555555555555555555555555555555555555555555555555555555555555555"
147+ // wikiOwnerOld and wikiOtherOld are archived revisions that the
148+ // current versions replaced.
149+ wikiOwnerOld = "7777777777777777777777777777777777777777777777777777777777777777"
150+ wikiOtherOld = "8888888888888888888888888888888888888888888888888888888888888888"
151 )
152
153 // wikiBackend answers the three wiki queries with one page that has two
154@@ -29,11 +33,15 @@ type wikiBackend struct {
155 private bool
156 // proposal makes the second author's version a proposal in this state
157 // (pending, approved or rejected), decided by the owner when it is not
158- // pending. approver marks the caller as allowed to decide, and shown
159- // makes the proposal the page's shown version.
160+ // pending, with an approved earlier revision behind it in the history.
161+ // approver marks the caller as allowed to decide, and shown makes the
162+ // proposal the page's shown version.
163 proposal string
164 approver bool
165 shown bool
166+ // archived gives the owner's version an earlier revision, which a
167+ // request naming it by version opens.
168+ archived bool
169 }
170
171 func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawMessage, actor string) (any, error) {
172@@ -49,20 +57,31 @@ func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawM
173 if b.private && actor == "" {
174 return nil, errors.New("auth-required: sign in to read this relay")
175 }
176- owner := map[string]any{"id": wikiOwnerV, "author": wikiOwner, "created_at": float64(1788876000), "d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "summary": "What changed", "coordinate": "30818:" + wikiOwner + ":release-notes-1-4", "likes": float64(0),
177+ owner := map[string]any{"id": wikiOwnerV, "author": wikiOwner, "created_at": float64(1788876000), "d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "summary": "What changed", "coordinate": "30818:" + wikiOwner + ":release-notes-1-4", "likes": float64(0), "revision": float64(1), "revisions": float64(1),
178 "content": "# Highlights\n\nPrivate tenants and _encrypted_ uploads. See [[Files and Private Repositories]].\n\n<script>alert(1)</script>", "links": []any{"files-and-private-repositories"}}
179- other := map[string]any{"id": wikiOtherV, "author": wikiOther, "created_at": float64(1788880000), "d": "release-notes-1-4", "title": "Release notes 1.4", "coordinate": "30818:" + wikiOther + ":release-notes-1-4", "likes": float64(0), "fork": map[string]any{"a": owner["coordinate"], "e": wikiOwnerV}, "content": "# Highlights\n\nA proposed rewrite."}
180+ other := map[string]any{"id": wikiOtherV, "author": wikiOther, "created_at": float64(1788880000), "d": "release-notes-1-4", "title": "Release notes 1.4", "coordinate": "30818:" + wikiOther + ":release-notes-1-4", "likes": float64(0), "revision": float64(1), "revisions": float64(1), "fork": map[string]any{"a": owner["coordinate"], "e": wikiOwnerV}, "content": "# Highlights\n\nA proposed rewrite."}
181 merge := map[string]any{"id": wikiMerge, "author": wikiOther, "created_at": float64(1788881000), "content": "Please take my rewrite", "target": owner["coordinate"], "target_d": "release-notes-1-4", "destination": wikiOwner, "source": wikiOtherV, "status": "open"}
182- shown := owner
183+ shown, preferredBy := owner, "owner"
184+ history := []any{other, owner}
185 if b.proposal != "" {
186- other["proposal"], other["approval"] = true, b.proposal
187+ other["proposal"], other["approval"], other["revision"], other["revisions"] = true, b.proposal, float64(2), float64(2)
188 if b.proposal != "pending" {
189 other["approval_event"], other["approval_at"], other["approval_by"] = wikiMerge, float64(1788882000), wikiOwner
190 }
191+ old := map[string]any{"id": wikiOtherOld, "author": wikiOther, "created_at": float64(1788878000), "d": "release-notes-1-4", "title": "Release notes 1.4", "coordinate": other["coordinate"], "likes": float64(0), "revision": float64(1), "revisions": float64(2), "superseded_by": wikiOtherV, "proposal": true, "approval": "approved", "approval_event": wikiMerge, "approval_at": float64(1788879000), "approval_by": wikiOwner}
192+ history = []any{other, old, owner}
193 if b.shown {
194 shown = other
195 }
196 }
197+ if b.archived {
198+ owner["revision"], owner["revisions"] = float64(2), float64(2)
199+ old := map[string]any{"id": wikiOwnerOld, "author": wikiOwner, "created_at": float64(1788870000), "d": "release-notes-1-4", "title": "Release notes 1.3", "coordinate": owner["coordinate"], "likes": float64(1), "revision": float64(1), "revisions": float64(2), "superseded_by": wikiOwnerV, "content": "# Highlights\n\nThe first draft."}
200+ history = append(history, old)
201+ if object["version"] == wikiOwnerOld {
202+ shown, preferredBy = old, "version"
203+ }
204+ }
205 switch method {
206 case "browsewiki":
207 return map[string]any{"items": []any{map[string]any{"d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "version": shown, "versions": float64(2), "open_merges": float64(1)}}, "next_cursor": "release-notes-1-4", "can_approve": b.approver}, nil
208@@ -70,7 +89,7 @@ func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawM
209 if object["d"] != "release-notes-1-4" {
210 return nil, errors.New("not found: wiki page")
211 }
212- return map[string]any{"d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "version": shown, "preferred_by": "owner", "versions": []any{other, owner}, "merges": []any{merge}, "redirects_to": []any{map[string]any{"d": "changelog-1-4", "target_d": "release-notes-1-4"}}, "redirects_from": []any{}, "can_approve": b.approver}, nil
213+ return map[string]any{"d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "version": shown, "preferred_by": preferredBy, "versions": []any{other, owner}, "history": history, "merges": []any{merge}, "redirects_to": []any{map[string]any{"d": "changelog-1-4", "target_d": "release-notes-1-4"}}, "redirects_from": []any{}, "can_approve": b.approver}, nil
214 case "browsewikimerge":
215 return map[string]any{"merge": merge, "proposed": other, "target": owner}, nil
216 }
217@@ -151,7 +170,7 @@ func TestWikiPageRendersArticleMergeBarAndPanel(t *testing.T) {
218 `<merge-request><nostr-name pubkey="`+wikiOther+`"`, `proposes a new version of this page`, `href="/wiki/release-notes-1-4?merge=`+wikiMerge+`">compare</a>`,
219 `<nostr-react event="`+wikiMerge+`" pubkey="`+wikiOther+`" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>`,
220 `<a href="/e/`+wikiMerge+`">Reply</a>`,
221- `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`,
222+ `<wiki-article><header>wiki | release-notes-1-4 | revision 1 by <nostr-name pubkey="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`,
223 `<h1>Highlights</h1>`, `<em>encrypted</em>`, `<a href="/wiki/files-and-private-repositories">Files and Private Repositories</a>`, `&lt;script&gt;alert(1)&lt;/script&gt;`,
224 `<h4>Page</h4>`, `<th>versions</th><td>2</td>`, `<th>merge requests</th><td>1 open</td>`, `<th>links</th><td>1</td>`,
225 `<h4>Other versions</h4>`, `href="/wiki/release-notes-1-4?version=`+wikiOtherV+`">222222222222</a> | fork`,
226@@ -186,7 +205,44 @@ func TestWikiPageCompareEditorAndHistory(t *testing.T) {
227
228 path = "/wiki/release-notes-1-4?history=1"
229 body = wikiGet(t, wikiApp(t, b, wikiOwner), path)
230- wantAll(t, path, body, `<th>author</th><th>version</th>`, `fork of 444444444444`, `href="/wiki/release-notes-1-4?version=`+wikiOwnerV+`">444444444444</a>`)
231+ wantAll(t, path, body, `<th>author</th><th>revision</th><th>version</th>`, `<td>1 <small>current</small></td>`, `fork of 444444444444`, `href="/wiki/release-notes-1-4?version=`+wikiOwnerV+`">444444444444</a>`)
232+ wantAll(t, path, body, `<th>revisions</th><td>2</td>`)
233+ wantNone(t, path, body, "<small>replaced</small>")
234+}
235+
236+func TestWikiHistoryListsRevisionsWithStates(t *testing.T) {
237+ b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", approver: true, shown: true, archived: true}
238+ path := "/wiki/release-notes-1-4?history=1"
239+ body := wikiGet(t, wikiApp(t, b, wikiOwner), path)
240+ wantAll(t, path, body,
241+ `<tr data-state="pending"><td><nostr-name pubkey="`+wikiOther+`"`, `<td>2 <small>current</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOtherV+`">555555555555</a></td><td>fork of 444444444444 | proposed pending</td>`,
242+ `<tr data-state="approved"><td><nostr-name pubkey="`+wikiOther+`"`, `<td>1 <small>replaced</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOtherOld+`">888888888888</a></td><td> | proposed approved</td>`,
243+ `<td>2 <small>current</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOwnerV+`">444444444444</a></td>`,
244+ `<td>1 <small>replaced</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOwnerOld+`">777777777777</a></td><td> | 1 likes</td>`,
245+ `<th>revisions</th><td>4</td>`)
246+ // A member sees the rows without their proposal states.
247+ b.approver = false
248+ body = wikiGet(t, wikiApp(t, b, wikiMember), path)
249+ wantAll(t, path, body, `<td>1 <small>replaced</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOtherOld+`">888888888888</a></td><td></td>`)
250+ wantNone(t, path, body, "<tr data-state=", "proposed ")
251+}
252+
253+func TestWikiArchivedRevisionShowsANote(t *testing.T) {
254+ b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, archived: true}
255+ path := "/wiki/release-notes-1-4?version=" + wikiOwnerOld
256+ body := wikiGet(t, wikiApp(t, b, wikiOther), path)
257+ if b.params["browsewikipage"]["version"] != wikiOwnerOld {
258+ t.Fatalf("version lookup params=%v", b.params["browsewikipage"])
259+ }
260+ wantAll(t, path, body,
261+ `<wiki-article><header>wiki | release-notes-1-4 | revision 1 by <nostr-name pubkey="`+wikiOwner+`"`,
262+ `<p id="wiki-revision-note">revision 1 of 2 by this author; a newer revision exists | <a href="/wiki/release-notes-1-4?author=`+wikiOwner+`">show the newest</a></p>`,
263+ `The first draft.`)
264+ // The author's current revision carries no note.
265+ path = "/wiki/release-notes-1-4"
266+ body = wikiGet(t, wikiApp(t, b, wikiOther), path)
267+ wantAll(t, path, body, `revision 2 by <nostr-name pubkey="`+wikiOwner+`"`, `<em>encrypted</em>`)
268+ wantNone(t, path, body, `id="wiki-revision-note"`, "The first draft.")
269 }
270
271 func TestWikiMissingPageOffersTheEditorToMembers(t *testing.T) {
272@@ -221,15 +277,22 @@ func TestWikiProposalBarOffersTheDecisionToApprovers(t *testing.T) {
273 path := "/wiki/release-notes-1-4"
274 body := wikiGet(t, wikiApp(t, b, wikiOwner), path)
275 wantAll(t, path, body,
276- `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending<nostr-react event="`+wikiOtherV+`" pubkey="`+wikiOther+`" kind="30818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react></wiki-proposal>`,
277- `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOther+`"`, `A proposed rewrite.`)
278+ `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending<nostr-react event="`+wikiOtherV+`" pubkey="`+wikiOther+`" kind="30818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react><small>showing pending revision 2; readers see approved revision 1</small></wiki-proposal>`,
279+ `<wiki-article><header>wiki | release-notes-1-4 | revision 2 by <nostr-name pubkey="`+wikiOther+`"`, `A proposed rewrite.`)
280 // A decided proposal names the decision, its time and the deciding key,
281- // and offers no buttons.
282+ // and offers no buttons. Readers still see the earlier approved
283+ // revision while the edit is rejected, and the edit itself once it is
284+ // approved.
285 for _, state := range []string{"approved", "rejected"} {
286 b.proposal = state
287 body = wikiGet(t, wikiApp(t, b, wikiOwner), path)
288 wantAll(t, path, body, `<wiki-proposal data-state="`+state+`">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| `+state+` 2026-09-08 15:40 UTC by <nostr-name pubkey="`+wikiOwner+`"`)
289 wantNone(t, path, body, "<nostr-react", ">Accept<")
290+ if state == "rejected" {
291+ wantAll(t, path, body, `<small>showing rejected revision 2; readers see approved revision 1</small></wiki-proposal>`)
292+ } else {
293+ wantNone(t, path, body, "readers see approved revision")
294+ }
295 }
296 // A pending proposal that is not the shown version is marked in the
297 // panel and the history, with no bar above the article.
298@@ -245,7 +308,7 @@ func TestWikiProposalAuthorSeesTheStateWithoutButtons(t *testing.T) {
299 b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", shown: true}
300 path := "/wiki/release-notes-1-4"
301 body := wikiGet(t, wikiApp(t, b, wikiOther), path)
302- wantAll(t, path, body, `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending</wiki-proposal>`)
303+ wantAll(t, path, body, `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending<small>showing pending revision 2; readers see approved revision 1</small></wiki-proposal>`)
304 wantNone(t, path, body, "<nostr-react", ">Accept<", ">Reject<")
305 body = wikiGet(t, wikiApp(t, b, wikiOther), "/wiki")
306 wantAll(t, "/wiki", body, `<tr data-state="pending"><td><a href="/wiki/release-notes-1-4">Release notes 1.4 &lt;b&gt;</a> <small>proposed | pending</small></td>`)

About

ownerc01503e8035f
visibilitypublic
headd985fba24fa5

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs