- commit
- d985fba24fa50271a3e7ca42149298887194b397
- parent
- 602d2af64f0d23cd7a22532bc624a129486a5ad3
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 2 | +- |
| M | internal/ | 49 | ++++++++++++---- |
| M | internal/ | 15 | +++-- |
| M | internal/ | 89 | ++++++++++++++++++++++++++---- |
@@ -230,7 +230,7 @@ merge-request,wiki-proposal,collaboration-proposal{display:block;border:1px soli
230230 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)}231231 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)}232232 #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)}233-#merge-status{font:12px var(--mono);color:var(--muted)}#wiki-compare h2{margin-top:18px}#wiki-redirects{font:12px var(--mono);color:var(--muted)}233+#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)}234234 wiki-compose{display:block;max-width:720px;border-bottom:1px solid var(--line);padding:10px 0}235235 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}236236 wiki-compose label:has(textarea){display:block}wiki-compose textarea{min-height:14em}wiki-compose button{margin-right:6px}@@ -13,23 +13,37 @@ import (
1313 1414 // wikiView is one page as the templates see it. Version is the version1515 // being shown, Number its place counted from the oldest, Others the rest.16+// Revision and Revisions number the shown version among its author's17+// revisions; History lists every revision the viewer may see, newest18+// first. Archived is set when an older revision was opened by id, and19+// ReadersRevision names the approved revision readers see while the20+// shown one is a proposal that is not approved.1621 type wikiView struct {17- D, Title string18- Version map[string]any19- Author string20- Number, Count int21- Forks, Links int22- Versions []map[string]any23- Others []map[string]any24- Merges []map[string]any25- OpenMerges []map[string]any26- OpenCount int27- RedirectsTo []map[string]any28- RedirectsFrom []map[string]any22+ D, Title string23+ Version map[string]any24+ Author string25+ Number, Count int26+ Revision, Revisions int27+ Forks, Links int28+ Versions []map[string]any29+ Others []map[string]any30+ History []map[string]any31+ Archived bool32+ ReadersRevision int33+ Merges []map[string]any34+ OpenMerges []map[string]any35+ OpenCount int36+ RedirectsTo []map[string]any37+ RedirectsFrom []map[string]any2938 // CanApprove is set when the viewer may accept or reject proposals.3039 CanApprove bool3140 }3241 42+func wikiInt(value any) int {43+ number, _ := value.(float64)44+ return int(number)45+}46+3347 // wikiProposalView is how a version's proposal state reads for one viewer.3448 // Shown is set for the people who may see the state: the owner and3549 // moderators, and the version's author. Decide is set when the viewer may@@ -77,14 +91,25 @@ func wikiPageView(result any) wikiView {
7791 view.Merges = wikiMaps(page["merges"])7892 view.RedirectsTo = wikiMaps(page["redirects_to"])7993 view.RedirectsFrom = wikiMaps(page["redirects_from"])94+ view.History = wikiMaps(page["history"])8095 view.CanApprove, _ = page["can_approve"].(bool)8196 view.Count = len(view.Versions)8297 if current := valueMap(page["version"]); current != nil {8398 view.Version = current8499 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"85102 if links, ok := current["links"].([]any); ok {86103 view.Links = len(links)87104 }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+ break110+ }111+ }112+ }88113 }89114 if view.Title == "" {90115 view.Title = view.D@@ -23,13 +23,14 @@
2323 <h1>{{$w.Title}}</h1>2424 {{template "alert" .Notice}}{{if not $w.Version}}<p>This name has no article of its own.</p>2525 {{template "wikiRedirects" $w}}{{if .Actor}}{{template "wikiEditor" .}}{{end}}26-{{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>26+{{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>2727 {{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>2828 {{end}}{{if .Query.Get "merge"}}{{template "wikiCompare" .}}2929 {{else if .Query.Get "edit"}}{{template "wikiEditor" .}}3030 {{else if .Query.Get "history"}}{{template "wikiHistory" .}}31-{{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>32-{{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>31+{{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>32+{{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>33+{{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>3334 {{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>3435 {{end}}{{wikiHTML (index $w.Version "content")}}</wiki-article>3536 {{template "wikiRedirects" $w}}{{end}}{{end}}</section>@@ -62,18 +63,18 @@
6263 {{else}}<p>The destination author has no version of this page.</p>6364 {{end}}<p><a href="{{wikiURL $w.D}}">back to the page</a></p></div>{{end}}{{end}}6465 65-{{/* wikiHistory lists every version of a page, newest first. */}}66+{{/* wikiHistory lists every revision of a page across authors, newest first. */}}6667 {{define "wikiHistory"}}{{$w := wikiView .Event}}<table id="rows">67-<thead><tr><th>author</th><th>version</th><th>notes</th><th>published</th></tr></thead>68+<thead><tr><th>author</th><th>revision</th><th>version</th><th>notes</th><th>published</th></tr></thead>6869 <tbody>69-{{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>70+{{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>7071 {{end}}</tbody>7172 </table>7273 <p><a href="{{wikiURL $w.D}}">back to the page</a></p>{{end}}7374 7475 {{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>7576 <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>76-{{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>77+{{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>7778 {{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>7879 {{end}}{{if $w.RedirectsTo}}<section><h4>Also known as</h4><ul>{{range $w.RedirectsTo}}<li><code>{{str (index . "d")}}</code></li>{{end}}</ul></section>7980 {{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}}@@ -18,6 +18,10 @@ const (
1818 wikiMerge = "3333333333333333333333333333333333333333333333333333333333333333"1919 wikiOwnerV = "4444444444444444444444444444444444444444444444444444444444444444"2020 wikiOtherV = "5555555555555555555555555555555555555555555555555555555555555555"21+ // wikiOwnerOld and wikiOtherOld are archived revisions that the22+ // current versions replaced.23+ wikiOwnerOld = "7777777777777777777777777777777777777777777777777777777777777777"24+ wikiOtherOld = "8888888888888888888888888888888888888888888888888888888888888888"2125 )2226 2327 // wikiBackend answers the three wiki queries with one page that has two@@ -29,11 +33,15 @@ type wikiBackend struct {
2933 private bool3034 // proposal makes the second author's version a proposal in this state3135 // (pending, approved or rejected), decided by the owner when it is not32- // pending. approver marks the caller as allowed to decide, and shown33- // makes the proposal the page's shown version.36+ // pending, with an approved earlier revision behind it in the history.37+ // approver marks the caller as allowed to decide, and shown makes the38+ // proposal the page's shown version.3439 proposal string3540 approver bool3641 shown bool42+ // archived gives the owner's version an earlier revision, which a43+ // request naming it by version opens.44+ archived bool3745 }3846 3947 func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawMessage, actor string) (any, error) {@@ -49,20 +57,31 @@ func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawM
4957 if b.private && actor == "" {5058 return nil, errors.New("auth-required: sign in to read this relay")5159 }52- 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),60+ 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),5361 "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"}}54- 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."}62+ 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."}5563 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"}56- shown := owner64+ shown, preferredBy := owner, "owner"65+ history := []any{other, owner}5766 if b.proposal != "" {58- other["proposal"], other["approval"] = true, b.proposal67+ other["proposal"], other["approval"], other["revision"], other["revisions"] = true, b.proposal, float64(2), float64(2)5968 if b.proposal != "pending" {6069 other["approval_event"], other["approval_at"], other["approval_by"] = wikiMerge, float64(1788882000), wikiOwner6170 }71+ 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}72+ history = []any{other, old, owner}6273 if b.shown {6374 shown = other6475 }6576 }77+ if b.archived {78+ owner["revision"], owner["revisions"] = float64(2), float64(2)79+ 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."}80+ history = append(history, old)81+ if object["version"] == wikiOwnerOld {82+ shown, preferredBy = old, "version"83+ }84+ }6685 switch method {6786 case "browsewiki":6887 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@@ -70,7 +89,7 @@ func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawM
7089 if object["d"] != "release-notes-1-4" {7190 return nil, errors.New("not found: wiki page")7291 }73- 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}, nil92+ 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}, nil7493 case "browsewikimerge":7594 return map[string]any{"merge": merge, "proposed": other, "target": owner}, nil7695 }@@ -151,7 +170,7 @@ func TestWikiPageRendersArticleMergeBarAndPanel(t *testing.T) {
151170 `<merge-request><nostr-name pubkey="`+wikiOther+`"`, `proposes a new version of this page`, `href="/wiki/release-notes-1-4?merge=`+wikiMerge+`">compare</a>`,152171 `<nostr-react event="`+wikiMerge+`" pubkey="`+wikiOther+`" kind="818"><button name="reaction" value="+">Accept</button><button name="reaction" value="-">Reject</button></nostr-react>`,153172 `<a href="/e/`+wikiMerge+`">Reply</a>`,154- `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`,173+ `<wiki-article><header>wiki | release-notes-1-4 | revision 1 by <nostr-name pubkey="`+wikiOwner+`"`, `version 1 of 2 | 1 fork</header>`,155174 `<h1>Highlights</h1>`, `<em>encrypted</em>`, `<a href="/wiki/files-and-private-repositories">Files and Private Repositories</a>`, `<script>alert(1)</script>`,156175 `<h4>Page</h4>`, `<th>versions</th><td>2</td>`, `<th>merge requests</th><td>1 open</td>`, `<th>links</th><td>1</td>`,157176 `<h4>Other versions</h4>`, `href="/wiki/release-notes-1-4?version=`+wikiOtherV+`">222222222222</a> | fork`,@@ -186,7 +205,44 @@ func TestWikiPageCompareEditorAndHistory(t *testing.T) {
186205 187206 path = "/wiki/release-notes-1-4?history=1"188207 body = wikiGet(t, wikiApp(t, b, wikiOwner), path)189- wantAll(t, path, body, `<th>author</th><th>version</th>`, `fork of 444444444444`, `href="/wiki/release-notes-1-4?version=`+wikiOwnerV+`">444444444444</a>`)208+ 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>`)209+ wantAll(t, path, body, `<th>revisions</th><td>2</td>`)210+ wantNone(t, path, body, "<small>replaced</small>")211+}212+213+func TestWikiHistoryListsRevisionsWithStates(t *testing.T) {214+ b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", approver: true, shown: true, archived: true}215+ path := "/wiki/release-notes-1-4?history=1"216+ body := wikiGet(t, wikiApp(t, b, wikiOwner), path)217+ wantAll(t, path, body,218+ `<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>`,219+ `<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>`,220+ `<td>2 <small>current</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOwnerV+`">444444444444</a></td>`,221+ `<td>1 <small>replaced</small></td><td><a href="/wiki/release-notes-1-4?version=`+wikiOwnerOld+`">777777777777</a></td><td> | 1 likes</td>`,222+ `<th>revisions</th><td>4</td>`)223+ // A member sees the rows without their proposal states.224+ b.approver = false225+ body = wikiGet(t, wikiApp(t, b, wikiMember), path)226+ 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>`)227+ wantNone(t, path, body, "<tr data-state=", "proposed ")228+}229+230+func TestWikiArchivedRevisionShowsANote(t *testing.T) {231+ b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, archived: true}232+ path := "/wiki/release-notes-1-4?version=" + wikiOwnerOld233+ body := wikiGet(t, wikiApp(t, b, wikiOther), path)234+ if b.params["browsewikipage"]["version"] != wikiOwnerOld {235+ t.Fatalf("version lookup params=%v", b.params["browsewikipage"])236+ }237+ wantAll(t, path, body,238+ `<wiki-article><header>wiki | release-notes-1-4 | revision 1 by <nostr-name pubkey="`+wikiOwner+`"`,239+ `<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>`,240+ `The first draft.`)241+ // The author's current revision carries no note.242+ path = "/wiki/release-notes-1-4"243+ body = wikiGet(t, wikiApp(t, b, wikiOther), path)244+ wantAll(t, path, body, `revision 2 by <nostr-name pubkey="`+wikiOwner+`"`, `<em>encrypted</em>`)245+ wantNone(t, path, body, `id="wiki-revision-note"`, "The first draft.")190246 }191247 192248 func TestWikiMissingPageOffersTheEditorToMembers(t *testing.T) {@@ -221,15 +277,22 @@ func TestWikiProposalBarOffersTheDecisionToApprovers(t *testing.T) {
221277 path := "/wiki/release-notes-1-4"222278 body := wikiGet(t, wikiApp(t, b, wikiOwner), path)223279 wantAll(t, path, body,224- `<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>`,225- `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOther+`"`, `A proposed rewrite.`)280+ `<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>`,281+ `<wiki-article><header>wiki | release-notes-1-4 | revision 2 by <nostr-name pubkey="`+wikiOther+`"`, `A proposed rewrite.`)226282 // A decided proposal names the decision, its time and the deciding key,227- // and offers no buttons.283+ // and offers no buttons. Readers still see the earlier approved284+ // revision while the edit is rejected, and the edit itself once it is285+ // approved.228286 for _, state := range []string{"approved", "rejected"} {229287 b.proposal = state230288 body = wikiGet(t, wikiApp(t, b, wikiOwner), path)231289 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+`"`)232290 wantNone(t, path, body, "<nostr-react", ">Accept<")291+ if state == "rejected" {292+ wantAll(t, path, body, `<small>showing rejected revision 2; readers see approved revision 1</small></wiki-proposal>`)293+ } else {294+ wantNone(t, path, body, "readers see approved revision")295+ }233296 }234297 // A pending proposal that is not the shown version is marked in the235298 // panel and the history, with no bar above the article.@@ -245,7 +308,7 @@ func TestWikiProposalAuthorSeesTheStateWithoutButtons(t *testing.T) {
245308 b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", shown: true}246309 path := "/wiki/release-notes-1-4"247310 body := wikiGet(t, wikiApp(t, b, wikiOther), path)248- wantAll(t, path, body, `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending</wiki-proposal>`)311+ 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>`)249312 wantNone(t, path, body, "<nostr-react", ">Accept<", ">Reject<")250313 body = wikiGet(t, wikiApp(t, b, wikiOther), "/wiki")251314 wantAll(t, "/wiki", body, `<tr data-state="pending"><td><a href="/wiki/release-notes-1-4">Release notes 1.4 <b></a> <small>proposed | pending</small></td>`)