| 1 | diff --git a/internal/webui/style.css b/internal/webui/style.css |
| 2 | index 9f5ee4a..40e8524 100644 |
| 3 | --- a/internal/webui/style.css |
| 4 | +++ b/internal/webui/style.css |
| 5 | @@ -226,8 +226,9 @@ diff-view td>nostr-event{white-space:normal;max-width:720px;border:0;padding:6px |
| 6 | wiki-article{display:block;max-width:720px;overflow-wrap:anywhere}wiki-article>header{font:12px var(--mono);color:var(--muted);margin-bottom:8px}wiki-article>header nostr-key,wiki-article>header nostr-name{color:var(--ink)} |
| 7 | wiki-article h1{font-size:20px;margin:18px 0 8px}wiki-article h2{font-size:17px}wiki-article h3{font-size:15px}wiki-article p{margin:0 0 10px} |
| 8 | wiki-article pre{background:var(--rail);border:1px solid var(--line);padding:10px 12px;overflow:auto;font-size:12.5px;line-height:1.5;white-space:pre}wiki-article ul,wiki-article ol{padding-left:20px;margin:0 0 10px} |
| 9 | -merge-request{display:block;border:1px solid var(--line);background:var(--soft);padding:8px 10px;margin:0 0 14px;max-width:720px;font:13px var(--sans)}merge-request nostr-key,merge-request nostr-name{color:var(--ink);font-weight:500}merge-request>a,merge-request button{margin-left:6px;font-size:12px} |
| 10 | -merge-request nostr-react,merge-request nostr-react form{display:inline}merge-request nostr-react output{display:inline-block;margin-left:6px;font:12px var(--mono);color:var(--muted)}merge-request nostr-react output[data-error]{color:var(--alert)} |
| 11 | +merge-request,wiki-proposal{display:block;border:1px solid var(--line);background:var(--soft);padding:8px 10px;margin:0 0 14px;max-width:720px;font:13px var(--sans)}merge-request nostr-key,merge-request nostr-name,wiki-proposal nostr-key,wiki-proposal nostr-name{color:var(--ink);font-weight:500}merge-request>a,merge-request button,wiki-proposal button{margin-left:6px;font-size:12px} |
| 12 | +merge-request nostr-react,merge-request nostr-react form,wiki-proposal nostr-react,wiki-proposal nostr-react form{display:inline}merge-request nostr-react output,wiki-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]{color:var(--alert)} |
| 13 | +wiki-proposal[data-state=pending]{border-color:var(--bark)}wiki-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)} |
| 14 | #merge-status{font:12px var(--mono);color:var(--muted)}#wiki-compare h2{margin-top:18px}#wiki-redirects{font:12px var(--mono);color:var(--muted)} |
| 15 | wiki-compose{display:block;max-width:720px;border-bottom:1px solid var(--line);padding:10px 0} |
| 16 | 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} |
| 17 | diff --git a/internal/webui/templates.go b/internal/webui/templates.go |
| 18 | index c984b56..5f9f499 100644 |
| 19 | --- a/internal/webui/templates.go |
| 20 | +++ b/internal/webui/templates.go |
| 21 | @@ -279,6 +279,7 @@ func parseTemplates() (*template.Template, error) { |
| 22 | "approvalDevices": approvalDevices, |
| 23 | "wikiHTML": wikiHTML, |
| 24 | "wikiView": wikiPageView, |
| 25 | + "wikiProposal": wikiProposal, |
| 26 | "wikiURL": wikiURL, |
| 27 | "roomItems": roomItems, |
| 28 | "roomRoot": roomRoot, |
| 29 | diff --git a/internal/webui/wiki.go b/internal/webui/wiki.go |
| 30 | index 27f45ef..eab4f49 100644 |
| 31 | --- a/internal/webui/wiki.go |
| 32 | +++ b/internal/webui/wiki.go |
| 33 | @@ -26,6 +26,34 @@ type wikiView struct { |
| 34 | OpenCount int |
| 35 | RedirectsTo []map[string]any |
| 36 | RedirectsFrom []map[string]any |
| 37 | + // CanApprove is set when the viewer may accept or reject proposals. |
| 38 | + CanApprove bool |
| 39 | +} |
| 40 | + |
| 41 | +// wikiProposalView is how a version's proposal state reads for one viewer. |
| 42 | +// Shown is set for the people who may see the state: the owner and |
| 43 | +// moderators, and the version's author. Decide is set when the viewer may |
| 44 | +// accept or reject it, which is only while it is pending. |
| 45 | +type wikiProposalView struct { |
| 46 | + Shown, Decide bool |
| 47 | + State string |
| 48 | + At any |
| 49 | + By string |
| 50 | +} |
| 51 | + |
| 52 | +// wikiProposal reads a version's proposal state for the viewer. canApprove |
| 53 | +// is the browse result's can_approve flag. |
| 54 | +func wikiProposal(version map[string]any, actor string, canApprove any) wikiProposalView { |
| 55 | + if version == nil || version["proposal"] != true { |
| 56 | + return wikiProposalView{} |
| 57 | + } |
| 58 | + approver, _ := canApprove.(bool) |
| 59 | + if !approver && (actor == "" || actor != plainString(version["author"])) { |
| 60 | + return wikiProposalView{} |
| 61 | + } |
| 62 | + view := wikiProposalView{Shown: true, State: plainString(version["approval"]), At: version["approval_at"], By: plainString(version["approval_by"])} |
| 63 | + view.Decide = approver && view.State == "pending" |
| 64 | + return view |
| 65 | } |
| 66 | |
| 67 | func wikiMaps(value any) []map[string]any { |
| 68 | @@ -49,6 +77,7 @@ 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.CanApprove, _ = page["can_approve"].(bool) |
| 73 | view.Count = len(view.Versions) |
| 74 | if current := valueMap(page["version"]); current != nil { |
| 75 | view.Version = current |
| 76 | diff --git a/internal/webui/wiki.html b/internal/webui/wiki.html |
| 77 | index 12c79c4..ac97c2e 100644 |
| 78 | --- a/internal/webui/wiki.html |
| 79 | +++ b/internal/webui/wiki.html |
| 80 | @@ -3,10 +3,10 @@ |
| 81 | <form method="get" action="/wiki" id="wiki-search"><label>Search <input name="q" value="{{.Query.Get "q"}}" placeholder="title or summary" autocomplete="off"></label> <button>Search</button>{{if .Query.Get "q"}} <a href="/wiki">clear</a>{{end}}</form> |
| 82 | {{if and .Actor (.Query.Get "new")}}<h2>New page</h2>{{template "wikiEditor" .}} |
| 83 | {{end}}{{if .Error}}{{template "alert" .Error}} |
| 84 | -{{else if .Feed}}<table id="rows"> |
| 85 | +{{else if .Feed}}{{$can := index (asMap .Event) "can_approve"}}<table id="rows"> |
| 86 | <thead><tr><th>page</th><th>author</th><th>versions</th><th>merge requests</th><th>updated</th></tr></thead> |
| 87 | <tbody> |
| 88 | -{{range .Feed}}{{$p := asMap .}}{{$v := asMap (index $p "version")}}<tr><td><a href="{{wikiURL (str (index $p "d"))}}">{{str (index $p "title")}}</a></td><td>{{template "nostrName" (str (index $v "author"))}}</td><td>{{str (index $p "versions")}}</td><td>{{str (index $p "open_merges")}} open</td><td>{{when (index $v "created_at")}}</td></tr> |
| 89 | +{{range .Feed}}{{$p := asMap .}}{{$v := asMap (index $p "version")}}{{$s := wikiProposal $v $.Actor $can}}<tr{{if $s.Shown}} data-state="{{$s.State}}"{{end}}><td><a href="{{wikiURL (str (index $p "d"))}}">{{str (index $p "title")}}</a>{{if $s.Shown}} <small>proposed | {{$s.State}}</small>{{end}}</td><td>{{template "nostrName" (str (index $v "author"))}}</td><td>{{str (index $p "versions")}}</td><td>{{str (index $p "open_merges")}} open</td><td>{{when (index $v "created_at")}}</td></tr> |
| 90 | {{end}}</tbody> |
| 91 | </table> |
| 92 | {{else}}<p>No pages yet.</p> |
| 93 | @@ -23,10 +23,11 @@ |
| 94 | <h1>{{$w.Title}}</h1> |
| 95 | {{template "alert" .Notice}}{{if not $w.Version}}<p>This name has no article of its own.</p> |
| 96 | {{template "wikiRedirects" $w}}{{if .Actor}}{{template "wikiEditor" .}}{{end}} |
| 97 | -{{else}}{{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> |
| 98 | +{{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> |
| 99 | +{{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> |
| 100 | {{end}}{{if .Query.Get "merge"}}{{template "wikiCompare" .}} |
| 101 | {{else if .Query.Get "edit"}}{{template "wikiEditor" .}} |
| 102 | -{{else if .Query.Get "history"}}{{template "wikiHistory" $w}} |
| 103 | +{{else if .Query.Get "history"}}{{template "wikiHistory" .}} |
| 104 | {{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> |
| 105 | {{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> |
| 106 | {{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> |
| 107 | @@ -62,17 +63,17 @@ |
| 108 | {{end}}<p><a href="{{wikiURL $w.D}}">back to the page</a></p></div>{{end}}{{end}} |
| 109 | |
| 110 | {{/* wikiHistory lists every version of a page, newest first. */}} |
| 111 | -{{define "wikiHistory"}}<table id="rows"> |
| 112 | +{{define "wikiHistory"}}{{$w := wikiView .Event}}<table id="rows"> |
| 113 | <thead><tr><th>author</th><th>version</th><th>notes</th><th>published</th></tr></thead> |
| 114 | <tbody> |
| 115 | -{{range .Versions}}<tr><td>{{template "nostrName" (str (index . "author"))}}</td><td><a href="{{wikiURL $.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}}</td><td>{{when (index . "created_at")}}</td></tr> |
| 116 | +{{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> |
| 117 | {{end}}</tbody> |
| 118 | </table> |
| 119 | -<p><a href="{{wikiURL .D}}">back to the page</a></p>{{end}} |
| 120 | +<p><a href="{{wikiURL $w.D}}">back to the page</a></p>{{end}} |
| 121 | |
| 122 | {{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> |
| 123 | <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> |
| 124 | {{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> |
| 125 | -{{if $w.Others}}<section><h4>Other versions</h4><ul>{{range $w.Others}}<li><a href="{{wikiURL $w.D "version" (str (index . "id"))}}">{{shortID (index . "author")}}</a>{{with asMap (index . "fork")}} | fork{{end}} <small>{{short (index . "created_at")}}</small></li>{{end}}</ul></section> |
| 126 | +{{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> |
| 127 | {{end}}{{if $w.RedirectsTo}}<section><h4>Also known as</h4><ul>{{range $w.RedirectsTo}}<li><code>{{str (index . "d")}}</code></li>{{end}}</ul></section> |
| 128 | {{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}} |
| 129 | diff --git a/internal/webui/wiki_test.go b/internal/webui/wiki_test.go |
| 130 | index 8278f53..fa3eb3b 100644 |
| 131 | --- a/internal/webui/wiki_test.go |
| 132 | +++ b/internal/webui/wiki_test.go |
| 133 | @@ -27,6 +27,13 @@ type wikiBackend struct { |
| 134 | calls []string |
| 135 | params map[string]map[string]any |
| 136 | private bool |
| 137 | + // proposal makes the second author's version a proposal in this state |
| 138 | + // (pending, approved or rejected), decided by the owner when it is not |
| 139 | + // pending. approver marks the caller as allowed to decide, and shown |
| 140 | + // makes the proposal the page's shown version. |
| 141 | + proposal string |
| 142 | + approver bool |
| 143 | + shown bool |
| 144 | } |
| 145 | |
| 146 | func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawMessage, actor string) (any, error) { |
| 147 | @@ -46,14 +53,24 @@ func (b *wikiBackend) Query(_ context.Context, method string, params []json.RawM |
| 148 | "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"}} |
| 149 | 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."} |
| 150 | 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"} |
| 151 | + shown := owner |
| 152 | + if b.proposal != "" { |
| 153 | + other["proposal"], other["approval"] = true, b.proposal |
| 154 | + if b.proposal != "pending" { |
| 155 | + other["approval_event"], other["approval_at"], other["approval_by"] = wikiMerge, float64(1788882000), wikiOwner |
| 156 | + } |
| 157 | + if b.shown { |
| 158 | + shown = other |
| 159 | + } |
| 160 | + } |
| 161 | switch method { |
| 162 | case "browsewiki": |
| 163 | - return map[string]any{"items": []any{map[string]any{"d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "version": owner, "versions": float64(2), "open_merges": float64(1)}}, "next_cursor": "release-notes-1-4"}, nil |
| 164 | + 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 |
| 165 | case "browsewikipage": |
| 166 | if object["d"] != "release-notes-1-4" { |
| 167 | return nil, errors.New("not found: wiki page") |
| 168 | } |
| 169 | - return map[string]any{"d": "release-notes-1-4", "title": "Release notes 1.4 <b>", "version": owner, "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{}}, nil |
| 170 | + 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 |
| 171 | case "browsewikimerge": |
| 172 | return map[string]any{"merge": merge, "proposed": other, "target": owner}, nil |
| 173 | } |
| 174 | @@ -196,3 +213,58 @@ func TestWikiGuestAccessFollowsTheReadsPolicy(t *testing.T) { |
| 175 | wantNone(t, path, body, `role="alert"`) |
| 176 | } |
| 177 | } |
| 178 | + |
| 179 | +const wikiMember = "6666666666666666666666666666666666666666666666666666666666666666" |
| 180 | + |
| 181 | +func TestWikiProposalBarOffersTheDecisionToApprovers(t *testing.T) { |
| 182 | + b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", approver: true, shown: true} |
| 183 | + path := "/wiki/release-notes-1-4" |
| 184 | + body := wikiGet(t, wikiApp(t, b, wikiOwner), path) |
| 185 | + wantAll(t, path, body, |
| 186 | + `<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>`, |
| 187 | + `<wiki-article><header>wiki | release-notes-1-4 | by <nostr-name pubkey="`+wikiOther+`"`, `A proposed rewrite.`) |
| 188 | + // A decided proposal names the decision, its time and the deciding key, |
| 189 | + // and offers no buttons. |
| 190 | + for _, state := range []string{"approved", "rejected"} { |
| 191 | + b.proposal = state |
| 192 | + body = wikiGet(t, wikiApp(t, b, wikiOwner), path) |
| 193 | + 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+`"`) |
| 194 | + wantNone(t, path, body, "<nostr-react", ">Accept<") |
| 195 | + } |
| 196 | + // A pending proposal that is not the shown version is marked in the |
| 197 | + // panel and the history, with no bar above the article. |
| 198 | + b.proposal, b.shown = "pending", false |
| 199 | + body = wikiGet(t, wikiApp(t, b, wikiOwner), path) |
| 200 | + wantAll(t, path, body, `<h4>Other versions</h4><ul><li data-state="pending"><a href="/wiki/release-notes-1-4?version=`+wikiOtherV+`">222222222222</a> | fork | proposed pending <small>`) |
| 201 | + wantNone(t, path, body, "<wiki-proposal") |
| 202 | + body = wikiGet(t, wikiApp(t, b, wikiOwner), path+"?history=1") |
| 203 | + wantAll(t, path, body, `<tr data-state="pending"><td><nostr-name pubkey="`+wikiOther+`"`, `fork of 444444444444 | proposed pending</td>`) |
| 204 | +} |
| 205 | + |
| 206 | +func TestWikiProposalAuthorSeesTheStateWithoutButtons(t *testing.T) { |
| 207 | + b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "pending", shown: true} |
| 208 | + path := "/wiki/release-notes-1-4" |
| 209 | + body := wikiGet(t, wikiApp(t, b, wikiOther), path) |
| 210 | + wantAll(t, path, body, `<wiki-proposal data-state="pending">Proposed by <nostr-name pubkey="`+wikiOther+`"`, `| pending</wiki-proposal>`) |
| 211 | + wantNone(t, path, body, "<nostr-react", ">Accept<", ">Reject<") |
| 212 | + body = wikiGet(t, wikiApp(t, b, wikiOther), "/wiki") |
| 213 | + 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>`) |
| 214 | +} |
| 215 | + |
| 216 | +func TestWikiProposalStateIsHiddenFromMembers(t *testing.T) { |
| 217 | + b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "approved", shown: true} |
| 218 | + for _, path := range []string{"/wiki/release-notes-1-4", "/wiki/release-notes-1-4?history=1", "/wiki"} { |
| 219 | + body := wikiGet(t, wikiApp(t, b, wikiMember), path) |
| 220 | + wantAll(t, path, body, "Release notes 1.4") |
| 221 | + wantNone(t, path, body, "<wiki-proposal", "<tr data-state=", "<li data-state=", "proposed | ", "| proposed ", "<nostr-react") |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +func TestWikiListMarksProposalRowsForApprovers(t *testing.T) { |
| 226 | + b := &wikiBackend{fakeBackend: fakeBackend{policy: policy.Defaults(wikiOwner)}, proposal: "rejected", approver: true, shown: true} |
| 227 | + body := wikiGet(t, wikiApp(t, b, wikiOwner), "/wiki") |
| 228 | + wantAll(t, "/wiki", body, `<tr data-state="rejected"><td><a href="/wiki/release-notes-1-4">Release notes 1.4 <b></a> <small>proposed | rejected</small></td><td><nostr-name pubkey="`+wikiOther+`"`) |
| 229 | + b.proposal = "" |
| 230 | + body = wikiGet(t, wikiApp(t, b, wikiOwner), "/wiki") |
| 231 | + wantNone(t, "/wiki", body, "<tr data-state=", "proposed | ") |
| 232 | +} |